diff options
Diffstat (limited to 'gfx/2d')
194 files changed, 75717 insertions, 0 deletions
diff --git a/gfx/2d/2D.h b/gfx/2d/2D.h new file mode 100644 index 0000000000..024723868b --- /dev/null +++ b/gfx/2d/2D.h @@ -0,0 +1,2318 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_2D_H +#define _MOZILLA_GFX_2D_H + +#include "Types.h" +#include "Point.h" +#include "Rect.h" +#include "Matrix.h" +#include "Quaternion.h" +#include "UserData.h" +#include "FontVariation.h" +#include <functional> +#include <vector> + +// GenericRefCountedBase allows us to hold on to refcounted objects of any type +// (contrary to RefCounted<T> which requires knowing the type T) and, in +// particular, without having a dependency on that type. This is used for +// DrawTargetSkia to be able to hold on to a GLContext. +#include "mozilla/GenericRefCounted.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Path.h" + +// This RefPtr class isn't ideal for usage in Azure, as it doesn't allow T** +// outparams using the &-operator. But it will have to do as there's no easy +// solution. +#include "mozilla/RefPtr.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/ThreadSafeWeakPtr.h" +#include "mozilla/Atomics.h" + +#include "mozilla/DebugOnly.h" + +#include "nsRegionFwd.h" + +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GTK) +# ifndef MOZ_ENABLE_FREETYPE +# define MOZ_ENABLE_FREETYPE +# endif +#endif + +struct _cairo_surface; +typedef _cairo_surface cairo_surface_t; + +struct _cairo_scaled_font; +typedef _cairo_scaled_font cairo_scaled_font_t; + +struct FT_LibraryRec_; +typedef FT_LibraryRec_* FT_Library; + +struct FT_FaceRec_; +typedef FT_FaceRec_* FT_Face; + +typedef int FT_Error; + +struct _FcPattern; +typedef _FcPattern FcPattern; + +struct ID3D11Texture2D; +struct ID3D11Device; +struct ID2D1Device; +struct ID2D1DeviceContext; +struct ID2D1Multithread; +struct IDWriteFactory; +struct IDWriteRenderingParams; +struct IDWriteFontFace; +struct IDWriteFontCollection; + +class SkCanvas; +struct gfxFontStyle; + +struct CGContext; +typedef struct CGContext* CGContextRef; + +struct CGFont; +typedef CGFont* CGFontRef; + +namespace mozilla { + +class Mutex; + +namespace layers { +class MemoryOrShmem; +class SurfaceDescriptorBuffer; +class TextureData; +} // namespace layers + +namespace wr { +struct FontInstanceOptions; +struct FontInstancePlatformOptions; +} // namespace wr + +namespace gfx { +class UnscaledFont; +class ScaledFont; +} // namespace gfx + +namespace gfx { + +class AlphaBoxBlur; +class ScaledFont; +class SourceSurface; +class DataSourceSurface; +class DrawTarget; +class DrawEventRecorder; +class FilterNode; +class LogForwarder; + +struct NativeSurface { + NativeSurfaceType mType; + SurfaceFormat mFormat; + gfx::IntSize mSize; + void* mSurface; +}; + +/** + * This structure is used to send draw options that are universal to all drawing + * operations. + */ +struct DrawOptions { + /// For constructor parameter description, see member data documentation. + explicit DrawOptions(Float aAlpha = 1.0f, + CompositionOp aCompositionOp = CompositionOp::OP_OVER, + AntialiasMode aAntialiasMode = AntialiasMode::DEFAULT) + : mAlpha(aAlpha), + mCompositionOp(aCompositionOp), + mAntialiasMode(aAntialiasMode) {} + + Float mAlpha; /**< Alpha value by which the mask generated by this + operation is multiplied. */ + CompositionOp mCompositionOp; /**< The operator that indicates how the source + and destination patterns are blended. */ + AntialiasMode mAntialiasMode; /**< The AntiAlias mode used for this drawing + operation. */ +}; + +struct StoredStrokeOptions; + +/** + * This structure is used to send stroke options that are used in stroking + * operations. + */ +struct StrokeOptions { + /// For constructor parameter description, see member data documentation. + explicit StrokeOptions(Float aLineWidth = 1.0f, + JoinStyle aLineJoin = JoinStyle::MITER_OR_BEVEL, + CapStyle aLineCap = CapStyle::BUTT, + Float aMiterLimit = 10.0f, size_t aDashLength = 0, + const Float* aDashPattern = 0, Float aDashOffset = 0.f) + : mLineWidth(aLineWidth), + mMiterLimit(aMiterLimit), + mDashPattern(aDashLength > 0 ? aDashPattern : 0), + mDashLength(aDashLength), + mDashOffset(aDashOffset), + mLineJoin(aLineJoin), + mLineCap(aLineCap) { + MOZ_ASSERT(aDashLength == 0 || aDashPattern); + } + + Float mLineWidth; //!< Width of the stroke in userspace. + Float mMiterLimit; //!< Miter limit in units of linewidth + const Float* mDashPattern; /**< Series of on/off userspace lengths defining + dash. Owned by the caller; must live at least as + long as this StrokeOptions. + mDashPattern != null <=> mDashLength > 0. */ + size_t mDashLength; //!< Number of on/off lengths in mDashPattern. + Float mDashOffset; /**< Userspace offset within mDashPattern at which + stroking begins. */ + JoinStyle mLineJoin; //!< Join style used for joining lines. + CapStyle mLineCap; //!< Cap style used for capping lines. + + StoredStrokeOptions* Clone() const; + + bool operator==(const StrokeOptions& aOther) const { + return mLineWidth == aOther.mLineWidth && + mMiterLimit == aOther.mMiterLimit && + mDashLength == aOther.mDashLength && + (!mDashLength || (mDashPattern && aOther.mDashPattern && + !memcmp(mDashPattern, aOther.mDashPattern, + mDashLength * sizeof(Float)))) && + mDashOffset == aOther.mDashOffset && mLineJoin == aOther.mLineJoin && + mLineCap == aOther.mLineCap; + } +}; + +/** + * Heap-allocated variation of StrokeOptions that ensures dash patterns are + * properly allocated and destroyed even if the source was stack-allocated. + */ +struct StoredStrokeOptions : public StrokeOptions { + explicit StoredStrokeOptions(const StrokeOptions& aOptions) + : StrokeOptions(aOptions) { + if (mDashLength) { + Float* pattern = new Float[mDashLength]; + memcpy(pattern, mDashPattern, mDashLength * sizeof(Float)); + mDashPattern = pattern; + } + } + + ~StoredStrokeOptions() { + if (mDashPattern) { + delete[] mDashPattern; + } + } +}; + +inline StoredStrokeOptions* StrokeOptions::Clone() const { + return new StoredStrokeOptions(*this); +} + +/** + * This structure supplies additional options for calls to DrawSurface. + */ +struct DrawSurfaceOptions { + /// For constructor parameter description, see member data documentation. + explicit DrawSurfaceOptions( + SamplingFilter aSamplingFilter = SamplingFilter::LINEAR, + SamplingBounds aSamplingBounds = SamplingBounds::UNBOUNDED) + : mSamplingFilter(aSamplingFilter), mSamplingBounds(aSamplingBounds) {} + + SamplingFilter + mSamplingFilter; /**< SamplingFilter used when resampling source surface + region to the destination region. */ + SamplingBounds mSamplingBounds; /**< This indicates whether the implementation + is allowed to sample pixels outside the + source rectangle as specified in + DrawSurface on the surface. */ +}; + +/** + * ShadowOptions supplies options necessary for describing the appearance of a + * a shadow in draw calls that use shadowing. + */ +struct ShadowOptions { + explicit ShadowOptions(const DeviceColor& aColor = DeviceColor(0.0f, 0.0f, + 0.0f), + const Point& aOffset = Point(), Float aSigma = 0.0f) + : mColor(aColor), mOffset(aOffset), mSigma(aSigma) {} + + DeviceColor mColor; /**< Color of the drawn shadow. */ + Point mOffset; /**< Offset of the shadow. */ + Float mSigma; /**< Sigma used for the Gaussian filter kernel. */ + + int32_t BlurRadius() const; +}; + +/** + * This class is used to store gradient stops, it can only be used with a + * matching DrawTarget. Not adhering to this condition will make a draw call + * fail. + */ +class GradientStops : public SupportsThreadSafeWeakPtr<GradientStops> { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStops) + virtual ~GradientStops() = default; + + virtual BackendType GetBackendType() const = 0; + virtual bool IsValid() const { return true; } + + protected: + GradientStops() = default; +}; + +/** + * This is the base class for 'patterns'. Patterns describe the pixels used as + * the source for a masked composition operation that is done by the different + * drawing commands. These objects are not backend specific, however for + * example the gradient stops on a gradient pattern can be backend specific. + */ +class Pattern { + public: + virtual ~Pattern() = default; + + virtual PatternType GetType() const = 0; + + /** Instantiate a new clone with the same pattern type and values. Any + * internal strong references will be converted to weak references. */ + virtual Pattern* CloneWeak() const { return nullptr; } + + /** Whether the pattern holds an internal weak reference. */ + virtual bool IsWeak() const { return false; } + + /** Whether any internal weak references still point to a target. */ + virtual bool IsValid() const { return true; } + + /** Determine if the pattern type and values exactly match. */ + virtual bool operator==(const Pattern& aOther) const = 0; + + bool operator!=(const Pattern& aOther) const { return !(*this == aOther); } + + protected: + Pattern() = default; + + // Utility functions to check if a weak reference is still valid. + template <typename T> + static inline bool IsRefValid(const RefPtr<T>& aPtr) { + // RefPtrs are always valid. + return true; + } + + template <typename T> + static inline bool IsRefValid(const ThreadSafeWeakPtr<T>& aPtr) { + // Weak refs are only valid if they aren't dead. + return !aPtr.IsDead(); + } +}; + +class ColorPattern : public Pattern { + public: + // Explicit because consumers should generally use ToDeviceColor when + // creating a ColorPattern. + explicit ColorPattern(const DeviceColor& aColor) : mColor(aColor) {} + + PatternType GetType() const override { return PatternType::COLOR; } + + Pattern* CloneWeak() const override { return new ColorPattern(mColor); } + + bool operator==(const Pattern& aOther) const override { + if (aOther.GetType() != PatternType::COLOR) { + return false; + } + const ColorPattern& other = static_cast<const ColorPattern&>(aOther); + return mColor == other.mColor; + } + + DeviceColor mColor; +}; + +/** + * This class is used for Linear Gradient Patterns, the gradient stops are + * stored in a separate object and are backend dependent. This class itself + * may be used on the stack. + */ +template <template <typename> typename REF = RefPtr> +class LinearGradientPatternT : public Pattern { + typedef LinearGradientPatternT<ThreadSafeWeakPtr> Weak; + + public: + /// For constructor parameter description, see member data documentation. + LinearGradientPatternT(const Point& aBegin, const Point& aEnd, + RefPtr<GradientStops> aStops, + const Matrix& aMatrix = Matrix()) + : mBegin(aBegin), + mEnd(aEnd), + mStops(std::move(aStops)), + mMatrix(aMatrix) {} + + PatternType GetType() const override { return PatternType::LINEAR_GRADIENT; } + + Pattern* CloneWeak() const override { + return new Weak(mBegin, mEnd, do_AddRef(mStops), mMatrix); + } + + bool IsWeak() const override { + return std::is_same<decltype(*this), Weak>::value; + } + + bool IsValid() const override { return IsRefValid(mStops); } + + template <template <typename> typename T> + bool operator==(const LinearGradientPatternT<T>& aOther) const { + return mBegin == aOther.mBegin && mEnd == aOther.mEnd && + mStops == aOther.mStops && mMatrix.ExactlyEquals(aOther.mMatrix); + } + + bool operator==(const Pattern& aOther) const override { + if (aOther.GetType() != PatternType::LINEAR_GRADIENT) { + return false; + } + return aOther.IsWeak() + ? *this == static_cast<const Weak&>(aOther) + : *this == static_cast<const LinearGradientPatternT<>&>(aOther); + } + + Point mBegin; //!< Start of the linear gradient + Point mEnd; /**< End of the linear gradient - NOTE: In the case + of a zero length gradient it will act as the + color of the last stop. */ + REF<GradientStops> mStops; /**< GradientStops object for this gradient, this + should match the backend type of the draw + target this pattern will be used with. */ + Matrix mMatrix; /**< A matrix that transforms the pattern into + user space */ +}; + +typedef LinearGradientPatternT<> LinearGradientPattern; + +/** + * This class is used for Radial Gradient Patterns, the gradient stops are + * stored in a separate object and are backend dependent. This class itself + * may be used on the stack. + */ +template <template <typename> typename REF = RefPtr> +class RadialGradientPatternT : public Pattern { + typedef RadialGradientPatternT<ThreadSafeWeakPtr> Weak; + + public: + /// For constructor parameter description, see member data documentation. + RadialGradientPatternT(const Point& aCenter1, const Point& aCenter2, + Float aRadius1, Float aRadius2, + RefPtr<GradientStops> aStops, + const Matrix& aMatrix = Matrix()) + : mCenter1(aCenter1), + mCenter2(aCenter2), + mRadius1(aRadius1), + mRadius2(aRadius2), + mStops(std::move(aStops)), + mMatrix(aMatrix) {} + + PatternType GetType() const override { return PatternType::RADIAL_GRADIENT; } + + Pattern* CloneWeak() const override { + return new Weak(mCenter1, mCenter2, mRadius1, mRadius2, do_AddRef(mStops), + mMatrix); + } + + bool IsWeak() const override { + return std::is_same<decltype(*this), Weak>::value; + } + + bool IsValid() const override { return IsRefValid(mStops); } + + template <template <typename> typename T> + bool operator==(const RadialGradientPatternT<T>& aOther) const { + return mCenter1 == aOther.mCenter1 && mCenter2 == aOther.mCenter2 && + mRadius1 == aOther.mRadius1 && mRadius2 == aOther.mRadius2 && + mStops == aOther.mStops && mMatrix.ExactlyEquals(aOther.mMatrix); + } + + bool operator==(const Pattern& aOther) const override { + if (aOther.GetType() != PatternType::RADIAL_GRADIENT) { + return false; + } + return aOther.IsWeak() + ? *this == static_cast<const Weak&>(aOther) + : *this == static_cast<const RadialGradientPatternT<>&>(aOther); + } + + Point mCenter1; //!< Center of the inner (focal) circle. + Point mCenter2; //!< Center of the outer circle. + Float mRadius1; //!< Radius of the inner (focal) circle. + Float mRadius2; //!< Radius of the outer circle. + REF<GradientStops> mStops; /**< GradientStops object for this gradient, this + should match the backend type of the draw + target this pattern will be used with. */ + Matrix mMatrix; //!< A matrix that transforms the pattern into user space +}; + +typedef RadialGradientPatternT<> RadialGradientPattern; + +/** + * This class is used for Conic Gradient Patterns, the gradient stops are + * stored in a separate object and are backend dependent. This class itself + * may be used on the stack. + */ +template <template <typename> typename REF = RefPtr> +class ConicGradientPatternT : public Pattern { + typedef ConicGradientPatternT<ThreadSafeWeakPtr> Weak; + + public: + /// For constructor parameter description, see member data documentation. + ConicGradientPatternT(const Point& aCenter, Float aAngle, Float aStartOffset, + Float aEndOffset, RefPtr<GradientStops> aStops, + const Matrix& aMatrix = Matrix()) + : mCenter(aCenter), + mAngle(aAngle), + mStartOffset(aStartOffset), + mEndOffset(aEndOffset), + mStops(std::move(aStops)), + mMatrix(aMatrix) {} + + PatternType GetType() const override { return PatternType::CONIC_GRADIENT; } + + Pattern* CloneWeak() const override { + return new Weak(mCenter, mAngle, mStartOffset, mEndOffset, + do_AddRef(mStops), mMatrix); + } + + bool IsWeak() const override { + return std::is_same<decltype(*this), Weak>::value; + } + + bool IsValid() const override { return IsRefValid(mStops); } + + template <template <typename> typename T> + bool operator==(const ConicGradientPatternT<T>& aOther) const { + return mCenter == aOther.mCenter && mAngle == aOther.mAngle && + mStartOffset == aOther.mStartOffset && + mEndOffset == aOther.mEndOffset && mStops == aOther.mStops && + mMatrix.ExactlyEquals(aOther.mMatrix); + } + + bool operator==(const Pattern& aOther) const override { + if (aOther.GetType() != PatternType::CONIC_GRADIENT) { + return false; + } + return aOther.IsWeak() + ? *this == static_cast<const Weak&>(aOther) + : *this == static_cast<const ConicGradientPatternT<>&>(aOther); + } + + Point mCenter; //!< Center of the gradient + Float mAngle; //!< Start angle of gradient + Float mStartOffset; // Offset of first stop + Float mEndOffset; // Offset of last stop + REF<GradientStops> mStops; /**< GradientStops object for this gradient, this + should match the backend type of the draw + target this pattern will be used with. */ + Matrix mMatrix; //!< A matrix that transforms the pattern into user space +}; + +typedef ConicGradientPatternT<> ConicGradientPattern; + +/** + * This class is used for Surface Patterns, they wrap a surface and a + * repetition mode for the surface. This may be used on the stack. + */ +template <template <typename> typename REF = RefPtr> +class SurfacePatternT : public Pattern { + typedef SurfacePatternT<ThreadSafeWeakPtr> Weak; + + public: + /// For constructor parameter description, see member data documentation. + SurfacePatternT(RefPtr<SourceSurface> aSourceSurface, ExtendMode aExtendMode, + const Matrix& aMatrix = Matrix(), + SamplingFilter aSamplingFilter = SamplingFilter::GOOD, + const IntRect& aSamplingRect = IntRect()) + : mSurface(std::move(aSourceSurface)), + mExtendMode(aExtendMode), + mSamplingFilter(aSamplingFilter), + mMatrix(aMatrix), + mSamplingRect(aSamplingRect) {} + + PatternType GetType() const override { return PatternType::SURFACE; } + + Pattern* CloneWeak() const override { + return new Weak(do_AddRef(mSurface), mExtendMode, mMatrix, mSamplingFilter, + mSamplingRect); + } + + bool IsWeak() const override { + return std::is_same<decltype(*this), Weak>::value; + } + + bool IsValid() const override { return IsRefValid(mSurface); } + + template <template <typename> typename T> + bool operator==(const SurfacePatternT<T>& aOther) const { + return mSurface == aOther.mSurface && mExtendMode == aOther.mExtendMode && + mSamplingFilter == aOther.mSamplingFilter && + mMatrix.ExactlyEquals(aOther.mMatrix) && + mSamplingRect.IsEqualEdges(aOther.mSamplingRect); + } + + bool operator==(const Pattern& aOther) const override { + if (aOther.GetType() != PatternType::SURFACE) { + return false; + } + return aOther.IsWeak() + ? *this == static_cast<const Weak&>(aOther) + : *this == static_cast<const SurfacePatternT<>&>(aOther); + } + + REF<SourceSurface> mSurface; //!< Surface to use for drawing + ExtendMode mExtendMode; /**< This determines how the image is extended + outside the bounds of the image */ + SamplingFilter + mSamplingFilter; //!< Resampling filter for resampling the image. + Matrix mMatrix; //!< Transforms the pattern into user space + + IntRect mSamplingRect; /**< Rect that must not be sampled outside of, + or an empty rect if none has been + specified. */ +}; + +typedef SurfacePatternT<> SurfacePattern; + +class StoredPattern; + +static const int32_t kReasonableSurfaceSize = 8192; + +/** + * This is the base class for source surfaces. These objects are surfaces + * which may be used as a source in a SurfacePattern or a DrawSurface call. + * They cannot be drawn to directly. + * + * Although SourceSurface has thread-safe refcount, some SourceSurface cannot + * be used on random threads at the same time. Only DataSourceSurface can be + * used on random threads now. This will be fixed in the future. Eventually + * all SourceSurface should be thread-safe. + */ +class SourceSurface : public SupportsThreadSafeWeakPtr<SourceSurface> { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurface) + virtual ~SourceSurface() = default; + + virtual SurfaceType GetType() const = 0; + virtual IntSize GetSize() const = 0; + /* GetRect is useful for when the underlying surface doesn't actually + * have a backing store starting at 0, 0. e.g. SourceSurfaceOffset */ + virtual IntRect GetRect() const { return IntRect(IntPoint(0, 0), GetSize()); } + virtual SurfaceFormat GetFormat() const = 0; + + /** + * Structure containing memory size information for the surface. + */ + struct SizeOfInfo { + SizeOfInfo() + : mHeapBytes(0), + mNonHeapBytes(0), + mUnknownBytes(0), + mExternalHandles(0), + mExternalId(0), + mTypes(0) {} + + void Accumulate(const SizeOfInfo& aOther) { + mHeapBytes += aOther.mHeapBytes; + mNonHeapBytes += aOther.mNonHeapBytes; + mUnknownBytes += aOther.mUnknownBytes; + mExternalHandles += aOther.mExternalHandles; + if (aOther.mExternalId) { + mExternalId = aOther.mExternalId; + } + mTypes |= aOther.mTypes; + } + + void AddType(SurfaceType aType) { mTypes |= 1 << uint32_t(aType); } + + size_t mHeapBytes; // Bytes allocated on the heap. + size_t mNonHeapBytes; // Bytes allocated off the heap. + size_t mUnknownBytes; // Bytes allocated to either, but unknown. + size_t mExternalHandles; // Open handles for the surface. + uint64_t mExternalId; // External ID for WebRender, if available. + uint32_t mTypes; // Bit shifted values representing SurfaceType. + }; + + /** + * Get the size information of the underlying data buffer. + */ + virtual void SizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + SizeOfInfo& aInfo) const { + // Default is to estimate the footprint based on its size/format. + auto size = GetSize(); + auto format = GetFormat(); + aInfo.AddType(GetType()); + aInfo.mUnknownBytes = size.width * size.height * BytesPerPixel(format); + } + + /** This returns false if some event has made this source surface invalid for + * usage with current DrawTargets. For example in the case of Direct2D this + * could return false if we have switched devices since this surface was + * created. + */ + virtual bool IsValid() const { return true; } + + /** + * This returns true if it is the same underlying surface data, even if + * the objects are different (e.g. indirection due to + * DataSourceSurfaceWrapper). + */ + virtual bool Equals(SourceSurface* aOther, bool aSymmetric = true) { + return this == aOther || + (aSymmetric && aOther && aOther->Equals(this, false)); + } + + /** + * This function will return true if the surface type matches that of a + * DataSourceSurface and if GetDataSurface will return the same object. + */ + bool IsDataSourceSurface() const { + switch (GetType()) { + case SurfaceType::DATA: + case SurfaceType::DATA_SHARED: + case SurfaceType::DATA_RECYCLING_SHARED: + case SurfaceType::DATA_ALIGNED: + case SurfaceType::DATA_SHARED_WRAPPER: + case SurfaceType::DATA_MAPPED: + case SurfaceType::SKIA: + case SurfaceType::WEBGL: + return true; + default: + return false; + } + } + + /** + * This function will get a DataSourceSurface for this surface, a + * DataSourceSurface's data can be accessed directly. + */ + virtual already_AddRefed<DataSourceSurface> GetDataSurface() = 0; + + /** This function will return a SourceSurface without any offset. */ + virtual already_AddRefed<SourceSurface> GetUnderlyingSurface() { + RefPtr<SourceSurface> surface = this; + return surface.forget(); + } + + /** Tries to get this SourceSurface's native surface. This will fail if aType + * is not the type of this SourceSurface's native surface. + */ + virtual void* GetNativeSurface(NativeSurfaceType aType) { return nullptr; } + + void AddUserData(UserDataKey* key, void* userData, void (*destroy)(void*)) { + mUserData.Add(key, userData, destroy); + } + void* GetUserData(UserDataKey* key) const { return mUserData.Get(key); } + void RemoveUserData(UserDataKey* key) { mUserData.RemoveAndDestroy(key); } + + /** Tries to extract an optimal subrect for the surface. This may fail if the + * request can't be satisfied. + */ + virtual already_AddRefed<SourceSurface> ExtractSubrect(const IntRect& aRect) { + return nullptr; + } + + protected: + friend class StoredPattern; + + ThreadSafeUserData mUserData; +}; + +class DataSourceSurface : public SourceSurface { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurface, override) + DataSourceSurface() : mMapCount(0) {} + +#ifdef DEBUG + virtual ~DataSourceSurface() { MOZ_ASSERT(mMapCount == 0); } +#endif + + struct MappedSurface { + uint8_t* mData = nullptr; + int32_t mStride = 0; + }; + + enum MapType { READ, WRITE, READ_WRITE }; + + /** + * This is a scoped version of Map(). Map() is called in the constructor and + * Unmap() in the destructor. Use this for automatic unmapping of your data + * surfaces. + * + * Use IsMapped() to verify whether Map() succeeded or not. + */ + class ScopedMap final { + public: + ScopedMap(DataSourceSurface* aSurface, MapType aType) + : mSurface(aSurface), mIsMapped(aSurface->Map(aType, &mMap)) {} + + ScopedMap(ScopedMap&& aOther) + : mSurface(std::move(aOther.mSurface)), + mMap(aOther.mMap), + mIsMapped(aOther.mIsMapped) { + aOther.mMap.mData = nullptr; + aOther.mIsMapped = false; + } + + ScopedMap& operator=(ScopedMap&& aOther) { + if (mIsMapped) { + mSurface->Unmap(); + } + mSurface = std::move(aOther.mSurface); + mMap = aOther.mMap; + mIsMapped = aOther.mIsMapped; + aOther.mMap.mData = nullptr; + aOther.mIsMapped = false; + return *this; + } + + ~ScopedMap() { + if (mIsMapped) { + mSurface->Unmap(); + } + } + + uint8_t* GetData() const { + MOZ_ASSERT(mIsMapped); + return mMap.mData; + } + + int32_t GetStride() const { + MOZ_ASSERT(mIsMapped); + return mMap.mStride; + } + + const MappedSurface* GetMappedSurface() const { + MOZ_ASSERT(mIsMapped); + return &mMap; + } + + const DataSourceSurface* GetSurface() const { + MOZ_ASSERT(mIsMapped); + return mSurface; + } + + bool IsMapped() const { return mIsMapped; } + + private: + ScopedMap(const ScopedMap& aOther) = delete; + ScopedMap& operator=(const ScopedMap& aOther) = delete; + + RefPtr<DataSourceSurface> mSurface; + MappedSurface mMap; + bool mIsMapped; + }; + + SurfaceType GetType() const override { return SurfaceType::DATA; } + /** @deprecated + * Get the raw bitmap data of the surface. + * Can return null if there was OOM allocating surface data. + * + * Deprecated means you shouldn't be using this!! Use Map instead. + * Please deny any reviews which add calls to this! + */ + virtual uint8_t* GetData() = 0; + + /** @deprecated + * Stride of the surface, distance in bytes between the start of the image + * data belonging to row y and row y+1. This may be negative. + * Can return 0 if there was OOM allocating surface data. + */ + virtual int32_t Stride() = 0; + + /** + * The caller is responsible for ensuring aMappedSurface is not null. + // Althought Map (and Moz2D in general) isn't normally threadsafe, + // we want to allow it for SourceSurfaceRawData since it should + // always be fine (for reading at least). + // + // This is the same as the base class implementation except using + // mMapCount instead of mIsMapped since that breaks for multithread. + // + // Once mfbt supports Monitors we should implement proper read/write + // locking to prevent write races. + */ + virtual bool Map(MapType, MappedSurface* aMappedSurface) { + aMappedSurface->mData = GetData(); + aMappedSurface->mStride = Stride(); + bool success = !!aMappedSurface->mData; + if (success) { + mMapCount++; + } + return success; + } + + virtual void Unmap() { + mMapCount--; + MOZ_ASSERT(mMapCount >= 0); + } + + /** + * Returns a DataSourceSurface with the same data as this one, but + * guaranteed to have surface->GetType() == SurfaceType::DATA. + * + * The returning surface might be null, because of OOM or gfx device reset. + * The caller needs to do null-check before using it. + */ + already_AddRefed<DataSourceSurface> GetDataSurface() override; + + /** + * Returns whether or not the data was allocated on the heap. This should + * be used to determine if the memory needs to be cleared to 0. + */ + virtual bool OnHeap() const { return true; } + + /** + * Yields a dirty rect of what has changed since it was last called. + */ + virtual Maybe<IntRect> TakeDirtyRect() { return Nothing(); } + + /** + * Indicate a region which has changed in the surface. + */ + virtual void Invalidate(const IntRect& aDirtyRect) {} + + protected: + Atomic<int32_t> mMapCount; +}; + +/** This is an abstract object that accepts path segments. */ +class PathSink : public RefCounted<PathSink> { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathSink) + virtual ~PathSink() = default; + + /** Move the current point in the path, any figure currently being drawn will + * be considered closed during fill operations, however when stroking the + * closing line segment will not be drawn. + */ + virtual void MoveTo(const Point& aPoint) = 0; + /** Add a linesegment to the current figure */ + virtual void LineTo(const Point& aPoint) = 0; + /** Add a cubic bezier curve to the current figure */ + virtual void BezierTo(const Point& aCP1, const Point& aCP2, + const Point& aCP3) = 0; + /** Add a quadratic bezier curve to the current figure */ + virtual void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) = 0; + /** Close the current figure, this will essentially generate a line segment + * from the current point to the starting point for the current figure + */ + virtual void Close() = 0; + /** Add an arc to the current figure */ + virtual void Arc(const Point& aOrigin, float aRadius, float aStartAngle, + float aEndAngle, bool aAntiClockwise = false) = 0; + + virtual Point CurrentPoint() const { return mCurrentPoint; } + + virtual Point BeginPoint() const { return mBeginPoint; } + + virtual void SetCurrentPoint(const Point& aPoint) { mCurrentPoint = aPoint; } + + virtual void SetBeginPoint(const Point& aPoint) { mBeginPoint = aPoint; } + + protected: + /** Point the current subpath is at - or where the next subpath will start + * if there is no active subpath. + */ + Point mCurrentPoint; + + /** Position of the previous MoveTo operation. */ + Point mBeginPoint; +}; + +class PathBuilder; +class FlattenedPath; + +/** The path class is used to create (sets of) figures of any shape that can be + * filled or stroked to a DrawTarget + */ +class Path : public external::AtomicRefCounted<Path> { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(Path) + virtual ~Path(); + + virtual BackendType GetBackendType() const = 0; + + /** This returns a PathBuilder object that contains a copy of the contents of + * this path and is still writable. + */ + inline already_AddRefed<PathBuilder> CopyToBuilder() const { + return CopyToBuilder(GetFillRule()); + } + inline already_AddRefed<PathBuilder> TransformedCopyToBuilder( + const Matrix& aTransform) const { + return TransformedCopyToBuilder(aTransform, GetFillRule()); + } + /** This returns a PathBuilder object that contains a copy of the contents of + * this path, converted to use the specified FillRule, and still writable. + */ + virtual already_AddRefed<PathBuilder> CopyToBuilder( + FillRule aFillRule) const = 0; + virtual already_AddRefed<PathBuilder> TransformedCopyToBuilder( + const Matrix& aTransform, FillRule aFillRule) const = 0; + + /** This function checks if a point lies within a path. It allows passing a + * transform that will transform the path to the coordinate space in which + * aPoint is given. + */ + virtual bool ContainsPoint(const Point& aPoint, + const Matrix& aTransform) const = 0; + + /** This function checks if a point lies within the stroke of a path using the + * specified strokeoptions. It allows passing a transform that will transform + * the path to the coordinate space in which aPoint is given. + */ + virtual bool StrokeContainsPoint(const StrokeOptions& aStrokeOptions, + const Point& aPoint, + const Matrix& aTransform) const = 0; + + /** This functions gets the bounds of this path. These bounds are not + * guaranteed to be tight. A transform may be specified that gives the bounds + * after application of the transform. + */ + virtual Rect GetBounds(const Matrix& aTransform = Matrix()) const = 0; + + /** This function gets the bounds of the stroke of this path using the + * specified strokeoptions. These bounds are not guaranteed to be tight. + * A transform may be specified that gives the bounds after application of + * the transform. + */ + virtual Rect GetStrokedBounds(const StrokeOptions& aStrokeOptions, + const Matrix& aTransform = Matrix()) const = 0; + + /** Gets conservative bounds for the path, optionally stroked or transformed. + * This function will prioritize speed of computation over tightness of the + * computed bounds if the backend supports the distinction. + */ + virtual Rect GetFastBounds( + const Matrix& aTransform = Matrix(), + const StrokeOptions* aStrokeOptions = nullptr) const; + + /** Take the contents of this path and stream it to another sink, this works + * regardless of the backend that might be used for the destination sink. + */ + virtual void StreamToSink(PathSink* aSink) const = 0; + + /** This gets the fillrule this path's builder was created with. This is not + * mutable. + */ + virtual FillRule GetFillRule() const = 0; + + virtual Float ComputeLength(); + + virtual Maybe<Rect> AsRect() const { return Nothing(); } + + virtual Point ComputePointAtLength(Float aLength, Point* aTangent = nullptr); + + virtual bool IsEmpty() const = 0; + + protected: + Path(); + void EnsureFlattenedPath(); + + RefPtr<FlattenedPath> mFlattenedPath; +}; + +/** The PathBuilder class allows path creation. Once finish is called on the + * pathbuilder it may no longer be written to. + */ +class PathBuilder : public PathSink { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilder, override) + /** Finish writing to the path and return a Path object that can be used for + * drawing. Future use of the builder results in a crash! + */ + virtual already_AddRefed<Path> Finish() = 0; + + virtual BackendType GetBackendType() const = 0; + + virtual bool IsActive() const = 0; +}; + +struct Glyph { + uint32_t mIndex; + Point mPosition; +}; + +static inline bool operator==(const Glyph& aOne, const Glyph& aOther) { + return aOne.mIndex == aOther.mIndex && aOne.mPosition == aOther.mPosition; +} + +/** This class functions as a glyph buffer that can be drawn to a DrawTarget. + * @todo XXX - This should probably contain the guts of gfxTextRun in the future + * as roc suggested. But for now it's a simple container for a glyph vector. + */ +struct GlyphBuffer { + const Glyph* + mGlyphs; //!< A pointer to a buffer of glyphs. Managed by the caller. + uint32_t mNumGlyphs; //!< Number of glyphs mGlyphs points to. +}; + +#ifdef MOZ_ENABLE_FREETYPE +class SharedFTFace; + +/** SharedFTFaceData abstracts data that may be used to back a SharedFTFace. + * Its main function is to manage the lifetime of the data and ensure that it + * lasts as long as the face. + */ +class SharedFTFaceData { + public: + /** Utility for creating a new face from this data. */ + virtual already_AddRefed<SharedFTFace> CloneFace(int aFaceIndex = 0) { + return nullptr; + } + /** Binds the data's lifetime to the face. */ + virtual void BindData() = 0; + /** Signals that the data is no longer needed by a face. */ + virtual void ReleaseData() = 0; +}; + +/** Wrapper class for ref-counted SharedFTFaceData that handles calling the + * appropriate ref-counting methods + */ +template <class T> +class SharedFTFaceRefCountedData : public SharedFTFaceData { + public: + void BindData() { static_cast<T*>(this)->AddRef(); } + void ReleaseData() { static_cast<T*>(this)->Release(); } +}; + +// Helper class used for clearing out user font data when FT font +// face is destroyed. Since multiple faces may use the same data, be +// careful to assure that the data is only cleared out when all uses +// expire. The font entry object contains a refptr to FTUserFontData and +// each FT face created from that font entry contains a refptr to that +// same FTUserFontData object. +// This is also attached to FT faces for installed fonts (recording the +// filename, rather than storing the font data) if variations are present. +class FTUserFontData final + : public mozilla::gfx::SharedFTFaceRefCountedData<FTUserFontData> { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FTUserFontData) + + FTUserFontData(const uint8_t* aData, uint32_t aLength) + : mFontData(aData), mLength(aLength) {} + explicit FTUserFontData(const char* aFilename) : mFilename(aFilename) {} + + const uint8_t* FontData() const { return mFontData; } + + already_AddRefed<mozilla::gfx::SharedFTFace> CloneFace( + int aFaceIndex = 0) override; + + private: + ~FTUserFontData() { + if (mFontData) { + free((void*)mFontData); + } + } + + std::string mFilename; + const uint8_t* mFontData = nullptr; + uint32_t mLength = 0; +}; + +/** SharedFTFace is a shared wrapper around an FT_Face. It is ref-counted, + * unlike FT_Face itself, so that it may be shared among many users with + * RefPtr. Users should take care to lock SharedFTFace before accessing any + * FT_Face fields that may change to ensure exclusive access to it. It also + * allows backing data's lifetime to be bound to it via SharedFTFaceData so + * that the data will not disappear before the face does. + */ +class SharedFTFace : public external::AtomicRefCounted<SharedFTFace> { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SharedFTFace) + + explicit SharedFTFace(FT_Face aFace, SharedFTFaceData* aData); + virtual ~SharedFTFace(); + + FT_Face GetFace() const { return mFace; } + SharedFTFaceData* GetData() const { return mData; } + + /** Locks the face for exclusive access by a given owner. Returns false if + * the given owner is acquiring the lock for the first time, and true if + * the owner was the prior owner of the lock. Thus the return value can be + * used to do owner-specific initialization of the FT face such as setting + * a size or transform that may have been invalidated by a previous owner. + * If no owner is given, then the user should avoid modifying any state on + * the face so as not to invalidate the prior owner's modification. + */ + bool Lock(const void* aOwner = nullptr) MOZ_CAPABILITY_ACQUIRE(mLock) { + mLock.Lock(); + return !aOwner || mLastLockOwner.exchange(aOwner) == aOwner; + } + void Unlock() MOZ_CAPABILITY_RELEASE(mLock) { mLock.Unlock(); } + + /** Should be called when a lock owner is destroyed so that we don't have + * a dangling pointer to a destroyed owner. + */ + void ForgetLockOwner(const void* aOwner) { + if (aOwner) { + mLastLockOwner.compareExchange(aOwner, nullptr); + } + } + + private: + FT_Face mFace; + SharedFTFaceData* mData; + Mutex mLock; + // Remember the last owner of the lock, even after unlocking, to allow users + // to avoid reinitializing state on the FT face if the last owner hasn't + // changed by the next time it is locked with the same owner. + Atomic<const void*> mLastLockOwner; +}; +#endif + +class UnscaledFont : public SupportsThreadSafeWeakPtr<UnscaledFont> { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFont) + + virtual ~UnscaledFont(); + + virtual FontType GetType() const = 0; + + static uint32_t DeletionCounter() { return sDeletionCounter; } + + typedef void (*FontFileDataOutput)(const uint8_t* aData, uint32_t aLength, + uint32_t aIndex, void* aBaton); + typedef void (*FontInstanceDataOutput)(const uint8_t* aData, uint32_t aLength, + void* aBaton); + typedef void (*FontDescriptorOutput)(const uint8_t* aData, uint32_t aLength, + uint32_t aIndex, void* aBaton); + + virtual bool GetFontFileData(FontFileDataOutput, void*) { return false; } + + virtual bool GetFontInstanceData(FontInstanceDataOutput, void*) { + return false; + } + + virtual bool GetFontDescriptor(FontDescriptorOutput, void*) { return false; } + + virtual already_AddRefed<ScaledFont> CreateScaledFont( + Float aGlyphSize, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength, const FontVariation* aVariations, + uint32_t aNumVariations) { + return nullptr; + } + + virtual already_AddRefed<ScaledFont> CreateScaledFontFromWRFont( + Float aGlyphSize, const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions, + const FontVariation* aVariations, uint32_t aNumVariations) { + return CreateScaledFont(aGlyphSize, nullptr, 0, aVariations, + aNumVariations); + } + + protected: + UnscaledFont() = default; + + private: + static Atomic<uint32_t> sDeletionCounter; +}; + +/** This class is an abstraction of a backend/platform specific font object + * at a particular size. It is passed into text drawing calls to describe + * the font used for the drawing call. + */ +class ScaledFont : public SupportsThreadSafeWeakPtr<ScaledFont> { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFont) + + virtual ~ScaledFont(); + + virtual FontType GetType() const = 0; + virtual Float GetSize() const = 0; + virtual AntialiasMode GetDefaultAAMode() { return AntialiasMode::DEFAULT; } + + static uint32_t DeletionCounter() { return sDeletionCounter; } + + /** This allows getting a path that describes the outline of a set of glyphs. + * A target is passed in so that the guarantee is made the returned path + * can be used with any DrawTarget that has the same backend as the one + * passed in. + */ + virtual already_AddRefed<Path> GetPathForGlyphs( + const GlyphBuffer& aBuffer, const DrawTarget* aTarget) = 0; + + /** This copies the path describing the glyphs into a PathBuilder. We use this + * API rather than a generic API to append paths because it allows easier + * implementation in some backends, and more efficient implementation in + * others. + */ + virtual void CopyGlyphsToBuilder(const GlyphBuffer& aBuffer, + PathBuilder* aBuilder, + const Matrix* aTransformHint = nullptr) = 0; + + typedef void (*FontInstanceDataOutput)(const uint8_t* aData, uint32_t aLength, + const FontVariation* aVariations, + uint32_t aNumVariations, void* aBaton); + + virtual bool GetFontInstanceData(FontInstanceDataOutput, void*) { + return false; + } + + virtual bool GetWRFontInstanceOptions( + Maybe<wr::FontInstanceOptions>* aOutOptions, + Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions, + std::vector<FontVariation>* aOutVariations) { + return false; + } + + virtual bool CanSerialize() { return false; } + + virtual bool HasVariationSettings() { return false; } + + virtual bool MayUseBitmaps() { return false; } + + virtual bool UseSubpixelPosition() const { return false; } + + void AddUserData(UserDataKey* key, void* userData, void (*destroy)(void*)) { + mUserData.Add(key, userData, destroy); + } + void* GetUserData(UserDataKey* key) { return mUserData.Get(key); } + + void RemoveUserData(UserDataKey* key) { mUserData.RemoveAndDestroy(key); } + + const RefPtr<UnscaledFont>& GetUnscaledFont() const { return mUnscaledFont; } + + virtual cairo_scaled_font_t* GetCairoScaledFont() { return nullptr; } + + Float GetSyntheticObliqueAngle() const { return mSyntheticObliqueAngle; } + void SetSyntheticObliqueAngle(Float aAngle) { + mSyntheticObliqueAngle = aAngle; + } + + protected: + explicit ScaledFont(const RefPtr<UnscaledFont>& aUnscaledFont) + : mUnscaledFont(aUnscaledFont), mSyntheticObliqueAngle(0.0f) {} + + ThreadSafeUserData mUserData; + RefPtr<UnscaledFont> mUnscaledFont; + Float mSyntheticObliqueAngle; + + private: + static Atomic<uint32_t> sDeletionCounter; +}; + +/** + * Derived classes hold a native font resource from which to create + * ScaledFonts. + */ +class NativeFontResource + : public external::AtomicRefCounted<NativeFontResource> { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(NativeFontResource) + + /** + * Creates a UnscaledFont using the font corresponding to the index. + * + * @param aIndex index for the font within the resource. + * @param aInstanceData pointer to read-only buffer of any available instance + * data. + * @param aInstanceDataLength the size of the instance data. + * @return an already_addrefed UnscaledFont, containing nullptr if failed. + */ + virtual already_AddRefed<UnscaledFont> CreateUnscaledFont( + uint32_t aIndex, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength) = 0; + + NativeFontResource(size_t aDataLength); + virtual ~NativeFontResource(); + + static void RegisterMemoryReporter(); + + private: + size_t mDataLength; +}; + +/** This is the main class used for all the drawing. It is created through the + * factory and accepts drawing commands. The results of drawing to a target + * may be used either through a Snapshot or by flushing the target and directly + * accessing the backing store a DrawTarget was created with. + */ +class DrawTarget : public external::AtomicRefCounted<DrawTarget> { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTarget) + DrawTarget() + : mTransformDirty(false), + mPermitSubpixelAA(false), + mFormat(SurfaceFormat::UNKNOWN) {} + virtual ~DrawTarget() = default; + + virtual bool IsValid() const { return true; }; + virtual DrawTargetType GetType() const = 0; + + virtual BackendType GetBackendType() const = 0; + + virtual bool IsRecording() const { return false; } + + /** + * Method to generate hyperlink in PDF output (with appropriate backend). + */ + virtual void Link(const char* aDestination, const Rect& aRect) {} + virtual void Destination(const char* aDestination, const Point& aPoint) {} + + /** + * Returns a SourceSurface which is a snapshot of the current contents of the + * DrawTarget. Multiple calls to Snapshot() without any drawing operations in + * between will normally return the same SourceSurface object. + */ + virtual already_AddRefed<SourceSurface> Snapshot() = 0; + + /** + * Returns a SourceSurface which wraps the buffer backing the DrawTarget. The + * contents of the buffer may change if there are drawing operations after + * calling but only guarantees that it reflects the state at the time it was + * called. + */ + virtual already_AddRefed<SourceSurface> GetBackingSurface() { + return Snapshot(); + } + + // Snapshots the contents and returns an alpha mask + // based on the RGB values. + virtual already_AddRefed<SourceSurface> IntoLuminanceSource( + LuminanceType aLuminanceType, float aOpacity); + virtual IntSize GetSize() const = 0; + virtual IntRect GetRect() const { return IntRect(IntPoint(0, 0), GetSize()); } + + /** + * If possible returns the bits to this DrawTarget for direct manipulation. + * While the bits is locked any modifications to this DrawTarget is forbidden. + * Release takes the original data pointer for safety. + */ + virtual bool LockBits(uint8_t** aData, IntSize* aSize, int32_t* aStride, + SurfaceFormat* aFormat, IntPoint* aOrigin = nullptr) { + return false; + } + virtual void ReleaseBits(uint8_t* aData) {} + + /** Ensure that the DrawTarget backend has flushed all drawing operations to + * this draw target. This must be called before using the backing surface of + * this draw target outside of GFX 2D code. + */ + virtual void Flush() = 0; + + /** + * Draw a surface to the draw target. Possibly doing partial drawing or + * applying scaling. No sampling happens outside the source. + * + * @param aSurface Source surface to draw + * @param aDest Destination rectangle that this drawing operation should draw + * to + * @param aSource Source rectangle in aSurface coordinates, this area of + * aSurface + * will be stretched to the size of aDest. + * @param aOptions General draw options that are applied to the operation + * @param aSurfOptions DrawSurface options that are applied + */ + virtual void DrawSurface( + SourceSurface* aSurface, const Rect& aDest, const Rect& aSource, + const DrawSurfaceOptions& aSurfOptions = DrawSurfaceOptions(), + const DrawOptions& aOptions = DrawOptions()) = 0; + + /** + * Draw a surface to the draw target, when the surface will be available + * at a later time. This is only valid for recording DrawTargets. + * + * This is considered fallible, and replaying this without making the surface + * available to the replay will just skip the draw. + */ + virtual void DrawDependentSurface(uint64_t aId, const Rect& aDest) { + MOZ_CRASH("GFX: DrawDependentSurface"); + } + + /** + * Draw the output of a FilterNode to the DrawTarget. + * + * @param aNode FilterNode to draw + * @param aSourceRect Source rectangle in FilterNode space to draw + * @param aDestPoint Destination point on the DrawTarget to draw the + * SourceRectangle of the filter output to + */ + virtual void DrawFilter(FilterNode* aNode, const Rect& aSourceRect, + const Point& aDestPoint, + const DrawOptions& aOptions = DrawOptions()) = 0; + + /** + * Blend a surface to the draw target with a shadow. The shadow is drawn as a + * gaussian blur using a specified sigma. The shadow is clipped to the size + * of the input surface, so the input surface should contain a transparent + * border the size of the approximate coverage of the blur (3 * aSigma). + * NOTE: This function works in device space! + * + * @param aSurface Source surface to draw. + * @param aDest Destination point that this drawing operation should draw to. + * @param aShadow Description of shadow to be drawn. + * @param aOperator Composition operator used + */ + virtual void DrawSurfaceWithShadow(SourceSurface* aSurface, + const Point& aDest, + const ShadowOptions& aShadow, + CompositionOp aOperator) = 0; + + /** + * Draws a shadow for the specified path, which may be optionally stroked. + * + * @param aPath The path to use for the shadow geometry. + * @param aPattern The pattern to use for filling the path. + * @param aShadow Description of shadow to be drawn. + * @param aOptions General drawing options to apply to drawing the path. + * @param aStrokeOptions Stroking parameters that control stroking of path + * geometry, if supplied. + */ + virtual void DrawShadow(const Path* aPath, const Pattern& aPattern, + const ShadowOptions& aShadow, + const DrawOptions& aOptions = DrawOptions(), + const StrokeOptions* aStrokeOptions = nullptr); + + /** + * Clear a rectangle on the draw target to transparent black. This will + * respect the clipping region and transform. + * + * @param aRect Rectangle to clear + */ + virtual void ClearRect(const Rect& aRect) = 0; + + /** + * This is essentially a 'memcpy' between two surfaces. It moves a pixel + * aligned area from the source surface unscaled directly onto the + * drawtarget. This ignores both transform and clip. + * + * @param aSurface Surface to copy from + * @param aSourceRect Source rectangle to be copied + * @param aDest Destination point to copy the surface to + */ + virtual void CopySurface(SourceSurface* aSurface, const IntRect& aSourceRect, + const IntPoint& aDestination) = 0; + + /** @see CopySurface + * Same as CopySurface, except uses itself as the source. + * + * Some backends may be able to optimize this better + * than just taking a snapshot and using CopySurface. + */ + virtual void CopyRect(const IntRect& aSourceRect, + const IntPoint& aDestination) { + RefPtr<SourceSurface> source = Snapshot(); + CopySurface(source, aSourceRect, aDestination); + } + + /** + * Fill a rectangle on the DrawTarget with a certain source pattern. + * + * @param aRect Rectangle that forms the mask of this filling operation + * @param aPattern Pattern that forms the source of this filling operation + * @param aOptions Options that are applied to this operation + */ + virtual void FillRect(const Rect& aRect, const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()) = 0; + + /** + * Fill a rounded rectangle on the DrawTarget with a certain source pattern. + * + * @param aRect Rounded rectangle that forms the mask of this filling + * operation + * @param aPattern Pattern that forms the source of this filling operation + * @param aOptions Options that are applied to this operation + */ + virtual void FillRoundedRect(const RoundedRect& aRect, + const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()); + + /** + * Stroke a rectangle on the DrawTarget with a certain source pattern. + * + * @param aRect Rectangle that forms the mask of this stroking operation + * @param aPattern Pattern that forms the source of this stroking operation + * @param aOptions Options that are applied to this operation + */ + virtual void StrokeRect(const Rect& aRect, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()) = 0; + + /** + * Stroke a line on the DrawTarget with a certain source pattern. + * + * @param aStart Starting point of the line + * @param aEnd End point of the line + * @param aPattern Pattern that forms the source of this stroking operation + * @param aOptions Options that are applied to this operation + */ + virtual void StrokeLine(const Point& aStart, const Point& aEnd, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()) = 0; + + /** + * Stroke a circle on the DrawTarget with a certain source pattern. + * + * @param aCircle the parameters of the circle + * @param aPattern Pattern that forms the source of this stroking operation + * @param aOptions Options that are applied to this operation + */ + virtual void StrokeCircle( + const Point& aOrigin, float radius, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()); + + /** + * 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 circle on the DrawTarget with a certain source pattern. + * + * @param aCircle the parameters of the circle + * @param aPattern Pattern that forms the source of this stroking operation + * @param aOptions Options that are applied to this operation + */ + virtual void FillCircle(const Point& aOrigin, float radius, + const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()); + + /** + * Fill a series of glyphs on the draw target with a certain source pattern. + */ + virtual void FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()) = 0; + + /** + * Stroke a series of glyphs on the draw target with a certain source pattern. + */ + virtual void StrokeGlyphs( + ScaledFont* aFont, const GlyphBuffer& aBuffer, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()); + + /** + * This takes a source pattern and a mask, and composites the source pattern + * onto the destination surface using the alpha channel of the mask pattern + * as a mask for the operation. + * + * @param aSource Source pattern + * @param aMask Mask pattern + * @param aOptions Drawing options + */ + virtual void Mask(const Pattern& aSource, const Pattern& aMask, + const DrawOptions& aOptions = DrawOptions()) = 0; + + /** + * This takes a source pattern and a mask, and composites the source pattern + * onto the destination surface using the alpha channel of the mask source. + * The operation is bound by the extents of the mask. + * + * @param aSource Source pattern + * @param aMask Mask surface + * @param aOffset a transformed offset that the surface is masked at + * @param aOptions Drawing options + */ + virtual void MaskSurface(const Pattern& aSource, SourceSurface* aMask, + Point aOffset, + const DrawOptions& aOptions = DrawOptions()) = 0; + + /** + * Draw aSurface using the 3D transform aMatrix. The DrawTarget's transform + * and clip are applied after the 3D transform. + * + * If the transform fails (i.e. because aMatrix is singular), false is + * returned and nothing is drawn. + */ + virtual bool Draw3DTransformedSurface(SourceSurface* aSurface, + const Matrix4x4& aMatrix); + + /** + * Push a clip to the DrawTarget. + * + * @param aPath The path to clip to + */ + virtual void PushClip(const Path* aPath) = 0; + + /** + * Push an axis-aligned rectangular clip to the DrawTarget. This rectangle + * is specified in user space. + * + * @param aRect The rect to clip to + */ + virtual void PushClipRect(const Rect& aRect) = 0; + + /** + * Push a clip region specifed by the union of axis-aligned rectangular + * clips to the DrawTarget. These rectangles are specified in device space. + * This must be balanced by a corresponding call to PopClip within a layer. + * + * @param aRects The rects to clip to + * @param aCount The number of rectangles + */ + virtual void PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount); + + /** Pop a clip from the DrawTarget. A pop without a corresponding push will + * be ignored. + */ + virtual void PopClip() = 0; + + /** + * Push a 'layer' to the DrawTarget, a layer is a temporary surface that all + * drawing will be redirected to, this is used for example to support group + * opacity or the masking of groups. Clips must be balanced within a layer, + * i.e. between a matching PushLayer/PopLayer pair there must be as many + * PushClip(Rect) calls as there are PopClip calls. + * + * @param aOpaque Whether the layer will be opaque + * @param aOpacity Opacity of the layer + * @param aMask Mask applied to the layer + * @param aMaskTransform Transform applied to the layer mask + * @param aBounds Optional bounds in device space to which the layer is + * limited in size. + * @param aCopyBackground Whether to copy the background into the layer, this + * is only supported when aOpaque is true. + */ + virtual void PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds = IntRect(), + bool aCopyBackground = false) { + MOZ_CRASH("GFX: PushLayer"); + } + + /** + * Push a 'layer' to the DrawTarget, a layer is a temporary surface that all + * drawing will be redirected to, this is used for example to support group + * opacity or the masking of groups. Clips must be balanced within a layer, + * i.e. between a matching PushLayer/PopLayer pair there must be as many + * PushClip(Rect) calls as there are PopClip calls. + * + * @param aOpaque Whether the layer will be opaque + * @param aOpacity Opacity of the layer + * @param aMask Mask applied to the layer + * @param aMaskTransform Transform applied to the layer mask + * @param aBounds Optional bounds in device space to which the layer is + * limited in size. + * @param aCopyBackground Whether to copy the background into the layer, this + * is only supported when aOpaque is true. + */ + virtual void PushLayerWithBlend(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds = IntRect(), + bool aCopyBackground = false, + CompositionOp = CompositionOp::OP_OVER) { + MOZ_CRASH("GFX: PushLayerWithBlend"); + } + + /** + * This balances a call to PushLayer and proceeds to blend the layer back + * onto the background. This blend will blend the temporary surface back + * onto the target in device space using POINT sampling and operator over. + */ + virtual void PopLayer() { MOZ_CRASH("GFX: PopLayer"); } + + /** + * Perform an in-place blur operation. This is only supported on data draw + * targets. + */ + virtual void Blur(const AlphaBoxBlur& aBlur); + + /** + * Performs an in-place edge padding operation. + * aRegion is specified in device space. + */ + virtual void PadEdges(const IntRegion& aRegion); + + /** + * Performs an in-place buffer unrotation operation. + */ + virtual bool Unrotate(IntPoint aRotation); + + /** + * Create a SourceSurface optimized for use with this DrawTarget from + * existing bitmap data in memory. + * + * The SourceSurface does not take ownership of aData, and may be freed at any + * time. + */ + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData( + unsigned char* aData, const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat) const = 0; + + /** + * Create a SourceSurface optimized for use with this DrawTarget from an + * arbitrary SourceSurface type supported by this backend. This may return + * aSourceSurface or some other existing surface. + */ + virtual already_AddRefed<SourceSurface> OptimizeSourceSurface( + SourceSurface* aSurface) const = 0; + virtual already_AddRefed<SourceSurface> OptimizeSourceSurfaceForUnknownAlpha( + SourceSurface* aSurface) const { + return OptimizeSourceSurface(aSurface); + } + + /** + * Create a SourceSurface for a type of NativeSurface. This may fail if the + * draw target does not know how to deal with the type of NativeSurface passed + * in. If this succeeds, the SourceSurface takes the ownersip of the + * NativeSurface. + */ + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromNativeSurface( + const NativeSurface& aSurface) const = 0; + + /** + * Create a DrawTarget whose snapshot is optimized for use with this + * DrawTarget. + */ + virtual already_AddRefed<DrawTarget> CreateSimilarDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat) const = 0; + + /** + * Create a DrawTarget whose backing surface is optimized for use with this + * DrawTarget. + */ + virtual already_AddRefed<DrawTarget> CreateSimilarDrawTargetWithBacking( + const IntSize& aSize, SurfaceFormat aFormat) const { + return CreateSimilarDrawTarget(aSize, aFormat); + } + + /** + * Create a DrawTarget whose snapshot is optimized for use with this + * DrawTarget and aFilter. + * @param aSource is the FilterNode that that will be attached to this + * surface. + * @param aSourceRect is the source rect that will be passed to DrawFilter + * @param aDestPoint is the dest point that will be passed to DrawFilter. + */ + virtual already_AddRefed<DrawTarget> CreateSimilarDrawTargetForFilter( + const IntSize& aSize, SurfaceFormat aFormat, FilterNode* aFilter, + FilterNode* aSource, const Rect& aSourceRect, const Point& aDestPoint) { + return CreateSimilarDrawTarget(aSize, aFormat); + } + + /** + * Returns false if CreateSimilarDrawTarget would return null with the same + * parameters. May return true even in cases where CreateSimilarDrawTarget + * return null (i.e. this function returning false has meaning, but returning + * true doesn't guarantee anything). + */ + virtual bool CanCreateSimilarDrawTarget(const IntSize& aSize, + SurfaceFormat aFormat) const { + return true; + } + + /** + * Create a draw target optimized for drawing a shadow. + * + * Note that aSigma is the blur radius that must be used when we draw the + * shadow. Also note that this doesn't affect the size of the allocated + * surface, the caller is still responsible for including the shadow area in + * its size. + */ + virtual already_AddRefed<DrawTarget> CreateShadowDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat, float aSigma) const { + return CreateSimilarDrawTarget(aSize, aFormat); + } + + /** + * Create a similar DrawTarget in the same space as this DrawTarget whose + * device size may be clipped based on the active clips intersected with + * aBounds (if it is not empty). + * aRect is a rectangle in user space. + */ + virtual RefPtr<DrawTarget> CreateClippedDrawTarget(const Rect& aBounds, + SurfaceFormat aFormat) = 0; + + /** + * Create a similar draw target, but if the draw target is not backed by a + * raster backend (for example, it is capturing or recording), force it to + * create a raster target instead. This is intended for code that wants to + * cache pixels, and would have no effect if it were caching a recording. + */ + virtual RefPtr<DrawTarget> CreateSimilarRasterTarget( + const IntSize& aSize, SurfaceFormat aFormat) const { + return CreateSimilarDrawTarget(aSize, aFormat); + } + + /** + * Create a path builder with the specified fillmode. + * + * We need the fill mode up front because of Direct2D. + * ID2D1SimplifiedGeometrySink requires the fill mode + * to be set before calling BeginFigure(). + */ + virtual already_AddRefed<PathBuilder> CreatePathBuilder( + FillRule aFillRule = FillRule::FILL_WINDING) const = 0; + + /** + * Create a GradientStops object that holds information about a set of + * gradient stops, this object is required for linear or radial gradient + * patterns to represent the color stops in the gradient. + * + * @param aStops An array of gradient stops + * @param aNumStops Number of stops in the array aStops + * @param aExtendNone This describes how to extend the stop color outside of + * the gradient area. + */ + virtual already_AddRefed<GradientStops> CreateGradientStops( + GradientStop* aStops, uint32_t aNumStops, + ExtendMode aExtendMode = ExtendMode::CLAMP) const = 0; + + /** + * Create a FilterNode object that can be used to apply a filter to various + * inputs. + * + * @param aType Type of filter node to be created. + */ + virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) = 0; + + Matrix GetTransform() const { return mTransform; } + + /** + * Set a transform on the surface, this transform is applied at drawing time + * to both the mask and source of the operation. + * + * Performance note: For some backends it is expensive to change the current + * transform (because transforms affect a lot of the parts of the pipeline, + * so new transform change can result in a pipeline flush). To get around + * this, DrawTarget implementations buffer transform changes and try to only + * set the current transform on the backend when required. That tracking has + * its own performance impact though, and ideally callers would be smart + * enough not to require it. At a future date this method may stop this + * doing transform buffering so, if you're a consumer, please try to be smart + * about calling this method as little as possible. For example, instead of + * concatenating a translation onto the current transform then calling + * FillRect, try to integrate the translation into FillRect's aRect + * argument's x/y offset. + */ + virtual void SetTransform(const Matrix& aTransform) { + mTransform = aTransform; + mTransformDirty = true; + } + + inline void ConcatTransform(const Matrix& aTransform) { + SetTransform(aTransform * Matrix(GetTransform())); + } + + SurfaceFormat GetFormat() const { return mFormat; } + + /** Tries to get a native surface for a DrawTarget, this may fail if the + * draw target cannot convert to this surface type. + */ + virtual void* GetNativeSurface(NativeSurfaceType aType) { return nullptr; } + + virtual bool IsTiledDrawTarget() const { return false; } + virtual bool SupportsRegionClipping() const { return true; } + + void AddUserData(UserDataKey* key, void* userData, void (*destroy)(void*)) { + mUserData.Add(key, userData, destroy); + } + void* GetUserData(UserDataKey* key) const { return mUserData.Get(key); } + void* RemoveUserData(UserDataKey* key) { return mUserData.Remove(key); } + + /** Within this rectangle all pixels will be opaque by the time the result of + * this DrawTarget is first used for drawing. Either by the underlying surface + * being used as an input to external drawing, or Snapshot() being called. + * This rectangle is specified in device space. + */ + void SetOpaqueRect(const IntRect& aRect) { mOpaqueRect = aRect; } + + const IntRect& GetOpaqueRect() const { return mOpaqueRect; } + + virtual bool IsCurrentGroupOpaque() { + return GetFormat() == SurfaceFormat::B8G8R8X8; + } + + virtual void SetPermitSubpixelAA(bool aPermitSubpixelAA) { + mPermitSubpixelAA = aPermitSubpixelAA; + } + + bool GetPermitSubpixelAA() { return mPermitSubpixelAA; } + + /** + * Mark the end of an Item in a DrawTargetRecording. These markers + * are used for merging recordings together. + * + * This should only be called on the 'root' DrawTargetRecording. + * Calling it on a child DrawTargetRecordings will cause confusion. + * + * Note: this is a bit of a hack. It might be better to just recreate + * the DrawTargetRecording. + */ + virtual void FlushItem(const IntRect& aBounds) {} + + /** + * Ensures that no snapshot is still pointing to this DrawTarget's surface + * data. + * + * This can be useful if the DrawTarget is wrapped around data that it does + * not own, and for some reason the owner of the data has to make it + * temporarily unavailable without the DrawTarget knowing about it. This can + * cause costly surface copies, so it should not be used without a a good + * reason. + */ + virtual void DetachAllSnapshots() = 0; + + /** + * Remove all clips in the DrawTarget. + */ + virtual bool RemoveAllClips() { return false; } + + protected: + UserData mUserData; + Matrix mTransform; + IntRect mOpaqueRect; + bool mTransformDirty : 1; + bool mPermitSubpixelAA : 1; + + SurfaceFormat mFormat; +}; + +class DrawEventRecorder : public RefCounted<DrawEventRecorder> { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorder) + virtual RecorderType GetRecorderType() const { return RecorderType::UNKNOWN; } + // returns true if there were any items in the recording + virtual bool Finish() = 0; + virtual ~DrawEventRecorder() = default; +}; + +struct Tile { + RefPtr<DrawTarget> mDrawTarget; + IntPoint mTileOrigin; +}; + +struct TileSet { + Tile* mTiles; + size_t mTileCount; +}; + +struct Config { + LogForwarder* mLogForwarder; + int32_t mMaxTextureSize; + int32_t mMaxAllocSize; + + Config() + : mLogForwarder(nullptr), + mMaxTextureSize(kReasonableSurfaceSize), + mMaxAllocSize(52000000) {} +}; + +class GFX2D_API Factory { + using char_type = filesystem::Path::value_type; + + public: + static void Init(const Config& aConfig); + static void ShutDown(); + + static bool HasSSE2(); + static bool HasSSE4(); + + /** + * Returns false if any of the following are true: + * + * - the width/height of |sz| are less than or equal to zero + * - the width/height of |sz| are greater than |limit| + * - the number of bytes that need to be allocated for the surface is too + * big to fit in an int32_t, or bigger than |allocLimit|, if specifed + * + * To calculate the number of bytes that need to be allocated for the surface + * this function makes the conservative assumption that there need to be + * 4 bytes-per-pixel, and the stride alignment is 16 bytes. + * + * The reason for using int32_t rather than uint32_t is again to be + * conservative; some code has in the past and may in the future use signed + * integers to store buffer lengths etc. + */ + static bool CheckSurfaceSize(const IntSize& sz, int32_t limit = 0, + int32_t allocLimit = 0); + + /** Make sure the given dimension satisfies the CheckSurfaceSize and is + * within 8k limit. The 8k value is chosen a bit randomly. + */ + static bool ReasonableSurfaceSize(const IntSize& aSize); + + static bool AllowedSurfaceSize(const IntSize& aSize); + + static already_AddRefed<DrawTarget> CreateDrawTargetForCairoSurface( + cairo_surface_t* aSurface, const IntSize& aSize, + SurfaceFormat* aFormat = nullptr); + + static already_AddRefed<SourceSurface> CreateSourceSurfaceForCairoSurface( + cairo_surface_t* aSurface, const IntSize& aSize, SurfaceFormat aFormat); + + static already_AddRefed<DrawTarget> CreateDrawTarget(BackendType aBackend, + const IntSize& aSize, + SurfaceFormat aFormat); + + static already_AddRefed<PathBuilder> CreatePathBuilder( + BackendType aBackend, FillRule aFillRule = FillRule::FILL_WINDING); + + /** + * Create a simple PathBuilder, which uses SKIA backend. + */ + static already_AddRefed<PathBuilder> CreateSimplePathBuilder(); + + static already_AddRefed<DrawTarget> CreateRecordingDrawTarget( + DrawEventRecorder* aRecorder, DrawTarget* aDT, IntRect aRect); + + static already_AddRefed<DrawTarget> CreateDrawTargetForData( + BackendType aBackend, unsigned char* aData, const IntSize& aSize, + int32_t aStride, SurfaceFormat aFormat, bool aUninitialized = false); + +#ifdef XP_DARWIN + static already_AddRefed<ScaledFont> CreateScaledFontForMacFont( + CGFontRef aCGFont, const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize, + bool aUseFontSmoothing = true, bool aApplySyntheticBold = false, + bool aHasColorGlyphs = false); +#endif + +#ifdef MOZ_WIDGET_GTK + static already_AddRefed<ScaledFont> CreateScaledFontForFontconfigFont( + const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize, + RefPtr<SharedFTFace> aFace, FcPattern* aPattern); +#endif + +#ifdef MOZ_WIDGET_ANDROID + static already_AddRefed<ScaledFont> CreateScaledFontForFreeTypeFont( + const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize, + RefPtr<SharedFTFace> aFace, bool aApplySyntheticBold = false); +#endif + + /** + * This creates a NativeFontResource from TrueType data. + * + * @param aData Pointer to the data + * @param aSize Size of the TrueType data + * @param aFontType Type of NativeFontResource that should be created. + * @param aFontContext Optional native font context to be used to create the + * NativeFontResource. + * @return a NativeFontResource of nullptr if failed. + */ + static already_AddRefed<NativeFontResource> CreateNativeFontResource( + uint8_t* aData, uint32_t aSize, FontType aFontType, + void* aFontContext = nullptr); + + /** + * This creates an unscaled font of the given type based on font descriptor + * data retrieved from ScaledFont::GetFontDescriptor. + */ + static already_AddRefed<UnscaledFont> CreateUnscaledFontFromFontDescriptor( + FontType aType, const uint8_t* aData, uint32_t aDataLength, + uint32_t aIndex); + + /** + * This creates a simple data source surface for a certain size. It allocates + * new memory for the surface. This memory is freed when the surface is + * destroyed. The caller is responsible for handing the case where nullptr + * is returned. The surface is not zeroed unless requested. + */ + static already_AddRefed<DataSourceSurface> CreateDataSourceSurface( + const IntSize& aSize, SurfaceFormat aFormat, bool aZero = false); + + /** + * This creates a simple data source surface for a certain size with a + * specific stride, which must be large enough to fit all pixels. + * It allocates new memory for the surface. This memory is freed when + * the surface is destroyed. The caller is responsible for handling the case + * where nullptr is returned. The surface is not zeroed unless requested. + */ + static already_AddRefed<DataSourceSurface> CreateDataSourceSurfaceWithStride( + const IntSize& aSize, SurfaceFormat aFormat, int32_t aStride, + bool aZero = false); + + typedef void (*SourceSurfaceDeallocator)(void* aClosure); + + /** + * This creates a simple data source surface for some existing data. It will + * wrap this data and the data for this source surface. + * + * We can provide a custom destroying function for |aData|. This will be + * called in the surface dtor using |aDeallocator| and the |aClosure|. If + * there are errors during construction(return a nullptr surface), the caller + * is responsible for the deallocation. + * + * If there is no destroying function, the caller is responsible for + * deallocating the aData memory only after destruction of this + * DataSourceSurface. + */ + static already_AddRefed<DataSourceSurface> CreateWrappingDataSourceSurface( + uint8_t* aData, int32_t aStride, const IntSize& aSize, + SurfaceFormat aFormat, SourceSurfaceDeallocator aDeallocator = nullptr, + void* aClosure = nullptr); + + static void CopyDataSourceSurface(DataSourceSurface* aSource, + DataSourceSurface* aDest); + + static uint32_t GetMaxSurfaceSize(BackendType aType); + + static LogForwarder* GetLogForwarder() { + return sConfig ? sConfig->mLogForwarder : nullptr; + } + + private: + static Config* sConfig; + + public: + static void PurgeAllCaches(); + + static already_AddRefed<DrawTarget> CreateOffsetDrawTarget( + DrawTarget* aDrawTarget, IntPoint aTileOrigin); + + static bool DoesBackendSupportDataDrawtarget(BackendType aType); + + static void SetBGRSubpixelOrder(bool aBGR); + static bool GetBGRSubpixelOrder(); + + private: + static bool mBGRSubpixelOrder; + + public: + static already_AddRefed<DrawTarget> CreateDrawTargetWithSkCanvas( + SkCanvas* aCanvas); + +#ifdef MOZ_ENABLE_FREETYPE + static void SetFTLibrary(FT_Library aFTLibrary); + static FT_Library GetFTLibrary(); + + static FT_Library NewFTLibrary(); + static void ReleaseFTLibrary(FT_Library aFTLibrary); + static void LockFTLibrary(FT_Library aFTLibrary); + static void UnlockFTLibrary(FT_Library aFTLibrary); + + static FT_Face NewFTFace(FT_Library aFTLibrary, const char* aFileName, + int aFaceIndex); + static already_AddRefed<SharedFTFace> NewSharedFTFace(FT_Library aFTLibrary, + const char* aFilename, + int aFaceIndex); + static FT_Face NewFTFaceFromData(FT_Library aFTLibrary, const uint8_t* aData, + size_t aDataSize, int aFaceIndex); + static already_AddRefed<SharedFTFace> NewSharedFTFaceFromData( + FT_Library aFTLibrary, const uint8_t* aData, size_t aDataSize, + int aFaceIndex, SharedFTFaceData* aSharedData = nullptr); + static void ReleaseFTFace(FT_Face aFace); + static FT_Error LoadFTGlyph(FT_Face aFace, uint32_t aGlyphIndex, + int32_t aFlags); + + private: + static FT_Library mFTLibrary; + static StaticMutex mFTLock; + + public: +#endif + +#ifdef WIN32 + static already_AddRefed<DrawTarget> CreateDrawTargetForD3D11Texture( + ID3D11Texture2D* aTexture, SurfaceFormat aFormat); + + /* + * Attempts to create and install a D2D1 device from the supplied Direct3D11 + * device. Returns true on success, or false on failure and leaves the + * D2D1/Direct3D11 devices unset. + */ + static bool SetDirect3D11Device(ID3D11Device* aDevice); + static RefPtr<ID3D11Device> GetDirect3D11Device(); + static RefPtr<ID2D1Device> GetD2D1Device(uint32_t* aOutSeqNo = nullptr); + static bool HasD2D1Device(); + static RefPtr<IDWriteFactory> GetDWriteFactory(); + static RefPtr<IDWriteFactory> EnsureDWriteFactory(); + static bool SupportsD2D1(); + static RefPtr<IDWriteFontCollection> GetDWriteSystemFonts( + bool aUpdate = false); + static RefPtr<ID2D1DeviceContext> GetD2DDeviceContext(); + + static uint64_t GetD2DVRAMUsageDrawTarget(); + static uint64_t GetD2DVRAMUsageSourceSurface(); + static void D2DCleanup(); + + static already_AddRefed<ScaledFont> CreateScaledFontForDWriteFont( + IDWriteFontFace* aFontFace, const gfxFontStyle* aStyle, + const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize, + bool aUseEmbeddedBitmap, bool aUseMultistrikeBold, bool aGDIForced); + + static already_AddRefed<ScaledFont> CreateScaledFontForGDIFont( + const void* aLogFont, const RefPtr<UnscaledFont>& aUnscaledFont, + Float aSize); + + static void SetSystemTextQuality(uint8_t aQuality); + + static already_AddRefed<DataSourceSurface> + CreateBGRA8DataSourceSurfaceForD3D11Texture(ID3D11Texture2D* aSrcTexture, + uint32_t aArrayIndex = 0); + + static nsresult CreateSdbForD3D11Texture( + ID3D11Texture2D* aSrcTexture, const IntSize& aSrcSize, + layers::SurfaceDescriptorBuffer& aSdBuffer, + const std::function<layers::MemoryOrShmem(uint32_t)>& aAllocate); + + static bool ReadbackTexture(layers::TextureData* aDestCpuTexture, + ID3D11Texture2D* aSrcTexture); + + static bool ReadbackTexture(DataSourceSurface* aDestCpuTexture, + ID3D11Texture2D* aSrcTexture, + uint32_t aArrayIndex = 0); + + private: + static StaticRefPtr<ID2D1Device> mD2D1Device; + static StaticRefPtr<ID3D11Device> mD3D11Device; + static StaticRefPtr<IDWriteFactory> mDWriteFactory; + static bool mDWriteFactoryInitialized; + static StaticRefPtr<IDWriteFontCollection> mDWriteSystemFonts; + static StaticRefPtr<ID2D1DeviceContext> mMTDC; + static StaticRefPtr<ID2D1DeviceContext> mOffMTDC; + + static bool ReadbackTexture(uint8_t* aDestData, int32_t aDestStride, + ID3D11Texture2D* aSrcTexture); + + // DestTextureT can be TextureData or DataSourceSurface. + template <typename DestTextureT> + static bool ConvertSourceAndRetryReadback(DestTextureT* aDestCpuTexture, + ID3D11Texture2D* aSrcTexture, + uint32_t aArrayIndex = 0); + + protected: + // This guards access to the singleton devices above, as well as the + // singleton devices in DrawTargetD2D1. + static StaticMutex mDeviceLock; + // This synchronizes access between different D2D drawtargets and their + // implied dependency graph. + static StaticMutex mDTDependencyLock; + + friend class DrawTargetD2D1; +#endif // WIN32 +}; + +class MOZ_RAII AutoSerializeWithMoz2D final { + public: + explicit AutoSerializeWithMoz2D(BackendType aBackendType); + ~AutoSerializeWithMoz2D(); + + private: +#if defined(WIN32) + RefPtr<ID2D1Multithread> mMT; +#endif +}; + +} // namespace gfx +} // namespace mozilla + +#endif // _MOZILLA_GFX_2D_H diff --git a/gfx/2d/AutoHelpersWin.h b/gfx/2d/AutoHelpersWin.h new file mode 100644 index 0000000000..733f8a1e27 --- /dev/null +++ b/gfx/2d/AutoHelpersWin.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_AutoHelpersWin_h +#define mozilla_gfx_AutoHelpersWin_h + +#include <windows.h> + +namespace mozilla { +namespace gfx { + +// Get the global device context, and auto-release it on destruction. +class AutoDC { + public: + AutoDC() { mDC = ::GetDC(nullptr); } + + ~AutoDC() { ::ReleaseDC(nullptr, mDC); } + + HDC GetDC() { return mDC; } + + private: + HDC mDC; +}; + +// Select a font into the given DC, and auto-restore. +class AutoSelectFont { + public: + AutoSelectFont(HDC aDC, LOGFONTW* aLogFont) : mOwnsFont(false) { + mFont = ::CreateFontIndirectW(aLogFont); + if (mFont) { + mOwnsFont = true; + mDC = aDC; + mOldFont = (HFONT)::SelectObject(aDC, mFont); + } else { + mOldFont = nullptr; + } + } + + AutoSelectFont(HDC aDC, HFONT aFont) : mOwnsFont(false) { + mDC = aDC; + mFont = aFont; + mOldFont = (HFONT)::SelectObject(aDC, aFont); + } + + ~AutoSelectFont() { + if (mOldFont) { + ::SelectObject(mDC, mOldFont); + if (mOwnsFont) { + ::DeleteObject(mFont); + } + } + } + + bool IsValid() const { return mFont != nullptr; } + + HFONT GetFont() const { return mFont; } + + private: + HDC mDC; + HFONT mFont; + HFONT mOldFont; + bool mOwnsFont; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // mozilla_gfx_AutoHelpersWin_h diff --git a/gfx/2d/BaseCoord.h b/gfx/2d/BaseCoord.h new file mode 100644 index 0000000000..41a82ea047 --- /dev/null +++ b/gfx/2d/BaseCoord.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_BASECOORD_H_ +#define MOZILLA_GFX_BASECOORD_H_ + +#include <ostream> + +#include "mozilla/Attributes.h" +#include "mozilla/MathAlgorithms.h" + +namespace mozilla::gfx { + +/** + * Do not use this class directly. Subclass it, pass that subclass as the + * Sub parameter, and only use that subclass. This allows methods to safely + * cast 'this' to 'Sub*'. + */ +template <class T, class Sub> +struct BaseCoord { + T value; + + // Constructors + constexpr BaseCoord() : value(0) {} + explicit constexpr BaseCoord(T aValue) : value(aValue) {} + + // Note that '=' isn't defined so we'll get the + // compiler generated default assignment operator + + friend constexpr Sub Abs(BaseCoord aCoord) { return Abs(aCoord.value); } + + constexpr operator T() const { return value; } + + friend constexpr bool operator==(Sub aA, Sub aB) { + return aA.value == aB.value; + } + friend constexpr bool operator!=(Sub aA, Sub aB) { + return aA.value != aB.value; + } + + friend constexpr Sub operator+(Sub aA, Sub aB) { + return Sub(aA.value + aB.value); + } + friend constexpr Sub operator-(Sub aA, Sub aB) { + return Sub(aA.value - aB.value); + } + friend constexpr Sub operator*(Sub aCoord, T aScale) { + return Sub(aCoord.value * aScale); + } + friend constexpr Sub operator*(T aScale, Sub aCoord) { + return Sub(aScale * aCoord.value); + } + friend constexpr Sub operator/(Sub aCoord, T aScale) { + return Sub(aCoord.value / aScale); + } + // 'scale / coord' is intentionally omitted because it doesn't make sense. + + constexpr Sub& operator+=(Sub aCoord) { + value += aCoord.value; + return *static_cast<Sub*>(this); + } + constexpr Sub& operator-=(Sub aCoord) { + value -= aCoord.value; + return *static_cast<Sub*>(this); + } + constexpr Sub& operator*=(T aScale) { + value *= aScale; + return *static_cast<Sub*>(this); + } + constexpr Sub& operator/=(T aScale) { + value /= aScale; + return *static_cast<Sub*>(this); + } + + // Since BaseCoord is implicitly convertible to its value type T, we need + // mixed-type operator overloads to avoid ambiguities at mixed-type call + // sites. As we transition more of our code to strongly-typed classes, we + // may be able to remove some or all of these overloads. + friend constexpr bool operator==(Sub aA, T aB) { return aA.value == aB; } + friend constexpr bool operator==(T aA, Sub aB) { return aA == aB.value; } + friend constexpr bool operator!=(Sub aA, T aB) { return aA.value != aB; } + friend constexpr bool operator!=(T aA, Sub aB) { return aA != aB.value; } + friend constexpr T operator+(Sub aA, T aB) { return aA.value + aB; } + friend constexpr T operator+(T aA, Sub aB) { return aA + aB.value; } + friend constexpr T operator-(Sub aA, T aB) { return aA.value - aB; } + friend constexpr T operator-(T aA, Sub aB) { return aA - aB.value; } + + constexpr Sub operator-() const { return Sub(-value); } + + friend std::ostream& operator<<(std::ostream& aStream, + const BaseCoord<T, Sub>& aCoord) { + return aStream << aCoord.value; + } +}; + +} // namespace mozilla::gfx + +#endif /* MOZILLA_GFX_BASECOORD_H_ */ diff --git a/gfx/2d/BaseMargin.h b/gfx/2d/BaseMargin.h new file mode 100644 index 0000000000..469541e617 --- /dev/null +++ b/gfx/2d/BaseMargin.h @@ -0,0 +1,164 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_BASEMARGIN_H_ +#define MOZILLA_GFX_BASEMARGIN_H_ + +#include <ostream> + +#include "Types.h" + +namespace mozilla { + +/** + * Sides represents a set of physical sides. + */ +struct Sides final { + Sides() : mBits(SideBits::eNone) {} + explicit Sides(SideBits aSideBits) { + MOZ_ASSERT((aSideBits & ~SideBits::eAll) == SideBits::eNone, + "illegal side bits"); + mBits = aSideBits; + } + bool IsEmpty() const { return mBits == SideBits::eNone; } + bool Top() const { return (mBits & SideBits::eTop) == SideBits::eTop; } + bool Right() const { return (mBits & SideBits::eRight) == SideBits::eRight; } + bool Bottom() const { + return (mBits & SideBits::eBottom) == SideBits::eBottom; + } + bool Left() const { return (mBits & SideBits::eLeft) == SideBits::eLeft; } + bool Contains(SideBits aSideBits) const { + MOZ_ASSERT(!(aSideBits & ~SideBits::eAll), "illegal side bits"); + return (mBits & aSideBits) == aSideBits; + } + Sides operator|(Sides aOther) const { + return Sides(SideBits(mBits | aOther.mBits)); + } + Sides operator|(SideBits aSideBits) const { return *this | Sides(aSideBits); } + Sides& operator|=(Sides aOther) { + mBits |= aOther.mBits; + return *this; + } + Sides& operator|=(SideBits aSideBits) { return *this |= Sides(aSideBits); } + bool operator==(Sides aOther) const { return mBits == aOther.mBits; } + bool operator!=(Sides aOther) const { return !(*this == aOther); } + + private: + SideBits mBits; +}; + +namespace gfx { + +/** + * Do not use this class directly. Subclass it, pass that subclass as the + * Sub parameter, and only use that subclass. + */ +template <class T, class Sub, class Coord = T> +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. + Coord top, right, bottom, left; + + // Constructors + BaseMargin() : top(0), right(0), bottom(0), left(0) {} + BaseMargin(Coord aTop, Coord aRight, Coord aBottom, Coord aLeft) + : top(aTop), right(aRight), bottom(aBottom), left(aLeft) {} + + void SizeTo(Coord aTop, Coord aRight, Coord aBottom, Coord aLeft) { + top = aTop; + right = aRight; + bottom = aBottom; + left = aLeft; + } + + Coord LeftRight() const { return left + right; } + Coord TopBottom() const { return top + bottom; } + + Coord& Side(SideT aSide) { + // This is ugly! + return *(&top + int(aSide)); + } + Coord Side(SideT aSide) const { + // This is ugly! + return *(&top + int(aSide)); + } + + Sub& ApplySkipSides(Sides aSkipSides) { + if (aSkipSides.Top()) { + top = 0; + } + if (aSkipSides.Right()) { + right = 0; + } + if (aSkipSides.Bottom()) { + bottom = 0; + } + if (aSkipSides.Left()) { + left = 0; + } + return *static_cast<Sub*>(this); + } + + // Ensures that all our sides are at least as big as the argument. + void EnsureAtLeast(const BaseMargin& aMargin) { + top = std::max(top, aMargin.top); + right = std::max(right, aMargin.right); + bottom = std::max(bottom, aMargin.bottom); + left = std::max(left, aMargin.left); + } + + // Ensures that all our sides are at most as big as the argument. + void EnsureAtMost(const BaseMargin& aMargin) { + top = std::min(top, aMargin.top); + right = std::min(right, aMargin.right); + bottom = std::min(bottom, aMargin.bottom); + left = std::min(left, aMargin.left); + } + + // Overloaded operators. Note that '=' isn't defined so we'll get the + // compiler generated default assignment operator + bool operator==(const Sub& aMargin) const { + return top == aMargin.top && right == aMargin.right && + bottom == aMargin.bottom && left == aMargin.left; + } + bool operator!=(const Sub& aMargin) const { return !(*this == aMargin); } + Sub operator+(const Sub& aMargin) const { + return Sub(top + aMargin.top, right + aMargin.right, + bottom + aMargin.bottom, left + aMargin.left); + } + Sub operator-(const Sub& aMargin) const { + return Sub(top - aMargin.top, right - aMargin.right, + bottom - aMargin.bottom, left - aMargin.left); + } + Sub operator-() const { return Sub(-top, -right, -bottom, -left); } + Sub& operator+=(const Sub& aMargin) { + top += aMargin.top; + right += aMargin.right; + bottom += aMargin.bottom; + left += aMargin.left; + return *static_cast<Sub*>(this); + } + Sub& operator-=(const Sub& aMargin) { + top -= aMargin.top; + right -= aMargin.right; + bottom -= aMargin.bottom; + left -= aMargin.left; + return *static_cast<Sub*>(this); + } + + friend std::ostream& operator<<(std::ostream& aStream, + const BaseMargin& aMargin) { + return aStream << "(t=" << aMargin.top << ", r=" << aMargin.right + << ", b=" << aMargin.bottom << ", l=" << aMargin.left << ')'; + } +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_BASEMARGIN_H_ */ diff --git a/gfx/2d/BasePoint.h b/gfx/2d/BasePoint.h new file mode 100644 index 0000000000..7f5dd7e1e3 --- /dev/null +++ b/gfx/2d/BasePoint.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_BASEPOINT_H_ +#define MOZILLA_GFX_BASEPOINT_H_ + +#include <cmath> +#include <ostream> +#include <type_traits> +#include "mozilla/Attributes.h" +#include "mozilla/FloatingPoint.h" + +namespace mozilla { +namespace gfx { + +/** + * Do not use this class directly. Subclass it, pass that subclass as the + * Sub parameter, and only use that subclass. This allows methods to safely + * cast 'this' to 'Sub*'. + */ +template <class T, class Sub, class Coord = T> +struct BasePoint { + union { + struct { + Coord x, y; + }; + Coord components[2]; + }; + + // Constructors + constexpr BasePoint() : x(0), y(0) {} + constexpr BasePoint(Coord aX, Coord aY) : x(aX), y(aY) {} + + MOZ_ALWAYS_INLINE Coord X() const { return x; } + MOZ_ALWAYS_INLINE Coord Y() const { return y; } + + void MoveTo(Coord aX, Coord aY) { + x = aX; + y = aY; + } + void MoveBy(Coord aDx, Coord aDy) { + x += aDx; + y += aDy; + } + + // Note that '=' isn't defined so we'll get the + // compiler generated default assignment operator + + bool operator==(const Sub& aPoint) const { + return x == aPoint.x && y == aPoint.y; + } + bool operator!=(const Sub& aPoint) const { + return x != aPoint.x || y != aPoint.y; + } + + Sub operator+(const Sub& aPoint) const { + return Sub(x + aPoint.x, y + aPoint.y); + } + Sub operator-(const Sub& aPoint) const { + return Sub(x - aPoint.x, y - aPoint.y); + } + Sub& operator+=(const Sub& aPoint) { + x += aPoint.x; + y += aPoint.y; + return *static_cast<Sub*>(this); + } + Sub& operator-=(const Sub& aPoint) { + x -= aPoint.x; + y -= aPoint.y; + return *static_cast<Sub*>(this); + } + + Sub operator*(T aScale) const { return Sub(x * aScale, y * aScale); } + Sub operator/(T aScale) const { return Sub(x / aScale, y / aScale); } + + Sub operator-() const { return Sub(-x, -y); } + + T DotProduct(const Sub& aPoint) const { + return x.value * aPoint.x.value + y.value * aPoint.y.value; + } + + // FIXME: Maybe Length() should return a float Coord event for integer Points? + Coord Length() const { + return static_cast<decltype(x.value)>(hypot(x.value, y.value)); + } + + T LengthSquare() const { return x.value * x.value + y.value * y.value; } + + // Round() is *not* rounding to nearest integer if the values are negative. + // They are always rounding as floor(n + 0.5). + // See https://bugzilla.mozilla.org/show_bug.cgi?id=410748#c14 + Sub& Round() { + x = Coord(std::floor(T(x) + T(0.5f))); + y = Coord(std::floor(T(y) + T(0.5f))); + return *static_cast<Sub*>(this); + } + + // "Finite" means not inf and not NaN + bool IsFinite() const { + using FloatType = + std::conditional_t<std::is_same_v<T, float>, float, double>; + return (std::isfinite(FloatType(x)) && std::isfinite(FloatType(y))); + } + + void Clamp(Coord aMaxAbsValue) { + x = std::max(std::min(x, aMaxAbsValue), -aMaxAbsValue); + y = std::max(std::min(y, aMaxAbsValue), -aMaxAbsValue); + } + + friend std::ostream& operator<<(std::ostream& stream, + const BasePoint<T, Sub, Coord>& aPoint) { + return stream << '(' << aPoint.x << ',' << aPoint.y << ')'; + } +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_BASEPOINT_H_ */ diff --git a/gfx/2d/BasePoint3D.h b/gfx/2d/BasePoint3D.h new file mode 100644 index 0000000000..36d272c61b --- /dev/null +++ b/gfx/2d/BasePoint3D.h @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_BASEPOINT3D_H_ +#define MOZILLA_BASEPOINT3D_H_ + +#include "mozilla/Assertions.h" + +namespace mozilla { +namespace gfx { + +/** + * Do not use this class directly. Subclass it, pass that subclass as the + * Sub parameter, and only use that subclass. This allows methods to safely + * cast 'this' to 'Sub*'. + */ +template <class T, class Sub> +struct BasePoint3D { + union { + struct { + T x, y, z; + }; + T components[3]; + }; + + // Constructors + BasePoint3D() : x(0), y(0), z(0) {} + BasePoint3D(T aX, T aY, T aZ) : x(aX), y(aY), z(aZ) {} + + void MoveTo(T aX, T aY, T aZ) { + x = aX; + y = aY; + z = aZ; + } + void MoveBy(T aDx, T aDy, T aDz) { + x += aDx; + y += aDy; + z += aDz; + } + + // Note that '=' isn't defined so we'll get the + // compiler generated default assignment operator + + T& operator[](int aIndex) { + MOZ_ASSERT(aIndex >= 0 && aIndex <= 2); + return *((&x) + aIndex); + } + + const T& operator[](int aIndex) const { + MOZ_ASSERT(aIndex >= 0 && aIndex <= 2); + return *((&x) + aIndex); + } + + bool operator==(const Sub& aPoint) const { + return x == aPoint.x && y == aPoint.y && z == aPoint.z; + } + bool operator!=(const Sub& aPoint) const { + return x != aPoint.x || y != aPoint.y || z != aPoint.z; + } + + Sub operator+(const Sub& aPoint) const { + return Sub(x + aPoint.x, y + aPoint.y, z + aPoint.z); + } + Sub operator-(const Sub& aPoint) const { + return Sub(x - aPoint.x, y - aPoint.y, z - aPoint.z); + } + Sub& operator+=(const Sub& aPoint) { + x += aPoint.x; + y += aPoint.y; + z += aPoint.z; + return *static_cast<Sub*>(this); + } + Sub& operator-=(const Sub& aPoint) { + x -= aPoint.x; + y -= aPoint.y; + z -= aPoint.z; + return *static_cast<Sub*>(this); + } + + Sub operator*(T aScale) const { + return Sub(x * aScale, y * aScale, z * aScale); + } + Sub operator/(T aScale) const { + return Sub(x / aScale, y / aScale, z / aScale); + } + + Sub& operator*=(T aScale) { + x *= aScale; + y *= aScale; + z *= aScale; + return *static_cast<Sub*>(this); + } + + Sub& operator/=(T aScale) { + x /= aScale; + y /= aScale; + z /= aScale; + return *static_cast<Sub*>(this); + } + + Sub operator-() const { return Sub(-x, -y, -z); } + + Sub CrossProduct(const Sub& aPoint) const { + return Sub(y * aPoint.z - aPoint.y * z, z * aPoint.x - aPoint.z * x, + x * aPoint.y - aPoint.x * y); + } + + T DotProduct(const Sub& aPoint) const { + return x * aPoint.x + y * aPoint.y + z * aPoint.z; + } + + T Length() const { return sqrt(x * x + y * y + z * z); } + + // Invalid for points with distance from origin of 0. + void Normalize() { *this /= Length(); } + + void RobustNormalize() { + // If the distance is infinite, we scale it by 1/(the maximum value of T) + // before doing normalization, so we can avoid getting a zero point. + T length = Length(); + if (std::isinf(length)) { + *this /= std::numeric_limits<T>::max(); + length = Length(); + } + + *this /= length; + } + + friend std::ostream& operator<<(std::ostream& stream, + const BasePoint3D<T, Sub>& aPoint) { + return stream << '(' << aPoint.x << ',' << aPoint.y << ',' << aPoint.z + << ')'; + } +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_BASEPOINT3D_H_ */ diff --git a/gfx/2d/BasePoint4D.h b/gfx/2d/BasePoint4D.h new file mode 100644 index 0000000000..2dd4cb11d2 --- /dev/null +++ b/gfx/2d/BasePoint4D.h @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_BASEPOINT4D_H_ +#define MOZILLA_BASEPOINT4D_H_ + +#include "mozilla/Assertions.h" + +namespace mozilla { +namespace gfx { + +/** + * Do not use this class directly. Subclass it, pass that subclass as the + * Sub parameter, and only use that subclass. This allows methods to safely + * cast 'this' to 'Sub*'. + */ +template <class T, class Sub> +struct BasePoint4D { + union { + struct { + T x, y, z, w; + }; + T components[4]; + }; + + // Constructors + BasePoint4D() : x(0), y(0), z(0), w(0) {} + BasePoint4D(T aX, T aY, T aZ, T aW) : x(aX), y(aY), z(aZ), w(aW) {} + + void MoveTo(T aX, T aY, T aZ, T aW) { + x = aX; + y = aY; + z = aZ; + w = aW; + } + void MoveBy(T aDx, T aDy, T aDz, T aDw) { + x += aDx; + y += aDy; + z += aDz; + w += aDw; + } + + // Note that '=' isn't defined so we'll get the + // compiler generated default assignment operator + + bool operator==(const Sub& aPoint) const { + return x == aPoint.x && y == aPoint.y && z == aPoint.z && w == aPoint.w; + } + bool operator!=(const Sub& aPoint) const { + return x != aPoint.x || y != aPoint.y || z != aPoint.z || w != aPoint.w; + } + + Sub operator+(const Sub& aPoint) const { + return Sub(x + aPoint.x, y + aPoint.y, z + aPoint.z, w + aPoint.w); + } + Sub operator-(const Sub& aPoint) const { + return Sub(x - aPoint.x, y - aPoint.y, z - aPoint.z, w - aPoint.w); + } + Sub& operator+=(const Sub& aPoint) { + x += aPoint.x; + y += aPoint.y; + z += aPoint.z; + w += aPoint.w; + return *static_cast<Sub*>(this); + } + Sub& operator-=(const Sub& aPoint) { + x -= aPoint.x; + y -= aPoint.y; + z -= aPoint.z; + w -= aPoint.w; + return *static_cast<Sub*>(this); + } + + Sub operator*(T aScale) const { + return Sub(x * aScale, y * aScale, z * aScale, w * aScale); + } + Sub operator/(T aScale) const { + return Sub(x / aScale, y / aScale, z / aScale, w / aScale); + } + + Sub& operator*=(T aScale) { + x *= aScale; + y *= aScale; + z *= aScale; + w *= aScale; + return *static_cast<Sub*>(this); + } + + Sub& operator/=(T aScale) { + x /= aScale; + y /= aScale; + z /= aScale; + w /= aScale; + return *static_cast<Sub*>(this); + } + + Sub operator-() const { return Sub(-x, -y, -z, -w); } + + T& operator[](int aIndex) { + MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid array index"); + return *((&x) + aIndex); + } + + const T& operator[](int aIndex) const { + MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid array index"); + return *((&x) + aIndex); + } + + T DotProduct(const Sub& aPoint) const { + return x * aPoint.x + y * aPoint.y + z * aPoint.z + w * aPoint.w; + } + + // Ignores the 4th component! + Sub CrossProduct(const Sub& aPoint) const { + return Sub(y * aPoint.z - aPoint.y * z, z * aPoint.x - aPoint.z * x, + x * aPoint.y - aPoint.x * y, 0); + } + + T Length() const { return sqrt(x * x + y * y + z * z + w * w); } + + void Normalize() { *this /= Length(); } + + bool HasPositiveWCoord() { return w > 0; } +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_BASEPOINT4D_H_ */ diff --git a/gfx/2d/BaseRect.h b/gfx/2d/BaseRect.h new file mode 100644 index 0000000000..70d82050b3 --- /dev/null +++ b/gfx/2d/BaseRect.h @@ -0,0 +1,751 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_BASERECT_H_ +#define MOZILLA_GFX_BASERECT_H_ + +#include <algorithm> +#include <cmath> +#include <ostream> +#include <type_traits> + +#include "mozilla/Assertions.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/gfx/ScaleFactors2D.h" +#include "Types.h" + +namespace mozilla::gfx { + +/** + * Rectangles have two interpretations: a set of (zero-size) points, + * and a rectangular area of the plane. Most rectangle operations behave + * the same no matter what interpretation is being used, but some operations + * differ: + * -- Equality tests behave differently. When a rectangle represents an area, + * all zero-width and zero-height rectangles are equal to each other since they + * represent the empty area. But when a rectangle represents a set of + * mathematical points, zero-width and zero-height rectangles can be unequal. + * -- The union operation can behave differently. When rectangles represent + * areas, taking the union of a zero-width or zero-height rectangle with + * another rectangle can just ignore the empty rectangle. But when rectangles + * represent sets of mathematical points, we may need to extend the latter + * rectangle to include the points of a zero-width or zero-height rectangle. + * + * To ensure that these interpretations are explicitly disambiguated, we + * deny access to the == and != operators and require use of IsEqualEdges and + * IsEqualInterior instead. Similarly we provide separate Union and UnionEdges + * methods. + * + * Do not use this class directly. Subclass it, pass that subclass as the + * Sub parameter, and only use that subclass. + */ +template <class T, class Sub, class Point, class SizeT, class MarginT> +struct BaseRect { + T x, y, width, height; + + // Constructors + BaseRect() : x(0), y(0), width(0), height(0) {} + BaseRect(const Point& aOrigin, const SizeT& aSize) + : x(aOrigin.x), y(aOrigin.y), width(aSize.width), height(aSize.height) {} + BaseRect(T aX, T aY, T aWidth, T aHeight) + : x(aX), y(aY), width(aWidth), height(aHeight) {} + + // Emptiness. An empty rect is one that has no area, i.e. its height or width + // is <= 0. Zero rect is the one with height and width set to zero. Note + // that SetEmpty() may change a rectangle that identified as IsEmpty(). + MOZ_ALWAYS_INLINE bool IsZeroArea() const { + return height == 0 || width == 0; + } + MOZ_ALWAYS_INLINE bool IsEmpty() const { return height <= 0 || width <= 0; } + void SetEmpty() { width = height = 0; } + + // "Finite" means not inf and not NaN + bool IsFinite() const { + using FloatType = + std::conditional_t<std::is_same_v<T, float>, float, double>; + return (std::isfinite(FloatType(x)) && std::isfinite(FloatType(y)) && + std::isfinite(FloatType(width)) && + std::isfinite(FloatType(height))); + } + + // Returns true if this rectangle contains the interior of aRect. Always + // returns true if aRect is empty, and always returns false is aRect is + // nonempty but this rect is empty. + bool Contains(const Sub& aRect) const { + return aRect.IsEmpty() || (x <= aRect.x && aRect.XMost() <= XMost() && + y <= aRect.y && aRect.YMost() <= YMost()); + } + // Returns true if this rectangle contains the point. Points are considered + // in the rectangle if they are on the left or top edge, but outside if they + // are on the right or bottom edge. + MOZ_ALWAYS_INLINE bool Contains(T aX, T aY) const { + return x <= aX && aX < XMost() && y <= aY && aY < YMost(); + } + MOZ_ALWAYS_INLINE bool ContainsX(T aX) const { + return x <= aX && aX < XMost(); + } + MOZ_ALWAYS_INLINE bool ContainsY(T aY) const { + return y <= aY && aY < YMost(); + } + // Returns true if this rectangle contains the point. Points are considered + // in the rectangle if they are on the left or top edge, but outside if they + // are on the right or bottom edge. + bool Contains(const Point& aPoint) const { + return Contains(aPoint.x, aPoint.y); + } + + // Returns true if this rectangle contains the point, considering points on + // all edges of the rectangle to be contained (as compared to Contains() + // which only includes points on the top & left but not bottom & right edges). + MOZ_ALWAYS_INLINE bool ContainsInclusively(const Point& aPoint) const { + return x <= aPoint.x && aPoint.x <= XMost() && y <= aPoint.y && + aPoint.y <= YMost(); + } + + // Intersection. Returns TRUE if the receiver's area has non-empty + // intersection with aRect's area, and FALSE otherwise. + // Always returns false if aRect is empty or 'this' is empty. + bool Intersects(const Sub& aRect) const { + return !IsEmpty() && !aRect.IsEmpty() && x < aRect.XMost() && + aRect.x < XMost() && y < aRect.YMost() && aRect.y < YMost(); + } + // Returns the rectangle containing the intersection of the points + // (including edges) of *this and aRect. If there are no points in that + // intersection, returns an empty rectangle with x/y set to the std::max of + // the x/y of *this and aRect. + // + // Intersection with an empty Rect may not produce an empty Rect if overflow + // occurs. e.g. {INT_MIN, 0, 0, 20} Intersect { 5000, 0, 500, 20 } gives: + // the non-emtpy {5000, 0, 500, 20 } instead of {5000, 0, 0, 0} + [[nodiscard]] Sub Intersect(const Sub& aRect) const { + Sub result; + result.x = std::max<T>(x, aRect.x); + result.y = std::max<T>(y, aRect.y); + result.width = + std::min<T>(x - result.x + width, aRect.x - result.x + aRect.width); + result.height = + std::min<T>(y - result.y + height, aRect.y - result.y + aRect.height); + // See bug 1457110, this function expects to -only- size to 0,0 if the + // width/height is explicitly negative. + if (result.width < 0 || result.height < 0) { + result.SizeTo(0, 0); + } + return result; + } + + // Gives the same results as Intersect() but handles integer overflow + // better. This comes at a tiny cost in performance. + // e.g. {INT_MIN, 0, 0, 20} Intersect { 5000, 0, 500, 20 } gives: + // {5000, 0, 0, 0} + [[nodiscard]] Sub SafeIntersect(const Sub& aRect) const { + Sub result; + result.x = std::max<T>(x, aRect.x); + result.y = std::max<T>(y, aRect.y); + T right = std::min<T>(x + width, aRect.x + aRect.width); + T bottom = std::min<T>(y + height, aRect.y + aRect.height); + // See bug 1457110, this function expects to -only- size to 0,0 if the + // width/height is explicitly negative. + if (right < result.x || bottom < result.y) { + result.width = 0; + result.height = 0; + } else { + result.width = right - result.x; + result.height = bottom - result.y; + } + return result; + } + + // Sets *this to be the rectangle containing the intersection of the points + // (including edges) of *this and aRect. If there are no points in that + // intersection, sets *this to be an empty rectangle with x/y set to the + // std::max of the x/y of *this and aRect. + // + // 'this' can be the same object as either aRect1 or aRect2 + bool IntersectRect(const Sub& aRect1, const Sub& aRect2) { + T newX = std::max<T>(aRect1.x, aRect2.x); + T newY = std::max<T>(aRect1.y, aRect2.y); + width = std::min<T>(aRect1.x - newX + aRect1.width, + aRect2.x - newX + aRect2.width); + height = std::min<T>(aRect1.y - newY + aRect1.height, + aRect2.y - newY + aRect2.height); + x = newX; + y = newY; + if (width <= 0 || height <= 0) { + SizeTo(0, 0); + return false; + } + return true; + } + + // Returns the smallest rectangle that contains both the area of both + // this and aRect. Thus, empty input rectangles are ignored. + // Note: if both rectangles are empty, returns aRect. + // WARNING! This is not safe against overflow, prefer using SafeUnion instead + // when dealing with int-based rects. + [[nodiscard]] Sub Union(const Sub& aRect) const { + if (IsEmpty()) { + return aRect; + } else if (aRect.IsEmpty()) { + return *static_cast<const Sub*>(this); + } else { + return UnionEdges(aRect); + } + } + // Returns the smallest rectangle that contains both the points (including + // edges) of both aRect1 and aRect2. + // Thus, empty input rectangles are allowed to affect the result. + // WARNING! This is not safe against overflow, prefer using SafeUnionEdges + // instead when dealing with int-based rects. + [[nodiscard]] Sub UnionEdges(const Sub& aRect) const { + Sub result; + result.x = std::min(x, aRect.x); + result.y = std::min(y, aRect.y); + result.width = std::max(XMost(), aRect.XMost()) - result.x; + result.height = std::max(YMost(), aRect.YMost()) - result.y; + return result; + } + // Computes the smallest rectangle that contains both the area of both + // aRect1 and aRect2, and fills 'this' with the result. + // Thus, empty input rectangles are ignored. + // If both rectangles are empty, sets 'this' to aRect2. + // + // 'this' can be the same object as either aRect1 or aRect2 + void UnionRect(const Sub& aRect1, const Sub& aRect2) { + *static_cast<Sub*>(this) = aRect1.Union(aRect2); + } + + void OrWith(const Sub& aRect1) { + UnionRect(*static_cast<Sub*>(this), aRect1); + } + + // Computes the smallest rectangle that contains both the points (including + // edges) of both aRect1 and aRect2. + // Thus, empty input rectangles are allowed to affect the result. + // + // 'this' can be the same object as either aRect1 or aRect2 + void UnionRectEdges(const Sub& aRect1, const Sub& aRect2) { + *static_cast<Sub*>(this) = aRect1.UnionEdges(aRect2); + } + + // Expands the rect to include the point + void ExpandToEnclose(const Point& aPoint) { + if (aPoint.x < x) { + width = XMost() - aPoint.x; + x = aPoint.x; + } else if (aPoint.x > XMost()) { + width = aPoint.x - x; + } + if (aPoint.y < y) { + height = YMost() - aPoint.y; + y = aPoint.y; + } else if (aPoint.y > YMost()) { + height = aPoint.y - y; + } + } + + MOZ_ALWAYS_INLINE void SetRect(T aX, T aY, T aWidth, T aHeight) { + x = aX; + y = aY; + width = aWidth; + height = aHeight; + } + MOZ_ALWAYS_INLINE void SetRectX(T aX, T aWidth) { + x = aX; + width = aWidth; + } + MOZ_ALWAYS_INLINE void SetRectY(T aY, T aHeight) { + y = aY; + height = aHeight; + } + MOZ_ALWAYS_INLINE void SetBox(T aX, T aY, T aXMost, T aYMost) { + x = aX; + y = aY; + width = aXMost - aX; + height = aYMost - aY; + } + MOZ_ALWAYS_INLINE void SetNonEmptyBox(T aX, T aY, T aXMost, T aYMost) { + x = aX; + y = aY; + width = std::max(0, aXMost - aX); + height = std::max(0, aYMost - aY); + } + MOZ_ALWAYS_INLINE void SetBoxX(T aX, T aXMost) { + x = aX; + width = aXMost - aX; + } + MOZ_ALWAYS_INLINE void SetBoxY(T aY, T aYMost) { + y = aY; + height = aYMost - aY; + } + void SetRect(const Point& aPt, const SizeT& aSize) { + SetRect(aPt.x, aPt.y, aSize.width, aSize.height); + } + MOZ_ALWAYS_INLINE void GetRect(T* aX, T* aY, T* aWidth, T* aHeight) const { + *aX = x; + *aY = y; + *aWidth = width; + *aHeight = height; + } + + MOZ_ALWAYS_INLINE void MoveTo(T aX, T aY) { + x = aX; + y = aY; + } + MOZ_ALWAYS_INLINE void MoveToX(T aX) { x = aX; } + MOZ_ALWAYS_INLINE void MoveToY(T aY) { y = aY; } + MOZ_ALWAYS_INLINE void MoveTo(const Point& aPoint) { + x = aPoint.x; + y = aPoint.y; + } + MOZ_ALWAYS_INLINE void MoveBy(T aDx, T aDy) { + x += aDx; + y += aDy; + } + MOZ_ALWAYS_INLINE void MoveByX(T aDx) { x += aDx; } + MOZ_ALWAYS_INLINE void MoveByY(T aDy) { y += aDy; } + MOZ_ALWAYS_INLINE void MoveBy(const Point& aPoint) { + x += aPoint.x; + y += aPoint.y; + } + MOZ_ALWAYS_INLINE void SizeTo(T aWidth, T aHeight) { + width = aWidth; + height = aHeight; + } + MOZ_ALWAYS_INLINE void SizeTo(const SizeT& aSize) { + width = aSize.width; + height = aSize.height; + } + + // Variant of MoveBy that ensures that even after translation by a point that + // the rectangle coordinates will still fit within numeric limits. The origin + // and size will be clipped within numeric limits to ensure this. + void SafeMoveByX(T aDx) { + T x2 = XMost(); + if (aDx >= T(0)) { + T limit = std::numeric_limits<T>::max(); + x = limit - aDx < x ? limit : x + aDx; + width = (limit - aDx < x2 ? limit : x2 + aDx) - x; + } else { + T limit = std::numeric_limits<T>::min(); + x = limit - aDx > x ? limit : x + aDx; + width = (limit - aDx > x2 ? limit : x2 + aDx) - x; + } + } + void SafeMoveByY(T aDy) { + T y2 = YMost(); + if (aDy >= T(0)) { + T limit = std::numeric_limits<T>::max(); + y = limit - aDy < y ? limit : y + aDy; + height = (limit - aDy < y2 ? limit : y2 + aDy) - y; + } else { + T limit = std::numeric_limits<T>::min(); + y = limit - aDy > y ? limit : y + aDy; + height = (limit - aDy > y2 ? limit : y2 + aDy) - y; + } + } + void SafeMoveBy(T aDx, T aDy) { + SafeMoveByX(aDx); + SafeMoveByY(aDy); + } + void SafeMoveBy(const Point& aPoint) { SafeMoveBy(aPoint.x, aPoint.y); } + + void Inflate(T aD) { Inflate(aD, aD); } + void Inflate(T aDx, T aDy) { + x -= aDx; + y -= aDy; + width += 2 * aDx; + height += 2 * aDy; + } + void Inflate(const MarginT& aMargin) { + x -= aMargin.left; + y -= aMargin.top; + width += aMargin.LeftRight(); + height += aMargin.TopBottom(); + } + void Inflate(const SizeT& aSize) { Inflate(aSize.width, aSize.height); } + + void Deflate(T aD) { Deflate(aD, aD); } + void Deflate(T aDx, T aDy) { + x += aDx; + y += aDy; + width = std::max(T(0), width - 2 * aDx); + height = std::max(T(0), height - 2 * aDy); + } + void Deflate(const MarginT& aMargin) { + x += aMargin.left; + y += aMargin.top; + width = std::max(T(0), width - aMargin.LeftRight()); + height = std::max(T(0), height - aMargin.TopBottom()); + } + void Deflate(const SizeT& aSize) { Deflate(aSize.width, aSize.height); } + + // Return true if the rectangles contain the same set of points, including + // points on the edges. + // Use when we care about the exact x/y/width/height values being + // equal (i.e. we care about differences in empty rectangles). + bool IsEqualEdges(const Sub& aRect) const { + return x == aRect.x && y == aRect.y && width == aRect.width && + height == aRect.height; + } + MOZ_ALWAYS_INLINE bool IsEqualRect(T aX, T aY, T aW, T aH) { + return x == aX && y == aY && width == aW && height == aH; + } + MOZ_ALWAYS_INLINE bool IsEqualXY(T aX, T aY) { return x == aX && y == aY; } + + MOZ_ALWAYS_INLINE bool IsEqualSize(T aW, T aH) { + return width == aW && height == aH; + } + + // Return true if the rectangles contain the same area of the plane. + // Use when we do not care about differences in empty rectangles. + bool IsEqualInterior(const Sub& aRect) const { + return IsEqualEdges(aRect) || (IsEmpty() && aRect.IsEmpty()); + } + + friend Sub operator+(Sub aSub, const Point& aPoint) { + aSub += aPoint; + return aSub; + } + friend Sub operator-(Sub aSub, const Point& aPoint) { + aSub -= aPoint; + return aSub; + } + friend Sub operator+(Sub aSub, const SizeT& aSize) { + aSub += aSize; + return aSub; + } + friend Sub operator-(Sub aSub, const SizeT& aSize) { + aSub -= aSize; + return aSub; + } + Sub& operator+=(const Point& aPoint) { + MoveBy(aPoint); + return *static_cast<Sub*>(this); + } + Sub& operator-=(const Point& aPoint) { + MoveBy(-aPoint); + return *static_cast<Sub*>(this); + } + Sub& operator+=(const SizeT& aSize) { + width += aSize.width; + height += aSize.height; + return *static_cast<Sub*>(this); + } + Sub& operator-=(const SizeT& aSize) { + width -= aSize.width; + height -= aSize.height; + return *static_cast<Sub*>(this); + } + // Find difference as a Margin + MarginT operator-(const Sub& aRect) const { + return MarginT(aRect.y - y, XMost() - aRect.XMost(), + YMost() - aRect.YMost(), aRect.x - x); + } + + // Helpers for accessing the vertices + Point TopLeft() const { return Point(x, y); } + Point TopRight() const { return Point(XMost(), y); } + Point BottomLeft() const { return Point(x, YMost()); } + Point BottomRight() const { return Point(XMost(), YMost()); } + Point AtCorner(Corner aCorner) const { + switch (aCorner) { + case eCornerTopLeft: + return TopLeft(); + case eCornerTopRight: + return TopRight(); + case eCornerBottomRight: + return BottomRight(); + case eCornerBottomLeft: + return BottomLeft(); + } + MOZ_CRASH("GFX: Incomplete switch"); + } + Point CCWCorner(mozilla::Side side) const { + switch (side) { + case eSideTop: + return TopLeft(); + case eSideRight: + return TopRight(); + case eSideBottom: + return BottomRight(); + case eSideLeft: + return BottomLeft(); + } + MOZ_CRASH("GFX: Incomplete switch"); + } + Point CWCorner(mozilla::Side side) const { + switch (side) { + case eSideTop: + return TopRight(); + case eSideRight: + return BottomRight(); + case eSideBottom: + return BottomLeft(); + case eSideLeft: + return TopLeft(); + } + MOZ_CRASH("GFX: Incomplete switch"); + } + Point Center() const { return Point(x, y) + Point(width, height) / 2; } + SizeT Size() const { return SizeT(width, height); } + + T Area() const { return width * height; } + + // Helper methods for computing the extents + MOZ_ALWAYS_INLINE T X() const { return x; } + MOZ_ALWAYS_INLINE T Y() const { return y; } + MOZ_ALWAYS_INLINE T Width() const { return width; } + MOZ_ALWAYS_INLINE T Height() const { return height; } + MOZ_ALWAYS_INLINE T XMost() const { return x + width; } + MOZ_ALWAYS_INLINE T YMost() const { return y + height; } + + // Set width and height. SizeTo() sets them together. + MOZ_ALWAYS_INLINE void SetWidth(T aWidth) { width = aWidth; } + MOZ_ALWAYS_INLINE void SetHeight(T aHeight) { height = aHeight; } + + // Get the coordinate of the edge on the given side. + T Edge(mozilla::Side aSide) const { + switch (aSide) { + case eSideTop: + return Y(); + case eSideRight: + return XMost(); + case eSideBottom: + return YMost(); + case eSideLeft: + return X(); + } + MOZ_CRASH("GFX: Incomplete switch"); + } + + // Moves one edge of the rect without moving the opposite edge. + void SetLeftEdge(T aX) { + width = XMost() - aX; + x = aX; + } + void SetRightEdge(T aXMost) { width = aXMost - x; } + void SetTopEdge(T aY) { + height = YMost() - aY; + y = aY; + } + void SetBottomEdge(T aYMost) { height = aYMost - y; } + void Swap() { + std::swap(x, y); + std::swap(width, height); + } + + // Round the rectangle edges to integer coordinates, such that the rounded + // rectangle has the same set of pixel centers as the original rectangle. + // Edges at offset 0.5 round up. + // Suitable for most places where integral device coordinates + // are needed, but note that any translation should be applied first to + // avoid pixel rounding errors. + // Note that this is *not* rounding to nearest integer if the values are + // negative. They are always rounding as floor(n + 0.5). See + // https://bugzilla.mozilla.org/show_bug.cgi?id=410748#c14 If you need similar + // method which is using NS_round(), you should create new + // |RoundAwayFromZero()| method. + void Round() { + T x0 = static_cast<T>(std::floor(T(X()) + 0.5f)); + T y0 = static_cast<T>(std::floor(T(Y()) + 0.5f)); + T x1 = static_cast<T>(std::floor(T(XMost()) + 0.5f)); + T y1 = static_cast<T>(std::floor(T(YMost()) + 0.5f)); + + x = x0; + y = y0; + + width = x1 - x0; + height = y1 - y0; + } + + // Snap the rectangle edges to integer coordinates, such that the + // original rectangle contains the resulting rectangle. + void RoundIn() { + T x0 = static_cast<T>(std::ceil(T(X()))); + T y0 = static_cast<T>(std::ceil(T(Y()))); + T x1 = static_cast<T>(std::floor(T(XMost()))); + T y1 = static_cast<T>(std::floor(T(YMost()))); + + x = x0; + y = y0; + + width = x1 - x0; + height = y1 - y0; + } + + // Snap the rectangle edges to integer coordinates, such that the + // resulting rectangle contains the original rectangle. + void RoundOut() { + T x0 = static_cast<T>(std::floor(T(X()))); + T y0 = static_cast<T>(std::floor(T(Y()))); + T x1 = static_cast<T>(std::ceil(T(XMost()))); + T y1 = static_cast<T>(std::ceil(T(YMost()))); + + x = x0; + y = y0; + + width = x1 - x0; + height = y1 - y0; + } + + // Scale 'this' by aScale.xScale and aScale.yScale without doing any rounding. + template <class Src, class Dst> + void Scale(const BaseScaleFactors2D<Src, Dst, T>& aScale) { + Scale(aScale.xScale, aScale.yScale); + } + // Scale 'this' by aScale without doing any rounding. + void Scale(T aScale) { Scale(aScale, aScale); } + // Scale 'this' by aXScale and aYScale, without doing any rounding. + void Scale(T aXScale, T aYScale) { + x = x * aXScale; + y = y * aYScale; + width = width * aXScale; + height = height * aYScale; + } + // Scale 'this' by aScale, converting coordinates to integers so that the + // result is the smallest integer-coordinate rectangle containing the + // unrounded result. Note: this can turn an empty rectangle into a non-empty + // rectangle + void ScaleRoundOut(double aScale) { ScaleRoundOut(aScale, aScale); } + // Scale 'this' by aXScale and aYScale, converting coordinates to integers so + // that the result is the smallest integer-coordinate rectangle containing the + // unrounded result. + // Note: this can turn an empty rectangle into a non-empty rectangle + void ScaleRoundOut(double aXScale, double aYScale) { + T right = static_cast<T>(ceil(double(XMost()) * aXScale)); + T bottom = static_cast<T>(ceil(double(YMost()) * aYScale)); + x = static_cast<T>(floor(double(x) * aXScale)); + y = static_cast<T>(floor(double(y) * aYScale)); + width = right - x; + height = bottom - y; + } + // Scale 'this' by aScale, converting coordinates to integers so that the + // result is the largest integer-coordinate rectangle contained by the + // unrounded result. + void ScaleRoundIn(double aScale) { ScaleRoundIn(aScale, aScale); } + // Scale 'this' by aXScale and aYScale, converting coordinates to integers so + // that the result is the largest integer-coordinate rectangle contained by + // the unrounded result. + void ScaleRoundIn(double aXScale, double aYScale) { + T right = static_cast<T>(floor(double(XMost()) * aXScale)); + T bottom = static_cast<T>(floor(double(YMost()) * aYScale)); + x = static_cast<T>(ceil(double(x) * aXScale)); + y = static_cast<T>(ceil(double(y) * aYScale)); + width = std::max<T>(0, right - x); + height = std::max<T>(0, bottom - y); + } + // Scale 'this' by 1/aScale, converting coordinates to integers so that the + // result is the smallest integer-coordinate rectangle containing the + // unrounded result. Note: this can turn an empty rectangle into a non-empty + // rectangle + void ScaleInverseRoundOut(double aScale) { + ScaleInverseRoundOut(aScale, aScale); + } + // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers + // so that the result is the smallest integer-coordinate rectangle containing + // the unrounded result. Note: this can turn an empty rectangle into a + // non-empty rectangle + void ScaleInverseRoundOut(double aXScale, double aYScale) { + T right = static_cast<T>(ceil(double(XMost()) / aXScale)); + T bottom = static_cast<T>(ceil(double(YMost()) / aYScale)); + x = static_cast<T>(floor(double(x) / aXScale)); + y = static_cast<T>(floor(double(y) / aYScale)); + width = right - x; + height = bottom - y; + } + // Scale 'this' by 1/aScale, converting coordinates to integers so that the + // result is the largest integer-coordinate rectangle contained by the + // unrounded result. + void ScaleInverseRoundIn(double aScale) { + ScaleInverseRoundIn(aScale, aScale); + } + // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers + // so that the result is the largest integer-coordinate rectangle contained by + // the unrounded result. + void ScaleInverseRoundIn(double aXScale, double aYScale) { + T right = static_cast<T>(floor(double(XMost()) / aXScale)); + T bottom = static_cast<T>(floor(double(YMost()) / aYScale)); + x = static_cast<T>(ceil(double(x) / aXScale)); + y = static_cast<T>(ceil(double(y) / aYScale)); + width = std::max<T>(0, right - x); + height = std::max<T>(0, bottom - y); + } + + /** + * Clamp aPoint to this rectangle. It is allowed to end up on any + * edge of the rectangle. + */ + [[nodiscard]] Point ClampPoint(const Point& aPoint) const { + using Coord = decltype(aPoint.x); + return Point(std::max(Coord(x), std::min(Coord(XMost()), aPoint.x)), + std::max(Coord(y), std::min(Coord(YMost()), aPoint.y))); + } + + /** + * Translate this rectangle to be inside aRect. If it doesn't fit inside + * aRect then the dimensions that don't fit will be shrunk so that they + * do fit. The resulting rect is returned. + */ + [[nodiscard]] Sub MoveInsideAndClamp(const Sub& aRect) const { + Sub rect(std::max(aRect.x, x), std::max(aRect.y, y), + std::min(aRect.width, width), std::min(aRect.height, height)); + rect.x = std::min(rect.XMost(), aRect.XMost()) - rect.width; + rect.y = std::min(rect.YMost(), aRect.YMost()) - rect.height; + return rect; + } + + // Returns the largest rectangle that can be represented with 32-bit + // signed integers, centered around a point at 0,0. As BaseRect's represent + // the dimensions as a top-left point with a width and height, the width + // and height will be the largest positive 32-bit value. The top-left + // position coordinate is divided by two to center the rectangle around a + // point at 0,0. + static Sub MaxIntRect() { + return Sub(static_cast<T>(-std::numeric_limits<int32_t>::max() * 0.5), + static_cast<T>(-std::numeric_limits<int32_t>::max() * 0.5), + static_cast<T>(std::numeric_limits<int32_t>::max()), + static_cast<T>(std::numeric_limits<int32_t>::max())); + }; + + // Returns a point representing the distance, along each dimension, of the + // given point from this rectangle. The distance along a dimension is defined + // as zero if the point is within the bounds of the rectangle in that + // dimension; otherwise, it's the distance to the closer endpoint of the + // rectangle in that dimension. + Point DistanceTo(const Point& aPoint) const { + return {DistanceFromInterval(aPoint.x, x, XMost()), + DistanceFromInterval(aPoint.y, y, YMost())}; + } + + friend std::ostream& operator<<( + std::ostream& stream, + const BaseRect<T, Sub, Point, SizeT, MarginT>& aRect) { + return stream << "(x=" << aRect.x << ", y=" << aRect.y + << ", w=" << aRect.width << ", h=" << aRect.height << ')'; + } + + private: + // Do not use the default operator== or operator!= ! + // Use IsEqualEdges or IsEqualInterior explicitly. + bool operator==(const Sub& aRect) const { return false; } + bool operator!=(const Sub& aRect) const { return false; } + + // Helper function for DistanceTo() that computes the distance of a + // coordinate along one dimension from an interval in that dimension. + static T DistanceFromInterval(T aCoord, T aIntervalStart, T aIntervalEnd) { + if (aCoord < aIntervalStart) { + return aIntervalStart - aCoord; + } + if (aCoord > aIntervalEnd) { + return aCoord - aIntervalEnd; + } + return 0; + } +}; + +} // namespace mozilla::gfx + +#endif /* MOZILLA_GFX_BASERECT_H_ */ diff --git a/gfx/2d/BaseSize.h b/gfx/2d/BaseSize.h new file mode 100644 index 0000000000..502aed0113 --- /dev/null +++ b/gfx/2d/BaseSize.h @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_BASESIZE_H_ +#define MOZILLA_GFX_BASESIZE_H_ + +#include <algorithm> +#include <ostream> + +#include "mozilla/Attributes.h" + +namespace mozilla::gfx { + +/** + * Do not use this class directly. Subclass it, pass that subclass as the + * Sub parameter, and only use that subclass. This allows methods to safely + * cast 'this' to 'Sub*'. + */ +template <class T, class Sub, class Coord = T> +struct BaseSize { + union { + struct { + T width, height; + }; + T components[2]; + }; + + // Constructors + constexpr BaseSize() : width(0), height(0) {} + constexpr BaseSize(Coord aWidth, Coord aHeight) + : width(aWidth), height(aHeight) {} + + void SizeTo(T aWidth, T aHeight) { + width = aWidth; + height = aHeight; + } + + bool IsEmpty() const { return width <= 0 || height <= 0; } + + bool IsSquare() const { return width == height; } + + MOZ_ALWAYS_INLINE T Width() const { return width; } + MOZ_ALWAYS_INLINE T Height() const { return height; } + + // Note that '=' isn't defined so we'll get the + // compiler generated default assignment operator + + bool operator==(const Sub& aSize) const { + return width == aSize.width && height == aSize.height; + } + bool operator!=(const Sub& aSize) const { + return width != aSize.width || height != aSize.height; + } + bool operator<=(const Sub& aSize) const { + return width <= aSize.width && height <= aSize.height; + } + bool operator<(const Sub& aSize) const { + return *this <= aSize && *this != aSize; + } + + Sub operator+(const Sub& aSize) const { + return Sub(width + aSize.width, height + aSize.height); + } + Sub operator-(const Sub& aSize) const { + return Sub(width - aSize.width, height - aSize.height); + } + Sub& operator+=(const Sub& aSize) { + width += aSize.width; + height += aSize.height; + return *static_cast<Sub*>(this); + } + Sub& operator-=(const Sub& aSize) { + width -= aSize.width; + height -= aSize.height; + return *static_cast<Sub*>(this); + } + + Sub operator*(T aScale) const { return Sub(width * aScale, height * aScale); } + Sub operator/(T aScale) const { return Sub(width / aScale, height / aScale); } + friend Sub operator*(T aScale, const Sub& aSize) { + return Sub(aScale * aSize.width, aScale * aSize.height); + } + void Scale(T aXScale, T aYScale) { + width *= aXScale; + height *= aYScale; + } + + Sub operator*(const Sub& aSize) const { + return Sub(width * aSize.width, height * aSize.height); + } + Sub operator/(const Sub& aSize) const { + return Sub(width / aSize.width, height / aSize.height); + } + + friend Sub Min(const Sub& aA, const Sub& aB) { + return Sub(std::min(aA.width, aB.width), std::min(aA.height, aB.height)); + } + + friend Sub Max(const Sub& aA, const Sub& aB) { + return Sub(std::max(aA.width, aB.width), std::max(aA.height, aB.height)); + } + + friend std::ostream& operator<<(std::ostream& aStream, + const BaseSize<T, Sub, Coord>& aSize) { + return aStream << '(' << aSize.width << " x " << aSize.height << ')'; + } +}; + +} // namespace mozilla::gfx + +#endif /* MOZILLA_GFX_BASESIZE_H_ */ diff --git a/gfx/2d/BezierUtils.cpp b/gfx/2d/BezierUtils.cpp new file mode 100644 index 0000000000..8c80d1c43f --- /dev/null +++ b/gfx/2d/BezierUtils.cpp @@ -0,0 +1,326 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BezierUtils.h" + +#include "PathHelpers.h" + +namespace mozilla { +namespace gfx { + +Point GetBezierPoint(const Bezier& aBezier, Float t) { + Float s = 1.0f - t; + + return Point(aBezier.mPoints[0].x * s * s * s + + 3.0f * aBezier.mPoints[1].x * t * s * s + + 3.0f * aBezier.mPoints[2].x * t * t * s + + aBezier.mPoints[3].x * t * t * t, + aBezier.mPoints[0].y * s * s * s + + 3.0f * aBezier.mPoints[1].y * t * s * s + + 3.0f * aBezier.mPoints[2].y * t * t * s + + aBezier.mPoints[3].y * t * t * t); +} + +Point GetBezierDifferential(const Bezier& aBezier, Float t) { + // Return P'(t). + + Float s = 1.0f - t; + + return Point( + -3.0f * ((aBezier.mPoints[0].x - aBezier.mPoints[1].x) * s * s + + 2.0f * (aBezier.mPoints[1].x - aBezier.mPoints[2].x) * t * s + + (aBezier.mPoints[2].x - aBezier.mPoints[3].x) * t * t), + -3.0f * ((aBezier.mPoints[0].y - aBezier.mPoints[1].y) * s * s + + 2.0f * (aBezier.mPoints[1].y - aBezier.mPoints[2].y) * t * s + + (aBezier.mPoints[2].y - aBezier.mPoints[3].y) * t * t)); +} + +Point GetBezierDifferential2(const Bezier& aBezier, Float t) { + // Return P''(t). + + Float s = 1.0f - t; + + return Point(6.0f * ((aBezier.mPoints[0].x - aBezier.mPoints[1].x) * s - + (aBezier.mPoints[1].x - aBezier.mPoints[2].x) * (s - t) - + (aBezier.mPoints[2].x - aBezier.mPoints[3].x) * t), + 6.0f * ((aBezier.mPoints[0].y - aBezier.mPoints[1].y) * s - + (aBezier.mPoints[1].y - aBezier.mPoints[2].y) * (s - t) - + (aBezier.mPoints[2].y - aBezier.mPoints[3].y) * t)); +} + +Float GetBezierLength(const Bezier& aBezier, Float a, Float b) { + if (a < 0.5f && b > 0.5f) { + // To increase the accuracy, split into two parts. + return GetBezierLength(aBezier, a, 0.5f) + + GetBezierLength(aBezier, 0.5f, b); + } + + // Calculate length of simple bezier curve with Simpson's rule. + // _ + // / b + // length = | |P'(x)| dx + // _/ a + // + // b - a a + b + // = ----- [ |P'(a)| + 4 |P'(-----)| + |P'(b)| ] + // 6 2 + + Float fa = GetBezierDifferential(aBezier, a).Length(); + Float fab = GetBezierDifferential(aBezier, (a + b) / 2.0f).Length(); + Float fb = GetBezierDifferential(aBezier, b).Length(); + + return (b - a) / 6.0f * (fa + 4.0f * fab + fb); +} + +static void SplitBezierA(Bezier* aSubBezier, const Bezier& aBezier, Float t) { + // Split bezier curve into [0,t] and [t,1] parts, and return [0,t] part. + + Float s = 1.0f - t; + + Point tmp1; + Point tmp2; + + aSubBezier->mPoints[0] = aBezier.mPoints[0]; + + aSubBezier->mPoints[1] = aBezier.mPoints[0] * s + aBezier.mPoints[1] * t; + tmp1 = aBezier.mPoints[1] * s + aBezier.mPoints[2] * t; + tmp2 = aBezier.mPoints[2] * s + aBezier.mPoints[3] * t; + + aSubBezier->mPoints[2] = aSubBezier->mPoints[1] * s + tmp1 * t; + tmp1 = tmp1 * s + tmp2 * t; + + aSubBezier->mPoints[3] = aSubBezier->mPoints[2] * s + tmp1 * t; +} + +static void SplitBezierB(Bezier* aSubBezier, const Bezier& aBezier, Float t) { + // Split bezier curve into [0,t] and [t,1] parts, and return [t,1] part. + + Float s = 1.0f - t; + + Point tmp1; + Point tmp2; + + aSubBezier->mPoints[3] = aBezier.mPoints[3]; + + aSubBezier->mPoints[2] = aBezier.mPoints[2] * s + aBezier.mPoints[3] * t; + tmp1 = aBezier.mPoints[1] * s + aBezier.mPoints[2] * t; + tmp2 = aBezier.mPoints[0] * s + aBezier.mPoints[1] * t; + + aSubBezier->mPoints[1] = tmp1 * s + aSubBezier->mPoints[2] * t; + tmp1 = tmp2 * s + tmp1 * t; + + aSubBezier->mPoints[0] = tmp1 * s + aSubBezier->mPoints[1] * t; +} + +void GetSubBezier(Bezier* aSubBezier, const Bezier& aBezier, Float t1, + Float t2) { + Bezier tmp; + SplitBezierB(&tmp, aBezier, t1); + + Float range = 1.0f - t1; + if (range == 0.0f) { + *aSubBezier = tmp; + } else { + SplitBezierA(aSubBezier, tmp, (t2 - t1) / range); + } +} + +static Point BisectBezierNearestPoint(const Bezier& aBezier, + const Point& aTarget, Float* aT) { + // Find a nearest point on bezier curve with Binary search. + // Called from FindBezierNearestPoint. + + Float lower = 0.0f; + Float upper = 1.0f; + Float t; + + Point P, lastP; + const size_t MAX_LOOP = 32; + const Float DIST_MARGIN = 0.1f; + const Float DIST_MARGIN_SQUARE = DIST_MARGIN * DIST_MARGIN; + const Float DIFF = 0.0001f; + for (size_t i = 0; i < MAX_LOOP; i++) { + t = (upper + lower) / 2.0f; + P = GetBezierPoint(aBezier, t); + + // Check if it converged. + if (i > 0 && (lastP - P).LengthSquare() < DIST_MARGIN_SQUARE) { + break; + } + + Float distSquare = (P - aTarget).LengthSquare(); + if ((GetBezierPoint(aBezier, t + DIFF) - aTarget).LengthSquare() < + distSquare) { + lower = t; + } else if ((GetBezierPoint(aBezier, t - DIFF) - aTarget).LengthSquare() < + distSquare) { + upper = t; + } else { + break; + } + + lastP = P; + } + + if (aT) { + *aT = t; + } + + return P; +} + +Point FindBezierNearestPoint(const Bezier& aBezier, const Point& aTarget, + Float aInitialT, Float* aT) { + // Find a nearest point on bezier curve with Newton's method. + // It converges within 4 iterations in most cases. + // + // f(t_n) + // t_{n+1} = t_n - --------- + // f'(t_n) + // + // d 2 + // f(t) = ---- | P(t) - aTarget | + // dt + + Float t = aInitialT; + Point P; + Point lastP = GetBezierPoint(aBezier, t); + + const size_t MAX_LOOP = 4; + const Float DIST_MARGIN = 0.1f; + const Float DIST_MARGIN_SQUARE = DIST_MARGIN * DIST_MARGIN; + for (size_t i = 0; i <= MAX_LOOP; i++) { + Point dP = GetBezierDifferential(aBezier, t); + Point ddP = GetBezierDifferential2(aBezier, t); + Float f = 2.0f * (lastP.DotProduct(dP) - aTarget.DotProduct(dP)); + Float df = 2.0f * (dP.DotProduct(dP) + lastP.DotProduct(ddP) - + aTarget.DotProduct(ddP)); + t = t - f / df; + P = GetBezierPoint(aBezier, t); + if ((P - lastP).LengthSquare() < DIST_MARGIN_SQUARE) { + break; + } + lastP = P; + + if (i == MAX_LOOP) { + // If aInitialT is too bad, it won't converge in a few iterations, + // fallback to binary search. + return BisectBezierNearestPoint(aBezier, aTarget, aT); + } + } + + if (aT) { + *aT = t; + } + + return P; +} + +void GetBezierPointsForCorner(Bezier* aBezier, Corner aCorner, + const Point& aCornerPoint, + const Size& aCornerSize) { + // Calculate bezier control points for elliptic arc. + + const Float signsList[4][2] = { + {+1.0f, +1.0f}, {-1.0f, +1.0f}, {-1.0f, -1.0f}, {+1.0f, -1.0f}}; + const Float(&signs)[2] = signsList[aCorner]; + + aBezier->mPoints[0] = aCornerPoint; + aBezier->mPoints[0].x += signs[0] * aCornerSize.width; + + aBezier->mPoints[1] = aBezier->mPoints[0]; + aBezier->mPoints[1].x -= signs[0] * aCornerSize.width * kKappaFactor; + + aBezier->mPoints[3] = aCornerPoint; + aBezier->mPoints[3].y += signs[1] * aCornerSize.height; + + aBezier->mPoints[2] = aBezier->mPoints[3]; + aBezier->mPoints[2].y -= signs[1] * aCornerSize.height * kKappaFactor; +} + +Float GetQuarterEllipticArcLength(Float a, Float b) { + // Calculate the approximate length of a quarter elliptic arc formed by radii + // (a, b), by Ramanujan's approximation of the perimeter p of an ellipse. + // _ _ + // | 2 | + // | 3 * (a - b) | + // p = PI | (a + b) + ------------------------------------------- | + // | 2 2 | + // |_ 10 * (a + b) + sqrt(a + 14 * a * b + b ) _| + // + // _ _ + // | 2 | + // | 3 * (a - b) | + // = PI | (a + b) + -------------------------------------------------- | + // | 2 2 | + // |_ 10 * (a + b) + sqrt(4 * (a + b) - 3 * (a - b) ) _| + // + // _ _ + // | 2 | + // | 3 * S | + // = PI | A + -------------------------------------- | + // | 2 2 | + // |_ 10 * A + sqrt(4 * A - 3 * S ) _| + // + // where A = a + b, S = a - b + + Float A = a + b, S = a - b; + Float A2 = A * A, S2 = S * S; + Float p = M_PI * (A + 3.0f * S2 / (10.0f * A + sqrt(4.0f * A2 - 3.0f * S2))); + return p / 4.0f; +} + +Float CalculateDistanceToEllipticArc(const Point& P, const Point& normal, + const Point& origin, Float width, + Float height) { + // Solve following equations with n and return smaller n. + // + // / (x, y) = P + n * normal + // | + // < _ _ 2 _ _ 2 + // | | x - origin.x | | y - origin.y | + // | | ------------ | + | ------------ | = 1 + // \ |_ width _| |_ height _| + + Float a = (P.x - origin.x) / width; + Float b = normal.x / width; + Float c = (P.y - origin.y) / height; + Float d = normal.y / height; + + Float A = b * b + d * d; + // In the quadratic formulat B would be 2*(a*b+c*d), however we factor the 2 + // out Here which cancels out later. + Float B = a * b + c * d; + Float C = a * a + c * c - 1.0; + + Float signB = 1.0; + if (B < 0.0) { + signB = -1.0; + } + + // 2nd degree polynomials are typically computed using the formulae + // r1 = -(B - sqrt(delta)) / (2 * A) + // r2 = -(B + sqrt(delta)) / (2 * A) + // However B - sqrt(delta) can be an inportant source of precision loss for + // one of the roots when computing the difference between two similar and + // large numbers. To avoid that we pick the root with no precision loss in r1 + // and compute r2 using the Citardauq formula. + // Factoring out 2 from B earlier let + Float S = B + signB * sqrt(B * B - A * C); + Float r1 = -S / A; + Float r2 = -C / S; + +#ifdef DEBUG + Float epsilon = (Float)0.001; + MOZ_ASSERT(r1 >= -epsilon); + MOZ_ASSERT(r2 >= -epsilon); +#endif + + return std::max((r1 < r2 ? r1 : r2), (Float)0.0); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/BezierUtils.h b/gfx/2d/BezierUtils.h new file mode 100644 index 0000000000..3ffcd70214 --- /dev/null +++ b/gfx/2d/BezierUtils.h @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_BezierUtils_h_ +#define mozilla_BezierUtils_h_ + +#include "mozilla/gfx/Point.h" +#include "mozilla/gfx/Types.h" + +namespace mozilla { +namespace gfx { + +// Control points for bezier curve +// +// mPoints[2] +// +-----___---+ mPoints[3] +// __-- +// _-- +// / +// / +// mPoints[1] + | +// | | +// || +// || +// | +// | +// | +// | +// mPoints[0] + +struct Bezier { + Point mPoints[4]; +}; + +// Calculate a point or it's differential of a bezier curve formed by +// aBezier and parameter t. +// +// GetBezierPoint = P(t) +// GetBezierDifferential = P'(t) +// GetBezierDifferential2 = P''(t) +// +// mPoints[2] +// +-----___---+ mPoints[3] +// __-- P(1) +// _-- +// + +// / P(t) +// mPoints[1] + | +// | | +// || +// || +// | +// | +// | +// | +// mPoints[0] + P(0) +Point GetBezierPoint(const Bezier& aBezier, Float t); +Point GetBezierDifferential(const Bezier& aBezier, Float t); +Point GetBezierDifferential2(const Bezier& aBezier, Float t); + +// Calculate length of a simple bezier curve formed by aBezier and range [a, b]. +Float GetBezierLength(const Bezier& aBezier, Float a, Float b); + +// Split bezier curve formed by aBezier into [0,t1], [t1,t2], [t2,1] parts, and +// stores control points for [t1,t2] to aSubBezier. +// +// ___---+ +// __+- P(1) +// _-- P(t2) +// - +// / <-- aSubBezier +// | +// | +// + +// | P(t1) +// | +// | +// | +// | +// + P(0) +void GetSubBezier(Bezier* aSubBezier, const Bezier& aBezier, Float t1, + Float t2); + +// Find a nearest point on bezier curve formed by aBezier to a point aTarget. +// aInitialT is a hint to find the parameter t for the nearest point. +// If aT is non-null, parameter for the nearest point is stored to *aT. +// This function expects a bezier curve to be an approximation of elliptic arc. +// Otherwise it will return wrong point. +// +// aTarget +// + ___---+ +// __-- +// _-- +// + +// / nearest point = P(t = *aT) +// | +// | +// | +// + P(aInitialT) +// | +// | +// | +// | +// + +Point FindBezierNearestPoint(const Bezier& aBezier, const Point& aTarget, + Float aInitialT, Float* aT = nullptr); + +// Calculate control points for a bezier curve that is an approximation of +// an elliptic arc. +// +// aCornerSize.width +// |<----------------->| +// | | +// aCornerPoint| mPoints[2] | +// -------------+-------+-----___---+ mPoints[3] +// ^ | __-- +// | | _-- +// | | - +// | | / +// aCornerSize.height | mPoints[1] + | +// | | | +// | || +// | || +// | | +// | | +// | | +// v mPoints[0] | +// -------------+ +void GetBezierPointsForCorner(Bezier* aBezier, mozilla::Corner aCorner, + const Point& aCornerPoint, + const Size& aCornerSize); + +// Calculate the approximate length of a quarter elliptic arc formed by radii +// (a, b). +// +// a +// |<----------------->| +// | | +// ---+-------------___---+ +// ^ | __-- +// | | _-- +// | | - +// | | / +// b | | | +// | | | +// | || +// | || +// | | +// | | +// | | +// v | +// ---+ +Float GetQuarterEllipticArcLength(Float a, Float b); + +// Calculate the distance between an elliptic arc formed by (origin, width, +// height), and a point P, along a line formed by |P + n * normal|. +// P should be outside of the ellipse, and the line should cross with the +// ellipse twice at n > 0 points. +// +// width +// |<----------------->| +// origin | | +// -----------+-------------___---+ +// ^ normal | __-- +// | P +->__ | _-- +// | --__ - +// | | --+ +// height | | | +// | | | +// | || +// | || +// | | +// | | +// | | +// v | +// -----------+ +Float CalculateDistanceToEllipticArc(const Point& P, const Point& normal, + const Point& origin, Float width, + Float height); + +} // namespace gfx +} // namespace mozilla + +#endif /* mozilla_BezierUtils_h_ */ diff --git a/gfx/2d/BigEndianInts.h b/gfx/2d/BigEndianInts.h new file mode 100644 index 0000000000..b50a2e2148 --- /dev/null +++ b/gfx/2d/BigEndianInts.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_BigEndianInts_h +#define mozilla_BigEndianInts_h + +#include "mozilla/EndianUtils.h" + +namespace mozilla { + +#pragma pack(push, 1) + +struct BigEndianUint16 { +#ifdef __SUNPRO_CC + BigEndianUint16& operator=(const uint16_t aValue) { + value = NativeEndian::swapToBigEndian(aValue); + return *this; + } +#else + MOZ_IMPLICIT BigEndianUint16(const uint16_t aValue) { + value = NativeEndian::swapToBigEndian(aValue); + } +#endif + + operator uint16_t() const { return NativeEndian::swapFromBigEndian(value); } + + friend inline bool operator==(const BigEndianUint16& lhs, + const BigEndianUint16& rhs) { + return lhs.value == rhs.value; + } + + friend inline bool operator!=(const BigEndianUint16& lhs, + const BigEndianUint16& rhs) { + return !(lhs == rhs); + } + + private: + uint16_t value; +}; + +struct BigEndianUint32 { +#ifdef __SUNPRO_CC + BigEndianUint32& operator=(const uint32_t aValue) { + value = NativeEndian::swapToBigEndian(aValue); + return *this; + } +#else + MOZ_IMPLICIT BigEndianUint32(const uint32_t aValue) { + value = NativeEndian::swapToBigEndian(aValue); + } +#endif + + operator uint32_t() const { return NativeEndian::swapFromBigEndian(value); } + + private: + uint32_t value; +}; + +#pragma pack(pop) + +} // namespace mozilla + +#endif // mozilla_BigEndianInts_h diff --git a/gfx/2d/Blur.cpp b/gfx/2d/Blur.cpp new file mode 100644 index 0000000000..a598f2c758 --- /dev/null +++ b/gfx/2d/Blur.cpp @@ -0,0 +1,904 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Blur.h" + +#include <algorithm> +#include <math.h> +#include <string.h> + +#include "mozilla/CheckedInt.h" +#include "NumericTools.h" + +#include "2D.h" +#include "DataSurfaceHelpers.h" +#include "Tools.h" + +#ifdef USE_NEON +# include "mozilla/arm.h" +#endif + +namespace mozilla { +namespace gfx { + +/** + * Helper function to process each row of the box blur. + * It takes care of transposing the data on input or output depending + * on whether we intend a horizontal or vertical blur, and whether we're + * reading from the initial source or writing to the final destination. + * It allows starting or ending anywhere within the row to accomodate + * a skip rect. + */ +template <bool aTransposeInput, bool aTransposeOutput> +static inline void BoxBlurRow(const uint8_t* aInput, uint8_t* aOutput, + int32_t aLeftLobe, int32_t aRightLobe, + int32_t aWidth, int32_t aStride, int32_t aStart, + int32_t aEnd) { + // If the input or output is transposed, then we will move down a row + // for each step, instead of moving over a column. Since these values + // only depend on a template parameter, they will more easily get + // copy-propagated in the non-transposed case, which is why they + // are not passed as parameters. + const int32_t inputStep = aTransposeInput ? aStride : 1; + const int32_t outputStep = aTransposeOutput ? aStride : 1; + + // We need to sample aLeftLobe pixels to the left and aRightLobe pixels + // to the right of the current position, then average them. So this is + // the size of the total width of this filter. + const int32_t boxSize = aLeftLobe + aRightLobe + 1; + + // Instead of dividing the pixel sum by boxSize to average, we can just + // compute a scale that will normalize the result so that it can be quickly + // shifted into the desired range. + const uint32_t reciprocal = (1 << 24) / boxSize; + + // The shift would normally truncate the result, whereas we would rather + // prefer to round the result to the closest increment. By adding 0.5 units + // to the initial sum, we bias the sum so that it will be rounded by the + // truncation instead. + uint32_t alphaSum = (boxSize + 1) / 2; + + // We process the row with a moving filter, keeping a sum (alphaSum) of + // boxSize pixels. As we move over a pixel, we need to add on a pixel + // from the right extreme of the window that moved into range, and subtract + // off a pixel from the left extreme of window that moved out of range. + // But first, we need to initialization alphaSum to the contents of + // the window before we can get going. If the window moves out of bounds + // of the row, we clamp each sample to be the closest pixel from within + // row bounds, so the 0th and aWidth-1th pixel. + int32_t initLeft = aStart - aLeftLobe; + if (initLeft < 0) { + // If the left lobe samples before the row, add in clamped samples. + alphaSum += -initLeft * aInput[0]; + initLeft = 0; + } + int32_t initRight = aStart + boxSize - aLeftLobe; + if (initRight > aWidth) { + // If the right lobe samples after the row, add in clamped samples. + alphaSum += (initRight - aWidth) * aInput[(aWidth - 1) * inputStep]; + initRight = aWidth; + } + // Finally, add in all the valid, non-clamped samples to fill up the + // rest of the window. + const uint8_t* src = &aInput[initLeft * inputStep]; + const uint8_t* iterEnd = &aInput[initRight * inputStep]; + +#define INIT_ITER \ + alphaSum += *src; \ + src += inputStep; + + // We unroll the per-pixel loop here substantially. The amount of work + // done per sample is so small that the cost of a loop condition check + // and a branch can substantially add to or even dominate the performance + // of the loop. + while (src + 16 * inputStep <= iterEnd) { + INIT_ITER; + INIT_ITER; + INIT_ITER; + INIT_ITER; + INIT_ITER; + INIT_ITER; + INIT_ITER; + INIT_ITER; + INIT_ITER; + INIT_ITER; + INIT_ITER; + INIT_ITER; + INIT_ITER; + INIT_ITER; + INIT_ITER; + INIT_ITER; + } + while (src < iterEnd) { + INIT_ITER; + } + + // Now we start moving the window over the row. We will be accessing + // pixels form aStart - aLeftLobe up to aEnd + aRightLobe, which may be + // out of bounds of the row. To avoid having to check within the inner + // loops if we are in bound, we instead compute the points at which + // we will move out of bounds of the row on the left side (splitLeft) + // and right side (splitRight). + int32_t splitLeft = std::min(std::max(aLeftLobe, aStart), aEnd); + int32_t splitRight = + std::min(std::max(aWidth - (boxSize - aLeftLobe), aStart), aEnd); + // If the filter window is actually large than the size of the row, + // there will be a middle area of overlap where the leftmost and rightmost + // pixel of the filter will both be outside the row. In this case, we need + // to invert the splits so that splitLeft <= splitRight. + if (boxSize > aWidth) { + std::swap(splitLeft, splitRight); + } + + // Process all pixels up to splitLeft that would sample before the start of + // the row. Note that because inputStep and outputStep may not be a const 1 + // value, it is more performant to increment pointers here for the source and + // destination rather than use a loop counter, since doing so would entail an + // expensive multiplication that significantly slows down the loop. + uint8_t* dst = &aOutput[aStart * outputStep]; + iterEnd = &aOutput[splitLeft * outputStep]; + src = &aInput[(aStart + boxSize - aLeftLobe) * inputStep]; + uint8_t firstVal = aInput[0]; + +#define LEFT_ITER \ + *dst = (alphaSum * reciprocal) >> 24; \ + alphaSum += *src - firstVal; \ + dst += outputStep; \ + src += inputStep; + + while (dst + 16 * outputStep <= iterEnd) { + LEFT_ITER; + LEFT_ITER; + LEFT_ITER; + LEFT_ITER; + LEFT_ITER; + LEFT_ITER; + LEFT_ITER; + LEFT_ITER; + LEFT_ITER; + LEFT_ITER; + LEFT_ITER; + LEFT_ITER; + LEFT_ITER; + LEFT_ITER; + LEFT_ITER; + LEFT_ITER; + } + while (dst < iterEnd) { + LEFT_ITER; + } + + // Process all pixels between splitLeft and splitRight. + iterEnd = &aOutput[splitRight * outputStep]; + if (boxSize <= aWidth) { + // The filter window is smaller than the row size, so the leftmost and + // rightmost samples are both within row bounds. + src = &aInput[(splitLeft - aLeftLobe) * inputStep]; + int32_t boxStep = boxSize * inputStep; + +#define CENTER_ITER \ + *dst = (alphaSum * reciprocal) >> 24; \ + alphaSum += src[boxStep] - *src; \ + dst += outputStep; \ + src += inputStep; + + while (dst + 16 * outputStep <= iterEnd) { + CENTER_ITER; + CENTER_ITER; + CENTER_ITER; + CENTER_ITER; + CENTER_ITER; + CENTER_ITER; + CENTER_ITER; + CENTER_ITER; + CENTER_ITER; + CENTER_ITER; + CENTER_ITER; + CENTER_ITER; + CENTER_ITER; + CENTER_ITER; + CENTER_ITER; + CENTER_ITER; + } + while (dst < iterEnd) { + CENTER_ITER; + } + } else { + // The filter window is larger than the row size, and we're in the area of + // split overlap. So the leftmost and rightmost samples are both out of + // bounds and need to be clamped. We can just precompute the difference here + // consequently. + int32_t firstLastDiff = aInput[(aWidth - 1) * inputStep] - aInput[0]; + while (dst < iterEnd) { + *dst = (alphaSum * reciprocal) >> 24; + alphaSum += firstLastDiff; + dst += outputStep; + } + } + + // Process all remaining pixels after splitRight that would sample after the + // row end. + iterEnd = &aOutput[aEnd * outputStep]; + src = &aInput[(splitRight - aLeftLobe) * inputStep]; + uint8_t lastVal = aInput[(aWidth - 1) * inputStep]; + +#define RIGHT_ITER \ + *dst = (alphaSum * reciprocal) >> 24; \ + alphaSum += lastVal - *src; \ + dst += outputStep; \ + src += inputStep; + + while (dst + 16 * outputStep <= iterEnd) { + RIGHT_ITER; + RIGHT_ITER; + RIGHT_ITER; + RIGHT_ITER; + RIGHT_ITER; + RIGHT_ITER; + RIGHT_ITER; + RIGHT_ITER; + RIGHT_ITER; + RIGHT_ITER; + RIGHT_ITER; + RIGHT_ITER; + RIGHT_ITER; + RIGHT_ITER; + RIGHT_ITER; + RIGHT_ITER; + } + while (dst < iterEnd) { + RIGHT_ITER; + } +} + +/** + * Box blur involves looking at one pixel, and setting its value to the average + * of its neighbouring pixels. This is meant to provide a 3-pass approximation + * of a Gaussian blur. + * @param aTranspose Whether to transpose the buffer when reading and writing + * to it. + * @param aData The buffer to be blurred. + * @param aLobes The number of pixels to blend on the left and right for each of + * 3 passes. + * @param aWidth The number of columns in the buffers. + * @param aRows The number of rows in the buffers. + * @param aStride The stride of the buffer. + */ +template <bool aTranspose> +static void BoxBlur(uint8_t* aData, const int32_t aLobes[3][2], int32_t aWidth, + int32_t aRows, int32_t aStride, IntRect aSkipRect) { + if (aTranspose) { + std::swap(aWidth, aRows); + aSkipRect.Swap(); + } + + MOZ_ASSERT(aWidth > 0); + + // All three passes of the box blur that approximate the Gaussian are done + // on each row in turn, so we only need two temporary row buffers to process + // each row, instead of a full-sized buffer. Data moves from the source to the + // first temporary, from the first temporary to the second, then from the + // second back to the destination. This way is more cache-friendly than + // processing whe whole buffer in each pass and thus yields a nice speedup. + uint8_t* tmpRow = new (std::nothrow) uint8_t[2 * aWidth]; + if (!tmpRow) { + return; + } + uint8_t* tmpRow2 = tmpRow + aWidth; + + const int32_t stride = aTranspose ? 1 : aStride; + bool skipRectCoversWholeRow = + 0 >= aSkipRect.X() && aWidth <= aSkipRect.XMost(); + + for (int32_t y = 0; y < aRows; y++) { + // Check whether the skip rect intersects this row. If the skip + // rect covers the whole surface in this row, we can avoid + // this row entirely (and any others along the skip rect). + bool inSkipRectY = aSkipRect.ContainsY(y); + if (inSkipRectY && skipRectCoversWholeRow) { + aData += stride * (aSkipRect.YMost() - y); + y = aSkipRect.YMost() - 1; + continue; + } + + // Read in data from the source transposed if necessary. + BoxBlurRow<aTranspose, false>(aData, tmpRow, aLobes[0][0], aLobes[0][1], + aWidth, aStride, 0, aWidth); + + // For the middle pass, the data is already pre-transposed and does not need + // to be post-transposed yet. + BoxBlurRow<false, false>(tmpRow, tmpRow2, aLobes[1][0], aLobes[1][1], + aWidth, aStride, 0, aWidth); + + // Write back data to the destination transposed if necessary too. + // Make sure not to overwrite the skip rect by only outputting to the + // destination before and after the skip rect, if requested. + int32_t skipStart = + inSkipRectY ? std::min(std::max(aSkipRect.X(), 0), aWidth) : aWidth; + int32_t skipEnd = std::max(skipStart, aSkipRect.XMost()); + if (skipStart > 0) { + BoxBlurRow<false, aTranspose>(tmpRow2, aData, aLobes[2][0], aLobes[2][1], + aWidth, aStride, 0, skipStart); + } + if (skipEnd < aWidth) { + BoxBlurRow<false, aTranspose>(tmpRow2, aData, aLobes[2][0], aLobes[2][1], + aWidth, aStride, skipEnd, aWidth); + } + + aData += stride; + } + + delete[] tmpRow; +} + +static void ComputeLobes(int32_t aRadius, int32_t aLobes[3][2]) { + int32_t major, minor, final; + + /* See http://www.w3.org/TR/SVG/filters.html#feGaussianBlur for + * some notes about approximating the Gaussian blur with box-blurs. + * The comments below are in the terminology of that page. + */ + int32_t z = aRadius / 3; + switch (aRadius % 3) { + case 0: + // aRadius = z*3; choose d = 2*z + 1 + major = minor = final = z; + break; + case 1: + // aRadius = z*3 + 1 + // This is a tricky case since there is no value of d which will + // yield a radius of exactly aRadius. If d is odd, i.e. d=2*k + 1 + // for some integer k, then the radius will be 3*k. If d is even, + // i.e. d=2*k, then the radius will be 3*k - 1. + // So we have to choose values that don't match the standard + // algorithm. + major = z + 1; + minor = final = z; + break; + case 2: + // aRadius = z*3 + 2; choose d = 2*z + 2 + major = final = z + 1; + minor = z; + break; + default: + // Mathematical impossibility! + MOZ_ASSERT(false); + major = minor = final = 0; + } + MOZ_ASSERT(major + minor + final == aRadius); + + aLobes[0][0] = major; + aLobes[0][1] = minor; + aLobes[1][0] = minor; + aLobes[1][1] = major; + aLobes[2][0] = final; + aLobes[2][1] = final; +} + +static void SpreadHorizontal(uint8_t* aInput, uint8_t* aOutput, int32_t aRadius, + int32_t aWidth, int32_t aRows, int32_t aStride, + const IntRect& aSkipRect) { + if (aRadius == 0) { + memcpy(aOutput, aInput, aStride * aRows); + return; + } + + bool skipRectCoversWholeRow = + 0 >= aSkipRect.X() && aWidth <= aSkipRect.XMost(); + for (int32_t y = 0; y < aRows; y++) { + // Check whether the skip rect intersects this row. If the skip + // rect covers the whole surface in this row, we can avoid + // this row entirely (and any others along the skip rect). + bool inSkipRectY = aSkipRect.ContainsY(y); + if (inSkipRectY && skipRectCoversWholeRow) { + y = aSkipRect.YMost() - 1; + continue; + } + + for (int32_t x = 0; x < aWidth; x++) { + // Check whether we are within the skip rect. If so, go + // to the next point outside the skip rect. + if (inSkipRectY && aSkipRect.ContainsX(x)) { + x = aSkipRect.XMost(); + if (x >= aWidth) break; + } + + int32_t sMin = std::max(x - aRadius, 0); + int32_t sMax = std::min(x + aRadius, aWidth - 1); + int32_t v = 0; + for (int32_t s = sMin; s <= sMax; ++s) { + v = std::max<int32_t>(v, aInput[aStride * y + s]); + } + aOutput[aStride * y + x] = v; + } + } +} + +static void SpreadVertical(uint8_t* aInput, uint8_t* aOutput, int32_t aRadius, + int32_t aWidth, int32_t aRows, int32_t aStride, + const IntRect& aSkipRect) { + if (aRadius == 0) { + memcpy(aOutput, aInput, aStride * aRows); + return; + } + + bool skipRectCoversWholeColumn = + 0 >= aSkipRect.Y() && aRows <= aSkipRect.YMost(); + for (int32_t x = 0; x < aWidth; x++) { + bool inSkipRectX = aSkipRect.ContainsX(x); + if (inSkipRectX && skipRectCoversWholeColumn) { + x = aSkipRect.XMost() - 1; + continue; + } + + for (int32_t y = 0; y < aRows; y++) { + // Check whether we are within the skip rect. If so, go + // to the next point outside the skip rect. + if (inSkipRectX && aSkipRect.ContainsY(y)) { + y = aSkipRect.YMost(); + if (y >= aRows) break; + } + + int32_t sMin = std::max(y - aRadius, 0); + int32_t sMax = std::min(y + aRadius, aRows - 1); + int32_t v = 0; + for (int32_t s = sMin; s <= sMax; ++s) { + v = std::max<int32_t>(v, aInput[aStride * s + x]); + } + aOutput[aStride * y + x] = v; + } + } +} + +CheckedInt<int32_t> AlphaBoxBlur::RoundUpToMultipleOf4(int32_t aVal) { + CheckedInt<int32_t> val(aVal); + + val += 3; + val /= 4; + val *= 4; + + return val; +} + +AlphaBoxBlur::AlphaBoxBlur(const Rect& aRect, const IntSize& aSpreadRadius, + const IntSize& aBlurRadius, const Rect* aDirtyRect, + const Rect* aSkipRect) + : mStride(0), mSurfaceAllocationSize(0) { + Init(aRect, aSpreadRadius, aBlurRadius, aDirtyRect, aSkipRect); +} + +AlphaBoxBlur::AlphaBoxBlur() + : mStride(0), mSurfaceAllocationSize(0), mHasDirtyRect(false) {} + +void AlphaBoxBlur::Init(const Rect& aRect, const IntSize& aSpreadRadius, + const IntSize& aBlurRadius, const Rect* aDirtyRect, + const Rect* aSkipRect) { + mSpreadRadius = aSpreadRadius; + mBlurRadius = aBlurRadius; + + Rect rect(aRect); + rect.Inflate(Size(aBlurRadius + aSpreadRadius)); + rect.RoundOut(); + + if (aDirtyRect) { + // If we get passed a dirty rect from layout, we can minimize the + // shadow size and make painting faster. + mHasDirtyRect = true; + mDirtyRect = *aDirtyRect; + Rect requiredBlurArea = mDirtyRect.Intersect(rect); + requiredBlurArea.Inflate(Size(aBlurRadius + aSpreadRadius)); + rect = requiredBlurArea.Intersect(rect); + } else { + mHasDirtyRect = false; + } + + mRect = TruncatedToInt(rect); + if (mRect.IsEmpty()) { + return; + } + + if (aSkipRect) { + // If we get passed a skip rect, we can lower the amount of + // blurring/spreading we need to do. We convert it to IntRect to avoid + // expensive int<->float conversions if we were to use Rect instead. + Rect skipRect = *aSkipRect; + skipRect.Deflate(Size(aBlurRadius + aSpreadRadius)); + mSkipRect = RoundedIn(skipRect); + mSkipRect = mSkipRect.Intersect(mRect); + if (mSkipRect.IsEqualInterior(mRect)) { + return; + } + + mSkipRect -= mRect.TopLeft(); + // Ensure the skip rect is 4-pixel-aligned in the x axis, so that all our + // accesses later are aligned as well, see bug 1622113. + mSkipRect.SetLeftEdge(RoundUpToMultiple(mSkipRect.X(), 4)); + mSkipRect.SetRightEdge(RoundDownToMultiple(mSkipRect.XMost(), 4)); + if (mSkipRect.IsEmpty()) { + mSkipRect = IntRect(); + } + } else { + mSkipRect = IntRect(); + } + + CheckedInt<int32_t> stride = RoundUpToMultipleOf4(mRect.Width()); + if (stride.isValid()) { + mStride = stride.value(); + + // We need to leave room for an additional 3 bytes for a potential overrun + // in our blurring code. + size_t size = BufferSizeFromStrideAndHeight(mStride, mRect.Height(), 3); + if (size != 0) { + mSurfaceAllocationSize = size; + } + } +} + +AlphaBoxBlur::AlphaBoxBlur(const Rect& aRect, int32_t aStride, float aSigmaX, + float aSigmaY) + : mRect(TruncatedToInt(aRect)), + + mBlurRadius(CalculateBlurRadius(Point(aSigmaX, aSigmaY))), + mStride(aStride), + mSurfaceAllocationSize(0), + mHasDirtyRect(false) { + IntRect intRect; + if (aRect.ToIntRect(&intRect)) { + size_t minDataSize = + BufferSizeFromStrideAndHeight(intRect.Width(), intRect.Height()); + if (minDataSize != 0) { + mSurfaceAllocationSize = minDataSize; + } + } +} + +AlphaBoxBlur::~AlphaBoxBlur() = default; + +IntSize AlphaBoxBlur::GetSize() const { + IntSize size(mRect.Width(), mRect.Height()); + return size; +} + +int32_t AlphaBoxBlur::GetStride() const { return mStride; } + +IntRect AlphaBoxBlur::GetRect() const { return mRect; } + +Rect* AlphaBoxBlur::GetDirtyRect() { + if (mHasDirtyRect) { + return &mDirtyRect; + } + + return nullptr; +} + +size_t AlphaBoxBlur::GetSurfaceAllocationSize() const { + return mSurfaceAllocationSize; +} + +void AlphaBoxBlur::Blur(uint8_t* aData) const { + if (!aData) { + return; + } + + // no need to do all this if not blurring or spreading + if (mBlurRadius != IntSize(0, 0) || mSpreadRadius != IntSize(0, 0)) { + int32_t stride = GetStride(); + + IntSize size = GetSize(); + + if (mSpreadRadius.width > 0 || mSpreadRadius.height > 0) { + // No need to use CheckedInt here - we have validated it in the + // constructor. + size_t szB = stride * size.height; + uint8_t* tmpData = new (std::nothrow) uint8_t[szB]; + + if (!tmpData) { + return; + } + + memset(tmpData, 0, szB); + + SpreadHorizontal(aData, tmpData, mSpreadRadius.width, size.width, + size.height, stride, mSkipRect); + SpreadVertical(tmpData, aData, mSpreadRadius.height, size.width, + size.height, stride, mSkipRect); + + delete[] tmpData; + } + + int32_t horizontalLobes[3][2]; + ComputeLobes(mBlurRadius.width, horizontalLobes); + int32_t verticalLobes[3][2]; + ComputeLobes(mBlurRadius.height, verticalLobes); + + // We want to allow for some extra space on the left for alignment reasons. + int32_t maxLeftLobe = + RoundUpToMultipleOf4(horizontalLobes[0][0] + 1).value(); + + IntSize integralImageSize( + size.width + maxLeftLobe + horizontalLobes[1][1], + size.height + verticalLobes[0][0] + verticalLobes[1][1] + 1); + + if ((integralImageSize.width * integralImageSize.height) > (1 << 24)) { + // Fallback to old blurring code when the surface is so large it may + // overflow our integral image! + if (mBlurRadius.width > 0) { + BoxBlur<false>(aData, horizontalLobes, size.width, size.height, stride, + mSkipRect); + } + if (mBlurRadius.height > 0) { + BoxBlur<true>(aData, verticalLobes, size.width, size.height, stride, + mSkipRect); + } + } else { + size_t integralImageStride = + GetAlignedStride<16>(integralImageSize.width, 4); + if (integralImageStride == 0) { + return; + } + + // We need to leave room for an additional 12 bytes for a maximum overrun + // of 3 pixels in the blurring code. + size_t bufLen = BufferSizeFromStrideAndHeight( + integralImageStride, integralImageSize.height, 12); + if (bufLen == 0) { + return; + } + // bufLen is a byte count, but here we want a multiple of 32-bit ints, so + // we divide by 4. + AlignedArray<uint32_t> integralImage((bufLen / 4) + + ((bufLen % 4) ? 1 : 0)); + + if (!integralImage) { + return; + } + +#ifdef USE_SSE2 + if (Factory::HasSSE2()) { + BoxBlur_SSE2(aData, horizontalLobes[0][0], horizontalLobes[0][1], + verticalLobes[0][0], verticalLobes[0][1], integralImage, + integralImageStride); + BoxBlur_SSE2(aData, horizontalLobes[1][0], horizontalLobes[1][1], + verticalLobes[1][0], verticalLobes[1][1], integralImage, + integralImageStride); + BoxBlur_SSE2(aData, horizontalLobes[2][0], horizontalLobes[2][1], + verticalLobes[2][0], verticalLobes[2][1], integralImage, + integralImageStride); + } else +#endif +#ifdef USE_NEON + if (mozilla::supports_neon()) { + BoxBlur_NEON(aData, horizontalLobes[0][0], horizontalLobes[0][1], + verticalLobes[0][0], verticalLobes[0][1], integralImage, + integralImageStride); + BoxBlur_NEON(aData, horizontalLobes[1][0], horizontalLobes[1][1], + verticalLobes[1][0], verticalLobes[1][1], integralImage, + integralImageStride); + BoxBlur_NEON(aData, horizontalLobes[2][0], horizontalLobes[2][1], + verticalLobes[2][0], verticalLobes[2][1], integralImage, + integralImageStride); + } else +#endif + { +#ifdef _MIPS_ARCH_LOONGSON3A + BoxBlur_LS3(aData, horizontalLobes[0][0], horizontalLobes[0][1], + verticalLobes[0][0], verticalLobes[0][1], integralImage, + integralImageStride); + BoxBlur_LS3(aData, horizontalLobes[1][0], horizontalLobes[1][1], + verticalLobes[1][0], verticalLobes[1][1], integralImage, + integralImageStride); + BoxBlur_LS3(aData, horizontalLobes[2][0], horizontalLobes[2][1], + verticalLobes[2][0], verticalLobes[2][1], integralImage, + integralImageStride); +#else + BoxBlur_C(aData, horizontalLobes[0][0], horizontalLobes[0][1], + verticalLobes[0][0], verticalLobes[0][1], integralImage, + integralImageStride); + BoxBlur_C(aData, horizontalLobes[1][0], horizontalLobes[1][1], + verticalLobes[1][0], verticalLobes[1][1], integralImage, + integralImageStride); + BoxBlur_C(aData, horizontalLobes[2][0], horizontalLobes[2][1], + verticalLobes[2][0], verticalLobes[2][1], integralImage, + integralImageStride); +#endif + } + } + } +} + +MOZ_ALWAYS_INLINE void GenerateIntegralRow(uint32_t* aDest, + const uint8_t* aSource, + uint32_t* aPreviousRow, + const uint32_t& aSourceWidth, + const uint32_t& aLeftInflation, + const uint32_t& aRightInflation) { + uint32_t currentRowSum = 0; + uint32_t pixel = aSource[0]; + for (uint32_t x = 0; x < aLeftInflation; x++) { + currentRowSum += pixel; + *aDest++ = currentRowSum + *aPreviousRow++; + } + for (uint32_t x = aLeftInflation; x < (aSourceWidth + aLeftInflation); + x += 4) { + uint32_t alphaValues = *(uint32_t*)(aSource + (x - aLeftInflation)); +#if defined WORDS_BIGENDIAN || defined IS_BIG_ENDIAN || defined __BIG_ENDIAN__ + currentRowSum += (alphaValues >> 24) & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + currentRowSum += (alphaValues >> 16) & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + currentRowSum += (alphaValues >> 8) & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; +#else + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + alphaValues >>= 8; + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + alphaValues >>= 8; + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; + alphaValues >>= 8; + currentRowSum += alphaValues & 0xff; + *aDest++ = *aPreviousRow++ + currentRowSum; +#endif + } + pixel = aSource[aSourceWidth - 1]; + for (uint32_t x = (aSourceWidth + aLeftInflation); + x < (aSourceWidth + aLeftInflation + aRightInflation); x++) { + currentRowSum += pixel; + *aDest++ = currentRowSum + *aPreviousRow++; + } +} + +MOZ_ALWAYS_INLINE void GenerateIntegralImage_C( + int32_t aLeftInflation, int32_t aRightInflation, int32_t aTopInflation, + int32_t aBottomInflation, uint32_t* aIntegralImage, + size_t aIntegralImageStride, uint8_t* aSource, int32_t aSourceStride, + const IntSize& aSize) { + uint32_t stride32bit = aIntegralImageStride / 4; + + IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation, + aSize.height + aTopInflation + aBottomInflation); + + memset(aIntegralImage, 0, aIntegralImageStride); + + GenerateIntegralRow(aIntegralImage, aSource, aIntegralImage, aSize.width, + aLeftInflation, aRightInflation); + for (int y = 1; y < aTopInflation + 1; y++) { + GenerateIntegralRow(aIntegralImage + (y * stride32bit), aSource, + aIntegralImage + (y - 1) * stride32bit, aSize.width, + aLeftInflation, aRightInflation); + } + + for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) { + GenerateIntegralRow(aIntegralImage + (y * stride32bit), + aSource + aSourceStride * (y - aTopInflation), + aIntegralImage + (y - 1) * stride32bit, aSize.width, + aLeftInflation, aRightInflation); + } + + if (aBottomInflation) { + for (int y = (aSize.height + aTopInflation); y < integralImageSize.height; + y++) { + GenerateIntegralRow(aIntegralImage + (y * stride32bit), + aSource + ((aSize.height - 1) * aSourceStride), + aIntegralImage + (y - 1) * stride32bit, aSize.width, + aLeftInflation, aRightInflation); + } + } +} + +/** + * Attempt to do an in-place box blur using an integral image. + */ +void AlphaBoxBlur::BoxBlur_C(uint8_t* aData, int32_t aLeftLobe, + int32_t aRightLobe, int32_t aTopLobe, + int32_t aBottomLobe, uint32_t* aIntegralImage, + size_t aIntegralImageStride) const { + IntSize size = GetSize(); + + MOZ_ASSERT(size.width > 0); + + // Our 'left' or 'top' lobe will include the current pixel. i.e. when + // looking at an integral image the value of a pixel at 'x,y' is calculated + // using the value of the integral image values above/below that. + aLeftLobe++; + aTopLobe++; + int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe); + + MOZ_ASSERT(boxSize > 0); + + if (boxSize == 1) { + return; + } + + int32_t stride32bit = aIntegralImageStride / 4; + + int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value(); + + GenerateIntegralImage_C(leftInflation, aRightLobe, aTopLobe, aBottomLobe, + aIntegralImage, aIntegralImageStride, aData, mStride, + size); + + uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); + + uint32_t* innerIntegral = + aIntegralImage + (aTopLobe * stride32bit) + leftInflation; + + // Storing these locally makes this about 30% faster! Presumably the compiler + // can't be sure we're not altering the member variables in this loop. + IntRect skipRect = mSkipRect; + uint8_t* data = aData; + int32_t stride = mStride; + for (int32_t y = 0; y < size.height; y++) { + // Not using ContainsY(y) because we do not skip y == skipRect.Y() + // although that may not be done on purpose + bool inSkipRectY = y > skipRect.Y() && y < skipRect.YMost(); + + uint32_t* topLeftBase = + innerIntegral + ((y - aTopLobe) * stride32bit - aLeftLobe); + uint32_t* topRightBase = + innerIntegral + ((y - aTopLobe) * stride32bit + aRightLobe); + uint32_t* bottomRightBase = + innerIntegral + ((y + aBottomLobe) * stride32bit + aRightLobe); + uint32_t* bottomLeftBase = + innerIntegral + ((y + aBottomLobe) * stride32bit - aLeftLobe); + + for (int32_t x = 0; x < size.width; x++) { + // Not using ContainsX(x) because we do not skip x == skipRect.X() + // although that may not be done on purpose + if (inSkipRectY && x > skipRect.X() && x < skipRect.XMost()) { + x = skipRect.XMost() - 1; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + int32_t topLeft = topLeftBase[x]; + int32_t topRight = topRightBase[x]; + int32_t bottomRight = bottomRightBase[x]; + int32_t bottomLeft = bottomLeftBase[x]; + + uint32_t value = bottomRight - topRight - bottomLeft; + value += topLeft; + + data[stride * y + x] = + (uint64_t(reciprocal) * value + (uint64_t(1) << 31)) >> 32; + } + } +} + +/** + * Compute the box blur size (which we're calling the blur radius) from + * the standard deviation. + * + * Much of this, the 3 * sqrt(2 * pi) / 4, is the known value for + * approximating a Gaussian using box blurs. This yields quite a good + * approximation for a Gaussian. Then we multiply this by 1.5 since our + * code wants the radius of the entire triple-box-blur kernel instead of + * the diameter of an individual box blur. For more details, see: + * http://www.w3.org/TR/SVG11/filters.html#feGaussianBlurElement + * https://bugzilla.mozilla.org/show_bug.cgi?id=590039#c19 + */ +static const Float GAUSSIAN_SCALE_FACTOR = + Float((3 * sqrt(2 * M_PI) / 4) * 1.5); + +IntSize AlphaBoxBlur::CalculateBlurRadius(const Point& aStd) { + IntSize size( + static_cast<int32_t>(floor(aStd.x * GAUSSIAN_SCALE_FACTOR + 0.5f)), + static_cast<int32_t>(floor(aStd.y * GAUSSIAN_SCALE_FACTOR + 0.5f))); + + return size; +} + +Float AlphaBoxBlur::CalculateBlurSigma(int32_t aBlurRadius) { + return aBlurRadius / GAUSSIAN_SCALE_FACTOR; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/Blur.h b/gfx/2d/Blur.h new file mode 100644 index 0000000000..e0f18c4c39 --- /dev/null +++ b/gfx/2d/Blur.h @@ -0,0 +1,198 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_BLUR_H_ +#define MOZILLA_GFX_BLUR_H_ + +#include "mozilla/gfx/Rect.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/CheckedInt.h" + +namespace mozilla { +namespace gfx { + +#ifdef _MSC_VER +# pragma warning(disable : 4251) +#endif + +/** + * Implementation of a triple box blur approximation of a Gaussian blur. + * + * A Gaussian blur is good for blurring because, when done independently + * in the horizontal and vertical directions, it matches the result that + * would be obtained using a different (rotated) set of axes. A triple + * box blur is a very close approximation of a Gaussian. + * + * This is a "service" class; the constructors set up all the information + * based on the values and compute the minimum size for an 8-bit alpha + * channel context. + * The callers are responsible for creating and managing the backing surface + * and passing the pointer to the data to the Blur() method. This class does + * not retain the pointer to the data outside of the Blur() call. + * + * A spread N makes each output pixel the maximum value of all source + * pixels within a square of side length 2N+1 centered on the output pixel. + */ +class GFX2D_API AlphaBoxBlur final { + public: + /** Constructs a box blur and computes the backing surface size. + * + * @param aRect The coordinates of the surface to create in device units. + * + * @param aBlurRadius The blur radius in pixels. This is the radius of the + * entire (triple) kernel function. Each individual box blur has radius + * approximately 1/3 this value, or diameter approximately 2/3 this value. + * This parameter should nearly always be computed using + * CalculateBlurRadius, below. + * + * @param aDirtyRect A pointer to a dirty rect, measured in device units, if + * available. This will be used for optimizing the blur operation. It is + * safe to pass nullptr here. + * + * @param aSkipRect A pointer to a rect, measured in device units, that + * represents an area where blurring is unnecessary and shouldn't be done + * for speed reasons. It is safe to pass nullptr here. + */ + AlphaBoxBlur(const Rect& aRect, const IntSize& aSpreadRadius, + const IntSize& aBlurRadius, const Rect* aDirtyRect, + const Rect* aSkipRect); + + AlphaBoxBlur(const Rect& aRect, int32_t aStride, float aSigmaX, + float aSigmaY); + + AlphaBoxBlur(); + + void Init(const Rect& aRect, const IntSize& aSpreadRadius, + const IntSize& aBlurRadius, const Rect* aDirtyRect, + const Rect* aSkipRect); + + ~AlphaBoxBlur(); + + /** + * Return the size, in pixels, of the 8-bit alpha surface we'd use. + */ + IntSize GetSize() const; + + /** + * Return the stride, in bytes, of the 8-bit alpha surface we'd use. + */ + int32_t GetStride() const; + + /** + * Returns the device-space rectangle the 8-bit alpha surface covers. + */ + IntRect GetRect() const; + + /** + * Return a pointer to a dirty rect, as passed in to the constructor, or + * nullptr if none was passed in. + */ + Rect* GetDirtyRect(); + + /** + * Return the spread radius, in pixels. + */ + IntSize GetSpreadRadius() const { return mSpreadRadius; } + + /** + * Return the blur radius, in pixels. + */ + IntSize GetBlurRadius() const { return mBlurRadius; } + + /** + * Return the minimum buffer size that should be given to Blur() method. If + * zero, the class is not properly setup for blurring. Note that this + * includes the extra three bytes on top of the stride*width, where something + * like gfxImageSurface::GetDataSize() would report without it, even if it + * happens to have the extra bytes. + */ + size_t GetSurfaceAllocationSize() const; + + /** + * Perform the blur in-place on the surface backed by specified 8-bit + * alpha surface data. The size must be at least that returned by + * GetSurfaceAllocationSize() or bad things will happen. + */ + void Blur(uint8_t* aData) const; + + /** + * Calculates a blur radius that, when used with box blur, approximates a + * Gaussian blur with the given standard deviation. The result of this + * function should be used as the aBlurRadius parameter to AlphaBoxBlur's + * constructor, above. + */ + static IntSize CalculateBlurRadius(const Point& aStandardDeviation); + static Float CalculateBlurSigma(int32_t aBlurRadius); + + private: + void BoxBlur_C(uint8_t* aData, int32_t aLeftLobe, int32_t aRightLobe, + int32_t aTopLobe, int32_t aBottomLobe, + uint32_t* aIntegralImage, size_t aIntegralImageStride) const; + void BoxBlur_SSE2(uint8_t* aData, int32_t aLeftLobe, int32_t aRightLobe, + int32_t aTopLobe, int32_t aBottomLobe, + uint32_t* aIntegralImage, + size_t aIntegralImageStride) const; + void BoxBlur_NEON(uint8_t* aData, int32_t aLeftLobe, int32_t aRightLobe, + int32_t aTopLobe, int32_t aBottomLobe, + uint32_t* aIntegralImage, + size_t aIntegralImageStride) const; +#ifdef _MIPS_ARCH_LOONGSON3A + void BoxBlur_LS3(uint8_t* aData, int32_t aLeftLobe, int32_t aRightLobe, + int32_t aTopLobe, int32_t aBottomLobe, + uint32_t* aIntegralImage, size_t aIntegralImageStride) const; +#endif + + static CheckedInt<int32_t> RoundUpToMultipleOf4(int32_t aVal); + + /** + * A rect indicating the area where blurring is unnecessary, and the blur + * algorithm should skip over it. + * + * This is guaranteed to be 4-pixel aligned in the x axis. + */ + IntRect mSkipRect; + + /** + * The device-space rectangle the the backing 8-bit alpha surface covers. + */ + IntRect mRect; + + /** + * A copy of the dirty rect passed to the constructor. This will only be valid + * if mHasDirtyRect is true. + */ + Rect mDirtyRect; + + /** + * The spread radius, in pixels. + */ + IntSize mSpreadRadius; + + /** + * The blur radius, in pixels. + */ + IntSize mBlurRadius; + + /** + * The stride of the data passed to Blur() + */ + int32_t mStride; + + /** + * The minimum size of the buffer needed for the Blur() operation. + */ + size_t mSurfaceAllocationSize; + + /** + * Whether mDirtyRect contains valid data. + */ + bool mHasDirtyRect; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_BLUR_H_ */ diff --git a/gfx/2d/BlurLS3.cpp b/gfx/2d/BlurLS3.cpp new file mode 100644 index 0000000000..f4b96e5cf3 --- /dev/null +++ b/gfx/2d/BlurLS3.cpp @@ -0,0 +1,569 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Blur.h" + +#include <string.h> + +#ifdef _MIPS_ARCH_LOONGSON3A + +# include "MMIHelpers.h" + +namespace mozilla { +namespace gfx { + +typedef struct { + double l; + double h; +} __m128i; + +MOZ_ALWAYS_INLINE +__m128i loadUnaligned128(__m128i* p) { + __m128i v; + + asm volatile( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "gsldlc1 %[vh], 0xf(%[p]) \n\t" + "gsldrc1 %[vh], 0x8(%[p]) \n\t" + "gsldlc1 %[vl], 0x7(%[p]) \n\t" + "gsldrc1 %[vl], 0x0(%[p]) \n\t" + ".set pop \n\t" + : [vh] "=f"(v.h), [vl] "=f"(v.l) + : [p] "r"(p) + : "memory"); + + return v; +} + +MOZ_ALWAYS_INLINE +__m128i Divide(__m128i aValues, __m128i aDivisor) { + uint64_t tmp; + double srl32; + __m128i mask, ra, p4321, t1, t2; + + asm volatile( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "li %[tmp], 0x80000000 \n\t" + "mtc1 %[tmp], %[ral] \n\t" + "xor %[maskl], %[maskl], %[maskl] \n\t" + "mov.d %[rah], %[ral] \n\t" + "li %[tmp], 0xffffffff \n\t" + "mthc1 %[tmp], %[maskl] \n\t" + "mov.d %[maskh], %[maskl] \n\t" + ".set pop \n\t" + : [rah] "=f"(ra.h), [ral] "=f"(ra.l), [maskh] "=f"(mask.h), + [maskl] "=f"(mask.l), [tmp] "=&r"(tmp)); + + asm volatile( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "ori %[tmp], $0, 32 \n\t" + "mtc1 %[tmp], %[srl32] \n\t" _mm_pmuluw(t1, av, ad) + _mm_psrld(t2, av, srl32) _mm_pmuluw(t2, t2, ad) + // Add 1 << 31 before shifting or masking the lower 32 bits away, so that + // the result is rounded. + _mm_paddd(t1, t1, ra) _mm_psrld(t1, t1, srl32) _mm_paddd(t2, t2, ra) + _mm_and(t2, t2, mask) _mm_or(p4321, t1, t2) ".set pop \n\t" + : [p4321h] "=&f"(p4321.h), [p4321l] "=&f"(p4321.l), [t1h] "=&f"(t1.h), + [t1l] "=&f"(t1.l), [t2h] "=&f"(t2.h), [t2l] "=&f"(t2.l), + [srl32] "=&f"(srl32), [tmp] "=&r"(tmp) + : [rah] "f"(ra.h), [ral] "f"(ra.l), [maskh] "f"(mask.h), + [maskl] "f"(mask.l), [avh] "f"(aValues.h), [avl] "f"(aValues.l), + [adh] "f"(aDivisor.h), [adl] "f"(aDivisor.l)); + + return p4321; +} + +MOZ_ALWAYS_INLINE +__m128i BlurFourPixels(const __m128i& aTopLeft, const __m128i& aTopRight, + const __m128i& aBottomRight, const __m128i& aBottomLeft, + const __m128i& aDivisor) { + __m128i values; + + asm volatile( + ".set push \n\t" + ".set arch=loongson3a \n\t" _mm_psubw(val, abr, atr) + _mm_psubw(val, val, abl) _mm_paddw(val, val, atl) ".set pop \n\t" + : [valh] "=&f"(values.h), [vall] "=&f"(values.l) + : [abrh] "f"(aBottomRight.h), [abrl] "f"(aBottomRight.l), + [atrh] "f"(aTopRight.h), [atrl] "f"(aTopRight.l), + [ablh] "f"(aBottomLeft.h), [abll] "f"(aBottomLeft.l), + [atlh] "f"(aTopLeft.h), [atll] "f"(aTopLeft.l)); + + return Divide(values, aDivisor); +} + +MOZ_ALWAYS_INLINE +void LoadIntegralRowFromRow(uint32_t* aDest, const uint8_t* aSource, + int32_t aSourceWidth, int32_t aLeftInflation, + int32_t aRightInflation) { + int32_t currentRowSum = 0; + + for (int x = 0; x < aLeftInflation; x++) { + currentRowSum += aSource[0]; + aDest[x] = currentRowSum; + } + for (int x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x++) { + currentRowSum += aSource[(x - aLeftInflation)]; + aDest[x] = currentRowSum; + } + for (int x = (aSourceWidth + aLeftInflation); + x < (aSourceWidth + aLeftInflation + aRightInflation); x++) { + currentRowSum += aSource[aSourceWidth - 1]; + aDest[x] = currentRowSum; + } +} + +// This function calculates an integral of four pixels stored in the 4 +// 32-bit integers on aPixels. i.e. for { 30, 50, 80, 100 } this returns +// { 30, 80, 160, 260 }. This seems to be the fastest way to do this after +// much testing. +MOZ_ALWAYS_INLINE +__m128i AccumulatePixelSums(__m128i aPixels) { + uint64_t tr; + double tmp, s4, s64; + __m128i sumPixels, currentPixels, zero; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_xor(z, z, z) + "li %[tr], 64 \n\t" + "mtc1 %[tr], %[s64] \n\t" + "li %[tr], 32 \n\t" + "mtc1 %[tr], %[s4] \n\t" + _mm_psllq(cp, ap, s4, s64, t) + _mm_paddw(sp, ap, cp) + _mm_punpckldq(cp, z, sp) + _mm_paddw(sp, sp, cp) + ".set pop \n\t" + :[sph]"=&f"(sumPixels.h), [spl]"=&f"(sumPixels.l), + [cph]"=&f"(currentPixels.h), [cpl]"=&f"(currentPixels.l), + [zh]"=&f"(zero.h), [zl]"=&f"(zero.l), + [s4]"=&f"(s4), [s64]"=&f"(s64), [t]"=&f"(tmp), [tr]"=&r"(tr) + :[aph]"f"(aPixels.h), [apl]"f"(aPixels.l) + ); + + return sumPixels; +} + +MOZ_ALWAYS_INLINE +void GenerateIntegralImage_LS3(int32_t aLeftInflation, int32_t aRightInflation, + int32_t aTopInflation, int32_t aBottomInflation, + uint32_t* aIntegralImage, + size_t aIntegralImageStride, uint8_t* aSource, + int32_t aSourceStride, const IntSize& aSize) { + MOZ_ASSERT(!(aLeftInflation & 3)); + + uint32_t stride32bit = aIntegralImageStride / 4; + + IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation, + aSize.height + aTopInflation + aBottomInflation); + + LoadIntegralRowFromRow(aIntegralImage, aSource, aSize.width, aLeftInflation, + aRightInflation); + + for (int y = 1; y < aTopInflation + 1; y++) { + uint32_t* intRow = aIntegralImage + (y * stride32bit); + uint32_t* intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint32_t* intFirstRow = aIntegralImage; + + for (int x = 0; x < integralImageSize.width; x += 4) { + __m128i firstRow, previousRow; + + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "gslqc1 %[frh], %[frl], (%[fr]) \n\t" + "gslqc1 %[prh], %[prl], (%[pr]) \n\t" + _mm_paddw(fr, fr, pr) + "gssqc1 %[frh], %[frl], (%[r]) \n\t" + ".set pop \n\t" + :[frh]"=&f"(firstRow.h), [frl]"=&f"(firstRow.l), + [prh]"=&f"(previousRow.h), [prl]"=&f"(previousRow.l) + :[fr]"r"(intFirstRow + x), [pr]"r"(intPrevRow + x), + [r]"r"(intRow + x) + :"memory" + ); + } + } + + uint64_t tmp; + double s44, see; + __m128i zero; + asm volatile( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "li %[tmp], 0xee \n\t" + "mtc1 %[tmp], %[see] \n\t" + "li %[tmp], 0x44 \n\t" + "mtc1 %[tmp], %[s44] \n\t" _mm_xor(zero, zero, zero) ".set pop \n\t" + : [tmp] "=&r"(tmp), [s44] "=f"(s44), [see] "=f"(see), + [zeroh] "=f"(zero.h), [zerol] "=f"(zero.l)); + for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) { + __m128i currentRowSum; + uint32_t* intRow = aIntegralImage + (y * stride32bit); + uint32_t* intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint8_t* sourceRow = aSource + aSourceStride * (y - aTopInflation); + uint32_t pixel = sourceRow[0]; + + asm volatile( + ".set push \n\t" + ".set arch=loongson3a \n\t" _mm_xor(cr, cr, cr) ".set pop \n\t" + : [crh] "=f"(currentRowSum.h), [crl] "=f"(currentRowSum.l)); + for (int x = 0; x < aLeftInflation; x += 4) { + __m128i sumPixels, t; + asm volatile( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "mtc1 %[pix], %[spl] \n\t" + "punpcklwd %[spl], %[spl], %[spl] \n\t" + "mov.d %[sph], %[spl] \n\t" + "pshufh %[sph], %[spl], %[s44] \n\t" + "pshufh %[spl], %[spl], %[s44] \n\t" + ".set pop \n\t" + : [sph] "=&f"(sumPixels.h), [spl] "=&f"(sumPixels.l) + : [pix] "r"(pixel), [s44] "f"(s44)); + sumPixels = AccumulatePixelSums(sumPixels); + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_paddw(sp, sp, cr) + "pshufh %[crh], %[sph], %[see] \n\t" + "pshufh %[crl], %[sph], %[see] \n\t" + "gslqc1 %[th], %[tl], (%[pr]) \n\t" + _mm_paddw(t, sp, t) + "gssqc1 %[th], %[tl], (%[r]) \n\t" + ".set pop \n\t" + :[th]"=&f"(t.h), [tl]"=&f"(t.l), + [sph]"+f"(sumPixels.h), [spl]"+f"(sumPixels.l), + [crh]"+f"(currentRowSum.h), [crl]"+f"(currentRowSum.l) + :[r]"r"(intRow + x), [pr]"r"(intPrevRow + x), [see]"f"(see) + :"memory" + ); + } + for (int x = aLeftInflation; x < (aSize.width + aLeftInflation); x += 4) { + uint32_t pixels = *(uint32_t*)(sourceRow + (x - aLeftInflation)); + __m128i sumPixels, t; + + // It's important to shuffle here. When we exit this loop currentRowSum + // has to be set to sumPixels, so that the following loop can get the + // correct pixel for the currentRowSum. The highest order pixel in + // currentRowSum could've originated from accumulation in the stride. + asm volatile( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "pshufh %[crl], %[crh], %[see] \n\t" + "pshufh %[crh], %[crh], %[see] \n\t" + "mtc1 %[pix], %[spl] \n\t" + "punpcklwd %[spl], %[spl], %[spl] \n\t" + "mov.d %[sph], %[spl] \n\t" _mm_punpcklbh(sp, sp, zero) + _mm_punpcklhw(sp, sp, zero) ".set pop \n\t" + : [sph] "=&f"(sumPixels.h), [spl] "=&f"(sumPixels.l), + [crh] "+f"(currentRowSum.h), [crl] "+f"(currentRowSum.l) + : [pix] "r"(pixels), [see] "f"(see), [zeroh] "f"(zero.h), + [zerol] "f"(zero.l)); + sumPixels = AccumulatePixelSums(sumPixels); + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_paddw(sp, sp, cr) + "mov.d %[crh], %[sph] \n\t" + "mov.d %[crl], %[spl] \n\t" + "gslqc1 %[th], %[tl], (%[pr]) \n\t" + _mm_paddw(t, sp, t) + "gssqc1 %[th], %[tl], (%[r]) \n\t" + ".set pop \n\t" + :[th]"=&f"(t.h), [tl]"=&f"(t.l), + [sph]"+f"(sumPixels.h), [spl]"+f"(sumPixels.l), + [crh]"+f"(currentRowSum.h), [crl]"+f"(currentRowSum.l) + :[r]"r"(intRow + x), [pr]"r"(intPrevRow + x) + :"memory" + ); + } + + pixel = sourceRow[aSize.width - 1]; + int x = (aSize.width + aLeftInflation); + if ((aSize.width & 3)) { + // Deal with unaligned portion. Get the correct pixel from currentRowSum, + // see explanation above. + uint32_t intCurrentRowSum = + ((uint32_t*)¤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) const { + IntSize size = GetSize(); + + MOZ_ASSERT(size.height > 0); + + // Our 'left' or 'top' lobe will include the current pixel. i.e. when + // looking at an integral image the value of a pixel at 'x,y' is calculated + // using the value of the integral image values above/below that. + aLeftLobe++; + aTopLobe++; + int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe); + + MOZ_ASSERT(boxSize > 0); + + if (boxSize == 1) { + return; + } + + uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); + + uint32_t stride32bit = aIntegralImageStride / 4; + int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value(); + + GenerateIntegralImage_LS3(leftInflation, aRightLobe, aTopLobe, aBottomLobe, + aIntegralImage, aIntegralImageStride, aData, + mStride, size); + + __m128i divisor, zero; + asm volatile( + ".set push \n\t" + ".set arch=loongson3a \n\t" + "mtc1 %[rec], %[divl] \n\t" + "punpcklwd %[divl], %[divl], %[divl] \n\t" + "mov.d %[divh], %[divl] \n\t" _mm_xor(zero, zero, zero) ".set pop \n\t" + : [divh] "=f"(divisor.h), [divl] "=f"(divisor.l), [zeroh] "=f"(zero.h), + [zerol] "=f"(zero.l) + : [rec] "r"(reciprocal)); + + // This points to the start of the rectangle within the IntegralImage that + // overlaps the surface being blurred. + uint32_t* innerIntegral = + aIntegralImage + (aTopLobe * stride32bit) + leftInflation; + + IntRect skipRect = mSkipRect; + int32_t stride = mStride; + uint8_t* data = aData; + for (int32_t y = 0; y < size.height; y++) { + bool inSkipRectY = y > skipRect.y && y < skipRect.YMost(); + + uint32_t* topLeftBase = + innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + uint32_t* topRightBase = + innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t* bottomRightBase = + innerIntegral + + ((y + aBottomLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t* bottomLeftBase = + innerIntegral + + ((y + aBottomLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + + int32_t x = 0; + // Process 16 pixels at a time for as long as possible. + for (; x <= size.width - 16; x += 16) { + if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { + x = skipRect.XMost() - 16; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + + __m128i topLeft; + __m128i topRight; + __m128i bottomRight; + __m128i bottomLeft; + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x)); + __m128i result1 = + BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 4)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x + 4)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 4)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 4)); + __m128i result2 = + BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 8)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x + 8)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 8)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 8)); + __m128i result3 = + BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 12)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x + 12)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 12)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 12)); + __m128i result4 = + BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + double t; + __m128i final; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_packsswh(r3, r3, r4, t) + _mm_packsswh(f, r1, r2, t) + _mm_packushb(f, f, r3, t) + "gssdlc1 %[fh], 0xf(%[d]) \n\t" + "gssdrc1 %[fh], 0x8(%[d]) \n\t" + "gssdlc1 %[fl], 0x7(%[d]) \n\t" + "gssdrc1 %[fl], 0x0(%[d]) \n\t" + ".set pop \n\t" + :[fh]"=&f"(final.h), [fl]"=&f"(final.l), + [r3h]"+f"(result3.h), [r3l]"+f"(result3.l), + [t]"=&f"(t) + :[r1h]"f"(result1.h), [r1l]"f"(result1.l), + [r2h]"f"(result2.h), [r2l]"f"(result2.l), + [r4h]"f"(result4.h), [r4l]"f"(result4.l), + [d]"r"(data + stride * y + x) + :"memory" + ); + } + + // Process the remaining pixels 4 bytes at a time. + for (; x < size.width; x += 4) { + if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { + x = skipRect.XMost() - 4; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + __m128i topLeft = loadUnaligned128((__m128i*)(topLeftBase + x)); + __m128i topRight = loadUnaligned128((__m128i*)(topRightBase + x)); + __m128i bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x)); + __m128i bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x)); + + __m128i result = + BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + double t; + __m128i final; + asm volatile ( + ".set push \n\t" + ".set arch=loongson3a \n\t" + _mm_packsswh(f, r, zero, t) + _mm_packushb(f, f, zero, t) + "swc1 %[fl], (%[d]) \n\t" + ".set pop \n\t" + :[fh]"=&f"(final.h), [fl]"=&f"(final.l), + [t]"=&f"(t) + :[d]"r"(data + stride * y + x), + [rh]"f"(result.h), [rl]"f"(result.l), + [zeroh]"f"(zero.h), [zerol]"f"(zero.l) + :"memory" + ); + } + } +} + +} // namespace gfx +} // namespace mozilla + +#endif /* _MIPS_ARCH_LOONGSON3A */ diff --git a/gfx/2d/BlurNEON.cpp b/gfx/2d/BlurNEON.cpp new file mode 100644 index 0000000000..601b6f363d --- /dev/null +++ b/gfx/2d/BlurNEON.cpp @@ -0,0 +1,303 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Blur.h" +#include <arm_neon.h> + +namespace mozilla { +namespace gfx { + +MOZ_ALWAYS_INLINE +uint16x4_t Divide(uint32x4_t aValues, uint32x2_t aDivisor) { + uint64x2_t roundingAddition = vdupq_n_u64(int64_t(1) << 31); + uint64x2_t multiplied21 = vmull_u32(vget_low_u32(aValues), aDivisor); + uint64x2_t multiplied43 = vmull_u32(vget_high_u32(aValues), aDivisor); + return vqmovn_u32( + vcombine_u32(vshrn_n_u64(vaddq_u64(multiplied21, roundingAddition), 32), + vshrn_n_u64(vaddq_u64(multiplied43, roundingAddition), 32))); +} + +MOZ_ALWAYS_INLINE +uint16x4_t BlurFourPixels(const uint32x4_t& aTopLeft, + const uint32x4_t& aTopRight, + const uint32x4_t& aBottomRight, + const uint32x4_t& aBottomLeft, + const uint32x2_t& aDivisor) { + uint32x4_t values = vaddq_u32( + vsubq_u32(vsubq_u32(aBottomRight, aTopRight), aBottomLeft), aTopLeft); + return Divide(values, aDivisor); +} + +MOZ_ALWAYS_INLINE +void LoadIntegralRowFromRow(uint32_t* aDest, const uint8_t* aSource, + int32_t aSourceWidth, int32_t aLeftInflation, + int32_t aRightInflation) { + int32_t currentRowSum = 0; + + for (int x = 0; x < aLeftInflation; x++) { + currentRowSum += aSource[0]; + aDest[x] = currentRowSum; + } + for (int x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x++) { + currentRowSum += aSource[(x - aLeftInflation)]; + aDest[x] = currentRowSum; + } + for (int x = (aSourceWidth + aLeftInflation); + x < (aSourceWidth + aLeftInflation + aRightInflation); x++) { + currentRowSum += aSource[aSourceWidth - 1]; + aDest[x] = currentRowSum; + } +} + +MOZ_ALWAYS_INLINE void GenerateIntegralImage_NEON( + int32_t aLeftInflation, int32_t aRightInflation, int32_t aTopInflation, + int32_t aBottomInflation, uint32_t* aIntegralImage, + size_t aIntegralImageStride, uint8_t* aSource, int32_t aSourceStride, + const IntSize& aSize) { + MOZ_ASSERT(!(aLeftInflation & 3)); + + uint32_t stride32bit = aIntegralImageStride / 4; + IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation, + aSize.height + aTopInflation + aBottomInflation); + + LoadIntegralRowFromRow(aIntegralImage, aSource, aSize.width, aLeftInflation, + aRightInflation); + + for (int y = 1; y < aTopInflation + 1; y++) { + uint32_t* intRow = aIntegralImage + (y * stride32bit); + uint32_t* intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint32_t* intFirstRow = aIntegralImage; + + for (int x = 0; x < integralImageSize.width; x += 4) { + uint32x4_t firstRow = vld1q_u32(intFirstRow + x); + uint32x4_t previousRow = vld1q_u32(intPrevRow + x); + vst1q_u32(intRow + x, vaddq_u32(firstRow, previousRow)); + } + } + + for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) { + uint32x4_t currentRowSum = vdupq_n_u32(0); + uint32_t* intRow = aIntegralImage + (y * stride32bit); + uint32_t* intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint8_t* sourceRow = aSource + aSourceStride * (y - aTopInflation); + + uint32_t pixel = sourceRow[0]; + for (int x = 0; x < aLeftInflation; x += 4) { + uint32_t temp[4]; + temp[0] = pixel; + temp[1] = temp[0] + pixel; + temp[2] = temp[1] + pixel; + temp[3] = temp[2] + pixel; + uint32x4_t sumPixels = vld1q_u32(temp); + sumPixels = vaddq_u32(sumPixels, currentRowSum); + currentRowSum = vdupq_n_u32(vgetq_lane_u32(sumPixels, 3)); + vst1q_u32(intRow + x, vaddq_u32(sumPixels, vld1q_u32(intPrevRow + x))); + } + + for (int x = aLeftInflation; x < (aSize.width + aLeftInflation); x += 4) { + // It's important to shuffle here. When we exit this loop currentRowSum + // has to be set to sumPixels, so that the following loop can get the + // correct pixel for the currentRowSum. The highest order pixel in + // currentRowSum could've originated from accumulation in the stride. + currentRowSum = vdupq_n_u32(vgetq_lane_u32(currentRowSum, 3)); + + uint32_t temp[4]; + temp[0] = *(sourceRow + (x - aLeftInflation)); + temp[1] = temp[0] + *(sourceRow + (x - aLeftInflation) + 1); + temp[2] = temp[1] + *(sourceRow + (x - aLeftInflation) + 2); + temp[3] = temp[2] + *(sourceRow + (x - aLeftInflation) + 3); + uint32x4_t sumPixels = vld1q_u32(temp); + sumPixels = vaddq_u32(sumPixels, currentRowSum); + currentRowSum = sumPixels; + vst1q_u32(intRow + x, vaddq_u32(sumPixels, vld1q_u32(intPrevRow + x))); + } + + pixel = sourceRow[aSize.width - 1]; + int x = (aSize.width + aLeftInflation); + if ((aSize.width & 3)) { + // Deal with unaligned portion. Get the correct pixel from currentRowSum, + // see explanation above. + uint32_t intCurrentRowSum = + ((uint32_t*)¤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) const { + IntSize size = GetSize(); + + MOZ_ASSERT(size.height > 0); + + // Our 'left' or 'top' lobe will include the current pixel. i.e. when + // looking at an integral image the value of a pixel at 'x,y' is calculated + // using the value of the integral image values above/below that. + aLeftLobe++; + aTopLobe++; + int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe); + + MOZ_ASSERT(boxSize > 0); + + if (boxSize == 1) { + return; + } + + uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); + uint32_t stride32bit = aIntegralImageStride / 4; + int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value(); + + GenerateIntegralImage_NEON(leftInflation, aRightLobe, aTopLobe, aBottomLobe, + aIntegralImage, aIntegralImageStride, aData, + mStride, size); + + uint32x2_t divisor = vdup_n_u32(reciprocal); + + // This points to the start of the rectangle within the IntegralImage that + // overlaps the surface being blurred. + uint32_t* innerIntegral = + aIntegralImage + (aTopLobe * stride32bit) + leftInflation; + IntRect skipRect = mSkipRect; + int32_t stride = mStride; + uint8_t* data = aData; + + for (int32_t y = 0; y < size.height; y++) { + bool inSkipRectY = y > skipRect.y && y < skipRect.YMost(); + uint32_t* topLeftBase = + innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + uint32_t* topRightBase = + innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t* bottomRightBase = + innerIntegral + + ((y + aBottomLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t* bottomLeftBase = + innerIntegral + + ((y + aBottomLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + + int32_t x = 0; + // Process 16 pixels at a time for as long as possible. + for (; x <= size.width - 16; x += 16) { + if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { + x = skipRect.XMost() - 16; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + + uint32x4_t topLeft; + uint32x4_t topRight; + uint32x4_t bottomRight; + uint32x4_t bottomLeft; + topLeft = vld1q_u32(topLeftBase + x); + topRight = vld1q_u32(topRightBase + x); + bottomRight = vld1q_u32(bottomRightBase + x); + bottomLeft = vld1q_u32(bottomLeftBase + x); + uint16x4_t result1 = + BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = vld1q_u32(topLeftBase + x + 4); + topRight = vld1q_u32(topRightBase + x + 4); + bottomRight = vld1q_u32(bottomRightBase + x + 4); + bottomLeft = vld1q_u32(bottomLeftBase + x + 4); + uint16x4_t result2 = + BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = vld1q_u32(topLeftBase + x + 8); + topRight = vld1q_u32(topRightBase + x + 8); + bottomRight = vld1q_u32(bottomRightBase + x + 8); + bottomLeft = vld1q_u32(bottomLeftBase + x + 8); + uint16x4_t result3 = + BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = vld1q_u32(topLeftBase + x + 12); + topRight = vld1q_u32(topRightBase + x + 12); + bottomRight = vld1q_u32(bottomRightBase + x + 12); + bottomLeft = vld1q_u32(bottomLeftBase + x + 12); + uint16x4_t result4 = + BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + uint8x8_t combine1 = vqmovn_u16(vcombine_u16(result1, result2)); + uint8x8_t combine2 = vqmovn_u16(vcombine_u16(result3, result4)); + uint8x16_t final = vcombine_u8(combine1, combine2); + vst1q_u8(data + stride * y + x, final); + } + + // Process the remaining pixels 4 bytes at a time. + for (; x < size.width; x += 4) { + if (inSkipRectY && x > skipRect.x && x < skipRect.XMost()) { + x = skipRect.XMost() - 4; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + + uint32x4_t topLeft = vld1q_u32(topLeftBase + x); + uint32x4_t topRight = vld1q_u32(topRightBase + x); + uint32x4_t bottomRight = vld1q_u32(bottomRightBase + x); + uint32x4_t bottomLeft = vld1q_u32(bottomLeftBase + x); + uint16x4_t result = + BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + uint32x2_t final = + vreinterpret_u32_u8(vmovn_u16(vcombine_u16(result, vdup_n_u16(0)))); + *(uint32_t*)(data + stride * y + x) = vget_lane_u32(final, 0); + } + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/BlurSSE2.cpp b/gfx/2d/BlurSSE2.cpp new file mode 100644 index 0000000000..69a30367ba --- /dev/null +++ b/gfx/2d/BlurSSE2.cpp @@ -0,0 +1,345 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Blur.h" + +#include "SSEHelpers.h" + +#include <string.h> + +namespace mozilla::gfx { + +MOZ_ALWAYS_INLINE +__m128i Divide(__m128i aValues, __m128i aDivisor) { + const __m128i mask = _mm_setr_epi32(0x0, 0xffffffff, 0x0, 0xffffffff); + static const union { + int64_t i64[2]; + __m128i m; + } roundingAddition = {{int64_t(1) << 31, int64_t(1) << 31}}; + + __m128i multiplied31 = _mm_mul_epu32(aValues, aDivisor); + __m128i multiplied42 = _mm_mul_epu32(_mm_srli_epi64(aValues, 32), aDivisor); + + // Add 1 << 31 before shifting or masking the lower 32 bits away, so that the + // result is rounded. + __m128i p_3_1 = + _mm_srli_epi64(_mm_add_epi64(multiplied31, roundingAddition.m), 32); + __m128i p4_2_ = + _mm_and_si128(_mm_add_epi64(multiplied42, roundingAddition.m), mask); + __m128i p4321 = _mm_or_si128(p_3_1, p4_2_); + return p4321; +} + +MOZ_ALWAYS_INLINE +__m128i BlurFourPixels(const __m128i& aTopLeft, const __m128i& aTopRight, + const __m128i& aBottomRight, const __m128i& aBottomLeft, + const __m128i& aDivisor) { + __m128i values = _mm_add_epi32( + _mm_sub_epi32(_mm_sub_epi32(aBottomRight, aTopRight), aBottomLeft), + aTopLeft); + return Divide(values, aDivisor); +} + +MOZ_ALWAYS_INLINE +void LoadIntegralRowFromRow(uint32_t* aDest, const uint8_t* aSource, + int32_t aSourceWidth, int32_t aLeftInflation, + int32_t aRightInflation) { + int32_t currentRowSum = 0; + + for (int x = 0; x < aLeftInflation; x++) { + currentRowSum += aSource[0]; + aDest[x] = currentRowSum; + } + for (int x = aLeftInflation; x < (aSourceWidth + aLeftInflation); x++) { + currentRowSum += aSource[(x - aLeftInflation)]; + aDest[x] = currentRowSum; + } + for (int x = (aSourceWidth + aLeftInflation); + x < (aSourceWidth + aLeftInflation + aRightInflation); x++) { + currentRowSum += aSource[aSourceWidth - 1]; + aDest[x] = currentRowSum; + } +} + +// This function calculates an integral of four pixels stored in the 4 +// 32-bit integers on aPixels. i.e. for { 30, 50, 80, 100 } this returns +// { 30, 80, 160, 260 }. This seems to be the fastest way to do this after +// much testing. +MOZ_ALWAYS_INLINE +__m128i AccumulatePixelSums(__m128i aPixels) { + __m128i sumPixels = aPixels; + __m128i currentPixels = _mm_slli_si128(aPixels, 4); + sumPixels = _mm_add_epi32(sumPixels, currentPixels); + currentPixels = _mm_unpacklo_epi64(_mm_setzero_si128(), sumPixels); + + return _mm_add_epi32(sumPixels, currentPixels); +} + +MOZ_ALWAYS_INLINE void GenerateIntegralImage_SSE2( + int32_t aLeftInflation, int32_t aRightInflation, int32_t aTopInflation, + int32_t aBottomInflation, uint32_t* aIntegralImage, + size_t aIntegralImageStride, uint8_t* aSource, int32_t aSourceStride, + const IntSize& aSize) { + MOZ_ASSERT(!(aLeftInflation & 3)); + + uint32_t stride32bit = aIntegralImageStride / 4; + + IntSize integralImageSize(aSize.width + aLeftInflation + aRightInflation, + aSize.height + aTopInflation + aBottomInflation); + + LoadIntegralRowFromRow(aIntegralImage, aSource, aSize.width, aLeftInflation, + aRightInflation); + + for (int y = 1; y < aTopInflation + 1; y++) { + uint32_t* intRow = aIntegralImage + (y * stride32bit); + uint32_t* intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint32_t* intFirstRow = aIntegralImage; + + for (int x = 0; x < integralImageSize.width; x += 4) { + __m128i firstRow = _mm_load_si128((__m128i*)(intFirstRow + x)); + __m128i previousRow = _mm_load_si128((__m128i*)(intPrevRow + x)); + _mm_store_si128((__m128i*)(intRow + x), + _mm_add_epi32(firstRow, previousRow)); + } + } + + for (int y = aTopInflation + 1; y < (aSize.height + aTopInflation); y++) { + __m128i currentRowSum = _mm_setzero_si128(); + uint32_t* intRow = aIntegralImage + (y * stride32bit); + uint32_t* intPrevRow = aIntegralImage + (y - 1) * stride32bit; + uint8_t* sourceRow = aSource + aSourceStride * (y - aTopInflation); + + uint32_t pixel = sourceRow[0]; + for (int x = 0; x < aLeftInflation; x += 4) { + __m128i sumPixels = AccumulatePixelSums( + _mm_shuffle_epi32(_mm_set1_epi32(pixel), _MM_SHUFFLE(0, 0, 0, 0))); + + sumPixels = _mm_add_epi32(sumPixels, currentRowSum); + + currentRowSum = _mm_shuffle_epi32(sumPixels, _MM_SHUFFLE(3, 3, 3, 3)); + + _mm_store_si128( + (__m128i*)(intRow + x), + _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x)))); + } + for (int x = aLeftInflation; x < (aSize.width + aLeftInflation); x += 4) { + uint32_t pixels = *(uint32_t*)(sourceRow + (x - aLeftInflation)); + + // It's important to shuffle here. When we exit this loop currentRowSum + // has to be set to sumPixels, so that the following loop can get the + // correct pixel for the currentRowSum. The highest order pixel in + // currentRowSum could've originated from accumulation in the stride. + currentRowSum = _mm_shuffle_epi32(currentRowSum, _MM_SHUFFLE(3, 3, 3, 3)); + + __m128i sumPixels = AccumulatePixelSums(_mm_unpacklo_epi16( + _mm_unpacklo_epi8(_mm_set1_epi32(pixels), _mm_setzero_si128()), + _mm_setzero_si128())); + sumPixels = _mm_add_epi32(sumPixels, currentRowSum); + + currentRowSum = sumPixels; + + _mm_store_si128( + (__m128i*)(intRow + x), + _mm_add_epi32(sumPixels, _mm_load_si128((__m128i*)(intPrevRow + x)))); + } + + pixel = sourceRow[aSize.width - 1]; + int x = (aSize.width + aLeftInflation); + if ((aSize.width & 3)) { + // Deal with unaligned portion. Get the correct pixel from currentRowSum, + // see explanation above. + uint32_t intCurrentRowSum = + ((uint32_t*)¤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) const { + IntSize size = GetSize(); + + MOZ_ASSERT(size.height > 0); + + // Our 'left' or 'top' lobe will include the current pixel. i.e. when + // looking at an integral image the value of a pixel at 'x,y' is calculated + // using the value of the integral image values above/below that. + aLeftLobe++; + aTopLobe++; + int32_t boxSize = (aLeftLobe + aRightLobe) * (aTopLobe + aBottomLobe); + + MOZ_ASSERT(boxSize > 0); + + if (boxSize == 1) { + return; + } + + uint32_t reciprocal = uint32_t((uint64_t(1) << 32) / boxSize); + + uint32_t stride32bit = aIntegralImageStride / 4; + int32_t leftInflation = RoundUpToMultipleOf4(aLeftLobe).value(); + + GenerateIntegralImage_SSE2(leftInflation, aRightLobe, aTopLobe, aBottomLobe, + aIntegralImage, aIntegralImageStride, aData, + mStride, size); + + __m128i divisor = _mm_set1_epi32(reciprocal); + + // This points to the start of the rectangle within the IntegralImage that + // overlaps the surface being blurred. + uint32_t* innerIntegral = + aIntegralImage + (aTopLobe * stride32bit) + leftInflation; + + IntRect skipRect = mSkipRect; + int32_t stride = mStride; + uint8_t* data = aData; + for (int32_t y = 0; y < size.height; y++) { + // Not using ContainsY(y) because we do not skip y == skipRect.Y() + // although that may not be done on purpose + bool inSkipRectY = y > skipRect.Y() && y < skipRect.YMost(); + + uint32_t* topLeftBase = + innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + uint32_t* topRightBase = + innerIntegral + ((y - aTopLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t* bottomRightBase = + innerIntegral + + ((y + aBottomLobe) * ptrdiff_t(stride32bit) + aRightLobe); + uint32_t* bottomLeftBase = + innerIntegral + + ((y + aBottomLobe) * ptrdiff_t(stride32bit) - aLeftLobe); + + int32_t x = 0; + // Process 16 pixels at a time for as long as possible. + for (; x <= size.width - 16; x += 16) { + // Not using ContainsX(x) because we do not skip x == skipRect.X() + // although that may not be done on purpose + if (inSkipRectY && x > skipRect.X() && x < skipRect.XMost()) { + x = skipRect.XMost() - 16; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + + __m128i topLeft; + __m128i topRight; + __m128i bottomRight; + __m128i bottomLeft; + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x)); + __m128i result1 = + BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 4)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x + 4)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 4)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 4)); + __m128i result2 = + BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 8)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x + 8)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 8)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 8)); + __m128i result3 = + BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + topLeft = loadUnaligned128((__m128i*)(topLeftBase + x + 12)); + topRight = loadUnaligned128((__m128i*)(topRightBase + x + 12)); + bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x + 12)); + bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x + 12)); + __m128i result4 = + BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + + __m128i final = _mm_packus_epi16(_mm_packs_epi32(result1, result2), + _mm_packs_epi32(result3, result4)); + + _mm_storeu_si128((__m128i*)(data + stride * y + x), final); + } + + // Process the remaining pixels 4 bytes at a time. + for (; x < size.width; x += 4) { + // Not using Containsx(x) because we do not skip x == skipRect.X() + // although that may not be done on purpose + if (inSkipRectY && x > skipRect.X() && x < skipRect.XMost()) { + x = skipRect.XMost() - 4; + // Trigger early jump on coming loop iterations, this will be reset + // next line anyway. + inSkipRectY = false; + continue; + } + __m128i topLeft = loadUnaligned128((__m128i*)(topLeftBase + x)); + __m128i topRight = loadUnaligned128((__m128i*)(topRightBase + x)); + __m128i bottomRight = loadUnaligned128((__m128i*)(bottomRightBase + x)); + __m128i bottomLeft = loadUnaligned128((__m128i*)(bottomLeftBase + x)); + + __m128i result = + BlurFourPixels(topLeft, topRight, bottomRight, bottomLeft, divisor); + __m128i final = _mm_packus_epi16( + _mm_packs_epi32(result, _mm_setzero_si128()), _mm_setzero_si128()); + + *(uint32_t*)(data + stride * y + x) = _mm_cvtsi128_si32(final); + } + } +} + +} // namespace mozilla::gfx diff --git a/gfx/2d/BorrowedContext.h b/gfx/2d/BorrowedContext.h new file mode 100644 index 0000000000..a908b0497c --- /dev/null +++ b/gfx/2d/BorrowedContext.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_BORROWED_CONTEXT_H +#define _MOZILLA_GFX_BORROWED_CONTEXT_H + +#include "2D.h" + +#ifdef MOZ_X11 +# include <X11/Xlib.h> +# include "X11UndefineNone.h" +#endif + +namespace mozilla { + +namespace gfx { + +#ifdef MOZ_X11 +/* This is a helper class that let's you borrow an Xlib drawable from + * a DrawTarget. This is used for drawing themed widgets. + * + * Callers should check the Xlib drawable after constructing the object + * to see if it succeeded. The DrawTarget should not be used while + * the drawable is borrowed. */ +class BorrowedXlibDrawable { + public: + BorrowedXlibDrawable() + : mDT(nullptr), + mDisplay(nullptr), + mDrawable(X11None), + mScreen(nullptr), + mVisual(nullptr) {} + + explicit BorrowedXlibDrawable(DrawTarget* aDT) + : mDT(nullptr), + mDisplay(nullptr), + mDrawable(X11None), + mScreen(nullptr), + mVisual(nullptr) { + Init(aDT); + } + + // We can optionally Init after construction in + // case we don't know what the DT will be at construction + // time. + bool Init(DrawTarget* aDT); + + // The caller needs to call Finish if drawable is non-zero when + // they are done with the context. This is currently explicit + // instead of happening implicitly in the destructor to make + // what's happening in the caller more clear. It also + // let's you resume using the DrawTarget in the same scope. + void Finish(); + + ~BorrowedXlibDrawable() { MOZ_ASSERT(!mDrawable); } + + Display* GetDisplay() const { return mDisplay; } + Drawable GetDrawable() const { return mDrawable; } + Screen* GetScreen() const { return mScreen; } + Visual* GetVisual() const { return mVisual; } + IntSize GetSize() const { return mSize; } + Point GetOffset() const { return mOffset; } + + private: + DrawTarget* mDT; + Display* mDisplay; + Drawable mDrawable; + Screen* mScreen; + Visual* mVisual; + IntSize mSize; + Point mOffset; +}; +#endif + +#ifdef XP_DARWIN +/* This is a helper class that let's you borrow a CGContextRef from a + * DrawTargetCG. This is used for drawing themed widgets. + * + * Callers should check the cg member after constructing the object + * to see if it succeeded. The DrawTarget should not be used while + * the context is borrowed. */ +class BorrowedCGContext { + public: + BorrowedCGContext() : cg(nullptr), mDT(nullptr) {} + + explicit BorrowedCGContext(DrawTarget* aDT) : mDT(aDT) { + MOZ_ASSERT(aDT, "Caller should check for nullptr"); + cg = BorrowCGContextFromDrawTarget(aDT); + } + + // We can optionally Init after construction in + // case we don't know what the DT will be at construction + // time. + CGContextRef Init(DrawTarget* aDT) { + MOZ_ASSERT(aDT, "Caller should check for nullptr"); + MOZ_ASSERT(!mDT, "Can't initialize twice!"); + mDT = aDT; + cg = BorrowCGContextFromDrawTarget(aDT); + return cg; + } + + // The caller needs to call Finish if cg is non-null when + // they are done with the context. This is currently explicit + // instead of happening implicitly in the destructor to make + // what's happening in the caller more clear. It also + // let's you resume using the DrawTarget in the same scope. + void Finish() { + if (cg) { + ReturnCGContextToDrawTarget(mDT, cg); + cg = nullptr; + } + } + + ~BorrowedCGContext() { MOZ_ASSERT(!cg); } + + CGContextRef cg; + + private: + static CGContextRef BorrowCGContextFromDrawTarget(DrawTarget* aDT); + static void ReturnCGContextToDrawTarget(DrawTarget* aDT, CGContextRef cg); + DrawTarget* mDT; +}; +#endif + +} // namespace gfx +} // namespace mozilla + +#endif // _MOZILLA_GFX_BORROWED_CONTEXT_H diff --git a/gfx/2d/BufferEdgePad.cpp b/gfx/2d/BufferEdgePad.cpp new file mode 100644 index 0000000000..69f508f788 --- /dev/null +++ b/gfx/2d/BufferEdgePad.cpp @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BufferEdgePad.h" + +#include "2D.h" // for DrawTarget +#include "Point.h" // for IntSize +#include "Types.h" // for SurfaceFormat + +#include "nsRegion.h" + +namespace mozilla { +namespace gfx { + +void PadDrawTargetOutFromRegion(DrawTarget* aDrawTarget, + const nsIntRegion& aRegion) { + struct LockedBits { + uint8_t* data; + IntSize size; + int32_t stride; + SurfaceFormat format; + static int clamp(int x, int min, int max) { + if (x < min) x = min; + if (x > max) x = max; + return x; + } + + static void ensure_memcpy(uint8_t* dst, uint8_t* src, size_t n, + uint8_t* bitmap, int stride, int height) { + if (src + n > bitmap + stride * height) { + MOZ_CRASH("GFX: long src memcpy"); + } + if (src < bitmap) { + MOZ_CRASH("GFX: short src memcpy"); + } + if (dst + n > bitmap + stride * height) { + MOZ_CRASH("GFX: long dst mempcy"); + } + if (dst < bitmap) { + MOZ_CRASH("GFX: short dst mempcy"); + } + } + + static void visitor(void* closure, VisitSide side, int x1, int y1, int x2, + int y2) { + LockedBits* lb = static_cast<LockedBits*>(closure); + uint8_t* bitmap = lb->data; + const int bpp = gfx::BytesPerPixel(lb->format); + const int stride = lb->stride; + const int width = lb->size.width; + const int height = lb->size.height; + + if (side == VisitSide::TOP) { + if (y1 > 0) { + x1 = clamp(x1, 0, width - 1); + x2 = clamp(x2, 0, width - 1); + ensure_memcpy(&bitmap[x1 * bpp + (y1 - 1) * stride], + &bitmap[x1 * bpp + y1 * stride], (x2 - x1) * bpp, + bitmap, stride, height); + memcpy(&bitmap[x1 * bpp + (y1 - 1) * stride], + &bitmap[x1 * bpp + y1 * stride], (x2 - x1) * bpp); + } + } else if (side == VisitSide::BOTTOM) { + if (y1 < height) { + x1 = clamp(x1, 0, width - 1); + x2 = clamp(x2, 0, width - 1); + ensure_memcpy(&bitmap[x1 * bpp + y1 * stride], + &bitmap[x1 * bpp + (y1 - 1) * stride], (x2 - x1) * bpp, + bitmap, stride, height); + memcpy(&bitmap[x1 * bpp + y1 * stride], + &bitmap[x1 * bpp + (y1 - 1) * stride], (x2 - x1) * bpp); + } + } else if (side == VisitSide::LEFT) { + if (x1 > 0) { + while (y1 != y2) { + memcpy(&bitmap[(x1 - 1) * bpp + y1 * stride], + &bitmap[x1 * bpp + y1 * stride], bpp); + y1++; + } + } + } else if (side == VisitSide::RIGHT) { + if (x1 < width) { + while (y1 != y2) { + memcpy(&bitmap[x1 * bpp + y1 * stride], + &bitmap[(x1 - 1) * bpp + y1 * stride], bpp); + y1++; + } + } + } + } + } lb; + + if (aDrawTarget->LockBits(&lb.data, &lb.size, &lb.stride, &lb.format)) { + // we can only pad software targets so if we can't lock the bits don't pad + aRegion.VisitEdges(lb.visitor, &lb); + aDrawTarget->ReleaseBits(lb.data); + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/BufferEdgePad.h b/gfx/2d/BufferEdgePad.h new file mode 100644 index 0000000000..6381a5f2fa --- /dev/null +++ b/gfx/2d/BufferEdgePad.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_BUFFER_EDGE_PAD_H +#define MOZILLA_GFX_BUFFER_EDGE_PAD_H + +#include "nsRegionFwd.h" + +namespace mozilla { +namespace gfx { + +class DrawTarget; + +void PadDrawTargetOutFromRegion(DrawTarget* aDrawTarget, + const nsIntRegion& aRegion); + +} // namespace gfx +} // namespace mozilla + +#endif // MOZILLA_GFX_BUFFER_EDGE_PAD_H diff --git a/gfx/2d/BufferUnrotate.cpp b/gfx/2d/BufferUnrotate.cpp new file mode 100644 index 0000000000..7b6768fdbd --- /dev/null +++ b/gfx/2d/BufferUnrotate.cpp @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BufferUnrotate.h" + +#include <algorithm> // min & max +#include <cstdlib> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +namespace mozilla { +namespace gfx { + +void BufferUnrotate(uint8_t* aBuffer, int aByteWidth, int aHeight, + int aByteStride, int aXBoundary, int aYBoundary) { + if (aXBoundary != 0) { + uint8_t* line = new uint8_t[aByteWidth]; + uint32_t smallStart = 0; + uint32_t smallLen = aXBoundary; + uint32_t smallDest = aByteWidth - aXBoundary; + uint32_t largeStart = aXBoundary; + uint32_t largeLen = aByteWidth - aXBoundary; + uint32_t largeDest = 0; + if (aXBoundary > aByteWidth / 2) { + smallStart = aXBoundary; + smallLen = aByteWidth - aXBoundary; + smallDest = 0; + largeStart = 0; + largeLen = aXBoundary; + largeDest = smallLen; + } + + for (int y = 0; y < aHeight; y++) { + int yOffset = y * aByteStride; + memcpy(line, &aBuffer[yOffset + smallStart], smallLen); + memmove(&aBuffer[yOffset + largeDest], &aBuffer[yOffset + largeStart], + largeLen); + memcpy(&aBuffer[yOffset + smallDest], line, smallLen); + } + + delete[] line; + } + + if (aYBoundary != 0) { + uint32_t smallestHeight = std::min(aHeight - aYBoundary, aYBoundary); + uint32_t largestHeight = std::max(aHeight - aYBoundary, aYBoundary); + uint32_t smallOffset = 0; + uint32_t largeOffset = aYBoundary * aByteStride; + uint32_t largeDestOffset = 0; + uint32_t smallDestOffset = largestHeight * aByteStride; + if (aYBoundary > aHeight / 2) { + smallOffset = aYBoundary * aByteStride; + largeOffset = 0; + largeDestOffset = smallestHeight * aByteStride; + smallDestOffset = 0; + } + + uint8_t* smallestSide = new uint8_t[aByteStride * smallestHeight]; + memcpy(smallestSide, &aBuffer[smallOffset], aByteStride * smallestHeight); + memmove(&aBuffer[largeDestOffset], &aBuffer[largeOffset], + aByteStride * largestHeight); + memcpy(&aBuffer[smallDestOffset], smallestSide, + aByteStride * smallestHeight); + delete[] smallestSide; + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/BufferUnrotate.h b/gfx/2d/BufferUnrotate.h new file mode 100644 index 0000000000..f0422255f9 --- /dev/null +++ b/gfx/2d/BufferUnrotate.h @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_BUFFER_UNROTATE_H +#define MOZILLA_GFX_BUFFER_UNROTATE_H + +#include "mozilla/Types.h" + +namespace mozilla { +namespace gfx { + +void BufferUnrotate(uint8_t* aBuffer, int aByteWidth, int aHeight, + int aByteStride, int aXByteBoundary, int aYBoundary); + +} // namespace gfx +} // namespace mozilla + +#endif // MOZILLA_GFX_BUFFER_UNROTATE_H diff --git a/gfx/2d/ConicGradientEffectD2D1.cpp b/gfx/2d/ConicGradientEffectD2D1.cpp new file mode 100644 index 0000000000..df03d94580 --- /dev/null +++ b/gfx/2d/ConicGradientEffectD2D1.cpp @@ -0,0 +1,375 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ConicGradientEffectD2D1.h" + +#include "Logging.h" + +#include "ShadersD2D1.h" +#include "HelpersD2D.h" + +#include <vector> + +#define TEXTW(x) L##x +#define XML(X) \ + TEXTW(#X) // This macro creates a single string from multiple lines of text. + +static const PCWSTR kXmlDescription = + XML( + <?xml version='1.0'?> + <Effect> + <!-- System Properties --> + <Property name='DisplayName' type='string' value='ConicGradientEffect'/> + <Property name='Author' type='string' value='Mozilla'/> + <Property name='Category' type='string' value='Pattern effects'/> + <Property name='Description' type='string' value='This effect is used to render CSS conic gradients.'/> + <Inputs> + <Input name='Geometry'/> + </Inputs> + <Property name='StopCollection' type='iunknown'> + <Property name='DisplayName' type='string' value='Gradient stop collection'/> + </Property> + <Property name='Center' type='vector2'> + <Property name='DisplayName' type='string' value='Gradient center'/> + </Property> + <Property name='Angle' type='vector2'> + <Property name='DisplayName' type='string' value='Gradient angle'/> + </Property> + <Property name='StartOffset' type='float'> + <Property name='DisplayName' type='string' value='Start stop offset'/> + </Property> + <Property name='EndOffset' type='float'> + <Property name='DisplayName' type='string' value='End stop offset'/> + </Property> + <Property name='Transform' type='matrix3x2'> + <Property name='DisplayName' type='string' value='Transform applied to the pattern'/> + </Property> + + </Effect> + ); + +// {091fda1d-857e-4b1e-828f-1c839d9b7897} +static const GUID GUID_SampleConicGradientPS = { + 0x091fda1d, + 0x857e, + 0x4b1e, + {0x82, 0x8f, 0x1c, 0x83, 0x9d, 0x9b, 0x78, 0x97}}; + +namespace mozilla { +namespace gfx { + +ConicGradientEffectD2D1::ConicGradientEffectD2D1() + : mRefCount(0), + mCenter(D2D1::Vector2F(0, 0)), + mAngle(0), + mStartOffset(0), + mEndOffset(0), + mTransform(D2D1::IdentityMatrix()) + +{} + +IFACEMETHODIMP +ConicGradientEffectD2D1::Initialize(ID2D1EffectContext* pContextInternal, + ID2D1TransformGraph* pTransformGraph) { + HRESULT hr; + + hr = pContextInternal->LoadPixelShader(GUID_SampleConicGradientPS, + SampleConicGradientPS, + sizeof(SampleConicGradientPS)); + + if (FAILED(hr)) { + return hr; + } + + hr = pTransformGraph->SetSingleTransformNode(this); + + if (FAILED(hr)) { + return hr; + } + + mEffectContext = pContextInternal; + + return S_OK; +} + +IFACEMETHODIMP +ConicGradientEffectD2D1::PrepareForRender(D2D1_CHANGE_TYPE changeType) { + if (changeType == D2D1_CHANGE_TYPE_NONE) { + return S_OK; + } + + // We'll need to inverse transform our pixel, precompute inverse here. + Matrix mat = ToMatrix(mTransform); + if (!mat.Invert()) { + // Singular + return S_OK; + } + + if (!mStopCollection) { + return S_OK; + } + + HRESULT hr = mDrawInfo->SetPixelShader(GUID_SampleConicGradientPS); + + if (FAILED(hr)) { + return hr; + } + + RefPtr<ID2D1ResourceTexture> tex = CreateGradientTexture(); + hr = mDrawInfo->SetResourceTexture(1, tex); + + if (FAILED(hr)) { + return hr; + } + + struct PSConstantBuffer { + float center[2]; + float angle; + float start_offset; + float end_offset; + float repeat_correct; + float allow_odd; + float padding[1]; + float transform[8]; + }; + + PSConstantBuffer buffer = { + {mCenter.x, mCenter.y}, + mAngle, + mStartOffset, + mEndOffset, + mStopCollection->GetExtendMode() != D2D1_EXTEND_MODE_CLAMP ? 1.0f : 0.0f, + mStopCollection->GetExtendMode() == D2D1_EXTEND_MODE_MIRROR ? 1.0f : 0.0f, + {0.0f}, + {mat._11, mat._21, mat._31, 0.0f, mat._12, mat._22, mat._32, 0.0f}}; + + hr = mDrawInfo->SetPixelShaderConstantBuffer((BYTE*)&buffer, sizeof(buffer)); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +IFACEMETHODIMP +ConicGradientEffectD2D1::SetGraph(ID2D1TransformGraph* pGraph) { + return pGraph->SetSingleTransformNode(this); +} + +IFACEMETHODIMP_(ULONG) +ConicGradientEffectD2D1::AddRef() { return ++mRefCount; } + +IFACEMETHODIMP_(ULONG) +ConicGradientEffectD2D1::Release() { + if (!--mRefCount) { + delete this; + return 0; + } + return mRefCount; +} + +IFACEMETHODIMP +ConicGradientEffectD2D1::QueryInterface(const IID& aIID, void** aPtr) { + if (!aPtr) { + return E_POINTER; + } + + if (aIID == IID_IUnknown) { + *aPtr = static_cast<IUnknown*>(static_cast<ID2D1EffectImpl*>(this)); + } else if (aIID == IID_ID2D1EffectImpl) { + *aPtr = static_cast<ID2D1EffectImpl*>(this); + } else if (aIID == IID_ID2D1DrawTransform) { + *aPtr = static_cast<ID2D1DrawTransform*>(this); + } else if (aIID == IID_ID2D1Transform) { + *aPtr = static_cast<ID2D1Transform*>(this); + } else if (aIID == IID_ID2D1TransformNode) { + *aPtr = static_cast<ID2D1TransformNode*>(this); + } else { + return E_NOINTERFACE; + } + + static_cast<IUnknown*>(*aPtr)->AddRef(); + return S_OK; +} + +IFACEMETHODIMP +ConicGradientEffectD2D1::MapInputRectsToOutputRect( + const D2D1_RECT_L* pInputRects, const D2D1_RECT_L* pInputOpaqueSubRects, + UINT32 inputRectCount, D2D1_RECT_L* pOutputRect, + D2D1_RECT_L* pOutputOpaqueSubRect) { + if (inputRectCount != 1) { + return E_INVALIDARG; + } + + *pOutputRect = *pInputRects; + *pOutputOpaqueSubRect = *pInputOpaqueSubRects; + return S_OK; +} + +IFACEMETHODIMP +ConicGradientEffectD2D1::MapOutputRectToInputRects( + const D2D1_RECT_L* pOutputRect, D2D1_RECT_L* pInputRects, + UINT32 inputRectCount) const { + if (inputRectCount != 1) { + return E_INVALIDARG; + } + + *pInputRects = *pOutputRect; + return S_OK; +} + +IFACEMETHODIMP +ConicGradientEffectD2D1::MapInvalidRect(UINT32 inputIndex, + D2D1_RECT_L invalidInputRect, + D2D1_RECT_L* pInvalidOutputRect) const { + MOZ_ASSERT(inputIndex == 0); + + *pInvalidOutputRect = invalidInputRect; + return S_OK; +} + +IFACEMETHODIMP +ConicGradientEffectD2D1::SetDrawInfo(ID2D1DrawInfo* pDrawInfo) { + mDrawInfo = pDrawInfo; + return S_OK; +} + +HRESULT +ConicGradientEffectD2D1::Register(ID2D1Factory1* aFactory) { + D2D1_PROPERTY_BINDING bindings[] = { + D2D1_VALUE_TYPE_BINDING(L"StopCollection", + &ConicGradientEffectD2D1::SetStopCollection, + &ConicGradientEffectD2D1::GetStopCollection), + D2D1_VALUE_TYPE_BINDING(L"Center", &ConicGradientEffectD2D1::SetCenter, + &ConicGradientEffectD2D1::GetCenter), + D2D1_VALUE_TYPE_BINDING(L"Angle", &ConicGradientEffectD2D1::SetAngle, + &ConicGradientEffectD2D1::GetAngle), + D2D1_VALUE_TYPE_BINDING(L"StartOffset", + &ConicGradientEffectD2D1::SetStartOffset, + &ConicGradientEffectD2D1::GetStartOffset), + D2D1_VALUE_TYPE_BINDING(L"EndOffset", + &ConicGradientEffectD2D1::SetEndOffset, + &ConicGradientEffectD2D1::GetEndOffset), + D2D1_VALUE_TYPE_BINDING(L"Transform", + &ConicGradientEffectD2D1::SetTransform, + &ConicGradientEffectD2D1::GetTransform)}; + HRESULT hr = aFactory->RegisterEffectFromString( + CLSID_ConicGradientEffect, kXmlDescription, bindings, ARRAYSIZE(bindings), + CreateEffect); + + if (FAILED(hr)) { + gfxWarning() << "Failed to register radial gradient effect."; + } + return hr; +} + +void ConicGradientEffectD2D1::Unregister(ID2D1Factory1* aFactory) { + aFactory->UnregisterEffect(CLSID_ConicGradientEffect); +} + +HRESULT __stdcall ConicGradientEffectD2D1::CreateEffect( + IUnknown** aEffectImpl) { + *aEffectImpl = static_cast<ID2D1EffectImpl*>(new ConicGradientEffectD2D1()); + (*aEffectImpl)->AddRef(); + + return S_OK; +} + +HRESULT +ConicGradientEffectD2D1::SetStopCollection(IUnknown* aStopCollection) { + if (SUCCEEDED(aStopCollection->QueryInterface( + (ID2D1GradientStopCollection**)getter_AddRefs(mStopCollection)))) { + return S_OK; + } + + return E_INVALIDARG; +} + +already_AddRefed<ID2D1ResourceTexture> +ConicGradientEffectD2D1::CreateGradientTexture() { + std::vector<D2D1_GRADIENT_STOP> rawStops; + rawStops.resize(mStopCollection->GetGradientStopCount()); + mStopCollection->GetGradientStops(&rawStops.front(), rawStops.size()); + + std::vector<unsigned char> textureData; + textureData.resize(4096 * 4); + unsigned char* texData = &textureData.front(); + + float prevColorPos = 0; + float nextColorPos = rawStops[0].position; + D2D1_COLOR_F prevColor = rawStops[0].color; + D2D1_COLOR_F nextColor = prevColor; + uint32_t stopPosition = 1; + + // Not the most optimized way but this will do for now. + for (int i = 0; i < 4096; i++) { + // The 4095 seems a little counter intuitive, but we want the gradient + // color at offset 0 at the first pixel, and at offset 1.0f at the last + // pixel. + float pos = float(i) / 4095; + + while (pos > nextColorPos) { + prevColor = nextColor; + prevColorPos = nextColorPos; + if (rawStops.size() > stopPosition) { + nextColor = rawStops[stopPosition].color; + nextColorPos = rawStops[stopPosition++].position; + } else { + nextColorPos = 1.0f; + } + } + + float interp; + + if (nextColorPos != prevColorPos) { + interp = (pos - prevColorPos) / (nextColorPos - prevColorPos); + } else { + interp = 0; + } + + DeviceColor newColor(prevColor.r + (nextColor.r - prevColor.r) * interp, + prevColor.g + (nextColor.g - prevColor.g) * interp, + prevColor.b + (nextColor.b - prevColor.b) * interp, + prevColor.a + (nextColor.a - prevColor.a) * interp); + + // Note D2D expects RGBA here!! + texData[i * 4] = (unsigned char)(255.0f * newColor.r); + texData[i * 4 + 1] = (unsigned char)(255.0f * newColor.g); + texData[i * 4 + 2] = (unsigned char)(255.0f * newColor.b); + texData[i * 4 + 3] = (unsigned char)(255.0f * newColor.a); + } + + RefPtr<ID2D1ResourceTexture> tex; + + UINT32 width = 4096; + UINT32 stride = 4096 * 4; + D2D1_RESOURCE_TEXTURE_PROPERTIES props; + // Older shader models do not support 1D textures. So just use a width x 1 + // texture. + props.dimensions = 2; + UINT32 dims[] = {width, 1}; + props.extents = dims; + props.channelDepth = D2D1_CHANNEL_DEPTH_4; + props.bufferPrecision = D2D1_BUFFER_PRECISION_8BPC_UNORM; + props.filter = D2D1_FILTER_MIN_MAG_MIP_LINEAR; + D2D1_EXTEND_MODE extendMode[] = {mStopCollection->GetExtendMode(), + mStopCollection->GetExtendMode()}; + props.extendModes = extendMode; + + HRESULT hr = mEffectContext->CreateResourceTexture( + nullptr, &props, &textureData.front(), &stride, 4096 * 4, + getter_AddRefs(tex)); + + if (FAILED(hr)) { + gfxWarning() << "Failed to create resource texture: " << hexa(hr); + } + + return tex.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/ConicGradientEffectD2D1.h b/gfx/2d/ConicGradientEffectD2D1.h new file mode 100644 index 0000000000..b0e168417b --- /dev/null +++ b/gfx/2d/ConicGradientEffectD2D1.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_CONICGRADIENTEFFECTD2D1_H_ +#define MOZILLA_GFX_CONICGRADIENTEFFECTD2D1_H_ + +#include <d2d1_1.h> +#include <d2d1effectauthor.h> +#include <d2d1effecthelpers.h> + +#include "2D.h" +#include "mozilla/Attributes.h" + +// {fa4e3246-be57-4052-8c8b-881cc3633287} +DEFINE_GUID(CLSID_ConicGradientEffect, 0xfa4e3246, 0xbe57, 0x4052, 0x8c, 0x8b, + 0x88, 0x1c, 0xc3, 0x63, 0x32, 0x87); + +// Macro to keep our class nice and clean. +#define SIMPLE_PROP(type, name) \ + public: \ + HRESULT Set##name(type a##name) { \ + m##name = a##name; \ + return S_OK; \ + } \ + type Get##name() const { return m##name; } \ + \ + private: \ + type m##name; + +namespace mozilla { +namespace gfx { + +enum { + CONIC_PROP_STOP_COLLECTION = 0, + CONIC_PROP_CENTER, + CONIC_PROP_ANGLE, + CONIC_PROP_START_OFFSET, + CONIC_PROP_END_OFFSET, + CONIC_PROP_TRANSFORM +}; + +class ConicGradientEffectD2D1 final : public ID2D1EffectImpl, + public ID2D1DrawTransform { + public: + // ID2D1EffectImpl + IFACEMETHODIMP Initialize(ID2D1EffectContext* pContextInternal, + ID2D1TransformGraph* pTransformGraph); + IFACEMETHODIMP PrepareForRender(D2D1_CHANGE_TYPE changeType); + IFACEMETHODIMP SetGraph(ID2D1TransformGraph* pGraph); + + // IUnknown + IFACEMETHODIMP_(ULONG) AddRef(); + IFACEMETHODIMP_(ULONG) Release(); + IFACEMETHODIMP QueryInterface(REFIID riid, void** ppOutput); + + // ID2D1Transform + IFACEMETHODIMP MapInputRectsToOutputRect( + const D2D1_RECT_L* pInputRects, const D2D1_RECT_L* pInputOpaqueSubRects, + UINT32 inputRectCount, D2D1_RECT_L* pOutputRect, + D2D1_RECT_L* pOutputOpaqueSubRect); + IFACEMETHODIMP MapOutputRectToInputRects(const D2D1_RECT_L* pOutputRect, + D2D1_RECT_L* pInputRects, + UINT32 inputRectCount) const; + IFACEMETHODIMP MapInvalidRect(UINT32 inputIndex, D2D1_RECT_L invalidInputRect, + D2D1_RECT_L* pInvalidOutputRect) const; + + // ID2D1TransformNode + IFACEMETHODIMP_(UINT32) GetInputCount() const { return 1; } + + // ID2D1DrawTransform + IFACEMETHODIMP SetDrawInfo(ID2D1DrawInfo* pDrawInfo); + + static HRESULT Register(ID2D1Factory1* aFactory); + static void Unregister(ID2D1Factory1* aFactory); + static HRESULT __stdcall CreateEffect(IUnknown** aEffectImpl); + + HRESULT SetStopCollection(IUnknown* aStopCollection); + IUnknown* GetStopCollection() const { return mStopCollection; } + + private: + already_AddRefed<ID2D1ResourceTexture> CreateGradientTexture(); + + ConicGradientEffectD2D1(); + + uint32_t mRefCount; + RefPtr<ID2D1GradientStopCollection> mStopCollection; + RefPtr<ID2D1EffectContext> mEffectContext; + RefPtr<ID2D1DrawInfo> mDrawInfo; + SIMPLE_PROP(D2D1_VECTOR_2F, Center); + SIMPLE_PROP(FLOAT, Angle); + SIMPLE_PROP(FLOAT, StartOffset); + SIMPLE_PROP(FLOAT, EndOffset); + SIMPLE_PROP(D2D_MATRIX_3X2_F, Transform); +}; + +} // namespace gfx +} // namespace mozilla +#undef SIMPLE_PROP + +#endif diff --git a/gfx/2d/ConvolutionFilter.cpp b/gfx/2d/ConvolutionFilter.cpp new file mode 100644 index 0000000000..31ace83dc7 --- /dev/null +++ b/gfx/2d/ConvolutionFilter.cpp @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ConvolutionFilter.h" +#include "HelpersSkia.h" +#include "SkConvolver.h" +#include "skia/include/core/SkBitmap.h" + +namespace mozilla::gfx { + +ConvolutionFilter::ConvolutionFilter() + : mFilter(MakeUnique<skia::SkConvolutionFilter1D>()) {} + +ConvolutionFilter::~ConvolutionFilter() = default; + +int32_t ConvolutionFilter::MaxFilter() const { return mFilter->maxFilter(); } + +int32_t ConvolutionFilter::NumValues() const { return mFilter->numValues(); } + +bool ConvolutionFilter::GetFilterOffsetAndLength(int32_t aRowIndex, + int32_t* aResultOffset, + int32_t* aResultLength) { + if (aRowIndex >= mFilter->numValues()) { + return false; + } + mFilter->FilterForValue(aRowIndex, aResultOffset, aResultLength); + return true; +} + +void ConvolutionFilter::ConvolveHorizontally(const uint8_t* aSrc, uint8_t* aDst, + bool aHasAlpha) { + skia::convolve_horizontally(aSrc, *mFilter, aDst, aHasAlpha); +} + +void ConvolutionFilter::ConvolveVertically(uint8_t* const* aSrc, uint8_t* aDst, + int32_t aRowIndex, int32_t aRowSize, + bool aHasAlpha) { + MOZ_ASSERT(aRowIndex < mFilter->numValues()); + + int32_t filterOffset; + int32_t filterLength; + auto filterValues = + mFilter->FilterForValue(aRowIndex, &filterOffset, &filterLength); + skia::convolve_vertically(filterValues, filterLength, aSrc, aRowSize, aDst, + aHasAlpha); +} + +bool ConvolutionFilter::ComputeResizeFilter(ResizeMethod aResizeMethod, + int32_t aSrcSize, + int32_t aDstSize) { + if (aSrcSize < 0 || aDstSize < 0) { + return false; + } + + switch (aResizeMethod) { + case ResizeMethod::BOX: + return mFilter->ComputeFilterValues(skia::SkBoxFilter(), aSrcSize, + aDstSize); + case ResizeMethod::LANCZOS3: + return mFilter->ComputeFilterValues(skia::SkLanczosFilter(), aSrcSize, + aDstSize); + default: + return false; + } +} + +bool Scale(uint8_t* srcData, int32_t srcWidth, int32_t srcHeight, + int32_t srcStride, uint8_t* dstData, int32_t dstWidth, + int32_t dstHeight, int32_t dstStride, SurfaceFormat format) { + if (!srcData || !dstData || srcWidth < 1 || srcHeight < 1 || dstWidth < 1 || + dstHeight < 1) { + return false; + } + + SkPixmap srcPixmap(MakeSkiaImageInfo(IntSize(srcWidth, srcHeight), format), + srcData, srcStride); + + // Rescaler is compatible with N32 only. Convert to N32 if needed. + SkBitmap tmpBitmap; + if (srcPixmap.colorType() != kN32_SkColorType) { + if (!tmpBitmap.tryAllocPixels( + SkImageInfo::MakeN32Premul(srcWidth, srcHeight)) || + !tmpBitmap.writePixels(srcPixmap) || + !tmpBitmap.peekPixels(&srcPixmap)) { + return false; + } + } + + ConvolutionFilter xFilter; + ConvolutionFilter yFilter; + ConvolutionFilter* xOrYFilter = &xFilter; + bool isSquare = srcWidth == srcHeight && dstWidth == dstHeight; + if (!xFilter.ComputeResizeFilter(ConvolutionFilter::ResizeMethod::LANCZOS3, + srcWidth, dstWidth)) { + return false; + } + if (!isSquare) { + if (!yFilter.ComputeResizeFilter(ConvolutionFilter::ResizeMethod::LANCZOS3, + srcHeight, dstHeight)) { + return false; + } + xOrYFilter = &yFilter; + } + + return skia::BGRAConvolve2D( + static_cast<const uint8_t*>(srcPixmap.addr()), int(srcPixmap.rowBytes()), + !srcPixmap.isOpaque(), xFilter.GetSkiaFilter(), + xOrYFilter->GetSkiaFilter(), int(dstStride), dstData); +} + +} // namespace mozilla::gfx diff --git a/gfx/2d/ConvolutionFilter.h b/gfx/2d/ConvolutionFilter.h new file mode 100644 index 0000000000..282222b8d6 --- /dev/null +++ b/gfx/2d/ConvolutionFilter.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_CONVOLUTION_FILTER_H_ +#define MOZILLA_GFX_CONVOLUTION_FILTER_H_ + +#include "mozilla/UniquePtr.h" + +namespace skia { +class SkConvolutionFilter1D; +} + +namespace mozilla { +namespace gfx { + +class ConvolutionFilter final { + public: + ConvolutionFilter(); + ~ConvolutionFilter(); + + int32_t MaxFilter() const; + int32_t NumValues() const; + + bool GetFilterOffsetAndLength(int32_t aRowIndex, int32_t* aResultOffset, + int32_t* aResultLength); + + void ConvolveHorizontally(const uint8_t* aSrc, uint8_t* aDst, bool aHasAlpha); + void ConvolveVertically(uint8_t* const* aSrc, uint8_t* aDst, + int32_t aRowIndex, int32_t aRowSize, bool aHasAlpha); + + enum class ResizeMethod { BOX, LANCZOS3 }; + + bool ComputeResizeFilter(ResizeMethod aResizeMethod, int32_t aSrcSize, + int32_t aDstSize); + + static inline size_t PadBytesForSIMD(size_t aBytes) { + return (aBytes + 31) & ~31; + } + + const skia::SkConvolutionFilter1D& GetSkiaFilter() const { + return *mFilter.get(); + } + + private: + UniquePtr<skia::SkConvolutionFilter1D> mFilter; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_CONVOLUTION_FILTER_H_ */ diff --git a/gfx/2d/ConvolutionFilterAVX2.cpp b/gfx/2d/ConvolutionFilterAVX2.cpp new file mode 100644 index 0000000000..633e95b830 --- /dev/null +++ b/gfx/2d/ConvolutionFilterAVX2.cpp @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2011-2016 Google Inc. +// Use of this source code is governed by a BSD-style license that can be +// found in the gfx/skia/LICENSE file. + +#include "SkConvolver.h" +#include <immintrin.h> + +namespace skia { + +void convolve_vertically_avx2( + const SkConvolutionFilter1D::ConvolutionFixed* filter, int filterLen, + unsigned char* const* srcRows, int width, unsigned char* out, + bool hasAlpha) { + // It's simpler to work with the output array in terms of 4-byte pixels. + auto* dst = (int*)out; + + // Output up to eight pixels per iteration. + for (int x = 0; x < width; x += 8) { + // Accumulated result for 4 (non-adjacent) pairs of pixels, + // with each channel in signed 17.14 fixed point. + auto accum04 = _mm256_setzero_si256(), accum15 = _mm256_setzero_si256(), + accum26 = _mm256_setzero_si256(), accum37 = _mm256_setzero_si256(); + + // Convolve with the filter. (This inner loop is where we spend ~all our + // time.) While we can, we consume 2 filter coefficients and 2 rows of 8 + // pixels each at a time. + auto convolve_16_pixels = [&](__m256i interlaced_coeffs, + __m256i pixels_01234567, + __m256i pixels_89ABCDEF) { + // Interlaced R0R8 G0G8 B0B8 A0A8 R1R9 G1G9... 32 8-bit values each. + auto _08194C5D = _mm256_unpacklo_epi8(pixels_01234567, pixels_89ABCDEF), + _2A3B6E7F = _mm256_unpackhi_epi8(pixels_01234567, pixels_89ABCDEF); + + // Still interlaced R0R8 G0G8... as above, each channel expanded to 16-bit + // lanes. + auto _084C = _mm256_unpacklo_epi8(_08194C5D, _mm256_setzero_si256()), + _195D = _mm256_unpackhi_epi8(_08194C5D, _mm256_setzero_si256()), + _2A6E = _mm256_unpacklo_epi8(_2A3B6E7F, _mm256_setzero_si256()), + _3B7F = _mm256_unpackhi_epi8(_2A3B6E7F, _mm256_setzero_si256()); + + // accum0_R += R0*coeff0 + R8*coeff1, etc. + accum04 = _mm256_add_epi32(accum04, + _mm256_madd_epi16(_084C, interlaced_coeffs)); + accum15 = _mm256_add_epi32(accum15, + _mm256_madd_epi16(_195D, interlaced_coeffs)); + accum26 = _mm256_add_epi32(accum26, + _mm256_madd_epi16(_2A6E, interlaced_coeffs)); + accum37 = _mm256_add_epi32(accum37, + _mm256_madd_epi16(_3B7F, interlaced_coeffs)); + }; + + int i = 0; + for (; i < filterLen / 2 * 2; i += 2) { + convolve_16_pixels( + _mm256_set1_epi32(*(const int32_t*)(filter + i)), + _mm256_loadu_si256((const __m256i*)(srcRows[i + 0] + x * 4)), + _mm256_loadu_si256((const __m256i*)(srcRows[i + 1] + x * 4))); + } + if (i < filterLen) { + convolve_16_pixels( + _mm256_set1_epi32(*(const int16_t*)(filter + i)), + _mm256_loadu_si256((const __m256i*)(srcRows[i] + x * 4)), + _mm256_setzero_si256()); + } + + // Trim the fractional parts off the accumulators. + accum04 = _mm256_srai_epi32(accum04, 14); + accum15 = _mm256_srai_epi32(accum15, 14); + accum26 = _mm256_srai_epi32(accum26, 14); + accum37 = _mm256_srai_epi32(accum37, 14); + + // Pack back down to 8-bit channels. + auto pixels = _mm256_packus_epi16(_mm256_packs_epi32(accum04, accum15), + _mm256_packs_epi32(accum26, accum37)); + + if (hasAlpha) { + // Clamp alpha to the max of r,g,b to make sure we stay premultiplied. + __m256i max_rg = _mm256_max_epu8(pixels, _mm256_srli_epi32(pixels, 8)), + max_rgb = _mm256_max_epu8(max_rg, _mm256_srli_epi32(pixels, 16)); + pixels = _mm256_max_epu8(pixels, _mm256_slli_epi32(max_rgb, 24)); + } else { + // Force opaque. + pixels = _mm256_or_si256(pixels, _mm256_set1_epi32(0xff000000)); + } + + // Normal path to store 8 pixels. + if (x + 8 <= width) { + _mm256_storeu_si256((__m256i*)dst, pixels); + dst += 8; + continue; + } + + // Store one pixel at a time on the last iteration. + for (int i = x; i < width; i++) { + *dst++ = _mm_cvtsi128_si32(_mm256_castsi256_si128(pixels)); + pixels = _mm256_permutevar8x32_epi32( + pixels, _mm256_setr_epi32(1, 2, 3, 4, 5, 6, 7, 0)); + } + } +} + +} // namespace skia diff --git a/gfx/2d/ConvolutionFilterNEON.cpp b/gfx/2d/ConvolutionFilterNEON.cpp new file mode 100644 index 0000000000..9983a0681a --- /dev/null +++ b/gfx/2d/ConvolutionFilterNEON.cpp @@ -0,0 +1,287 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2011-2016 Google Inc. +// Use of this source code is governed by a BSD-style license that can be +// found in the gfx/skia/LICENSE file. + +#include "SkConvolver.h" +#include "mozilla/Attributes.h" +#include <arm_neon.h> + +namespace skia { + +static MOZ_ALWAYS_INLINE void AccumRemainder( + const unsigned char* pixelsLeft, + const SkConvolutionFilter1D::ConvolutionFixed* filterValues, + int32x4_t& accum, int r) { + int remainder[4] = {0}; + for (int i = 0; i < r; i++) { + SkConvolutionFilter1D::ConvolutionFixed coeff = filterValues[i]; + remainder[0] += coeff * pixelsLeft[i * 4 + 0]; + remainder[1] += coeff * pixelsLeft[i * 4 + 1]; + remainder[2] += coeff * pixelsLeft[i * 4 + 2]; + remainder[3] += coeff * pixelsLeft[i * 4 + 3]; + } + int32x4_t t = {remainder[0], remainder[1], remainder[2], remainder[3]}; + accum += t; +} + +// Convolves horizontally along a single row. The row data is given in +// |srcData| and continues for the numValues() of the filter. +void convolve_horizontally_neon(const unsigned char* srcData, + const SkConvolutionFilter1D& filter, + unsigned char* outRow, bool /*hasAlpha*/) { + // Loop over each pixel on this row in the output image. + int numValues = filter.numValues(); + for (int outX = 0; outX < numValues; outX++) { + uint8x8_t coeff_mask0 = vcreate_u8(0x0100010001000100); + uint8x8_t coeff_mask1 = vcreate_u8(0x0302030203020302); + uint8x8_t coeff_mask2 = vcreate_u8(0x0504050405040504); + uint8x8_t coeff_mask3 = vcreate_u8(0x0706070607060706); + // Get the filter that determines the current output pixel. + int filterOffset, filterLength; + const SkConvolutionFilter1D::ConvolutionFixed* filterValues = + filter.FilterForValue(outX, &filterOffset, &filterLength); + + // Compute the first pixel in this row that the filter affects. It will + // touch |filterLength| pixels (4 bytes each) after this. + const unsigned char* rowToFilter = &srcData[filterOffset * 4]; + + // Apply the filter to the row to get the destination pixel in |accum|. + int32x4_t accum = vdupq_n_s32(0); + for (int filterX = 0; filterX < filterLength >> 2; filterX++) { + // Load 4 coefficients + int16x4_t coeffs, coeff0, coeff1, coeff2, coeff3; + coeffs = vld1_s16(filterValues); + coeff0 = vreinterpret_s16_u8( + vtbl1_u8(vreinterpret_u8_s16(coeffs), coeff_mask0)); + coeff1 = vreinterpret_s16_u8( + vtbl1_u8(vreinterpret_u8_s16(coeffs), coeff_mask1)); + coeff2 = vreinterpret_s16_u8( + vtbl1_u8(vreinterpret_u8_s16(coeffs), coeff_mask2)); + coeff3 = vreinterpret_s16_u8( + vtbl1_u8(vreinterpret_u8_s16(coeffs), coeff_mask3)); + + // Load pixels and calc + uint8x16_t pixels = vld1q_u8(rowToFilter); + int16x8_t p01_16 = vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(pixels))); + int16x8_t p23_16 = vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(pixels))); + + int16x4_t p0_src = vget_low_s16(p01_16); + int16x4_t p1_src = vget_high_s16(p01_16); + int16x4_t p2_src = vget_low_s16(p23_16); + int16x4_t p3_src = vget_high_s16(p23_16); + + int32x4_t p0 = vmull_s16(p0_src, coeff0); + int32x4_t p1 = vmull_s16(p1_src, coeff1); + int32x4_t p2 = vmull_s16(p2_src, coeff2); + int32x4_t p3 = vmull_s16(p3_src, coeff3); + + accum += p0; + accum += p1; + accum += p2; + accum += p3; + + // Advance the pointers + rowToFilter += 16; + filterValues += 4; + } + + int r = filterLength & 3; + if (r) { + int remainder_offset = (filterOffset + filterLength - r) * 4; + AccumRemainder(srcData + remainder_offset, filterValues, accum, r); + } + + // Bring this value back in range. All of the filter scaling factors + // are in fixed point with kShiftBits bits of fractional part. + accum = vshrq_n_s32(accum, SkConvolutionFilter1D::kShiftBits); + + // Pack and store the new pixel. + int16x4_t accum16 = vqmovn_s32(accum); + uint8x8_t accum8 = vqmovun_s16(vcombine_s16(accum16, accum16)); + vst1_lane_u32(reinterpret_cast<uint32_t*>(outRow), + vreinterpret_u32_u8(accum8), 0); + outRow += 4; + } +} + +// Does vertical convolution to produce one output row. The filter values and +// length are given in the first two parameters. These are applied to each +// of the rows pointed to in the |sourceDataRows| array, with each row +// being |pixelWidth| wide. +// +// The output must have room for |pixelWidth * 4| bytes. +template <bool hasAlpha> +static void ConvolveVertically( + const SkConvolutionFilter1D::ConvolutionFixed* filterValues, + int filterLength, unsigned char* const* sourceDataRows, int pixelWidth, + unsigned char* outRow) { + int width = pixelWidth & ~3; + + // Output four pixels per iteration (16 bytes). + for (int outX = 0; outX < width; outX += 4) { + // Accumulated result for each pixel. 32 bits per RGBA channel. + int32x4_t accum0 = vdupq_n_s32(0); + int32x4_t accum1 = vdupq_n_s32(0); + int32x4_t accum2 = vdupq_n_s32(0); + int32x4_t accum3 = vdupq_n_s32(0); + + // Convolve with one filter coefficient per iteration. + for (int filterY = 0; filterY < filterLength; filterY++) { + // Duplicate the filter coefficient 4 times. + // [16] cj cj cj cj + int16x4_t coeff16 = vdup_n_s16(filterValues[filterY]); + + // Load four pixels (16 bytes) together. + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + uint8x16_t src8 = vld1q_u8(&sourceDataRows[filterY][outX << 2]); + + int16x8_t src16_01 = vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(src8))); + int16x8_t src16_23 = vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(src8))); + int16x4_t src16_0 = vget_low_s16(src16_01); + int16x4_t src16_1 = vget_high_s16(src16_01); + int16x4_t src16_2 = vget_low_s16(src16_23); + int16x4_t src16_3 = vget_high_s16(src16_23); + + accum0 += vmull_s16(src16_0, coeff16); + accum1 += vmull_s16(src16_1, coeff16); + accum2 += vmull_s16(src16_2, coeff16); + accum3 += vmull_s16(src16_3, coeff16); + } + + // Shift right for fixed point implementation. + accum0 = vshrq_n_s32(accum0, SkConvolutionFilter1D::kShiftBits); + accum1 = vshrq_n_s32(accum1, SkConvolutionFilter1D::kShiftBits); + accum2 = vshrq_n_s32(accum2, SkConvolutionFilter1D::kShiftBits); + accum3 = vshrq_n_s32(accum3, SkConvolutionFilter1D::kShiftBits); + + // Packing 32 bits |accum| to 16 bits per channel (signed saturation). + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + int16x8_t accum16_0 = vcombine_s16(vqmovn_s32(accum0), vqmovn_s32(accum1)); + // [16] a3 b3 g3 r3 a2 b2 g2 r2 + int16x8_t accum16_1 = vcombine_s16(vqmovn_s32(accum2), vqmovn_s32(accum3)); + + // Packing 16 bits |accum| to 8 bits per channel (unsigned saturation). + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + uint8x16_t accum8 = + vcombine_u8(vqmovun_s16(accum16_0), vqmovun_s16(accum16_1)); + + if (hasAlpha) { + // Compute the max(ri, gi, bi) for each pixel. + // [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0 + uint8x16_t a = + vreinterpretq_u8_u32(vshrq_n_u32(vreinterpretq_u32_u8(accum8), 8)); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + uint8x16_t b = vmaxq_u8(a, accum8); // Max of r and g + // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0 + a = vreinterpretq_u8_u32(vshrq_n_u32(vreinterpretq_u32_u8(accum8), 16)); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + b = vmaxq_u8(a, b); // Max of r and g and b. + // [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00 + b = vreinterpretq_u8_u32(vshlq_n_u32(vreinterpretq_u32_u8(b), 24)); + + // Make sure the value of alpha channel is always larger than maximum + // value of color channels. + accum8 = vmaxq_u8(b, accum8); + } else { + // Set value of alpha channels to 0xFF. + accum8 = vreinterpretq_u8_u32(vreinterpretq_u32_u8(accum8) | + vdupq_n_u32(0xFF000000)); + } + + // Store the convolution result (16 bytes) and advance the pixel pointers. + vst1q_u8(outRow, accum8); + outRow += 16; + } + + // Process the leftovers when the width of the output is not divisible + // by 4, that is at most 3 pixels. + int r = pixelWidth & 3; + if (r) { + int32x4_t accum0 = vdupq_n_s32(0); + int32x4_t accum1 = vdupq_n_s32(0); + int32x4_t accum2 = vdupq_n_s32(0); + + for (int filterY = 0; filterY < filterLength; ++filterY) { + int16x4_t coeff16 = vdup_n_s16(filterValues[filterY]); + + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + uint8x16_t src8 = vld1q_u8(&sourceDataRows[filterY][width << 2]); + + int16x8_t src16_01 = vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(src8))); + int16x8_t src16_23 = vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(src8))); + int16x4_t src16_0 = vget_low_s16(src16_01); + int16x4_t src16_1 = vget_high_s16(src16_01); + int16x4_t src16_2 = vget_low_s16(src16_23); + + accum0 += vmull_s16(src16_0, coeff16); + accum1 += vmull_s16(src16_1, coeff16); + accum2 += vmull_s16(src16_2, coeff16); + } + + accum0 = vshrq_n_s32(accum0, SkConvolutionFilter1D::kShiftBits); + accum1 = vshrq_n_s32(accum1, SkConvolutionFilter1D::kShiftBits); + accum2 = vshrq_n_s32(accum2, SkConvolutionFilter1D::kShiftBits); + + int16x8_t accum16_0 = vcombine_s16(vqmovn_s32(accum0), vqmovn_s32(accum1)); + int16x8_t accum16_1 = vcombine_s16(vqmovn_s32(accum2), vqmovn_s32(accum2)); + + uint8x16_t accum8 = + vcombine_u8(vqmovun_s16(accum16_0), vqmovun_s16(accum16_1)); + + if (hasAlpha) { + // Compute the max(ri, gi, bi) for each pixel. + // [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0 + uint8x16_t a = + vreinterpretq_u8_u32(vshrq_n_u32(vreinterpretq_u32_u8(accum8), 8)); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + uint8x16_t b = vmaxq_u8(a, accum8); // Max of r and g + // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0 + a = vreinterpretq_u8_u32(vshrq_n_u32(vreinterpretq_u32_u8(accum8), 16)); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + b = vmaxq_u8(a, b); // Max of r and g and b. + // [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00 + b = vreinterpretq_u8_u32(vshlq_n_u32(vreinterpretq_u32_u8(b), 24)); + // Make sure the value of alpha channel is always larger than maximum + // value of color channels. + accum8 = vmaxq_u8(b, accum8); + } else { + // Set value of alpha channels to 0xFF. + accum8 = vreinterpretq_u8_u32(vreinterpretq_u32_u8(accum8) | + vdupq_n_u32(0xFF000000)); + } + + switch (r) { + case 1: + vst1q_lane_u32(reinterpret_cast<uint32_t*>(outRow), + vreinterpretq_u32_u8(accum8), 0); + break; + case 2: + vst1_u32(reinterpret_cast<uint32_t*>(outRow), + vreinterpret_u32_u8(vget_low_u8(accum8))); + break; + case 3: + vst1_u32(reinterpret_cast<uint32_t*>(outRow), + vreinterpret_u32_u8(vget_low_u8(accum8))); + vst1q_lane_u32(reinterpret_cast<uint32_t*>(outRow + 8), + vreinterpretq_u32_u8(accum8), 2); + break; + } + } +} + +void convolve_vertically_neon( + const SkConvolutionFilter1D::ConvolutionFixed* filterValues, + int filterLength, unsigned char* const* sourceDataRows, int pixelWidth, + unsigned char* outRow, bool hasAlpha) { + if (hasAlpha) { + ConvolveVertically<true>(filterValues, filterLength, sourceDataRows, + pixelWidth, outRow); + } else { + ConvolveVertically<false>(filterValues, filterLength, sourceDataRows, + pixelWidth, outRow); + } +} + +} // namespace skia diff --git a/gfx/2d/ConvolutionFilterSSE2.cpp b/gfx/2d/ConvolutionFilterSSE2.cpp new file mode 100644 index 0000000000..c0aadb2245 --- /dev/null +++ b/gfx/2d/ConvolutionFilterSSE2.cpp @@ -0,0 +1,304 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2011-2016 Google Inc. +// Use of this source code is governed by a BSD-style license that can be +// found in the gfx/skia/LICENSE file. + +#include "SkConvolver.h" +#include "mozilla/Attributes.h" +#include <immintrin.h> + +namespace skia { + +static MOZ_ALWAYS_INLINE void AccumRemainder( + const unsigned char* pixelsLeft, + const SkConvolutionFilter1D::ConvolutionFixed* filterValues, __m128i& accum, + int r) { + int remainder[4] = {0}; + for (int i = 0; i < r; i++) { + SkConvolutionFilter1D::ConvolutionFixed coeff = filterValues[i]; + remainder[0] += coeff * pixelsLeft[i * 4 + 0]; + remainder[1] += coeff * pixelsLeft[i * 4 + 1]; + remainder[2] += coeff * pixelsLeft[i * 4 + 2]; + remainder[3] += coeff * pixelsLeft[i * 4 + 3]; + } + __m128i t = + _mm_setr_epi32(remainder[0], remainder[1], remainder[2], remainder[3]); + accum = _mm_add_epi32(accum, t); +} + +// Convolves horizontally along a single row. The row data is given in +// |srcData| and continues for the numValues() of the filter. +void convolve_horizontally_sse2(const unsigned char* srcData, + const SkConvolutionFilter1D& filter, + unsigned char* outRow, bool /*hasAlpha*/) { + // Output one pixel each iteration, calculating all channels (RGBA) together. + int numValues = filter.numValues(); + for (int outX = 0; outX < numValues; outX++) { + // Get the filter that determines the current output pixel. + int filterOffset, filterLength; + const SkConvolutionFilter1D::ConvolutionFixed* filterValues = + filter.FilterForValue(outX, &filterOffset, &filterLength); + + // Compute the first pixel in this row that the filter affects. It will + // touch |filterLength| pixels (4 bytes each) after this. + const unsigned char* rowToFilter = &srcData[filterOffset * 4]; + + __m128i zero = _mm_setzero_si128(); + __m128i accum = _mm_setzero_si128(); + + // We will load and accumulate with four coefficients per iteration. + for (int filterX = 0; filterX < filterLength >> 2; filterX++) { + // Load 4 coefficients => duplicate 1st and 2nd of them for all channels. + __m128i coeff, coeff16; + // [16] xx xx xx xx c3 c2 c1 c0 + coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filterValues)); + // [16] xx xx xx xx c1 c1 c0 c0 + coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0)); + // [16] c1 c1 c1 c1 c0 c0 c0 c0 + coeff16 = _mm_unpacklo_epi16(coeff16, coeff16); + + // Load four pixels => unpack the first two pixels to 16 bits => + // multiply with coefficients => accumulate the convolution result. + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + __m128i src8 = + _mm_loadu_si128(reinterpret_cast<const __m128i*>(rowToFilter)); + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + __m128i src16 = _mm_unpacklo_epi8(src8, zero); + __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16); + __m128i mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a0*c0 b0*c0 g0*c0 r0*c0 + __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + // [32] a1*c1 b1*c1 g1*c1 r1*c1 + t = _mm_unpackhi_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + + // Duplicate 3rd and 4th coefficients for all channels => + // unpack the 3rd and 4th pixels to 16 bits => multiply with coefficients + // => accumulate the convolution results. + // [16] xx xx xx xx c3 c3 c2 c2 + coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2)); + // [16] c3 c3 c3 c3 c2 c2 c2 c2 + coeff16 = _mm_unpacklo_epi16(coeff16, coeff16); + // [16] a3 g3 b3 r3 a2 g2 b2 r2 + src16 = _mm_unpackhi_epi8(src8, zero); + mul_hi = _mm_mulhi_epi16(src16, coeff16); + mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a2*c2 b2*c2 g2*c2 r2*c2 + t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + // [32] a3*c3 b3*c3 g3*c3 r3*c3 + t = _mm_unpackhi_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + + // Advance the pixel and coefficients pointers. + rowToFilter += 16; + filterValues += 4; + } + + // When |filterLength| is not divisible by 4, we accumulate the last 1 - 3 + // coefficients one at a time. + int r = filterLength & 3; + if (r) { + int remainderOffset = (filterOffset + filterLength - r) * 4; + AccumRemainder(srcData + remainderOffset, filterValues, accum, r); + } + + // Shift right for fixed point implementation. + accum = _mm_srai_epi32(accum, SkConvolutionFilter1D::kShiftBits); + + // Packing 32 bits |accum| to 16 bits per channel (signed saturation). + accum = _mm_packs_epi32(accum, zero); + // Packing 16 bits |accum| to 8 bits per channel (unsigned saturation). + accum = _mm_packus_epi16(accum, zero); + + // Store the pixel value of 32 bits. + *(reinterpret_cast<int*>(outRow)) = _mm_cvtsi128_si32(accum); + outRow += 4; + } +} + +// Does vertical convolution to produce one output row. The filter values and +// length are given in the first two parameters. These are applied to each +// of the rows pointed to in the |sourceDataRows| array, with each row +// being |pixelWidth| wide. +// +// The output must have room for |pixelWidth * 4| bytes. +template <bool hasAlpha> +static void ConvolveVertically( + const SkConvolutionFilter1D::ConvolutionFixed* filterValues, + int filterLength, unsigned char* const* sourceDataRows, int pixelWidth, + unsigned char* outRow) { + // Output four pixels per iteration (16 bytes). + int width = pixelWidth & ~3; + __m128i zero = _mm_setzero_si128(); + for (int outX = 0; outX < width; outX += 4) { + // Accumulated result for each pixel. 32 bits per RGBA channel. + __m128i accum0 = _mm_setzero_si128(); + __m128i accum1 = _mm_setzero_si128(); + __m128i accum2 = _mm_setzero_si128(); + __m128i accum3 = _mm_setzero_si128(); + + // Convolve with one filter coefficient per iteration. + for (int filterY = 0; filterY < filterLength; filterY++) { + // Duplicate the filter coefficient 8 times. + // [16] cj cj cj cj cj cj cj cj + __m128i coeff16 = _mm_set1_epi16(filterValues[filterY]); + + // Load four pixels (16 bytes) together. + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + const __m128i* src = + reinterpret_cast<const __m128i*>(&sourceDataRows[filterY][outX << 2]); + __m128i src8 = _mm_loadu_si128(src); + + // Unpack 1st and 2nd pixels from 8 bits to 16 bits for each channels => + // multiply with current coefficient => accumulate the result. + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + __m128i src16 = _mm_unpacklo_epi8(src8, zero); + __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16); + __m128i mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a0 b0 g0 r0 + __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum0 = _mm_add_epi32(accum0, t); + // [32] a1 b1 g1 r1 + t = _mm_unpackhi_epi16(mul_lo, mul_hi); + accum1 = _mm_add_epi32(accum1, t); + + // Unpack 3rd and 4th pixels from 8 bits to 16 bits for each channels => + // multiply with current coefficient => accumulate the result. + // [16] a3 b3 g3 r3 a2 b2 g2 r2 + src16 = _mm_unpackhi_epi8(src8, zero); + mul_hi = _mm_mulhi_epi16(src16, coeff16); + mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a2 b2 g2 r2 + t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum2 = _mm_add_epi32(accum2, t); + // [32] a3 b3 g3 r3 + t = _mm_unpackhi_epi16(mul_lo, mul_hi); + accum3 = _mm_add_epi32(accum3, t); + } + + // Shift right for fixed point implementation. + accum0 = _mm_srai_epi32(accum0, SkConvolutionFilter1D::kShiftBits); + accum1 = _mm_srai_epi32(accum1, SkConvolutionFilter1D::kShiftBits); + accum2 = _mm_srai_epi32(accum2, SkConvolutionFilter1D::kShiftBits); + accum3 = _mm_srai_epi32(accum3, SkConvolutionFilter1D::kShiftBits); + + // Packing 32 bits |accum| to 16 bits per channel (signed saturation). + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + accum0 = _mm_packs_epi32(accum0, accum1); + // [16] a3 b3 g3 r3 a2 b2 g2 r2 + accum2 = _mm_packs_epi32(accum2, accum3); + + // Packing 16 bits |accum| to 8 bits per channel (unsigned saturation). + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + accum0 = _mm_packus_epi16(accum0, accum2); + + if (hasAlpha) { + // Compute the max(ri, gi, bi) for each pixel. + // [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0 + __m128i a = _mm_srli_epi32(accum0, 8); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + __m128i b = _mm_max_epu8(a, accum0); // Max of r and g. + // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0 + a = _mm_srli_epi32(accum0, 16); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + b = _mm_max_epu8(a, b); // Max of r and g and b. + // [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00 + b = _mm_slli_epi32(b, 24); + + // Make sure the value of alpha channel is always larger than maximum + // value of color channels. + accum0 = _mm_max_epu8(b, accum0); + } else { + // Set value of alpha channels to 0xFF. + __m128i mask = _mm_set1_epi32(0xff000000); + accum0 = _mm_or_si128(accum0, mask); + } + + // Store the convolution result (16 bytes) and advance the pixel pointers. + _mm_storeu_si128(reinterpret_cast<__m128i*>(outRow), accum0); + outRow += 16; + } + + // When the width of the output is not divisible by 4, We need to save one + // pixel (4 bytes) each time. And also the fourth pixel is always absent. + int r = pixelWidth & 3; + if (r) { + __m128i accum0 = _mm_setzero_si128(); + __m128i accum1 = _mm_setzero_si128(); + __m128i accum2 = _mm_setzero_si128(); + for (int filterY = 0; filterY < filterLength; ++filterY) { + __m128i coeff16 = _mm_set1_epi16(filterValues[filterY]); + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + const __m128i* src = reinterpret_cast<const __m128i*>( + &sourceDataRows[filterY][width << 2]); + __m128i src8 = _mm_loadu_si128(src); + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + __m128i src16 = _mm_unpacklo_epi8(src8, zero); + __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16); + __m128i mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a0 b0 g0 r0 + __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum0 = _mm_add_epi32(accum0, t); + // [32] a1 b1 g1 r1 + t = _mm_unpackhi_epi16(mul_lo, mul_hi); + accum1 = _mm_add_epi32(accum1, t); + // [16] a3 b3 g3 r3 a2 b2 g2 r2 + src16 = _mm_unpackhi_epi8(src8, zero); + mul_hi = _mm_mulhi_epi16(src16, coeff16); + mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a2 b2 g2 r2 + t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum2 = _mm_add_epi32(accum2, t); + } + + accum0 = _mm_srai_epi32(accum0, SkConvolutionFilter1D::kShiftBits); + accum1 = _mm_srai_epi32(accum1, SkConvolutionFilter1D::kShiftBits); + accum2 = _mm_srai_epi32(accum2, SkConvolutionFilter1D::kShiftBits); + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + accum0 = _mm_packs_epi32(accum0, accum1); + // [16] a3 b3 g3 r3 a2 b2 g2 r2 + accum2 = _mm_packs_epi32(accum2, zero); + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + accum0 = _mm_packus_epi16(accum0, accum2); + if (hasAlpha) { + // [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0 + __m128i a = _mm_srli_epi32(accum0, 8); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + __m128i b = _mm_max_epu8(a, accum0); // Max of r and g. + // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0 + a = _mm_srli_epi32(accum0, 16); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + b = _mm_max_epu8(a, b); // Max of r and g and b. + // [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00 + b = _mm_slli_epi32(b, 24); + accum0 = _mm_max_epu8(b, accum0); + } else { + __m128i mask = _mm_set1_epi32(0xff000000); + accum0 = _mm_or_si128(accum0, mask); + } + + for (int i = 0; i < r; i++) { + *(reinterpret_cast<int*>(outRow)) = _mm_cvtsi128_si32(accum0); + accum0 = _mm_srli_si128(accum0, 4); + outRow += 4; + } + } +} + +void convolve_vertically_sse2( + const SkConvolutionFilter1D::ConvolutionFixed* filterValues, + int filterLength, unsigned char* const* sourceDataRows, int pixelWidth, + unsigned char* outRow, bool hasAlpha) { + if (hasAlpha) { + ConvolveVertically<true>(filterValues, filterLength, sourceDataRows, + pixelWidth, outRow); + } else { + ConvolveVertically<false>(filterValues, filterLength, sourceDataRows, + pixelWidth, outRow); + } +} + +} // namespace skia diff --git a/gfx/2d/Coord.h b/gfx/2d/Coord.h new file mode 100644 index 0000000000..3b81482b44 --- /dev/null +++ b/gfx/2d/Coord.h @@ -0,0 +1,202 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_COORD_H_ +#define MOZILLA_GFX_COORD_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/FloatingPoint.h" +#include "Types.h" +#include "BaseCoord.h" + +#include <cmath> +#include <type_traits> + +namespace mozilla { + +namespace gfx { + +template <class Units, class Rep = int32_t> +struct IntCoordTyped; +template <class Units, class F = Float> +struct CoordTyped; + +} // namespace gfx + +} // namespace mozilla + +namespace std { + +template <class Units, class Rep> +struct common_type<mozilla::gfx::IntCoordTyped<Units, Rep>, float> { + using type = mozilla::gfx::CoordTyped<Units, common_type_t<Rep, float>>; +}; + +template <class Units, class Rep> +struct common_type<mozilla::gfx::IntCoordTyped<Units, Rep>, double> { + using type = mozilla::gfx::CoordTyped<Units, common_type_t<Rep, double>>; +}; + +template <class Units, class Rep> +struct common_type<mozilla::gfx::IntCoordTyped<Units, Rep>, int32_t> { + using type = mozilla::gfx::IntCoordTyped<Units, common_type_t<Rep, int32_t>>; +}; + +template <class Units, class Rep> +struct common_type<mozilla::gfx::IntCoordTyped<Units, Rep>, uint32_t> { + using type = mozilla::gfx::IntCoordTyped<Units, common_type_t<Rep, uint32_t>>; +}; + +template <class Units, class F, class T> +struct common_type<mozilla::gfx::CoordTyped<Units, F>, T> { + using type = mozilla::gfx::CoordTyped<Units, common_type_t<F, T>>; +}; + +// With a few exceptions, we use CoordTyped values with a float representation. +// These are the types for which we have short typedefs like +// CSSCoord, and the types expected in most interfaces. +// So, for float inputs, keep the results as float even if the other +// operand is a double, accepting a slight loss of precision. +template <class Units, class T> +struct common_type<mozilla::gfx::CoordTyped<Units, float>, T> { + using type = mozilla::gfx::CoordTyped<Units, float>; +}; + +} // namespace std + +namespace mozilla { + +template <typename> +struct IsPixel; + +namespace gfx { + +// Should only be used to define generic typedefs like Coord, Point, etc. +struct UnknownUnits {}; + +// 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 Enable, 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; } + + friend auto operator+(Coord aA, Primitive aB) { return aA.value + aB; } + friend auto operator+(Primitive aA, Coord aB) { return aA + aB.value; } + friend auto operator-(Coord aA, Primitive aB) { return aA.value - aB; } + friend auto operator-(Primitive aA, Coord aB) { return aA - aB.value; } + friend auto operator*(Coord aCoord, Primitive aScale) { + return std::common_type_t<Coord, Primitive>(aCoord.value * aScale); + } + friend auto operator*(Primitive aScale, Coord aCoord) { + return aCoord * aScale; + } + friend auto operator/(Coord aCoord, Primitive aScale) { + return std::common_type_t<Coord, Primitive>(aCoord.value / aScale); + } + // 'scale / coord' is intentionally omitted because it doesn't make sense. +}; + +template <class Units, class Rep> +struct MOZ_EMPTY_BASES IntCoordTyped + : public BaseCoord<Rep, IntCoordTyped<Units, Rep>>, + public CoordOperatorsHelper<true, IntCoordTyped<Units, Rep>, float>, + public CoordOperatorsHelper<true, IntCoordTyped<Units, Rep>, double> { + static_assert(IsPixel<Units>::value, + "'Units' must be a coordinate system tag"); + + using Super = BaseCoord<Rep, IntCoordTyped<Units, Rep>>; + + constexpr IntCoordTyped() : Super() { + static_assert(sizeof(IntCoordTyped) == sizeof(Rep), + "Would be unfortunate otherwise!"); + } + template <class T, typename = typename std::enable_if_t< + std::is_integral_v<T> || std::is_enum_v<T>>> + constexpr MOZ_IMPLICIT IntCoordTyped(T aValue) : Super(aValue) { + static_assert(sizeof(IntCoordTyped) == sizeof(Rep), + "Would be unfortunate otherwise!"); + } +}; + +template <class Units, class F> +struct MOZ_EMPTY_BASES CoordTyped + : public BaseCoord<F, CoordTyped<Units, F>>, + public CoordOperatorsHelper<!std::is_same_v<F, int32_t>, + CoordTyped<Units, F>, int32_t>, + public CoordOperatorsHelper<!std::is_same_v<F, uint32_t>, + CoordTyped<Units, F>, uint32_t>, + public CoordOperatorsHelper<!std::is_same_v<F, double>, + CoordTyped<Units, F>, double>, + public CoordOperatorsHelper<!std::is_same_v<F, float>, + CoordTyped<Units, F>, float> { + static_assert(IsPixel<Units>::value, + "'Units' must be a coordinate system tag"); + + using Super = BaseCoord<F, CoordTyped<Units, F>>; + + constexpr CoordTyped() : Super() { + static_assert(sizeof(CoordTyped) == sizeof(F), + "Would be unfortunate otherwise!"); + } + constexpr MOZ_IMPLICIT CoordTyped(F aValue) : Super(aValue) { + static_assert(sizeof(CoordTyped) == sizeof(F), + "Would be unfortunate otherwise!"); + } + explicit constexpr CoordTyped(const IntCoordTyped<Units>& aCoord) + : Super(F(aCoord.value)) { + static_assert(sizeof(CoordTyped) == sizeof(F), + "Would be unfortunate otherwise!"); + } + + void Round() { this->value = floor(this->value + 0.5); } + void Truncate() { this->value = int32_t(this->value); } + + IntCoordTyped<Units> Rounded() const { + return IntCoordTyped<Units>(int32_t(floor(this->value + 0.5))); + } + IntCoordTyped<Units> Truncated() const { + return IntCoordTyped<Units>(int32_t(this->value)); + } +}; + +typedef CoordTyped<UnknownUnits> Coord; + +} // namespace gfx + +template <class Units, class F> +static MOZ_ALWAYS_INLINE bool FuzzyEqualsAdditive( + gfx::CoordTyped<Units, F> aValue1, gfx::CoordTyped<Units, F> aValue2, + gfx::CoordTyped<Units, F> aEpsilon = + detail::FuzzyEqualsEpsilon<F>::value()) { + return FuzzyEqualsAdditive(aValue1.value, aValue2.value, aEpsilon.value); +} + +template <class Units, class F> +static MOZ_ALWAYS_INLINE bool FuzzyEqualsMultiplicative( + gfx::CoordTyped<Units, F> aValue1, gfx::CoordTyped<Units, F> aValue2, + gfx::CoordTyped<Units, F> aEpsilon = + detail::FuzzyEqualsEpsilon<F>::value()) { + return FuzzyEqualsMultiplicative(aValue1.value, aValue2.value, + aEpsilon.value); +} + +} // namespace mozilla + +#endif /* MOZILLA_GFX_COORD_H_ */ diff --git a/gfx/2d/CriticalSection.h b/gfx/2d/CriticalSection.h new file mode 100644 index 0000000000..4ecb9d26e8 --- /dev/null +++ b/gfx/2d/CriticalSection.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_CRITICALSECTION_H_ +#define MOZILLA_GFX_CRITICALSECTION_H_ + +#ifdef WIN32 +# include <windows.h> +#else +# include <pthread.h> +# include "mozilla/DebugOnly.h" +#endif + +namespace mozilla { +namespace gfx { + +#ifdef WIN32 + +class CriticalSection { + public: + CriticalSection() { ::InitializeCriticalSection(&mCriticalSection); } + + ~CriticalSection() { ::DeleteCriticalSection(&mCriticalSection); } + + void Enter() { ::EnterCriticalSection(&mCriticalSection); } + + void Leave() { ::LeaveCriticalSection(&mCriticalSection); } + + protected: + CRITICAL_SECTION mCriticalSection; +}; + +#else +// posix + +class PosixCondvar; +class CriticalSection { + public: + CriticalSection() { + DebugOnly<int> err = pthread_mutex_init(&mMutex, nullptr); + MOZ_ASSERT(!err); + } + + ~CriticalSection() { + DebugOnly<int> err = pthread_mutex_destroy(&mMutex); + MOZ_ASSERT(!err); + } + + void Enter() { + DebugOnly<int> err = pthread_mutex_lock(&mMutex); + MOZ_ASSERT(!err); + } + + void Leave() { + DebugOnly<int> err = pthread_mutex_unlock(&mMutex); + MOZ_ASSERT(!err); + } + + protected: + pthread_mutex_t mMutex; + friend class PosixCondVar; +}; + +#endif + +/// RAII helper. +struct CriticalSectionAutoEnter final { + explicit CriticalSectionAutoEnter(CriticalSection* aSection) + : mSection(aSection) { + mSection->Enter(); + } + ~CriticalSectionAutoEnter() { mSection->Leave(); } + + protected: + CriticalSection* mSection; +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/DWriteSettings.cpp b/gfx/2d/DWriteSettings.cpp new file mode 100644 index 0000000000..acd26b4e5b --- /dev/null +++ b/gfx/2d/DWriteSettings.cpp @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "DWriteSettings.h" + +#include "mozilla/DataMutex.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/gfxVars.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +static std::atomic<Float> sClearTypeLevel{1.0f}; +static std::atomic<Float> sEnhancedContrast{1.0f}; +static std::atomic<Float> sGamma{2.2f}; +static Atomic<DWRITE_PIXEL_GEOMETRY> sPixelGeometry; +static Atomic<DWRITE_RENDERING_MODE> sRenderingMode; +static Atomic<DWRITE_MEASURING_MODE> sMeasuringMode; +static std::atomic<Float> sGDIGamma{1.4f}; +StaticDataMutex<StaticRefPtr<IDWriteRenderingParams>> sStandardRenderingParams( + "StandardRenderingParams"); +StaticDataMutex<StaticRefPtr<IDWriteRenderingParams>> sGDIRenderingParams( + "GDIRenderingParams"); + +static void ClearStandardRenderingParams() { + auto lockedParams = sStandardRenderingParams.Lock(); + lockedParams.ref() = nullptr; +} + +static void ClearGDIRenderingParams() { + auto lockedParams = sGDIRenderingParams.Lock(); + lockedParams.ref() = nullptr; +} + +static void UpdateClearTypeLevel() { + sClearTypeLevel = gfxVars::SystemTextClearTypeLevel(); +} +static void ClearTypeLevelVarUpdated() { + UpdateClearTypeLevel(); + ClearStandardRenderingParams(); + ClearGDIRenderingParams(); +} + +static void UpdateEnhancedContrast() { + sEnhancedContrast = gfxVars::SystemTextEnhancedContrast(); +} +static void EnhancedContrastVarUpdated() { + UpdateEnhancedContrast(); + ClearStandardRenderingParams(); +} + +static void UpdateGamma() { sGamma = gfxVars::SystemTextGamma(); } +static void GammaVarUpdated() { + UpdateGamma(); + ClearStandardRenderingParams(); +} + +static void UpdateGDIGamma() { sGDIGamma = gfxVars::SystemGDIGamma(); } +static void GDIGammaVarUpdated() { + UpdateGDIGamma(); + ClearGDIRenderingParams(); +} + +static void UpdatePixelGeometry() { + sPixelGeometry = + static_cast<DWRITE_PIXEL_GEOMETRY>(gfxVars::SystemTextPixelGeometry()); + Factory::SetBGRSubpixelOrder(sPixelGeometry == DWRITE_PIXEL_GEOMETRY_BGR); +} +static void PixelGeometryVarUpdated() { + UpdatePixelGeometry(); + ClearStandardRenderingParams(); + ClearGDIRenderingParams(); +} + +static void UpdateRenderingMode() { + sRenderingMode = + static_cast<DWRITE_RENDERING_MODE>(gfxVars::SystemTextRenderingMode()); + switch (sRenderingMode) { + case DWRITE_RENDERING_MODE_ALIASED: + case DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC: + sMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC; + break; + case DWRITE_RENDERING_MODE_CLEARTYPE_GDI_NATURAL: + sMeasuringMode = DWRITE_MEASURING_MODE_GDI_NATURAL; + break; + default: + sMeasuringMode = DWRITE_MEASURING_MODE_NATURAL; + break; + } +} +static void RenderingModeVarUpdated() { + UpdateRenderingMode(); + ClearStandardRenderingParams(); +} + +DWriteSettings::DWriteSettings(bool aUseGDISettings) + : mUseGDISettings(aUseGDISettings) {} + +/* static */ +void DWriteSettings::Initialize() { + UpdateClearTypeLevel(); + gfxVars::SetSystemTextClearTypeLevelListener(ClearTypeLevelVarUpdated); + + UpdateEnhancedContrast(); + gfxVars::SetSystemTextEnhancedContrastListener(EnhancedContrastVarUpdated); + + UpdateGamma(); + gfxVars::SetSystemTextGammaListener(GammaVarUpdated); + + UpdateGDIGamma(); + gfxVars::SetSystemGDIGammaListener(GDIGammaVarUpdated); + + UpdateRenderingMode(); + gfxVars::SetSystemTextRenderingModeListener(RenderingModeVarUpdated); + + UpdatePixelGeometry(); + gfxVars::SetSystemTextPixelGeometryListener(PixelGeometryVarUpdated); +} + +/* static */ +DWriteSettings& DWriteSettings::Get(bool aGDISettings) { + DWriteSettings* settings; + if (aGDISettings) { + static DWriteSettings* sGDISettings = + new DWriteSettings(/* aUseGDISettings */ true); + settings = sGDISettings; + } else { + static DWriteSettings* sStandardSettings = + new DWriteSettings(/* aUseGDISettings */ false); + settings = sStandardSettings; + } + return *settings; +} + +Float DWriteSettings::ClearTypeLevel() { return sClearTypeLevel; } + +Float DWriteSettings::EnhancedContrast() { + return mUseGDISettings ? 0.0f : sEnhancedContrast.load(); +} + +Float DWriteSettings::Gamma() { return mUseGDISettings ? sGDIGamma : sGamma; } + +DWRITE_PIXEL_GEOMETRY DWriteSettings::PixelGeometry() { return sPixelGeometry; } + +DWRITE_RENDERING_MODE DWriteSettings::RenderingMode() { + return mUseGDISettings ? DWRITE_RENDERING_MODE_GDI_CLASSIC : sRenderingMode; +} + +DWRITE_MEASURING_MODE DWriteSettings::MeasuringMode() { + return mUseGDISettings ? DWRITE_MEASURING_MODE_GDI_CLASSIC : sMeasuringMode; +} + +already_AddRefed<IDWriteRenderingParams> DWriteSettings::RenderingParams() { + auto lockedParams = mUseGDISettings ? sGDIRenderingParams.Lock() + : sStandardRenderingParams.Lock(); + if (!lockedParams.ref()) { + RefPtr<IDWriteRenderingParams> params; + HRESULT hr = Factory::GetDWriteFactory()->CreateCustomRenderingParams( + Gamma(), EnhancedContrast(), ClearTypeLevel(), PixelGeometry(), + RenderingMode(), getter_AddRefs(params)); + if (SUCCEEDED(hr)) { + lockedParams.ref() = params.forget(); + } else { + gfxWarning() << "Failed to create DWrite custom rendering params."; + } + } + + return do_AddRef(lockedParams.ref()); +} diff --git a/gfx/2d/DWriteSettings.h b/gfx/2d/DWriteSettings.h new file mode 100644 index 0000000000..39a46cfb6d --- /dev/null +++ b/gfx/2d/DWriteSettings.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_2D_DWRITESETTINGS_H_ +#define MOZILLA_GFX_2D_DWRITESETTINGS_H_ + +#include <dwrite.h> + +#include "mozilla/AlreadyAddRefed.h" +#include "Types.h" + +namespace mozilla { +namespace gfx { + +class DWriteSettings final { + public: + static void Initialize(); + + static DWriteSettings& Get(bool aGDISettings); + + Float ClearTypeLevel(); + Float EnhancedContrast(); + Float Gamma(); + DWRITE_PIXEL_GEOMETRY PixelGeometry(); + DWRITE_RENDERING_MODE RenderingMode(); + DWRITE_MEASURING_MODE MeasuringMode(); + already_AddRefed<IDWriteRenderingParams> RenderingParams(); + + private: + explicit DWriteSettings(bool aUseGDISettings); + + const bool mUseGDISettings; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // MOZILLA_GFX_2D_DWRITESETTINGS_H_ diff --git a/gfx/2d/DataSourceSurface.cpp b/gfx/2d/DataSourceSurface.cpp new file mode 100644 index 0000000000..cf892d2eab --- /dev/null +++ b/gfx/2d/DataSourceSurface.cpp @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "2D.h" +#include "DataSourceSurfaceWrapper.h" + +namespace mozilla { +namespace gfx { + +already_AddRefed<DataSourceSurface> DataSourceSurface::GetDataSurface() { + RefPtr<DataSourceSurface> surface = + IsDataSourceSurface() ? this : new DataSourceSurfaceWrapper(this); + return surface.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/DataSourceSurfaceWrapper.h b/gfx/2d/DataSourceSurfaceWrapper.h new file mode 100644 index 0000000000..b5d3466147 --- /dev/null +++ b/gfx/2d/DataSourceSurfaceWrapper.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_DATASOURCESURFACEWRAPPER_H_ +#define MOZILLA_GFX_DATASOURCESURFACEWRAPPER_H_ + +#include "2D.h" + +namespace mozilla { +namespace gfx { + +// Wraps a DataSourceSurface and forwards all methods except for GetType(), +// from which it always returns SurfaceType::DATA. +class DataSourceSurfaceWrapper final : public DataSourceSurface { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceWrapper, override) + explicit DataSourceSurfaceWrapper(DataSourceSurface* aSurface) + : mSurface(aSurface) {} + + bool Equals(SourceSurface* aOther, bool aSymmetric = true) override { + return DataSourceSurface::Equals(aOther, aSymmetric) || + mSurface->Equals(aOther, aSymmetric); + } + + SurfaceType GetType() const override { return SurfaceType::DATA; } + + uint8_t* GetData() override { return mSurface->GetData(); } + int32_t Stride() override { return mSurface->Stride(); } + IntSize GetSize() const override { return mSurface->GetSize(); } + SurfaceFormat GetFormat() const override { return mSurface->GetFormat(); } + bool IsValid() const override { return mSurface->IsValid(); } + + bool Map(MapType aType, MappedSurface* aMappedSurface) override { + return mSurface->Map(aType, aMappedSurface); + } + + void Unmap() override { mSurface->Unmap(); } + + private: + RefPtr<DataSourceSurface> mSurface; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_DATASOURCESURFACEWRAPPER_H_ */ diff --git a/gfx/2d/DataSurfaceHelpers.cpp b/gfx/2d/DataSurfaceHelpers.cpp new file mode 100644 index 0000000000..0c3863b850 --- /dev/null +++ b/gfx/2d/DataSurfaceHelpers.cpp @@ -0,0 +1,343 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <cstring> + +#include "2D.h" +#include "DataSurfaceHelpers.h" +#include "Logging.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/PodOperations.h" +#include "Swizzle.h" +#include "Tools.h" + +namespace mozilla { +namespace gfx { + +int32_t StrideForFormatAndWidth(SurfaceFormat aFormat, int32_t aWidth) { + MOZ_ASSERT(aFormat <= SurfaceFormat::UNKNOWN); + MOZ_ASSERT(aWidth > 0); + + // There's nothing special about this alignment, other than that it's what + // cairo_format_stride_for_width uses. + static const int32_t alignment = sizeof(int32_t); + + const int32_t bpp = BytesPerPixel(aFormat); + + if (aWidth >= (INT32_MAX - alignment) / bpp) { + return -1; // too big + } + + return (bpp * aWidth + alignment - 1) & ~(alignment - 1); +} + +already_AddRefed<DataSourceSurface> CreateDataSourceSurfaceFromData( + const IntSize& aSize, SurfaceFormat aFormat, const uint8_t* aData, + int32_t aDataStride) { + RefPtr<DataSourceSurface> srcSurface = + Factory::CreateWrappingDataSourceSurface(const_cast<uint8_t*>(aData), + aDataStride, aSize, aFormat); + RefPtr<DataSourceSurface> destSurface = + Factory::CreateDataSourceSurface(aSize, aFormat, false); + + if (!srcSurface || !destSurface) { + return nullptr; + } + + if (CopyRect(srcSurface, destSurface, + IntRect(IntPoint(), srcSurface->GetSize()), IntPoint())) { + return destSurface.forget(); + } + + return nullptr; +} + +already_AddRefed<DataSourceSurface> CreateDataSourceSurfaceWithStrideFromData( + const IntSize& aSize, SurfaceFormat aFormat, int32_t aStride, + const uint8_t* aData, int32_t aDataStride) { + RefPtr<DataSourceSurface> srcSurface = + Factory::CreateWrappingDataSourceSurface(const_cast<uint8_t*>(aData), + aDataStride, aSize, aFormat); + RefPtr<DataSourceSurface> destSurface = + Factory::CreateDataSourceSurfaceWithStride(aSize, aFormat, aStride, + false); + + if (!srcSurface || !destSurface) { + return nullptr; + } + + if (CopyRect(srcSurface, destSurface, + IntRect(IntPoint(), srcSurface->GetSize()), IntPoint())) { + return destSurface.forget(); + } + + return nullptr; +} + +uint8_t* DataAtOffset(DataSourceSurface* aSurface, + const DataSourceSurface::MappedSurface* aMap, + IntPoint aPoint) { + if (!SurfaceContainsPoint(aSurface, aPoint)) { + MOZ_CRASH("GFX: sample position needs to be inside surface!"); + } + + MOZ_ASSERT(Factory::CheckSurfaceSize(aSurface->GetSize()), + "surface size overflows - this should have been prevented when " + "the surface was created"); + + uint8_t* data = + aMap->mData + size_t(aPoint.y) * size_t(aMap->mStride) + + size_t(aPoint.x) * size_t(BytesPerPixel(aSurface->GetFormat())); + + if (data < aMap->mData) { + MOZ_CRASH("GFX: out-of-range data access"); + } + + return data; +} + +// This check is safe against integer overflow. +bool SurfaceContainsPoint(SourceSurface* aSurface, const IntPoint& aPoint) { + IntSize size = aSurface->GetSize(); + return aPoint.x >= 0 && aPoint.x < size.width && aPoint.y >= 0 && + aPoint.y < size.height; +} + +void CopySurfaceDataToPackedArray(uint8_t* aSrc, uint8_t* aDst, + IntSize aSrcSize, int32_t aSrcStride, + int32_t aBytesPerPixel) { + CheckedInt<size_t> packedStride(aBytesPerPixel); + packedStride *= aSrcSize.width; + if (!packedStride.isValid()) { + MOZ_ASSERT(false, "Invalid stride"); + return; + } + + CheckedInt<size_t> totalSize(aSrcStride); + totalSize *= aSrcSize.height; + if (!totalSize.isValid()) { + MOZ_ASSERT(false, "Invalid surface size"); + return; + } + + if (size_t(aSrcStride) == packedStride.value()) { + // aSrc is already packed, so we can copy with a single memcpy. + memcpy(aDst, aSrc, totalSize.value()); + } else { + // memcpy one row at a time. + for (int row = 0; row < aSrcSize.height; ++row) { + memcpy(aDst, aSrc, packedStride.value()); + aSrc += aSrcStride; + aDst += packedStride.value(); + } + } +} + +UniquePtr<uint8_t[]> SurfaceToPackedBGRA(DataSourceSurface* aSurface) { + SurfaceFormat format = aSurface->GetFormat(); + if (format != SurfaceFormat::B8G8R8A8 && format != SurfaceFormat::B8G8R8X8) { + return nullptr; + } + + IntSize size = aSurface->GetSize(); + if (size.width < 0 || size.width >= INT32_MAX / 4) { + return nullptr; + } + int32_t stride = size.width * 4; + CheckedInt<size_t> bufferSize = + CheckedInt<size_t>(stride) * CheckedInt<size_t>(size.height); + if (!bufferSize.isValid()) { + return nullptr; + } + UniquePtr<uint8_t[]> imageBuffer(new (std::nothrow) + uint8_t[bufferSize.value()]); + if (!imageBuffer) { + return nullptr; + } + + DataSourceSurface::MappedSurface map; + if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) { + return nullptr; + } + + CopySurfaceDataToPackedArray(map.mData, imageBuffer.get(), size, map.mStride, + 4); + + aSurface->Unmap(); + + if (format == SurfaceFormat::B8G8R8X8) { + // Convert BGRX to BGRA by setting a to 255. + SwizzleData(imageBuffer.get(), stride, SurfaceFormat::X8R8G8B8_UINT32, + imageBuffer.get(), stride, SurfaceFormat::A8R8G8B8_UINT32, + size); + } + + return imageBuffer; +} + +uint8_t* SurfaceToPackedBGR(DataSourceSurface* aSurface) { + SurfaceFormat format = aSurface->GetFormat(); + MOZ_ASSERT(format == SurfaceFormat::B8G8R8X8, "Format not supported"); + + if (format != SurfaceFormat::B8G8R8X8) { + // To support B8G8R8A8 we'd need to un-pre-multiply alpha + return nullptr; + } + + IntSize size = aSurface->GetSize(); + if (size.width < 0 || size.width >= INT32_MAX / 3) { + return nullptr; + } + int32_t stride = size.width * 3; + CheckedInt<size_t> bufferSize = + CheckedInt<size_t>(stride) * CheckedInt<size_t>(size.height); + if (!bufferSize.isValid()) { + return nullptr; + } + uint8_t* imageBuffer = new (std::nothrow) uint8_t[bufferSize.value()]; + if (!imageBuffer) { + return nullptr; + } + + DataSourceSurface::MappedSurface map; + if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) { + delete[] imageBuffer; + return nullptr; + } + + SwizzleData(map.mData, map.mStride, SurfaceFormat::B8G8R8X8, imageBuffer, + stride, SurfaceFormat::B8G8R8, size); + + aSurface->Unmap(); + + return imageBuffer; +} + +void ClearDataSourceSurface(DataSourceSurface* aSurface) { + DataSourceSurface::MappedSurface map; + if (!aSurface->Map(DataSourceSurface::MapType::WRITE, &map)) { + MOZ_ASSERT(false, "Failed to map DataSourceSurface"); + return; + } + + // We avoid writing into the gaps between the rows here since we can't be + // sure that some drivers don't use those bytes. + + uint32_t width = aSurface->GetSize().width; + uint32_t bytesPerRow = width * BytesPerPixel(aSurface->GetFormat()); + uint8_t* row = map.mData; + // converting to size_t here because otherwise the temporaries can overflow + // and we can end up with |end| being a bad address! + uint8_t* end = row + size_t(map.mStride) * size_t(aSurface->GetSize().height); + + while (row != end) { + memset(row, 0, bytesPerRow); + row += map.mStride; + } + + aSurface->Unmap(); +} + +size_t BufferSizeFromStrideAndHeight(int32_t aStride, int32_t aHeight, + int32_t aExtraBytes) { + if (MOZ_UNLIKELY(aHeight <= 0) || MOZ_UNLIKELY(aStride <= 0)) { + return 0; + } + + // We limit the length returned to values that can be represented by int32_t + // because we don't want to allocate buffers any bigger than that. This + // allows for a buffer size of over 2 GiB which is already rediculously + // large and will make the process janky. (Note the choice of the signed type + // is deliberate because we specifically don't want the returned value to + // overflow if someone stores the buffer length in an int32_t variable.) + + CheckedInt32 requiredBytes = + CheckedInt32(aStride) * CheckedInt32(aHeight) + CheckedInt32(aExtraBytes); + if (MOZ_UNLIKELY(!requiredBytes.isValid())) { + gfxWarning() << "Buffer size too big; returning zero " << aStride << ", " + << aHeight << ", " << aExtraBytes; + return 0; + } + return requiredBytes.value(); +} + +size_t BufferSizeFromDimensions(int32_t aWidth, int32_t aHeight, int32_t aDepth, + int32_t aExtraBytes) { + if (MOZ_UNLIKELY(aHeight <= 0) || MOZ_UNLIKELY(aWidth <= 0) || + MOZ_UNLIKELY(aDepth <= 0)) { + return 0; + } + + // Similar to BufferSizeFromStrideAndHeight, but with an extra parameter. + + CheckedInt32 requiredBytes = + CheckedInt32(aWidth) * CheckedInt32(aHeight) * CheckedInt32(aDepth) + + CheckedInt32(aExtraBytes); + if (MOZ_UNLIKELY(!requiredBytes.isValid())) { + gfxWarning() << "Buffer size too big; returning zero " << aWidth << ", " + << aHeight << ", " << aDepth << ", " << aExtraBytes; + return 0; + } + return requiredBytes.value(); +} + +/** + * aSrcRect: Rect relative to the aSrc surface + * aDestPoint: Point inside aDest surface + * + * aSrcRect and aDestPoint are in internal local coordinates. + * i.e. locations of pixels and not in the same coordinate space + * as aSrc->GetRect() + */ +bool CopyRect(DataSourceSurface* aSrc, DataSourceSurface* aDest, + IntRect aSrcRect, IntPoint aDestPoint) { + if (aSrcRect.Overflows() || + IntRect(aDestPoint, aSrcRect.Size()).Overflows()) { + MOZ_CRASH("GFX: we should never be getting invalid rects at this point"); + } + + MOZ_RELEASE_ASSERT(aSrc->GetFormat() == aDest->GetFormat(), + "GFX: different surface formats"); + MOZ_RELEASE_ASSERT(IntRect(IntPoint(), aSrc->GetSize()).Contains(aSrcRect), + "GFX: source rect too big for source surface"); + MOZ_RELEASE_ASSERT(IntRect(IntPoint(), aDest->GetSize()) + .Contains(IntRect(aDestPoint, aSrcRect.Size())), + "GFX: dest surface too small"); + + if (aSrcRect.IsEmpty()) { + return false; + } + + DataSourceSurface::ScopedMap srcMap(aSrc, DataSourceSurface::READ); + DataSourceSurface::ScopedMap destMap(aDest, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!srcMap.IsMapped() || !destMap.IsMapped())) { + return false; + } + + uint8_t* sourceData = + DataAtOffset(aSrc, srcMap.GetMappedSurface(), aSrcRect.TopLeft()); + uint8_t* destData = + DataAtOffset(aDest, destMap.GetMappedSurface(), aDestPoint); + + SwizzleData(sourceData, srcMap.GetStride(), aSrc->GetFormat(), destData, + destMap.GetStride(), aDest->GetFormat(), aSrcRect.Size()); + + return true; +} + +already_AddRefed<DataSourceSurface> CreateDataSourceSurfaceByCloning( + DataSourceSurface* aSource) { + RefPtr<DataSourceSurface> copy = Factory::CreateDataSourceSurface( + aSource->GetSize(), aSource->GetFormat(), true); + if (copy) { + CopyRect(aSource, copy, IntRect(IntPoint(), aSource->GetSize()), + IntPoint()); + } + return copy.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/DataSurfaceHelpers.h b/gfx/2d/DataSurfaceHelpers.h new file mode 100644 index 0000000000..34bf36e8c8 --- /dev/null +++ b/gfx/2d/DataSurfaceHelpers.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_DATASURFACEHELPERS_H +#define _MOZILLA_GFX_DATASURFACEHELPERS_H + +#include "2D.h" + +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace gfx { + +int32_t StrideForFormatAndWidth(SurfaceFormat aFormat, int32_t aWidth); + +/** + * Create a DataSourceSurface and init the surface with the |aData|. The stride + * of this source surface might be different from the input data's + * |aDataStride|. System will try to use the optimal one. + */ +already_AddRefed<DataSourceSurface> CreateDataSourceSurfaceFromData( + const IntSize& aSize, SurfaceFormat aFormat, const uint8_t* aData, + int32_t aDataStride); + +/** + * Similar to CreateDataSourceSurfaceFromData(), but could setup the stride for + * this surface. + */ +already_AddRefed<DataSourceSurface> CreateDataSourceSurfaceWithStrideFromData( + const IntSize& aSize, SurfaceFormat aFormat, int32_t aStride, + const uint8_t* aData, int32_t aDataStride); + +/** + * Copy the pixel data from aSrc and pack it into aDst. aSrcSize, aSrcStride + * and aBytesPerPixel give the size, stride and bytes per pixel for aSrc's + * surface. Callers are responsible for making sure that aDst is big enough to + * contain |aSrcSize.width * aSrcSize.height * aBytesPerPixel| bytes. + */ +void CopySurfaceDataToPackedArray(uint8_t* aSrc, uint8_t* aDst, + IntSize aSrcSize, int32_t aSrcStride, + int32_t aBytesPerPixel); + +/** + * Convert aSurface to a packed buffer in BGRA format. + */ +UniquePtr<uint8_t[]> SurfaceToPackedBGRA(DataSourceSurface* aSurface); + +/** + * Convert aSurface to a packed buffer in BGR format. The pixel data is + * returned in a buffer allocated with new uint8_t[]. The caller then has + * ownership of the buffer and is responsible for delete[]'ing it. + * + * This function is currently only intended for use with surfaces of format + * SurfaceFormat::B8G8R8X8 since the X components of the pixel data (if any) + * are simply dropped (no attempt is made to un-pre-multiply alpha from the + * color components). + */ +uint8_t* SurfaceToPackedBGR(DataSourceSurface* aSurface); + +/** + * Clears all the bytes in a DataSourceSurface's data array to zero (so to + * transparent black for SurfaceFormat::B8G8R8A8, for example). + * Note that DataSourceSurfaces can be initialized to zero, which is + * more efficient than zeroing the surface after initialization. + */ +void ClearDataSourceSurface(DataSourceSurface* aSurface); + +/** + * Multiplies aStride and aHeight and makes sure the result is limited to + * something sane. To keep things consistent, this should always be used + * wherever we allocate a buffer based on surface stride and height. + * + * @param aExtra Optional argument to specify an additional number of trailing + * bytes (useful for creating intermediate surfaces for filters, for + * example). + * + * @return The result of the multiplication if it is acceptable, or else zero. + */ +size_t BufferSizeFromStrideAndHeight(int32_t aStride, int32_t aHeight, + int32_t aExtraBytes = 0); + +/** + * Multiplies aWidth, aHeight, aDepth and makes sure the result is limited to + * something sane. To keep things consistent, this should always be used + * wherever we allocate a buffer based on surface dimensions. + * + * @param aExtra Optional argument to specify an additional number of trailing + * bytes (useful for creating intermediate surfaces for filters, for + * example). + * + * @return The result of the multiplication if it is acceptable, or else zero. + */ +size_t BufferSizeFromDimensions(int32_t aWidth, int32_t aHeight, int32_t aDepth, + int32_t aExtraBytes = 0); +/** + * Copy aSrcRect from aSrc to aDest starting at aDestPoint. + * @returns false if the copy is not successful or the aSrc's size is empty. + */ +bool CopyRect(DataSourceSurface* aSrc, DataSourceSurface* aDest, + IntRect aSrcRect, IntPoint aDestPoint); + +/** + * Create a non aliasing copy of aSource. This creates a new DataSourceSurface + * using the factory and copies the bits. + * + * @return a dss allocated by Factory that contains a copy a aSource. + */ +already_AddRefed<DataSourceSurface> CreateDataSourceSurfaceByCloning( + DataSourceSurface* aSource); + +/** + * Return the byte at aPoint. + */ +uint8_t* DataAtOffset(DataSourceSurface* aSurface, + const DataSourceSurface::MappedSurface* aMap, + IntPoint aPoint); + +/** + * Check if aPoint is contained by the surface. + * + * @returns true if and only if aPoint is inside the surface. + */ +bool SurfaceContainsPoint(SourceSurface* aSurface, const IntPoint& aPoint); + +} // namespace gfx +} // namespace mozilla + +#endif // _MOZILLA_GFX_DATASURFACEHELPERS_H diff --git a/gfx/2d/DrawEventRecorder.cpp b/gfx/2d/DrawEventRecorder.cpp new file mode 100644 index 0000000000..967c02f8a0 --- /dev/null +++ b/gfx/2d/DrawEventRecorder.cpp @@ -0,0 +1,190 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DrawEventRecorder.h" + +#include "mozilla/UniquePtrExtensions.h" +#include "PathRecording.h" +#include "RecordingTypes.h" +#include "RecordedEventImpl.h" + +namespace mozilla { +namespace gfx { + +DrawEventRecorderPrivate::DrawEventRecorderPrivate() : mExternalFonts(false) {} + +DrawEventRecorderPrivate::~DrawEventRecorderPrivate() { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); +} + +void DrawEventRecorderPrivate::SetDrawTarget(ReferencePtr aDT) { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); + + RecordEvent(RecordedSetCurrentDrawTarget(aDT)); + mCurrentDT = aDT; +} + +void DrawEventRecorderPrivate::StoreExternalSurfaceRecording( + SourceSurface* aSurface, uint64_t aKey) { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); + + RecordEvent(RecordedExternalSurfaceCreation(aSurface, aKey)); + mExternalSurfaces.push_back({aSurface}); +} + +void DrawEventRecorderPrivate::StoreSourceSurfaceRecording( + SourceSurface* aSurface, const char* aReason) { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); + + RefPtr<DataSourceSurface> dataSurf = aSurface->GetDataSurface(); + IntSize surfaceSize = aSurface->GetSize(); + Maybe<DataSourceSurface::ScopedMap> map; + if (dataSurf) { + map.emplace(dataSurf, DataSourceSurface::READ); + } + if (!dataSurf || !map->IsMapped() || + !Factory::AllowedSurfaceSize(surfaceSize)) { + gfxWarning() << "Recording failed to record SourceSurface for " << aReason; + + // If surface size is not allowed, replace with reasonable size. + if (!Factory::AllowedSurfaceSize(surfaceSize)) { + surfaceSize.width = std::min(surfaceSize.width, kReasonableSurfaceSize); + surfaceSize.height = std::min(surfaceSize.height, kReasonableSurfaceSize); + } + + // Insert a dummy source surface. + int32_t stride = surfaceSize.width * BytesPerPixel(aSurface->GetFormat()); + UniquePtr<uint8_t[]> sourceData = + MakeUniqueFallible<uint8_t[]>(stride * surfaceSize.height); + if (!sourceData) { + // If the surface is too big just create a 1 x 1 dummy. + surfaceSize.width = 1; + surfaceSize.height = 1; + stride = surfaceSize.width * BytesPerPixel(aSurface->GetFormat()); + sourceData = MakeUnique<uint8_t[]>(stride * surfaceSize.height); + } + + RecordEvent(RecordedSourceSurfaceCreation(aSurface, sourceData.get(), + stride, surfaceSize, + aSurface->GetFormat())); + return; + } + + RecordEvent(RecordedSourceSurfaceCreation( + aSurface, map->GetData(), map->GetStride(), dataSurf->GetSize(), + dataSurf->GetFormat())); +} + +void DrawEventRecorderPrivate::RecordSourceSurfaceDestruction(void* aSurface) { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); + + RemoveSourceSurface(static_cast<SourceSurface*>(aSurface)); + RemoveStoredObject(aSurface); + RecordEvent(RecordedSourceSurfaceDestruction(ReferencePtr(aSurface))); +} + +void DrawEventRecorderPrivate::DecrementUnscaledFontRefCount( + const ReferencePtr aUnscaledFont) { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); + + auto element = mUnscaledFontRefs.Lookup(aUnscaledFont); + MOZ_DIAGNOSTIC_ASSERT(element, + "DecrementUnscaledFontRefCount calls should balance " + "with IncrementUnscaledFontRefCount calls"); + if (--element.Data() <= 0) { + RecordEvent(RecordedUnscaledFontDestruction(aUnscaledFont)); + element.Remove(); + } +} + +void DrawEventRecorderMemory::RecordEvent(const RecordedEvent& aEvent) { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderMemory); + aEvent.RecordToStream(mOutputStream); +} + +void DrawEventRecorderMemory::AddDependentSurface(uint64_t aDependencyId) { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderMemory); + mDependentSurfaces.Insert(aDependencyId); +} + +nsTHashSet<uint64_t>&& DrawEventRecorderMemory::TakeDependentSurfaces() { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderMemory); + return std::move(mDependentSurfaces); +} + +DrawEventRecorderMemory::DrawEventRecorderMemory() { + WriteHeader(mOutputStream); +} + +DrawEventRecorderMemory::DrawEventRecorderMemory( + const SerializeResourcesFn& aFn) + : mSerializeCallback(aFn) { + mExternalFonts = !!mSerializeCallback; + WriteHeader(mOutputStream); +} + +void DrawEventRecorderMemory::Flush() { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderMemory); +} + +void DrawEventRecorderMemory::FlushItem(IntRect aRect) { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderMemory); + + MOZ_RELEASE_ASSERT(!aRect.IsEmpty()); + // Detaching our existing resources will add some + // destruction events to our stream so we need to do that + // first. + DetachResources(); + + // See moz2d_renderer.rs for a description of the stream format + WriteElement(mIndex, mOutputStream.mLength); + + // write out the fonts into the extra data section + mSerializeCallback(mOutputStream, mScaledFonts); + WriteElement(mIndex, mOutputStream.mLength); + + WriteElement(mIndex, aRect.x); + WriteElement(mIndex, aRect.y); + WriteElement(mIndex, aRect.XMost()); + WriteElement(mIndex, aRect.YMost()); + ClearResources(); + + // write out a new header for the next recording in the stream + WriteHeader(mOutputStream); +} + +bool DrawEventRecorderMemory::Finish() { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderMemory); + + // this length might be 0, and things should still work. + // for example if there are no items in a particular area + size_t indexOffset = mOutputStream.mLength; + // write out the index + mOutputStream.write(mIndex.mData, mIndex.mLength); + bool hasItems = mIndex.mLength != 0; + mIndex.reset(); + // write out the offset of the Index to the end of the output stream + WriteElement(mOutputStream, indexOffset); + ClearResources(); + return hasItems; +} + +size_t DrawEventRecorderMemory::RecordingSize() { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderMemory); + return mOutputStream.mLength; +} + +void DrawEventRecorderMemory::WipeRecording() { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderMemory); + + mOutputStream.reset(); + mIndex.reset(); + + WriteHeader(mOutputStream); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/DrawEventRecorder.h b/gfx/2d/DrawEventRecorder.h new file mode 100644 index 0000000000..13380b014a --- /dev/null +++ b/gfx/2d/DrawEventRecorder.h @@ -0,0 +1,319 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_DRAWEVENTRECORDER_H_ +#define MOZILLA_GFX_DRAWEVENTRECORDER_H_ + +#include "2D.h" +#include "RecordedEvent.h" +#include "RecordingTypes.h" + +#include <deque> +#include <functional> +#include <vector> + +#include "mozilla/DataMutex.h" +#include "mozilla/ThreadSafeWeakPtr.h" +#include "nsTHashMap.h" +#include "nsTHashSet.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace gfx { + +class DrawTargetRecording; +class PathRecording; + +class DrawEventRecorderPrivate : public DrawEventRecorder { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderPrivate, override) + + DrawEventRecorderPrivate(); + virtual ~DrawEventRecorderPrivate(); + RecorderType GetRecorderType() const override { + return RecorderType::PRIVATE; + } + bool Finish() override { + ClearResources(); + return true; + } + virtual void FlushItem(IntRect) {} + void DetachResources() { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); + + nsTHashSet<ScaledFont*> fonts = std::move(mStoredFonts); + for (const auto& font : fonts) { + font->RemoveUserData(reinterpret_cast<UserDataKey*>(this)); + } + + // SourceSurfaces can be deleted off the main thread, so we use + // ThreadSafeWeakPtrs to allow for this. RemoveUserData is thread safe. + nsTHashMap<void*, ThreadSafeWeakPtr<SourceSurface>> surfaces = + std::move(mStoredSurfaces); + for (const auto& entry : surfaces) { + RefPtr<SourceSurface> strongRef(entry.GetData()); + if (strongRef) { + strongRef->RemoveUserData(reinterpret_cast<UserDataKey*>(this)); + } + } + + // Now that we've detached we can't get any more pending deletions, so + // processing now should mean we include all clean up operations. + ProcessPendingDeletions(); + } + + void ClearResources() { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); + mStoredObjects.Clear(); + mStoredFontData.Clear(); + mScaledFonts.clear(); + mCurrentDT = nullptr; + } + + template <class S> + void WriteHeader(S& aStream) { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); + WriteElement(aStream, kMagicInt); + WriteElement(aStream, kMajorRevision); + WriteElement(aStream, kMinorRevision); + } + + virtual void RecordEvent(const RecordedEvent& aEvent) = 0; + + void RecordEvent(DrawTargetRecording* aDT, const RecordedEvent& aEvent) { + ReferencePtr dt = aDT; + if (mCurrentDT != dt) { + SetDrawTarget(dt); + } + RecordEvent(aEvent); + } + + void SetDrawTarget(ReferencePtr aDT); + + void ClearDrawTarget(DrawTargetRecording* aDT) { + ReferencePtr dt = aDT; + if (mCurrentDT == dt) { + mCurrentDT = nullptr; + } + } + + void AddStoredObject(const ReferencePtr aObject) { + ProcessPendingDeletions(); + mStoredObjects.Insert(aObject); + } + + /** + * This is a combination of HasStoredObject and AddStoredObject, so that we + * only have to call ProcessPendingDeletions once, which involves locking. + * @param aObject the object to store if not already stored + * @return true if the object was not already stored, false if it was + */ + bool TryAddStoredObject(const ReferencePtr aObject) { + ProcessPendingDeletions(); + return mStoredObjects.EnsureInserted(aObject); + } + + void AddPendingDeletion(std::function<void()>&& aPendingDeletion) { + auto lockedPendingDeletions = mPendingDeletions.Lock(); + lockedPendingDeletions->emplace_back(std::move(aPendingDeletion)); + } + + void RemoveStoredObject(const ReferencePtr aObject) { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); + mStoredObjects.Remove(aObject); + } + + /** + * @param aUnscaledFont the UnscaledFont to increment the reference count for + * @return the previous reference count + */ + int32_t IncrementUnscaledFontRefCount(const ReferencePtr aUnscaledFont) { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); + int32_t& count = mUnscaledFontRefs.LookupOrInsert(aUnscaledFont, 0); + return count++; + } + + /** + * Decrements the reference count for aUnscaledFont and, if count is now zero, + * records its destruction. + * @param aUnscaledFont the UnscaledFont to decrement the reference count for + */ + void DecrementUnscaledFontRefCount(const ReferencePtr aUnscaledFont); + + void AddScaledFont(ScaledFont* aFont) { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); + if (mStoredFonts.EnsureInserted(aFont) && WantsExternalFonts()) { + mScaledFonts.push_back(aFont); + } + } + + void RemoveScaledFont(ScaledFont* aFont) { mStoredFonts.Remove(aFont); } + + void AddSourceSurface(SourceSurface* aSurface) { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); + mStoredSurfaces.InsertOrUpdate(aSurface, aSurface); + } + + void RemoveSourceSurface(SourceSurface* aSurface) { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); + mStoredSurfaces.Remove(aSurface); + } + +#if defined(DEBUG) + // Only used within debug assertions. + bool HasStoredObject(const ReferencePtr aObject) { + ProcessPendingDeletions(); + return mStoredObjects.Contains(aObject); + } +#endif + + void AddStoredFontData(const uint64_t aFontDataKey) { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); + mStoredFontData.Insert(aFontDataKey); + } + + bool HasStoredFontData(const uint64_t aFontDataKey) { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); + return mStoredFontData.Contains(aFontDataKey); + } + + bool WantsExternalFonts() const { return mExternalFonts; } + + virtual void StoreSourceSurfaceRecording(SourceSurface* aSurface, + const char* aReason); + + /** + * Used when a source surface is destroyed, aSurface is a void* instead of a + * SourceSurface* because this is called during the SourceSurface destructor, + * so it is partially destructed and should not be accessed. + * @param aSurface the surface whose destruction is being recorded + */ + void RecordSourceSurfaceDestruction(void* aSurface); + + virtual void AddDependentSurface(uint64_t aDependencyId) { + MOZ_CRASH("GFX: AddDependentSurface"); + } + + struct ExternalSurfaceEntry { + RefPtr<SourceSurface> mSurface; + int64_t mEventCount = -1; + }; + + using ExternalSurfacesHolder = std::deque<ExternalSurfaceEntry>; + + void TakeExternalSurfaces(ExternalSurfacesHolder& aSurfaces) { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); + aSurfaces = std::move(mExternalSurfaces); + } + + protected: + NS_DECL_OWNINGTHREAD + + void StoreExternalSurfaceRecording(SourceSurface* aSurface, uint64_t aKey); + + void ProcessPendingDeletions() { + NS_ASSERT_OWNINGTHREAD(DrawEventRecorderPrivate); + + PendingDeletionsVector pendingDeletions; + { + auto lockedPendingDeletions = mPendingDeletions.Lock(); + pendingDeletions.swap(*lockedPendingDeletions); + } + for (const auto& pendingDeletion : pendingDeletions) { + pendingDeletion(); + } + } + + virtual void Flush() = 0; + + nsTHashSet<const void*> mStoredObjects; + + using PendingDeletionsVector = std::vector<std::function<void()>>; + DataMutex<PendingDeletionsVector> mPendingDeletions{ + "DrawEventRecorderPrivate::mPendingDeletions"}; + + // It's difficult to track the lifetimes of UnscaledFonts directly, so we + // instead track the number of recorded ScaledFonts that hold a reference to + // an Unscaled font and use that as a proxy to the real lifetime. An + // UnscaledFonts lifetime could be longer than this, but we only use the + // ScaledFonts directly and if another uses an UnscaledFont we have destroyed + // on the translation side, it will be recreated. + nsTHashMap<const void*, int32_t> mUnscaledFontRefs; + + nsTHashSet<uint64_t> mStoredFontData; + nsTHashSet<ScaledFont*> mStoredFonts; + std::vector<RefPtr<ScaledFont>> mScaledFonts; + + // SourceSurfaces can get deleted off the main thread, so we hold a map of the + // raw pointer to a ThreadSafeWeakPtr to protect against this. + nsTHashMap<void*, ThreadSafeWeakPtr<SourceSurface>> mStoredSurfaces; + + ReferencePtr mCurrentDT; + ExternalSurfacesHolder mExternalSurfaces; + bool mExternalFonts; +}; + +typedef std::function<void(MemStream& aStream, + std::vector<RefPtr<ScaledFont>>& aScaledFonts)> + SerializeResourcesFn; + +// WARNING: This should not be used in its existing state because +// it is likely to OOM because of large continguous allocations. +class DrawEventRecorderMemory : public DrawEventRecorderPrivate { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderMemory, override) + + /** + * Constructs a DrawEventRecorder that stores the recording in memory. + */ + DrawEventRecorderMemory(); + explicit DrawEventRecorderMemory(const SerializeResourcesFn& aSerialize); + + RecorderType GetRecorderType() const override { return RecorderType::MEMORY; } + + void RecordEvent(const RecordedEvent& aEvent) override; + + void AddDependentSurface(uint64_t aDependencyId) override; + + nsTHashSet<uint64_t>&& TakeDependentSurfaces(); + + /** + * @return the current size of the recording (in chars). + */ + size_t RecordingSize(); + + /** + * Wipes the internal recording buffer, but the recorder does NOT forget which + * objects it has recorded. This can be used so that a recording can be copied + * and processed in chunks, releasing memory as it goes. + */ + void WipeRecording(); + bool Finish() override; + void FlushItem(IntRect) override; + + MemStream mOutputStream; + /* The index stream is of the form: + * ItemIndex { size_t dataEnd; size_t extraDataEnd; } + * It gets concatenated to the end of mOutputStream in Finish() + * The last size_t in the stream is offset of the begining of the + * index. + */ + MemStream mIndex; + + protected: + virtual ~DrawEventRecorderMemory() = default; + + private: + SerializeResourcesFn mSerializeCallback; + nsTHashSet<uint64_t> mDependentSurfaces; + + void Flush() override; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_DRAWEVENTRECORDER_H_ */ diff --git a/gfx/2d/DrawTarget.cpp b/gfx/2d/DrawTarget.cpp new file mode 100644 index 0000000000..f6e8b378d5 --- /dev/null +++ b/gfx/2d/DrawTarget.cpp @@ -0,0 +1,355 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "2D.h" +#include "Blur.h" +#include "Logging.h" +#include "PathHelpers.h" +#include "SourceSurfaceRawData.h" +#include "Tools.h" + +#include "BufferEdgePad.h" +#include "BufferUnrotate.h" + +#ifdef USE_NEON +# include "mozilla/arm.h" +# include "LuminanceNEON.h" +#endif + +namespace mozilla { +namespace gfx { + +/** + * Byte offsets of channels in a native packed gfxColor or cairo image surface. + */ +#ifdef IS_BIG_ENDIAN +# define GFX_ARGB32_OFFSET_A 0 +# define GFX_ARGB32_OFFSET_R 1 +# define GFX_ARGB32_OFFSET_G 2 +# define GFX_ARGB32_OFFSET_B 3 +#else +# define GFX_ARGB32_OFFSET_A 3 +# define GFX_ARGB32_OFFSET_R 2 +# define GFX_ARGB32_OFFSET_G 1 +# define GFX_ARGB32_OFFSET_B 0 +#endif + +// c = n / 255 +// c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4)) * 255 + 0.5 +static const uint8_t gsRGBToLinearRGBMap[256] = { + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, + 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, + 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, + 12, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 17, + 18, 18, 19, 19, 20, 20, 21, 22, 22, 23, 23, 24, 24, 25, 25, + 26, 27, 27, 28, 29, 29, 30, 30, 31, 32, 32, 33, 34, 35, 35, + 36, 37, 37, 38, 39, 40, 41, 41, 42, 43, 44, 45, 45, 46, 47, + 48, 49, 50, 51, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 76, 77, + 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 90, 91, 92, 93, 95, + 96, 97, 99, 100, 101, 103, 104, 105, 107, 108, 109, 111, 112, 114, 115, + 116, 118, 119, 121, 122, 124, 125, 127, 128, 130, 131, 133, 134, 136, 138, + 139, 141, 142, 144, 146, 147, 149, 151, 152, 154, 156, 157, 159, 161, 163, + 164, 166, 168, 170, 171, 173, 175, 177, 179, 181, 183, 184, 186, 188, 190, + 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, + 222, 224, 226, 229, 231, 233, 235, 237, 239, 242, 244, 246, 248, 250, 253, + 255}; + +static void ComputesRGBLuminanceMask(const uint8_t* aSourceData, + int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntSize& aSize, + float aOpacity) { +#ifdef USE_NEON + if (mozilla::supports_neon()) { + ComputesRGBLuminanceMask_NEON(aSourceData, aSourceStride, aDestData, + aDestStride, aSize, aOpacity); + return; + } +#endif + + int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity + int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity + int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721 + int32_t sourceOffset = aSourceStride - 4 * aSize.width; + const uint8_t* sourcePixel = aSourceData; + int32_t destOffset = aDestStride - aSize.width; + uint8_t* destPixel = aDestData; + + for (int32_t y = 0; y < aSize.height; y++) { + for (int32_t x = 0; x < aSize.width; x++) { + uint8_t a = sourcePixel[GFX_ARGB32_OFFSET_A]; + + if (a) { + *destPixel = (redFactor * sourcePixel[GFX_ARGB32_OFFSET_R] + + greenFactor * sourcePixel[GFX_ARGB32_OFFSET_G] + + blueFactor * sourcePixel[GFX_ARGB32_OFFSET_B]) >> + 8; + } else { + *destPixel = 0; + } + sourcePixel += 4; + destPixel++; + } + sourcePixel += sourceOffset; + destPixel += destOffset; + } +} + +static void ComputeLinearRGBLuminanceMask( + const uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntSize& aSize, float aOpacity) { + int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity + int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity + int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721 + int32_t sourceOffset = aSourceStride - 4 * aSize.width; + const uint8_t* sourcePixel = aSourceData; + int32_t destOffset = aDestStride - aSize.width; + uint8_t* destPixel = aDestData; + + for (int32_t y = 0; y < aSize.height; y++) { + for (int32_t x = 0; x < aSize.width; x++) { + uint8_t a = sourcePixel[GFX_ARGB32_OFFSET_A]; + + // unpremultiply + if (a) { + if (a == 255) { + /* sRGB -> linearRGB -> intensity */ + *destPixel = static_cast<uint8_t>( + (gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_R]] * + redFactor + + gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_G]] * + greenFactor + + gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_B]] * + blueFactor) >> + 8); + } else { + uint8_t tempPixel[4]; + tempPixel[GFX_ARGB32_OFFSET_B] = + (255 * sourcePixel[GFX_ARGB32_OFFSET_B]) / a; + tempPixel[GFX_ARGB32_OFFSET_G] = + (255 * sourcePixel[GFX_ARGB32_OFFSET_G]) / a; + tempPixel[GFX_ARGB32_OFFSET_R] = + (255 * sourcePixel[GFX_ARGB32_OFFSET_R]) / a; + + /* sRGB -> linearRGB -> intensity */ + *destPixel = static_cast<uint8_t>( + ((gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_R]] * + redFactor + + gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_G]] * + greenFactor + + gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_B]] * + blueFactor) >> + 8) * + (a / 255.0f)); + } + } else { + *destPixel = 0; + } + sourcePixel += 4; + destPixel++; + } + sourcePixel += sourceOffset; + destPixel += destOffset; + } +} + +void DrawTarget::PushDeviceSpaceClipRects(const IntRect* aRects, + uint32_t aCount) { + Matrix oldTransform = GetTransform(); + SetTransform(Matrix()); + + RefPtr<PathBuilder> pathBuilder = CreatePathBuilder(); + for (uint32_t i = 0; i < aCount; i++) { + AppendRectToPath(pathBuilder, Rect(aRects[i])); + } + RefPtr<Path> path = pathBuilder->Finish(); + PushClip(path); + + SetTransform(oldTransform); +} + +void DrawTarget::FillRoundedRect(const RoundedRect& aRect, + const Pattern& aPattern, + const DrawOptions& aOptions) { + RefPtr<Path> path = MakePathForRoundedRect(*this, aRect.rect, aRect.corners); + Fill(path, aPattern, aOptions); +} + +void DrawTarget::StrokeCircle(const Point& aOrigin, float radius, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) { + RefPtr<Path> path = MakePathForCircle(*this, aOrigin, radius); + Stroke(path, aPattern, aStrokeOptions, aOptions); +} + +void DrawTarget::FillCircle(const Point& aOrigin, float radius, + const Pattern& aPattern, + const DrawOptions& aOptions) { + RefPtr<Path> path = MakePathForCircle(*this, aOrigin, radius); + Fill(path, aPattern, aOptions); +} + +void DrawTarget::StrokeGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) { + if (RefPtr<Path> path = aFont->GetPathForGlyphs(aBuffer, this)) { + Stroke(path, aPattern, aStrokeOptions, aOptions); + } +} + +already_AddRefed<SourceSurface> DrawTarget::IntoLuminanceSource( + LuminanceType aMaskType, float aOpacity) { + // The default IntoLuminanceSource implementation needs a format of B8G8R8A8. + if (mFormat != SurfaceFormat::B8G8R8A8) { + return nullptr; + } + + RefPtr<SourceSurface> surface = Snapshot(); + if (!surface) { + return nullptr; + } + + IntSize size = surface->GetSize(); + + RefPtr<DataSourceSurface> maskSurface = surface->GetDataSurface(); + if (!maskSurface) { + return nullptr; + } + + DataSourceSurface::MappedSurface map; + if (!maskSurface->Map(DataSourceSurface::MapType::READ, &map)) { + return nullptr; + } + + // Create alpha channel mask for output + RefPtr<SourceSurfaceAlignedRawData> destMaskSurface = + new SourceSurfaceAlignedRawData; + if (!destMaskSurface->Init(size, SurfaceFormat::A8, false, 0)) { + return nullptr; + } + DataSourceSurface::MappedSurface destMap; + if (!destMaskSurface->Map(DataSourceSurface::MapType::WRITE, &destMap)) { + return nullptr; + } + + switch (aMaskType) { + case LuminanceType::LUMINANCE: { + ComputesRGBLuminanceMask(map.mData, map.mStride, destMap.mData, + destMap.mStride, size, aOpacity); + break; + } + case LuminanceType::LINEARRGB: { + ComputeLinearRGBLuminanceMask(map.mData, map.mStride, destMap.mData, + destMap.mStride, size, aOpacity); + break; + } + } + + maskSurface->Unmap(); + destMaskSurface->Unmap(); + + return destMaskSurface.forget(); +} + +void DrawTarget::Blur(const AlphaBoxBlur& aBlur) { + uint8_t* data; + IntSize size; + int32_t stride; + SurfaceFormat format; + if (!LockBits(&data, &size, &stride, &format)) { + gfxWarning() << "Cannot perform in-place blur on non-data DrawTarget"; + return; + } + + // Sanity check that the blur size matches the draw target. + MOZ_ASSERT(size == aBlur.GetSize()); + MOZ_ASSERT(stride == aBlur.GetStride()); + aBlur.Blur(data); + + ReleaseBits(data); +} + +void DrawTarget::PadEdges(const IntRegion& aRegion) { + PadDrawTargetOutFromRegion(this, aRegion); +} + +bool DrawTarget::Unrotate(IntPoint aRotation) { + unsigned char* data; + IntSize size; + int32_t stride; + SurfaceFormat format; + + if (LockBits(&data, &size, &stride, &format)) { + uint8_t bytesPerPixel = BytesPerPixel(format); + BufferUnrotate(data, size.width * bytesPerPixel, size.height, stride, + aRotation.x * bytesPerPixel, aRotation.y); + ReleaseBits(data); + return true; + } + return false; +} + +int32_t ShadowOptions::BlurRadius() const { + return AlphaBoxBlur::CalculateBlurRadius(Point(mSigma, mSigma)).width; +} + +void DrawTarget::DrawShadow(const Path* aPath, const Pattern& aPattern, + const ShadowOptions& aShadow, + const DrawOptions& aOptions, + const StrokeOptions* aStrokeOptions) { + // Get the approximate bounds of the source path + Rect bounds = aPath->GetFastBounds(GetTransform(), aStrokeOptions); + if (bounds.IsEmpty()) { + return; + } + // Inflate the bounds by the blur radius + bounds += aShadow.mOffset; + int32_t blurRadius = aShadow.BlurRadius(); + bounds.Inflate(blurRadius); + bounds.RoundOut(); + // Check if the bounds intersect the viewport + Rect viewport(GetRect()); + viewport.Inflate(blurRadius); + bounds = bounds.Intersect(viewport); + IntRect intBounds; + if (bounds.IsEmpty() || !bounds.ToIntRect(&intBounds) || + !CanCreateSimilarDrawTarget(intBounds.Size(), SurfaceFormat::A8)) { + return; + } + // Create a draw target for drawing the shadow mask with enough room for blur + RefPtr<DrawTarget> shadowTarget = CreateShadowDrawTarget( + intBounds.Size(), SurfaceFormat::A8, aShadow.mSigma); + if (shadowTarget) { + // See bug 1524554. + shadowTarget->ClearRect(Rect()); + } + if (!shadowTarget || !shadowTarget->IsValid()) { + return; + } + // Draw the path into the target for the initial shadow mask + Point offset = Point(intBounds.TopLeft()) - aShadow.mOffset; + shadowTarget->SetTransform(GetTransform().PostTranslate(-offset)); + DrawOptions shadowDrawOptions( + aOptions.mAlpha, CompositionOp::OP_OVER, + blurRadius > 1 ? AntialiasMode::NONE : aOptions.mAntialiasMode); + if (aStrokeOptions) { + shadowTarget->Stroke(aPath, aPattern, *aStrokeOptions, shadowDrawOptions); + } else { + shadowTarget->Fill(aPath, aPattern, shadowDrawOptions); + } + RefPtr<SourceSurface> snapshot = shadowTarget->Snapshot(); + // Finally, hand a snapshot of the mask to DrawSurfaceWithShadow for the + // final shadow blur + if (snapshot) { + DrawSurfaceWithShadow(snapshot, offset, aShadow, aOptions.mCompositionOp); + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/DrawTargetCairo.cpp b/gfx/2d/DrawTargetCairo.cpp new file mode 100644 index 0000000000..e189fe2445 --- /dev/null +++ b/gfx/2d/DrawTargetCairo.cpp @@ -0,0 +1,2009 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DrawTargetCairo.h" + +#include "SourceSurfaceCairo.h" +#include "PathCairo.h" +#include "HelpersCairo.h" +#include "BorrowedContext.h" +#include "FilterNodeSoftware.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Vector.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_print.h" +#include "nsPrintfCString.h" + +#include "cairo.h" +#include "cairo-tee.h" +#include <string.h> + +#include "Blur.h" +#include "Logging.h" +#include "Tools.h" + +#ifdef CAIRO_HAS_QUARTZ_SURFACE +# include "cairo-quartz.h" +# ifdef MOZ_WIDGET_COCOA +# include <ApplicationServices/ApplicationServices.h> +# endif +#endif + +#ifdef CAIRO_HAS_XLIB_SURFACE +# include "cairo-xlib.h" +#endif + +#ifdef CAIRO_HAS_WIN32_SURFACE +# include "cairo-win32.h" +#endif + +#define PIXMAN_DONT_DEFINE_STDINT +#include "pixman.h" + +#include <algorithm> + +// 2^23 +#define CAIRO_COORD_MAX (Float(0x7fffff)) + +namespace mozilla { +namespace gfx { + +cairo_surface_t* DrawTargetCairo::mDummySurface; + +namespace { + +// An RAII class to prepare to draw a context and optional path. Saves and +// restores the context on construction/destruction. +class AutoPrepareForDrawing { + public: + AutoPrepareForDrawing(DrawTargetCairo* dt, cairo_t* ctx) : mCtx(ctx) { + dt->PrepareForDrawing(ctx); + cairo_save(mCtx); + MOZ_ASSERT(cairo_status(mCtx) || + dt->GetTransform().FuzzyEquals(GetTransform())); + } + + AutoPrepareForDrawing(DrawTargetCairo* dt, cairo_t* ctx, const Path* path) + : mCtx(ctx) { + dt->PrepareForDrawing(ctx, path); + cairo_save(mCtx); + MOZ_ASSERT(cairo_status(mCtx) || + dt->GetTransform().FuzzyEquals(GetTransform())); + } + + ~AutoPrepareForDrawing() { + cairo_restore(mCtx); + cairo_status_t status = cairo_status(mCtx); + if (status) { + gfxWarning() << "DrawTargetCairo context in error state: " + << cairo_status_to_string(status) << "(" << status << ")"; + } + } + + private: +#ifdef DEBUG + Matrix GetTransform() { + cairo_matrix_t mat; + cairo_get_matrix(mCtx, &mat); + return Matrix(mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0); + } +#endif + + cairo_t* mCtx; +}; + +/* Clamp r to (0,0) (2^23,2^23) + * these are to be device coordinates. + * + * Returns false if the rectangle is completely out of bounds, + * true otherwise. + * + * This function assumes that it will be called with a rectangle being + * drawn into a surface with an identity transformation matrix; that + * is, anything above or to the left of (0,0) will be offscreen. + * + * First it checks if the rectangle is entirely beyond + * CAIRO_COORD_MAX; if so, it can't ever appear on the screen -- + * false is returned. + * + * Then it shifts any rectangles with x/y < 0 so that x and y are = 0, + * and adjusts the width and height appropriately. For example, a + * rectangle from (0,-5) with dimensions (5,10) will become a + * rectangle from (0,0) with dimensions (5,5). + * + * If after negative x/y adjustment to 0, either the width or height + * is negative, then the rectangle is completely offscreen, and + * nothing is drawn -- false is returned. + * + * Finally, if x+width or y+height are greater than CAIRO_COORD_MAX, + * the width and height are clamped such x+width or y+height are equal + * to CAIRO_COORD_MAX, and true is returned. + */ +static bool ConditionRect(Rect& r) { + // if either x or y is way out of bounds; + // note that we don't handle negative w/h here + if (r.X() > CAIRO_COORD_MAX || r.Y() > CAIRO_COORD_MAX) return false; + + if (r.X() < 0.f) { + r.SetWidth(r.XMost()); + if (r.Width() < 0.f) return false; + r.MoveToX(0.f); + } + + if (r.XMost() > CAIRO_COORD_MAX) { + r.SetRightEdge(CAIRO_COORD_MAX); + } + + if (r.Y() < 0.f) { + r.SetHeight(r.YMost()); + if (r.Height() < 0.f) return false; + + r.MoveToY(0.f); + } + + if (r.YMost() > CAIRO_COORD_MAX) { + r.SetBottomEdge(CAIRO_COORD_MAX); + } + return true; +} + +} // end anonymous namespace + +static bool SupportsSelfCopy(cairo_surface_t* surface) { + switch (cairo_surface_get_type(surface)) { +#ifdef CAIRO_HAS_QUARTZ_SURFACE + case CAIRO_SURFACE_TYPE_QUARTZ: + return true; +#endif +#ifdef CAIRO_HAS_WIN32_SURFACE + case CAIRO_SURFACE_TYPE_WIN32: + case CAIRO_SURFACE_TYPE_WIN32_PRINTING: + return true; +#endif + default: + return false; + } +} + +static bool PatternIsCompatible(const Pattern& aPattern) { + switch (aPattern.GetType()) { + case PatternType::LINEAR_GRADIENT: { + const LinearGradientPattern& pattern = + static_cast<const LinearGradientPattern&>(aPattern); + return pattern.mStops->GetBackendType() == BackendType::CAIRO; + } + case PatternType::RADIAL_GRADIENT: { + const RadialGradientPattern& pattern = + static_cast<const RadialGradientPattern&>(aPattern); + return pattern.mStops->GetBackendType() == BackendType::CAIRO; + } + case PatternType::CONIC_GRADIENT: { + const ConicGradientPattern& pattern = + static_cast<const ConicGradientPattern&>(aPattern); + return pattern.mStops->GetBackendType() == BackendType::CAIRO; + } + default: + return true; + } +} + +static cairo_user_data_key_t surfaceDataKey; + +static void ReleaseData(void* aData) { + DataSourceSurface* data = static_cast<DataSourceSurface*>(aData); + data->Unmap(); + data->Release(); +} + +static cairo_surface_t* CopyToImageSurface(unsigned char* aData, + const IntRect& aRect, + int32_t aStride, + SurfaceFormat aFormat) { + MOZ_ASSERT(aData); + + auto aRectWidth = aRect.Width(); + auto aRectHeight = aRect.Height(); + + cairo_surface_t* surf = cairo_image_surface_create( + GfxFormatToCairoFormat(aFormat), aRectWidth, aRectHeight); + // In certain scenarios, requesting larger than 8k image fails. Bug 803568 + // covers the details of how to run into it, but the full detailed + // investigation hasn't been done to determine the underlying cause. We + // will just handle the failure to allocate the surface to avoid a crash. + if (cairo_surface_status(surf)) { + gfxWarning() << "Invalid surface DTC " << cairo_surface_status(surf); + return nullptr; + } + + unsigned char* surfData = cairo_image_surface_get_data(surf); + int surfStride = cairo_image_surface_get_stride(surf); + int32_t pixelWidth = BytesPerPixel(aFormat); + + unsigned char* source = aData + aRect.Y() * aStride + aRect.X() * pixelWidth; + + MOZ_ASSERT(aStride >= aRectWidth * pixelWidth); + for (int32_t y = 0; y < aRectHeight; ++y) { + memcpy(surfData + y * surfStride, source + y * aStride, + aRectWidth * pixelWidth); + } + cairo_surface_mark_dirty(surf); + return surf; +} + +/** + * If aSurface can be represented as a surface of type + * CAIRO_SURFACE_TYPE_IMAGE then returns that surface. Does + * not add a reference. + */ +static cairo_surface_t* GetAsImageSurface(cairo_surface_t* aSurface) { + if (cairo_surface_get_type(aSurface) == CAIRO_SURFACE_TYPE_IMAGE) { + return aSurface; +#ifdef CAIRO_HAS_WIN32_SURFACE + } else if (cairo_surface_get_type(aSurface) == CAIRO_SURFACE_TYPE_WIN32) { + return cairo_win32_surface_get_image(aSurface); +#endif + } + + return nullptr; +} + +static cairo_surface_t* CreateSubImageForData(unsigned char* aData, + const IntRect& aRect, int aStride, + SurfaceFormat aFormat) { + if (!aData) { + gfxWarning() << "DrawTargetCairo.CreateSubImageForData null aData"; + return nullptr; + } + unsigned char* data = + aData + aRect.Y() * aStride + aRect.X() * BytesPerPixel(aFormat); + + cairo_surface_t* image = cairo_image_surface_create_for_data( + data, GfxFormatToCairoFormat(aFormat), aRect.Width(), aRect.Height(), + aStride); + // Set the subimage's device offset so that in remains in the same place + // relative to the parent + cairo_surface_set_device_offset(image, -aRect.X(), -aRect.Y()); + return image; +} + +/** + * Returns a referenced cairo_surface_t representing the + * sub-image specified by aSubImage. + */ +static cairo_surface_t* ExtractSubImage(cairo_surface_t* aSurface, + const IntRect& aSubImage, + SurfaceFormat aFormat) { + // No need to worry about retaining a reference to the original + // surface since the only caller of this function guarantees + // that aSurface will stay alive as long as the result + + cairo_surface_t* image = GetAsImageSurface(aSurface); + if (image) { + image = + CreateSubImageForData(cairo_image_surface_get_data(image), aSubImage, + cairo_image_surface_get_stride(image), aFormat); + return image; + } + + cairo_surface_t* similar = cairo_surface_create_similar( + aSurface, cairo_surface_get_content(aSurface), aSubImage.Width(), + aSubImage.Height()); + + cairo_t* ctx = cairo_create(similar); + cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface(ctx, aSurface, -aSubImage.X(), -aSubImage.Y()); + cairo_paint(ctx); + cairo_destroy(ctx); + + cairo_surface_set_device_offset(similar, -aSubImage.X(), -aSubImage.Y()); + return similar; +} + +/** + * Returns cairo surface for the given SourceSurface. + * If possible, it will use the cairo_surface associated with aSurface, + * otherwise, it will create a new cairo_surface. + * In either case, the caller must call cairo_surface_destroy on the + * result when it is done with it. + */ +static cairo_surface_t* GetCairoSurfaceForSourceSurface( + SourceSurface* aSurface, bool aExistingOnly = false, + const IntRect& aSubImage = IntRect()) { + if (!aSurface) { + return nullptr; + } + + IntRect subimage = IntRect(IntPoint(), aSurface->GetSize()); + if (!aSubImage.IsEmpty()) { + MOZ_ASSERT(!aExistingOnly); + MOZ_ASSERT(subimage.Contains(aSubImage)); + subimage = aSubImage; + } + + if (aSurface->GetType() == SurfaceType::CAIRO) { + cairo_surface_t* surf = + static_cast<SourceSurfaceCairo*>(aSurface)->GetSurface(); + if (aSubImage.IsEmpty()) { + cairo_surface_reference(surf); + } else { + surf = ExtractSubImage(surf, subimage, aSurface->GetFormat()); + } + return surf; + } + + if (aSurface->GetType() == SurfaceType::CAIRO_IMAGE) { + cairo_surface_t* surf = + static_cast<const DataSourceSurfaceCairo*>(aSurface)->GetSurface(); + if (aSubImage.IsEmpty()) { + cairo_surface_reference(surf); + } else { + surf = ExtractSubImage(surf, subimage, aSurface->GetFormat()); + } + return surf; + } + + if (aExistingOnly) { + return nullptr; + } + + RefPtr<DataSourceSurface> data = aSurface->GetDataSurface(); + if (!data) { + return nullptr; + } + + DataSourceSurface::MappedSurface map; + if (!data->Map(DataSourceSurface::READ, &map)) { + return nullptr; + } + + cairo_surface_t* surf = CreateSubImageForData(map.mData, subimage, + map.mStride, data->GetFormat()); + + // In certain scenarios, requesting larger than 8k image fails. Bug 803568 + // covers the details of how to run into it, but the full detailed + // investigation hasn't been done to determine the underlying cause. We + // will just handle the failure to allocate the surface to avoid a crash. + if (!surf || cairo_surface_status(surf)) { + if (surf && (cairo_surface_status(surf) == CAIRO_STATUS_INVALID_STRIDE)) { + // If we failed because of an invalid stride then copy into + // a new surface with a stride that cairo chooses. No need to + // set user data since we're not dependent on the original + // data. + cairo_surface_t* result = CopyToImageSurface( + map.mData, subimage, map.mStride, data->GetFormat()); + data->Unmap(); + return result; + } + data->Unmap(); + return nullptr; + } + + cairo_surface_set_user_data(surf, &surfaceDataKey, data.forget().take(), + ReleaseData); + return surf; +} + +// An RAII class to temporarily clear any device offset set +// on a surface. Note that this does not take a reference to the +// surface. +class AutoClearDeviceOffset final { + public: + explicit AutoClearDeviceOffset(SourceSurface* aSurface) + : mSurface(nullptr), mX(0), mY(0) { + Init(aSurface); + } + + explicit AutoClearDeviceOffset(const Pattern& aPattern) + : mSurface(nullptr), mX(0.0), mY(0.0) { + if (aPattern.GetType() == PatternType::SURFACE) { + const SurfacePattern& pattern = + static_cast<const SurfacePattern&>(aPattern); + Init(pattern.mSurface); + } + } + + ~AutoClearDeviceOffset() { + if (mSurface) { + cairo_surface_set_device_offset(mSurface, mX, mY); + } + } + + private: + void Init(SourceSurface* aSurface) { + cairo_surface_t* surface = GetCairoSurfaceForSourceSurface(aSurface, true); + if (surface) { + Init(surface); + cairo_surface_destroy(surface); + } + } + + void Init(cairo_surface_t* aSurface) { + mSurface = aSurface; + cairo_surface_get_device_offset(mSurface, &mX, &mY); + cairo_surface_set_device_offset(mSurface, 0, 0); + } + + cairo_surface_t* mSurface; + double mX; + double mY; +}; + +static inline void CairoPatternAddGradientStop(cairo_pattern_t* aPattern, + const GradientStop& aStop, + Float aNudge = 0) { + cairo_pattern_add_color_stop_rgba(aPattern, aStop.offset + aNudge, + aStop.color.r, aStop.color.g, aStop.color.b, + aStop.color.a); +} + +// Never returns nullptr. As such, you must always pass in Cairo-compatible +// patterns, most notably gradients with a GradientStopCairo. +// The pattern returned must have cairo_pattern_destroy() called on it by the +// caller. +// As the cairo_pattern_t returned may depend on the Pattern passed in, the +// lifetime of the cairo_pattern_t returned must not exceed the lifetime of the +// Pattern passed in. +static cairo_pattern_t* GfxPatternToCairoPattern(const Pattern& aPattern, + Float aAlpha, + const Matrix& aTransform) { + cairo_pattern_t* pat; + const Matrix* matrix = nullptr; + + switch (aPattern.GetType()) { + case PatternType::COLOR: { + DeviceColor color = static_cast<const ColorPattern&>(aPattern).mColor; + pat = cairo_pattern_create_rgba(color.r, color.g, color.b, + color.a * aAlpha); + break; + } + + case PatternType::SURFACE: { + const SurfacePattern& pattern = + static_cast<const SurfacePattern&>(aPattern); + cairo_surface_t* surf = GetCairoSurfaceForSourceSurface( + pattern.mSurface, false, pattern.mSamplingRect); + if (!surf) return nullptr; + + pat = cairo_pattern_create_for_surface(surf); + + matrix = &pattern.mMatrix; + + cairo_pattern_set_filter( + pat, GfxSamplingFilterToCairoFilter(pattern.mSamplingFilter)); + cairo_pattern_set_extend(pat, + GfxExtendToCairoExtend(pattern.mExtendMode)); + + cairo_surface_destroy(surf); + break; + } + case PatternType::LINEAR_GRADIENT: { + const LinearGradientPattern& pattern = + static_cast<const LinearGradientPattern&>(aPattern); + + pat = cairo_pattern_create_linear(pattern.mBegin.x, pattern.mBegin.y, + pattern.mEnd.x, pattern.mEnd.y); + + MOZ_ASSERT(pattern.mStops->GetBackendType() == BackendType::CAIRO); + GradientStopsCairo* cairoStops = + static_cast<GradientStopsCairo*>(pattern.mStops.get()); + cairo_pattern_set_extend( + pat, GfxExtendToCairoExtend(cairoStops->GetExtendMode())); + + matrix = &pattern.mMatrix; + + const std::vector<GradientStop>& stops = cairoStops->GetStops(); + for (size_t i = 0; i < stops.size(); ++i) { + CairoPatternAddGradientStop(pat, stops[i]); + } + + break; + } + case PatternType::RADIAL_GRADIENT: { + const RadialGradientPattern& pattern = + static_cast<const RadialGradientPattern&>(aPattern); + + pat = cairo_pattern_create_radial(pattern.mCenter1.x, pattern.mCenter1.y, + pattern.mRadius1, pattern.mCenter2.x, + pattern.mCenter2.y, pattern.mRadius2); + + MOZ_ASSERT(pattern.mStops->GetBackendType() == BackendType::CAIRO); + GradientStopsCairo* cairoStops = + static_cast<GradientStopsCairo*>(pattern.mStops.get()); + cairo_pattern_set_extend( + pat, GfxExtendToCairoExtend(cairoStops->GetExtendMode())); + + matrix = &pattern.mMatrix; + + const std::vector<GradientStop>& stops = cairoStops->GetStops(); + for (size_t i = 0; i < stops.size(); ++i) { + CairoPatternAddGradientStop(pat, stops[i]); + } + + break; + } + case PatternType::CONIC_GRADIENT: { + // XXX(ntim): Bug 1617039 - Implement conic-gradient for Cairo + pat = cairo_pattern_create_rgba(0.0, 0.0, 0.0, 0.0); + + break; + } + default: { + // We should support all pattern types! + MOZ_ASSERT(false); + } + } + + // The pattern matrix is a matrix that transforms the pattern into user + // space. Cairo takes a matrix that converts from user space to pattern + // space. Cairo therefore needs the inverse. + if (matrix) { + cairo_matrix_t mat; + GfxMatrixToCairoMatrix(*matrix, mat); + cairo_matrix_invert(&mat); + cairo_pattern_set_matrix(pat, &mat); + } + + return pat; +} + +static bool NeedIntermediateSurface(const Pattern& aPattern, + const DrawOptions& aOptions) { + // We pre-multiply colours' alpha by the global alpha, so we don't need to + // use an intermediate surface for them. + if (aPattern.GetType() == PatternType::COLOR) return false; + + if (aOptions.mAlpha == 1.0) return false; + + return true; +} + +DrawTargetCairo::DrawTargetCairo() + : mContext(nullptr), + mSurface(nullptr), + mTransformSingular(false), + mLockedBits(nullptr), + mFontOptions(nullptr) {} + +DrawTargetCairo::~DrawTargetCairo() { + cairo_destroy(mContext); + if (mSurface) { + cairo_surface_destroy(mSurface); + mSurface = nullptr; + } + if (mFontOptions) { + cairo_font_options_destroy(mFontOptions); + mFontOptions = nullptr; + } + MOZ_ASSERT(!mLockedBits); +} + +bool DrawTargetCairo::IsValid() const { + return mSurface && !cairo_surface_status(mSurface) && mContext && + !cairo_surface_status(cairo_get_group_target(mContext)); +} + +DrawTargetType DrawTargetCairo::GetType() const { + if (mContext) { + cairo_surface_type_t type = cairo_surface_get_type(mSurface); + if (type == CAIRO_SURFACE_TYPE_TEE) { + type = cairo_surface_get_type(cairo_tee_surface_index(mSurface, 0)); + MOZ_ASSERT(type != CAIRO_SURFACE_TYPE_TEE, "C'mon!"); + MOZ_ASSERT( + type == cairo_surface_get_type(cairo_tee_surface_index(mSurface, 1)), + "What should we do here?"); + } + switch (type) { + case CAIRO_SURFACE_TYPE_PDF: + case CAIRO_SURFACE_TYPE_PS: + case CAIRO_SURFACE_TYPE_SVG: + case CAIRO_SURFACE_TYPE_WIN32_PRINTING: + case CAIRO_SURFACE_TYPE_XML: + return DrawTargetType::VECTOR; + + case CAIRO_SURFACE_TYPE_VG: + case CAIRO_SURFACE_TYPE_GL: + case CAIRO_SURFACE_TYPE_GLITZ: + case CAIRO_SURFACE_TYPE_QUARTZ: + case CAIRO_SURFACE_TYPE_DIRECTFB: + return DrawTargetType::HARDWARE_RASTER; + + case CAIRO_SURFACE_TYPE_SKIA: + case CAIRO_SURFACE_TYPE_QT: + MOZ_FALLTHROUGH_ASSERT( + "Can't determine actual DrawTargetType for DrawTargetCairo - " + "assuming SOFTWARE_RASTER"); + case CAIRO_SURFACE_TYPE_IMAGE: + case CAIRO_SURFACE_TYPE_XLIB: + case CAIRO_SURFACE_TYPE_XCB: + case CAIRO_SURFACE_TYPE_WIN32: + case CAIRO_SURFACE_TYPE_BEOS: + case CAIRO_SURFACE_TYPE_OS2: + case CAIRO_SURFACE_TYPE_QUARTZ_IMAGE: + case CAIRO_SURFACE_TYPE_SCRIPT: + case CAIRO_SURFACE_TYPE_RECORDING: + case CAIRO_SURFACE_TYPE_DRM: + case CAIRO_SURFACE_TYPE_SUBSURFACE: + case CAIRO_SURFACE_TYPE_TEE: // included to silence warning about + // unhandled enum value + return DrawTargetType::SOFTWARE_RASTER; + default: + MOZ_CRASH("GFX: Unsupported cairo surface type"); + } + } + MOZ_ASSERT(false, "Could not determine DrawTargetType for DrawTargetCairo"); + return DrawTargetType::SOFTWARE_RASTER; +} + +IntSize DrawTargetCairo::GetSize() const { return mSize; } + +SurfaceFormat GfxFormatForCairoSurface(cairo_surface_t* surface) { + cairo_surface_type_t type = cairo_surface_get_type(surface); + if (type == CAIRO_SURFACE_TYPE_IMAGE) { + return CairoFormatToGfxFormat(cairo_image_surface_get_format(surface)); + } +#ifdef CAIRO_HAS_XLIB_SURFACE + // xlib is currently the only Cairo backend that creates 16bpp surfaces + if (type == CAIRO_SURFACE_TYPE_XLIB && + cairo_xlib_surface_get_depth(surface) == 16) { + return SurfaceFormat::R5G6B5_UINT16; + } +#endif + return CairoContentToGfxFormat(cairo_surface_get_content(surface)); +} + +void DrawTargetCairo::Link(const char* aDestination, const Rect& aRect) { + if (!aDestination || !*aDestination) { + // No destination? Just bail out. + return; + } + + // We need to \-escape any single-quotes in the destination string, in order + // to pass it via the attributes arg to cairo_tag_begin. + // + // We also need to escape any backslashes (bug 1748077), as per doc at + // https://www.cairographics.org/manual/cairo-Tags-and-Links.html#cairo-tag-begin + // The cairo-pdf-interchange backend (used on all platforms EXCEPT macOS) + // actually requires that we *doubly* escape the backslashes (this may be a + // cairo bug), while the quartz backend is fine with them singly-escaped. + // + // (Encoding of non-ASCII chars etc gets handled later by the PDF backend.) + nsAutoCString dest(aDestination); + for (size_t i = dest.Length(); i > 0;) { + --i; + if (dest[i] == '\'') { + dest.ReplaceLiteral(i, 1, "\\'"); + } else if (dest[i] == '\\') { +#ifdef XP_MACOSX + dest.ReplaceLiteral(i, 1, "\\\\"); +#else + dest.ReplaceLiteral(i, 1, "\\\\\\\\"); +#endif + } + } + + double x = aRect.x, y = aRect.y, w = aRect.width, h = aRect.height; + cairo_user_to_device(mContext, &x, &y); + cairo_user_to_device_distance(mContext, &w, &h); + + nsPrintfCString attributes("rect=[%f %f %f %f] ", x, y, w, h); + if (dest[0] == '#') { + // The actual destination does not have a leading '#'. + attributes.AppendPrintf("dest='%s'", dest.get() + 1); + } else { + attributes.AppendPrintf("uri='%s'", dest.get()); + } + + // We generate a begin/end pair with no content in between, because we are + // using the rect attribute of the begin tag to specify the link region + // rather than depending on cairo to accumulate the painted area. + cairo_tag_begin(mContext, CAIRO_TAG_LINK, attributes.get()); + cairo_tag_end(mContext, CAIRO_TAG_LINK); +} + +void DrawTargetCairo::Destination(const char* aDestination, + const Point& aPoint) { + if (!aDestination || !*aDestination) { + // No destination? Just bail out. + return; + } + + nsAutoCString dest(aDestination); + for (size_t i = dest.Length(); i > 0;) { + --i; + if (dest[i] == '\'') { + dest.ReplaceLiteral(i, 1, "\\'"); + } + } + + double x = aPoint.x, y = aPoint.y; + cairo_user_to_device(mContext, &x, &y); + + nsPrintfCString attributes("name='%s' x=%f y=%f internal", dest.get(), x, y); + cairo_tag_begin(mContext, CAIRO_TAG_DEST, attributes.get()); + cairo_tag_end(mContext, CAIRO_TAG_DEST); +} + +already_AddRefed<SourceSurface> DrawTargetCairo::Snapshot() { + if (!IsValid()) { + gfxCriticalNote << "DrawTargetCairo::Snapshot with bad surface " + << hexa(mSurface) << ", context " << hexa(mContext) + << ", status " + << (mSurface ? cairo_surface_status(mSurface) : -1); + return nullptr; + } + if (mSnapshot) { + RefPtr<SourceSurface> snapshot(mSnapshot); + return snapshot.forget(); + } + + IntSize size = GetSize(); + + mSnapshot = new SourceSurfaceCairo(mSurface, size, + GfxFormatForCairoSurface(mSurface), this); + RefPtr<SourceSurface> snapshot(mSnapshot); + return snapshot.forget(); +} + +bool DrawTargetCairo::LockBits(uint8_t** aData, IntSize* aSize, + int32_t* aStride, SurfaceFormat* aFormat, + IntPoint* aOrigin) { + cairo_surface_t* target = cairo_get_group_target(mContext); + cairo_surface_t* surf = target; +#ifdef CAIRO_HAS_WIN32_SURFACE + if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_WIN32) { + cairo_surface_t* imgsurf = cairo_win32_surface_get_image(surf); + if (imgsurf) { + surf = imgsurf; + } + } +#endif + if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_IMAGE && + cairo_surface_status(surf) == CAIRO_STATUS_SUCCESS) { + PointDouble offset; + cairo_surface_get_device_offset(target, &offset.x.value, &offset.y.value); + // verify the device offset can be converted to integers suitable for a + // bounds rect + IntPoint origin(int32_t(-offset.x), int32_t(-offset.y)); + if (-PointDouble(origin) != offset || (!aOrigin && origin != IntPoint())) { + return false; + } + + WillChange(); + Flush(); + + mLockedBits = cairo_image_surface_get_data(surf); + *aData = mLockedBits; + *aSize = IntSize(cairo_image_surface_get_width(surf), + cairo_image_surface_get_height(surf)); + *aStride = cairo_image_surface_get_stride(surf); + *aFormat = CairoFormatToGfxFormat(cairo_image_surface_get_format(surf)); + if (aOrigin) { + *aOrigin = origin; + } + return true; + } + + return false; +} + +void DrawTargetCairo::ReleaseBits(uint8_t* aData) { + MOZ_ASSERT(mLockedBits == aData); + mLockedBits = nullptr; + cairo_surface_t* surf = cairo_get_group_target(mContext); +#ifdef CAIRO_HAS_WIN32_SURFACE + if (cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_WIN32) { + cairo_surface_t* imgsurf = cairo_win32_surface_get_image(surf); + if (imgsurf) { + cairo_surface_mark_dirty(imgsurf); + } + } +#endif + cairo_surface_mark_dirty(surf); +} + +void DrawTargetCairo::Flush() { + cairo_surface_t* surf = cairo_get_group_target(mContext); + cairo_surface_flush(surf); +} + +void DrawTargetCairo::PrepareForDrawing(cairo_t* aContext, + const Path* aPath /* = nullptr */) { + WillChange(aPath); +} + +cairo_surface_t* DrawTargetCairo::GetDummySurface() { + if (mDummySurface) { + return mDummySurface; + } + + mDummySurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + + return mDummySurface; +} + +static void PaintWithAlpha(cairo_t* aContext, const DrawOptions& aOptions) { + if (aOptions.mCompositionOp == CompositionOp::OP_SOURCE) { + // Cairo treats the source operator like a lerp when alpha is < 1. + // Approximate the desired operator by: out = 0; out += src*alpha; + if (aOptions.mAlpha == 1) { + cairo_set_operator(aContext, CAIRO_OPERATOR_SOURCE); + cairo_paint(aContext); + } else { + cairo_set_operator(aContext, CAIRO_OPERATOR_CLEAR); + cairo_paint(aContext); + cairo_set_operator(aContext, CAIRO_OPERATOR_ADD); + cairo_paint_with_alpha(aContext, aOptions.mAlpha); + } + } else { + cairo_set_operator(aContext, GfxOpToCairoOp(aOptions.mCompositionOp)); + cairo_paint_with_alpha(aContext, aOptions.mAlpha); + } +} + +void DrawTargetCairo::DrawSurface(SourceSurface* aSurface, const Rect& aDest, + const Rect& aSource, + const DrawSurfaceOptions& aSurfOptions, + const DrawOptions& aOptions) { + if (mTransformSingular || aDest.IsEmpty()) { + return; + } + + if (!IsValid() || !aSurface) { + gfxCriticalNote << "DrawSurface with bad surface " + << cairo_surface_status(cairo_get_group_target(mContext)); + return; + } + + AutoPrepareForDrawing prep(this, mContext); + AutoClearDeviceOffset clear(aSurface); + + float sx = aSource.Width() / aDest.Width(); + float sy = aSource.Height() / aDest.Height(); + + cairo_matrix_t src_mat; + cairo_matrix_init_translate(&src_mat, aSource.X() - aSurface->GetRect().x, + aSource.Y() - aSurface->GetRect().y); + cairo_matrix_scale(&src_mat, sx, sy); + + cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aSurface); + if (!surf) { + gfxWarning() + << "Failed to create cairo surface for DrawTargetCairo::DrawSurface"; + return; + } + cairo_pattern_t* pat = cairo_pattern_create_for_surface(surf); + cairo_surface_destroy(surf); + + cairo_pattern_set_matrix(pat, &src_mat); + cairo_pattern_set_filter( + pat, GfxSamplingFilterToCairoFilter(aSurfOptions.mSamplingFilter)); + // For PDF output, we avoid using EXTEND_PAD here because floating-point + // error accumulation may lead cairo_pdf_surface to conclude that padding + // is needed due to an apparent one- or two-pixel mismatch between source + // pattern and destination rect sizes when we're rendering a pdf.js page, + // and this forces undesirable fallback to the rasterization codepath + // instead of simply replaying the recording. + // (See bug 1777209.) + cairo_pattern_set_extend( + pat, cairo_surface_get_type(mSurface) == CAIRO_SURFACE_TYPE_PDF + ? CAIRO_EXTEND_NONE + : CAIRO_EXTEND_PAD); + + cairo_set_antialias(mContext, + GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode)); + + // If the destination rect covers the entire clipped area, then unbounded and + // bounded operations are identical, and we don't need to push a group. + bool needsGroup = !IsOperatorBoundByMask(aOptions.mCompositionOp) && + !aDest.Contains(GetUserSpaceClip()); + + cairo_translate(mContext, aDest.X(), aDest.Y()); + + if (needsGroup) { + cairo_push_group(mContext); + cairo_new_path(mContext); + cairo_rectangle(mContext, 0, 0, aDest.Width(), aDest.Height()); + cairo_set_source(mContext, pat); + cairo_fill(mContext); + cairo_pop_group_to_source(mContext); + } else { + cairo_new_path(mContext); + cairo_rectangle(mContext, 0, 0, aDest.Width(), aDest.Height()); + cairo_clip(mContext); + cairo_set_source(mContext, pat); + } + + PaintWithAlpha(mContext, aOptions); + + cairo_pattern_destroy(pat); +} + +void DrawTargetCairo::DrawFilter(FilterNode* aNode, const Rect& aSourceRect, + const Point& aDestPoint, + const DrawOptions& aOptions) { + FilterNodeSoftware* filter = static_cast<FilterNodeSoftware*>(aNode); + filter->Draw(this, aSourceRect, aDestPoint, aOptions); +} + +void DrawTargetCairo::DrawSurfaceWithShadow(SourceSurface* aSurface, + const Point& aDest, + const ShadowOptions& aShadow, + CompositionOp aOperator) { + if (!IsValid() || !aSurface) { + gfxCriticalNote << "DrawSurfaceWithShadow with bad surface " + << cairo_surface_status(cairo_get_group_target(mContext)); + return; + } + + if (aSurface->GetType() != SurfaceType::CAIRO) { + return; + } + + AutoClearDeviceOffset clear(aSurface); + + Float width = Float(aSurface->GetSize().width); + Float height = Float(aSurface->GetSize().height); + + SourceSurfaceCairo* source = static_cast<SourceSurfaceCairo*>(aSurface); + cairo_surface_t* sourcesurf = source->GetSurface(); + cairo_surface_t* blursurf; + cairo_surface_t* surf; + + // We only use the A8 surface for blurred shadows. Unblurred shadows can just + // use the RGBA surface directly. + if (cairo_surface_get_type(sourcesurf) == CAIRO_SURFACE_TYPE_TEE) { + blursurf = cairo_tee_surface_index(sourcesurf, 0); + surf = cairo_tee_surface_index(sourcesurf, 1); + } else { + blursurf = sourcesurf; + surf = sourcesurf; + } + + if (aShadow.mSigma != 0.0f) { + MOZ_ASSERT(cairo_surface_get_type(blursurf) == CAIRO_SURFACE_TYPE_IMAGE); + Rect extents(0, 0, width, height); + AlphaBoxBlur blur(extents, cairo_image_surface_get_stride(blursurf), + aShadow.mSigma, aShadow.mSigma); + blur.Blur(cairo_image_surface_get_data(blursurf)); + } + + WillChange(); + ClearSurfaceForUnboundedSource(aOperator); + + cairo_save(mContext); + cairo_set_operator(mContext, GfxOpToCairoOp(aOperator)); + cairo_identity_matrix(mContext); + cairo_translate(mContext, aDest.x, aDest.y); + + bool needsGroup = !IsOperatorBoundByMask(aOperator); + if (needsGroup) { + cairo_push_group(mContext); + } + + cairo_set_source_rgba(mContext, aShadow.mColor.r, aShadow.mColor.g, + aShadow.mColor.b, aShadow.mColor.a); + cairo_mask_surface(mContext, blursurf, aShadow.mOffset.x, aShadow.mOffset.y); + + if (blursurf != surf || aSurface->GetFormat() != SurfaceFormat::A8) { + // Now that the shadow has been drawn, we can draw the surface on top. + cairo_set_source_surface(mContext, surf, 0, 0); + cairo_new_path(mContext); + cairo_rectangle(mContext, 0, 0, width, height); + cairo_fill(mContext); + } + + if (needsGroup) { + cairo_pop_group_to_source(mContext); + cairo_paint(mContext); + } + + cairo_restore(mContext); +} + +void DrawTargetCairo::DrawPattern(const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions, + DrawPatternType aDrawType, + bool aPathBoundsClip) { + if (!PatternIsCompatible(aPattern)) { + return; + } + + AutoClearDeviceOffset clear(aPattern); + + cairo_pattern_t* pat = + GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform()); + if (!pat) { + return; + } + if (cairo_pattern_status(pat)) { + cairo_pattern_destroy(pat); + gfxWarning() << "Invalid pattern"; + return; + } + + cairo_set_source(mContext, pat); + + cairo_set_antialias(mContext, + GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode)); + + if (NeedIntermediateSurface(aPattern, aOptions) || + (!IsOperatorBoundByMask(aOptions.mCompositionOp) && !aPathBoundsClip)) { + cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA); + + // Don't want operators to be applied twice + cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); + + if (aDrawType == DRAW_STROKE) { + SetCairoStrokeOptions(mContext, aStrokeOptions); + cairo_stroke_preserve(mContext); + } else { + cairo_fill_preserve(mContext); + } + + cairo_pop_group_to_source(mContext); + + // Now draw the content using the desired operator + PaintWithAlpha(mContext, aOptions); + } else { + cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp)); + + if (aDrawType == DRAW_STROKE) { + SetCairoStrokeOptions(mContext, aStrokeOptions); + cairo_stroke_preserve(mContext); + } else { + cairo_fill_preserve(mContext); + } + } + + cairo_pattern_destroy(pat); +} + +void DrawTargetCairo::FillRect(const Rect& aRect, const Pattern& aPattern, + const DrawOptions& aOptions) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext); + + bool restoreTransform = false; + Matrix mat; + Rect r = aRect; + + /* Clamp coordinates to work around a design bug in cairo */ + if (r.Width() > CAIRO_COORD_MAX || r.Height() > CAIRO_COORD_MAX || + r.X() < -CAIRO_COORD_MAX || r.X() > CAIRO_COORD_MAX || + r.Y() < -CAIRO_COORD_MAX || r.Y() > CAIRO_COORD_MAX) { + if (!mat.IsRectilinear()) { + gfxWarning() << "DrawTargetCairo::FillRect() misdrawing huge Rect " + "with non-rectilinear transform"; + } + + mat = GetTransform(); + r = mat.TransformBounds(r); + + if (!ConditionRect(r)) { + gfxWarning() << "Ignoring DrawTargetCairo::FillRect() call with " + "out-of-bounds Rect"; + return; + } + + restoreTransform = true; + SetTransform(Matrix()); + } + + cairo_new_path(mContext); + cairo_rectangle(mContext, r.X(), r.Y(), r.Width(), r.Height()); + + bool pathBoundsClip = false; + + if (r.Contains(GetUserSpaceClip())) { + pathBoundsClip = true; + } + + DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL, pathBoundsClip); + + if (restoreTransform) { + SetTransform(mat); + } +} + +void DrawTargetCairo::CopySurfaceInternal(cairo_surface_t* aSurface, + const IntRect& aSource, + const IntPoint& aDest) { + if (cairo_surface_status(aSurface)) { + gfxWarning() << "Invalid surface" << cairo_surface_status(aSurface); + return; + } + + cairo_identity_matrix(mContext); + + cairo_set_source_surface(mContext, aSurface, aDest.x - aSource.X(), + aDest.y - aSource.Y()); + cairo_set_operator(mContext, CAIRO_OPERATOR_SOURCE); + cairo_set_antialias(mContext, CAIRO_ANTIALIAS_NONE); + + cairo_reset_clip(mContext); + cairo_new_path(mContext); + cairo_rectangle(mContext, aDest.x, aDest.y, aSource.Width(), + aSource.Height()); + cairo_fill(mContext); +} + +void DrawTargetCairo::CopySurface(SourceSurface* aSurface, + const IntRect& aSource, + const IntPoint& aDest) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext); + AutoClearDeviceOffset clear(aSurface); + + if (!aSurface) { + gfxWarning() << "Unsupported surface type specified"; + return; + } + + cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aSurface); + if (!surf) { + gfxWarning() << "Unsupported surface type specified"; + return; + } + + CopySurfaceInternal(surf, aSource, aDest); + cairo_surface_destroy(surf); +} + +void DrawTargetCairo::CopyRect(const IntRect& aSource, const IntPoint& aDest) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext); + + IntRect source = aSource; + cairo_surface_t* surf = mSurface; + + if (!SupportsSelfCopy(mSurface) && aSource.ContainsY(aDest.y)) { + cairo_surface_t* similar = cairo_surface_create_similar( + mSurface, GfxFormatToCairoContent(GetFormat()), aSource.Width(), + aSource.Height()); + cairo_t* ctx = cairo_create(similar); + cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface(ctx, surf, -aSource.X(), -aSource.Y()); + cairo_paint(ctx); + cairo_destroy(ctx); + + source.MoveTo(0, 0); + surf = similar; + } + + CopySurfaceInternal(surf, source, aDest); + + if (surf != mSurface) { + cairo_surface_destroy(surf); + } +} + +void DrawTargetCairo::ClearRect(const Rect& aRect) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext); + + if (!mContext || aRect.Width() < 0 || aRect.Height() < 0 || + !std::isfinite(aRect.X()) || !std::isfinite(aRect.Width()) || + !std::isfinite(aRect.Y()) || !std::isfinite(aRect.Height())) { + gfxCriticalNote << "ClearRect with invalid argument " << gfx::hexa(mContext) + << " with " << aRect.Width() << "x" << aRect.Height() + << " [" << aRect.X() << ", " << aRect.Y() << "]"; + } + + cairo_set_antialias(mContext, CAIRO_ANTIALIAS_NONE); + cairo_new_path(mContext); + cairo_set_operator(mContext, CAIRO_OPERATOR_CLEAR); + cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(), + aRect.Height()); + cairo_fill(mContext); +} + +void DrawTargetCairo::StrokeRect( + const Rect& aRect, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions /* = StrokeOptions() */, + const DrawOptions& aOptions /* = DrawOptions() */) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext); + + cairo_new_path(mContext); + cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(), + aRect.Height()); + + DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE); +} + +void DrawTargetCairo::StrokeLine( + const Point& aStart, const Point& aEnd, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions /* = StrokeOptions() */, + const DrawOptions& aOptions /* = DrawOptions() */) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext); + + cairo_new_path(mContext); + cairo_move_to(mContext, aStart.x, aStart.y); + cairo_line_to(mContext, aEnd.x, aEnd.y); + + DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE); +} + +void DrawTargetCairo::Stroke( + const Path* aPath, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions /* = StrokeOptions() */, + const DrawOptions& aOptions /* = DrawOptions() */) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext, aPath); + + if (aPath->GetBackendType() != BackendType::CAIRO) return; + + PathCairo* path = + const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath)); + path->SetPathOnContext(mContext); + + DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE); +} + +void DrawTargetCairo::Fill(const Path* aPath, const Pattern& aPattern, + const DrawOptions& aOptions /* = DrawOptions() */) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext, aPath); + + if (aPath->GetBackendType() != BackendType::CAIRO) return; + + PathCairo* path = + const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath)); + path->SetPathOnContext(mContext); + + DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL); +} + +bool DrawTargetCairo::IsCurrentGroupOpaque() { + cairo_surface_t* surf = cairo_get_group_target(mContext); + + if (!surf) { + return false; + } + + return cairo_surface_get_content(surf) == CAIRO_CONTENT_COLOR; +} + +void DrawTargetCairo::SetFontOptions(cairo_antialias_t aAAMode) { + // This will attempt to detect if the currently set scaled font on the + // context has enabled subpixel AA. If it is not permitted, then it will + // downgrade to grayscale AA. + // This only currently works effectively for the cairo-ft backend relative + // to system defaults, as only cairo-ft reflect system defaults in the scaled + // font state. However, this will work for cairo-ft on both tree Cairo and + // system Cairo. + // Other backends leave the CAIRO_ANTIALIAS_DEFAULT setting untouched while + // potentially interpreting it as subpixel or even other types of AA that + // can't be safely equivocated with grayscale AA. For this reason we don't + // try to also detect and modify the default AA setting, only explicit + // subpixel AA. These other backends must instead rely on tree Cairo's + // cairo_surface_set_subpixel_antialiasing extension. + + // If allowing subpixel AA, then leave Cairo's default AA state. + if (mPermitSubpixelAA && aAAMode == CAIRO_ANTIALIAS_DEFAULT) { + return; + } + + if (!mFontOptions) { + mFontOptions = cairo_font_options_create(); + if (!mFontOptions) { + gfxWarning() << "Failed allocating Cairo font options"; + return; + } + } + + cairo_get_font_options(mContext, mFontOptions); + cairo_antialias_t oldAA = cairo_font_options_get_antialias(mFontOptions); + cairo_antialias_t newAA = + aAAMode == CAIRO_ANTIALIAS_DEFAULT ? oldAA : aAAMode; + // Nothing to change if switching to default AA. + if (newAA == CAIRO_ANTIALIAS_DEFAULT) { + return; + } + // If the current font requests subpixel AA, force it to gray since we don't + // allow subpixel AA. + if (!mPermitSubpixelAA && newAA == CAIRO_ANTIALIAS_SUBPIXEL) { + newAA = CAIRO_ANTIALIAS_GRAY; + } + // Only override old AA with lower levels of AA. + if (oldAA == CAIRO_ANTIALIAS_DEFAULT || (int)newAA < (int)oldAA) { + cairo_font_options_set_antialias(mFontOptions, newAA); + cairo_set_font_options(mContext, mFontOptions); + } +} + +void DrawTargetCairo::SetPermitSubpixelAA(bool aPermitSubpixelAA) { + DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA); + cairo_surface_set_subpixel_antialiasing( + cairo_get_group_target(mContext), + aPermitSubpixelAA ? CAIRO_SUBPIXEL_ANTIALIASING_ENABLED + : CAIRO_SUBPIXEL_ANTIALIASING_DISABLED); +} + +static bool SupportsVariationSettings(cairo_surface_t* surface) { + switch (cairo_surface_get_type(surface)) { + case CAIRO_SURFACE_TYPE_PDF: + case CAIRO_SURFACE_TYPE_PS: + return false; + default: + return true; + } +} + +void DrawTargetCairo::FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const DrawOptions& aOptions) { + if (mTransformSingular) { + return; + } + + if (!IsValid()) { + gfxDebug() << "FillGlyphs bad surface " + << cairo_surface_status(cairo_get_group_target(mContext)); + return; + } + + cairo_scaled_font_t* cairoScaledFont = + aFont ? aFont->GetCairoScaledFont() : nullptr; + if (!cairoScaledFont) { + gfxDevCrash(LogReason::InvalidFont) << "Invalid scaled font"; + return; + } + + AutoPrepareForDrawing prep(this, mContext); + AutoClearDeviceOffset clear(aPattern); + + cairo_set_scaled_font(mContext, cairoScaledFont); + + cairo_pattern_t* pat = + GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform()); + if (!pat) return; + + cairo_set_source(mContext, pat); + cairo_pattern_destroy(pat); + + cairo_antialias_t aa = GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode); + cairo_set_antialias(mContext, aa); + + // Override any font-specific options as necessary. + SetFontOptions(aa); + + // Convert our GlyphBuffer into a vector of Cairo glyphs. This code can + // execute millions of times in short periods, so we want to avoid heap + // allocation whenever possible. So we use an inline vector capacity of 1024 + // bytes (the maximum allowed by mozilla::Vector), which gives an inline + // length of 1024 / 24 = 42 elements, which is enough to typically avoid heap + // allocation in ~99% of cases. + Vector<cairo_glyph_t, 1024 / sizeof(cairo_glyph_t)> glyphs; + if (!glyphs.resizeUninitialized(aBuffer.mNumGlyphs)) { + gfxDevCrash(LogReason::GlyphAllocFailedCairo) << "glyphs allocation failed"; + return; + } + for (uint32_t i = 0; i < aBuffer.mNumGlyphs; ++i) { + glyphs[i].index = aBuffer.mGlyphs[i].mIndex; + glyphs[i].x = aBuffer.mGlyphs[i].mPosition.x; + glyphs[i].y = aBuffer.mGlyphs[i].mPosition.y; + } + + if (!SupportsVariationSettings(mSurface) && aFont->HasVariationSettings() && + StaticPrefs::print_font_variations_as_paths()) { + cairo_set_fill_rule(mContext, CAIRO_FILL_RULE_WINDING); + cairo_new_path(mContext); + cairo_glyph_path(mContext, &glyphs[0], aBuffer.mNumGlyphs); + cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); + cairo_fill(mContext); + } else { + cairo_show_glyphs(mContext, &glyphs[0], aBuffer.mNumGlyphs); + } + + if (cairo_surface_status(cairo_get_group_target(mContext))) { + gfxDebug() << "Ending FillGlyphs with a bad surface " + << cairo_surface_status(cairo_get_group_target(mContext)); + } +} + +void DrawTargetCairo::Mask(const Pattern& aSource, const Pattern& aMask, + const DrawOptions& aOptions /* = DrawOptions() */) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext); + AutoClearDeviceOffset clearSource(aSource); + AutoClearDeviceOffset clearMask(aMask); + + cairo_set_antialias(mContext, + GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode)); + + cairo_pattern_t* source = + GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform()); + if (!source) { + return; + } + + cairo_pattern_t* mask = + GfxPatternToCairoPattern(aMask, aOptions.mAlpha, GetTransform()); + if (!mask) { + cairo_pattern_destroy(source); + return; + } + + if (cairo_pattern_status(source) || cairo_pattern_status(mask)) { + cairo_pattern_destroy(source); + cairo_pattern_destroy(mask); + gfxWarning() << "Invalid pattern"; + return; + } + + cairo_set_source(mContext, source); + cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp)); + cairo_mask(mContext, mask); + + cairo_pattern_destroy(mask); + cairo_pattern_destroy(source); +} + +void DrawTargetCairo::MaskSurface(const Pattern& aSource, SourceSurface* aMask, + Point aOffset, const DrawOptions& aOptions) { + if (mTransformSingular) { + return; + } + + AutoPrepareForDrawing prep(this, mContext); + AutoClearDeviceOffset clearSource(aSource); + AutoClearDeviceOffset clearMask(aMask); + + if (!PatternIsCompatible(aSource)) { + return; + } + + cairo_set_antialias(mContext, + GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode)); + + cairo_pattern_t* pat = + GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform()); + if (!pat) { + return; + } + + if (cairo_pattern_status(pat)) { + cairo_pattern_destroy(pat); + gfxWarning() << "Invalid pattern"; + return; + } + + cairo_set_source(mContext, pat); + + if (NeedIntermediateSurface(aSource, aOptions)) { + cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA); + + // Don't want operators to be applied twice + cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); + + // Now draw the content using the desired operator + cairo_paint_with_alpha(mContext, aOptions.mAlpha); + + cairo_pop_group_to_source(mContext); + } + + cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aMask); + if (!surf) { + cairo_pattern_destroy(pat); + return; + } + cairo_pattern_t* mask = cairo_pattern_create_for_surface(surf); + cairo_matrix_t matrix; + + cairo_matrix_init_translate(&matrix, -aOffset.x - aMask->GetRect().x, + -aOffset.y - aMask->GetRect().y); + cairo_pattern_set_matrix(mask, &matrix); + + cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp)); + + cairo_mask(mContext, mask); + + cairo_surface_destroy(surf); + cairo_pattern_destroy(mask); + cairo_pattern_destroy(pat); +} + +void DrawTargetCairo::PushClip(const Path* aPath) { + if (aPath->GetBackendType() != BackendType::CAIRO) { + return; + } + + WillChange(aPath); + cairo_save(mContext); + + PathCairo* path = + const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath)); + + if (mTransformSingular) { + cairo_new_path(mContext); + cairo_rectangle(mContext, 0, 0, 0, 0); + } else { + path->SetPathOnContext(mContext); + } + cairo_clip_preserve(mContext); +} + +void DrawTargetCairo::PushClipRect(const Rect& aRect) { + WillChange(); + cairo_save(mContext); + + cairo_new_path(mContext); + if (mTransformSingular) { + cairo_rectangle(mContext, 0, 0, 0, 0); + } else { + cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(), + aRect.Height()); + } + cairo_clip_preserve(mContext); +} + +void DrawTargetCairo::PopClip() { + // save/restore does not affect the path, so no need to call WillChange() + + // cairo_restore will restore the transform too and we don't want to do that + // so we'll save it now and restore it after the cairo_restore + cairo_matrix_t mat; + cairo_get_matrix(mContext, &mat); + + cairo_restore(mContext); + + cairo_set_matrix(mContext, &mat); +} + +void DrawTargetCairo::PushLayer(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds, bool aCopyBackground) { + PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform, aBounds, + aCopyBackground, CompositionOp::OP_OVER); +} + +void DrawTargetCairo::PushLayerWithBlend(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds, + bool aCopyBackground, + CompositionOp aCompositionOp) { + cairo_content_t content = CAIRO_CONTENT_COLOR_ALPHA; + + if (mFormat == SurfaceFormat::A8) { + content = CAIRO_CONTENT_ALPHA; + } else if (aOpaque) { + content = CAIRO_CONTENT_COLOR; + } + + if (aCopyBackground) { + cairo_surface_t* source = cairo_get_group_target(mContext); + cairo_push_group_with_content(mContext, content); + cairo_surface_t* dest = cairo_get_group_target(mContext); + cairo_t* ctx = cairo_create(dest); + cairo_set_source_surface(ctx, source, 0, 0); + cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE); + cairo_paint(ctx); + cairo_destroy(ctx); + } else { + cairo_push_group_with_content(mContext, content); + } + + PushedLayer layer(aOpacity, aCompositionOp, mPermitSubpixelAA); + + if (aMask) { + cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aMask); + if (surf) { + layer.mMaskPattern = cairo_pattern_create_for_surface(surf); + Matrix maskTransform = aMaskTransform; + maskTransform.PreTranslate(aMask->GetRect().X(), aMask->GetRect().Y()); + cairo_matrix_t mat; + GfxMatrixToCairoMatrix(maskTransform, mat); + cairo_matrix_invert(&mat); + cairo_pattern_set_matrix(layer.mMaskPattern, &mat); + cairo_surface_destroy(surf); + } else { + gfxCriticalError() << "Failed to get cairo surface for mask surface!"; + } + } + + mPushedLayers.push_back(layer); + + SetPermitSubpixelAA(aOpaque); +} + +void DrawTargetCairo::PopLayer() { + MOZ_RELEASE_ASSERT(!mPushedLayers.empty()); + + cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); + + cairo_pop_group_to_source(mContext); + + PushedLayer layer = mPushedLayers.back(); + mPushedLayers.pop_back(); + + if (!layer.mMaskPattern) { + cairo_set_operator(mContext, GfxOpToCairoOp(layer.mCompositionOp)); + cairo_paint_with_alpha(mContext, layer.mOpacity); + } else { + if (layer.mOpacity != Float(1.0)) { + cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA); + + // Now draw the content using the desired operator + cairo_paint_with_alpha(mContext, layer.mOpacity); + + cairo_pop_group_to_source(mContext); + } + cairo_set_operator(mContext, GfxOpToCairoOp(layer.mCompositionOp)); + cairo_mask(mContext, layer.mMaskPattern); + } + + cairo_matrix_t mat; + GfxMatrixToCairoMatrix(mTransform, mat); + cairo_set_matrix(mContext, &mat); + + cairo_set_operator(mContext, CAIRO_OPERATOR_OVER); + + cairo_pattern_destroy(layer.mMaskPattern); + SetPermitSubpixelAA(layer.mWasPermittingSubpixelAA); +} + +void DrawTargetCairo::ClearSurfaceForUnboundedSource( + const CompositionOp& aOperator) { + if (aOperator != CompositionOp::OP_SOURCE) return; + cairo_set_operator(mContext, CAIRO_OPERATOR_CLEAR); + // It doesn't really matter what the source is here, since Paint + // isn't bounded by the source and the mask covers the entire clip + // region. + cairo_paint(mContext); +} + +already_AddRefed<GradientStops> DrawTargetCairo::CreateGradientStops( + GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode) const { + return MakeAndAddRef<GradientStopsCairo>(aStops, aNumStops, aExtendMode); +} + +already_AddRefed<FilterNode> DrawTargetCairo::CreateFilter(FilterType aType) { + return FilterNodeSoftware::Create(aType); +} + +already_AddRefed<SourceSurface> DrawTargetCairo::CreateSourceSurfaceFromData( + unsigned char* aData, const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat) const { + if (!aData) { + gfxWarning() << "DrawTargetCairo::CreateSourceSurfaceFromData null aData"; + return nullptr; + } + + cairo_surface_t* surf = + CopyToImageSurface(aData, IntRect(IntPoint(), aSize), aStride, aFormat); + if (!surf) { + return nullptr; + } + + RefPtr<SourceSurfaceCairo> source_surf = + new SourceSurfaceCairo(surf, aSize, aFormat); + cairo_surface_destroy(surf); + + return source_surf.forget(); +} + +already_AddRefed<SourceSurface> DrawTargetCairo::OptimizeSourceSurface( + SourceSurface* aSurface) const { + RefPtr<SourceSurface> surface(aSurface); + return surface.forget(); +} + +already_AddRefed<SourceSurface> +DrawTargetCairo::CreateSourceSurfaceFromNativeSurface( + const NativeSurface& aSurface) const { + return nullptr; +} + +already_AddRefed<DrawTarget> DrawTargetCairo::CreateSimilarDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat) const { + if (cairo_surface_status(cairo_get_group_target(mContext))) { + RefPtr<DrawTargetCairo> target = new DrawTargetCairo(); + if (target->Init(aSize, aFormat)) { + return target.forget(); + } + } + + cairo_surface_t* similar; + switch (cairo_surface_get_type(mSurface)) { +#ifdef CAIRO_HAS_WIN32_SURFACE + case CAIRO_SURFACE_TYPE_WIN32: + similar = cairo_win32_surface_create_with_dib( + GfxFormatToCairoFormat(aFormat), aSize.width, aSize.height); + break; +#endif +#ifdef CAIRO_HAS_QUARTZ_SURFACE + case CAIRO_SURFACE_TYPE_QUARTZ: + if (StaticPrefs::gfx_cairo_quartz_cg_layer_enabled()) { + similar = cairo_quartz_surface_create_cg_layer( + mSurface, GfxFormatToCairoContent(aFormat), aSize.width, + aSize.height); + break; + } + [[fallthrough]]; +#endif + default: + similar = cairo_surface_create_similar(mSurface, + GfxFormatToCairoContent(aFormat), + aSize.width, aSize.height); + break; + } + + if (!cairo_surface_status(similar)) { + RefPtr<DrawTargetCairo> target = new DrawTargetCairo(); + if (target->InitAlreadyReferenced(similar, aSize)) { + return target.forget(); + } + } + + gfxCriticalError( + CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(aSize))) + << "Failed to create similar cairo surface! Size: " << aSize + << " Status: " << cairo_surface_status(similar) + << cairo_surface_status(cairo_get_group_target(mContext)) << " format " + << (int)aFormat; + cairo_surface_destroy(similar); + + return nullptr; +} + +RefPtr<DrawTarget> DrawTargetCairo::CreateClippedDrawTarget( + const Rect& aBounds, SurfaceFormat aFormat) { + RefPtr<DrawTarget> result; + // Doing this save()/restore() dance is wasteful + cairo_save(mContext); + + if (!aBounds.IsEmpty()) { + cairo_new_path(mContext); + cairo_rectangle(mContext, aBounds.X(), aBounds.Y(), aBounds.Width(), + aBounds.Height()); + cairo_clip(mContext); + } + cairo_identity_matrix(mContext); + IntRect clipBounds = IntRect::RoundOut(GetUserSpaceClip()); + if (!clipBounds.IsEmpty()) { + RefPtr<DrawTarget> dt = CreateSimilarDrawTarget( + IntSize(clipBounds.width, clipBounds.height), aFormat); + if (dt) { + result = gfx::Factory::CreateOffsetDrawTarget( + dt, IntPoint(clipBounds.x, clipBounds.y)); + if (result) { + result->SetTransform(mTransform); + } + } + } else { + // Everything is clipped but we still want some kind of surface + result = CreateSimilarDrawTarget(IntSize(1, 1), aFormat); + } + + cairo_restore(mContext); + return result; +} +bool DrawTargetCairo::InitAlreadyReferenced(cairo_surface_t* aSurface, + const IntSize& aSize, + SurfaceFormat* aFormat) { + if (cairo_surface_status(aSurface)) { + gfxCriticalNote << "Attempt to create DrawTarget for invalid surface. " + << aSize + << " Cairo Status: " << cairo_surface_status(aSurface); + cairo_surface_destroy(aSurface); + return false; + } + + mContext = cairo_create(aSurface); + mSurface = aSurface; + mSize = aSize; + mFormat = aFormat ? *aFormat : GfxFormatForCairoSurface(aSurface); + + // Cairo image surface have a bug where they will allocate a mask surface (for + // clipping) the size of the clip extents, and don't take the surface extents + // into account. Add a manual clip to the surface extents to prevent this. + cairo_new_path(mContext); + cairo_rectangle(mContext, 0, 0, mSize.width, mSize.height); + cairo_clip(mContext); + + if (mFormat == SurfaceFormat::A8R8G8B8_UINT32 || + mFormat == SurfaceFormat::R8G8B8A8) { + SetPermitSubpixelAA(false); + } else { + SetPermitSubpixelAA(true); + } + + return true; +} + +already_AddRefed<DrawTarget> DrawTargetCairo::CreateShadowDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat, float aSigma) const { + cairo_surface_t* similar = cairo_surface_create_similar( + cairo_get_target(mContext), GfxFormatToCairoContent(aFormat), aSize.width, + aSize.height); + + if (cairo_surface_status(similar)) { + return nullptr; + } + + // If we don't have a blur then we can use the RGBA mask and keep all the + // operations in graphics memory. + if (aSigma == 0.0f || aFormat == SurfaceFormat::A8) { + RefPtr<DrawTargetCairo> target = new DrawTargetCairo(); + if (target->InitAlreadyReferenced(similar, aSize)) { + return target.forget(); + } else { + return nullptr; + } + } + + cairo_surface_t* blursurf = + cairo_image_surface_create(CAIRO_FORMAT_A8, aSize.width, aSize.height); + + if (cairo_surface_status(blursurf)) { + return nullptr; + } + + cairo_surface_t* tee = cairo_tee_surface_create(blursurf); + cairo_surface_destroy(blursurf); + if (cairo_surface_status(tee)) { + cairo_surface_destroy(similar); + return nullptr; + } + + cairo_tee_surface_add(tee, similar); + cairo_surface_destroy(similar); + + RefPtr<DrawTargetCairo> target = new DrawTargetCairo(); + if (target->InitAlreadyReferenced(tee, aSize)) { + return target.forget(); + } + return nullptr; +} + +bool DrawTargetCairo::Draw3DTransformedSurface(SourceSurface* aSurface, + const Matrix4x4& aMatrix) { + return DrawTarget::Draw3DTransformedSurface(aSurface, aMatrix); +} + +bool DrawTargetCairo::Init(cairo_surface_t* aSurface, const IntSize& aSize, + SurfaceFormat* aFormat) { + cairo_surface_reference(aSurface); + return InitAlreadyReferenced(aSurface, aSize, aFormat); +} + +bool DrawTargetCairo::Init(const IntSize& aSize, SurfaceFormat aFormat) { + cairo_surface_t* surf = cairo_image_surface_create( + GfxFormatToCairoFormat(aFormat), aSize.width, aSize.height); + return InitAlreadyReferenced(surf, aSize); +} + +bool DrawTargetCairo::Init(unsigned char* aData, const IntSize& aSize, + int32_t aStride, SurfaceFormat aFormat) { + cairo_surface_t* surf = cairo_image_surface_create_for_data( + aData, GfxFormatToCairoFormat(aFormat), aSize.width, aSize.height, + aStride); + return InitAlreadyReferenced(surf, aSize); +} + +void* DrawTargetCairo::GetNativeSurface(NativeSurfaceType aType) { + if (aType == NativeSurfaceType::CAIRO_CONTEXT) { + return mContext; + } + + return nullptr; +} + +void DrawTargetCairo::MarkSnapshotIndependent() { + if (mSnapshot) { + if (mSnapshot->refCount() > 1) { + // We only need to worry about snapshots that someone else knows about + mSnapshot->DrawTargetWillChange(); + } + mSnapshot = nullptr; + } +} + +void DrawTargetCairo::WillChange(const Path* aPath /* = nullptr */) { + MarkSnapshotIndependent(); + MOZ_ASSERT(!mLockedBits); +} + +void DrawTargetCairo::SetTransform(const Matrix& aTransform) { + DrawTarget::SetTransform(aTransform); + + mTransformSingular = aTransform.IsSingular(); + if (!mTransformSingular) { + cairo_matrix_t mat; + GfxMatrixToCairoMatrix(mTransform, mat); + cairo_set_matrix(mContext, &mat); + } +} + +Rect DrawTargetCairo::GetUserSpaceClip() const { + double clipX1, clipY1, clipX2, clipY2; + cairo_clip_extents(mContext, &clipX1, &clipY1, &clipX2, &clipY2); + return Rect(clipX1, clipY1, clipX2 - clipX1, + clipY2 - clipY1); // Narrowing of doubles to floats +} + +#ifdef MOZ_X11 +bool BorrowedXlibDrawable::Init(DrawTarget* aDT) { + MOZ_ASSERT(aDT, "Caller should check for nullptr"); + MOZ_ASSERT(!mDT, "Can't initialize twice!"); + mDT = aDT; + mDrawable = X11None; + +# ifdef CAIRO_HAS_XLIB_SURFACE + if (aDT->GetBackendType() != BackendType::CAIRO || aDT->IsTiledDrawTarget()) { + return false; + } + + DrawTargetCairo* cairoDT = static_cast<DrawTargetCairo*>(aDT); + cairo_surface_t* surf = cairo_get_group_target(cairoDT->mContext); + if (cairo_surface_get_type(surf) != CAIRO_SURFACE_TYPE_XLIB) { + return false; + } + cairo_surface_flush(surf); + + cairoDT->WillChange(); + + mDisplay = cairo_xlib_surface_get_display(surf); + mDrawable = cairo_xlib_surface_get_drawable(surf); + mScreen = cairo_xlib_surface_get_screen(surf); + mVisual = cairo_xlib_surface_get_visual(surf); + mSize.width = cairo_xlib_surface_get_width(surf); + mSize.height = cairo_xlib_surface_get_height(surf); + + double x = 0, y = 0; + cairo_surface_get_device_offset(surf, &x, &y); + mOffset = Point(x, y); + + return true; +# else + return false; +# endif +} + +void BorrowedXlibDrawable::Finish() { + DrawTargetCairo* cairoDT = static_cast<DrawTargetCairo*>(mDT); + cairo_surface_t* surf = cairo_get_group_target(cairoDT->mContext); + cairo_surface_mark_dirty(surf); + if (mDrawable) { + mDrawable = X11None; + } +} +#endif + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/DrawTargetCairo.h b/gfx/2d/DrawTargetCairo.h new file mode 100644 index 0000000000..8de89f9397 --- /dev/null +++ b/gfx/2d/DrawTargetCairo.h @@ -0,0 +1,252 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_DRAWTARGET_CAIRO_H_ +#define _MOZILLA_GFX_DRAWTARGET_CAIRO_H_ + +#include "2D.h" +#include "cairo.h" +#include "PathCairo.h" + +#include <vector> + +namespace mozilla { +namespace gfx { + +class SourceSurfaceCairo; + +class GradientStopsCairo : public GradientStops { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsCairo, override) + + GradientStopsCairo(GradientStop* aStops, uint32_t aNumStops, + ExtendMode aExtendMode) + : mExtendMode(aExtendMode) { + for (uint32_t i = 0; i < aNumStops; ++i) { + mStops.push_back(aStops[i]); + } + } + + virtual ~GradientStopsCairo() = default; + + const std::vector<GradientStop>& GetStops() const { return mStops; } + + ExtendMode GetExtendMode() const { return mExtendMode; } + + virtual BackendType GetBackendType() const override { + return BackendType::CAIRO; + } + + private: + std::vector<GradientStop> mStops; + ExtendMode mExtendMode; +}; + +class DrawTargetCairo final : public DrawTarget { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetCairo, override) + friend class BorrowedXlibDrawable; + + DrawTargetCairo(); + virtual ~DrawTargetCairo(); + + virtual bool IsValid() const override; + virtual DrawTargetType GetType() const override; + virtual BackendType GetBackendType() const override { + return BackendType::CAIRO; + } + + virtual void Link(const char* aDestination, const Rect& aRect) override; + virtual void Destination(const char* aDestination, + const Point& aPoint) override; + + virtual already_AddRefed<SourceSurface> Snapshot() override; + virtual IntSize GetSize() const override; + + virtual bool IsCurrentGroupOpaque() override; + + virtual void SetPermitSubpixelAA(bool aPermitSubpixelAA) override; + + virtual bool LockBits(uint8_t** aData, IntSize* aSize, int32_t* aStride, + SurfaceFormat* aFormat, + IntPoint* aOrigin = nullptr) override; + virtual void ReleaseBits(uint8_t* aData) override; + + virtual void Flush() override; + virtual void DrawSurface( + SourceSurface* aSurface, const Rect& aDest, const Rect& aSource, + const DrawSurfaceOptions& aSurfOptions = DrawSurfaceOptions(), + const DrawOptions& aOptions = DrawOptions()) override; + virtual void DrawFilter(FilterNode* aNode, const Rect& aSourceRect, + const Point& aDestPoint, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void DrawSurfaceWithShadow(SourceSurface* aSurface, + const Point& aDest, + const ShadowOptions& aShadow, + CompositionOp aOperator) override; + + virtual void ClearRect(const Rect& aRect) override; + + virtual void CopySurface(SourceSurface* aSurface, const IntRect& aSourceRect, + const IntPoint& aDestination) override; + virtual void CopyRect(const IntRect& aSourceRect, + const IntPoint& aDestination) override; + + virtual void FillRect(const Rect& aRect, const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void StrokeRect(const Rect& aRect, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()) override; + virtual void StrokeLine(const Point& aStart, const Point& aEnd, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()) override; + + virtual void Stroke(const Path* aPath, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()) override; + + virtual void Fill(const Path* aPath, const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()) override; + + virtual void FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const DrawOptions& aOptions) override; + virtual void Mask(const Pattern& aSource, const Pattern& aMask, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void MaskSurface( + const Pattern& aSource, SourceSurface* aMask, Point aOffset, + const DrawOptions& aOptions = DrawOptions()) override; + + virtual bool Draw3DTransformedSurface(SourceSurface* aSurface, + const Matrix4x4& aMatrix) override; + + virtual void PushClip(const Path* aPath) override; + virtual void PushClipRect(const Rect& aRect) override; + virtual void PopClip() override; + virtual void PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds = IntRect(), + bool aCopyBackground = false) override; + virtual void PushLayerWithBlend( + bool aOpaque, Float aOpacity, SourceSurface* aMask, + const Matrix& aMaskTransform, const IntRect& aBounds = IntRect(), + bool aCopyBackground = false, + CompositionOp = CompositionOp::OP_OVER) override; + virtual void PopLayer() override; + + virtual already_AddRefed<PathBuilder> CreatePathBuilder( + FillRule aFillRule = FillRule::FILL_WINDING) const override { + return PathBuilderCairo::Create(aFillRule); + } + + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData( + unsigned char* aData, const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat) const override; + virtual already_AddRefed<SourceSurface> OptimizeSourceSurface( + SourceSurface* aSurface) const override; + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromNativeSurface( + const NativeSurface& aSurface) const override; + virtual already_AddRefed<DrawTarget> CreateSimilarDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat) const override; + virtual already_AddRefed<DrawTarget> CreateShadowDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat, float aSigma) const override; + virtual RefPtr<DrawTarget> CreateClippedDrawTarget( + const Rect& aBounds, SurfaceFormat aFormat) override; + + virtual already_AddRefed<GradientStops> CreateGradientStops( + GradientStop* aStops, uint32_t aNumStops, + ExtendMode aExtendMode = ExtendMode::CLAMP) const override; + + virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override; + + virtual void* GetNativeSurface(NativeSurfaceType aType) override; + + bool Init(cairo_surface_t* aSurface, const IntSize& aSize, + SurfaceFormat* aFormat = nullptr); + bool Init(const IntSize& aSize, SurfaceFormat aFormat); + bool Init(unsigned char* aData, const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat); + + virtual void SetTransform(const Matrix& aTransform) override; + + virtual void DetachAllSnapshots() override { MarkSnapshotIndependent(); } + + // Call to set up aContext for drawing (with the current transform, etc). + // Pass the path you're going to be using if you have one. + // Implicitly calls WillChange(aPath). + void PrepareForDrawing(cairo_t* aContext, const Path* aPath = nullptr); + + static cairo_surface_t* GetDummySurface(); + + // Cairo hardcodes this as its maximum surface size. + static size_t GetMaxSurfaceSize() { return 32766; } + + private: // methods + // Init cairo surface without doing a cairo_surface_reference() call. + bool InitAlreadyReferenced(cairo_surface_t* aSurface, const IntSize& aSize, + SurfaceFormat* aFormat = nullptr); + enum DrawPatternType { DRAW_FILL, DRAW_STROKE }; + void DrawPattern(const Pattern& aPattern, const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions, DrawPatternType aDrawType, + bool aPathBoundsClip = false); + + void CopySurfaceInternal(cairo_surface_t* aSurface, const IntRect& aSource, + const IntPoint& aDest); + + Rect GetUserSpaceClip() const; + + // Call before you make any changes to the backing surface with which this + // context is associated. Pass the path you're going to be using if you have + // one. + void WillChange(const Path* aPath = nullptr); + + // Call if there is any reason to disassociate the snapshot from this draw + // target; for example, because we're going to be destroyed. + void MarkSnapshotIndependent(); + + // If the current operator is "source" then clear the destination before we + // draw into it, to simulate the effect of an unbounded source operator. + void ClearSurfaceForUnboundedSource(const CompositionOp& aOperator); + + // Set the Cairo context font options according to the current draw target + // font state. + void SetFontOptions(cairo_antialias_t aAAMode = CAIRO_ANTIALIAS_DEFAULT); + + private: // data + cairo_t* mContext; + cairo_surface_t* mSurface; + IntSize mSize; + bool mTransformSingular; + + uint8_t* mLockedBits; + + cairo_font_options_t* mFontOptions; + + struct PushedLayer { + PushedLayer(Float aOpacity, CompositionOp aCompositionOp, + bool aWasPermittingSubpixelAA) + : mOpacity(aOpacity), + mCompositionOp(aCompositionOp), + mMaskPattern(nullptr), + mWasPermittingSubpixelAA(aWasPermittingSubpixelAA) {} + Float mOpacity; + CompositionOp mCompositionOp; + cairo_pattern_t* mMaskPattern; + bool mWasPermittingSubpixelAA; + }; + std::vector<PushedLayer> mPushedLayers; + + // The latest snapshot of this surface. This needs to be told when this + // target is modified. We keep it alive as a cache. + RefPtr<SourceSurfaceCairo> mSnapshot; + static cairo_surface_t* mDummySurface; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // _MOZILLA_GFX_DRAWTARGET_CAIRO_H_ diff --git a/gfx/2d/DrawTargetD2D1.cpp b/gfx/2d/DrawTargetD2D1.cpp new file mode 100644 index 0000000000..e0bdd4d055 --- /dev/null +++ b/gfx/2d/DrawTargetD2D1.cpp @@ -0,0 +1,2422 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <optional> + +#include <initguid.h> +#include "DrawTargetD2D1.h" +#include "FilterNodeSoftware.h" +#include "GradientStopsD2D.h" +#include "SourceSurfaceD2D1.h" +#include "ConicGradientEffectD2D1.h" +#include "RadialGradientEffectD2D1.h" + +#include "HelpersD2D.h" +#include "FilterNodeD2D1.h" +#include "ExtendInputEffectD2D1.h" +#include "nsAppRunner.h" +#include "MainThreadUtils.h" + +#include "mozilla/Mutex.h" + +// decltype is not usable for overloaded functions. +typedef HRESULT(WINAPI* D2D1CreateFactoryFunc)( + D2D1_FACTORY_TYPE factoryType, REFIID iid, + CONST D2D1_FACTORY_OPTIONS* pFactoryOptions, void** factory); + +namespace mozilla { +namespace gfx { + +uint64_t DrawTargetD2D1::mVRAMUsageDT; +uint64_t DrawTargetD2D1::mVRAMUsageSS; +StaticRefPtr<ID2D1Factory1> DrawTargetD2D1::mFactory; + +const D2D1_MATRIX_5X4_F kLuminanceMatrix = + D2D1::Matrix5x4F(0, 0, 0, 0.2125f, 0, 0, 0, 0.7154f, 0, 0, 0, 0.0721f, 0, 0, + 0, 0, 0, 0, 0, 0); + +RefPtr<ID2D1Factory1> D2DFactory() { return DrawTargetD2D1::factory(); } + +DrawTargetD2D1::DrawTargetD2D1() + : mPushedLayers(1), + mSnapshotLock(std::make_shared<Mutex>("DrawTargetD2D1::mSnapshotLock")), + mUsedCommandListsSincePurge(0), + mTransformedGlyphsSinceLastPurge(0), + mComplexBlendsWithListInList(0), + mDeviceSeq(0), + mInitState(InitState::Uninitialized) {} + +DrawTargetD2D1::~DrawTargetD2D1() { + PopAllClips(); + + if (mSnapshot) { + MutexAutoLock lock(*mSnapshotLock); + // We may hold the only reference. MarkIndependent will clear mSnapshot; + // keep the snapshot object alive so it doesn't get destroyed while + // MarkIndependent is running. + RefPtr<SourceSurfaceD2D1> deathGrip = mSnapshot; + // mSnapshot can be treated as independent of this DrawTarget since we know + // this DrawTarget won't change again. + deathGrip->MarkIndependent(); + // mSnapshot will be cleared now. + } + + if (mDC && IsDeviceContextValid()) { + // The only way mDC can be null is if Init failed, but it can happen and the + // destructor is the only place where we need to check for it since the + // DrawTarget will destroyed right after Init fails. + mDC->EndDraw(); + } + + { + // Until this point in the destructor it -must- still be valid for + // FlushInternal to be called on this. + StaticMutexAutoLock lock(Factory::mDTDependencyLock); + // Targets depending on us can break that dependency, since we're obviously + // not going to be modified in the future. + for (auto iter = mDependentTargets.begin(); iter != mDependentTargets.end(); + iter++) { + (*iter)->mDependingOnTargets.erase(this); + } + // Our dependencies on other targets no longer matter. + for (TargetSet::iterator iter = mDependingOnTargets.begin(); + iter != mDependingOnTargets.end(); iter++) { + (*iter)->mDependentTargets.erase(this); + } + } +} + +bool DrawTargetD2D1::IsValid() const { + if (mInitState != InitState::Uninitialized && !IsDeviceContextValid()) { + return false; + } + if (NS_IsMainThread()) { + // Uninitialized DTs are considered valid. + return mInitState != InitState::Failure; + } else { + return const_cast<DrawTargetD2D1*>(this)->EnsureInitialized(); + } +} + +already_AddRefed<SourceSurface> DrawTargetD2D1::Snapshot() { + if (!EnsureInitialized()) { + return nullptr; + } + + MutexAutoLock lock(*mSnapshotLock); + if (mSnapshot) { + RefPtr<SourceSurface> snapshot(mSnapshot); + return snapshot.forget(); + } + PopAllClips(); + + Flush(); + + mSnapshot = new SourceSurfaceD2D1(mBitmap, mDC, mFormat, mSize, this); + + RefPtr<SourceSurface> snapshot(mSnapshot); + return snapshot.forget(); +} + +bool DrawTargetD2D1::EnsureLuminanceEffect() { + if (mLuminanceEffect.get()) { + return true; + } + + HRESULT hr = mDC->CreateEffect(CLSID_D2D1ColorMatrix, + getter_AddRefs(mLuminanceEffect)); + if (FAILED(hr)) { + gfxCriticalError() << "Failed to create luminance effect. Code: " + << hexa(hr); + return false; + } + + mLuminanceEffect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, + kLuminanceMatrix); + mLuminanceEffect->SetValue(D2D1_COLORMATRIX_PROP_ALPHA_MODE, + D2D1_COLORMATRIX_ALPHA_MODE_STRAIGHT); + return true; +} + +already_AddRefed<SourceSurface> DrawTargetD2D1::IntoLuminanceSource( + LuminanceType aLuminanceType, float aOpacity) { + if (!EnsureInitialized()) { + return nullptr; + } + if ((aLuminanceType != LuminanceType::LUMINANCE) || + // See bug 1372577, some race condition where we get invalid + // results with D2D in the parent process. Fallback in that case. + XRE_IsParentProcess()) { + return DrawTarget::IntoLuminanceSource(aLuminanceType, aOpacity); + } + + // Create the luminance effect + if (!EnsureLuminanceEffect()) { + return DrawTarget::IntoLuminanceSource(aLuminanceType, aOpacity); + } + + Flush(); + + { + D2D1_MATRIX_5X4_F matrix = kLuminanceMatrix; + matrix._14 *= aOpacity; + matrix._24 *= aOpacity; + matrix._34 *= aOpacity; + + mLuminanceEffect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, matrix); + } + + mLuminanceEffect->SetInput(0, mBitmap); + + RefPtr<ID2D1Image> luminanceOutput; + mLuminanceEffect->GetOutput(getter_AddRefs(luminanceOutput)); + + return MakeAndAddRef<SourceSurfaceD2D1>(luminanceOutput, mDC, + SurfaceFormat::B8G8R8A8, mSize); +} + +// Command lists are kept around by device contexts until EndDraw is called, +// this can cause issues with memory usage (see bug 1238328). EndDraw/BeginDraw +// are expensive though, especially relatively when little work is done, so +// we try to reduce the amount of times we execute these purges. +static const uint32_t kPushedLayersBeforePurge = 25; +// Rendering glyphs with different transforms causes the glyph cache to grow +// very large (see bug 1474883) so we must call EndDraw every so often. +static const uint32_t kTransformedGlyphsBeforePurge = 1000; + +void DrawTargetD2D1::Flush() { FlushInternal(); } + +bool DrawTargetD2D1::MaybeClearRect(CompositionOp aOp, const Rect& aBounds) { + if (aOp == CompositionOp::OP_CLEAR) { + FillRect(aBounds, ColorPattern(DeviceColor(1.0f, 1.0f, 1.0f, 1.0f)), + DrawOptions(1.0f, aOp)); + return true; + } + return false; +} + +void DrawTargetD2D1::DrawSurface(SourceSurface* aSurface, const Rect& aDest, + const Rect& aSource, + const DrawSurfaceOptions& aSurfOptions, + const DrawOptions& aOptions) { + if (MaybeClearRect(aOptions.mCompositionOp, aDest)) { + return; + } + + if (!PrepareForDrawing(aOptions.mCompositionOp, + ColorPattern(DeviceColor()))) { + return; + } + + Rect source = aSource - aSurface->GetRect().TopLeft(); + + D2D1_RECT_F samplingBounds; + + if (aSurfOptions.mSamplingBounds == SamplingBounds::BOUNDED) { + samplingBounds = D2DRect(source); + } else { + samplingBounds = D2D1::RectF(0, 0, Float(aSurface->GetSize().width), + Float(aSurface->GetSize().height)); + } + + Float xScale = aDest.Width() / source.Width(); + Float yScale = aDest.Height() / source.Height(); + + RefPtr<ID2D1ImageBrush> brush; + + // Here we scale the source pattern up to the size and position where we want + // it to be. + Matrix transform; + transform.PreTranslate(aDest.X() - source.X() * xScale, + aDest.Y() - source.Y() * yScale); + transform.PreScale(xScale, yScale); + + RefPtr<ID2D1Image> image = + GetImageForSurface(aSurface, transform, ExtendMode::CLAMP); + + if (!image) { + gfxWarning() << *this << ": Unable to get D2D image for surface."; + return; + } + + RefPtr<ID2D1Bitmap> bitmap; + HRESULT hr = E_FAIL; + if (aSurface->GetType() == SurfaceType::D2D1_1_IMAGE) { + // If this is called with a DataSourceSurface it might do a partial upload + // that our DrawBitmap call doesn't support. + hr = image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap)); + } + + if (SUCCEEDED(hr) && bitmap && + aSurfOptions.mSamplingBounds == SamplingBounds::UNBOUNDED) { + mDC->DrawBitmap(bitmap, D2DRect(aDest), aOptions.mAlpha, + D2DFilter(aSurfOptions.mSamplingFilter), D2DRect(source)); + } else { + // This has issues ignoring the alpha channel on windows 7 with images + // marked opaque. + MOZ_ASSERT(aSurface->GetFormat() != SurfaceFormat::B8G8R8X8); + + // Bug 1275478 - D2D1 cannot draw A8 surface correctly. + MOZ_ASSERT(aSurface->GetFormat() != SurfaceFormat::A8); + + mDC->CreateImageBrush( + image, + D2D1::ImageBrushProperties( + samplingBounds, D2D1_EXTEND_MODE_CLAMP, D2D1_EXTEND_MODE_CLAMP, + D2DInterpolationMode(aSurfOptions.mSamplingFilter)), + D2D1::BrushProperties(aOptions.mAlpha, D2DMatrix(transform)), + getter_AddRefs(brush)); + mDC->FillRectangle(D2DRect(aDest), brush); + } + + FinalizeDrawing(aOptions.mCompositionOp, ColorPattern(DeviceColor())); +} + +void DrawTargetD2D1::DrawFilter(FilterNode* aNode, const Rect& aSourceRect, + const Point& aDestPoint, + const DrawOptions& aOptions) { + if (aNode->GetBackendType() != FILTER_BACKEND_DIRECT2D1_1) { + gfxWarning() << *this << ": Incompatible filter passed to DrawFilter."; + return; + } + + if (MaybeClearRect(aOptions.mCompositionOp, + Rect(aDestPoint, aSourceRect.Size()))) { + return; + } + + if (!PrepareForDrawing(aOptions.mCompositionOp, + ColorPattern(DeviceColor()))) { + return; + } + + mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); + + FilterNodeD2D1* node = static_cast<FilterNodeD2D1*>(aNode); + node->WillDraw(this); + + if (aOptions.mAlpha == 1.0f) { + mDC->DrawImage(node->OutputEffect(), D2DPoint(aDestPoint), + D2DRect(aSourceRect)); + } else { + RefPtr<ID2D1Image> image; + node->OutputEffect()->GetOutput(getter_AddRefs(image)); + + Matrix mat = Matrix::Translation(aDestPoint); + + RefPtr<ID2D1ImageBrush> imageBrush; + mDC->CreateImageBrush( + image, D2D1::ImageBrushProperties(D2DRect(aSourceRect)), + D2D1::BrushProperties(aOptions.mAlpha, D2DMatrix(mat)), + getter_AddRefs(imageBrush)); + mDC->FillRectangle(D2D1::RectF(aDestPoint.x, aDestPoint.y, + aDestPoint.x + aSourceRect.width, + aDestPoint.y + aSourceRect.height), + imageBrush); + } + + FinalizeDrawing(aOptions.mCompositionOp, ColorPattern(DeviceColor())); +} + +void DrawTargetD2D1::DrawSurfaceWithShadow(SourceSurface* aSurface, + const Point& aDest, + const ShadowOptions& aShadow, + CompositionOp aOperator) { + if (!EnsureInitialized()) { + return; + } + + if (MaybeClearRect(aOperator, Rect(aDest, Size(aSurface->GetSize())))) { + return; + } + + MarkChanged(); + + Matrix mat; + RefPtr<ID2D1Image> image = + GetImageForSurface(aSurface, mat, ExtendMode::CLAMP, nullptr, false); + + if (!image) { + gfxWarning() << "Couldn't get image for surface."; + return; + } + + if (!mat.IsIdentity()) { + gfxDebug() << *this + << ": At this point complex partial uploads are not supported " + "for Shadow surfaces."; + return; + } + + if (!PrepareForDrawing(aOperator, ColorPattern(aShadow.mColor))) { + return; + } + + mDC->SetTransform(D2D1::IdentityMatrix()); + mTransformDirty = true; + + RefPtr<ID2D1Effect> shadowEffect; + HRESULT hr = mDC->CreateEffect( + mFormat == SurfaceFormat::A8 ? CLSID_D2D1GaussianBlur : CLSID_D2D1Shadow, + getter_AddRefs(shadowEffect)); + if (SUCCEEDED(hr) && shadowEffect) { + shadowEffect->SetInput(0, image); + if (mFormat == SurfaceFormat::A8) { + shadowEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, + aShadow.mSigma); + shadowEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_BORDER_MODE, + D2D1_BORDER_MODE_HARD); + } else { + shadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, + aShadow.mSigma); + D2D1_VECTOR_4F color = {aShadow.mColor.r, aShadow.mColor.g, + aShadow.mColor.b, aShadow.mColor.a}; + shadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, color); + } + + D2D1_POINT_2F shadowPoint = D2DPoint(aDest + aShadow.mOffset); + mDC->DrawImage(shadowEffect, &shadowPoint, nullptr, + D2D1_INTERPOLATION_MODE_LINEAR, + D2D1_COMPOSITE_MODE_SOURCE_OVER); + } else { + gfxWarning() << "Failed to create shadow effect. Code: " << hexa(hr); + } + + if (aSurface->GetFormat() != SurfaceFormat::A8) { + D2D1_POINT_2F imgPoint = D2DPoint(aDest); + mDC->DrawImage(image, &imgPoint, nullptr, D2D1_INTERPOLATION_MODE_LINEAR, + D2D1_COMPOSITE_MODE_SOURCE_OVER); + } + + FinalizeDrawing(aOperator, ColorPattern(aShadow.mColor)); +} + +void DrawTargetD2D1::ClearRect(const Rect& aRect) { + if (!EnsureInitialized()) { + return; + } + + if (aRect.IsEmpty()) { + // Nothing to be done. + return; + } + + MarkChanged(); + + PopAllClips(); + + PushClipRect(aRect); + + if (mTransformDirty || !mTransform.IsIdentity()) { + mDC->SetTransform(D2D1::IdentityMatrix()); + mTransformDirty = true; + } + + D2D1_RECT_F clipRect; + bool isPixelAligned; + if (mTransform.IsRectilinear() && + GetDeviceSpaceClipRect(clipRect, isPixelAligned)) { + mDC->PushAxisAlignedClip(clipRect, isPixelAligned + ? D2D1_ANTIALIAS_MODE_ALIASED + : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + mDC->Clear(); + mDC->PopAxisAlignedClip(); + + PopClip(); + return; + } + + RefPtr<ID2D1CommandList> list; + mUsedCommandListsSincePurge++; + mDC->CreateCommandList(getter_AddRefs(list)); + mDC->SetTarget(list); + + IntRect addClipRect; + RefPtr<ID2D1Geometry> geom = GetClippedGeometry(&addClipRect); + + RefPtr<ID2D1SolidColorBrush> brush; + mDC->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), + getter_AddRefs(brush)); + mDC->PushAxisAlignedClip( + D2D1::RectF(addClipRect.X(), addClipRect.Y(), addClipRect.XMost(), + addClipRect.YMost()), + D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + mDC->FillGeometry(geom, brush); + mDC->PopAxisAlignedClip(); + + mDC->SetTarget(CurrentTarget()); + list->Close(); + + mDC->DrawImage(list, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, + D2D1_COMPOSITE_MODE_DESTINATION_OUT); + + PopClip(); + + return; +} + +void DrawTargetD2D1::MaskSurface(const Pattern& aSource, SourceSurface* aMask, + Point aOffset, const DrawOptions& aOptions) { + if (!EnsureInitialized()) { + return; + } + MarkChanged(); + + RefPtr<ID2D1Bitmap> bitmap; + + Matrix mat = Matrix::Translation(aOffset); + RefPtr<ID2D1Image> image = + GetImageForSurface(aMask, mat, ExtendMode::CLAMP, nullptr); + + MOZ_ASSERT(!mat.HasNonTranslation()); + aOffset.x = mat._31; + aOffset.y = mat._32; + + if (!image) { + gfxWarning() << "Failed to get image for surface."; + return; + } + + if (!PrepareForDrawing(aOptions.mCompositionOp, aSource)) { + return; + } + + IntSize size = + IntSize::Truncate(aMask->GetSize().width, aMask->GetSize().height); + Rect dest = + Rect(aOffset.x + aMask->GetRect().x, aOffset.y + aMask->GetRect().y, + Float(size.width), Float(size.height)); + + HRESULT hr = image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap)); + if (!bitmap || FAILED(hr)) { + // D2D says if we have an actual ID2D1Image and not a bitmap underlying the + // object, we can't query for a bitmap. Instead, Push/PopLayer + gfxWarning() << "FillOpacityMask only works with Bitmap source surfaces. " + "Falling back to push/pop layer"; + + RefPtr<ID2D1Brush> source = CreateBrushForPattern(aSource, aOptions); + RefPtr<ID2D1ImageBrush> maskBrush; + hr = mDC->CreateImageBrush( + image, + D2D1::ImageBrushProperties(D2D1::RectF(0, 0, size.width, size.height)), + D2D1::BrushProperties( + 1.0f, D2D1::Matrix3x2F::Translation(aMask->GetRect().x, + aMask->GetRect().y)), + getter_AddRefs(maskBrush)); + MOZ_ASSERT(SUCCEEDED(hr)); + + mDC->PushLayer( + D2D1::LayerParameters1(D2D1::InfiniteRect(), nullptr, + D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, + D2D1::Matrix3x2F::Translation( + aMask->GetRect().x, aMask->GetRect().y), + 1.0f, maskBrush, D2D1_LAYER_OPTIONS1_NONE), + nullptr); + + mDC->FillRectangle(D2DRect(dest), source); + mDC->PopLayer(); + + FinalizeDrawing(aOptions.mCompositionOp, aSource); + return; + } else { + // If this is a data source surface, we might have created a partial bitmap + // for this surface and only uploaded part of the mask. In that case, + // we have to fixup our sizes here. + size.width = bitmap->GetSize().width; + size.height = bitmap->GetSize().height; + dest.SetWidth(size.width); + dest.SetHeight(size.height); + } + + // FillOpacityMask only works if the antialias mode is MODE_ALIASED + mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); + + Rect maskRect = Rect(aMask->GetRect().x, aMask->GetRect().y, + Float(size.width), Float(size.height)); + RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aSource, aOptions); + mDC->FillOpacityMask(bitmap, brush, D2D1_OPACITY_MASK_CONTENT_GRAPHICS, + D2DRect(dest), D2DRect(maskRect)); + + mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + + FinalizeDrawing(aOptions.mCompositionOp, aSource); +} + +void DrawTargetD2D1::CopySurface(SourceSurface* aSurface, + const IntRect& aSourceRect, + const IntPoint& aDestination) { + if (!EnsureInitialized()) { + return; + } + MarkChanged(); + + PopAllClips(); + + mDC->SetTransform(D2D1::IdentityMatrix()); + mTransformDirty = true; + + Matrix mat = Matrix::Translation(aDestination.x - aSourceRect.X(), + aDestination.y - aSourceRect.Y()); + RefPtr<ID2D1Image> image = + GetImageForSurface(aSurface, mat, ExtendMode::CLAMP, nullptr, false); + + if (!image) { + gfxWarning() << "Couldn't get image for surface."; + return; + } + + if (mat.HasNonIntegerTranslation()) { + gfxDebug() << *this + << ": At this point scaled partial uploads are not supported " + "for CopySurface."; + return; + } + + IntRect sourceRect = aSourceRect; + sourceRect.SetLeftEdge(sourceRect.X() + (aDestination.x - aSourceRect.X()) - + mat._31); + sourceRect.SetTopEdge(sourceRect.Y() + (aDestination.y - aSourceRect.Y()) - + mat._32); + + RefPtr<ID2D1Bitmap> bitmap; + HRESULT hr = image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap)); + + if (SUCCEEDED(hr) && bitmap && mFormat == SurfaceFormat::A8) { + RefPtr<ID2D1SolidColorBrush> brush; + mDC->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), + D2D1::BrushProperties(), getter_AddRefs(brush)); + mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); + mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY); + mDC->FillOpacityMask(bitmap, brush, D2D1_OPACITY_MASK_CONTENT_GRAPHICS); + mDC->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER); + return; + } + + Rect srcRect(Float(sourceRect.X()), Float(sourceRect.Y()), + Float(aSourceRect.Width()), Float(aSourceRect.Height())); + + Rect dstRect(Float(aDestination.x), Float(aDestination.y), + Float(aSourceRect.Width()), Float(aSourceRect.Height())); + + if (SUCCEEDED(hr) && bitmap) { + mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY); + mDC->DrawBitmap(bitmap, D2DRect(dstRect), 1.0f, + D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, + D2DRect(srcRect)); + mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER); + return; + } + + mDC->DrawImage(image, + D2D1::Point2F(Float(aDestination.x), Float(aDestination.y)), + D2DRect(srcRect), D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, + D2D1_COMPOSITE_MODE_BOUNDED_SOURCE_COPY); +} + +void DrawTargetD2D1::FillRect(const Rect& aRect, const Pattern& aPattern, + const DrawOptions& aOptions) { + if (!PrepareForDrawing(aOptions.mCompositionOp, aPattern)) { + return; + } + + mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); + + RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions); + mDC->FillRectangle(D2DRect(aRect), brush); + + FinalizeDrawing(aOptions.mCompositionOp, aPattern); +} + +void DrawTargetD2D1::FillRoundedRect(const RoundedRect& aRect, + const Pattern& aPattern, + const DrawOptions& aOptions) { + if (!aRect.corners.AreRadiiSame()) { + return DrawTarget::FillRoundedRect(aRect, aPattern, aOptions); + } + + if (!PrepareForDrawing(aOptions.mCompositionOp, aPattern)) { + return; + } + + mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); + + RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions); + mDC->FillRoundedRectangle(D2DRoundedRect(aRect), brush); + + FinalizeDrawing(aOptions.mCompositionOp, aPattern); +} + +void DrawTargetD2D1::StrokeRect(const Rect& aRect, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) { + if (!PrepareForDrawing(aOptions.mCompositionOp, aPattern)) { + return; + } + + mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); + + RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions); + RefPtr<ID2D1StrokeStyle> strokeStyle = + CreateStrokeStyleForOptions(aStrokeOptions); + + mDC->DrawRectangle(D2DRect(aRect), brush, aStrokeOptions.mLineWidth, + strokeStyle); + + FinalizeDrawing(aOptions.mCompositionOp, aPattern); +} + +void DrawTargetD2D1::StrokeLine(const Point& aStart, const Point& aEnd, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) { + if (!PrepareForDrawing(aOptions.mCompositionOp, aPattern)) { + return; + } + + mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); + + RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions); + RefPtr<ID2D1StrokeStyle> strokeStyle = + CreateStrokeStyleForOptions(aStrokeOptions); + + mDC->DrawLine(D2DPoint(aStart), D2DPoint(aEnd), brush, + aStrokeOptions.mLineWidth, strokeStyle); + + FinalizeDrawing(aOptions.mCompositionOp, aPattern); +} +void DrawTargetD2D1::StrokeCircle(const Point& aOrigin, float radius, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) { + if (!PrepareForDrawing(aOptions.mCompositionOp, aPattern)) { + return; + } + + mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); + + RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions); + RefPtr<ID2D1StrokeStyle> strokeStyle = + CreateStrokeStyleForOptions(aStrokeOptions); + + mDC->DrawEllipse(D2D1::Ellipse(D2DPoint(aOrigin), radius, radius), brush, + aStrokeOptions.mLineWidth, strokeStyle); + + FinalizeDrawing(aOptions.mCompositionOp, aPattern); +} + +void DrawTargetD2D1::FillCircle(const Point& aOrigin, float radius, + const Pattern& aPattern, + const DrawOptions& aOptions) { + if (!PrepareForDrawing(aOptions.mCompositionOp, aPattern)) { + return; + } + + mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); + + RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions); + + mDC->FillEllipse(D2D1::Ellipse(D2DPoint(aOrigin), radius, radius), brush); + + FinalizeDrawing(aOptions.mCompositionOp, aPattern); +} + +void DrawTargetD2D1::Stroke(const Path* aPath, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) { + const Path* path = aPath; + if (path->GetBackendType() != BackendType::DIRECT2D1_1) { + gfxDebug() << *this << ": Ignoring drawing call for incompatible path."; + return; + } + const PathD2D* d2dPath = static_cast<const PathD2D*>(path); + + if (!PrepareForDrawing(aOptions.mCompositionOp, aPattern)) { + return; + } + + mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); + + RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions); + RefPtr<ID2D1StrokeStyle> strokeStyle = + CreateStrokeStyleForOptions(aStrokeOptions); + + mDC->DrawGeometry(d2dPath->mGeometry, brush, aStrokeOptions.mLineWidth, + strokeStyle); + + FinalizeDrawing(aOptions.mCompositionOp, aPattern); +} + +void DrawTargetD2D1::Fill(const Path* aPath, const Pattern& aPattern, + const DrawOptions& aOptions) { + const Path* path = aPath; + if (!path || path->GetBackendType() != BackendType::DIRECT2D1_1) { + gfxDebug() << *this << ": Ignoring drawing call for incompatible path."; + return; + } + const PathD2D* d2dPath = static_cast<const PathD2D*>(path); + + if (!PrepareForDrawing(aOptions.mCompositionOp, aPattern)) { + return; + } + + mDC->SetAntialiasMode(D2DAAMode(aOptions.mAntialiasMode)); + + RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions); + + mDC->FillGeometry(d2dPath->mGeometry, brush); + + FinalizeDrawing(aOptions.mCompositionOp, aPattern); +} + +void DrawTargetD2D1::FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const DrawOptions& aOptions) { + if (aFont->GetType() != FontType::DWRITE) { + gfxDebug() << *this << ": Ignoring drawing call for incompatible font."; + return; + } + + ScaledFontDWrite* font = static_cast<ScaledFontDWrite*>(aFont); + + // May be null, if we failed to initialize the default rendering params. + RefPtr<IDWriteRenderingParams> params = + font->DWriteSettings().RenderingParams(); + + AntialiasMode aaMode = font->GetDefaultAAMode(); + + if (aOptions.mAntialiasMode != AntialiasMode::DEFAULT) { + aaMode = aOptions.mAntialiasMode; + } + + if (!PrepareForDrawing(aOptions.mCompositionOp, aPattern)) { + return; + } + + bool forceClearType = false; + if (!CurrentLayer().mIsOpaque && mPermitSubpixelAA && + aOptions.mCompositionOp == CompositionOp::OP_OVER && + aaMode == AntialiasMode::SUBPIXEL) { + forceClearType = true; + } + + D2D1_TEXT_ANTIALIAS_MODE d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT; + + switch (aaMode) { + case AntialiasMode::NONE: + d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_ALIASED; + break; + case AntialiasMode::GRAY: + d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE; + break; + case AntialiasMode::SUBPIXEL: + d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; + break; + default: + d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT; + } + + if (d2dAAMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE && + !CurrentLayer().mIsOpaque && !forceClearType) { + d2dAAMode = D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE; + } + + mDC->SetTextAntialiasMode(d2dAAMode); + + if (params != mTextRenderingParams) { + // According to + // https://docs.microsoft.com/en-us/windows/win32/api/d2d1/nf-d2d1-id2d1rendertarget-settextrenderingparams + // it's OK to pass null for params here; it will just "clear current text + // rendering options". + mDC->SetTextRenderingParams(params); + mTextRenderingParams = params; + } + + RefPtr<ID2D1Brush> brush = CreateBrushForPattern(aPattern, aOptions); + + AutoDWriteGlyphRun autoRun; + DWriteGlyphRunFromGlyphs(aBuffer, font, &autoRun); + + bool needsRepushedLayers = false; + if (forceClearType) { + D2D1_RECT_F rect; + bool isAligned; + needsRepushedLayers = CurrentLayer().mPushedClips.size() && + !GetDeviceSpaceClipRect(rect, isAligned); + + // If we have a complex clip in our stack and we have a transparent + // background, and subpixel AA is permitted, we need to repush our layer + // stack limited by the glyph run bounds initializing our layers for + // subpixel AA. + if (needsRepushedLayers) { + mDC->GetGlyphRunWorldBounds(D2D1::Point2F(), &autoRun, + DWRITE_MEASURING_MODE_NATURAL, &rect); + rect.left = std::floor(rect.left); + rect.right = std::ceil(rect.right); + rect.top = std::floor(rect.top); + rect.bottom = std::ceil(rect.bottom); + + PopAllClips(); + + if (!mTransform.IsRectilinear()) { + // We must limit the pixels we touch to the -user space- bounds of + // the glyphs being drawn. In order not to get transparent pixels + // copied up in our pushed layer stack. + D2D1_RECT_F userRect; + mDC->SetTransform(D2D1::IdentityMatrix()); + mDC->GetGlyphRunWorldBounds(D2D1::Point2F(), &autoRun, + DWRITE_MEASURING_MODE_NATURAL, &userRect); + + RefPtr<ID2D1PathGeometry> path; + factory()->CreatePathGeometry(getter_AddRefs(path)); + RefPtr<ID2D1GeometrySink> sink; + path->Open(getter_AddRefs(sink)); + AddRectToSink(sink, userRect); + sink->Close(); + + mDC->PushLayer( + D2D1::LayerParameters1( + D2D1::InfiniteRect(), path, D2D1_ANTIALIAS_MODE_ALIASED, + D2DMatrix(mTransform), 1.0f, nullptr, + D2D1_LAYER_OPTIONS1_INITIALIZE_FROM_BACKGROUND | + D2D1_LAYER_OPTIONS1_IGNORE_ALPHA), + nullptr); + } + + PushClipsToDC(mDC, true, rect); + mDC->SetTransform(D2DMatrix(mTransform)); + } + } + + if (brush) { + mDC->DrawGlyphRun(D2D1::Point2F(), &autoRun, brush); + } + + if (mTransform.HasNonTranslation()) { + mTransformedGlyphsSinceLastPurge += aBuffer.mNumGlyphs; + } + + if (needsRepushedLayers) { + PopClipsFromDC(mDC); + + if (!mTransform.IsRectilinear()) { + mDC->PopLayer(); + } + } + + FinalizeDrawing(aOptions.mCompositionOp, aPattern); +} + +void DrawTargetD2D1::Mask(const Pattern& aSource, const Pattern& aMask, + const DrawOptions& aOptions) { + if (!PrepareForDrawing(aOptions.mCompositionOp, aSource)) { + return; + } + + RefPtr<ID2D1Brush> source = CreateBrushForPattern(aSource, aOptions); + RefPtr<ID2D1Brush> mask = CreateBrushForPattern(aMask, DrawOptions()); + mDC->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), nullptr, + D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, + D2D1::IdentityMatrix(), 1.0f, mask), + nullptr); + + Rect rect(0, 0, (Float)mSize.width, (Float)mSize.height); + Matrix mat = mTransform; + mat.Invert(); + + mDC->FillRectangle(D2DRect(mat.TransformBounds(rect)), source); + + mDC->PopLayer(); + + FinalizeDrawing(aOptions.mCompositionOp, aSource); +} + +void DrawTargetD2D1::PushClipGeometry(ID2D1Geometry* aGeometry, + const D2D1_MATRIX_3X2_F& aTransform, + bool aPixelAligned) { + mCurrentClippedGeometry = nullptr; + + PushedClip clip; + clip.mGeometry = aGeometry; + clip.mTransform = aTransform; + clip.mIsPixelAligned = aPixelAligned; + + aGeometry->GetBounds(aTransform, &clip.mBounds); + + CurrentLayer().mPushedClips.push_back(clip); + + // The transform of clips is relative to the world matrix, since we use the + // total transform for the clips, make the world matrix identity. + mDC->SetTransform(D2D1::IdentityMatrix()); + mTransformDirty = true; + + if (CurrentLayer().mClipsArePushed) { + PushD2DLayer(mDC, clip.mGeometry, clip.mTransform, clip.mIsPixelAligned); + } +} + +void DrawTargetD2D1::PushClip(const Path* aPath) { + const Path* path = aPath; + if (path->GetBackendType() != BackendType::DIRECT2D1_1) { + gfxDebug() << *this << ": Ignoring clipping call for incompatible path."; + return; + } + if (!EnsureInitialized()) { + return; + } + + RefPtr<PathD2D> pathD2D = static_cast<PathD2D*>(const_cast<Path*>(path)); + + PushClipGeometry(pathD2D->GetGeometry(), D2DMatrix(mTransform)); +} + +void DrawTargetD2D1::PushClipRect(const Rect& aRect) { + if (!EnsureInitialized()) { + return; + } + if (!mTransform.IsRectilinear()) { + // Whoops, this isn't a rectangle in device space, Direct2D will not deal + // with this transform the way we want it to. + // See remarks: + // http://msdn.microsoft.com/en-us/library/dd316860%28VS.85%29.aspx + RefPtr<ID2D1Geometry> geom = ConvertRectToGeometry(D2DRect(aRect)); + return PushClipGeometry(geom, D2DMatrix(mTransform)); + } + + mCurrentClippedGeometry = nullptr; + + PushedClip clip; + Rect rect = mTransform.TransformBounds(aRect); + IntRect intRect; + clip.mIsPixelAligned = rect.ToIntRect(&intRect); + + // Do not store the transform, just store the device space rectangle directly. + clip.mBounds = D2DRect(rect); + + CurrentLayer().mPushedClips.push_back(clip); + + mDC->SetTransform(D2D1::IdentityMatrix()); + mTransformDirty = true; + + if (CurrentLayer().mClipsArePushed) { + mDC->PushAxisAlignedClip( + clip.mBounds, clip.mIsPixelAligned ? D2D1_ANTIALIAS_MODE_ALIASED + : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + } +} + +void DrawTargetD2D1::PushDeviceSpaceClipRects(const IntRect* aRects, + uint32_t aCount) { + if (!EnsureInitialized()) { + return; + } + // Build a path for the union of the rects. + RefPtr<ID2D1PathGeometry> path; + factory()->CreatePathGeometry(getter_AddRefs(path)); + RefPtr<ID2D1GeometrySink> sink; + path->Open(getter_AddRefs(sink)); + sink->SetFillMode(D2D1_FILL_MODE_WINDING); + for (uint32_t i = 0; i < aCount; i++) { + const IntRect& rect = aRects[i]; + sink->BeginFigure(D2DPoint(rect.TopLeft()), D2D1_FIGURE_BEGIN_FILLED); + D2D1_POINT_2F lines[3] = {D2DPoint(rect.TopRight()), + D2DPoint(rect.BottomRight()), + D2DPoint(rect.BottomLeft())}; + sink->AddLines(lines, 3); + sink->EndFigure(D2D1_FIGURE_END_CLOSED); + } + sink->Close(); + + // The path is in device-space, so there is no transform needed, + // and all rects are pixel aligned. + PushClipGeometry(path, D2D1::IdentityMatrix(), true); +} + +void DrawTargetD2D1::PopClip() { + if (!EnsureInitialized()) { + return; + } + mCurrentClippedGeometry = nullptr; + if (CurrentLayer().mPushedClips.empty()) { + gfxDevCrash(LogReason::UnbalancedClipStack) + << "DrawTargetD2D1::PopClip: No clip to pop."; + return; + } + + if (CurrentLayer().mClipsArePushed) { + if (CurrentLayer().mPushedClips.back().mGeometry) { + mDC->PopLayer(); + } else { + mDC->PopAxisAlignedClip(); + } + } + CurrentLayer().mPushedClips.pop_back(); +} + +bool DrawTargetD2D1::RemoveAllClips() { + if (!EnsureInitialized()) { + return false; + } + mCurrentClippedGeometry = nullptr; + while (!CurrentLayer().mPushedClips.empty()) { + PopClip(); + } + return true; +} + +void DrawTargetD2D1::PushLayer(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds, bool aCopyBackground) { + if (!EnsureInitialized()) { + return; + } + D2D1_LAYER_OPTIONS1 options = D2D1_LAYER_OPTIONS1_NONE; + + if (aOpaque) { + options |= D2D1_LAYER_OPTIONS1_IGNORE_ALPHA; + } + if (aCopyBackground) { + options |= D2D1_LAYER_OPTIONS1_INITIALIZE_FROM_BACKGROUND; + } + + RefPtr<ID2D1ImageBrush> mask; + Matrix maskTransform = aMaskTransform; + RefPtr<ID2D1PathGeometry> clip; + + if (aMask) { + RefPtr<ID2D1Image> image = + GetImageForSurface(aMask, maskTransform, ExtendMode::CLAMP); + mDC->SetTransform(D2D1::IdentityMatrix()); + mTransformDirty = true; + + maskTransform = + maskTransform.PreTranslate(aMask->GetRect().X(), aMask->GetRect().Y()); + // The mask is given in user space. Our layer will apply it in device space. + maskTransform = maskTransform * mTransform; + + if (image) { + IntSize maskSize = aMask->GetSize(); + HRESULT hr = mDC->CreateImageBrush( + image, + D2D1::ImageBrushProperties( + D2D1::RectF(0, 0, maskSize.width, maskSize.height)), + D2D1::BrushProperties(1.0f, D2DMatrix(maskTransform)), + getter_AddRefs(mask)); + if (FAILED(hr)) { + gfxWarning() << "[D2D1.1] Failed to create a ImageBrush, code: " + << hexa(hr); + } + + factory()->CreatePathGeometry(getter_AddRefs(clip)); + RefPtr<ID2D1GeometrySink> sink; + clip->Open(getter_AddRefs(sink)); + AddRectToSink(sink, D2D1::RectF(0, 0, aMask->GetSize().width, + aMask->GetSize().height)); + sink->Close(); + } else { + gfxCriticalError() << "Failed to get image for mask surface!"; + } + } + + PushAllClips(); + + mDC->PushLayer(D2D1::LayerParameters1( + D2D1::InfiniteRect(), clip, D2D1_ANTIALIAS_MODE_ALIASED, + D2DMatrix(maskTransform), aOpacity, mask, options), + nullptr); + PushedLayer pushedLayer; + pushedLayer.mClipsArePushed = false; + pushedLayer.mIsOpaque = aOpaque; + pushedLayer.mOldPermitSubpixelAA = mPermitSubpixelAA; + mPermitSubpixelAA = aOpaque; + + mDC->CreateCommandList(getter_AddRefs(pushedLayer.mCurrentList)); + mPushedLayers.push_back(pushedLayer); + + mDC->SetTarget(CurrentTarget()); + + mUsedCommandListsSincePurge++; +} + +void DrawTargetD2D1::PopLayer() { + // We must have at least one layer at all times. + MOZ_ASSERT(mPushedLayers.size() > 1); + MOZ_ASSERT(CurrentLayer().mPushedClips.size() == 0); + if (!EnsureInitialized() || mPushedLayers.size() <= 1) { + return; + } + RefPtr<ID2D1CommandList> list = CurrentLayer().mCurrentList; + mPermitSubpixelAA = CurrentLayer().mOldPermitSubpixelAA; + + mPushedLayers.pop_back(); + mDC->SetTarget(CurrentTarget()); + + list->Close(); + mDC->SetTransform(D2D1::IdentityMatrix()); + mTransformDirty = true; + + DCCommandSink sink(mDC); + list->Stream(&sink); + + mComplexBlendsWithListInList = 0; + + mDC->PopLayer(); +} + +already_AddRefed<SourceSurface> DrawTargetD2D1::CreateSourceSurfaceFromData( + unsigned char* aData, const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat) const { + RefPtr<ID2D1Bitmap1> bitmap; + + RefPtr<ID2D1DeviceContext> dc = Factory::GetD2DDeviceContext(); + if (!dc) { + return nullptr; + } + + HRESULT hr = + dc->CreateBitmap(D2DIntSize(aSize), aData, aStride, + D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_NONE, + D2DPixelFormat(aFormat)), + getter_AddRefs(bitmap)); + + if (FAILED(hr) || !bitmap) { + gfxCriticalError( + CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(aSize))) + << "[D2D1.1] 1CreateBitmap failure " << aSize << " Code: " << hexa(hr) + << " format " << (int)aFormat; + return nullptr; + } + + return MakeAndAddRef<SourceSurfaceD2D1>(bitmap.get(), dc.get(), aFormat, + aSize); +} + +already_AddRefed<DrawTarget> DrawTargetD2D1::CreateSimilarDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat) const { + RefPtr<DrawTargetD2D1> dt = new DrawTargetD2D1(); + + if (!dt->Init(aSize, aFormat)) { + return nullptr; + } + + return dt.forget(); +} + +bool DrawTargetD2D1::CanCreateSimilarDrawTarget(const IntSize& aSize, + SurfaceFormat aFormat) const { + RefPtr<ID2D1DeviceContext> dc = Factory::GetD2DDeviceContext(); + if (!dc) { + return false; + } + return (dc->GetMaximumBitmapSize() >= UINT32(aSize.width) && + dc->GetMaximumBitmapSize() >= UINT32(aSize.height)); +} + +RefPtr<DrawTarget> DrawTargetD2D1::CreateClippedDrawTarget( + const Rect& aBounds, SurfaceFormat aFormat) { + RefPtr<DrawTarget> result; + + if (!aBounds.IsEmpty()) { + PushClipRect(aBounds); + } + + D2D1_RECT_F clipRect; + bool isAligned; + GetDeviceSpaceClipRect(clipRect, isAligned); + IntRect rect = RoundedOut(ToRect(clipRect)); + + RefPtr<DrawTarget> dt = CreateSimilarDrawTarget(rect.Size(), aFormat); + if (dt) { + result = gfx::Factory::CreateOffsetDrawTarget(dt, rect.TopLeft()); + if (result) { + result->SetTransform(mTransform); + } + } + if (!aBounds.IsEmpty()) { + PopClip(); + } + + return result; +} + +already_AddRefed<GradientStops> DrawTargetD2D1::CreateGradientStops( + GradientStop* rawStops, uint32_t aNumStops, ExtendMode aExtendMode) const { + if (aNumStops == 0) { + gfxWarning() << *this + << ": Failed to create GradientStopCollection with no stops."; + return nullptr; + } + + D2D1_GRADIENT_STOP* stops = new D2D1_GRADIENT_STOP[aNumStops]; + + for (uint32_t i = 0; i < aNumStops; i++) { + stops[i].position = rawStops[i].offset; + stops[i].color = D2DColor(rawStops[i].color); + } + + RefPtr<ID2D1GradientStopCollection1> stopCollection; + + RefPtr<ID2D1DeviceContext> dc = Factory::GetD2DDeviceContext(); + + if (!dc) { + return nullptr; + } + + HRESULT hr = dc->CreateGradientStopCollection( + stops, aNumStops, D2D1_COLOR_SPACE_SRGB, D2D1_COLOR_SPACE_SRGB, + D2D1_BUFFER_PRECISION_8BPC_UNORM, D2DExtend(aExtendMode, Axis::BOTH), + D2D1_COLOR_INTERPOLATION_MODE_PREMULTIPLIED, + getter_AddRefs(stopCollection)); + delete[] stops; + + if (FAILED(hr)) { + gfxWarning() << *this << ": Failed to create GradientStopCollection. Code: " + << hexa(hr); + return nullptr; + } + + RefPtr<ID3D11Device> device = Factory::GetDirect3D11Device(); + return MakeAndAddRef<GradientStopsD2D>(stopCollection, device); +} + +already_AddRefed<FilterNode> DrawTargetD2D1::CreateFilter(FilterType aType) { + if (!EnsureInitialized()) { + return nullptr; + } + return FilterNodeD2D1::Create(mDC, aType); +} + +bool DrawTargetD2D1::Init(ID3D11Texture2D* aTexture, SurfaceFormat aFormat) { + RefPtr<ID2D1Device> device = Factory::GetD2D1Device(&mDeviceSeq); + if (!device) { + gfxCriticalNote << "[D2D1.1] Failed to obtain a device for " + "DrawTargetD2D1::Init(ID3D11Texture2D*, SurfaceFormat)."; + return false; + } + + aTexture->QueryInterface(__uuidof(IDXGISurface), + (void**)((IDXGISurface**)getter_AddRefs(mSurface))); + if (!mSurface) { + gfxCriticalError() << "[D2D1.1] Failed to obtain a DXGI surface."; + return false; + } + + mFormat = aFormat; + + D3D11_TEXTURE2D_DESC desc; + aTexture->GetDesc(&desc); + mSize.width = desc.Width; + mSize.height = desc.Height; + + return true; +} + +bool DrawTargetD2D1::Init(const IntSize& aSize, SurfaceFormat aFormat) { + RefPtr<ID2D1Device> device = Factory::GetD2D1Device(&mDeviceSeq); + if (!device) { + gfxCriticalNote << "[D2D1.1] Failed to obtain a device for " + "DrawTargetD2D1::Init(IntSize, SurfaceFormat)."; + return false; + } + + if (!CanCreateSimilarDrawTarget(aSize, aFormat)) { + // Size unsupported. + return false; + } + + mFormat = aFormat; + mSize = aSize; + + return true; +} + +/** + * Private helpers. + */ +uint32_t DrawTargetD2D1::GetByteSize() const { + return mSize.width * mSize.height * BytesPerPixel(mFormat); +} + +RefPtr<ID2D1Factory1> DrawTargetD2D1::factory() { + StaticMutexAutoLock lock(Factory::mDeviceLock); + + if (mFactory || !NS_IsMainThread()) { + return mFactory; + } + + // We don't allow initializing the factory off the main thread. + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + RefPtr<ID2D1Factory> factory; + D2D1CreateFactoryFunc createD2DFactory; + HMODULE d2dModule = LoadLibraryW(L"d2d1.dll"); + createD2DFactory = + (D2D1CreateFactoryFunc)GetProcAddress(d2dModule, "D2D1CreateFactory"); + + if (!createD2DFactory) { + gfxWarning() << "Failed to locate D2D1CreateFactory function."; + return nullptr; + } + + D2D1_FACTORY_OPTIONS options; +#ifdef _DEBUG + options.debugLevel = D2D1_DEBUG_LEVEL_WARNING; +#else + options.debugLevel = D2D1_DEBUG_LEVEL_NONE; +#endif + // options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION; + + HRESULT hr = + createD2DFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, __uuidof(ID2D1Factory), + &options, getter_AddRefs(factory)); + + if (FAILED(hr) || !factory) { + gfxCriticalNote << "Failed to create a D2D1 content device: " << hexa(hr); + return nullptr; + } + + RefPtr<ID2D1Factory1> factory1; + hr = factory->QueryInterface(__uuidof(ID2D1Factory1), + getter_AddRefs(factory1)); + if (FAILED(hr) || !factory1) { + return nullptr; + } + + mFactory = factory1; + + ExtendInputEffectD2D1::Register(mFactory); + ConicGradientEffectD2D1::Register(mFactory); + RadialGradientEffectD2D1::Register(mFactory); + + return mFactory; +} + +void DrawTargetD2D1::CleanupD2D() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + Factory::mDeviceLock.AssertCurrentThreadOwns(); + + if (mFactory) { + RadialGradientEffectD2D1::Unregister(mFactory); + ConicGradientEffectD2D1::Unregister(mFactory); + ExtendInputEffectD2D1::Unregister(mFactory); + mFactory = nullptr; + } +} + +void DrawTargetD2D1::FlushInternal(bool aHasDependencyMutex /* = false */) { + if (IsDeviceContextValid()) { + if ((mUsedCommandListsSincePurge >= kPushedLayersBeforePurge || + mTransformedGlyphsSinceLastPurge >= kTransformedGlyphsBeforePurge) && + mPushedLayers.size() == 1) { + // It's important to pop all clips as otherwise layers can forget about + // their clip when doing an EndDraw. When we have layers pushed we cannot + // easily pop all underlying clips to delay the purge until we have no + // layers pushed. + PopAllClips(); + mUsedCommandListsSincePurge = 0; + mTransformedGlyphsSinceLastPurge = 0; + mDC->EndDraw(); + mDC->BeginDraw(); + } else { + mDC->Flush(); + } + } + + Maybe<StaticMutexAutoLock> lock; + + if (!aHasDependencyMutex) { + lock.emplace(Factory::mDTDependencyLock); + } + + Factory::mDTDependencyLock.AssertCurrentThreadOwns(); + // We no longer depend on any target. + for (TargetSet::iterator iter = mDependingOnTargets.begin(); + iter != mDependingOnTargets.end(); iter++) { + (*iter)->mDependentTargets.erase(this); + } + mDependingOnTargets.clear(); +} + +bool DrawTargetD2D1::EnsureInitialized() { + if (mInitState != InitState::Uninitialized) { + return mInitState == InitState::Success; + } + + // Don't retry. + mInitState = InitState::Failure; + + HRESULT hr; + + RefPtr<ID2D1Device> device = Factory::GetD2D1Device(&mDeviceSeq); + if (!device) { + gfxCriticalNote << "[D2D1.1] Failed to obtain a device for " + "DrawTargetD2D1::EnsureInitialized()."; + return false; + } + + hr = device->CreateDeviceContext( + D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS, + getter_AddRefs(mDC)); + + if (FAILED(hr)) { + gfxCriticalError() << "[D2D1.1] 2Failed to create a DeviceContext, code: " + << hexa(hr) << " format " << (int)mFormat; + return false; + } + + if (!mSurface) { + if (mDC->GetMaximumBitmapSize() < UINT32(mSize.width) || + mDC->GetMaximumBitmapSize() < UINT32(mSize.height)) { + // This is 'ok', so don't assert + gfxCriticalNote << "[D2D1.1] Attempt to use unsupported surface size " + << mSize; + return false; + } + + D2D1_BITMAP_PROPERTIES1 props; + props.dpiX = 96; + props.dpiY = 96; + props.pixelFormat = D2DPixelFormat(mFormat); + props.colorContext = nullptr; + props.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET; + hr = mDC->CreateBitmap(D2DIntSize(mSize), nullptr, 0, props, + (ID2D1Bitmap1**)getter_AddRefs(mBitmap)); + + if (FAILED(hr)) { + gfxCriticalError() << "[D2D1.1] 3CreateBitmap failure " << mSize + << " Code: " << hexa(hr) << " format " << (int)mFormat; + return false; + } + } else { + D2D1_BITMAP_PROPERTIES1 props; + props.dpiX = 96; + props.dpiY = 96; + props.pixelFormat = D2DPixelFormat(mFormat); + props.colorContext = nullptr; + props.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET; + hr = mDC->CreateBitmapFromDxgiSurface( + mSurface, props, (ID2D1Bitmap1**)getter_AddRefs(mBitmap)); + + if (FAILED(hr)) { + gfxCriticalError() + << "[D2D1.1] CreateBitmapFromDxgiSurface failure Code: " << hexa(hr) + << " format " << (int)mFormat; + return false; + } + } + + mDC->SetTarget(CurrentTarget()); + + hr = mDC->CreateSolidColorBrush(D2D1::ColorF(0, 0), + getter_AddRefs(mSolidColorBrush)); + + if (FAILED(hr)) { + gfxCriticalError() << "[D2D1.1] Failure creating solid color brush (I2)."; + return false; + } + + mDC->BeginDraw(); + + CurrentLayer().mIsOpaque = mFormat == SurfaceFormat::B8G8R8X8; + + if (!mSurface) { + mDC->Clear(); + } + + mInitState = InitState::Success; + + return true; +} + +void DrawTargetD2D1::MarkChanged() { + if (mSnapshot) { + MutexAutoLock lock(*mSnapshotLock); + if (mSnapshot->hasOneRef()) { + // Just destroy it, since no-one else knows about it. + mSnapshot = nullptr; + } else { + mSnapshot->DrawTargetWillChange(); + // The snapshot will no longer depend on this target. + MOZ_ASSERT(!mSnapshot); + } + } + + { + StaticMutexAutoLock lock(Factory::mDTDependencyLock); + if (mDependentTargets.size()) { + // Copy mDependentTargets since the Flush()es below will modify it. + TargetSet tmpTargets = mDependentTargets; + for (TargetSet::iterator iter = tmpTargets.begin(); + iter != tmpTargets.end(); iter++) { + (*iter)->FlushInternal(true); + } + // The Flush() should have broken all dependencies on this target. + MOZ_ASSERT(!mDependentTargets.size()); + } + } +} + +bool DrawTargetD2D1::ShouldClipTemporarySurfaceDrawing(CompositionOp aOp, + const Pattern& aPattern, + bool aClipIsComplex) { + bool patternSupported = IsPatternSupportedByD2D(aPattern, aOp); + return patternSupported && !CurrentLayer().mIsOpaque && + D2DSupportsCompositeMode(aOp) && IsOperatorBoundByMask(aOp) && + aClipIsComplex; +} + +bool DrawTargetD2D1::PrepareForDrawing(CompositionOp aOp, + const Pattern& aPattern) { + if (!EnsureInitialized()) { + return false; + } + + MarkChanged(); + + PushAllClips(); + + bool patternSupported = IsPatternSupportedByD2D(aPattern, aOp); + if (D2DSupportsPrimitiveBlendMode(aOp) && patternSupported) { + // It's important to do this before FlushTransformToDC! As this will cause + // the transform to become dirty. + + FlushTransformToDC(); + + if (aOp != CompositionOp::OP_OVER) { + mDC->SetPrimitiveBlend(D2DPrimitiveBlendMode(aOp)); + } + + return true; + } + + HRESULT result = mDC->CreateCommandList(getter_AddRefs(mCommandList)); + mDC->SetTarget(mCommandList); + mUsedCommandListsSincePurge++; + + // This is where we should have a valid command list. If we don't, something + // is wrong, and it's likely an OOM. + if (!mCommandList) { + gfxDevCrash(LogReason::InvalidCommandList) + << "Invalid D2D1.1 command list on creation " + << mUsedCommandListsSincePurge << ", " << gfx::hexa(result); + } + + D2D1_RECT_F rect; + bool isAligned; + bool clipIsComplex = CurrentLayer().mPushedClips.size() && + !GetDeviceSpaceClipRect(rect, isAligned); + + if (ShouldClipTemporarySurfaceDrawing(aOp, aPattern, clipIsComplex)) { + PushClipsToDC(mDC); + } + + FlushTransformToDC(); + + return true; +} + +void DrawTargetD2D1::FinalizeDrawing(CompositionOp aOp, + const Pattern& aPattern) { + bool patternSupported = IsPatternSupportedByD2D(aPattern, aOp); + + if (D2DSupportsPrimitiveBlendMode(aOp) && patternSupported) { + if (aOp != CompositionOp::OP_OVER) + mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER); + return; + } + + D2D1_RECT_F rect; + bool isAligned; + bool clipIsComplex = CurrentLayer().mPushedClips.size() && + !GetDeviceSpaceClipRect(rect, isAligned); + + if (ShouldClipTemporarySurfaceDrawing(aOp, aPattern, clipIsComplex)) { + PopClipsFromDC(mDC); + } + + mDC->SetTarget(CurrentTarget()); + if (!mCommandList) { + gfxDevCrash(LogReason::InvalidCommandList) + << "Invalid D21.1 command list on finalize"; + return; + } + mCommandList->Close(); + + RefPtr<ID2D1CommandList> source = mCommandList; + mCommandList = nullptr; + + mDC->SetTransform(D2D1::IdentityMatrix()); + mTransformDirty = true; + + if (patternSupported) { + if (D2DSupportsCompositeMode(aOp)) { + RefPtr<ID2D1Image> tmpImage; + if (clipIsComplex) { + PopAllClips(); + if (!IsOperatorBoundByMask(aOp)) { + tmpImage = GetImageForLayerContent(); + } + } + mDC->DrawImage(source, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, + D2DCompositionMode(aOp)); + + if (tmpImage) { + RefPtr<ID2D1ImageBrush> brush; + RefPtr<ID2D1Geometry> inverseGeom = GetInverseClippedGeometry(); + mDC->CreateImageBrush(tmpImage, + D2D1::ImageBrushProperties( + D2D1::RectF(0, 0, mSize.width, mSize.height)), + getter_AddRefs(brush)); + + mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY); + mDC->FillGeometry(inverseGeom, brush); + mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER); + } + return; + } + + RefPtr<ID2D1Effect> blendEffect; + HRESULT hr = + mDC->CreateEffect(CLSID_D2D1Blend, getter_AddRefs(blendEffect)); + + if (FAILED(hr) || !blendEffect) { + gfxWarning() << "Failed to create blend effect!"; + return; + } + + IntRect bounds(IntPoint(), mSize); + RefPtr<ID2D1Geometry> geom; + if (CurrentLayer().mPushedClips.size() > 0) { + geom = GetClippedGeometry(&bounds); + } + RefPtr<ID2D1Image> tmpImage = GetImageForLayerContent(&bounds, bool(geom)); + if (!tmpImage) { + return; + } + + blendEffect->SetInput(0, tmpImage); + blendEffect->SetInput(1, source); + blendEffect->SetValue(D2D1_BLEND_PROP_MODE, D2DBlendMode(aOp)); + + if (geom) { + RefPtr<ID2D1Image> blendOutput; + blendEffect->GetOutput(getter_AddRefs(blendOutput)); + + RefPtr<ID2D1ImageBrush> brush; + mDC->CreateImageBrush( + blendOutput, D2D1::ImageBrushProperties(D2DRect(bounds)), + D2D1::BrushProperties( + 1.0f, D2D1::Matrix3x2F::Translation(bounds.x, bounds.y)), + getter_AddRefs(brush)); + + mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY); + mDC->FillGeometry(geom, brush); + mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER); + } else { + mDC->DrawImage(blendEffect, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, + D2D1_COMPOSITE_MODE_BOUNDED_SOURCE_COPY); + } + + mComplexBlendsWithListInList++; + return; + } + + if (aPattern.GetType() == PatternType::CONIC_GRADIENT) { + const ConicGradientPattern* pat = + static_cast<const ConicGradientPattern*>(&aPattern); + + if (!pat->mStops || + pat->mStops->GetBackendType() != BackendType::DIRECT2D) { + // Draw nothing because of no color stops + return; + } + RefPtr<ID2D1Effect> conicGradientEffect; + + HRESULT hr = mDC->CreateEffect(CLSID_ConicGradientEffect, + getter_AddRefs(conicGradientEffect)); + if (FAILED(hr) || !conicGradientEffect) { + gfxWarning() << "Failed to create conic gradient effect. Code: " + << hexa(hr); + return; + } + + PushAllClips(); + + conicGradientEffect->SetValue( + CONIC_PROP_STOP_COLLECTION, + static_cast<const GradientStopsD2D*>(pat->mStops.get()) + ->mStopCollection); + conicGradientEffect->SetValue( + CONIC_PROP_CENTER, D2D1::Vector2F(pat->mCenter.x, pat->mCenter.y)); + conicGradientEffect->SetValue(CONIC_PROP_ANGLE, pat->mAngle); + conicGradientEffect->SetValue(CONIC_PROP_START_OFFSET, pat->mStartOffset); + conicGradientEffect->SetValue(CONIC_PROP_END_OFFSET, pat->mEndOffset); + conicGradientEffect->SetValue(CONIC_PROP_TRANSFORM, + D2DMatrix(pat->mMatrix * mTransform)); + conicGradientEffect->SetInput(0, source); + + mDC->DrawImage(conicGradientEffect, + D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, + D2DCompositionMode(aOp)); + return; + } + + MOZ_ASSERT(aPattern.GetType() == PatternType::RADIAL_GRADIENT); + + const RadialGradientPattern* pat = + static_cast<const RadialGradientPattern*>(&aPattern); + if (pat->mCenter1 == pat->mCenter2 && pat->mRadius1 == pat->mRadius2) { + // Draw nothing! + return; + } + + if (!pat->mStops || pat->mStops->GetBackendType() != BackendType::DIRECT2D) { + // Draw nothing because of no color stops + return; + } + + RefPtr<ID2D1Effect> radialGradientEffect; + + HRESULT hr = mDC->CreateEffect(CLSID_RadialGradientEffect, + getter_AddRefs(radialGradientEffect)); + if (FAILED(hr) || !radialGradientEffect) { + gfxWarning() << "Failed to create radial gradient effect. Code: " + << hexa(hr); + return; + } + + PushAllClips(); + + radialGradientEffect->SetValue( + RADIAL_PROP_STOP_COLLECTION, + static_cast<const GradientStopsD2D*>(pat->mStops.get())->mStopCollection); + radialGradientEffect->SetValue( + RADIAL_PROP_CENTER_1, D2D1::Vector2F(pat->mCenter1.x, pat->mCenter1.y)); + radialGradientEffect->SetValue( + RADIAL_PROP_CENTER_2, D2D1::Vector2F(pat->mCenter2.x, pat->mCenter2.y)); + radialGradientEffect->SetValue(RADIAL_PROP_RADIUS_1, pat->mRadius1); + radialGradientEffect->SetValue(RADIAL_PROP_RADIUS_2, pat->mRadius2); + radialGradientEffect->SetValue(RADIAL_PROP_RADIUS_2, pat->mRadius2); + radialGradientEffect->SetValue(RADIAL_PROP_TRANSFORM, + D2DMatrix(pat->mMatrix * mTransform)); + radialGradientEffect->SetInput(0, source); + + mDC->DrawImage(radialGradientEffect, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, + D2DCompositionMode(aOp)); +} + +void DrawTargetD2D1::AddDependencyOnSource(SourceSurfaceD2D1* aSource) { + Maybe<MutexAutoLock> snapshotLock; + // We grab the SnapshotLock as well, this guaranteeds aSource->mDrawTarget + // cannot be cleared in between the if statement and the dereference. + if (aSource->mSnapshotLock) { + snapshotLock.emplace(*aSource->mSnapshotLock); + } + { + StaticMutexAutoLock lock(Factory::mDTDependencyLock); + if (aSource->mDrawTarget && + !mDependingOnTargets.count(aSource->mDrawTarget)) { + aSource->mDrawTarget->mDependentTargets.insert(this); + mDependingOnTargets.insert(aSource->mDrawTarget); + } + } +} + +static D2D1_RECT_F IntersectRect(const D2D1_RECT_F& aRect1, + const D2D1_RECT_F& aRect2) { + D2D1_RECT_F result; + result.left = std::max(aRect1.left, aRect2.left); + result.top = std::max(aRect1.top, aRect2.top); + result.right = std::min(aRect1.right, aRect2.right); + result.bottom = std::min(aRect1.bottom, aRect2.bottom); + + result.right = std::max(result.right, result.left); + result.bottom = std::max(result.bottom, result.top); + + return result; +} + +bool DrawTargetD2D1::GetDeviceSpaceClipRect(D2D1_RECT_F& aClipRect, + bool& aIsPixelAligned) { + aIsPixelAligned = true; + aClipRect = D2D1::RectF(0, 0, mSize.width, mSize.height); + + if (!CurrentLayer().mPushedClips.size()) { + return false; + } + + for (auto iter = CurrentLayer().mPushedClips.begin(); + iter != CurrentLayer().mPushedClips.end(); iter++) { + if (iter->mGeometry) { + return false; + } + aClipRect = IntersectRect(aClipRect, iter->mBounds); + if (!iter->mIsPixelAligned) { + aIsPixelAligned = false; + } + } + return true; +} + +static const uint32_t sComplexBlendsWithListAllowedInList = 4; + +already_AddRefed<ID2D1Image> DrawTargetD2D1::GetImageForLayerContent( + const IntRect* aBounds, bool aShouldPreserveContent) { + PopAllClips(); + + IntRect bounds = aBounds ? *aBounds : IntRect(IntPoint(), mSize); + IntSize size(bounds.XMost(), bounds.YMost()); + if (!CurrentLayer().mCurrentList) { + RefPtr<ID2D1Bitmap> tmpBitmap; + HRESULT hr = mDC->CreateBitmap( + D2DIntSize(size), D2D1::BitmapProperties(D2DPixelFormat(mFormat)), + getter_AddRefs(tmpBitmap)); + if (FAILED(hr)) { + gfxCriticalError( + CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(size))) + << "[D2D1.1] 6CreateBitmap failure " << size << " Code: " << hexa(hr) + << " format " << (int)mFormat; + // If it's a recreate target error, return and handle it elsewhere. + if (hr == D2DERR_RECREATE_TARGET) { + mDC->Flush(); + return nullptr; + } + // For now, crash in other scenarios; this should happen because tmpBitmap + // is null and CopyFromBitmap call below dereferences it. + } + mDC->Flush(); + + D2D1_POINT_2U destOffset = D2D1::Point2U(bounds.x, bounds.y); + D2D1_RECT_U srcRect = + D2D1::RectU(bounds.x, bounds.y, bounds.width, bounds.height); + tmpBitmap->CopyFromBitmap(&destOffset, mBitmap, &srcRect); + return tmpBitmap.forget(); + } else { + RefPtr<ID2D1CommandList> list = CurrentLayer().mCurrentList; + mDC->CreateCommandList(getter_AddRefs(CurrentLayer().mCurrentList)); + mDC->SetTarget(CurrentTarget()); + list->Close(); + + RefPtr<ID2D1Bitmap1> tmpBitmap; + if (mComplexBlendsWithListInList >= sComplexBlendsWithListAllowedInList) { + D2D1_BITMAP_PROPERTIES1 props = D2D1::BitmapProperties1( + D2D1_BITMAP_OPTIONS_TARGET, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, + D2D1_ALPHA_MODE_PREMULTIPLIED)); + mDC->CreateBitmap(D2DIntSize(size), nullptr, 0, &props, + getter_AddRefs(tmpBitmap)); + mDC->SetTransform(D2D1::IdentityMatrix()); + mTransformDirty = true; + mDC->SetTarget(tmpBitmap); + mDC->DrawImage(list, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, + D2D1_COMPOSITE_MODE_BOUNDED_SOURCE_COPY); + mDC->SetTarget(CurrentTarget()); + mComplexBlendsWithListInList = 0; + } + + DCCommandSink sink(mDC); + + if (aShouldPreserveContent) { + list->Stream(&sink); + } + + if (tmpBitmap) { + return tmpBitmap.forget(); + } + + return list.forget(); + } +} + +already_AddRefed<ID2D1Geometry> DrawTargetD2D1::GetClippedGeometry( + IntRect* aClipBounds) { + if (mCurrentClippedGeometry) { + *aClipBounds = mCurrentClipBounds; + RefPtr<ID2D1Geometry> clippedGeometry(mCurrentClippedGeometry); + return clippedGeometry.forget(); + } + + MOZ_ASSERT(CurrentLayer().mPushedClips.size()); + + mCurrentClipBounds = IntRect(IntPoint(0, 0), mSize); + + // if pathGeom is null then pathRect represents the path. + RefPtr<ID2D1Geometry> pathGeom; + D2D1_RECT_F pathRect; + bool pathRectIsAxisAligned = false; + auto iter = CurrentLayer().mPushedClips.begin(); + + if (iter->mGeometry) { + pathGeom = GetTransformedGeometry(iter->mGeometry, iter->mTransform); + } else { + pathRect = iter->mBounds; + pathRectIsAxisAligned = iter->mIsPixelAligned; + } + + iter++; + for (; iter != CurrentLayer().mPushedClips.end(); iter++) { + // Do nothing but add it to the current clip bounds. + if (!iter->mGeometry && iter->mIsPixelAligned) { + mCurrentClipBounds.IntersectRect( + mCurrentClipBounds, + IntRect(int32_t(iter->mBounds.left), int32_t(iter->mBounds.top), + int32_t(iter->mBounds.right - iter->mBounds.left), + int32_t(iter->mBounds.bottom - iter->mBounds.top))); + continue; + } + + if (!pathGeom) { + if (pathRectIsAxisAligned) { + mCurrentClipBounds.IntersectRect( + mCurrentClipBounds, + IntRect(int32_t(pathRect.left), int32_t(pathRect.top), + int32_t(pathRect.right - pathRect.left), + int32_t(pathRect.bottom - pathRect.top))); + } + if (iter->mGeometry) { + // See if pathRect needs to go into the path geometry. + if (!pathRectIsAxisAligned) { + pathGeom = ConvertRectToGeometry(pathRect); + } else { + pathGeom = GetTransformedGeometry(iter->mGeometry, iter->mTransform); + } + } else { + pathRect = IntersectRect(pathRect, iter->mBounds); + pathRectIsAxisAligned = false; + continue; + } + } + + RefPtr<ID2D1PathGeometry> newGeom; + factory()->CreatePathGeometry(getter_AddRefs(newGeom)); + + RefPtr<ID2D1GeometrySink> currentSink; + newGeom->Open(getter_AddRefs(currentSink)); + + if (iter->mGeometry) { + pathGeom->CombineWithGeometry(iter->mGeometry, + D2D1_COMBINE_MODE_INTERSECT, + iter->mTransform, currentSink); + } else { + RefPtr<ID2D1Geometry> rectGeom = ConvertRectToGeometry(iter->mBounds); + pathGeom->CombineWithGeometry(rectGeom, D2D1_COMBINE_MODE_INTERSECT, + D2D1::IdentityMatrix(), currentSink); + } + + currentSink->Close(); + + pathGeom = newGeom.forget(); + } + + // For now we need mCurrentClippedGeometry to always be non-nullptr. This + // method might seem a little strange but it is just fine, if pathGeom is + // nullptr pathRect will always still contain 1 clip unaccounted for + // regardless of mCurrentClipBounds. + if (!pathGeom) { + pathGeom = ConvertRectToGeometry(pathRect); + } + mCurrentClippedGeometry = pathGeom.forget(); + *aClipBounds = mCurrentClipBounds; + RefPtr<ID2D1Geometry> clippedGeometry(mCurrentClippedGeometry); + return clippedGeometry.forget(); +} + +already_AddRefed<ID2D1Geometry> DrawTargetD2D1::GetInverseClippedGeometry() { + IntRect bounds; + RefPtr<ID2D1Geometry> geom = GetClippedGeometry(&bounds); + RefPtr<ID2D1RectangleGeometry> rectGeom; + RefPtr<ID2D1PathGeometry> inverseGeom; + + factory()->CreateRectangleGeometry( + D2D1::RectF(0, 0, mSize.width, mSize.height), getter_AddRefs(rectGeom)); + factory()->CreatePathGeometry(getter_AddRefs(inverseGeom)); + RefPtr<ID2D1GeometrySink> sink; + inverseGeom->Open(getter_AddRefs(sink)); + rectGeom->CombineWithGeometry(geom, D2D1_COMBINE_MODE_EXCLUDE, + D2D1::IdentityMatrix(), sink); + sink->Close(); + + return inverseGeom.forget(); +} + +void DrawTargetD2D1::PopAllClips() { + if (CurrentLayer().mClipsArePushed) { + PopClipsFromDC(mDC); + + CurrentLayer().mClipsArePushed = false; + } +} + +void DrawTargetD2D1::PushAllClips() { + if (!CurrentLayer().mClipsArePushed) { + PushClipsToDC(mDC); + + CurrentLayer().mClipsArePushed = true; + } +} + +void DrawTargetD2D1::PushClipsToDC(ID2D1DeviceContext* aDC, + bool aForceIgnoreAlpha, + const D2D1_RECT_F& aMaxRect) { + mDC->SetTransform(D2D1::IdentityMatrix()); + mTransformDirty = true; + + for (auto iter = CurrentLayer().mPushedClips.begin(); + iter != CurrentLayer().mPushedClips.end(); iter++) { + if (iter->mGeometry) { + PushD2DLayer(aDC, iter->mGeometry, iter->mTransform, + iter->mIsPixelAligned, aForceIgnoreAlpha, aMaxRect); + } else { + mDC->PushAxisAlignedClip(iter->mBounds, + iter->mIsPixelAligned + ? D2D1_ANTIALIAS_MODE_ALIASED + : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + } + } +} + +void DrawTargetD2D1::PopClipsFromDC(ID2D1DeviceContext* aDC) { + for (int i = CurrentLayer().mPushedClips.size() - 1; i >= 0; i--) { + if (CurrentLayer().mPushedClips[i].mGeometry) { + aDC->PopLayer(); + } else { + aDC->PopAxisAlignedClip(); + } + } +} + +already_AddRefed<ID2D1Brush> DrawTargetD2D1::CreateTransparentBlackBrush() { + return GetSolidColorBrush(D2D1::ColorF(0, 0)); +} + +already_AddRefed<ID2D1SolidColorBrush> DrawTargetD2D1::GetSolidColorBrush( + const D2D_COLOR_F& aColor) { + RefPtr<ID2D1SolidColorBrush> brush = mSolidColorBrush; + brush->SetColor(aColor); + return brush.forget(); +} + +already_AddRefed<ID2D1Brush> DrawTargetD2D1::CreateBrushForPattern( + const Pattern& aPattern, const DrawOptions& aOptions) { + if (!IsPatternSupportedByD2D(aPattern) || + aOptions.mCompositionOp == CompositionOp::OP_CLEAR) { + return GetSolidColorBrush(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f)); + } + + if (aPattern.GetType() == PatternType::COLOR) { + DeviceColor color = static_cast<const ColorPattern*>(&aPattern)->mColor; + return GetSolidColorBrush( + D2D1::ColorF(color.r, color.g, color.b, color.a * aOptions.mAlpha)); + } + if (aPattern.GetType() == PatternType::LINEAR_GRADIENT) { + RefPtr<ID2D1LinearGradientBrush> gradBrush; + const LinearGradientPattern* pat = + static_cast<const LinearGradientPattern*>(&aPattern); + + if (!pat->mStops || + pat->mStops->GetBackendType() != BackendType::DIRECT2D) { + gfxDebug() << "No stops specified for gradient pattern."; + return CreateTransparentBlackBrush(); + } + + if (pat->mBegin == pat->mEnd) { + return CreateTransparentBlackBrush(); + } + + GradientStopsD2D* stops = static_cast<GradientStopsD2D*>(pat->mStops.get()); + + mDC->CreateLinearGradientBrush( + D2D1::LinearGradientBrushProperties(D2DPoint(pat->mBegin), + D2DPoint(pat->mEnd)), + D2D1::BrushProperties(aOptions.mAlpha, D2DMatrix(pat->mMatrix)), + stops->mStopCollection, getter_AddRefs(gradBrush)); + + if (!gradBrush) { + gfxWarning() << "Couldn't create gradient brush."; + return CreateTransparentBlackBrush(); + } + + return gradBrush.forget(); + } + if (aPattern.GetType() == PatternType::RADIAL_GRADIENT) { + RefPtr<ID2D1RadialGradientBrush> gradBrush; + const RadialGradientPattern* pat = + static_cast<const RadialGradientPattern*>(&aPattern); + + if (!pat->mStops || + pat->mStops->GetBackendType() != BackendType::DIRECT2D) { + gfxDebug() << "No stops specified for gradient pattern."; + return CreateTransparentBlackBrush(); + } + + if (pat->mCenter1 == pat->mCenter2 && pat->mRadius1 == pat->mRadius2) { + return CreateTransparentBlackBrush(); + } + + GradientStopsD2D* stops = static_cast<GradientStopsD2D*>(pat->mStops.get()); + + // This will not be a complex radial gradient brush. + mDC->CreateRadialGradientBrush( + D2D1::RadialGradientBrushProperties( + D2DPoint(pat->mCenter2), D2DPoint(pat->mCenter1 - pat->mCenter2), + pat->mRadius2, pat->mRadius2), + D2D1::BrushProperties(aOptions.mAlpha, D2DMatrix(pat->mMatrix)), + stops->mStopCollection, getter_AddRefs(gradBrush)); + + if (!gradBrush) { + gfxWarning() << "Couldn't create gradient brush."; + return CreateTransparentBlackBrush(); + } + + return gradBrush.forget(); + } + if (aPattern.GetType() == PatternType::SURFACE) { + const SurfacePattern* pat = static_cast<const SurfacePattern*>(&aPattern); + + if (!pat->mSurface) { + gfxDebug() << "No source surface specified for surface pattern"; + return CreateTransparentBlackBrush(); + } + + D2D1_RECT_F samplingBounds; + Matrix mat = pat->mMatrix; + + MOZ_ASSERT(pat->mSurface->IsValid()); + + RefPtr<SourceSurface> surf = pat->mSurface; + + RefPtr<ID2D1Image> image = GetImageForSurface( + surf, mat, pat->mExtendMode, + !pat->mSamplingRect.IsEmpty() ? &pat->mSamplingRect : nullptr); + + if (!image) { + return CreateTransparentBlackBrush(); + } + + if (surf->GetFormat() == SurfaceFormat::A8) { + // See bug 1251431, at least FillOpacityMask does not appear to allow a + // source bitmapbrush with source format A8. This creates a BGRA surface + // with the same alpha values that the A8 surface has. + RefPtr<ID2D1Bitmap> bitmap; + HRESULT hr = image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap)); + if (SUCCEEDED(hr) && bitmap) { + RefPtr<ID2D1Image> oldTarget; + RefPtr<ID2D1Bitmap1> tmpBitmap; + mDC->CreateBitmap(D2D1::SizeU(pat->mSurface->GetSize().width, + pat->mSurface->GetSize().height), + nullptr, 0, + D2D1::BitmapProperties1( + D2D1_BITMAP_OPTIONS_TARGET, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, + D2D1_ALPHA_MODE_PREMULTIPLIED)), + getter_AddRefs(tmpBitmap)); + + if (!tmpBitmap) { + return CreateTransparentBlackBrush(); + } + + mDC->GetTarget(getter_AddRefs(oldTarget)); + mDC->SetTarget(tmpBitmap); + + RefPtr<ID2D1SolidColorBrush> brush; + mDC->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), + getter_AddRefs(brush)); + mDC->FillOpacityMask(bitmap, brush); + mDC->SetTarget(oldTarget); + image = tmpBitmap; + } + } + + if (pat->mSamplingRect.IsEmpty()) { + RefPtr<ID2D1Bitmap> bitmap; + HRESULT hr = image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap)); + if (SUCCEEDED(hr) && bitmap) { + /** + * Create the brush with the proper repeat modes. + */ + RefPtr<ID2D1BitmapBrush> bitmapBrush; + D2D1_EXTEND_MODE xRepeat = D2DExtend(pat->mExtendMode, Axis::X_AXIS); + D2D1_EXTEND_MODE yRepeat = D2DExtend(pat->mExtendMode, Axis::Y_AXIS); + + mDC->CreateBitmapBrush( + bitmap, + D2D1::BitmapBrushProperties(xRepeat, yRepeat, + D2DFilter(pat->mSamplingFilter)), + D2D1::BrushProperties(aOptions.mAlpha, D2DMatrix(mat)), + getter_AddRefs(bitmapBrush)); + if (!bitmapBrush) { + gfxWarning() << "Couldn't create bitmap brush!"; + return CreateTransparentBlackBrush(); + } + return bitmapBrush.forget(); + } + } + + RefPtr<ID2D1ImageBrush> imageBrush; + if (pat->mSamplingRect.IsEmpty()) { + samplingBounds = D2D1::RectF(0, 0, Float(pat->mSurface->GetSize().width), + Float(pat->mSurface->GetSize().height)); + } else if (surf->GetType() == SurfaceType::D2D1_1_IMAGE) { + samplingBounds = D2DRect(pat->mSamplingRect); + mat.PreTranslate(pat->mSamplingRect.X(), pat->mSamplingRect.Y()); + } else { + // We will do a partial upload of the sampling restricted area from + // GetImageForSurface. + samplingBounds = D2D1::RectF(0, 0, pat->mSamplingRect.Width(), + pat->mSamplingRect.Height()); + } + + D2D1_EXTEND_MODE xRepeat = D2DExtend(pat->mExtendMode, Axis::X_AXIS); + D2D1_EXTEND_MODE yRepeat = D2DExtend(pat->mExtendMode, Axis::Y_AXIS); + + mDC->CreateImageBrush( + image, + D2D1::ImageBrushProperties(samplingBounds, xRepeat, yRepeat, + D2DInterpolationMode(pat->mSamplingFilter)), + D2D1::BrushProperties(aOptions.mAlpha, D2DMatrix(mat)), + getter_AddRefs(imageBrush)); + + if (!imageBrush) { + gfxWarning() << "Couldn't create image brush!"; + return CreateTransparentBlackBrush(); + } + + return imageBrush.forget(); + } + + gfxWarning() << "Invalid pattern type detected."; + return CreateTransparentBlackBrush(); +} + +already_AddRefed<ID2D1Image> DrawTargetD2D1::GetImageForSurface( + SourceSurface* aSurface, Matrix& aSourceTransform, ExtendMode aExtendMode, + const IntRect* aSourceRect, bool aUserSpace) { + RefPtr<ID2D1Image> image; + RefPtr<SourceSurface> surface = aSurface->GetUnderlyingSurface(); + + if (!surface) { + return nullptr; + } + + switch (surface->GetType()) { + case SurfaceType::D2D1_1_IMAGE: { + SourceSurfaceD2D1* surf = static_cast<SourceSurfaceD2D1*>(surface.get()); + image = surf->GetImage(); + AddDependencyOnSource(surf); + } break; + default: { + RefPtr<DataSourceSurface> dataSurf = surface->GetDataSurface(); + if (!dataSurf) { + gfxWarning() << "Invalid surface type."; + return nullptr; + } + Matrix transform = aUserSpace ? mTransform : Matrix(); + return CreatePartialBitmapForSurface(dataSurf, transform, mSize, + aExtendMode, aSourceTransform, mDC, + aSourceRect); + } break; + } + + return image.forget(); +} + +already_AddRefed<SourceSurface> DrawTargetD2D1::OptimizeSourceSurface( + SourceSurface* aSurface) const { + if (aSurface->GetType() == SurfaceType::D2D1_1_IMAGE) { + RefPtr<SourceSurface> surface(aSurface); + return surface.forget(); + } + + RefPtr<ID2D1DeviceContext> dc = Factory::GetD2DDeviceContext(); + if (!dc) { + return nullptr; + } + + RefPtr<DataSourceSurface> data = aSurface->GetDataSurface(); + + std::optional<SurfaceFormat> convertTo; + switch (data->GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + convertTo = SurfaceFormat::B8G8R8X8; + break; + case gfx::SurfaceFormat::R8G8B8A8: + convertTo = SurfaceFormat::B8G8R8X8; + break; + default: + break; + } + + if (convertTo) { + const auto size = data->GetSize(); + const RefPtr<DrawTarget> dt = + Factory::CreateDrawTarget(BackendType::SKIA, size, *convertTo); + if (!dt) { + return nullptr; + } + dt->CopySurface(data, {{}, size}, {}); + + const RefPtr<SourceSurface> snapshot = dt->Snapshot(); + data = snapshot->GetDataSurface(); + } + + RefPtr<ID2D1Bitmap1> bitmap; + { + DataSourceSurface::ScopedMap map(data, DataSourceSurface::READ); + if (MOZ2D_WARN_IF(!map.IsMapped())) { + return nullptr; + } + + HRESULT hr = dc->CreateBitmap( + D2DIntSize(data->GetSize()), map.GetData(), map.GetStride(), + D2D1::BitmapProperties1(D2D1_BITMAP_OPTIONS_NONE, + D2DPixelFormat(data->GetFormat())), + getter_AddRefs(bitmap)); + + if (FAILED(hr)) { + gfxCriticalError(CriticalLog::DefaultOptions( + Factory::ReasonableSurfaceSize(data->GetSize()))) + << "[D2D1.1] 4CreateBitmap failure " << data->GetSize() + << " Code: " << hexa(hr) << " format " << (int)data->GetFormat(); + } + } + + if (!bitmap) { + return data.forget(); + } + + return MakeAndAddRef<SourceSurfaceD2D1>(bitmap.get(), dc.get(), + data->GetFormat(), data->GetSize()); +} + +void DrawTargetD2D1::PushD2DLayer(ID2D1DeviceContext* aDC, + ID2D1Geometry* aGeometry, + const D2D1_MATRIX_3X2_F& aTransform, + bool aPixelAligned, bool aForceIgnoreAlpha, + const D2D1_RECT_F& aMaxRect) { + D2D1_LAYER_OPTIONS1 options = D2D1_LAYER_OPTIONS1_NONE; + + if (CurrentLayer().mIsOpaque || aForceIgnoreAlpha) { + options = D2D1_LAYER_OPTIONS1_IGNORE_ALPHA | + D2D1_LAYER_OPTIONS1_INITIALIZE_FROM_BACKGROUND; + } + + D2D1_ANTIALIAS_MODE antialias = aPixelAligned + ? D2D1_ANTIALIAS_MODE_ALIASED + : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE; + + mDC->PushLayer(D2D1::LayerParameters1(aMaxRect, aGeometry, antialias, + aTransform, 1.0, nullptr, options), + nullptr); +} + +bool DrawTargetD2D1::IsDeviceContextValid() const { + uint32_t seqNo; + return mDC && Factory::GetD2D1Device(&seqNo) && seqNo == mDeviceSeq; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/DrawTargetD2D1.h b/gfx/2d/DrawTargetD2D1.h new file mode 100644 index 0000000000..efe571e588 --- /dev/null +++ b/gfx/2d/DrawTargetD2D1.h @@ -0,0 +1,339 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_DRAWTARGETD2D1_H_ +#define MOZILLA_GFX_DRAWTARGETD2D1_H_ + +#include "2D.h" +#include <d3d11.h> +#include <d2d1_1.h> +#include "PathD2D.h" +#include "HelpersD2D.h" +#include "mozilla/StaticPtr.h" + +#include <vector> +#include <sstream> + +#include <unordered_set> + +struct IDWriteFactory; + +namespace mozilla { +namespace gfx { + +class SourceSurfaceD2D1; + +const int32_t kLayerCacheSize1 = 5; + +class DrawTargetD2D1 : public DrawTarget { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetD2D1, override) + DrawTargetD2D1(); + virtual ~DrawTargetD2D1(); + + virtual bool IsValid() const override; + virtual DrawTargetType GetType() const override { + return DrawTargetType::HARDWARE_RASTER; + } + virtual BackendType GetBackendType() const override { + return BackendType::DIRECT2D1_1; + } + virtual already_AddRefed<SourceSurface> Snapshot() override; + virtual already_AddRefed<SourceSurface> IntoLuminanceSource( + LuminanceType aLuminanceType, float aOpacity) override; + virtual IntSize GetSize() const override { return mSize; } + + virtual void Flush() override; + virtual void DrawSurface(SourceSurface* aSurface, const Rect& aDest, + const Rect& aSource, + const DrawSurfaceOptions& aSurfOptions, + const DrawOptions& aOptions) override; + virtual void DrawFilter(FilterNode* aNode, const Rect& aSourceRect, + const Point& aDestPoint, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void DrawSurfaceWithShadow(SourceSurface* aSurface, + const Point& aDest, + const ShadowOptions& aShadow, + CompositionOp aOperator) override; + virtual void ClearRect(const Rect& aRect) override; + virtual void MaskSurface( + const Pattern& aSource, SourceSurface* aMask, Point aOffset, + const DrawOptions& aOptions = DrawOptions()) override; + + virtual void CopySurface(SourceSurface* aSurface, const IntRect& aSourceRect, + const IntPoint& aDestination) override; + + virtual void FillRect(const Rect& aRect, const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void FillRoundedRect( + const RoundedRect& aRect, const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()) override; + + virtual void StrokeRect(const Rect& aRect, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()) override; + virtual void StrokeLine(const Point& aStart, const Point& aEnd, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()) override; + virtual void StrokeCircle( + const Point& aOrigin, float radius, 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 FillCircle(const Point& aOrigin, float radius, + const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void Mask(const Pattern& aSource, const Pattern& aMask, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void PushClip(const Path* aPath) override; + virtual void PushClipRect(const Rect& aRect) override; + virtual void PushDeviceSpaceClipRects(const IntRect* aRects, + uint32_t aCount) override; + + virtual void PopClip() override; + virtual bool RemoveAllClips() override; + + virtual void PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds = IntRect(), + bool aCopyBackground = false) override; + virtual void PopLayer() override; + + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData( + unsigned char* aData, const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat) const override; + virtual already_AddRefed<SourceSurface> OptimizeSourceSurface( + SourceSurface* aSurface) const override; + + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromNativeSurface( + const NativeSurface& aSurface) const override { + return nullptr; + } + + virtual already_AddRefed<DrawTarget> CreateSimilarDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat) const override; + virtual bool CanCreateSimilarDrawTarget(const IntSize& aSize, + SurfaceFormat aFormat) const override; + virtual RefPtr<DrawTarget> CreateClippedDrawTarget( + const Rect& aBounds, SurfaceFormat aFormat) override; + + virtual already_AddRefed<PathBuilder> CreatePathBuilder( + FillRule aFillRule = FillRule::FILL_WINDING) const override { + return PathBuilderD2D::Create(aFillRule); + } + + virtual already_AddRefed<GradientStops> CreateGradientStops( + GradientStop* aStops, uint32_t aNumStops, + ExtendMode aExtendMode = ExtendMode::CLAMP) const override; + + virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override; + + virtual bool SupportsRegionClipping() const override { return false; } + virtual bool IsCurrentGroupOpaque() override { + return CurrentLayer().mIsOpaque; + } + + virtual void* GetNativeSurface(NativeSurfaceType aType) override { + return nullptr; + } + + virtual void DetachAllSnapshots() override { MarkChanged(); } + + bool Init(const IntSize& aSize, SurfaceFormat aFormat); + bool Init(ID3D11Texture2D* aTexture, SurfaceFormat aFormat); + uint32_t GetByteSize() const; + + // This function will get an image for a surface, it may adjust the source + // transform for any transformation of the resulting image relative to the + // oritingal SourceSurface. By default, the surface and its transform are + // interpreted in user-space, but may be specified in device-space instead. + already_AddRefed<ID2D1Image> GetImageForSurface( + SourceSurface* aSurface, Matrix& aSourceTransform, ExtendMode aExtendMode, + const IntRect* aSourceRect = nullptr, bool aUserSpace = true); + + already_AddRefed<ID2D1Image> GetImageForSurface(SourceSurface* aSurface, + ExtendMode aExtendMode) { + Matrix mat; + return GetImageForSurface(aSurface, mat, aExtendMode, nullptr); + } + + static RefPtr<ID2D1Factory1> factory(); + static void CleanupD2D(); + + operator std::string() const { + std::stringstream stream; + stream << "DrawTargetD2D 1.1 (" << this << ")"; + return stream.str(); + } + + static uint32_t GetMaxSurfaceSize() { + return D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION; + } + + static uint64_t mVRAMUsageDT; + static uint64_t mVRAMUsageSS; + + private: + friend class SourceSurfaceD2D1; + + void FlushInternal(bool aHasDependencyMutex = false); + bool EnsureInitialized(); + + typedef std::unordered_set<DrawTargetD2D1*> TargetSet; + + // This function will mark the surface as changing, and make sure any + // copy-on-write snapshots are notified. + void MarkChanged(); + bool ShouldClipTemporarySurfaceDrawing(CompositionOp aOp, + const Pattern& aPattern, + bool aClipIsComplex); + bool PrepareForDrawing(CompositionOp aOp, const Pattern& aPattern); + void FinalizeDrawing(CompositionOp aOp, const Pattern& aPattern); + bool MaybeClearRect(CompositionOp aOp, const Rect& aBounds); + void FlushTransformToDC() { + if (mTransformDirty) { + mDC->SetTransform(D2DMatrix(mTransform)); + mTransformDirty = false; + } + } + void AddDependencyOnSource(SourceSurfaceD2D1* aSource); + + // Must be called with all clips popped and an identity matrix set. + already_AddRefed<ID2D1Image> GetImageForLayerContent( + const IntRect* aBounds = nullptr, bool aShouldPreserveContent = true); + + ID2D1Image* CurrentTarget() { + if (CurrentLayer().mCurrentList) { + return CurrentLayer().mCurrentList; + } + return mBitmap; + } + + // This returns the clipped geometry, in addition it returns aClipBounds which + // represents the intersection of all pixel-aligned rectangular clips that + // are currently set. The returned clipped geometry must be clipped by these + // bounds to correctly reflect the total clip. This is in device space and + // only for clips applied to the -current layer-. + already_AddRefed<ID2D1Geometry> GetClippedGeometry(IntRect* aClipBounds); + + already_AddRefed<ID2D1Geometry> GetInverseClippedGeometry(); + + // This gives the device space clip rect applied to the -current layer-. + bool GetDeviceSpaceClipRect(D2D1_RECT_F& aClipRect, bool& aIsPixelAligned); + + void PopAllClips(); + void PushAllClips(); + void PushClipsToDC(ID2D1DeviceContext* aDC, bool aForceIgnoreAlpha = false, + const D2D1_RECT_F& aMaxRect = D2D1::InfiniteRect()); + void PopClipsFromDC(ID2D1DeviceContext* aDC); + + already_AddRefed<ID2D1Brush> CreateTransparentBlackBrush(); + already_AddRefed<ID2D1SolidColorBrush> GetSolidColorBrush( + const D2D_COLOR_F& aColor); + already_AddRefed<ID2D1Brush> CreateBrushForPattern( + const Pattern& aPattern, const DrawOptions& aOptions); + + void PushClipGeometry(ID2D1Geometry* aGeometry, + const D2D1_MATRIX_3X2_F& aTransform, + bool aPixelAligned = false); + + void PushD2DLayer(ID2D1DeviceContext* aDC, ID2D1Geometry* aGeometry, + const D2D1_MATRIX_3X2_F& aTransform, + bool aPixelAligned = false, bool aForceIgnoreAlpha = false, + const D2D1_RECT_F& aLayerRect = D2D1::InfiniteRect()); + + // This function is used to determine if the mDC is still valid; if it is + // stale, we should avoid using it to execute any draw commands. + bool IsDeviceContextValid() const; + + IntSize mSize; + + RefPtr<ID2D1Geometry> mCurrentClippedGeometry; + // This is only valid if mCurrentClippedGeometry is non-null. And will + // only be the intersection of all pixel-aligned retangular clips. This is in + // device space. + IntRect mCurrentClipBounds; + mutable RefPtr<ID2D1DeviceContext> mDC; + RefPtr<ID2D1Bitmap1> mBitmap; + RefPtr<ID2D1CommandList> mCommandList; + + RefPtr<ID2D1SolidColorBrush> mSolidColorBrush; + + // We store this to prevent excessive SetTextRenderingParams calls. + RefPtr<IDWriteRenderingParams> mTextRenderingParams; + + // List of pushed clips. + struct PushedClip { + D2D1_RECT_F mBounds; + // If mGeometry is non-null, the mTransform member will be used. + D2D1_MATRIX_3X2_F mTransform; + RefPtr<ID2D1Geometry> mGeometry; + // Indicates if mBounds, and when non-null, mGeometry with mTransform + // applied, are pixel-aligned. + bool mIsPixelAligned; + }; + + // List of pushed layers. + struct PushedLayer { + PushedLayer() + : mClipsArePushed(false), + mIsOpaque(false), + mOldPermitSubpixelAA(false) {} + + std::vector<PushedClip> mPushedClips; + RefPtr<ID2D1CommandList> mCurrentList; + // True if the current clip stack is pushed to the CurrentTarget(). + bool mClipsArePushed; + bool mIsOpaque; + bool mOldPermitSubpixelAA; + }; + std::vector<PushedLayer> mPushedLayers; + PushedLayer& CurrentLayer() { return mPushedLayers.back(); } + + // The latest snapshot of this surface. This needs to be told when this + // target is modified. We keep it alive as a cache. + RefPtr<SourceSurfaceD2D1> mSnapshot; + std::shared_ptr<Mutex> mSnapshotLock; + // A list of targets we need to flush when we're modified. + TargetSet mDependentTargets; + // A list of targets which have this object in their mDependentTargets set + TargetSet mDependingOnTargets; + + uint32_t mUsedCommandListsSincePurge; + uint32_t mTransformedGlyphsSinceLastPurge; + // When a BlendEffect has been drawn to a command list, and that command list + // is subsequently used -again- as an input to a blend effect for a command + // list, this causes an infinite recursion inside D2D as it tries to resolve + // the bounds. If we resolve the current command list before this happens we + // can avoid the subsequent hang. (See bug 1293586) + uint32_t mComplexBlendsWithListInList; + + static StaticRefPtr<ID2D1Factory1> mFactory; + // This value is uesed to verify if the DrawTarget is created by a stale + // device. + uint32_t mDeviceSeq; + + // List of effects we use + bool EnsureLuminanceEffect(); + RefPtr<ID2D1Effect> mLuminanceEffect; + + enum class InitState { Uninitialized, Success, Failure }; + InitState mInitState; + RefPtr<IDXGISurface> mSurface; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_DRAWTARGETD2D_H_ */ diff --git a/gfx/2d/DrawTargetOffset.cpp b/gfx/2d/DrawTargetOffset.cpp new file mode 100644 index 0000000000..cc49cf0493 --- /dev/null +++ b/gfx/2d/DrawTargetOffset.cpp @@ -0,0 +1,226 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DrawTargetOffset.h" +#include "Logging.h" +#include "PathHelpers.h" + +namespace mozilla { +namespace gfx { + +DrawTargetOffset::DrawTargetOffset() = default; + +bool DrawTargetOffset::Init(DrawTarget* aDrawTarget, IntPoint aOrigin) { + mDrawTarget = aDrawTarget; + mOrigin = aOrigin; + mDrawTarget->SetTransform(Matrix::Translation(-mOrigin.x, -mOrigin.y)); + mFormat = mDrawTarget->GetFormat(); + SetPermitSubpixelAA(IsOpaque(mFormat)); + return true; +} + +already_AddRefed<SourceSurface> DrawTargetOffset::Snapshot() { + RefPtr<SourceSurface> snapshot = mDrawTarget->Snapshot(); + + if (!snapshot) { + return nullptr; + } + + return MakeAndAddRef<SourceSurfaceOffset>(snapshot, mOrigin); +} + +void DrawTargetOffset::DetachAllSnapshots() {} + +// Skip the mClippedOut check since this is only used for Flush() which +// should happen even if we're clipped. +#define OFFSET_COMMAND(command) \ + void DrawTargetOffset::command() { mDrawTarget->command(); } +#define OFFSET_COMMAND1(command, type1) \ + void DrawTargetOffset::command(type1 arg1) { mDrawTarget->command(arg1); } +#define OFFSET_COMMAND3(command, type1, type2, type3) \ + void DrawTargetOffset::command(type1 arg1, type2 arg2, type3 arg3) { \ + mDrawTarget->command(arg1, arg2, arg3); \ + } +#define OFFSET_COMMAND4(command, type1, type2, type3, type4) \ + void DrawTargetOffset::command(type1 arg1, type2 arg2, type3 arg3, \ + type4 arg4) { \ + mDrawTarget->command(arg1, arg2, arg3, arg4); \ + } +#define OFFSET_COMMAND5(command, type1, type2, type3, type4, type5) \ + void DrawTargetOffset::command(type1 arg1, type2 arg2, type3 arg3, \ + type4 arg4, type5 arg5) { \ + mDrawTarget->command(arg1, arg2, arg3, arg4, arg5); \ + } + +OFFSET_COMMAND(Flush) +OFFSET_COMMAND1(ClearRect, const Rect&) +OFFSET_COMMAND4(MaskSurface, const Pattern&, SourceSurface*, Point, + const DrawOptions&) +OFFSET_COMMAND4(FillGlyphs, ScaledFont*, const GlyphBuffer&, const Pattern&, + const DrawOptions&) +OFFSET_COMMAND5(StrokeGlyphs, ScaledFont*, const GlyphBuffer&, const Pattern&, + const StrokeOptions&, const DrawOptions&) +OFFSET_COMMAND3(FillRoundedRect, const RoundedRect&, const Pattern&, + const DrawOptions&) + +bool DrawTargetOffset::Draw3DTransformedSurface(SourceSurface* aSrc, + const Matrix4x4& aMatrix) { + return mDrawTarget->Draw3DTransformedSurface(aSrc, aMatrix); +} + +OFFSET_COMMAND3(Mask, const Pattern&, const Pattern&, const DrawOptions&) + +void DrawTargetOffset::DrawFilter(FilterNode* aNode, const Rect& aSourceRect, + const Point& aDestPoint, + const DrawOptions& aOptions) { + auto clone = mTransform; + bool invertible = clone.Invert(); + // aSourceRect is in filter space. The filter outputs from aSourceRect need + // to be drawn at aDestPoint in user space. + Rect userSpaceSource = Rect(aDestPoint, aSourceRect.Size()); + if (invertible) { + // Try to reduce the source rect so that it's not much bigger + // than the draw target. The result is not minimal. Examples + // are left as an exercise for the reader. + auto destRect = Rect(mDrawTarget->GetRect() + mOrigin); + Rect userSpaceBounds = clone.TransformBounds(destRect); + userSpaceSource = userSpaceSource.Intersect(userSpaceBounds); + } + + // Compute how much we moved the top-left of the source rect by, and use that + // to compute the new dest point, and move our intersected source rect back + // into the (new) filter space. + Point shift = userSpaceSource.TopLeft() - aDestPoint; + Rect filterSpaceSource = + Rect(aSourceRect.TopLeft() + shift, userSpaceSource.Size()); + mDrawTarget->DrawFilter(aNode, filterSpaceSource, aDestPoint + shift, + aOptions); +} + +void DrawTargetOffset::PushClip(const Path* aPath) { + mDrawTarget->PushClip(aPath); +} + +void DrawTargetOffset::PushClipRect(const Rect& aRect) { + mDrawTarget->PushClipRect(aRect); +} + +void DrawTargetOffset::PopClip() { mDrawTarget->PopClip(); } + +void DrawTargetOffset::CopySurface(SourceSurface* aSurface, + const IntRect& aSourceRect, + const IntPoint& aDestination) { + IntPoint tileOrigin = mOrigin; + // CopySurface ignores the transform, account for that here. + mDrawTarget->CopySurface(aSurface, aSourceRect, aDestination - tileOrigin); +} + +void DrawTargetOffset::SetTransform(const Matrix& aTransform) { + Matrix mat = aTransform; + mat.PostTranslate(Float(-mOrigin.x), Float(-mOrigin.y)); + mDrawTarget->SetTransform(mat); + + DrawTarget::SetTransform(aTransform); +} + +void DrawTargetOffset::SetPermitSubpixelAA(bool aPermitSubpixelAA) { + mDrawTarget->SetPermitSubpixelAA(aPermitSubpixelAA); +} + +void DrawTargetOffset::DrawSurface(SourceSurface* aSurface, const Rect& aDest, + const Rect& aSource, + const DrawSurfaceOptions& aSurfaceOptions, + const DrawOptions& aDrawOptions) { + mDrawTarget->DrawSurface(aSurface, aDest, aSource, aSurfaceOptions, + aDrawOptions); +} + +void DrawTargetOffset::FillRect(const Rect& aRect, const Pattern& aPattern, + const DrawOptions& aDrawOptions) { + mDrawTarget->FillRect(aRect, aPattern, aDrawOptions); +} + +void DrawTargetOffset::Stroke(const Path* aPath, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aDrawOptions) { + mDrawTarget->Stroke(aPath, aPattern, aStrokeOptions, aDrawOptions); +} + +void DrawTargetOffset::StrokeRect(const Rect& aRect, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aDrawOptions) { + mDrawTarget->StrokeRect(aRect, aPattern, aStrokeOptions, aDrawOptions); +} + +void DrawTargetOffset::StrokeLine(const Point& aStart, const Point& aEnd, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aDrawOptions) { + mDrawTarget->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, aDrawOptions); +} + +void DrawTargetOffset::Fill(const Path* aPath, const Pattern& aPattern, + const DrawOptions& aDrawOptions) { + mDrawTarget->Fill(aPath, aPattern, aDrawOptions); +} + +void DrawTargetOffset::PushLayer(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds, bool aCopyBackground) { + IntRect bounds = aBounds - mOrigin; + + mDrawTarget->PushLayer(aOpaque, aOpacity, aMask, aMaskTransform, bounds, + aCopyBackground); + SetPermitSubpixelAA(mDrawTarget->GetPermitSubpixelAA()); +} + +already_AddRefed<SourceSurface> DrawTargetOffset::IntoLuminanceSource( + LuminanceType aLuminanceType, float aOpacity) { + RefPtr<SourceSurface> surface = + mDrawTarget->IntoLuminanceSource(aLuminanceType, aOpacity); + + if (!surface) { + return nullptr; + } + + return MakeAndAddRef<SourceSurfaceOffset>(surface, mOrigin); +} + +void DrawTargetOffset::PushLayerWithBlend(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds, + bool aCopyBackground, + CompositionOp aOp) { + IntRect bounds = aBounds - mOrigin; + + mDrawTarget->PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform, + bounds, aCopyBackground, aOp); + SetPermitSubpixelAA(mDrawTarget->GetPermitSubpixelAA()); +} + +void DrawTargetOffset::PopLayer() { + mDrawTarget->PopLayer(); + SetPermitSubpixelAA(mDrawTarget->GetPermitSubpixelAA()); +} + +RefPtr<DrawTarget> DrawTargetOffset::CreateClippedDrawTarget( + const Rect& aBounds, SurfaceFormat aFormat) { + RefPtr<DrawTarget> result; + RefPtr<DrawTarget> dt = + mDrawTarget->CreateClippedDrawTarget(aBounds, aFormat); + if (dt) { + result = gfx::Factory::CreateOffsetDrawTarget(dt, mOrigin); + if (result) { + result->SetTransform(mTransform); + } + } + return result; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/DrawTargetOffset.h b/gfx/2d/DrawTargetOffset.h new file mode 100644 index 0000000000..3356cc15bf --- /dev/null +++ b/gfx/2d/DrawTargetOffset.h @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_DRAWTARGETOFFSET_H_ +#define MOZILLA_GFX_DRAWTARGETOFFSET_H_ + +#include "2D.h" + +#include "mozilla/Vector.h" + +#include "Filters.h" +#include "Logging.h" + +#include <vector> + +namespace mozilla { +namespace gfx { + +class SourceSurfaceOffset : public SourceSurface { + public: + SourceSurfaceOffset(RefPtr<SourceSurface> aSurface, IntPoint aOffset) + : mSurface(aSurface), mOffset(aOffset) { + MOZ_RELEASE_ASSERT(mSurface); + } + + virtual SurfaceType GetType() const override { return SurfaceType::OFFSET; } + virtual IntSize GetSize() const override { return mSurface->GetSize(); } + virtual IntRect GetRect() const override { + return mSurface->GetRect() + mOffset; + } + virtual SurfaceFormat GetFormat() const override { + return mSurface->GetFormat(); + } + virtual already_AddRefed<DataSourceSurface> GetDataSurface() override { + return mSurface->GetDataSurface(); + } + virtual already_AddRefed<SourceSurface> GetUnderlyingSurface() override { + return mSurface->GetUnderlyingSurface(); + } + + private: + RefPtr<SourceSurface> mSurface; + IntPoint mOffset; +}; + +class DrawTargetOffset : public DrawTarget { + public: + DrawTargetOffset(); + + bool Init(DrawTarget* aDrawTarget, IntPoint aOrigin); + + // We'll pestimistically return true here + virtual bool IsTiledDrawTarget() const override { return true; } + + virtual DrawTargetType GetType() const override { + return mDrawTarget->GetType(); + } + virtual BackendType GetBackendType() const override { + return mDrawTarget->GetBackendType(); + } + virtual already_AddRefed<SourceSurface> Snapshot() override; + virtual already_AddRefed<SourceSurface> IntoLuminanceSource( + LuminanceType aLuminanceType, float aOpacity) override; + virtual void DetachAllSnapshots() override; + virtual IntSize GetSize() const override { return mDrawTarget->GetSize(); } + virtual IntRect GetRect() const override { + return mDrawTarget->GetRect() + mOrigin; + } + + virtual void Flush() override; + virtual void DrawSurface(SourceSurface* aSurface, const Rect& aDest, + const Rect& aSource, + const DrawSurfaceOptions& aSurfOptions, + const DrawOptions& aOptions) override; + virtual void DrawFilter(FilterNode* aNode, const Rect& aSourceRect, + const Point& aDestPoint, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void DrawSurfaceWithShadow( + SourceSurface* aSurface, const Point& aDest, const ShadowOptions& aShadow, + CompositionOp aOperator) override { /* Not implemented */ + MOZ_CRASH("GFX: DrawSurfaceWithShadow"); + } + + virtual void ClearRect(const Rect& aRect) override; + virtual void MaskSurface( + const Pattern& aSource, SourceSurface* aMask, Point aOffset, + const DrawOptions& aOptions = DrawOptions()) override; + + virtual void CopySurface(SourceSurface* aSurface, const IntRect& aSourceRect, + const IntPoint& aDestination) override; + + virtual void FillRect(const Rect& aRect, const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void FillRoundedRect( + const RoundedRect& aRect, const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void StrokeRect(const Rect& aRect, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()) override; + virtual void StrokeLine(const Point& aStart, const Point& aEnd, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()) override; + virtual void Stroke(const Path* aPath, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()) override; + virtual void Fill(const Path* aPath, const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void StrokeGlyphs( + ScaledFont* aFont, const GlyphBuffer& aBuffer, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()) override; + virtual void Mask(const Pattern& aSource, const Pattern& aMask, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void PushClip(const Path* aPath) override; + virtual void PushClipRect(const Rect& aRect) override; + virtual void PopClip() override; + virtual void PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds = IntRect(), + bool aCopyBackground = false) override; + virtual void PushLayerWithBlend( + bool aOpaque, Float aOpacity, SourceSurface* aMask, + const Matrix& aMaskTransform, const IntRect& aBounds = IntRect(), + bool aCopyBackground = false, + CompositionOp = CompositionOp::OP_OVER) override; + virtual bool Draw3DTransformedSurface(SourceSurface* aSurface, + const Matrix4x4& aMatrix) override; + virtual void PopLayer() override; + + virtual void SetTransform(const Matrix& aTransform) override; + + virtual void SetPermitSubpixelAA(bool aPermitSubpixelAA) override; + + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData( + unsigned char* aData, const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat) const override { + return mDrawTarget->CreateSourceSurfaceFromData(aData, aSize, aStride, + aFormat); + } + virtual already_AddRefed<SourceSurface> OptimizeSourceSurface( + SourceSurface* aSurface) const override { + return mDrawTarget->OptimizeSourceSurface(aSurface); + } + + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromNativeSurface( + const NativeSurface& aSurface) const override { + return mDrawTarget->CreateSourceSurfaceFromNativeSurface(aSurface); + } + + virtual already_AddRefed<DrawTarget> CreateSimilarDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat) const override { + return mDrawTarget->CreateSimilarDrawTarget(aSize, aFormat); + } + + virtual bool CanCreateSimilarDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat) const override { + return mDrawTarget->CanCreateSimilarDrawTarget(aSize, aFormat); + } + virtual RefPtr<DrawTarget> CreateClippedDrawTarget( + const Rect& aBounds, SurfaceFormat aFormat) override; + + virtual already_AddRefed<PathBuilder> CreatePathBuilder( + FillRule aFillRule = FillRule::FILL_WINDING) const override { + return mDrawTarget->CreatePathBuilder(aFillRule); + } + + virtual already_AddRefed<GradientStops> CreateGradientStops( + GradientStop* aStops, uint32_t aNumStops, + ExtendMode aExtendMode = ExtendMode::CLAMP) const override { + return mDrawTarget->CreateGradientStops(aStops, aNumStops, aExtendMode); + } + virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override { + return mDrawTarget->CreateFilter(aType); + } + + private: + RefPtr<DrawTarget> mDrawTarget; + IntPoint mOrigin; +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/DrawTargetRecording.cpp b/gfx/2d/DrawTargetRecording.cpp new file mode 100644 index 0000000000..6edc8cdf91 --- /dev/null +++ b/gfx/2d/DrawTargetRecording.cpp @@ -0,0 +1,936 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DrawTargetRecording.h" +#include "DrawTargetSkia.h" +#include "PathRecording.h" +#include <stdio.h> + +#include "Logging.h" +#include "Tools.h" +#include "Filters.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/layers/CanvasDrawEventRecorder.h" +#include "mozilla/layers/RecordedCanvasEventImpl.h" +#include "mozilla/layers/SourceSurfaceSharedData.h" +#include "mozilla/UniquePtr.h" +#include "nsXULAppAPI.h" // for XRE_IsContentProcess() +#include "RecordingTypes.h" +#include "RecordedEventImpl.h" + +namespace mozilla { +namespace gfx { + +struct RecordingSourceSurfaceUserData { + void* refPtr; + RefPtr<DrawEventRecorderPrivate> recorder; + + // The optimized surface holds a reference to our surface, for GetDataSurface + // calls, so we must hold a weak reference to avoid circular dependency. + ThreadSafeWeakPtr<SourceSurface> optimizedSurface; +}; + +static void RecordingSourceSurfaceUserDataFunc(void* aUserData) { + RecordingSourceSurfaceUserData* userData = + static_cast<RecordingSourceSurfaceUserData*>(aUserData); + + if (NS_IsMainThread()) { + userData->recorder->RecordSourceSurfaceDestruction(userData->refPtr); + delete userData; + return; + } + + userData->recorder->AddPendingDeletion([userData]() -> void { + userData->recorder->RecordSourceSurfaceDestruction(userData->refPtr); + delete userData; + }); +} + +static bool EnsureSurfaceStoredRecording(DrawEventRecorderPrivate* aRecorder, + SourceSurface* aSurface, + const char* reason) { + // It's important that TryAddStoredObject is called first because that will + // run any pending processing required by recorded objects that have been + // deleted off the main thread. + if (!aRecorder->TryAddStoredObject(aSurface)) { + // Surface is already stored. + return false; + } + aRecorder->StoreSourceSurfaceRecording(aSurface, reason); + aRecorder->AddSourceSurface(aSurface); + + RecordingSourceSurfaceUserData* userData = new RecordingSourceSurfaceUserData; + userData->refPtr = aSurface; + userData->recorder = aRecorder; + aSurface->AddUserData(reinterpret_cast<UserDataKey*>(aRecorder), userData, + &RecordingSourceSurfaceUserDataFunc); + return true; +} + +class SourceSurfaceRecording : public SourceSurface { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceRecording, override) + + SourceSurfaceRecording(IntSize aSize, SurfaceFormat aFormat, + DrawEventRecorderPrivate* aRecorder, + SourceSurface* aOriginalSurface = nullptr) + : mSize(aSize), + mFormat(aFormat), + mRecorder(aRecorder), + mOriginalSurface(aOriginalSurface) { + mRecorder->AddStoredObject(this); + } + + ~SourceSurfaceRecording() { + mRecorder->RemoveStoredObject(this); + mRecorder->RecordEvent( + RecordedSourceSurfaceDestruction(ReferencePtr(this))); + } + + SurfaceType GetType() const override { return SurfaceType::RECORDING; } + IntSize GetSize() const override { return mSize; } + SurfaceFormat GetFormat() const override { return mFormat; } + already_AddRefed<DataSourceSurface> GetDataSurface() override { + if (mOriginalSurface) { + return mOriginalSurface->GetDataSurface(); + } + + return nullptr; + } + + already_AddRefed<SourceSurface> ExtractSubrect(const IntRect& aRect) override; + + IntSize mSize; + SurfaceFormat mFormat; + RefPtr<DrawEventRecorderPrivate> mRecorder; + // If a SourceSurfaceRecording is returned from an OptimizeSourceSurface call + // we need GetDataSurface to work, so we hold the original surface we + // optimized to return its GetDataSurface. + RefPtr<SourceSurface> mOriginalSurface; +}; + +class GradientStopsRecording : public GradientStops { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsRecording, override) + + explicit GradientStopsRecording(DrawEventRecorderPrivate* aRecorder) + : mRecorder(aRecorder) { + mRecorder->AddStoredObject(this); + } + + virtual ~GradientStopsRecording() { + mRecorder->RemoveStoredObject(this); + mRecorder->RecordEvent( + RecordedGradientStopsDestruction(ReferencePtr(this))); + } + + BackendType GetBackendType() const override { return BackendType::RECORDING; } + + RefPtr<DrawEventRecorderPrivate> mRecorder; +}; + +class FilterNodeRecording : public FilterNode { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeRecording, override) + using FilterNode::SetAttribute; + + explicit FilterNodeRecording(DrawEventRecorderPrivate* aRecorder) + : mRecorder(aRecorder) { + mRecorder->AddStoredObject(this); + } + + virtual ~FilterNodeRecording() { + mRecorder->RemoveStoredObject(this); + mRecorder->RecordEvent(RecordedFilterNodeDestruction(ReferencePtr(this))); + } + + void SetInput(uint32_t aIndex, SourceSurface* aSurface) override { + EnsureSurfaceStoredRecording(mRecorder, aSurface, "SetInput"); + + mRecorder->RecordEvent(RecordedFilterNodeSetInput(this, aIndex, aSurface)); + } + void SetInput(uint32_t aIndex, FilterNode* aFilter) override { + MOZ_ASSERT(mRecorder->HasStoredObject(aFilter)); + + mRecorder->RecordEvent(RecordedFilterNodeSetInput(this, aIndex, aFilter)); + } + +#define FORWARD_SET_ATTRIBUTE(type, argtype) \ + void SetAttribute(uint32_t aIndex, type aValue) override { \ + mRecorder->RecordEvent(RecordedFilterNodeSetAttribute( \ + this, aIndex, aValue, \ + RecordedFilterNodeSetAttribute::ARGTYPE_##argtype)); \ + } + + FORWARD_SET_ATTRIBUTE(bool, BOOL); + FORWARD_SET_ATTRIBUTE(uint32_t, UINT32); + FORWARD_SET_ATTRIBUTE(Float, FLOAT); + FORWARD_SET_ATTRIBUTE(const Size&, SIZE); + FORWARD_SET_ATTRIBUTE(const IntSize&, INTSIZE); + FORWARD_SET_ATTRIBUTE(const IntPoint&, INTPOINT); + FORWARD_SET_ATTRIBUTE(const Rect&, RECT); + FORWARD_SET_ATTRIBUTE(const IntRect&, INTRECT); + FORWARD_SET_ATTRIBUTE(const Point&, POINT); + FORWARD_SET_ATTRIBUTE(const Matrix&, MATRIX); + FORWARD_SET_ATTRIBUTE(const Matrix5x4&, MATRIX5X4); + FORWARD_SET_ATTRIBUTE(const Point3D&, POINT3D); + FORWARD_SET_ATTRIBUTE(const DeviceColor&, COLOR); + +#undef FORWARD_SET_ATTRIBUTE + + void SetAttribute(uint32_t aIndex, const Float* aFloat, + uint32_t aSize) override { + mRecorder->RecordEvent( + RecordedFilterNodeSetAttribute(this, aIndex, aFloat, aSize)); + } + + FilterBackend GetBackendType() override { return FILTER_BACKEND_RECORDING; } + + RefPtr<DrawEventRecorderPrivate> mRecorder; +}; + +DrawTargetRecording::DrawTargetRecording( + layers::CanvasDrawEventRecorder* aRecorder, int64_t aTextureId, + const layers::RemoteTextureOwnerId& aTextureOwnerId, DrawTarget* aDT, + const IntSize& aSize) + : mRecorder(static_cast<DrawEventRecorderPrivate*>(aRecorder)), + mFinalDT(aDT), + mRect(IntPoint(0, 0), aSize) { + mRecorder->RecordEvent(layers::RecordedCanvasDrawTargetCreation( + this, aTextureId, aTextureOwnerId, mFinalDT->GetBackendType(), aSize, + mFinalDT->GetFormat())); + mFormat = mFinalDT->GetFormat(); + DrawTarget::SetPermitSubpixelAA(IsOpaque(mFormat)); +} + +DrawTargetRecording::DrawTargetRecording(DrawEventRecorder* aRecorder, + DrawTarget* aDT, IntRect aRect, + bool aHasData) + : mRecorder(static_cast<DrawEventRecorderPrivate*>(aRecorder)), + mFinalDT(aDT), + mRect(aRect) { + MOZ_DIAGNOSTIC_ASSERT(aRecorder->GetRecorderType() != RecorderType::CANVAS); + RefPtr<SourceSurface> snapshot = aHasData ? mFinalDT->Snapshot() : nullptr; + mRecorder->RecordEvent( + RecordedDrawTargetCreation(this, mFinalDT->GetBackendType(), mRect, + mFinalDT->GetFormat(), aHasData, snapshot)); + mFormat = mFinalDT->GetFormat(); + DrawTarget::SetPermitSubpixelAA(IsOpaque(mFormat)); +} + +DrawTargetRecording::DrawTargetRecording(const DrawTargetRecording* aDT, + IntRect aRect, SurfaceFormat aFormat) + : mRecorder(aDT->mRecorder), mFinalDT(aDT->mFinalDT), mRect(aRect) { + mFormat = aFormat; + DrawTarget::SetPermitSubpixelAA(IsOpaque(mFormat)); +} + +DrawTargetRecording::~DrawTargetRecording() { + mRecorder->RecordEvent(RecordedDrawTargetDestruction(ReferencePtr(this))); + mRecorder->ClearDrawTarget(this); +} + +void DrawTargetRecording::Link(const char* aDestination, const Rect& aRect) { + MarkChanged(); + + mRecorder->RecordEvent(this, RecordedLink(aDestination, aRect)); +} + +void DrawTargetRecording::Destination(const char* aDestination, + const Point& aPoint) { + MarkChanged(); + + mRecorder->RecordEvent(this, RecordedDestination(aDestination, aPoint)); +} + +void DrawTargetRecording::FillRect(const Rect& aRect, const Pattern& aPattern, + const DrawOptions& aOptions) { + MarkChanged(); + + EnsurePatternDependenciesStored(aPattern); + + mRecorder->RecordEvent(this, RecordedFillRect(aRect, aPattern, aOptions)); +} + +void DrawTargetRecording::StrokeRect(const Rect& aRect, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) { + MarkChanged(); + + EnsurePatternDependenciesStored(aPattern); + + mRecorder->RecordEvent( + this, RecordedStrokeRect(aRect, aPattern, aStrokeOptions, aOptions)); +} + +void DrawTargetRecording::StrokeLine(const Point& aBegin, const Point& aEnd, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) { + MarkChanged(); + + EnsurePatternDependenciesStored(aPattern); + + mRecorder->RecordEvent(this, RecordedStrokeLine(aBegin, aEnd, aPattern, + aStrokeOptions, aOptions)); +} + +void DrawTargetRecording::Fill(const Path* aPath, const Pattern& aPattern, + const DrawOptions& aOptions) { + if (!aPath) { + return; + } + + MarkChanged(); + + if (aPath->GetBackendType() == BackendType::RECORDING) { + const PathRecording* path = static_cast<const PathRecording*>(aPath); + auto circle = path->AsCircle(); + if (circle) { + EnsurePatternDependenciesStored(aPattern); + mRecorder->RecordEvent( + this, RecordedFillCircle(circle.value(), aPattern, aOptions)); + return; + } + } + + RefPtr<PathRecording> pathRecording = EnsurePathStored(aPath); + EnsurePatternDependenciesStored(aPattern); + + mRecorder->RecordEvent(this, RecordedFill(pathRecording, aPattern, aOptions)); +} + +struct RecordingFontUserData { + void* refPtr; + void* unscaledFont; + RefPtr<DrawEventRecorderPrivate> recorder; +}; + +static void RecordingFontUserDataDestroyFunc(void* aUserData) { + RecordingFontUserData* userData = + static_cast<RecordingFontUserData*>(aUserData); + + userData->recorder->RecordEvent( + RecordedScaledFontDestruction(ReferencePtr(userData->refPtr))); + userData->recorder->RemoveScaledFont((ScaledFont*)userData->refPtr); + userData->recorder->DecrementUnscaledFontRefCount(userData->unscaledFont); + delete userData; +} + +void DrawTargetRecording::DrawGlyphs(ScaledFont* aFont, + const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const DrawOptions& aOptions, + const StrokeOptions* aStrokeOptions) { + if (!aFont) { + return; + } + + MarkChanged(); + + EnsurePatternDependenciesStored(aPattern); + + UserDataKey* userDataKey = reinterpret_cast<UserDataKey*>(mRecorder.get()); + if (mRecorder->WantsExternalFonts()) { + mRecorder->AddScaledFont(aFont); + } else if (!aFont->GetUserData(userDataKey)) { + UnscaledFont* unscaledFont = aFont->GetUnscaledFont(); + if (mRecorder->IncrementUnscaledFontRefCount(unscaledFont) == 0) { + // Prefer sending the description, if we can create one. This ensures + // we don't record the data of system fonts which saves time and can + // prevent duplicate copies from accumulating in the OS cache during + // playback. + RecordedFontDescriptor fontDesc(unscaledFont); + if (fontDesc.IsValid()) { + mRecorder->RecordEvent(fontDesc); + } else { + RecordedFontData fontData(unscaledFont); + RecordedFontDetails fontDetails; + if (fontData.GetFontDetails(fontDetails)) { + // Try to serialise the whole font, just in case this is a web font + // that is not present on the system. + if (!mRecorder->HasStoredFontData(fontDetails.fontDataKey)) { + mRecorder->RecordEvent(fontData); + mRecorder->AddStoredFontData(fontDetails.fontDataKey); + } + mRecorder->RecordEvent( + RecordedUnscaledFontCreation(unscaledFont, fontDetails)); + } else { + gfxWarning() << "DrawTargetRecording::FillGlyphs failed to serialise " + "UnscaledFont"; + } + } + } + mRecorder->RecordEvent(RecordedScaledFontCreation(aFont, unscaledFont)); + RecordingFontUserData* userData = new RecordingFontUserData; + userData->refPtr = aFont; + userData->unscaledFont = unscaledFont; + userData->recorder = mRecorder; + aFont->AddUserData(userDataKey, userData, + &RecordingFontUserDataDestroyFunc); + userData->recorder->AddScaledFont(aFont); + } + + if (aStrokeOptions) { + mRecorder->RecordEvent( + this, RecordedStrokeGlyphs(aFont, aPattern, *aStrokeOptions, aOptions, + aBuffer.mGlyphs, aBuffer.mNumGlyphs)); + } else { + mRecorder->RecordEvent( + this, RecordedFillGlyphs(aFont, aPattern, aOptions, aBuffer.mGlyphs, + aBuffer.mNumGlyphs)); + } +} + +void DrawTargetRecording::FillGlyphs(ScaledFont* aFont, + const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const DrawOptions& aOptions) { + DrawGlyphs(aFont, aBuffer, aPattern, aOptions); +} + +void DrawTargetRecording::StrokeGlyphs(ScaledFont* aFont, + const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) { + DrawGlyphs(aFont, aBuffer, aPattern, aOptions, &aStrokeOptions); +} + +void DrawTargetRecording::Mask(const Pattern& aSource, const Pattern& aMask, + const DrawOptions& aOptions) { + MarkChanged(); + + EnsurePatternDependenciesStored(aSource); + EnsurePatternDependenciesStored(aMask); + + mRecorder->RecordEvent(this, RecordedMask(aSource, aMask, aOptions)); +} + +void DrawTargetRecording::MaskSurface(const Pattern& aSource, + SourceSurface* aMask, Point aOffset, + const DrawOptions& aOptions) { + if (!aMask) { + return; + } + + MarkChanged(); + + EnsurePatternDependenciesStored(aSource); + EnsureSurfaceStoredRecording(mRecorder, aMask, "MaskSurface"); + + mRecorder->RecordEvent( + this, RecordedMaskSurface(aSource, aMask, aOffset, aOptions)); +} + +void DrawTargetRecording::Stroke(const Path* aPath, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) { + MarkChanged(); + + if (aPath->GetBackendType() == BackendType::RECORDING) { + const PathRecording* path = static_cast<const PathRecording*>(aPath); + auto circle = path->AsCircle(); + if (circle && circle->closed) { + EnsurePatternDependenciesStored(aPattern); + mRecorder->RecordEvent( + this, RecordedStrokeCircle(circle.value(), aPattern, aStrokeOptions, + aOptions)); + return; + } + + auto line = path->AsLine(); + if (line) { + EnsurePatternDependenciesStored(aPattern); + mRecorder->RecordEvent( + this, RecordedStrokeLine(line->origin, line->destination, aPattern, + aStrokeOptions, aOptions)); + return; + } + } + + RefPtr<PathRecording> pathRecording = EnsurePathStored(aPath); + EnsurePatternDependenciesStored(aPattern); + + mRecorder->RecordEvent( + this, RecordedStroke(pathRecording, aPattern, aStrokeOptions, aOptions)); +} + +void DrawTargetRecording::DrawShadow(const Path* aPath, const Pattern& aPattern, + const ShadowOptions& aShadow, + const DrawOptions& aOptions, + const StrokeOptions* aStrokeOptions) { + MarkChanged(); + + RefPtr<PathRecording> pathRecording = EnsurePathStored(aPath); + EnsurePatternDependenciesStored(aPattern); + + mRecorder->RecordEvent( + this, RecordedDrawShadow(pathRecording, aPattern, aShadow, aOptions, + aStrokeOptions)); +} + +void DrawTargetRecording::MarkChanged() { mIsDirty = true; } + +already_AddRefed<SourceSurface> DrawTargetRecording::Snapshot() { + RefPtr<SourceSurface> retSurf = + new SourceSurfaceRecording(mRect.Size(), mFormat, mRecorder); + + mRecorder->RecordEvent(this, RecordedSnapshot(ReferencePtr(retSurf))); + + return retSurf.forget(); +} + +already_AddRefed<SourceSurface> DrawTargetRecording::IntoLuminanceSource( + LuminanceType aLuminanceType, float aOpacity) { + RefPtr<SourceSurface> retSurf = + new SourceSurfaceRecording(mRect.Size(), SurfaceFormat::A8, mRecorder); + + mRecorder->RecordEvent( + this, RecordedIntoLuminanceSource(retSurf, aLuminanceType, aOpacity)); + + return retSurf.forget(); +} + +already_AddRefed<SourceSurface> SourceSurfaceRecording::ExtractSubrect( + const IntRect& aRect) { + if (aRect.IsEmpty() || !GetRect().Contains(aRect)) { + return nullptr; + } + + RefPtr<SourceSurface> subSurf = + new SourceSurfaceRecording(aRect.Size(), mFormat, mRecorder); + mRecorder->RecordEvent(RecordedExtractSubrect(subSurf, this, aRect)); + return subSurf.forget(); +} + +void DrawTargetRecording::Flush() { + mRecorder->RecordEvent(this, RecordedFlush()); +} + +void DrawTargetRecording::DetachAllSnapshots() { + mRecorder->RecordEvent(this, RecordedDetachAllSnapshots()); +} + +void DrawTargetRecording::DrawSurface(SourceSurface* aSurface, + const Rect& aDest, const Rect& aSource, + const DrawSurfaceOptions& aSurfOptions, + const DrawOptions& aOptions) { + if (!aSurface) { + return; + } + + MarkChanged(); + + EnsureSurfaceStoredRecording(mRecorder, aSurface, "DrawSurface"); + + mRecorder->RecordEvent(this, RecordedDrawSurface(aSurface, aDest, aSource, + aSurfOptions, aOptions)); +} + +void DrawTargetRecording::DrawDependentSurface(uint64_t aId, + const Rect& aDest) { + MarkChanged(); + + mRecorder->AddDependentSurface(aId); + mRecorder->RecordEvent(this, RecordedDrawDependentSurface(aId, aDest)); +} + +void DrawTargetRecording::DrawSurfaceWithShadow(SourceSurface* aSurface, + const Point& aDest, + const ShadowOptions& aShadow, + CompositionOp aOp) { + if (!aSurface) { + return; + } + + MarkChanged(); + + EnsureSurfaceStoredRecording(mRecorder, aSurface, "DrawSurfaceWithShadow"); + + mRecorder->RecordEvent( + this, RecordedDrawSurfaceWithShadow(aSurface, aDest, aShadow, aOp)); +} + +void DrawTargetRecording::DrawFilter(FilterNode* aNode, const Rect& aSourceRect, + const Point& aDestPoint, + const DrawOptions& aOptions) { + if (!aNode) { + return; + } + + MarkChanged(); + + MOZ_ASSERT(mRecorder->HasStoredObject(aNode)); + + mRecorder->RecordEvent( + this, RecordedDrawFilter(aNode, aSourceRect, aDestPoint, aOptions)); +} + +already_AddRefed<FilterNode> DrawTargetRecording::CreateFilter( + FilterType aType) { + RefPtr<FilterNode> retNode = new FilterNodeRecording(mRecorder); + + mRecorder->RecordEvent(this, RecordedFilterNodeCreation(retNode, aType)); + + return retNode.forget(); +} + +void DrawTargetRecording::ClearRect(const Rect& aRect) { + MarkChanged(); + + mRecorder->RecordEvent(this, RecordedClearRect(aRect)); +} + +void DrawTargetRecording::CopySurface(SourceSurface* aSurface, + const IntRect& aSourceRect, + const IntPoint& aDestination) { + if (!aSurface) { + return; + } + + MarkChanged(); + + EnsureSurfaceStoredRecording(mRecorder, aSurface, "CopySurface"); + + mRecorder->RecordEvent( + this, RecordedCopySurface(aSurface, aSourceRect, aDestination)); +} + +void DrawTargetRecording::PushClip(const Path* aPath) { + if (!aPath) { + return; + } + + // The canvas doesn't have a clipRect API so we always end up in the generic + // path. The D2D backend doesn't have a good way of specializing rectangular + // clips so we take advantage of the fact that aPath is usually backed by a + // SkiaPath which implements AsRect() and specialize it here. + auto rect = aPath->AsRect(); + if (rect.isSome()) { + PushClipRect(rect.value()); + return; + } + + RefPtr<PathRecording> pathRecording = EnsurePathStored(aPath); + + mRecorder->RecordEvent(this, RecordedPushClip(ReferencePtr(pathRecording))); +} + +void DrawTargetRecording::PushClipRect(const Rect& aRect) { + mRecorder->RecordEvent(this, RecordedPushClipRect(aRect)); +} + +void DrawTargetRecording::PopClip() { + mRecorder->RecordEvent(this, RecordedPopClip()); +} + +void DrawTargetRecording::PushLayer(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds, + bool aCopyBackground) { + if (aMask) { + EnsureSurfaceStoredRecording(mRecorder, aMask, "PushLayer"); + } + + mRecorder->RecordEvent( + this, RecordedPushLayer(aOpaque, aOpacity, aMask, aMaskTransform, aBounds, + aCopyBackground)); + + PushedLayer layer(GetPermitSubpixelAA()); + mPushedLayers.push_back(layer); + DrawTarget::SetPermitSubpixelAA(aOpaque); +} + +void DrawTargetRecording::PushLayerWithBlend(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds, + bool aCopyBackground, + CompositionOp aCompositionOp) { + if (aMask) { + EnsureSurfaceStoredRecording(mRecorder, aMask, "PushLayer"); + } + + mRecorder->RecordEvent(this, RecordedPushLayerWithBlend( + aOpaque, aOpacity, aMask, aMaskTransform, + aBounds, aCopyBackground, aCompositionOp)); + + PushedLayer layer(GetPermitSubpixelAA()); + mPushedLayers.push_back(layer); + DrawTarget::SetPermitSubpixelAA(aOpaque); +} + +void DrawTargetRecording::PopLayer() { + MarkChanged(); + + mRecorder->RecordEvent(this, RecordedPopLayer()); + + const PushedLayer& layer = mPushedLayers.back(); + DrawTarget::SetPermitSubpixelAA(layer.mOldPermitSubpixelAA); + mPushedLayers.pop_back(); +} + +already_AddRefed<SourceSurface> +DrawTargetRecording::CreateSourceSurfaceFromData(unsigned char* aData, + const IntSize& aSize, + int32_t aStride, + SurfaceFormat aFormat) const { + RefPtr<SourceSurface> surface = CreateDataSourceSurfaceWithStrideFromData( + aSize, aFormat, aStride, aData, aStride); + if (!surface) { + return nullptr; + } + + return OptimizeSourceSurface(surface); +} + +already_AddRefed<SourceSurface> DrawTargetRecording::OptimizeSourceSurface( + SourceSurface* aSurface) const { + // See if we have a previously optimized surface available. We have to do this + // check before the SurfaceType::RECORDING below, because aSurface might be a + // SurfaceType::RECORDING from another recorder we have previously optimized. + auto* userData = static_cast<RecordingSourceSurfaceUserData*>( + aSurface->GetUserData(reinterpret_cast<UserDataKey*>(mRecorder.get()))); + if (userData) { + RefPtr<SourceSurface> strongRef(userData->optimizedSurface); + if (strongRef) { + return do_AddRef(strongRef); + } + } else { + if (!EnsureSurfaceStoredRecording(mRecorder, aSurface, + "OptimizeSourceSurface")) { + // Surface was already stored, but doesn't have UserData so must be one + // of our recording surfaces. + MOZ_ASSERT(aSurface->GetType() == SurfaceType::RECORDING); + return do_AddRef(aSurface); + } + + userData = static_cast<RecordingSourceSurfaceUserData*>( + aSurface->GetUserData(reinterpret_cast<UserDataKey*>(mRecorder.get()))); + MOZ_ASSERT(userData, + "User data should always have been set by " + "EnsureSurfaceStoredRecording."); + } + + RefPtr<SourceSurface> retSurf = new SourceSurfaceRecording( + aSurface->GetSize(), aSurface->GetFormat(), mRecorder, aSurface); + mRecorder->RecordEvent(const_cast<DrawTargetRecording*>(this), + RecordedOptimizeSourceSurface(aSurface, retSurf)); + userData->optimizedSurface = retSurf; + + return retSurf.forget(); +} + +already_AddRefed<SourceSurface> +DrawTargetRecording::CreateSourceSurfaceFromNativeSurface( + const NativeSurface& aSurface) const { + MOZ_ASSERT(false); + return nullptr; +} + +already_AddRefed<DrawTarget> +DrawTargetRecording::CreateSimilarDrawTargetWithBacking( + const IntSize& aSize, SurfaceFormat aFormat) const { + RefPtr<DrawTarget> similarDT; + if (mFinalDT->CanCreateSimilarDrawTarget(aSize, aFormat)) { + // If the requested similar draw target is too big, then we should try to + // rasterize on the content side to avoid duplicating the effort when a + // blob image gets tiled. If we fail somehow to produce it, we can fall + // back to recording. + constexpr int32_t kRasterThreshold = 256 * 256 * 4; + int32_t stride = aSize.width * BytesPerPixel(aFormat); + int32_t surfaceBytes = aSize.height * stride; + if (surfaceBytes >= kRasterThreshold) { + auto surface = MakeRefPtr<SourceSurfaceSharedData>(); + if (surface->Init(aSize, stride, aFormat)) { + auto dt = MakeRefPtr<DrawTargetSkia>(); + if (dt->Init(std::move(surface))) { + return dt.forget(); + } else { + MOZ_ASSERT_UNREACHABLE("Skia should initialize given surface!"); + } + } + } + } + + return CreateSimilarDrawTarget(aSize, aFormat); +} + +already_AddRefed<DrawTarget> DrawTargetRecording::CreateSimilarDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat) const { + RefPtr<DrawTarget> similarDT; + if (mFinalDT->CanCreateSimilarDrawTarget(aSize, aFormat)) { + similarDT = + new DrawTargetRecording(this, IntRect(IntPoint(0, 0), aSize), aFormat); + mRecorder->RecordEvent( + const_cast<DrawTargetRecording*>(this), + RecordedCreateSimilarDrawTarget(similarDT.get(), aSize, aFormat)); + } else if (XRE_IsContentProcess()) { + // Crash any content process that calls this function with arguments that + // would fail to create a similar draw target. We do this to root out bad + // callers. We don't want to crash any important processes though so for + // for those we'll just gracefully return nullptr. + MOZ_CRASH( + "Content-process DrawTargetRecording can't create requested similar " + "drawtarget"); + } + return similarDT.forget(); +} + +bool DrawTargetRecording::CanCreateSimilarDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat) const { + return mFinalDT->CanCreateSimilarDrawTarget(aSize, aFormat); +} + +RefPtr<DrawTarget> DrawTargetRecording::CreateClippedDrawTarget( + const Rect& aBounds, SurfaceFormat aFormat) { + RefPtr<DrawTarget> similarDT; + similarDT = new DrawTargetRecording(this, mRect, aFormat); + mRecorder->RecordEvent( + this, RecordedCreateClippedDrawTarget(similarDT.get(), aBounds, aFormat)); + similarDT->SetTransform(mTransform); + return similarDT; +} + +already_AddRefed<DrawTarget> +DrawTargetRecording::CreateSimilarDrawTargetForFilter( + const IntSize& aMaxSize, SurfaceFormat aFormat, FilterNode* aFilter, + FilterNode* aSource, const Rect& aSourceRect, const Point& aDestPoint) { + RefPtr<DrawTarget> similarDT; + if (mFinalDT->CanCreateSimilarDrawTarget(aMaxSize, aFormat)) { + similarDT = new DrawTargetRecording(this, IntRect(IntPoint(0, 0), aMaxSize), + aFormat); + mRecorder->RecordEvent( + this, RecordedCreateDrawTargetForFilter(similarDT.get(), aMaxSize, + aFormat, aFilter, aSource, + aSourceRect, aDestPoint)); + } else if (XRE_IsContentProcess()) { + // See CreateSimilarDrawTarget + MOZ_CRASH( + "Content-process DrawTargetRecording can't create requested clipped " + "drawtarget"); + } + return similarDT.forget(); +} + +already_AddRefed<PathBuilder> DrawTargetRecording::CreatePathBuilder( + FillRule aFillRule) const { + return MakeAndAddRef<PathBuilderRecording>(mFinalDT->GetBackendType(), + aFillRule); +} + +already_AddRefed<GradientStops> DrawTargetRecording::CreateGradientStops( + GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode) const { + RefPtr<GradientStops> retStops = new GradientStopsRecording(mRecorder); + + mRecorder->RecordEvent( + const_cast<DrawTargetRecording*>(this), + RecordedGradientStopsCreation(retStops, aStops, aNumStops, aExtendMode)); + + return retStops.forget(); +} + +void DrawTargetRecording::SetTransform(const Matrix& aTransform) { + if (mTransform.ExactlyEquals(aTransform)) { + return; + } + DrawTarget::SetTransform(aTransform); + mRecorder->RecordEvent(this, RecordedSetTransform(aTransform)); +} + +void DrawTargetRecording::SetPermitSubpixelAA(bool aPermitSubpixelAA) { + if (aPermitSubpixelAA == mPermitSubpixelAA) { + return; + } + DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA); + mRecorder->RecordEvent(this, RecordedSetPermitSubpixelAA(aPermitSubpixelAA)); +} + +already_AddRefed<PathRecording> DrawTargetRecording::EnsurePathStored( + const Path* aPath) { + RefPtr<PathRecording> pathRecording; + if (aPath->GetBackendType() == BackendType::RECORDING) { + pathRecording = + const_cast<PathRecording*>(static_cast<const PathRecording*>(aPath)); + if (!mRecorder->TryAddStoredObject(pathRecording)) { + // Path is already stored. + return pathRecording.forget(); + } + } else { + MOZ_ASSERT(!mRecorder->HasStoredObject(aPath)); + FillRule fillRule = aPath->GetFillRule(); + RefPtr<PathBuilderRecording> builderRecording = + new PathBuilderRecording(mFinalDT->GetBackendType(), fillRule); + aPath->StreamToSink(builderRecording); + pathRecording = builderRecording->Finish().downcast<PathRecording>(); + mRecorder->AddStoredObject(pathRecording); + } + + // It's important that AddStoredObject or TryAddStoredObject is called before + // this because that will run any pending processing required by recorded + // objects that have been deleted off the main thread. + mRecorder->RecordEvent(this, RecordedPathCreation(pathRecording.get())); + pathRecording->mStoredRecorders.push_back(mRecorder); + + return pathRecording.forget(); +} + +// This should only be called on the 'root' DrawTargetRecording. +// Calling it on a child DrawTargetRecordings will cause confusion. +void DrawTargetRecording::FlushItem(const IntRect& aBounds) { + mRecorder->FlushItem(aBounds); + // Reinitialize the recorder (FlushItem will write a new recording header) + // Tell the new recording about our draw target + // This code should match what happens in the DrawTargetRecording constructor. + MOZ_DIAGNOSTIC_ASSERT(mRecorder->GetRecorderType() != RecorderType::CANVAS); + mRecorder->RecordEvent( + RecordedDrawTargetCreation(this, mFinalDT->GetBackendType(), mRect, + mFinalDT->GetFormat(), false, nullptr)); + // Add the current transform to the new recording + mRecorder->RecordEvent(this, + RecordedSetTransform(DrawTarget::GetTransform())); +} + +void DrawTargetRecording::EnsurePatternDependenciesStored( + const Pattern& aPattern) { + switch (aPattern.GetType()) { + case PatternType::COLOR: + // No dependencies here. + return; + case PatternType::LINEAR_GRADIENT: { + MOZ_ASSERT_IF( + static_cast<const LinearGradientPattern*>(&aPattern)->mStops, + mRecorder->HasStoredObject( + static_cast<const LinearGradientPattern*>(&aPattern)->mStops)); + return; + } + case PatternType::RADIAL_GRADIENT: { + MOZ_ASSERT_IF( + static_cast<const RadialGradientPattern*>(&aPattern)->mStops, + mRecorder->HasStoredObject( + static_cast<const RadialGradientPattern*>(&aPattern)->mStops)); + return; + } + case PatternType::CONIC_GRADIENT: { + MOZ_ASSERT_IF( + static_cast<const ConicGradientPattern*>(&aPattern)->mStops, + mRecorder->HasStoredObject( + static_cast<const ConicGradientPattern*>(&aPattern)->mStops)); + return; + } + case PatternType::SURFACE: { + const SurfacePattern* pat = static_cast<const SurfacePattern*>(&aPattern); + EnsureSurfaceStoredRecording(mRecorder, pat->mSurface, + "EnsurePatternDependenciesStored"); + return; + } + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/DrawTargetRecording.h b/gfx/2d/DrawTargetRecording.h new file mode 100644 index 0000000000..239d7ccfd4 --- /dev/null +++ b/gfx/2d/DrawTargetRecording.h @@ -0,0 +1,411 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_DRAWTARGETRECORDING_H_ +#define MOZILLA_GFX_DRAWTARGETRECORDING_H_ + +#include "2D.h" +#include "DrawEventRecorder.h" + +namespace mozilla { +namespace layers { +class CanvasDrawEventRecorder; +struct RemoteTextureOwnerId; +} // namespace layers + +namespace gfx { + +class DrawTargetRecording : public DrawTarget { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetRecording, override) + DrawTargetRecording(DrawEventRecorder* aRecorder, DrawTarget* aDT, + IntRect aRect, bool aHasData = false); + DrawTargetRecording(layers::CanvasDrawEventRecorder* aRecorder, + int64_t aTextureId, + const layers::RemoteTextureOwnerId& aTextureOwnerId, + DrawTarget* aDT, const IntSize& aSize); + + ~DrawTargetRecording(); + + virtual DrawTargetType GetType() const override { + return mFinalDT->GetType(); + } + virtual BackendType GetBackendType() const override { + return BackendType::RECORDING; + } + virtual bool IsRecording() const override { return true; } + + virtual void Link(const char* aDestination, const Rect& aRect) override; + virtual void Destination(const char* aDestination, + const Point& aPoint) override; + + virtual already_AddRefed<SourceSurface> Snapshot() override; + virtual already_AddRefed<SourceSurface> IntoLuminanceSource( + LuminanceType aLuminanceType, float aOpacity) override; + + virtual void DetachAllSnapshots() override; + + virtual IntSize GetSize() const override { return mRect.Size(); } + virtual IntRect GetRect() const override { return mRect; } + + virtual void Flush() override; + + virtual void FlushItem(const IntRect& aBounds) override; + + /* + * Draw a surface to the draw target. Possibly doing partial drawing or + * applying scaling. No sampling happens outside the source. + * + * aSurface Source surface to draw + * aDest Destination rectangle that this drawing operation should draw to + * aSource Source rectangle in aSurface coordinates, this area of aSurface + * will be stretched to the size of aDest. + * aOptions General draw options that are applied to the operation + * aSurfOptions DrawSurface options that are applied + */ + virtual void DrawSurface( + SourceSurface* aSurface, const Rect& aDest, const Rect& aSource, + const DrawSurfaceOptions& aSurfOptions = DrawSurfaceOptions(), + const DrawOptions& aOptions = DrawOptions()) override; + + virtual void DrawDependentSurface(uint64_t aId, const Rect& aDest) override; + + virtual void DrawFilter(FilterNode* aNode, const Rect& aSourceRect, + const Point& aDestPoint, + const DrawOptions& aOptions = DrawOptions()) override; + + virtual void DrawSurfaceWithShadow(SourceSurface* aSurface, + const Point& aDest, + const ShadowOptions& aShadow, + CompositionOp aOperator) override; + + virtual void DrawShadow(const Path* aPath, const Pattern& aPattern, + const ShadowOptions& aShadow, + const DrawOptions& aOptions, + const StrokeOptions* aStrokeOptions) 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 glyphs on the draw target with a certain source pattern. + */ + virtual void FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()) override; + + /** + * Stroke a series of glyphs on the draw target with a certain source pattern. + */ + virtual void StrokeGlyphs( + ScaledFont* aFont, const GlyphBuffer& aBuffer, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()) override; + + /* + * This takes a source pattern and a mask, and composites the source pattern + * onto the destination surface using the alpha channel of the mask pattern + * as a mask for the operation. + * + * aSource Source pattern + * aMask Mask pattern + * aOptions Drawing options + */ + virtual void Mask(const Pattern& aSource, const Pattern& aMask, + const DrawOptions& aOptions = DrawOptions()) override; + + virtual void MaskSurface( + const Pattern& aSource, SourceSurface* aMask, Point aOffset, + const DrawOptions& aOptions = DrawOptions()) override; + + /* + * Push a clip to the DrawTarget. + * + * aPath The path to clip to + */ + virtual void PushClip(const Path* aPath) override; + + /* + * Push an axis-aligned rectangular clip to the DrawTarget. This rectangle + * is specified in user space. + * + * aRect The rect to clip to + */ + virtual void PushClipRect(const Rect& aRect) override; + + /* Pop a clip from the DrawTarget. A pop without a corresponding push will + * be ignored. + */ + virtual void PopClip() override; + + /** + * Push a 'layer' to the DrawTarget, a layer is a temporary surface that all + * drawing will be redirected to, this is used for example to support group + * opacity or the masking of groups. Clips must be balanced within a layer, + * i.e. between a matching PushLayer/PopLayer pair there must be as many + * PushClip(Rect) calls as there are PopClip calls. + * + * @param aOpaque Whether the layer will be opaque + * @param aOpacity Opacity of the layer + * @param aMask Mask applied to the layer + * @param aMaskTransform Transform applied to the layer mask + * @param aBounds Optional bounds in device space to which the layer is + * limited in size. + * @param aCopyBackground Whether to copy the background into the layer, this + * is only supported when aOpaque is true. + */ + virtual void PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds = IntRect(), + bool aCopyBackground = false) override; + + /** + * Push a 'layer' to the DrawTarget, a layer is a temporary surface that all + * drawing will be redirected to, this is used for example to support group + * opacity or the masking of groups. Clips must be balanced within a layer, + * i.e. between a matching PushLayer/PopLayer pair there must be as many + * PushClip(Rect) calls as there are PopClip calls. + * + * @param aOpaque Whether the layer will be opaque + * @param aOpacity Opacity of the layer + * @param aMask Mask applied to the layer + * @param aMaskTransform Transform applied to the layer mask + * @param aBounds Optional bounds in device space to which the layer is + * limited in size. + * @param aCopyBackground Whether to copy the background into the layer, this + * is only supported when aOpaque is true.a + * @param aCompositionOp The CompositionOp to use when blending the layer into + * the destination + */ + virtual void PushLayerWithBlend( + bool aOpaque, Float aOpacity, SourceSurface* aMask, + const Matrix& aMaskTransform, const IntRect& aBounds = IntRect(), + bool aCopyBackground = false, + CompositionOp aCompositionOp = CompositionOp::OP_OVER) override; + + /** + * This balances a call to PushLayer and proceeds to blend the layer back + * onto the background. This blend will blend the temporary surface back + * onto the target in device space using POINT sampling and operator over. + */ + virtual void PopLayer() override; + + /* + * Create a SourceSurface optimized for use with this DrawTarget from + * existing bitmap data in memory. + * + * The SourceSurface does not take ownership of aData, and may be freed at any + * time. + */ + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData( + unsigned char* aData, const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat) const override; + + /* + * Create a SourceSurface optimized for use with this DrawTarget from + * an arbitrary other SourceSurface. This may return aSourceSurface or some + * other existing surface. + */ + virtual already_AddRefed<SourceSurface> OptimizeSourceSurface( + SourceSurface* aSurface) const override; + + /* + * Create a SourceSurface for a type of NativeSurface. This may fail if the + * draw target does not know how to deal with the type of NativeSurface passed + * in. + */ + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromNativeSurface( + const NativeSurface& aSurface) const override; + + /* + * Create a DrawTarget whose snapshot is optimized for use with this + * DrawTarget. + */ + virtual already_AddRefed<DrawTarget> CreateSimilarDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat) const override; + + /** + * Create a DrawTarget whose backing surface is optimized for use with this + * DrawTarget. + */ + virtual already_AddRefed<DrawTarget> CreateSimilarDrawTargetWithBacking( + const IntSize& aSize, SurfaceFormat aFormat) const override; + + bool CanCreateSimilarDrawTarget(const IntSize& aSize, + SurfaceFormat aFormat) const override; + /** + * Create a similar DrawTarget whose requested size may be clipped based + * on this DrawTarget's rect transformed to the new target's space. + */ + virtual RefPtr<DrawTarget> CreateClippedDrawTarget( + const Rect& aBounds, SurfaceFormat aFormat) override; + + virtual already_AddRefed<DrawTarget> CreateSimilarDrawTargetForFilter( + const IntSize& aSize, SurfaceFormat aFormat, FilterNode* aFilter, + FilterNode* aSource, const Rect& aSourceRect, + const Point& aDestPoint) override; + /* + * Create a path builder with the specified fillmode. + * + * We need the fill mode up front because of Direct2D. + * ID2D1SimplifiedGeometrySink requires the fill mode + * to be set before calling BeginFigure(). + */ + virtual already_AddRefed<PathBuilder> CreatePathBuilder( + FillRule aFillRule = FillRule::FILL_WINDING) const override; + + /* + * Create a GradientStops object that holds information about a set of + * gradient stops, this object is required for linear or radial gradient + * patterns to represent the color stops in the gradient. + * + * aStops An array of gradient stops + * aNumStops Number of stops in the array aStops + * aExtendNone This describes how to extend the stop color outside of the + * gradient area. + */ + virtual already_AddRefed<GradientStops> CreateGradientStops( + GradientStop* aStops, uint32_t aNumStops, + ExtendMode aExtendMode = ExtendMode::CLAMP) const override; + + virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override; + + /* + * Set a transform on the surface, this transform is applied at drawing time + * to both the mask and source of the operation. + */ + virtual void SetTransform(const Matrix& aTransform) override; + + virtual void SetPermitSubpixelAA(bool aPermitSubpixelAA) 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(); + } + + bool IsDirty() const { return mIsDirty; } + + void MarkClean() { mIsDirty = false; } + + private: + /** + * Used for creating a DrawTargetRecording for a CreateSimilarDrawTarget call. + * + * @param aDT DrawTargetRecording on which CreateSimilarDrawTarget was called + * @param aSize size of the the similar DrawTarget + * @param aFormat format of the similar DrawTarget + */ + DrawTargetRecording(const DrawTargetRecording* aDT, IntRect aRect, + SurfaceFormat aFormat); + + Path* GetPathForPathRecording(const Path* aPath) const; + already_AddRefed<PathRecording> EnsurePathStored(const Path* aPath); + void EnsurePatternDependenciesStored(const Pattern& aPattern); + + void DrawGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions(), + const StrokeOptions* aStrokeOptions = nullptr); + + void MarkChanged(); + + RefPtr<DrawEventRecorderPrivate> mRecorder; + RefPtr<DrawTarget> mFinalDT; + IntRect mRect; + + struct PushedLayer { + explicit PushedLayer(bool aOldPermitSubpixelAA) + : mOldPermitSubpixelAA(aOldPermitSubpixelAA) {} + bool mOldPermitSubpixelAA; + }; + std::vector<PushedLayer> mPushedLayers; + + bool mIsDirty = false; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_DRAWTARGETRECORDING_H_ */ diff --git a/gfx/2d/DrawTargetSkia.cpp b/gfx/2d/DrawTargetSkia.cpp new file mode 100644 index 0000000000..098ff81117 --- /dev/null +++ b/gfx/2d/DrawTargetSkia.cpp @@ -0,0 +1,2083 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DrawTargetSkia.h" +#include "SourceSurfaceSkia.h" +#include "ScaledFontBase.h" +#include "FilterNodeSoftware.h" +#include "HelpersSkia.h" + +#include "mozilla/CheckedInt.h" +#include "mozilla/Vector.h" + +#include "skia/include/core/SkBitmap.h" +#include "skia/include/core/SkCanvas.h" +#include "skia/include/core/SkFont.h" +#include "skia/include/core/SkSurface.h" +#include "skia/include/core/SkTextBlob.h" +#include "skia/include/core/SkTypeface.h" +#include "skia/include/effects/SkGradientShader.h" +#include "skia/include/core/SkColorFilter.h" +#include "skia/include/core/SkRegion.h" +#include "skia/include/effects/SkImageFilters.h" +#include "skia/include/private/base/SkMalloc.h" +#include "Blur.h" +#include "Logging.h" +#include "Tools.h" +#include "PathHelpers.h" +#include "PathSkia.h" +#include "Swizzle.h" +#include <algorithm> +#include <cmath> + +#ifdef MOZ_WIDGET_COCOA +# include "BorrowedContext.h" +# include <ApplicationServices/ApplicationServices.h> +#endif + +#ifdef XP_WIN +# include "ScaledFontDWrite.h" +#endif + +namespace mozilla { + +void RefPtrTraits<SkSurface>::Release(SkSurface* aSurface) { + SkSafeUnref(aSurface); +} + +void RefPtrTraits<SkSurface>::AddRef(SkSurface* aSurface) { + SkSafeRef(aSurface); +} + +} // namespace mozilla + +namespace mozilla::gfx { + +class GradientStopsSkia : public GradientStops { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsSkia, override) + + GradientStopsSkia(const std::vector<GradientStop>& aStops, uint32_t aNumStops, + ExtendMode aExtendMode) + : mCount(aNumStops), mExtendMode(aExtendMode) { + if (mCount == 0) { + return; + } + + // Skia gradients always require a stop at 0.0 and 1.0, insert these if + // we don't have them. + uint32_t shift = 0; + if (aStops[0].offset != 0) { + mCount++; + shift = 1; + } + if (aStops[aNumStops - 1].offset != 1) { + mCount++; + } + mColors.resize(mCount); + mPositions.resize(mCount); + if (aStops[0].offset != 0) { + mColors[0] = ColorToSkColor(aStops[0].color, 1.0); + mPositions[0] = 0; + } + for (uint32_t i = 0; i < aNumStops; i++) { + mColors[i + shift] = ColorToSkColor(aStops[i].color, 1.0); + mPositions[i + shift] = SkFloatToScalar(aStops[i].offset); + } + if (aStops[aNumStops - 1].offset != 1) { + mColors[mCount - 1] = ColorToSkColor(aStops[aNumStops - 1].color, 1.0); + mPositions[mCount - 1] = SK_Scalar1; + } + } + + BackendType GetBackendType() const override { return BackendType::SKIA; } + + std::vector<SkColor> mColors; + std::vector<SkScalar> mPositions; + int mCount; + ExtendMode mExtendMode; +}; + +/** + * When constructing a temporary SkImage via GetSkImageForSurface, we may also + * have to construct a temporary DataSourceSurface, which must live as long as + * the SkImage. We attach this temporary surface to the image's pixelref, so + * that it can be released once the pixelref is freed. + */ +static void ReleaseTemporarySurface(const void* aPixels, void* aContext) { + DataSourceSurface* surf = static_cast<DataSourceSurface*>(aContext); + if (surf) { + surf->Release(); + } +} + +static void ReleaseTemporaryMappedSurface(const void* aPixels, void* aContext) { + DataSourceSurface* surf = static_cast<DataSourceSurface*>(aContext); + if (surf) { + surf->Unmap(); + surf->Release(); + } +} + +static void WriteRGBXFormat(uint8_t* aData, const IntSize& aSize, + const int32_t aStride, SurfaceFormat aFormat) { + if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) { + return; + } + + SwizzleData(aData, aStride, SurfaceFormat::X8R8G8B8_UINT32, aData, aStride, + SurfaceFormat::A8R8G8B8_UINT32, aSize); +} + +#ifdef DEBUG +static IntRect CalculateSurfaceBounds(const IntSize& aSize, const Rect* aBounds, + const Matrix* aMatrix) { + IntRect surfaceBounds(IntPoint(0, 0), aSize); + if (!aBounds) { + return surfaceBounds; + } + + MOZ_ASSERT(aMatrix); + Matrix inverse(*aMatrix); + if (!inverse.Invert()) { + return surfaceBounds; + } + + IntRect bounds; + Rect sampledBounds = inverse.TransformBounds(*aBounds); + if (!sampledBounds.ToIntRect(&bounds)) { + return surfaceBounds; + } + + return surfaceBounds.Intersect(bounds); +} + +static const int kARGBAlphaOffset = + SurfaceFormat::A8R8G8B8_UINT32 == SurfaceFormat::B8G8R8A8 ? 3 : 0; + +static bool VerifyRGBXFormat(uint8_t* aData, const IntSize& aSize, + const int32_t aStride, SurfaceFormat aFormat) { + if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) { + return true; + } + // We should've initialized the data to be opaque already + // On debug builds, verify that this is actually true. + int height = aSize.height; + int width = aSize.width * 4; + + for (int row = 0; row < height; ++row) { + for (int column = 0; column < width; column += 4) { + if (aData[column + kARGBAlphaOffset] != 0xFF) { + gfxCriticalError() << "RGBX pixel at (" << column << "," << row + << ") in " << width << "x" << height + << " surface is not opaque: " << int(aData[column]) + << "," << int(aData[column + 1]) << "," + << int(aData[column + 2]) << "," + << int(aData[column + 3]); + } + } + aData += aStride; + } + + return true; +} + +// Since checking every pixel is expensive, this only checks the four corners +// and center of a surface that their alpha value is 0xFF. +static bool VerifyRGBXCorners(uint8_t* aData, const IntSize& aSize, + const int32_t aStride, SurfaceFormat aFormat, + const Rect* aBounds = nullptr, + const Matrix* aMatrix = nullptr) { + if (aFormat != SurfaceFormat::B8G8R8X8 || aSize.IsEmpty()) { + return true; + } + + IntRect bounds = CalculateSurfaceBounds(aSize, aBounds, aMatrix); + if (bounds.IsEmpty()) { + return true; + } + + const int height = bounds.Height(); + const int width = bounds.Width(); + const int pixelSize = 4; + MOZ_ASSERT(aSize.width * pixelSize <= aStride); + + const int translation = bounds.Y() * aStride + bounds.X() * pixelSize; + const int topLeft = translation; + const int topRight = topLeft + (width - 1) * pixelSize; + const int bottomLeft = translation + (height - 1) * aStride; + const int bottomRight = bottomLeft + (width - 1) * pixelSize; + + // Lastly the center pixel + const int middleRowHeight = height / 2; + const int middleRowWidth = (width / 2) * pixelSize; + const int middle = translation + aStride * middleRowHeight + middleRowWidth; + + const int offsets[] = {topLeft, topRight, bottomRight, bottomLeft, middle}; + for (int offset : offsets) { + if (aData[offset + kARGBAlphaOffset] != 0xFF) { + int row = offset / aStride; + int column = (offset % aStride) / pixelSize; + gfxCriticalError() << "RGBX corner pixel at (" << column << "," << row + << ") in " << aSize.width << "x" << aSize.height + << " surface, bounded by " + << "(" << bounds.X() << "," << bounds.Y() << "," + << width << "," << height + << ") is not opaque: " << int(aData[offset]) << "," + << int(aData[offset + 1]) << "," + << int(aData[offset + 2]) << "," + << int(aData[offset + 3]); + } + } + + return true; +} +#endif + +static sk_sp<SkImage> GetSkImageForSurface(SourceSurface* aSurface, + Maybe<MutexAutoLock>* aLock, + const Rect* aBounds = nullptr, + const Matrix* aMatrix = nullptr) { + if (!aSurface) { + gfxDebug() << "Creating null Skia image from null SourceSurface"; + return nullptr; + } + + if (aSurface->GetType() == SurfaceType::SKIA) { + return static_cast<SourceSurfaceSkia*>(aSurface)->GetImage(aLock); + } + + RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface(); + if (!dataSurface) { + gfxWarning() << "Failed getting DataSourceSurface for Skia image"; + return nullptr; + } + + DataSourceSurface::MappedSurface map; + SkImage::RasterReleaseProc releaseProc; + if (dataSurface->GetType() == SurfaceType::DATA_SHARED_WRAPPER) { + // Technically all surfaces should be mapped and unmapped explicitly but it + // appears SourceSurfaceSkia and DataSourceSurfaceWrapper have issues with + // this. For now, we just map SourceSurfaceSharedDataWrapper to ensure we + // don't unmap the data during the transaction (for blob images). + if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) { + gfxWarning() << "Failed mapping DataSourceSurface for Skia image"; + return nullptr; + } + releaseProc = ReleaseTemporaryMappedSurface; + } else { + map.mData = dataSurface->GetData(); + map.mStride = dataSurface->Stride(); + releaseProc = ReleaseTemporarySurface; + } + + DataSourceSurface* surf = dataSurface.forget().take(); + + // Skia doesn't support RGBX surfaces so ensure that the alpha value is opaque + // white. + MOZ_ASSERT(VerifyRGBXCorners(map.mData, surf->GetSize(), map.mStride, + surf->GetFormat(), aBounds, aMatrix)); + + SkPixmap pixmap(MakeSkiaImageInfo(surf->GetSize(), surf->GetFormat()), + map.mData, map.mStride); + sk_sp<SkImage> image = SkImage::MakeFromRaster(pixmap, releaseProc, surf); + if (!image) { + releaseProc(map.mData, surf); + gfxDebug() << "Failed making Skia raster image for temporary surface"; + } + + return image; +} + +DrawTargetSkia::DrawTargetSkia() + : mCanvas(nullptr), + mSnapshot(nullptr), + mSnapshotLock{"DrawTargetSkia::mSnapshotLock"} +#ifdef MOZ_WIDGET_COCOA + , + mCG(nullptr), + mColorSpace(nullptr), + mCanvasData(nullptr), + mCGSize(0, 0), + mNeedLayer(false) +#endif +{ +} + +DrawTargetSkia::~DrawTargetSkia() { + if (mSnapshot) { + MutexAutoLock lock(mSnapshotLock); + // We're going to go away, hand our SkSurface to the SourceSurface. + mSnapshot->GiveSurface(mSurface.forget().take()); + } + +#ifdef MOZ_WIDGET_COCOA + if (mCG) { + CGContextRelease(mCG); + mCG = nullptr; + } + + if (mColorSpace) { + CGColorSpaceRelease(mColorSpace); + mColorSpace = nullptr; + } +#endif +} + +already_AddRefed<SourceSurface> DrawTargetSkia::Snapshot( + SurfaceFormat aFormat) { + // Without this lock, this could cause us to get out a snapshot and race with + // Snapshot::~Snapshot() actually destroying itself. + MutexAutoLock lock(mSnapshotLock); + if (mSnapshot && aFormat != mSnapshot->GetFormat()) { + if (!mSnapshot->hasOneRef()) { + mSnapshot->DrawTargetWillChange(); + } + mSnapshot = nullptr; + } + RefPtr<SourceSurfaceSkia> snapshot = mSnapshot; + if (mSurface && !snapshot) { + snapshot = new SourceSurfaceSkia(); + sk_sp<SkImage> image; + // If the surface is raster, making a snapshot may trigger a pixel copy. + // Instead, try to directly make a raster image referencing the surface + // pixels. + SkPixmap pixmap; + if (mSurface->peekPixels(&pixmap)) { + image = SkImage::MakeFromRaster(pixmap, nullptr, nullptr); + } else { + image = mSurface->makeImageSnapshot(); + } + if (!snapshot->InitFromImage(image, aFormat, this)) { + return nullptr; + } + mSnapshot = snapshot; + } + + return snapshot.forget(); +} + +already_AddRefed<SourceSurface> DrawTargetSkia::GetBackingSurface() { + if (mBackingSurface) { + RefPtr<SourceSurface> snapshot = mBackingSurface; + return snapshot.forget(); + } + return Snapshot(); +} + +bool DrawTargetSkia::LockBits(uint8_t** aData, IntSize* aSize, int32_t* aStride, + SurfaceFormat* aFormat, IntPoint* aOrigin) { + SkImageInfo info; + size_t rowBytes; + SkIPoint origin; + void* pixels = mCanvas->accessTopLayerPixels(&info, &rowBytes, &origin); + if (!pixels || + // Ensure the layer is at the origin if required. + (!aOrigin && !origin.isZero())) { + return false; + } + + MarkChanged(); + + *aData = reinterpret_cast<uint8_t*>(pixels); + *aSize = IntSize(info.width(), info.height()); + *aStride = int32_t(rowBytes); + *aFormat = SkiaColorTypeToGfxFormat(info.colorType(), info.alphaType()); + if (aOrigin) { + *aOrigin = IntPoint(origin.x(), origin.y()); + } + return true; +} + +void DrawTargetSkia::ReleaseBits(uint8_t* aData) {} + +static void ReleaseImage(const void* aPixels, void* aContext) { + SkImage* image = static_cast<SkImage*>(aContext); + SkSafeUnref(image); +} + +static sk_sp<SkImage> ExtractSubset(sk_sp<SkImage> aImage, + const IntRect& aRect) { + SkIRect subsetRect = IntRectToSkIRect(aRect); + if (aImage->bounds() == subsetRect) { + return aImage; + } + // makeSubset is slow, so prefer to use SkPixmap::extractSubset where + // possible. + SkPixmap pixmap, subsetPixmap; + if (aImage->peekPixels(&pixmap) && + pixmap.extractSubset(&subsetPixmap, subsetRect)) { + // Release the original image reference so only the subset image keeps it + // alive. + return SkImage::MakeFromRaster(subsetPixmap, ReleaseImage, + aImage.release()); + } + return aImage->makeSubset(subsetRect); +} + +static void FreeAlphaPixels(void* aBuf, void*) { sk_free(aBuf); } + +static bool ExtractAlphaBitmap(const sk_sp<SkImage>& aImage, + SkBitmap* aResultBitmap, + bool aAllowReuse = false) { + SkPixmap pixmap; + if (aAllowReuse && aImage->isAlphaOnly() && aImage->peekPixels(&pixmap)) { + SkBitmap bitmap; + bitmap.installPixels(pixmap.info(), pixmap.writable_addr(), + pixmap.rowBytes()); + *aResultBitmap = bitmap; + return true; + } + SkImageInfo info = SkImageInfo::MakeA8(aImage->width(), aImage->height()); + // Skia does not fully allocate the last row according to stride. + // Since some of our algorithms (i.e. blur) depend on this, we must allocate + // the bitmap pixels manually. + size_t stride = GetAlignedStride<4>(info.width(), info.bytesPerPixel()); + if (stride) { + CheckedInt<size_t> size = stride; + size *= info.height(); + // We need to leave room for an additional 3 bytes for a potential overrun + // in our blurring code. + size += 3; + if (size.isValid()) { + void* buf = sk_malloc_flags(size.value(), 0); + if (buf) { + SkBitmap bitmap; + if (bitmap.installPixels(info, buf, stride, FreeAlphaPixels, nullptr) && + aImage->readPixels(bitmap.info(), bitmap.getPixels(), + bitmap.rowBytes(), 0, 0)) { + *aResultBitmap = bitmap; + return true; + } + } + } + } + + gfxWarning() << "Failed reading alpha pixels for Skia bitmap"; + return false; +} + +static void SetPaintPattern(SkPaint& aPaint, const Pattern& aPattern, + Maybe<MutexAutoLock>& aLock, Float aAlpha = 1.0, + const SkMatrix* aMatrix = nullptr, + const Rect* aBounds = nullptr) { + switch (aPattern.GetType()) { + case PatternType::COLOR: { + DeviceColor color = static_cast<const ColorPattern&>(aPattern).mColor; + aPaint.setColor(ColorToSkColor(color, aAlpha)); + break; + } + case PatternType::LINEAR_GRADIENT: { + const LinearGradientPattern& pat = + static_cast<const LinearGradientPattern&>(aPattern); + GradientStopsSkia* stops = + pat.mStops && pat.mStops->GetBackendType() == BackendType::SKIA + ? static_cast<GradientStopsSkia*>(pat.mStops.get()) + : nullptr; + if (!stops || stops->mCount < 2 || !pat.mBegin.IsFinite() || + !pat.mEnd.IsFinite() || pat.mBegin == pat.mEnd) { + aPaint.setColor(SK_ColorTRANSPARENT); + } else { + SkTileMode mode = ExtendModeToTileMode(stops->mExtendMode, Axis::BOTH); + SkPoint points[2]; + points[0] = SkPoint::Make(SkFloatToScalar(pat.mBegin.x), + SkFloatToScalar(pat.mBegin.y)); + points[1] = SkPoint::Make(SkFloatToScalar(pat.mEnd.x), + SkFloatToScalar(pat.mEnd.y)); + + SkMatrix mat; + GfxMatrixToSkiaMatrix(pat.mMatrix, mat); + if (aMatrix) { + mat.postConcat(*aMatrix); + } + sk_sp<SkShader> shader = SkGradientShader::MakeLinear( + points, &stops->mColors.front(), &stops->mPositions.front(), + stops->mCount, mode, 0, &mat); + if (shader) { + aPaint.setShader(shader); + } else { + aPaint.setColor(SK_ColorTRANSPARENT); + } + } + break; + } + case PatternType::RADIAL_GRADIENT: { + const RadialGradientPattern& pat = + static_cast<const RadialGradientPattern&>(aPattern); + GradientStopsSkia* stops = + pat.mStops && pat.mStops->GetBackendType() == BackendType::SKIA + ? static_cast<GradientStopsSkia*>(pat.mStops.get()) + : nullptr; + if (!stops || stops->mCount < 2 || !pat.mCenter1.IsFinite() || + !std::isfinite(pat.mRadius1) || !pat.mCenter2.IsFinite() || + !std::isfinite(pat.mRadius2) || + (pat.mCenter1 == pat.mCenter2 && pat.mRadius1 == pat.mRadius2)) { + aPaint.setColor(SK_ColorTRANSPARENT); + } else { + SkTileMode mode = ExtendModeToTileMode(stops->mExtendMode, Axis::BOTH); + SkPoint points[2]; + points[0] = SkPoint::Make(SkFloatToScalar(pat.mCenter1.x), + SkFloatToScalar(pat.mCenter1.y)); + points[1] = SkPoint::Make(SkFloatToScalar(pat.mCenter2.x), + SkFloatToScalar(pat.mCenter2.y)); + + SkMatrix mat; + GfxMatrixToSkiaMatrix(pat.mMatrix, mat); + if (aMatrix) { + mat.postConcat(*aMatrix); + } + sk_sp<SkShader> shader = SkGradientShader::MakeTwoPointConical( + points[0], SkFloatToScalar(pat.mRadius1), points[1], + SkFloatToScalar(pat.mRadius2), &stops->mColors.front(), + &stops->mPositions.front(), stops->mCount, mode, 0, &mat); + if (shader) { + aPaint.setShader(shader); + } else { + aPaint.setColor(SK_ColorTRANSPARENT); + } + } + break; + } + case PatternType::CONIC_GRADIENT: { + const ConicGradientPattern& pat = + static_cast<const ConicGradientPattern&>(aPattern); + GradientStopsSkia* stops = + pat.mStops && pat.mStops->GetBackendType() == BackendType::SKIA + ? static_cast<GradientStopsSkia*>(pat.mStops.get()) + : nullptr; + if (!stops || stops->mCount < 2 || !pat.mCenter.IsFinite() || + !std::isfinite(pat.mAngle)) { + aPaint.setColor(SK_ColorTRANSPARENT); + } else { + SkMatrix mat; + GfxMatrixToSkiaMatrix(pat.mMatrix, mat); + if (aMatrix) { + mat.postConcat(*aMatrix); + } + + SkScalar cx = SkFloatToScalar(pat.mCenter.x); + SkScalar cy = SkFloatToScalar(pat.mCenter.y); + + // Skia's sweep gradient angles are relative to the x-axis, not the + // y-axis. + Float angle = (pat.mAngle * 180.0 / M_PI) - 90.0; + if (angle != 0.0) { + mat.preRotate(angle, cx, cy); + } + + SkTileMode mode = ExtendModeToTileMode(stops->mExtendMode, Axis::BOTH); + sk_sp<SkShader> shader = SkGradientShader::MakeSweep( + cx, cy, &stops->mColors.front(), &stops->mPositions.front(), + stops->mCount, mode, 360 * pat.mStartOffset, 360 * pat.mEndOffset, + 0, &mat); + + if (shader) { + aPaint.setShader(shader); + } else { + aPaint.setColor(SK_ColorTRANSPARENT); + } + } + break; + } + case PatternType::SURFACE: { + const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern); + sk_sp<SkImage> image = + GetSkImageForSurface(pat.mSurface, &aLock, aBounds, &pat.mMatrix); + if (!image) { + aPaint.setColor(SK_ColorTRANSPARENT); + break; + } + + SkMatrix mat; + GfxMatrixToSkiaMatrix(pat.mMatrix, mat); + if (aMatrix) { + mat.postConcat(*aMatrix); + } + + if (!pat.mSamplingRect.IsEmpty()) { + image = ExtractSubset(image, pat.mSamplingRect); + if (!image) { + aPaint.setColor(SK_ColorTRANSPARENT); + break; + } + mat.preTranslate(pat.mSamplingRect.X(), pat.mSamplingRect.Y()); + } + + SkTileMode xTile = ExtendModeToTileMode(pat.mExtendMode, Axis::X_AXIS); + SkTileMode yTile = ExtendModeToTileMode(pat.mExtendMode, Axis::Y_AXIS); + + SkFilterMode filterMode = pat.mSamplingFilter == SamplingFilter::POINT + ? SkFilterMode::kNearest + : SkFilterMode::kLinear; + + sk_sp<SkShader> shader = + image->makeShader(xTile, yTile, SkSamplingOptions(filterMode), mat); + if (shader) { + aPaint.setShader(shader); + } else { + gfxDebug() << "Failed creating Skia surface shader: x-tile=" + << (int)xTile << " y-tile=" << (int)yTile + << " matrix=" << (mat.isFinite() ? "finite" : "non-finite"); + aPaint.setColor(SK_ColorTRANSPARENT); + } + break; + } + } +} + +static inline Rect GetClipBounds(SkCanvas* aCanvas) { + // Use a manually transformed getClipDeviceBounds instead of + // getClipBounds because getClipBounds inflates the the bounds + // by a pixel in each direction to compensate for antialiasing. + SkIRect deviceBounds; + if (!aCanvas->getDeviceClipBounds(&deviceBounds)) { + return Rect(); + } + SkMatrix inverseCTM; + if (!aCanvas->getTotalMatrix().invert(&inverseCTM)) { + return Rect(); + } + SkRect localBounds; + inverseCTM.mapRect(&localBounds, SkRect::Make(deviceBounds)); + return SkRectToRect(localBounds); +} + +struct AutoPaintSetup { + AutoPaintSetup(SkCanvas* aCanvas, const DrawOptions& aOptions, + const Pattern& aPattern, const Rect* aMaskBounds = nullptr, + const SkMatrix* aMatrix = nullptr, + const Rect* aSourceBounds = nullptr) + : mNeedsRestore(false), mAlpha(1.0) { + Init(aCanvas, aOptions, aMaskBounds, false); + SetPaintPattern(mPaint, aPattern, mLock, mAlpha, aMatrix, aSourceBounds); + } + + AutoPaintSetup(SkCanvas* aCanvas, const DrawOptions& aOptions, + const Rect* aMaskBounds = nullptr, bool aForceGroup = false) + : mNeedsRestore(false), mAlpha(1.0) { + Init(aCanvas, aOptions, aMaskBounds, aForceGroup); + } + + ~AutoPaintSetup() { + if (mNeedsRestore) { + mCanvas->restore(); + } + } + + void Init(SkCanvas* aCanvas, const DrawOptions& aOptions, + const Rect* aMaskBounds, bool aForceGroup) { + mPaint.setBlendMode(GfxOpToSkiaOp(aOptions.mCompositionOp)); + mCanvas = aCanvas; + + // TODO: Can we set greyscale somehow? + if (aOptions.mAntialiasMode != AntialiasMode::NONE) { + mPaint.setAntiAlias(true); + } else { + mPaint.setAntiAlias(false); + } + + bool needsGroup = + aForceGroup || + (!IsOperatorBoundByMask(aOptions.mCompositionOp) && + (!aMaskBounds || !aMaskBounds->Contains(GetClipBounds(aCanvas)))); + + // TODO: We could skip the temporary for operator_source and just + // clear the clip rect. The other operators would be harder + // but could be worth it to skip pushing a group. + if (needsGroup) { + mPaint.setBlendMode(SkBlendMode::kSrcOver); + SkPaint temp; + temp.setBlendMode(GfxOpToSkiaOp(aOptions.mCompositionOp)); + temp.setAlpha(ColorFloatToByte(aOptions.mAlpha)); + // TODO: Get a rect here + SkCanvas::SaveLayerRec rec(nullptr, &temp, + SkCanvas::kPreserveLCDText_SaveLayerFlag); + mCanvas->saveLayer(rec); + mNeedsRestore = true; + } else { + mPaint.setAlpha(ColorFloatToByte(aOptions.mAlpha)); + mAlpha = aOptions.mAlpha; + } + } + + // TODO: Maybe add an operator overload to access this easier? + SkPaint mPaint; + bool mNeedsRestore; + SkCanvas* mCanvas; + Maybe<MutexAutoLock> mLock; + Float mAlpha; +}; + +void DrawTargetSkia::Flush() { mCanvas->flush(); } + +void DrawTargetSkia::DrawSurface(SourceSurface* aSurface, const Rect& aDest, + const Rect& aSource, + const DrawSurfaceOptions& aSurfOptions, + const DrawOptions& aOptions) { + if (aSource.IsEmpty()) { + return; + } + + MarkChanged(); + + Maybe<MutexAutoLock> lock; + sk_sp<SkImage> image = GetSkImageForSurface(aSurface, &lock); + if (!image) { + return; + } + + SkRect destRect = RectToSkRect(aDest); + SkRect sourceRect = RectToSkRect(aSource - aSurface->GetRect().TopLeft()); + bool forceGroup = + image->isAlphaOnly() && aOptions.mCompositionOp != CompositionOp::OP_OVER; + + AutoPaintSetup paint(mCanvas, aOptions, &aDest, forceGroup); + + SkFilterMode filterMode = + aSurfOptions.mSamplingFilter == SamplingFilter::POINT + ? SkFilterMode::kNearest + : SkFilterMode::kLinear; + + mCanvas->drawImageRect(image, sourceRect, destRect, + SkSamplingOptions(filterMode), &paint.mPaint, + SkCanvas::kStrict_SrcRectConstraint); +} + +DrawTargetType DrawTargetSkia::GetType() const { + return DrawTargetType::SOFTWARE_RASTER; +} + +void DrawTargetSkia::DrawFilter(FilterNode* aNode, const Rect& aSourceRect, + const Point& aDestPoint, + const DrawOptions& aOptions) { + if (!aNode || aNode->GetBackendType() != FILTER_BACKEND_SOFTWARE) { + return; + } + FilterNodeSoftware* filter = static_cast<FilterNodeSoftware*>(aNode); + filter->Draw(this, aSourceRect, aDestPoint, aOptions); +} + +void DrawTargetSkia::DrawSurfaceWithShadow(SourceSurface* aSurface, + const Point& aDest, + const ShadowOptions& aShadow, + CompositionOp aOperator) { + if (aSurface->GetSize().IsEmpty()) { + return; + } + + MarkChanged(); + + Maybe<MutexAutoLock> lock; + sk_sp<SkImage> image = GetSkImageForSurface(aSurface, &lock); + if (!image) { + return; + } + + mCanvas->save(); + mCanvas->resetMatrix(); + + SkPaint paint; + paint.setBlendMode(GfxOpToSkiaOp(aOperator)); + + // bug 1201272 + // We can't use the SkDropShadowImageFilter here because it applies the xfer + // mode first to render the bitmap to a temporary layer, and then implicitly + // uses src-over to composite the resulting shadow. + // The canvas spec, however, states that the composite op must be used to + // composite the resulting shadow, so we must instead use a SkBlurImageFilter + // to blur the image ourselves. + + SkPaint shadowPaint; + shadowPaint.setBlendMode(GfxOpToSkiaOp(aOperator)); + + auto shadowDest = IntPoint::Round(aDest + aShadow.mOffset); + + SkBitmap blurMask; + // Extract the alpha channel of the image into a bitmap. If the image is A8 + // format already, then we can directly reuse the bitmap rather than create a + // new one as the surface only needs to be drawn from once. + if (ExtractAlphaBitmap(image, &blurMask, true)) { + // Prefer using our own box blur instead of Skia's. It currently performs + // much better than SkBlurImageFilter or SkBlurMaskFilter on the CPU. + AlphaBoxBlur blur(Rect(0, 0, blurMask.width(), blurMask.height()), + int32_t(blurMask.rowBytes()), aShadow.mSigma, + aShadow.mSigma); + blur.Blur(reinterpret_cast<uint8_t*>(blurMask.getPixels())); + blurMask.notifyPixelsChanged(); + + shadowPaint.setColor(ColorToSkColor(aShadow.mColor, 1.0f)); + + mCanvas->drawImage(blurMask.asImage(), shadowDest.x, shadowDest.y, + SkSamplingOptions(SkFilterMode::kLinear), &shadowPaint); + } else { + sk_sp<SkImageFilter> blurFilter( + SkImageFilters::Blur(aShadow.mSigma, aShadow.mSigma, nullptr)); + sk_sp<SkColorFilter> colorFilter(SkColorFilters::Blend( + ColorToSkColor(aShadow.mColor, 1.0f), SkBlendMode::kSrcIn)); + + shadowPaint.setImageFilter(blurFilter); + shadowPaint.setColorFilter(colorFilter); + + mCanvas->drawImage(image, shadowDest.x, shadowDest.y, + SkSamplingOptions(SkFilterMode::kLinear), &shadowPaint); + } + + if (aSurface->GetFormat() != SurfaceFormat::A8) { + // Composite the original image after the shadow + auto dest = IntPoint::Round(aDest); + mCanvas->drawImage(image, dest.x, dest.y, + SkSamplingOptions(SkFilterMode::kLinear), &paint); + } + + mCanvas->restore(); +} + +void DrawTargetSkia::FillRect(const Rect& aRect, const Pattern& aPattern, + const DrawOptions& aOptions) { + // The sprite blitting path in Skia can be faster than the shader blitter for + // operators other than source (or source-over with opaque surface). So, when + // possible/beneficial, route to DrawSurface which will use the sprite + // blitter. + if (aPattern.GetType() == PatternType::SURFACE && + aOptions.mCompositionOp != CompositionOp::OP_SOURCE) { + const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern); + // Verify there is a valid surface and a pattern matrix without skew. + if (pat.mSurface && + (aOptions.mCompositionOp != CompositionOp::OP_OVER || + GfxFormatToSkiaAlphaType(pat.mSurface->GetFormat()) != + kOpaque_SkAlphaType) && + !pat.mMatrix.HasNonAxisAlignedTransform()) { + // Bound the sampling to smaller of the bounds or the sampling rect. + IntRect srcRect(IntPoint(0, 0), pat.mSurface->GetSize()); + if (!pat.mSamplingRect.IsEmpty()) { + srcRect = srcRect.Intersect(pat.mSamplingRect); + } + // Transform the destination rectangle by the inverse of the pattern + // matrix so that it is in pattern space like the source rectangle. + Rect patRect = aRect - pat.mMatrix.GetTranslation(); + patRect.Scale(1.0f / pat.mMatrix._11, 1.0f / pat.mMatrix._22); + // Verify the pattern rectangle will not tile or clamp. + if (!patRect.IsEmpty() && srcRect.Contains(RoundedOut(patRect))) { + // The pattern is a surface with an axis-aligned source rectangle + // fitting entirely in its bounds, so just treat it as a DrawSurface. + DrawSurface(pat.mSurface, aRect, patRect, + DrawSurfaceOptions(pat.mSamplingFilter), aOptions); + return; + } + } + } + + MarkChanged(); + SkRect rect = RectToSkRect(aRect); + AutoPaintSetup paint(mCanvas, aOptions, aPattern, &aRect, nullptr, &aRect); + + mCanvas->drawRect(rect, paint.mPaint); +} + +void DrawTargetSkia::Stroke(const Path* aPath, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) { + MarkChanged(); + MOZ_ASSERT(aPath, "Null path"); + if (aPath->GetBackendType() != BackendType::SKIA) { + return; + } + + const PathSkia* skiaPath = static_cast<const PathSkia*>(aPath); + + AutoPaintSetup paint(mCanvas, aOptions, aPattern); + if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) { + return; + } + + if (!skiaPath->GetPath().isFinite()) { + return; + } + + mCanvas->drawPath(skiaPath->GetPath(), paint.mPaint); +} + +static Double DashPeriodLength(const StrokeOptions& aStrokeOptions) { + Double length = 0; + for (size_t i = 0; i < aStrokeOptions.mDashLength; i++) { + length += aStrokeOptions.mDashPattern[i]; + } + if (aStrokeOptions.mDashLength & 1) { + // "If an odd number of values is provided, then the list of values is + // repeated to yield an even number of values." + // Double the length. + length += length; + } + return length; +} + +static inline Double RoundDownToMultiple(Double aValue, Double aFactor) { + return floor(aValue / aFactor) * aFactor; +} + +static Rect UserSpaceStrokeClip(const IntRect& aDeviceClip, + const Matrix& aTransform, + const StrokeOptions& aStrokeOptions) { + Matrix inverse = aTransform; + if (!inverse.Invert()) { + return Rect(); + } + Rect deviceClip(aDeviceClip); + deviceClip.Inflate(MaxStrokeExtents(aStrokeOptions, aTransform)); + return inverse.TransformBounds(deviceClip); +} + +static Rect ShrinkClippedStrokedRect(const Rect& aStrokedRect, + const IntRect& aDeviceClip, + const Matrix& aTransform, + const StrokeOptions& aStrokeOptions) { + Rect userSpaceStrokeClip = + UserSpaceStrokeClip(aDeviceClip, aTransform, aStrokeOptions); + RectDouble strokedRectDouble(aStrokedRect.X(), aStrokedRect.Y(), + aStrokedRect.Width(), aStrokedRect.Height()); + RectDouble intersection = strokedRectDouble.Intersect( + RectDouble(userSpaceStrokeClip.X(), userSpaceStrokeClip.Y(), + userSpaceStrokeClip.Width(), userSpaceStrokeClip.Height())); + Double dashPeriodLength = DashPeriodLength(aStrokeOptions); + if (intersection.IsEmpty() || dashPeriodLength == 0.0f) { + return Rect(intersection.X(), intersection.Y(), intersection.Width(), + intersection.Height()); + } + + // Reduce the rectangle side lengths in multiples of the dash period length + // so that the visible dashes stay in the same place. + MarginDouble insetBy = strokedRectDouble - intersection; + insetBy.top = RoundDownToMultiple(insetBy.top, dashPeriodLength); + insetBy.right = RoundDownToMultiple(insetBy.right, dashPeriodLength); + insetBy.bottom = RoundDownToMultiple(insetBy.bottom, dashPeriodLength); + insetBy.left = RoundDownToMultiple(insetBy.left, dashPeriodLength); + + strokedRectDouble.Deflate(insetBy); + return Rect(strokedRectDouble.X(), strokedRectDouble.Y(), + strokedRectDouble.Width(), strokedRectDouble.Height()); +} + +void DrawTargetSkia::StrokeRect(const Rect& aRect, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) { + // Stroking large rectangles with dashes is expensive with Skia (fixed + // overhead based on the number of dashes, regardless of whether the dashes + // are visible), so we try to reduce the size of the stroked rectangle as + // much as possible before passing it on to Skia. + Rect rect = aRect; + if (aStrokeOptions.mDashLength > 0 && !rect.IsEmpty()) { + IntRect deviceClip(IntPoint(0, 0), mSize); + SkIRect clipBounds; + if (mCanvas->getDeviceClipBounds(&clipBounds)) { + deviceClip = SkIRectToIntRect(clipBounds); + } + rect = + ShrinkClippedStrokedRect(rect, deviceClip, mTransform, aStrokeOptions); + if (rect.IsEmpty()) { + return; + } + } + + MarkChanged(); + AutoPaintSetup paint(mCanvas, aOptions, aPattern); + if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) { + return; + } + + mCanvas->drawRect(RectToSkRect(rect), paint.mPaint); +} + +void DrawTargetSkia::StrokeLine(const Point& aStart, const Point& aEnd, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) { + MarkChanged(); + AutoPaintSetup paint(mCanvas, aOptions, aPattern); + if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) { + return; + } + + mCanvas->drawLine(SkFloatToScalar(aStart.x), SkFloatToScalar(aStart.y), + SkFloatToScalar(aEnd.x), SkFloatToScalar(aEnd.y), + paint.mPaint); +} + +void DrawTargetSkia::Fill(const Path* aPath, const Pattern& aPattern, + const DrawOptions& aOptions) { + MarkChanged(); + if (!aPath || aPath->GetBackendType() != BackendType::SKIA) { + return; + } + + const PathSkia* skiaPath = static_cast<const PathSkia*>(aPath); + + AutoPaintSetup paint(mCanvas, aOptions, aPattern); + + if (!skiaPath->GetPath().isFinite()) { + return; + } + + mCanvas->drawPath(skiaPath->GetPath(), paint.mPaint); +} + +#ifdef MOZ_WIDGET_COCOA +static inline CGAffineTransform GfxMatrixToCGAffineTransform(const Matrix& m) { + CGAffineTransform t; + t.a = m._11; + t.b = m._12; + t.c = m._21; + t.d = m._22; + t.tx = m._31; + t.ty = m._32; + return t; +} + +/*** + * We have to do a lot of work to draw glyphs with CG because + * CG assumes that the origin of rects are in the bottom left + * while every other DrawTarget assumes the top left is the origin. + * This means we have to transform the CGContext to have rects + * actually be applied in top left fashion. We do this by: + * + * 1) Translating the context up by the height of the canvas + * 2) Flipping the context by the Y axis so it's upside down. + * + * These two transforms put the origin in the top left. + * Transforms are better understood thinking about them from right to left order + * (mathematically). + * + * Consider a point we want to draw at (0, 10) in normal cartesian planes with + * a box of (100, 100). in CG terms, this would be at (0, 10). + * Positive Y values point up. + * In our DrawTarget terms, positive Y values point down, so (0, 10) would be + * at (0, 90) in cartesian plane terms. That means our point at (0, 10) in + * DrawTarget terms should end up at (0, 90). How does this work with the + * current transforms? + * + * Going right to left with the transforms, a CGPoint of (0, 10) has cartesian + * coordinates of (0, 10). The first flip of the Y axis puts the point now at + * (0, -10); Next, we translate the context up by the size of the canvas + * (Positive Y values go up in CG coordinates but down in our draw target + * coordinates). Since our canvas size is (100, 100), the resulting coordinate + * becomes (0, 90), which is what we expect from our DrawTarget code. These two + * transforms put the CG context equal to what every other DrawTarget expects. + * + * Next, we need two more transforms for actual text. IF we left the transforms + * as is, the text would be drawn upside down, so we need another flip of the Y + * axis to draw the text right side up. However, with only the flip, the text + * would be drawn in the wrong place. Thus we also have to invert the Y position + * of the glyphs to get them in the right place. + * + * Thus we have the following transforms: + * 1) Translation of the context up + * 2) Flipping the context around the Y axis + * 3) Flipping the context around the Y axis + * 4) Inverting the Y position of each glyph + * + * We cannot cancel out (2) and (3) as we have to apply the clips and transforms + * of DrawTargetSkia between (2) and (3). + * + * Consider the example letter P, drawn at (0, 20) in CG coordinates in a + * (100, 100) rect. + * Again, going right to left of the transforms. We'd get: + * + * 1) The letter P drawn at (0, -20) due to the inversion of the Y axis + * 2) The letter P upside down (b) at (0, 20) due to the second flip + * 3) The letter P right side up at (0, -20) due to the first flip + * 4) The letter P right side up at (0, 80) due to the translation + * + * tl;dr - CGRects assume origin is bottom left, DrawTarget rects assume top + * left. + */ +static bool SetupCGContext(DrawTargetSkia* aDT, CGContextRef aCGContext, + SkCanvas* aCanvas, const IntPoint& aOrigin, + const IntSize& aSize, bool aClipped) { + // DrawTarget expects the origin to be at the top left, but CG + // expects it to be at the bottom left. Transform to set the origin to + // the top left. Have to set this before we do anything else. + // This is transform (1) up top + CGContextTranslateCTM(aCGContext, -aOrigin.x, aOrigin.y + aSize.height); + + // Transform (2) from the comments. + CGContextScaleCTM(aCGContext, 1, -1); + + // Want to apply clips BEFORE the transform since the transform + // will apply to the clips we apply. + if (aClipped) { + SkRegion clipRegion; + aCanvas->temporary_internal_getRgnClip(&clipRegion); + Vector<CGRect, 8> rects; + for (SkRegion::Iterator it(clipRegion); !it.done(); it.next()) { + const SkIRect& rect = it.rect(); + if (!rects.append( + CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()))) { + break; + } + } + if (rects.length()) { + CGContextClipToRects(aCGContext, rects.begin(), rects.length()); + } + } + + CGContextConcatCTM(aCGContext, + GfxMatrixToCGAffineTransform(aDT->GetTransform())); + return true; +} +// End long comment about transforms. + +// The context returned from this method will have the origin +// in the top left and will have applied all the neccessary clips +// and transforms to the CGContext. See the comment above +// SetupCGContext. +CGContextRef DrawTargetSkia::BorrowCGContext(const DrawOptions& aOptions) { + // Since we can't replay Skia clips, we have to use a layer if we have a + // complex clip. After saving a layer, the SkCanvas queries for needing a + // layer change so save if we pushed a layer. + mNeedLayer = !mCanvas->isClipEmpty() && !mCanvas->isClipRect(); + if (mNeedLayer) { + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); + SkCanvas::SaveLayerRec rec(nullptr, &paint, + SkCanvas::kInitWithPrevious_SaveLayerFlag); + mCanvas->saveLayer(rec); + } + + uint8_t* data = nullptr; + int32_t stride; + SurfaceFormat format; + IntSize size; + IntPoint origin; + if (!LockBits(&data, &size, &stride, &format, &origin)) { + NS_WARNING("Could not lock skia bits to wrap CG around"); + return nullptr; + } + + if (!mNeedLayer && (data == mCanvasData) && mCG && (mCGSize == size)) { + // If our canvas data still points to the same data, + // we can reuse the CG Context + CGContextSetAlpha(mCG, aOptions.mAlpha); + CGContextSetShouldAntialias(mCG, + aOptions.mAntialiasMode != AntialiasMode::NONE); + CGContextSaveGState(mCG); + SetupCGContext(this, mCG, mCanvas, origin, size, true); + return mCG; + } + + if (!mColorSpace) { + mColorSpace = (format == SurfaceFormat::A8) ? CGColorSpaceCreateDeviceGray() + : CGColorSpaceCreateDeviceRGB(); + } + + if (mCG) { + // Release the old CG context since it's no longer valid. + CGContextRelease(mCG); + } + + mCanvasData = data; + mCGSize = size; + + uint32_t bitmapInfo = + (format == SurfaceFormat::A8) + ? kCGImageAlphaOnly + : kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; + + mCG = CGBitmapContextCreateWithData( + mCanvasData, mCGSize.width, mCGSize.height, 8, /* bits per component */ + stride, mColorSpace, bitmapInfo, NULL, /* Callback when released */ + NULL); + if (!mCG) { + if (mNeedLayer) { + mCanvas->restore(); + } + ReleaseBits(mCanvasData); + NS_WARNING("Could not create bitmap around skia data\n"); + return nullptr; + } + + CGContextSetAlpha(mCG, aOptions.mAlpha); + CGContextSetShouldAntialias(mCG, + aOptions.mAntialiasMode != AntialiasMode::NONE); + CGContextSetShouldSmoothFonts(mCG, true); + CGContextSetTextDrawingMode(mCG, kCGTextFill); + CGContextSaveGState(mCG); + SetupCGContext(this, mCG, mCanvas, origin, size, !mNeedLayer); + return mCG; +} + +void DrawTargetSkia::ReturnCGContext(CGContextRef aCGContext) { + MOZ_ASSERT(aCGContext == mCG); + ReleaseBits(mCanvasData); + CGContextRestoreGState(aCGContext); + + if (mNeedLayer) { + // A layer was used for clipping and is about to be popped by the restore. + // Make sure the CG context referencing it is released first so the popped + // layer doesn't accidentally get used. + if (mCG) { + CGContextRelease(mCG); + mCG = nullptr; + } + mCanvas->restore(); + } +} + +CGContextRef BorrowedCGContext::BorrowCGContextFromDrawTarget(DrawTarget* aDT) { + DrawTargetSkia* skiaDT = static_cast<DrawTargetSkia*>(aDT); + return skiaDT->BorrowCGContext(DrawOptions()); +} + +void BorrowedCGContext::ReturnCGContextToDrawTarget(DrawTarget* aDT, + CGContextRef cg) { + DrawTargetSkia* skiaDT = static_cast<DrawTargetSkia*>(aDT); + skiaDT->ReturnCGContext(cg); +} +#endif + +static bool CanDrawFont(ScaledFont* aFont) { + switch (aFont->GetType()) { + case FontType::FREETYPE: + case FontType::FONTCONFIG: + case FontType::MAC: + case FontType::GDI: + case FontType::DWRITE: + return true; + default: + return false; + } +} + +void DrawTargetSkia::DrawGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const StrokeOptions* aStrokeOptions, + const DrawOptions& aOptions) { + if (!CanDrawFont(aFont)) { + return; + } + + MarkChanged(); + + ScaledFontBase* skiaFont = static_cast<ScaledFontBase*>(aFont); + SkTypeface* typeface = skiaFont->GetSkTypeface(); + if (!typeface) { + return; + } + + AutoPaintSetup paint(mCanvas, aOptions, aPattern); + if (aStrokeOptions && !StrokeOptionsToPaint(paint.mPaint, *aStrokeOptions)) { + return; + } + + AntialiasMode aaMode = aFont->GetDefaultAAMode(); + if (aOptions.mAntialiasMode != AntialiasMode::DEFAULT) { + aaMode = aOptions.mAntialiasMode; + } + bool aaEnabled = aaMode != AntialiasMode::NONE; + paint.mPaint.setAntiAlias(aaEnabled); + + SkFont font(sk_ref_sp(typeface), SkFloatToScalar(skiaFont->mSize)); + + bool useSubpixelAA = + GetPermitSubpixelAA() && + (aaMode == AntialiasMode::DEFAULT || aaMode == AntialiasMode::SUBPIXEL); + font.setEdging(useSubpixelAA ? SkFont::Edging::kSubpixelAntiAlias + : (aaEnabled ? SkFont::Edging::kAntiAlias + : SkFont::Edging::kAlias)); + + skiaFont->SetupSkFontDrawOptions(font); + + // Limit the amount of internal batch allocations Skia does. + const uint32_t kMaxGlyphBatchSize = 8192; + + for (uint32_t offset = 0; offset < aBuffer.mNumGlyphs;) { + uint32_t batchSize = + std::min(aBuffer.mNumGlyphs - offset, kMaxGlyphBatchSize); + SkTextBlobBuilder builder; + auto runBuffer = builder.allocRunPos(font, batchSize); + for (uint32_t i = 0; i < batchSize; i++, offset++) { + runBuffer.glyphs[i] = aBuffer.mGlyphs[offset].mIndex; + runBuffer.points()[i] = PointToSkPoint(aBuffer.mGlyphs[offset].mPosition); + } + + sk_sp<SkTextBlob> text = builder.make(); + mCanvas->drawTextBlob(text, 0, 0, paint.mPaint); + } +} + +Maybe<Rect> DrawTargetSkia::GetGlyphLocalBounds( + ScaledFont* aFont, const GlyphBuffer& aBuffer, const Pattern& aPattern, + const StrokeOptions* aStrokeOptions, const DrawOptions& aOptions) { + if (!CanDrawFont(aFont)) { + return Nothing(); + } + + ScaledFontBase* skiaFont = static_cast<ScaledFontBase*>(aFont); + SkTypeface* typeface = skiaFont->GetSkTypeface(); + if (!typeface) { + return Nothing(); + } + + AutoPaintSetup paint(mCanvas, aOptions, aPattern); + if (aStrokeOptions && !StrokeOptionsToPaint(paint.mPaint, *aStrokeOptions)) { + return Nothing(); + } + + AntialiasMode aaMode = aFont->GetDefaultAAMode(); + if (aOptions.mAntialiasMode != AntialiasMode::DEFAULT) { + aaMode = aOptions.mAntialiasMode; + } + bool aaEnabled = aaMode != AntialiasMode::NONE; + paint.mPaint.setAntiAlias(aaEnabled); + + SkFont font(sk_ref_sp(typeface), SkFloatToScalar(skiaFont->mSize)); + + bool useSubpixelAA = + GetPermitSubpixelAA() && + (aaMode == AntialiasMode::DEFAULT || aaMode == AntialiasMode::SUBPIXEL); + font.setEdging(useSubpixelAA ? SkFont::Edging::kSubpixelAntiAlias + : (aaEnabled ? SkFont::Edging::kAntiAlias + : SkFont::Edging::kAlias)); + + skiaFont->SetupSkFontDrawOptions(font); + + // Limit the amount of internal batch allocations Skia does. + const uint32_t kMaxGlyphBatchSize = 8192; + + // Avoid using TextBlobBuilder for bounds computations as the conservative + // bounds can be wrong due to buggy font metrics. Instead, explicitly compute + // tight bounds directly with the SkFont. + Vector<SkGlyphID, 32> glyphs; + Vector<SkRect, 32> rects; + Rect bounds; + for (uint32_t offset = 0; offset < aBuffer.mNumGlyphs;) { + uint32_t batchSize = + std::min(aBuffer.mNumGlyphs - offset, kMaxGlyphBatchSize); + if (glyphs.resizeUninitialized(batchSize) && + rects.resizeUninitialized(batchSize)) { + for (uint32_t i = 0; i < batchSize; i++) { + glyphs[i] = aBuffer.mGlyphs[offset + i].mIndex; + } + font.getBounds(glyphs.begin(), batchSize, rects.begin(), nullptr); + for (uint32_t i = 0; i < batchSize; i++) { + bounds = bounds.Union(SkRectToRect(rects[i]) + + aBuffer.mGlyphs[offset + i].mPosition); + } + } + offset += batchSize; + } + + SkRect storage; + bounds = SkRectToRect( + paint.mPaint.computeFastBounds(RectToSkRect(bounds), &storage)); + + if (bounds.IsEmpty()) { + return Nothing(); + } + + return Some(bounds); +} + +void DrawTargetSkia::FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const DrawOptions& aOptions) { + DrawGlyphs(aFont, aBuffer, aPattern, nullptr, aOptions); +} + +void DrawTargetSkia::StrokeGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) { + DrawGlyphs(aFont, aBuffer, aPattern, &aStrokeOptions, aOptions); +} + +void DrawTargetSkia::Mask(const Pattern& aSource, const Pattern& aMask, + const DrawOptions& aOptions) { + Maybe<MutexAutoLock> lock; + SkPaint maskPaint; + SetPaintPattern(maskPaint, aMask, lock); + + sk_sp<SkShader> maskShader(maskPaint.getShader()); + if (!maskShader && maskPaint.getAlpha() != 0xFF) { + if (maskPaint.getAlpha() == 0) { + return; + } + maskShader = SkShaders::Color(maskPaint.getColor()); + if (!maskShader) { + gfxDebug() << "Failed creating Skia clip shader for Mask"; + return; + } + } + + MarkChanged(); + AutoPaintSetup paint(mCanvas, aOptions, aSource); + + mCanvas->save(); + if (maskShader) { + mCanvas->clipShader(maskShader); + } + + mCanvas->drawPaint(paint.mPaint); + + mCanvas->restore(); +} + +void DrawTargetSkia::MaskSurface(const Pattern& aSource, SourceSurface* aMask, + Point aOffset, const DrawOptions& aOptions) { + Maybe<MutexAutoLock> lock; + sk_sp<SkImage> maskImage = GetSkImageForSurface(aMask, &lock); + SkMatrix maskOffset = SkMatrix::Translate( + PointToSkPoint(aOffset + Point(aMask->GetRect().TopLeft()))); + sk_sp<SkShader> maskShader = maskImage->makeShader( + SkTileMode::kClamp, SkTileMode::kClamp, + SkSamplingOptions(SkFilterMode::kLinear), maskOffset); + if (!maskShader) { + gfxDebug() << "Failed creating Skia clip shader for MaskSurface"; + return; + } + + MarkChanged(); + AutoPaintSetup paint(mCanvas, aOptions, aSource); + + mCanvas->save(); + mCanvas->clipShader(maskShader); + + mCanvas->drawRect(RectToSkRect(Rect(aMask->GetRect()) + aOffset), + paint.mPaint); + + mCanvas->restore(); +} + +bool DrawTarget::Draw3DTransformedSurface(SourceSurface* aSurface, + const Matrix4x4& aMatrix) { + // Composite the 3D transform with the DT's transform. + Matrix4x4 fullMat = aMatrix * Matrix4x4::From2D(mTransform); + if (fullMat.IsSingular()) { + return false; + } + // Transform the surface bounds and clip to this DT. + IntRect xformBounds = RoundedOut(fullMat.TransformAndClipBounds( + Rect(Point(0, 0), Size(aSurface->GetSize())), + Rect(Point(0, 0), Size(GetSize())))); + if (xformBounds.IsEmpty()) { + return true; + } + // Offset the matrix by the transformed origin. + fullMat.PostTranslate(-xformBounds.X(), -xformBounds.Y(), 0); + + // Read in the source data. + Maybe<MutexAutoLock> lock; + sk_sp<SkImage> srcImage = GetSkImageForSurface(aSurface, &lock); + if (!srcImage) { + return true; + } + + // Set up an intermediate destination surface only the size of the transformed + // bounds. Try to pass through the source's format unmodified in both the BGRA + // and ARGB cases. + RefPtr<DataSourceSurface> dstSurf = Factory::CreateDataSourceSurface( + xformBounds.Size(), + !srcImage->isOpaque() ? aSurface->GetFormat() + : SurfaceFormat::A8R8G8B8_UINT32, + true); + if (!dstSurf) { + return false; + } + + DataSourceSurface::ScopedMap map(dstSurf, DataSourceSurface::READ_WRITE); + if (!map.IsMapped()) { + return false; + } + std::unique_ptr<SkCanvas> dstCanvas(SkCanvas::MakeRasterDirect( + SkImageInfo::Make(xformBounds.Width(), xformBounds.Height(), + GfxFormatToSkiaColorType(dstSurf->GetFormat()), + kPremul_SkAlphaType), + map.GetData(), map.GetStride())); + if (!dstCanvas) { + return false; + } + + // Do the transform. + SkPaint paint; + paint.setAntiAlias(true); + paint.setBlendMode(SkBlendMode::kSrc); + + SkMatrix xform; + GfxMatrixToSkiaMatrix(fullMat, xform); + dstCanvas->setMatrix(xform); + + dstCanvas->drawImage(srcImage, 0, 0, SkSamplingOptions(SkFilterMode::kLinear), + &paint); + dstCanvas->flush(); + + // Temporarily reset the DT's transform, since it has already been composed + // above. + Matrix origTransform = mTransform; + SetTransform(Matrix()); + + // Draw the transformed surface within the transformed bounds. + DrawSurface(dstSurf, Rect(xformBounds), + Rect(Point(0, 0), Size(xformBounds.Size()))); + + SetTransform(origTransform); + + return true; +} + +bool DrawTargetSkia::Draw3DTransformedSurface(SourceSurface* aSurface, + const Matrix4x4& aMatrix) { + if (aMatrix.IsSingular()) { + return false; + } + + MarkChanged(); + + Maybe<MutexAutoLock> lock; + sk_sp<SkImage> image = GetSkImageForSurface(aSurface, &lock); + if (!image) { + return true; + } + + mCanvas->save(); + + SkPaint paint; + paint.setAntiAlias(true); + + SkMatrix xform; + GfxMatrixToSkiaMatrix(aMatrix, xform); + mCanvas->concat(xform); + + mCanvas->drawImage(image, 0, 0, SkSamplingOptions(SkFilterMode::kLinear), + &paint); + + mCanvas->restore(); + + return true; +} + +already_AddRefed<SourceSurface> DrawTargetSkia::CreateSourceSurfaceFromData( + unsigned char* aData, const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat) const { + RefPtr<SourceSurfaceSkia> newSurf = new SourceSurfaceSkia(); + + if (!newSurf->InitFromData(aData, aSize, aStride, aFormat)) { + gfxDebug() << *this + << ": Failure to create source surface from data. Size: " + << aSize; + return nullptr; + } + + return newSurf.forget(); +} + +already_AddRefed<DrawTarget> DrawTargetSkia::CreateSimilarDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat) const { + RefPtr<DrawTargetSkia> target = new DrawTargetSkia(); +#ifdef DEBUG + if (!IsBackedByPixels(mCanvas)) { + // If our canvas is backed by vector storage such as PDF then we want to + // create a new DrawTarget with similar storage to avoid losing fidelity + // (fidelity will be lost if the returned DT is Snapshot()'ed and drawn + // back onto us since a raster will be drawn instead of vector commands). + NS_WARNING("Not backed by pixels - we need to handle PDF backed SkCanvas"); + } +#endif + + if (!target->Init(aSize, aFormat)) { + return nullptr; + } + return target.forget(); +} + +bool DrawTargetSkia::CanCreateSimilarDrawTarget(const IntSize& aSize, + SurfaceFormat aFormat) const { + auto minmaxPair = std::minmax(aSize.width, aSize.height); + return minmaxPair.first > 0 && + size_t(minmaxPair.second) < GetMaxSurfaceSize(); +} + +RefPtr<DrawTarget> DrawTargetSkia::CreateClippedDrawTarget( + const Rect& aBounds, SurfaceFormat aFormat) { + SkIRect clipBounds; + + RefPtr<DrawTarget> result; + // Doing this save()/restore() dance is wasteful + mCanvas->save(); + if (!aBounds.IsEmpty()) { + mCanvas->clipRect(RectToSkRect(aBounds), SkClipOp::kIntersect, true); + } + if (mCanvas->getDeviceClipBounds(&clipBounds)) { + RefPtr<DrawTarget> dt = CreateSimilarDrawTarget( + IntSize(clipBounds.width(), clipBounds.height()), aFormat); + if (dt) { + result = gfx::Factory::CreateOffsetDrawTarget( + dt, IntPoint(clipBounds.x(), clipBounds.y())); + if (result) { + result->SetTransform(mTransform); + } + } + } else { + // Everything is clipped but we still want some kind of surface + result = CreateSimilarDrawTarget(IntSize(1, 1), aFormat); + } + mCanvas->restore(); + return result; +} + +already_AddRefed<SourceSurface> +DrawTargetSkia::OptimizeSourceSurfaceForUnknownAlpha( + SourceSurface* aSurface) const { + if (aSurface->GetType() == SurfaceType::SKIA) { + RefPtr<SourceSurface> surface(aSurface); + return surface.forget(); + } + + if (RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface()) { + DataSourceSurface::ScopedMap map(dataSurface, + DataSourceSurface::READ_WRITE); + if (map.IsMapped()) { + // For plugins, GDI can sometimes just write 0 to the alpha channel + // even for RGBX formats. In this case, we have to manually write + // the alpha channel to make Skia happy with RGBX and in case GDI + // writes some bad data. Luckily, this only happens on plugins. + WriteRGBXFormat(map.GetData(), dataSurface->GetSize(), map.GetStride(), + dataSurface->GetFormat()); + return dataSurface.forget(); + } + } + + return nullptr; +} + +already_AddRefed<SourceSurface> DrawTargetSkia::OptimizeSourceSurface( + SourceSurface* aSurface) const { + if (aSurface->GetType() == SurfaceType::SKIA) { + RefPtr<SourceSurface> surface(aSurface); + return surface.forget(); + } + + // If we're not using skia-gl then drawing doesn't require any + // uploading, so any data surface is fine. Call GetDataSurface + // to trigger any required readback so that it only happens + // once. + if (RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface()) { +#ifdef DEBUG + DataSourceSurface::ScopedMap map(dataSurface, DataSourceSurface::READ); + if (map.IsMapped()) { + MOZ_ASSERT(VerifyRGBXFormat(map.GetData(), dataSurface->GetSize(), + map.GetStride(), dataSurface->GetFormat())); + } +#endif + return dataSurface.forget(); + } + + return nullptr; +} + +already_AddRefed<SourceSurface> +DrawTargetSkia::CreateSourceSurfaceFromNativeSurface( + const NativeSurface& aSurface) const { + return nullptr; +} + +void DrawTargetSkia::CopySurface(SourceSurface* aSurface, + const IntRect& aSourceRect, + const IntPoint& aDestination) { + MarkChanged(); + + Maybe<MutexAutoLock> lock; + sk_sp<SkImage> image = GetSkImageForSurface(aSurface, &lock); + if (!image) { + return; + } + + SkPixmap srcPixmap; + if (!image->peekPixels(&srcPixmap)) { + return; + } + + // Ensure the source rect intersects the surface bounds. + IntRect srcRect = aSourceRect.Intersect(SkIRectToIntRect(srcPixmap.bounds())); + // Move the destination offset to match the altered source rect. + IntPoint dstOffset = + aDestination + (srcRect.TopLeft() - aSourceRect.TopLeft()); + // Then ensure the dest rect intersect the canvas bounds. + IntRect dstRect = IntRect(dstOffset, srcRect.Size()).Intersect(GetRect()); + // Move the source rect to match the altered dest rect. + srcRect += dstRect.TopLeft() - dstOffset; + srcRect.SizeTo(dstRect.Size()); + + if (!srcPixmap.extractSubset(&srcPixmap, IntRectToSkIRect(srcRect))) { + return; + } + + mCanvas->writePixels(srcPixmap.info(), srcPixmap.addr(), srcPixmap.rowBytes(), + dstRect.x, dstRect.y); +} + +static inline SkPixelGeometry GetSkPixelGeometry() { + return Factory::GetBGRSubpixelOrder() ? kBGR_H_SkPixelGeometry + : kRGB_H_SkPixelGeometry; +} + +template <typename T> +[[nodiscard]] static already_AddRefed<T> AsRefPtr(sk_sp<T>&& aSkPtr) { + return already_AddRefed<T>(aSkPtr.release()); +} + +bool DrawTargetSkia::Init(const IntSize& aSize, SurfaceFormat aFormat) { + if (size_t(std::max(aSize.width, aSize.height)) > GetMaxSurfaceSize()) { + return false; + } + + // we need to have surfaces that have a stride aligned to 4 for interop with + // cairo + SkImageInfo info = MakeSkiaImageInfo(aSize, aFormat); + size_t stride = GetAlignedStride<4>(info.width(), info.bytesPerPixel()); + if (!stride) { + return false; + } + SkSurfaceProps props(0, GetSkPixelGeometry()); + + if (aFormat == SurfaceFormat::A8) { + // Skia does not fully allocate the last row according to stride. + // Since some of our algorithms (i.e. blur) depend on this, we must allocate + // the bitmap pixels manually. + CheckedInt<size_t> size = stride; + size *= info.height(); + // We need to leave room for an additional 3 bytes for a potential overrun + // in our blurring code. + size += 3; + if (!size.isValid()) { + return false; + } + void* buf = sk_malloc_flags(size.value(), SK_MALLOC_ZERO_INITIALIZE); + if (!buf) { + return false; + } + mSurface = AsRefPtr(SkSurface::MakeRasterDirectReleaseProc( + info, buf, stride, FreeAlphaPixels, nullptr, &props)); + } else { + mSurface = AsRefPtr(SkSurface::MakeRaster(info, stride, &props)); + } + if (!mSurface) { + return false; + } + + mSize = aSize; + mFormat = aFormat; + mCanvas = mSurface->getCanvas(); + SetPermitSubpixelAA(IsOpaque(mFormat)); + + if (info.isOpaque()) { + mCanvas->clear(SK_ColorBLACK); + } + return true; +} + +bool DrawTargetSkia::Init(SkCanvas* aCanvas) { + mCanvas = aCanvas; + + SkImageInfo imageInfo = mCanvas->imageInfo(); + + // If the canvas is backed by pixels we clear it to be on the safe side. If + // it's not (for example, for PDF output) we don't. + if (IsBackedByPixels(mCanvas)) { + SkColor clearColor = + imageInfo.isOpaque() ? SK_ColorBLACK : SK_ColorTRANSPARENT; + mCanvas->clear(clearColor); + } + + SkISize size = mCanvas->getBaseLayerSize(); + mSize.width = size.width(); + mSize.height = size.height(); + mFormat = + SkiaColorTypeToGfxFormat(imageInfo.colorType(), imageInfo.alphaType()); + SetPermitSubpixelAA(IsOpaque(mFormat)); + return true; +} + +bool DrawTargetSkia::Init(unsigned char* aData, const IntSize& aSize, + int32_t aStride, SurfaceFormat aFormat, + bool aUninitialized) { + MOZ_ASSERT((aFormat != SurfaceFormat::B8G8R8X8) || aUninitialized || + VerifyRGBXFormat(aData, aSize, aStride, aFormat)); + + SkSurfaceProps props(0, GetSkPixelGeometry()); + mSurface = AsRefPtr(SkSurface::MakeRasterDirect( + MakeSkiaImageInfo(aSize, aFormat), aData, aStride, &props)); + if (!mSurface) { + return false; + } + + mSize = aSize; + mFormat = aFormat; + mCanvas = mSurface->getCanvas(); + SetPermitSubpixelAA(IsOpaque(mFormat)); + return true; +} + +bool DrawTargetSkia::Init(RefPtr<DataSourceSurface>&& aSurface) { + auto map = + new DataSourceSurface::ScopedMap(aSurface, DataSourceSurface::READ_WRITE); + if (!map->IsMapped()) { + delete map; + return false; + } + + SurfaceFormat format = aSurface->GetFormat(); + IntSize size = aSurface->GetSize(); + MOZ_ASSERT((format != SurfaceFormat::B8G8R8X8) || + VerifyRGBXFormat(map->GetData(), size, map->GetStride(), format)); + + SkSurfaceProps props(0, GetSkPixelGeometry()); + mSurface = AsRefPtr(SkSurface::MakeRasterDirectReleaseProc( + MakeSkiaImageInfo(size, format), map->GetData(), map->GetStride(), + DrawTargetSkia::ReleaseMappedSkSurface, map, &props)); + if (!mSurface) { + delete map; + return false; + } + + // map is now owned by mSurface + mBackingSurface = std::move(aSurface); + mSize = size; + mFormat = format; + mCanvas = mSurface->getCanvas(); + SetPermitSubpixelAA(IsOpaque(format)); + return true; +} + +/* static */ void DrawTargetSkia::ReleaseMappedSkSurface(void* aPixels, + void* aContext) { + auto map = reinterpret_cast<DataSourceSurface::ScopedMap*>(aContext); + delete map; +} + +void DrawTargetSkia::SetTransform(const Matrix& aTransform) { + SkMatrix mat; + GfxMatrixToSkiaMatrix(aTransform, mat); + mCanvas->setMatrix(mat); + mTransform = aTransform; +} + +void* DrawTargetSkia::GetNativeSurface(NativeSurfaceType aType) { + return nullptr; +} + +already_AddRefed<PathBuilder> DrawTargetSkia::CreatePathBuilder( + FillRule aFillRule) const { + return PathBuilderSkia::Create(aFillRule); +} + +void DrawTargetSkia::ClearRect(const Rect& aRect) { + MarkChanged(); + mCanvas->save(); + // Restrict clearing to the clip region if requested + mCanvas->clipRect(RectToSkRect(aRect), SkClipOp::kIntersect, true); + SkColor clearColor = (mFormat == SurfaceFormat::B8G8R8X8) + ? SK_ColorBLACK + : SK_ColorTRANSPARENT; + mCanvas->clear(clearColor); + mCanvas->restore(); +} + +void DrawTargetSkia::PushClip(const Path* aPath) { + if (aPath->GetBackendType() != BackendType::SKIA) { + return; + } + + const PathSkia* skiaPath = static_cast<const PathSkia*>(aPath); + mCanvas->save(); + mCanvas->clipPath(skiaPath->GetPath(), SkClipOp::kIntersect, true); +} + +void DrawTargetSkia::PushDeviceSpaceClipRects(const IntRect* aRects, + uint32_t aCount) { + // Build a region by unioning all the rects together. + SkRegion region; + for (uint32_t i = 0; i < aCount; i++) { + region.op(IntRectToSkIRect(aRects[i]), SkRegion::kUnion_Op); + } + + // Clip with the resulting region. clipRegion does not transform + // this region by the current transform, unlike the other SkCanvas + // clip methods, so it is just passed through in device-space. + mCanvas->save(); + mCanvas->clipRegion(region, SkClipOp::kIntersect); +} + +void DrawTargetSkia::PushClipRect(const Rect& aRect) { + SkRect rect = RectToSkRect(aRect); + + mCanvas->save(); + mCanvas->clipRect(rect, SkClipOp::kIntersect, true); +} + +void DrawTargetSkia::PopClip() { + mCanvas->restore(); + SetTransform(GetTransform()); +} + +bool DrawTargetSkia::RemoveAllClips() { + mCanvas->restoreToCount(1); + SetTransform(GetTransform()); + return true; +} + +// Get clip bounds in device space for the clipping region. By default, only +// bounds for simple (empty or rect) regions are reported. If explicitly +// allowed, the bounds will be reported for complex (all other) regions as well. +Maybe<IntRect> DrawTargetSkia::GetDeviceClipRect(bool aAllowComplex) const { + if (mCanvas->isClipEmpty()) { + return Some(IntRect()); + } + if (aAllowComplex || mCanvas->isClipRect()) { + SkIRect deviceBounds; + if (mCanvas->getDeviceClipBounds(&deviceBounds)) { + return Some(SkIRectToIntRect(deviceBounds)); + } + } + return Nothing(); +} + +void DrawTargetSkia::PushLayer(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds, bool aCopyBackground) { + PushLayerWithBlend(aOpaque, aOpacity, aMask, aMaskTransform, aBounds, + aCopyBackground, CompositionOp::OP_OVER); +} + +void DrawTargetSkia::PushLayerWithBlend(bool aOpaque, Float aOpacity, + SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds, + bool aCopyBackground, + CompositionOp aCompositionOp) { + SkPaint paint; + + paint.setAlpha(ColorFloatToByte(aOpacity)); + paint.setBlendMode(GfxOpToSkiaOp(aCompositionOp)); + + // aBounds is supplied in device space, but SaveLayerRec wants local space. + SkRect bounds = SkRect::MakeEmpty(); + if (!aBounds.IsEmpty()) { + Matrix inverseTransform = mTransform; + if (inverseTransform.Invert()) { + bounds = RectToSkRect(inverseTransform.TransformBounds(Rect(aBounds))); + } + } + + // We don't pass a lock object to GetSkImageForSurface here, to force a + // copy of the data if this is a copy-on-write snapshot. If we instead held + // the lock until the corresponding PopLayer, we'd risk deadlocking if someone + // tried to touch the originating DrawTarget while the layer was pushed. + sk_sp<SkImage> clipImage = GetSkImageForSurface(aMask, nullptr); + bool usedMask = false; + if (bool(clipImage)) { + Rect maskBounds(aMask->GetRect()); + sk_sp<SkShader> shader = clipImage->makeShader( + SkTileMode::kClamp, SkTileMode::kClamp, + SkSamplingOptions(SkFilterMode::kLinear), + SkMatrix::Translate(PointToSkPoint(maskBounds.TopLeft()))); + if (shader) { + usedMask = true; + mCanvas->save(); + + auto oldMatrix = mCanvas->getLocalToDevice(); + SkMatrix clipMatrix; + GfxMatrixToSkiaMatrix(aMaskTransform, clipMatrix); + mCanvas->concat(clipMatrix); + + mCanvas->clipRect(RectToSkRect(maskBounds)); + mCanvas->clipShader(shader); + + mCanvas->setMatrix(oldMatrix); + } else { + gfxDebug() << "Failed to create Skia clip shader for PushLayerWithBlend"; + } + } + + PushedLayer layer(GetPermitSubpixelAA(), usedMask ? aMask : nullptr); + mPushedLayers.push_back(layer); + + SkCanvas::SaveLayerRec saveRec( + aBounds.IsEmpty() ? nullptr : &bounds, &paint, nullptr, + SkCanvas::kPreserveLCDText_SaveLayerFlag | + (aCopyBackground ? SkCanvas::kInitWithPrevious_SaveLayerFlag : 0)); + + mCanvas->saveLayer(saveRec); + + SetPermitSubpixelAA(aOpaque); + +#ifdef MOZ_WIDGET_COCOA + CGContextRelease(mCG); + mCG = nullptr; +#endif +} + +void DrawTargetSkia::PopLayer() { + MOZ_RELEASE_ASSERT(!mPushedLayers.empty()); + + MarkChanged(); + + const PushedLayer& layer = mPushedLayers.back(); + + mCanvas->restore(); + + if (layer.mMask) { + mCanvas->restore(); + } + + SetTransform(GetTransform()); + SetPermitSubpixelAA(layer.mOldPermitSubpixelAA); + + mPushedLayers.pop_back(); + +#ifdef MOZ_WIDGET_COCOA + CGContextRelease(mCG); + mCG = nullptr; +#endif +} + +already_AddRefed<GradientStops> DrawTargetSkia::CreateGradientStops( + GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode) const { + std::vector<GradientStop> stops; + stops.resize(aNumStops); + for (uint32_t i = 0; i < aNumStops; i++) { + stops[i] = aStops[i]; + } + std::stable_sort(stops.begin(), stops.end()); + + return MakeAndAddRef<GradientStopsSkia>(stops, aNumStops, aExtendMode); +} + +already_AddRefed<FilterNode> DrawTargetSkia::CreateFilter(FilterType aType) { + return FilterNodeSoftware::Create(aType); +} + +void DrawTargetSkia::MarkChanged() { + // I'm not entirely certain whether this lock is needed, as multiple threads + // should never modify the DrawTarget at the same time anyway, but this seems + // like the safest. + MutexAutoLock lock(mSnapshotLock); + if (mSnapshot) { + if (mSnapshot->hasOneRef()) { + // No owners outside of this DrawTarget's own reference. Just dump it. + mSnapshot = nullptr; + return; + } + + mSnapshot->DrawTargetWillChange(); + mSnapshot = nullptr; + + // Handle copying of any image snapshots bound to the surface. + if (mSurface) { + mSurface->notifyContentWillChange(SkSurface::kRetain_ContentChangeMode); + } + } +} + +} // namespace mozilla::gfx diff --git a/gfx/2d/DrawTargetSkia.h b/gfx/2d/DrawTargetSkia.h new file mode 100644 index 0000000000..e091a9e937 --- /dev/null +++ b/gfx/2d/DrawTargetSkia.h @@ -0,0 +1,212 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_DRAWTARGETSKIA_H +#define _MOZILLA_GFX_DRAWTARGETSKIA_H + +#include "2D.h" +#include <sstream> +#include <vector> + +#ifdef MOZ_WIDGET_COCOA +# include <ApplicationServices/ApplicationServices.h> +#endif + +class SkCanvas; +class SkSurface; + +namespace mozilla { + +template <> +class RefPtrTraits<SkSurface> { + public: + static void Release(SkSurface* aSurface); + static void AddRef(SkSurface* aSurface); +}; + +namespace gfx { + +class DataSourceSurface; +class SourceSurfaceSkia; +class BorrowedCGContext; + +class DrawTargetSkia : public DrawTarget { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTargetSkia, override) + DrawTargetSkia(); + virtual ~DrawTargetSkia(); + + virtual DrawTargetType GetType() const override; + virtual BackendType GetBackendType() const override { + return BackendType::SKIA; + } + already_AddRefed<SourceSurface> Snapshot(SurfaceFormat aFormat); + virtual already_AddRefed<SourceSurface> Snapshot() override { + return Snapshot(mFormat); + } + already_AddRefed<SourceSurface> GetBackingSurface() override; + virtual IntSize GetSize() const override { return mSize; }; + virtual bool LockBits(uint8_t** aData, IntSize* aSize, int32_t* aStride, + SurfaceFormat* aFormat, + IntPoint* aOrigin = nullptr) override; + virtual void ReleaseBits(uint8_t* aData) override; + virtual void Flush() override; + virtual void DrawSurface( + SourceSurface* aSurface, const Rect& aDest, const Rect& aSource, + const DrawSurfaceOptions& aSurfOptions = DrawSurfaceOptions(), + const DrawOptions& aOptions = DrawOptions()) override; + virtual void DrawFilter(FilterNode* aNode, const Rect& aSourceRect, + const Point& aDestPoint, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void DrawSurfaceWithShadow(SourceSurface* aSurface, + const Point& aDest, + const ShadowOptions& aShadow, + CompositionOp aOperator) override; + virtual void ClearRect(const Rect& aRect) override; + virtual void CopySurface(SourceSurface* aSurface, const IntRect& aSourceRect, + const IntPoint& aDestination) override; + virtual void FillRect(const Rect& aRect, const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void StrokeRect(const Rect& aRect, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()) override; + virtual void StrokeLine(const Point& aStart, const Point& aEnd, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()) override; + virtual void Stroke(const Path* aPath, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()) override; + virtual void Fill(const Path* aPath, const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()) override; + + virtual void FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void StrokeGlyphs( + ScaledFont* aFont, const GlyphBuffer& aBuffer, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions = StrokeOptions(), + const DrawOptions& aOptions = DrawOptions()) override; + virtual void Mask(const Pattern& aSource, const Pattern& aMask, + const DrawOptions& aOptions = DrawOptions()) override; + virtual void MaskSurface( + const Pattern& aSource, SourceSurface* aMask, Point aOffset, + const DrawOptions& aOptions = DrawOptions()) override; + virtual bool Draw3DTransformedSurface(SourceSurface* aSurface, + const Matrix4x4& aMatrix) override; + virtual void PushClip(const Path* aPath) override; + virtual void PushClipRect(const Rect& aRect) override; + virtual void PushDeviceSpaceClipRects(const IntRect* aRects, + uint32_t aCount) override; + virtual void PopClip() override; + virtual bool RemoveAllClips() override; + virtual void PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds = IntRect(), + bool aCopyBackground = false) override; + virtual void PushLayerWithBlend( + bool aOpaque, Float aOpacity, SourceSurface* aMask, + const Matrix& aMaskTransform, const IntRect& aBounds = IntRect(), + bool aCopyBackground = false, + CompositionOp aCompositionOp = CompositionOp::OP_OVER) override; + virtual void PopLayer() override; + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData( + unsigned char* aData, const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat) const override; + virtual already_AddRefed<SourceSurface> OptimizeSourceSurface( + SourceSurface* aSurface) const override; + virtual already_AddRefed<SourceSurface> OptimizeSourceSurfaceForUnknownAlpha( + SourceSurface* aSurface) const override; + virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromNativeSurface( + const NativeSurface& aSurface) const override; + virtual already_AddRefed<DrawTarget> CreateSimilarDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat) const override; + virtual bool CanCreateSimilarDrawTarget(const IntSize& aSize, + SurfaceFormat aFormat) const override; + virtual RefPtr<DrawTarget> CreateClippedDrawTarget( + const Rect& aBounds, SurfaceFormat aFormat) override; + + virtual already_AddRefed<PathBuilder> CreatePathBuilder( + FillRule aFillRule = FillRule::FILL_WINDING) const override; + + virtual already_AddRefed<GradientStops> CreateGradientStops( + GradientStop* aStops, uint32_t aNumStops, + ExtendMode aExtendMode = ExtendMode::CLAMP) const override; + virtual already_AddRefed<FilterNode> CreateFilter(FilterType aType) override; + virtual void SetTransform(const Matrix& aTransform) override; + virtual void* GetNativeSurface(NativeSurfaceType aType) override; + virtual void DetachAllSnapshots() override { MarkChanged(); } + + bool Init(const IntSize& aSize, SurfaceFormat aFormat); + bool Init(unsigned char* aData, const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat, bool aUninitialized = false); + bool Init(SkCanvas* aCanvas); + bool Init(RefPtr<DataSourceSurface>&& aSurface); + + // Skia assumes that texture sizes fit in 16-bit signed integers. + static size_t GetMaxSurfaceSize() { return 32767; } + + operator std::string() const { + std::stringstream stream; + stream << "DrawTargetSkia(" << this << ")"; + return stream.str(); + } + + Maybe<IntRect> GetDeviceClipRect(bool aAllowComplex = false) const; + + Maybe<Rect> GetGlyphLocalBounds(ScaledFont* aFont, const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const StrokeOptions* aStrokeOptions, + const DrawOptions& aOptions); + + private: + friend class SourceSurfaceSkia; + + static void ReleaseMappedSkSurface(void* aPixels, void* aContext); + + void MarkChanged(); + + void DrawGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const StrokeOptions* aStrokeOptions = nullptr, + const DrawOptions& aOptions = DrawOptions()); + + struct PushedLayer { + PushedLayer(bool aOldPermitSubpixelAA, SourceSurface* aMask) + : mOldPermitSubpixelAA(aOldPermitSubpixelAA), mMask(aMask) {} + bool mOldPermitSubpixelAA; + RefPtr<SourceSurface> mMask; + }; + std::vector<PushedLayer> mPushedLayers; + + IntSize mSize; + RefPtr<SkSurface> mSurface; + SkCanvas* mCanvas = nullptr; + RefPtr<DataSourceSurface> mBackingSurface; + RefPtr<SourceSurfaceSkia> mSnapshot; + Mutex mSnapshotLock MOZ_UNANNOTATED; + +#ifdef MOZ_WIDGET_COCOA + friend class BorrowedCGContext; + + CGContextRef BorrowCGContext(const DrawOptions& aOptions); + void ReturnCGContext(CGContextRef); + bool FillGlyphsWithCG(ScaledFont* aFont, const GlyphBuffer& aBuffer, + const Pattern& aPattern, + const DrawOptions& aOptions = DrawOptions()); + + CGContextRef mCG; + CGColorSpaceRef mColorSpace; + uint8_t* mCanvasData; + IntSize mCGSize; + bool mNeedLayer; +#endif +}; + +} // namespace gfx +} // namespace mozilla + +#endif // _MOZILLA_GFX_SOURCESURFACESKIA_H diff --git a/gfx/2d/ExtendInputEffectD2D1.cpp b/gfx/2d/ExtendInputEffectD2D1.cpp new file mode 100644 index 0000000000..1203df9a4f --- /dev/null +++ b/gfx/2d/ExtendInputEffectD2D1.cpp @@ -0,0 +1,190 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ExtendInputEffectD2D1.h" + +#include "Logging.h" + +#include "ShadersD2D1.h" +#include "HelpersD2D.h" + +#include <vector> + +#define TEXTW(x) L##x +#define XML(X) \ + TEXTW(#X) // This macro creates a single string from multiple lines of text. + +static const PCWSTR kXmlDescription = + XML( + <?xml version='1.0'?> + <Effect> + <!-- System Properties --> + <Property name='DisplayName' type='string' value='ExtendInputEffect'/> + <Property name='Author' type='string' value='Mozilla'/> + <Property name='Category' type='string' value='Utility Effects'/> + <Property name='Description' type='string' value='This effect is used to extend the output rect of any input effect to a specified rect.'/> + <Inputs> + <Input name='InputEffect'/> + </Inputs> + <Property name='OutputRect' type='vector4'> + <Property name='DisplayName' type='string' value='Output Rect'/> + </Property> + </Effect> + ); + +namespace mozilla { +namespace gfx { + +ExtendInputEffectD2D1::ExtendInputEffectD2D1() + : mRefCount(0), + mOutputRect(D2D1::Vector4F(-FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX)) {} + +IFACEMETHODIMP +ExtendInputEffectD2D1::Initialize(ID2D1EffectContext* pContextInternal, + ID2D1TransformGraph* pTransformGraph) { + HRESULT hr; + hr = pTransformGraph->SetSingleTransformNode(this); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +IFACEMETHODIMP +ExtendInputEffectD2D1::PrepareForRender(D2D1_CHANGE_TYPE changeType) { + return S_OK; +} + +IFACEMETHODIMP +ExtendInputEffectD2D1::SetGraph(ID2D1TransformGraph* pGraph) { + return E_NOTIMPL; +} + +IFACEMETHODIMP_(ULONG) +ExtendInputEffectD2D1::AddRef() { return ++mRefCount; } + +IFACEMETHODIMP_(ULONG) +ExtendInputEffectD2D1::Release() { + if (!--mRefCount) { + delete this; + return 0; + } + return mRefCount; +} + +IFACEMETHODIMP +ExtendInputEffectD2D1::QueryInterface(const IID& aIID, void** aPtr) { + if (!aPtr) { + return E_POINTER; + } + + if (aIID == IID_IUnknown) { + *aPtr = static_cast<IUnknown*>(static_cast<ID2D1EffectImpl*>(this)); + } else if (aIID == IID_ID2D1EffectImpl) { + *aPtr = static_cast<ID2D1EffectImpl*>(this); + } else if (aIID == IID_ID2D1DrawTransform) { + *aPtr = static_cast<ID2D1DrawTransform*>(this); + } else if (aIID == IID_ID2D1Transform) { + *aPtr = static_cast<ID2D1Transform*>(this); + } else if (aIID == IID_ID2D1TransformNode) { + *aPtr = static_cast<ID2D1TransformNode*>(this); + } else { + return E_NOINTERFACE; + } + + static_cast<IUnknown*>(*aPtr)->AddRef(); + return S_OK; +} + +static D2D1_RECT_L ConvertFloatToLongRect(const D2D1_VECTOR_4F& aRect) { + // Clamp values to LONG range. We can't use std::min/max here because we want + // the comparison to operate on a type that's different from the type of the + // result. + return D2D1::RectL(aRect.x <= float(LONG_MIN) ? LONG_MIN : LONG(aRect.x), + aRect.y <= float(LONG_MIN) ? LONG_MIN : LONG(aRect.y), + aRect.z >= float(LONG_MAX) ? LONG_MAX : LONG(aRect.z), + aRect.w >= float(LONG_MAX) ? LONG_MAX : LONG(aRect.w)); +} + +static D2D1_RECT_L IntersectRect(const D2D1_RECT_L& aRect1, + const D2D1_RECT_L& aRect2) { + return D2D1::RectL(std::max(aRect1.left, aRect2.left), + std::max(aRect1.top, aRect2.top), + std::min(aRect1.right, aRect2.right), + std::min(aRect1.bottom, aRect2.bottom)); +} + +IFACEMETHODIMP +ExtendInputEffectD2D1::MapInputRectsToOutputRect( + const D2D1_RECT_L* pInputRects, const D2D1_RECT_L* pInputOpaqueSubRects, + UINT32 inputRectCount, D2D1_RECT_L* pOutputRect, + D2D1_RECT_L* pOutputOpaqueSubRect) { + // This transform only accepts one input, so there will only be one input + // rect. + if (inputRectCount != 1) { + return E_INVALIDARG; + } + + // Set the output rect to the specified rect. This is the whole purpose of + // this effect. + *pOutputRect = ConvertFloatToLongRect(mOutputRect); + *pOutputOpaqueSubRect = IntersectRect(*pOutputRect, pInputOpaqueSubRects[0]); + return S_OK; +} + +IFACEMETHODIMP +ExtendInputEffectD2D1::MapOutputRectToInputRects(const D2D1_RECT_L* pOutputRect, + D2D1_RECT_L* pInputRects, + UINT32 inputRectCount) const { + if (inputRectCount != 1) { + return E_INVALIDARG; + } + + *pInputRects = *pOutputRect; + return S_OK; +} + +IFACEMETHODIMP +ExtendInputEffectD2D1::MapInvalidRect(UINT32 inputIndex, + D2D1_RECT_L invalidInputRect, + D2D1_RECT_L* pInvalidOutputRect) const { + MOZ_ASSERT(inputIndex == 0); + + *pInvalidOutputRect = invalidInputRect; + return S_OK; +} + +HRESULT +ExtendInputEffectD2D1::Register(ID2D1Factory1* aFactory) { + D2D1_PROPERTY_BINDING bindings[] = { + D2D1_VALUE_TYPE_BINDING(L"OutputRect", + &ExtendInputEffectD2D1::SetOutputRect, + &ExtendInputEffectD2D1::GetOutputRect), + }; + HRESULT hr = aFactory->RegisterEffectFromString( + CLSID_ExtendInputEffect, kXmlDescription, bindings, 1, CreateEffect); + + if (FAILED(hr)) { + gfxWarning() << "Failed to register extend input effect."; + } + return hr; +} + +void ExtendInputEffectD2D1::Unregister(ID2D1Factory1* aFactory) { + aFactory->UnregisterEffect(CLSID_ExtendInputEffect); +} + +HRESULT __stdcall ExtendInputEffectD2D1::CreateEffect(IUnknown** aEffectImpl) { + *aEffectImpl = static_cast<ID2D1EffectImpl*>(new ExtendInputEffectD2D1()); + (*aEffectImpl)->AddRef(); + + return S_OK; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/ExtendInputEffectD2D1.h b/gfx/2d/ExtendInputEffectD2D1.h new file mode 100644 index 0000000000..47a026b940 --- /dev/null +++ b/gfx/2d/ExtendInputEffectD2D1.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_EXTENDINPUTEFFECTD2D1_H_ +#define MOZILLA_GFX_EXTENDINPUTEFFECTD2D1_H_ + +#include <d2d1_1.h> +#include <d2d1effectauthor.h> +#include <d2d1effecthelpers.h> + +#include "2D.h" +#include "mozilla/Attributes.h" + +// {97143DC6-CBC4-4DD4-A8BA-13342B0BA46D} +DEFINE_GUID(CLSID_ExtendInputEffect, 0x5fb55c7c, 0xd795, 0x4ba3, 0xa9, 0x5c, + 0x22, 0x82, 0x5d, 0x0c, 0x4d, 0xf7); + +namespace mozilla { +namespace gfx { + +enum { EXTENDINPUT_PROP_OUTPUT_RECT = 0 }; + +// An effect type that passes through its input unchanged but sets the effect's +// output rect to a specified rect. Unlike the built-in Crop effect, the +// ExtendInput effect can extend the input rect, and not just make it smaller. +// The added margins are filled with transparent black. +// Some effects have different output depending on their input effect's output +// rect, for example the Border effect (which repeats the edges of its input +// effect's output rect) or the component transfer and color matrix effects +// (which can transform transparent pixels into non-transparent ones, but only +// inside their input effect's output rect). +class ExtendInputEffectD2D1 final : public ID2D1EffectImpl, + public ID2D1DrawTransform { + public: + // ID2D1EffectImpl + IFACEMETHODIMP Initialize(ID2D1EffectContext* pContextInternal, + ID2D1TransformGraph* pTransformGraph); + IFACEMETHODIMP PrepareForRender(D2D1_CHANGE_TYPE changeType); + IFACEMETHODIMP SetGraph(ID2D1TransformGraph* pGraph); + + // IUnknown + IFACEMETHODIMP_(ULONG) AddRef(); + IFACEMETHODIMP_(ULONG) Release(); + IFACEMETHODIMP QueryInterface(REFIID riid, void** ppOutput); + + // ID2D1Transform + IFACEMETHODIMP MapInputRectsToOutputRect( + const D2D1_RECT_L* pInputRects, const D2D1_RECT_L* pInputOpaqueSubRects, + UINT32 inputRectCount, D2D1_RECT_L* pOutputRect, + D2D1_RECT_L* pOutputOpaqueSubRect); + IFACEMETHODIMP MapOutputRectToInputRects(const D2D1_RECT_L* pOutputRect, + D2D1_RECT_L* pInputRects, + UINT32 inputRectCount) const; + IFACEMETHODIMP MapInvalidRect(UINT32 inputIndex, D2D1_RECT_L invalidInputRect, + D2D1_RECT_L* pInvalidOutputRect) const; + + // ID2D1TransformNode + IFACEMETHODIMP_(UINT32) GetInputCount() const { return 1; } + + // ID2D1DrawTransform + IFACEMETHODIMP SetDrawInfo(ID2D1DrawInfo* pDrawInfo) { return S_OK; } + + static HRESULT Register(ID2D1Factory1* aFactory); + static void Unregister(ID2D1Factory1* aFactory); + static HRESULT __stdcall CreateEffect(IUnknown** aEffectImpl); + + HRESULT SetOutputRect(D2D1_VECTOR_4F aOutputRect) { + mOutputRect = aOutputRect; + return S_OK; + } + D2D1_VECTOR_4F GetOutputRect() const { return mOutputRect; } + + private: + ExtendInputEffectD2D1(); + + uint32_t mRefCount; + D2D1_VECTOR_4F mOutputRect; +}; + +} // namespace gfx +} // namespace mozilla +#undef SIMPLE_PROP + +#endif diff --git a/gfx/2d/Factory.cpp b/gfx/2d/Factory.cpp new file mode 100644 index 0000000000..abf7a8669b --- /dev/null +++ b/gfx/2d/Factory.cpp @@ -0,0 +1,1384 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "2D.h" +#include "Swizzle.h" + +#ifdef USE_CAIRO +# include "DrawTargetCairo.h" +# include "PathCairo.h" +# include "SourceSurfaceCairo.h" +#endif + +#include "DrawTargetSkia.h" +#include "PathSkia.h" +#include "ScaledFontBase.h" + +#if defined(WIN32) +# include "ScaledFontWin.h" +# include "NativeFontResourceGDI.h" +# include "UnscaledFontGDI.h" +#endif + +#ifdef XP_DARWIN +# include "ScaledFontMac.h" +# include "NativeFontResourceMac.h" +# include "UnscaledFontMac.h" +#endif + +#ifdef MOZ_WIDGET_GTK +# include "ScaledFontFontconfig.h" +# include "NativeFontResourceFreeType.h" +# include "UnscaledFontFreeType.h" +#endif + +#ifdef MOZ_WIDGET_ANDROID +# include "ScaledFontFreeType.h" +# include "NativeFontResourceFreeType.h" +# include "UnscaledFontFreeType.h" +#endif + +#ifdef WIN32 +# include "DrawTargetD2D1.h" +# include "PathD2D.h" +# include "ScaledFontDWrite.h" +# include "NativeFontResourceDWrite.h" +# include "UnscaledFontDWrite.h" +# include <d3d10_1.h> +# include <stdlib.h> +# include "HelpersD2D.h" +# include "DXVA2Manager.h" +# include "ImageContainer.h" +# include "mozilla/layers/LayersSurfaces.h" +# include "mozilla/layers/TextureD3D11.h" +# include "nsWindowsHelpers.h" +#endif + +#include "DrawTargetOffset.h" +#include "DrawTargetRecording.h" + +#include "SourceSurfaceRawData.h" + +#include "mozilla/CheckedInt.h" + +#ifdef MOZ_ENABLE_FREETYPE +# include "ft2build.h" +# include FT_FREETYPE_H +#endif +#include "mozilla/StaticPrefs_gfx.h" + +#if defined(MOZ_LOGGING) +GFX2D_API mozilla::LogModule* GetGFX2DLog() { + static mozilla::LazyLogModule sLog("gfx2d"); + return sLog; +} +#endif + +// The following code was largely taken from xpcom/glue/SSE.cpp and +// made a little simpler. +enum CPUIDRegister { eax = 0, ebx = 1, ecx = 2, edx = 3 }; + +#ifdef HAVE_CPUID_H + +# if !(defined(__SSE2__) || defined(_M_X64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP >= 2)) || \ + !defined(__SSE4__) +// cpuid.h is available on gcc 4.3 and higher on i386 and x86_64 +# include <cpuid.h> + +static inline bool HasCPUIDBit(unsigned int level, CPUIDRegister reg, + unsigned int bit) { + unsigned int regs[4]; + return __get_cpuid(level, ®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 + +#ifdef MOZ_ENABLE_FREETYPE +extern "C" { + +void mozilla_AddRefSharedFTFace(void* aContext) { + if (aContext) { + static_cast<mozilla::gfx::SharedFTFace*>(aContext)->AddRef(); + } +} + +void mozilla_ReleaseSharedFTFace(void* aContext, void* aOwner) { + if (aContext) { + auto* sharedFace = static_cast<mozilla::gfx::SharedFTFace*>(aContext); + sharedFace->ForgetLockOwner(aOwner); + sharedFace->Release(); + } +} + +void mozilla_ForgetSharedFTFaceLockOwner(void* aContext, void* aOwner) { + static_cast<mozilla::gfx::SharedFTFace*>(aContext)->ForgetLockOwner(aOwner); +} + +int mozilla_LockSharedFTFace(void* aContext, + void* aOwner) MOZ_NO_THREAD_SAFETY_ANALYSIS { + return int(static_cast<mozilla::gfx::SharedFTFace*>(aContext)->Lock(aOwner)); +} + +void mozilla_UnlockSharedFTFace(void* aContext) MOZ_NO_THREAD_SAFETY_ANALYSIS { + static_cast<mozilla::gfx::SharedFTFace*>(aContext)->Unlock(); +} + +FT_Error mozilla_LoadFTGlyph(FT_Face aFace, uint32_t aGlyphIndex, + int32_t aFlags) { + return mozilla::gfx::Factory::LoadFTGlyph(aFace, aGlyphIndex, aFlags); +} + +void mozilla_LockFTLibrary(FT_Library aFTLibrary) { + mozilla::gfx::Factory::LockFTLibrary(aFTLibrary); +} + +void mozilla_UnlockFTLibrary(FT_Library aFTLibrary) { + mozilla::gfx::Factory::UnlockFTLibrary(aFTLibrary); +} +} +#endif + +namespace mozilla::gfx { + +#ifdef MOZ_ENABLE_FREETYPE +FT_Library Factory::mFTLibrary = nullptr; +StaticMutex Factory::mFTLock; + +already_AddRefed<SharedFTFace> FTUserFontData::CloneFace(int aFaceIndex) { + if (mFontData) { + RefPtr<SharedFTFace> face = Factory::NewSharedFTFaceFromData( + nullptr, mFontData, mLength, aFaceIndex, this); + if (!face || + (FT_Select_Charmap(face->GetFace(), FT_ENCODING_UNICODE) != FT_Err_Ok && + FT_Select_Charmap(face->GetFace(), FT_ENCODING_MS_SYMBOL) != + FT_Err_Ok)) { + return nullptr; + } + return face.forget(); + } + FT_Face face = Factory::NewFTFace(nullptr, mFilename.c_str(), aFaceIndex); + if (face) { + return MakeAndAddRef<SharedFTFace>(face, this); + } + return nullptr; +} +#endif + +#ifdef WIN32 +// Note: mDeviceLock must be held when mutating these values. +static uint32_t mDeviceSeq = 0; +StaticRefPtr<ID3D11Device> Factory::mD3D11Device; +StaticRefPtr<ID2D1Device> Factory::mD2D1Device; +StaticRefPtr<IDWriteFactory> Factory::mDWriteFactory; +StaticRefPtr<ID2D1DeviceContext> Factory::mMTDC; +StaticRefPtr<ID2D1DeviceContext> Factory::mOffMTDC; +bool Factory::mDWriteFactoryInitialized = false; +StaticRefPtr<IDWriteFontCollection> Factory::mDWriteSystemFonts; +StaticMutex Factory::mDeviceLock; +StaticMutex Factory::mDTDependencyLock; +#endif + +bool Factory::mBGRSubpixelOrder = false; + +mozilla::gfx::Config* Factory::sConfig = nullptr; + +void Factory::Init(const Config& aConfig) { + MOZ_ASSERT(!sConfig); + sConfig = new Config(aConfig); + +#ifdef XP_DARWIN + NativeFontResourceMac::RegisterMemoryReporter(); +#else + NativeFontResource::RegisterMemoryReporter(); +#endif +} + +void Factory::ShutDown() { + if (sConfig) { + delete sConfig->mLogForwarder; + delete sConfig; + sConfig = nullptr; + } + +#ifdef MOZ_ENABLE_FREETYPE + mFTLibrary = nullptr; +#endif +} + +bool Factory::HasSSE2() { +#if defined(__SSE2__) || defined(_M_X64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP >= 2) + // gcc with -msse2 (default on OSX and x86-64) + // cl.exe with -arch:SSE2 (default on x64 compiler) + return true; +#elif defined(HAVE_CPU_DETECTION) + static enum { + UNINITIALIZED, + NO_SSE2, + HAS_SSE2 + } sDetectionState = UNINITIALIZED; + + if (sDetectionState == UNINITIALIZED) { + sDetectionState = HasCPUIDBit(1u, edx, (1u << 26)) ? HAS_SSE2 : NO_SSE2; + } + return sDetectionState == HAS_SSE2; +#else + return false; +#endif +} + +bool Factory::HasSSE4() { +#if defined(__SSE4__) + // gcc with -msse2 (default on OSX and x86-64) + // cl.exe with -arch:SSE2 (default on x64 compiler) + return true; +#elif defined(HAVE_CPU_DETECTION) + static enum { + UNINITIALIZED, + NO_SSE4, + HAS_SSE4 + } sDetectionState = UNINITIALIZED; + + if (sDetectionState == UNINITIALIZED) { + sDetectionState = HasCPUIDBit(1u, ecx, (1u << 19)) ? HAS_SSE4 : NO_SSE4; + } + return sDetectionState == HAS_SSE4; +#else + return false; +#endif +} + +// If the size is "reasonable", we want gfxCriticalError to assert, so +// this is the option set up for it. +inline int LoggerOptionsBasedOnSize(const IntSize& aSize) { + return CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(aSize)); +} + +bool Factory::ReasonableSurfaceSize(const IntSize& aSize) { + return Factory::CheckSurfaceSize(aSize, kReasonableSurfaceSize); +} + +bool Factory::AllowedSurfaceSize(const IntSize& aSize) { + if (sConfig) { + return Factory::CheckSurfaceSize(aSize, sConfig->mMaxTextureSize, + sConfig->mMaxAllocSize); + } + + return CheckSurfaceSize(aSize); +} + +bool Factory::CheckSurfaceSize(const IntSize& sz, int32_t extentLimit, + int32_t allocLimit) { + if (sz.width <= 0 || sz.height <= 0) { + return false; + } + + // reject images with sides bigger than limit + if (extentLimit && (sz.width > extentLimit || sz.height > extentLimit)) { + gfxDebug() << "Surface size too large (exceeds extent limit)!"; + return false; + } + + // assuming 4 bytes per pixel, make sure the allocation size + // doesn't overflow a int32_t either + CheckedInt<int32_t> stride = GetAlignedStride<16>(sz.width, 4); + if (!stride.isValid() || stride.value() == 0) { + gfxDebug() << "Surface size too large (stride overflows int32_t)!"; + return false; + } + + CheckedInt<int32_t> numBytes = stride * sz.height; + if (!numBytes.isValid()) { + gfxDebug() + << "Surface size too large (allocation size would overflow int32_t)!"; + return false; + } + + if (allocLimit && allocLimit < numBytes.value()) { + gfxDebug() << "Surface size too large (exceeds allocation limit)!"; + return false; + } + + return true; +} + +already_AddRefed<DrawTarget> Factory::CreateDrawTarget(BackendType aBackend, + const IntSize& aSize, + SurfaceFormat aFormat) { + if (!AllowedSurfaceSize(aSize)) { + gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) + << "Failed to allocate a surface due to invalid size (CDT) " << aSize; + return nullptr; + } + + RefPtr<DrawTarget> retVal; + switch (aBackend) { +#ifdef WIN32 + case BackendType::DIRECT2D1_1: { + RefPtr<DrawTargetD2D1> newTarget; + newTarget = new DrawTargetD2D1(); + if (newTarget->Init(aSize, aFormat)) { + retVal = newTarget; + } + break; + } +#endif + case BackendType::SKIA: { + RefPtr<DrawTargetSkia> newTarget; + newTarget = new DrawTargetSkia(); + if (newTarget->Init(aSize, aFormat)) { + retVal = newTarget; + } + break; + } +#ifdef USE_CAIRO + case BackendType::CAIRO: { + RefPtr<DrawTargetCairo> newTarget; + newTarget = new DrawTargetCairo(); + if (newTarget->Init(aSize, aFormat)) { + retVal = newTarget; + } + break; + } +#endif + default: + return nullptr; + } + + if (!retVal) { + // Failed + gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) + << "Failed to create DrawTarget, Type: " << int(aBackend) + << " Size: " << aSize; + } + + return retVal.forget(); +} + +already_AddRefed<PathBuilder> Factory::CreatePathBuilder(BackendType aBackend, + FillRule aFillRule) { + switch (aBackend) { +#ifdef WIN32 + case BackendType::DIRECT2D1_1: + return PathBuilderD2D::Create(aFillRule); +#endif + case BackendType::SKIA: + case BackendType::WEBGL: + return PathBuilderSkia::Create(aFillRule); +#ifdef USE_CAIRO + case BackendType::CAIRO: + return PathBuilderCairo::Create(aFillRule); +#endif + default: + gfxCriticalNote << "Invalid PathBuilder type specified: " + << (int)aBackend; + return nullptr; + } +} + +already_AddRefed<PathBuilder> Factory::CreateSimplePathBuilder() { + return CreatePathBuilder(BackendType::SKIA); +} + +already_AddRefed<DrawTarget> Factory::CreateRecordingDrawTarget( + DrawEventRecorder* aRecorder, DrawTarget* aDT, IntRect aRect) { + return MakeAndAddRef<DrawTargetRecording>(aRecorder, aDT, aRect); +} + +already_AddRefed<DrawTarget> Factory::CreateDrawTargetForData( + BackendType aBackend, unsigned char* aData, const IntSize& aSize, + int32_t aStride, SurfaceFormat aFormat, bool aUninitialized) { + MOZ_ASSERT(aData); + if (!AllowedSurfaceSize(aSize)) { + gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) + << "Failed to allocate a surface due to invalid size (DTD) " << aSize; + return nullptr; + } + + RefPtr<DrawTarget> retVal; + + switch (aBackend) { + case BackendType::SKIA: { + RefPtr<DrawTargetSkia> newTarget; + newTarget = new DrawTargetSkia(); + if (newTarget->Init(aData, aSize, aStride, aFormat, aUninitialized)) { + retVal = newTarget; + } + break; + } +#ifdef USE_CAIRO + case BackendType::CAIRO: { + RefPtr<DrawTargetCairo> newTarget; + newTarget = new DrawTargetCairo(); + if (newTarget->Init(aData, aSize, aStride, aFormat)) { + retVal = std::move(newTarget); + } + break; + } +#endif + default: + gfxCriticalNote << "Invalid draw target type specified: " + << (int)aBackend; + return nullptr; + } + + if (!retVal) { + gfxCriticalNote << "Failed to create DrawTarget, Type: " << int(aBackend) + << " Size: " << aSize << ", Data: " << hexa((void*)aData) + << ", Stride: " << aStride; + } + + return retVal.forget(); +} + +already_AddRefed<DrawTarget> Factory::CreateOffsetDrawTarget( + DrawTarget* aDrawTarget, IntPoint aTileOrigin) { + RefPtr<DrawTargetOffset> dt = new DrawTargetOffset(); + + if (!dt->Init(aDrawTarget, aTileOrigin)) { + return nullptr; + } + + return dt.forget(); +} + +bool Factory::DoesBackendSupportDataDrawtarget(BackendType aType) { + switch (aType) { + case BackendType::DIRECT2D: + case BackendType::DIRECT2D1_1: + case BackendType::RECORDING: + case BackendType::NONE: + case BackendType::BACKEND_LAST: + case BackendType::WEBRENDER_TEXT: + case BackendType::WEBGL: + return false; + case BackendType::CAIRO: + case BackendType::SKIA: + return true; + } + + return false; +} + +uint32_t Factory::GetMaxSurfaceSize(BackendType aType) { + switch (aType) { + case BackendType::CAIRO: + return DrawTargetCairo::GetMaxSurfaceSize(); + case BackendType::SKIA: + return DrawTargetSkia::GetMaxSurfaceSize(); +#ifdef WIN32 + case BackendType::DIRECT2D1_1: + return DrawTargetD2D1::GetMaxSurfaceSize(); +#endif + default: + return 0; + } +} + +already_AddRefed<NativeFontResource> Factory::CreateNativeFontResource( + uint8_t* aData, uint32_t aSize, FontType aFontType, void* aFontContext) { + switch (aFontType) { +#ifdef WIN32 + case FontType::DWRITE: + return NativeFontResourceDWrite::Create(aData, aSize); + case FontType::GDI: + return NativeFontResourceGDI::Create(aData, aSize); +#elif defined(XP_DARWIN) + case FontType::MAC: + return NativeFontResourceMac::Create(aData, aSize); +#elif defined(MOZ_WIDGET_GTK) + case FontType::FONTCONFIG: + return NativeFontResourceFontconfig::Create( + aData, aSize, static_cast<FT_Library>(aFontContext)); +#elif defined(MOZ_WIDGET_ANDROID) + case FontType::FREETYPE: + return NativeFontResourceFreeType::Create( + aData, aSize, static_cast<FT_Library>(aFontContext)); +#endif + default: + gfxWarning() + << "Unable to create requested font resource from truetype data"; + return nullptr; + } +} + +already_AddRefed<UnscaledFont> Factory::CreateUnscaledFontFromFontDescriptor( + FontType aType, const uint8_t* aData, uint32_t aDataLength, + uint32_t aIndex) { + switch (aType) { +#ifdef WIN32 + case FontType::DWRITE: + return UnscaledFontDWrite::CreateFromFontDescriptor(aData, aDataLength, + aIndex); + case FontType::GDI: + return UnscaledFontGDI::CreateFromFontDescriptor(aData, aDataLength, + aIndex); +#elif defined(XP_DARWIN) + case FontType::MAC: + return UnscaledFontMac::CreateFromFontDescriptor(aData, aDataLength, + aIndex); +#elif defined(MOZ_WIDGET_GTK) + case FontType::FONTCONFIG: + return UnscaledFontFontconfig::CreateFromFontDescriptor( + aData, aDataLength, aIndex); +#elif defined(MOZ_WIDGET_ANDROID) + case FontType::FREETYPE: + return UnscaledFontFreeType::CreateFromFontDescriptor(aData, aDataLength, + aIndex); +#endif + default: + gfxWarning() << "Invalid type specified for UnscaledFont font descriptor"; + return nullptr; + } +} + +#ifdef XP_DARWIN +already_AddRefed<ScaledFont> Factory::CreateScaledFontForMacFont( + CGFontRef aCGFont, const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize, + bool aUseFontSmoothing, bool aApplySyntheticBold, bool aHasColorGlyphs) { + return MakeAndAddRef<ScaledFontMac>(aCGFont, aUnscaledFont, aSize, false, + aUseFontSmoothing, aApplySyntheticBold, + aHasColorGlyphs); +} +#endif + +#ifdef MOZ_WIDGET_GTK +already_AddRefed<ScaledFont> Factory::CreateScaledFontForFontconfigFont( + const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize, + RefPtr<SharedFTFace> aFace, FcPattern* aPattern) { + return MakeAndAddRef<ScaledFontFontconfig>(std::move(aFace), aPattern, + aUnscaledFont, aSize); +} +#endif + +#ifdef MOZ_WIDGET_ANDROID +already_AddRefed<ScaledFont> Factory::CreateScaledFontForFreeTypeFont( + const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize, + RefPtr<SharedFTFace> aFace, bool aApplySyntheticBold) { + return MakeAndAddRef<ScaledFontFreeType>(std::move(aFace), aUnscaledFont, + aSize, aApplySyntheticBold); +} +#endif + +void Factory::SetBGRSubpixelOrder(bool aBGR) { mBGRSubpixelOrder = aBGR; } + +bool Factory::GetBGRSubpixelOrder() { return mBGRSubpixelOrder; } + +#ifdef MOZ_ENABLE_FREETYPE +SharedFTFace::SharedFTFace(FT_Face aFace, SharedFTFaceData* aData) + : mFace(aFace), + mData(aData), + mLock("SharedFTFace::mLock"), + mLastLockOwner(nullptr) { + if (mData) { + mData->BindData(); + } +} + +SharedFTFace::~SharedFTFace() { + Factory::ReleaseFTFace(mFace); + if (mData) { + mData->ReleaseData(); + } +} + +void Factory::SetFTLibrary(FT_Library aFTLibrary) { mFTLibrary = aFTLibrary; } + +FT_Library Factory::GetFTLibrary() { + MOZ_ASSERT(mFTLibrary); + return mFTLibrary; +} + +FT_Library Factory::NewFTLibrary() { + FT_Library library; + if (FT_Init_FreeType(&library) != FT_Err_Ok) { + return nullptr; + } + return library; +} + +void Factory::ReleaseFTLibrary(FT_Library aFTLibrary) { + FT_Done_FreeType(aFTLibrary); +} + +void Factory::LockFTLibrary(FT_Library aFTLibrary) + MOZ_CAPABILITY_ACQUIRE(mFTLock) MOZ_NO_THREAD_SAFETY_ANALYSIS { + mFTLock.Lock(); +} + +void Factory::UnlockFTLibrary(FT_Library aFTLibrary) + MOZ_CAPABILITY_RELEASE(mFTLock) MOZ_NO_THREAD_SAFETY_ANALYSIS { + mFTLock.Unlock(); +} + +FT_Face Factory::NewFTFace(FT_Library aFTLibrary, const char* aFileName, + int aFaceIndex) { + StaticMutexAutoLock lock(mFTLock); + if (!aFTLibrary) { + aFTLibrary = mFTLibrary; + } + FT_Face face; + if (FT_New_Face(aFTLibrary, aFileName, aFaceIndex, &face) != FT_Err_Ok) { + return nullptr; + } + return face; +} + +already_AddRefed<SharedFTFace> Factory::NewSharedFTFace(FT_Library aFTLibrary, + const char* aFilename, + int aFaceIndex) { + FT_Face face = NewFTFace(aFTLibrary, aFilename, aFaceIndex); + if (!face) { + return nullptr; + } + + RefPtr<FTUserFontData> data; +# ifdef ANDROID + // If the font has variations, we may later need to "clone" it in + // UnscaledFontFreeType::CreateScaledFont. To support this, we attach an + // FTUserFontData that records the filename used to instantiate the face. + if (face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) { + data = new FTUserFontData(aFilename); + } +# endif + return MakeAndAddRef<SharedFTFace>(face, data); +} + +FT_Face Factory::NewFTFaceFromData(FT_Library aFTLibrary, const uint8_t* aData, + size_t aDataSize, int aFaceIndex) { + StaticMutexAutoLock lock(mFTLock); + if (!aFTLibrary) { + aFTLibrary = mFTLibrary; + } + FT_Face face; + if (FT_New_Memory_Face(aFTLibrary, aData, aDataSize, aFaceIndex, &face) != + FT_Err_Ok) { + return nullptr; + } + return face; +} + +already_AddRefed<SharedFTFace> Factory::NewSharedFTFaceFromData( + FT_Library aFTLibrary, const uint8_t* aData, size_t aDataSize, + int aFaceIndex, SharedFTFaceData* aSharedData) { + if (FT_Face face = + NewFTFaceFromData(aFTLibrary, aData, aDataSize, aFaceIndex)) { + return MakeAndAddRef<SharedFTFace>(face, aSharedData); + } else { + return nullptr; + } +} + +void Factory::ReleaseFTFace(FT_Face aFace) { + StaticMutexAutoLock lock(mFTLock); + FT_Done_Face(aFace); +} + +FT_Error Factory::LoadFTGlyph(FT_Face aFace, uint32_t aGlyphIndex, + int32_t aFlags) { + StaticMutexAutoLock lock(mFTLock); + return FT_Load_Glyph(aFace, aGlyphIndex, aFlags); +} +#endif + +AutoSerializeWithMoz2D::AutoSerializeWithMoz2D(BackendType aBackendType) { +#ifdef WIN32 + // We use a multi-threaded ID2D1Factory1, so that makes the calls through the + // Direct2D API thread-safe. However, if the Moz2D objects are using Direct3D + // resources we need to make sure that calls through the Direct3D or DXGI API + // use the Direct2D synchronization. It's possible that this should be pushed + // down into the TextureD3D11 objects, so that we always use this. + if (aBackendType == BackendType::DIRECT2D1_1 || + aBackendType == BackendType::DIRECT2D) { + auto factory = D2DFactory(); + if (factory) { + factory->QueryInterface( + static_cast<ID2D1Multithread**>(getter_AddRefs(mMT))); + if (mMT) { + mMT->Enter(); + } + } + } +#endif +} + +AutoSerializeWithMoz2D::~AutoSerializeWithMoz2D() { +#ifdef WIN32 + if (mMT) { + mMT->Leave(); + } +#endif +}; + +#ifdef WIN32 +already_AddRefed<DrawTarget> Factory::CreateDrawTargetForD3D11Texture( + ID3D11Texture2D* aTexture, SurfaceFormat aFormat) { + MOZ_ASSERT(aTexture); + + RefPtr<DrawTargetD2D1> newTarget; + + newTarget = new DrawTargetD2D1(); + if (newTarget->Init(aTexture, aFormat)) { + RefPtr<DrawTarget> retVal = newTarget; + return retVal.forget(); + } + + gfxWarning() << "Failed to create draw target for D3D11 texture."; + + // Failed + return nullptr; +} + +bool Factory::SetDirect3D11Device(ID3D11Device* aDevice) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + // D2DFactory already takes the device lock, so we get the factory before + // entering the lock scope. + RefPtr<ID2D1Factory1> factory = D2DFactory(); + + StaticMutexAutoLock lock(mDeviceLock); + + mD3D11Device = aDevice; + + if (mD2D1Device) { + mD2D1Device = nullptr; + mMTDC = nullptr; + mOffMTDC = nullptr; + } + + if (!aDevice) { + return true; + } + + RefPtr<IDXGIDevice> device; + aDevice->QueryInterface((IDXGIDevice**)getter_AddRefs(device)); + + RefPtr<ID2D1Device> d2dDevice; + HRESULT hr = factory->CreateDevice(device, getter_AddRefs(d2dDevice)); + if (FAILED(hr)) { + gfxCriticalError() + << "[D2D1] Failed to create gfx factory's D2D1 device, code: " + << hexa(hr); + + mD3D11Device = nullptr; + return false; + } + + mDeviceSeq++; + mD2D1Device = d2dDevice; + return true; +} + +RefPtr<ID3D11Device> Factory::GetDirect3D11Device() { + StaticMutexAutoLock lock(mDeviceLock); + return mD3D11Device; +} + +RefPtr<ID2D1Device> Factory::GetD2D1Device(uint32_t* aOutSeqNo) { + StaticMutexAutoLock lock(mDeviceLock); + if (aOutSeqNo) { + *aOutSeqNo = mDeviceSeq; + } + return mD2D1Device.get(); +} + +bool Factory::HasD2D1Device() { return !!GetD2D1Device(); } + +RefPtr<IDWriteFactory> Factory::GetDWriteFactory() { + StaticMutexAutoLock lock(mDeviceLock); + return mDWriteFactory; +} + +RefPtr<IDWriteFactory> Factory::EnsureDWriteFactory() { + StaticMutexAutoLock lock(mDeviceLock); + + if (mDWriteFactoryInitialized) { + return mDWriteFactory; + } + + mDWriteFactoryInitialized = true; + + HMODULE dwriteModule = LoadLibrarySystem32(L"dwrite.dll"); + decltype(DWriteCreateFactory)* createDWriteFactory = + (decltype(DWriteCreateFactory)*)GetProcAddress(dwriteModule, + "DWriteCreateFactory"); + + if (!createDWriteFactory) { + gfxWarning() << "Failed to locate DWriteCreateFactory function."; + return nullptr; + } + + HRESULT hr = + createDWriteFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), + reinterpret_cast<IUnknown**>(&mDWriteFactory)); + + if (FAILED(hr)) { + gfxWarning() << "Failed to create DWrite Factory."; + } + + return mDWriteFactory; +} + +RefPtr<IDWriteFontCollection> Factory::GetDWriteSystemFonts(bool aUpdate) { + StaticMutexAutoLock lock(mDeviceLock); + + if (mDWriteSystemFonts && !aUpdate) { + return mDWriteSystemFonts; + } + + if (!mDWriteFactory) { + if ((rand() & 0x3f) == 0) { + gfxCriticalError(int(gfx::LogOptions::AssertOnCall)) + << "Failed to create DWrite factory"; + } else { + gfxWarning() << "Failed to create DWrite factory"; + } + + return nullptr; + } + + RefPtr<IDWriteFontCollection> systemFonts; + HRESULT hr = + mDWriteFactory->GetSystemFontCollection(getter_AddRefs(systemFonts)); + if (FAILED(hr) || !systemFonts) { + // only crash some of the time so those experiencing this problem + // don't stop using Firefox + if ((rand() & 0x3f) == 0) { + gfxCriticalError(int(gfx::LogOptions::AssertOnCall)) + << "Failed to create DWrite system font collection"; + } else { + gfxWarning() << "Failed to create DWrite system font collection"; + } + return nullptr; + } + mDWriteSystemFonts = systemFonts; + + return mDWriteSystemFonts; +} + +RefPtr<ID2D1DeviceContext> Factory::GetD2DDeviceContext() { + StaticRefPtr<ID2D1DeviceContext>* ptr; + + if (NS_IsMainThread()) { + ptr = &mMTDC; + } else { + ptr = &mOffMTDC; + } + + if (*ptr) { + return *ptr; + } + + RefPtr<ID2D1Device> device = GetD2D1Device(); + + if (!device) { + return nullptr; + } + + RefPtr<ID2D1DeviceContext> dc; + HRESULT hr = device->CreateDeviceContext( + D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS, + getter_AddRefs(dc)); + + if (FAILED(hr)) { + gfxCriticalError() << "Failed to create global device context"; + return nullptr; + } + + *ptr = dc; + + return *ptr; +} + +bool Factory::SupportsD2D1() { return !!D2DFactory(); } + +BYTE sSystemTextQuality = CLEARTYPE_QUALITY; +void Factory::SetSystemTextQuality(uint8_t aQuality) { + sSystemTextQuality = aQuality; +} + +uint64_t Factory::GetD2DVRAMUsageDrawTarget() { + return DrawTargetD2D1::mVRAMUsageDT; +} + +uint64_t Factory::GetD2DVRAMUsageSourceSurface() { + return DrawTargetD2D1::mVRAMUsageSS; +} + +void Factory::D2DCleanup() { + StaticMutexAutoLock lock(mDeviceLock); + if (mD2D1Device) { + mD2D1Device = nullptr; + } + DrawTargetD2D1::CleanupD2D(); +} + +already_AddRefed<ScaledFont> Factory::CreateScaledFontForDWriteFont( + IDWriteFontFace* aFontFace, const gfxFontStyle* aStyle, + const RefPtr<UnscaledFont>& aUnscaledFont, float aSize, + bool aUseEmbeddedBitmap, bool aUseMultistrikeBold, bool aGDIForced) { + return MakeAndAddRef<ScaledFontDWrite>( + aFontFace, aUnscaledFont, aSize, aUseEmbeddedBitmap, aUseMultistrikeBold, + aGDIForced, aStyle); +} + +already_AddRefed<ScaledFont> Factory::CreateScaledFontForGDIFont( + const void* aLogFont, const RefPtr<UnscaledFont>& aUnscaledFont, + Float aSize) { + return MakeAndAddRef<ScaledFontWin>(static_cast<const LOGFONT*>(aLogFont), + aUnscaledFont, aSize); +} +#endif // WIN32 + +already_AddRefed<DrawTarget> Factory::CreateDrawTargetWithSkCanvas( + SkCanvas* aCanvas) { + RefPtr<DrawTargetSkia> newTarget = new DrawTargetSkia(); + if (!newTarget->Init(aCanvas)) { + return nullptr; + } + return newTarget.forget(); +} + +void Factory::PurgeAllCaches() {} + +already_AddRefed<DrawTarget> Factory::CreateDrawTargetForCairoSurface( + cairo_surface_t* aSurface, const IntSize& aSize, SurfaceFormat* aFormat) { + if (!AllowedSurfaceSize(aSize)) { + gfxWarning() << "Allowing surface with invalid size (Cairo) " << aSize; + } + + RefPtr<DrawTarget> retVal; + +#ifdef USE_CAIRO + RefPtr<DrawTargetCairo> newTarget = new DrawTargetCairo(); + + if (newTarget->Init(aSurface, aSize, aFormat)) { + retVal = newTarget; + } +#endif + return retVal.forget(); +} + +already_AddRefed<SourceSurface> Factory::CreateSourceSurfaceForCairoSurface( + cairo_surface_t* aSurface, const IntSize& aSize, SurfaceFormat aFormat) { + if (aSize.width <= 0 || aSize.height <= 0) { + gfxWarning() << "Can't create a SourceSurface without a valid size"; + return nullptr; + } + +#ifdef USE_CAIRO + return MakeAndAddRef<SourceSurfaceCairo>(aSurface, aSize, aFormat); +#else + return nullptr; +#endif +} + +already_AddRefed<DataSourceSurface> Factory::CreateWrappingDataSourceSurface( + uint8_t* aData, int32_t aStride, const IntSize& aSize, + SurfaceFormat aFormat, + SourceSurfaceDeallocator aDeallocator /* = nullptr */, + void* aClosure /* = nullptr */) { + // Just check for negative/zero size instead of the full AllowedSurfaceSize() + // - since the data is already allocated we do not need to check for a + // possible overflow - it already worked. + if (aSize.width <= 0 || aSize.height <= 0) { + return nullptr; + } + if (!aDeallocator && aClosure) { + return nullptr; + } + + MOZ_ASSERT(aData); + + RefPtr<SourceSurfaceRawData> newSurf = new SourceSurfaceRawData(); + newSurf->InitWrappingData(aData, aSize, aStride, aFormat, aDeallocator, + aClosure); + + return newSurf.forget(); +} + +already_AddRefed<DataSourceSurface> Factory::CreateDataSourceSurface( + const IntSize& aSize, SurfaceFormat aFormat, bool aZero) { + if (!AllowedSurfaceSize(aSize)) { + gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) + << "Failed to allocate a surface due to invalid size (DSS) " << aSize; + return nullptr; + } + + // Skia doesn't support RGBX, so memset RGBX to 0xFF + bool clearSurface = aZero || aFormat == SurfaceFormat::B8G8R8X8; + uint8_t clearValue = aFormat == SurfaceFormat::B8G8R8X8 ? 0xFF : 0; + + RefPtr<SourceSurfaceAlignedRawData> newSurf = + new SourceSurfaceAlignedRawData(); + if (newSurf->Init(aSize, aFormat, clearSurface, clearValue)) { + return newSurf.forget(); + } + + gfxWarning() << "CreateDataSourceSurface failed in init"; + return nullptr; +} + +already_AddRefed<DataSourceSurface> Factory::CreateDataSourceSurfaceWithStride( + const IntSize& aSize, SurfaceFormat aFormat, int32_t aStride, bool aZero) { + if (!AllowedSurfaceSize(aSize) || + aStride < aSize.width * BytesPerPixel(aFormat)) { + gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) + << "CreateDataSourceSurfaceWithStride failed with bad stride " + << aStride << ", " << aSize << ", " << aFormat; + return nullptr; + } + + // Skia doesn't support RGBX, so memset RGBX to 0xFF + bool clearSurface = aZero || aFormat == SurfaceFormat::B8G8R8X8; + uint8_t clearValue = aFormat == SurfaceFormat::B8G8R8X8 ? 0xFF : 0; + + RefPtr<SourceSurfaceAlignedRawData> newSurf = + new SourceSurfaceAlignedRawData(); + if (newSurf->Init(aSize, aFormat, clearSurface, clearValue, aStride)) { + return newSurf.forget(); + } + + gfxCriticalError(LoggerOptionsBasedOnSize(aSize)) + << "CreateDataSourceSurfaceWithStride failed to initialize " << aSize + << ", " << aFormat << ", " << aStride << ", " << aZero; + return nullptr; +} + +void Factory::CopyDataSourceSurface(DataSourceSurface* aSource, + DataSourceSurface* aDest) { + // Don't worry too much about speed. + MOZ_ASSERT(aSource->GetSize() == aDest->GetSize()); + MOZ_ASSERT(aSource->GetFormat() == SurfaceFormat::R8G8B8A8 || + aSource->GetFormat() == SurfaceFormat::R8G8B8X8 || + aSource->GetFormat() == SurfaceFormat::B8G8R8A8 || + aSource->GetFormat() == SurfaceFormat::B8G8R8X8); + MOZ_ASSERT(aDest->GetFormat() == SurfaceFormat::R8G8B8A8 || + aDest->GetFormat() == SurfaceFormat::R8G8B8X8 || + aDest->GetFormat() == SurfaceFormat::B8G8R8A8 || + aDest->GetFormat() == SurfaceFormat::B8G8R8X8 || + aDest->GetFormat() == SurfaceFormat::R5G6B5_UINT16); + + DataSourceSurface::MappedSurface srcMap; + DataSourceSurface::MappedSurface destMap; + if (!aSource->Map(DataSourceSurface::MapType::READ, &srcMap) || + !aDest->Map(DataSourceSurface::MapType::WRITE, &destMap)) { + MOZ_ASSERT(false, "CopyDataSourceSurface: Failed to map surface."); + return; + } + + SwizzleData(srcMap.mData, srcMap.mStride, aSource->GetFormat(), destMap.mData, + destMap.mStride, aDest->GetFormat(), aSource->GetSize()); + + aSource->Unmap(); + aDest->Unmap(); +} + +#ifdef WIN32 + +/* static */ +already_AddRefed<DataSourceSurface> +Factory::CreateBGRA8DataSourceSurfaceForD3D11Texture( + ID3D11Texture2D* aSrcTexture, uint32_t aArrayIndex) { + D3D11_TEXTURE2D_DESC srcDesc = {0}; + aSrcTexture->GetDesc(&srcDesc); + + RefPtr<gfx::DataSourceSurface> destTexture = + gfx::Factory::CreateDataSourceSurface( + IntSize(srcDesc.Width, srcDesc.Height), gfx::SurfaceFormat::B8G8R8A8); + if (NS_WARN_IF(!destTexture)) { + return nullptr; + } + if (!ReadbackTexture(destTexture, aSrcTexture, aArrayIndex)) { + return nullptr; + } + return destTexture.forget(); +} + +/* static */ nsresult Factory::CreateSdbForD3D11Texture( + ID3D11Texture2D* aSrcTexture, const IntSize& aSrcSize, + layers::SurfaceDescriptorBuffer& aSdBuffer, + const std::function<layers::MemoryOrShmem(uint32_t)>& aAllocate) { + D3D11_TEXTURE2D_DESC srcDesc = {0}; + aSrcTexture->GetDesc(&srcDesc); + if (srcDesc.Width != uint32_t(aSrcSize.width) || + srcDesc.Height != uint32_t(aSrcSize.height) || + srcDesc.Format != DXGI_FORMAT_B8G8R8A8_UNORM) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + const auto format = gfx::SurfaceFormat::B8G8R8A8; + uint8_t* buffer = nullptr; + int32_t stride = 0; + nsresult rv = layers::Image::AllocateSurfaceDescriptorBufferRgb( + aSrcSize, format, buffer, aSdBuffer, stride, aAllocate); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!ReadbackTexture(buffer, stride, aSrcTexture)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +/* static */ +template <typename DestTextureT> +bool Factory::ConvertSourceAndRetryReadback(DestTextureT* aDestCpuTexture, + ID3D11Texture2D* aSrcTexture, + uint32_t aArrayIndex) { + RefPtr<ID3D11Device> device; + aSrcTexture->GetDevice(getter_AddRefs(device)); + if (!device) { + gfxWarning() << "Failed to get D3D11 device from source texture"; + return false; + } + + nsAutoCString error; + std::unique_ptr<DXVA2Manager> manager( + DXVA2Manager::CreateD3D11DXVA(nullptr, error, device)); + if (!manager) { + gfxWarning() << "Failed to create DXVA2 manager!"; + return false; + } + + RefPtr<ID3D11Texture2D> newSrcTexture; + HRESULT hr = manager->CopyToBGRATexture(aSrcTexture, aArrayIndex, + getter_AddRefs(newSrcTexture)); + if (FAILED(hr)) { + gfxWarning() << "Failed to copy to BGRA texture."; + return false; + } + + return ReadbackTexture(aDestCpuTexture, newSrcTexture); +} + +/* static */ +bool Factory::ReadbackTexture(layers::TextureData* aDestCpuTexture, + ID3D11Texture2D* aSrcTexture) { + layers::MappedTextureData mappedData; + if (!aDestCpuTexture->BorrowMappedData(mappedData)) { + gfxWarning() << "Could not access in-memory texture"; + return false; + } + + D3D11_TEXTURE2D_DESC srcDesc = {0}; + aSrcTexture->GetDesc(&srcDesc); + + // Special case: If the source and destination have different formats and the + // destination is B8G8R8A8 then convert the source to B8G8R8A8 and readback. + if ((srcDesc.Format != DXGIFormat(mappedData.format)) && + (mappedData.format == SurfaceFormat::B8G8R8A8)) { + return ConvertSourceAndRetryReadback(aDestCpuTexture, aSrcTexture); + } + + if ((IntSize(srcDesc.Width, srcDesc.Height) != mappedData.size) || + (srcDesc.Format != DXGIFormat(mappedData.format))) { + gfxWarning() << "Attempted readback between incompatible textures"; + return false; + } + + return ReadbackTexture(mappedData.data, mappedData.stride, aSrcTexture); +} + +/* static */ +bool Factory::ReadbackTexture(DataSourceSurface* aDestCpuTexture, + ID3D11Texture2D* aSrcTexture, + uint32_t aArrayIndex) { + D3D11_TEXTURE2D_DESC srcDesc = {0}; + aSrcTexture->GetDesc(&srcDesc); + + // Special case: If the source and destination have different formats and the + // destination is B8G8R8A8 then convert the source to B8G8R8A8 and readback. + if ((srcDesc.Format != DXGIFormat(aDestCpuTexture->GetFormat())) && + (aDestCpuTexture->GetFormat() == SurfaceFormat::B8G8R8A8)) { + return ConvertSourceAndRetryReadback(aDestCpuTexture, aSrcTexture, + aArrayIndex); + } + + if ((IntSize(srcDesc.Width, srcDesc.Height) != aDestCpuTexture->GetSize()) || + (srcDesc.Format != DXGIFormat(aDestCpuTexture->GetFormat()))) { + gfxWarning() << "Attempted readback between incompatible textures"; + return false; + } + + gfx::DataSourceSurface::MappedSurface mappedSurface; + if (!aDestCpuTexture->Map(gfx::DataSourceSurface::WRITE, &mappedSurface)) { + return false; + } + + MOZ_ASSERT(aArrayIndex == 0); + + bool ret = + ReadbackTexture(mappedSurface.mData, mappedSurface.mStride, aSrcTexture); + aDestCpuTexture->Unmap(); + return ret; +} + +/* static */ +bool Factory::ReadbackTexture(uint8_t* aDestData, int32_t aDestStride, + ID3D11Texture2D* aSrcTexture) { + MOZ_ASSERT(aDestData && aDestStride && aSrcTexture); + + RefPtr<ID3D11Device> device; + aSrcTexture->GetDevice(getter_AddRefs(device)); + if (!device) { + gfxWarning() << "Failed to get D3D11 device from source texture"; + return false; + } + + RefPtr<ID3D11DeviceContext> context; + device->GetImmediateContext(getter_AddRefs(context)); + if (!context) { + gfxWarning() << "Could not get an immediate D3D11 context"; + return false; + } + + RefPtr<IDXGIKeyedMutex> mutex; + HRESULT hr = aSrcTexture->QueryInterface(__uuidof(IDXGIKeyedMutex), + (void**)getter_AddRefs(mutex)); + if (SUCCEEDED(hr) && mutex) { + hr = mutex->AcquireSync(0, 2000); + if (hr != S_OK) { + gfxWarning() << "Could not acquire DXGI surface lock in 2 seconds"; + return false; + } + } + + D3D11_TEXTURE2D_DESC srcDesc = {0}; + aSrcTexture->GetDesc(&srcDesc); + srcDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + srcDesc.Usage = D3D11_USAGE_STAGING; + srcDesc.BindFlags = 0; + srcDesc.MiscFlags = 0; + srcDesc.MipLevels = 1; + RefPtr<ID3D11Texture2D> srcCpuTexture; + hr = + device->CreateTexture2D(&srcDesc, nullptr, getter_AddRefs(srcCpuTexture)); + if (FAILED(hr)) { + gfxWarning() << "Could not create source texture for mapping"; + if (mutex) { + mutex->ReleaseSync(0); + } + return false; + } + + context->CopyResource(srcCpuTexture, aSrcTexture); + + if (mutex) { + mutex->ReleaseSync(0); + mutex = nullptr; + } + + D3D11_MAPPED_SUBRESOURCE srcMap; + hr = context->Map(srcCpuTexture, 0, D3D11_MAP_READ, 0, &srcMap); + if (FAILED(hr)) { + gfxWarning() << "Could not map source texture"; + return false; + } + + uint32_t width = srcDesc.Width; + uint32_t height = srcDesc.Height; + int bpp = BytesPerPixel(gfx::ToPixelFormat(srcDesc.Format)); + for (uint32_t y = 0; y < height; y++) { + memcpy(aDestData + aDestStride * y, + (unsigned char*)(srcMap.pData) + srcMap.RowPitch * y, width * bpp); + } + + context->Unmap(srcCpuTexture, 0); + return true; +} + +#endif // WIN32 + +// static +void CriticalLogger::OutputMessage(const std::string& aString, int aLevel, + bool aNoNewline) { + if (Factory::GetLogForwarder()) { + Factory::GetLogForwarder()->Log(aString); + } + + BasicLogger::OutputMessage(aString, aLevel, aNoNewline); +} + +void CriticalLogger::CrashAction(LogReason aReason) { + if (Factory::GetLogForwarder()) { + Factory::GetLogForwarder()->CrashAction(aReason); + } +} + +#ifdef WIN32 +void LogWStr(const wchar_t* aWStr, std::stringstream& aOut) { + int n = + WideCharToMultiByte(CP_ACP, 0, aWStr, -1, nullptr, 0, nullptr, nullptr); + if (n > 1) { + std::vector<char> str(n); + WideCharToMultiByte(CP_ACP, 0, aWStr, -1, str.data(), n, nullptr, nullptr); + aOut << str.data(); + } +} +#endif + +} // namespace mozilla::gfx diff --git a/gfx/2d/FilterNodeD2D1.cpp b/gfx/2d/FilterNodeD2D1.cpp new file mode 100644 index 0000000000..bc9026a8b8 --- /dev/null +++ b/gfx/2d/FilterNodeD2D1.cpp @@ -0,0 +1,1140 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FilterNodeD2D1.h" + +#include "Logging.h" + +#include "SourceSurfaceD2D1.h" +#include "DrawTargetD2D1.h" +#include "ExtendInputEffectD2D1.h" + +namespace mozilla { +namespace gfx { + +D2D1_COLORMATRIX_ALPHA_MODE D2DAlphaMode(uint32_t aMode) { + switch (aMode) { + case ALPHA_MODE_PREMULTIPLIED: + return D2D1_COLORMATRIX_ALPHA_MODE_PREMULTIPLIED; + case ALPHA_MODE_STRAIGHT: + return D2D1_COLORMATRIX_ALPHA_MODE_STRAIGHT; + default: + MOZ_CRASH("GFX: Unknown enum value D2DAlphaMode!"); + } + + return D2D1_COLORMATRIX_ALPHA_MODE_PREMULTIPLIED; +} + +D2D1_2DAFFINETRANSFORM_INTERPOLATION_MODE D2DAffineTransformInterpolationMode( + SamplingFilter aSamplingFilter) { + switch (aSamplingFilter) { + case SamplingFilter::GOOD: + return D2D1_2DAFFINETRANSFORM_INTERPOLATION_MODE_LINEAR; + case SamplingFilter::LINEAR: + return D2D1_2DAFFINETRANSFORM_INTERPOLATION_MODE_LINEAR; + case SamplingFilter::POINT: + return D2D1_2DAFFINETRANSFORM_INTERPOLATION_MODE_NEAREST_NEIGHBOR; + default: + MOZ_CRASH("GFX: Unknown enum value D2DAffineTIM!"); + } + + return D2D1_2DAFFINETRANSFORM_INTERPOLATION_MODE_LINEAR; +} + +D2D1_BLEND_MODE D2DBlendMode(uint32_t aMode) { + switch (aMode) { + case BLEND_MODE_DARKEN: + return D2D1_BLEND_MODE_DARKEN; + case BLEND_MODE_LIGHTEN: + return D2D1_BLEND_MODE_LIGHTEN; + case BLEND_MODE_MULTIPLY: + return D2D1_BLEND_MODE_MULTIPLY; + case BLEND_MODE_SCREEN: + return D2D1_BLEND_MODE_SCREEN; + case BLEND_MODE_OVERLAY: + return D2D1_BLEND_MODE_OVERLAY; + case BLEND_MODE_COLOR_DODGE: + return D2D1_BLEND_MODE_COLOR_DODGE; + case BLEND_MODE_COLOR_BURN: + return D2D1_BLEND_MODE_COLOR_BURN; + case BLEND_MODE_HARD_LIGHT: + return D2D1_BLEND_MODE_HARD_LIGHT; + case BLEND_MODE_SOFT_LIGHT: + return D2D1_BLEND_MODE_SOFT_LIGHT; + case BLEND_MODE_DIFFERENCE: + return D2D1_BLEND_MODE_DIFFERENCE; + case BLEND_MODE_EXCLUSION: + return D2D1_BLEND_MODE_EXCLUSION; + case BLEND_MODE_HUE: + return D2D1_BLEND_MODE_HUE; + case BLEND_MODE_SATURATION: + return D2D1_BLEND_MODE_SATURATION; + case BLEND_MODE_COLOR: + return D2D1_BLEND_MODE_COLOR; + case BLEND_MODE_LUMINOSITY: + return D2D1_BLEND_MODE_LUMINOSITY; + + default: + MOZ_CRASH("GFX: Unknown enum value D2DBlendMode!"); + } + + return D2D1_BLEND_MODE_DARKEN; +} + +D2D1_MORPHOLOGY_MODE D2DMorphologyMode(uint32_t aMode) { + switch (aMode) { + case MORPHOLOGY_OPERATOR_DILATE: + return D2D1_MORPHOLOGY_MODE_DILATE; + case MORPHOLOGY_OPERATOR_ERODE: + return D2D1_MORPHOLOGY_MODE_ERODE; + } + + MOZ_CRASH("GFX: Unknown enum value D2DMorphologyMode!"); + return D2D1_MORPHOLOGY_MODE_DILATE; +} + +D2D1_TURBULENCE_NOISE D2DTurbulenceNoise(uint32_t aMode) { + switch (aMode) { + case TURBULENCE_TYPE_FRACTAL_NOISE: + return D2D1_TURBULENCE_NOISE_FRACTAL_SUM; + case TURBULENCE_TYPE_TURBULENCE: + return D2D1_TURBULENCE_NOISE_TURBULENCE; + } + + MOZ_CRASH("GFX: Unknown enum value D2DTurbulenceNoise!"); + return D2D1_TURBULENCE_NOISE_TURBULENCE; +} + +D2D1_COMPOSITE_MODE D2DFilterCompositionMode(uint32_t aMode) { + switch (aMode) { + case COMPOSITE_OPERATOR_OVER: + return D2D1_COMPOSITE_MODE_SOURCE_OVER; + case COMPOSITE_OPERATOR_IN: + return D2D1_COMPOSITE_MODE_SOURCE_IN; + case COMPOSITE_OPERATOR_OUT: + return D2D1_COMPOSITE_MODE_SOURCE_OUT; + case COMPOSITE_OPERATOR_ATOP: + return D2D1_COMPOSITE_MODE_SOURCE_ATOP; + case COMPOSITE_OPERATOR_XOR: + return D2D1_COMPOSITE_MODE_XOR; + case COMPOSITE_OPERATOR_LIGHTER: + return D2D1_COMPOSITE_MODE_PLUS; + } + + MOZ_CRASH("GFX: Unknown enum value D2DFilterCompositionMode!"); + return D2D1_COMPOSITE_MODE_SOURCE_OVER; +} + +D2D1_CHANNEL_SELECTOR D2DChannelSelector(uint32_t aMode) { + switch (aMode) { + case COLOR_CHANNEL_R: + return D2D1_CHANNEL_SELECTOR_R; + case COLOR_CHANNEL_G: + return D2D1_CHANNEL_SELECTOR_G; + case COLOR_CHANNEL_B: + return D2D1_CHANNEL_SELECTOR_B; + case COLOR_CHANNEL_A: + return D2D1_CHANNEL_SELECTOR_A; + } + + MOZ_CRASH("GFX: Unknown enum value D2DChannelSelector!"); + return D2D1_CHANNEL_SELECTOR_R; +} + +already_AddRefed<ID2D1Image> GetImageForSourceSurface(DrawTarget* aDT, + SourceSurface* aSurface) { + if (aDT->IsTiledDrawTarget()) { + gfxDevCrash(LogReason::FilterNodeD2D1Target) + << "Incompatible draw target type! " << (int)aDT->IsTiledDrawTarget(); + return nullptr; + } + switch (aDT->GetBackendType()) { + case BackendType::DIRECT2D1_1: + return static_cast<DrawTargetD2D1*>(aDT)->GetImageForSurface( + aSurface, ExtendMode::CLAMP); + default: + gfxDevCrash(LogReason::FilterNodeD2D1Backend) + << "Unknown draw target type! " << (int)aDT->GetBackendType(); + return nullptr; + } +} + +uint32_t ConvertValue(FilterType aType, uint32_t aAttribute, uint32_t aValue) { + switch (aType) { + case FilterType::COLOR_MATRIX: + if (aAttribute == ATT_COLOR_MATRIX_ALPHA_MODE) { + aValue = D2DAlphaMode(aValue); + } + break; + case FilterType::TRANSFORM: + if (aAttribute == ATT_TRANSFORM_FILTER) { + aValue = D2DAffineTransformInterpolationMode(SamplingFilter(aValue)); + } + break; + case FilterType::BLEND: + if (aAttribute == ATT_BLEND_BLENDMODE) { + aValue = D2DBlendMode(aValue); + } + break; + case FilterType::MORPHOLOGY: + if (aAttribute == ATT_MORPHOLOGY_OPERATOR) { + aValue = D2DMorphologyMode(aValue); + } + break; + case FilterType::DISPLACEMENT_MAP: + if (aAttribute == ATT_DISPLACEMENT_MAP_X_CHANNEL || + aAttribute == ATT_DISPLACEMENT_MAP_Y_CHANNEL) { + aValue = D2DChannelSelector(aValue); + } + break; + case FilterType::TURBULENCE: + if (aAttribute == ATT_TURBULENCE_TYPE) { + aValue = D2DTurbulenceNoise(aValue); + } + break; + case FilterType::COMPOSITE: + if (aAttribute == ATT_COMPOSITE_OPERATOR) { + aValue = D2DFilterCompositionMode(aValue); + } + break; + default: + break; + } + + return aValue; +} + +void ConvertValue(FilterType aType, uint32_t aAttribute, IntSize& aValue) { + switch (aType) { + case FilterType::MORPHOLOGY: + if (aAttribute == ATT_MORPHOLOGY_RADII) { + aValue.width *= 2; + aValue.width += 1; + aValue.height *= 2; + aValue.height += 1; + } + break; + default: + break; + } +} + +UINT32 +GetD2D1InputForInput(FilterType aType, uint32_t aIndex) { return aIndex; } + +#define CONVERT_PROP(moz2dname, d2dname) \ + case ATT_##moz2dname: \ + return D2D1_##d2dname + +UINT32 +GetD2D1PropForAttribute(FilterType aType, uint32_t aIndex) { + switch (aType) { + case FilterType::COLOR_MATRIX: + switch (aIndex) { + CONVERT_PROP(COLOR_MATRIX_MATRIX, COLORMATRIX_PROP_COLOR_MATRIX); + CONVERT_PROP(COLOR_MATRIX_ALPHA_MODE, COLORMATRIX_PROP_ALPHA_MODE); + } + break; + case FilterType::TRANSFORM: + switch (aIndex) { + CONVERT_PROP(TRANSFORM_MATRIX, 2DAFFINETRANSFORM_PROP_TRANSFORM_MATRIX); + CONVERT_PROP(TRANSFORM_FILTER, + 2DAFFINETRANSFORM_PROP_INTERPOLATION_MODE); + } + case FilterType::BLEND: + switch (aIndex) { CONVERT_PROP(BLEND_BLENDMODE, BLEND_PROP_MODE); } + break; + case FilterType::MORPHOLOGY: + switch (aIndex) { + CONVERT_PROP(MORPHOLOGY_OPERATOR, MORPHOLOGY_PROP_MODE); + } + break; + case FilterType::FLOOD: + switch (aIndex) { CONVERT_PROP(FLOOD_COLOR, FLOOD_PROP_COLOR); } + break; + case FilterType::TILE: + switch (aIndex) { CONVERT_PROP(TILE_SOURCE_RECT, TILE_PROP_RECT); } + break; + case FilterType::TABLE_TRANSFER: + switch (aIndex) { + CONVERT_PROP(TABLE_TRANSFER_DISABLE_R, TABLETRANSFER_PROP_RED_DISABLE); + CONVERT_PROP(TABLE_TRANSFER_DISABLE_G, + TABLETRANSFER_PROP_GREEN_DISABLE); + CONVERT_PROP(TABLE_TRANSFER_DISABLE_B, TABLETRANSFER_PROP_BLUE_DISABLE); + CONVERT_PROP(TABLE_TRANSFER_DISABLE_A, + TABLETRANSFER_PROP_ALPHA_DISABLE); + CONVERT_PROP(TABLE_TRANSFER_TABLE_R, TABLETRANSFER_PROP_RED_TABLE); + CONVERT_PROP(TABLE_TRANSFER_TABLE_G, TABLETRANSFER_PROP_GREEN_TABLE); + CONVERT_PROP(TABLE_TRANSFER_TABLE_B, TABLETRANSFER_PROP_BLUE_TABLE); + CONVERT_PROP(TABLE_TRANSFER_TABLE_A, TABLETRANSFER_PROP_ALPHA_TABLE); + } + break; + case FilterType::DISCRETE_TRANSFER: + switch (aIndex) { + CONVERT_PROP(DISCRETE_TRANSFER_DISABLE_R, + DISCRETETRANSFER_PROP_RED_DISABLE); + CONVERT_PROP(DISCRETE_TRANSFER_DISABLE_G, + DISCRETETRANSFER_PROP_GREEN_DISABLE); + CONVERT_PROP(DISCRETE_TRANSFER_DISABLE_B, + DISCRETETRANSFER_PROP_BLUE_DISABLE); + CONVERT_PROP(DISCRETE_TRANSFER_DISABLE_A, + DISCRETETRANSFER_PROP_ALPHA_DISABLE); + CONVERT_PROP(DISCRETE_TRANSFER_TABLE_R, + DISCRETETRANSFER_PROP_RED_TABLE); + CONVERT_PROP(DISCRETE_TRANSFER_TABLE_G, + DISCRETETRANSFER_PROP_GREEN_TABLE); + CONVERT_PROP(DISCRETE_TRANSFER_TABLE_B, + DISCRETETRANSFER_PROP_BLUE_TABLE); + CONVERT_PROP(DISCRETE_TRANSFER_TABLE_A, + DISCRETETRANSFER_PROP_ALPHA_TABLE); + } + break; + case FilterType::LINEAR_TRANSFER: + switch (aIndex) { + CONVERT_PROP(LINEAR_TRANSFER_DISABLE_R, + LINEARTRANSFER_PROP_RED_DISABLE); + CONVERT_PROP(LINEAR_TRANSFER_DISABLE_G, + LINEARTRANSFER_PROP_GREEN_DISABLE); + CONVERT_PROP(LINEAR_TRANSFER_DISABLE_B, + LINEARTRANSFER_PROP_BLUE_DISABLE); + CONVERT_PROP(LINEAR_TRANSFER_DISABLE_A, + LINEARTRANSFER_PROP_ALPHA_DISABLE); + CONVERT_PROP(LINEAR_TRANSFER_INTERCEPT_R, + LINEARTRANSFER_PROP_RED_Y_INTERCEPT); + CONVERT_PROP(LINEAR_TRANSFER_INTERCEPT_G, + LINEARTRANSFER_PROP_GREEN_Y_INTERCEPT); + CONVERT_PROP(LINEAR_TRANSFER_INTERCEPT_B, + LINEARTRANSFER_PROP_BLUE_Y_INTERCEPT); + CONVERT_PROP(LINEAR_TRANSFER_INTERCEPT_A, + LINEARTRANSFER_PROP_ALPHA_Y_INTERCEPT); + CONVERT_PROP(LINEAR_TRANSFER_SLOPE_R, LINEARTRANSFER_PROP_RED_SLOPE); + CONVERT_PROP(LINEAR_TRANSFER_SLOPE_G, LINEARTRANSFER_PROP_GREEN_SLOPE); + CONVERT_PROP(LINEAR_TRANSFER_SLOPE_B, LINEARTRANSFER_PROP_BLUE_SLOPE); + CONVERT_PROP(LINEAR_TRANSFER_SLOPE_A, LINEARTRANSFER_PROP_ALPHA_SLOPE); + } + break; + case FilterType::GAMMA_TRANSFER: + switch (aIndex) { + CONVERT_PROP(GAMMA_TRANSFER_DISABLE_R, GAMMATRANSFER_PROP_RED_DISABLE); + CONVERT_PROP(GAMMA_TRANSFER_DISABLE_G, + GAMMATRANSFER_PROP_GREEN_DISABLE); + CONVERT_PROP(GAMMA_TRANSFER_DISABLE_B, GAMMATRANSFER_PROP_BLUE_DISABLE); + CONVERT_PROP(GAMMA_TRANSFER_DISABLE_A, + GAMMATRANSFER_PROP_ALPHA_DISABLE); + CONVERT_PROP(GAMMA_TRANSFER_AMPLITUDE_R, + GAMMATRANSFER_PROP_RED_AMPLITUDE); + CONVERT_PROP(GAMMA_TRANSFER_AMPLITUDE_G, + GAMMATRANSFER_PROP_GREEN_AMPLITUDE); + CONVERT_PROP(GAMMA_TRANSFER_AMPLITUDE_B, + GAMMATRANSFER_PROP_BLUE_AMPLITUDE); + CONVERT_PROP(GAMMA_TRANSFER_AMPLITUDE_A, + GAMMATRANSFER_PROP_ALPHA_AMPLITUDE); + CONVERT_PROP(GAMMA_TRANSFER_EXPONENT_R, + GAMMATRANSFER_PROP_RED_EXPONENT); + CONVERT_PROP(GAMMA_TRANSFER_EXPONENT_G, + GAMMATRANSFER_PROP_GREEN_EXPONENT); + CONVERT_PROP(GAMMA_TRANSFER_EXPONENT_B, + GAMMATRANSFER_PROP_BLUE_EXPONENT); + CONVERT_PROP(GAMMA_TRANSFER_EXPONENT_A, + GAMMATRANSFER_PROP_ALPHA_EXPONENT); + CONVERT_PROP(GAMMA_TRANSFER_OFFSET_R, GAMMATRANSFER_PROP_RED_OFFSET); + CONVERT_PROP(GAMMA_TRANSFER_OFFSET_G, GAMMATRANSFER_PROP_GREEN_OFFSET); + CONVERT_PROP(GAMMA_TRANSFER_OFFSET_B, GAMMATRANSFER_PROP_BLUE_OFFSET); + CONVERT_PROP(GAMMA_TRANSFER_OFFSET_A, GAMMATRANSFER_PROP_ALPHA_OFFSET); + } + break; + case FilterType::CONVOLVE_MATRIX: + switch (aIndex) { + CONVERT_PROP(CONVOLVE_MATRIX_BIAS, CONVOLVEMATRIX_PROP_BIAS); + CONVERT_PROP(CONVOLVE_MATRIX_KERNEL_MATRIX, + CONVOLVEMATRIX_PROP_KERNEL_MATRIX); + CONVERT_PROP(CONVOLVE_MATRIX_DIVISOR, CONVOLVEMATRIX_PROP_DIVISOR); + CONVERT_PROP(CONVOLVE_MATRIX_KERNEL_UNIT_LENGTH, + CONVOLVEMATRIX_PROP_KERNEL_UNIT_LENGTH); + CONVERT_PROP(CONVOLVE_MATRIX_PRESERVE_ALPHA, + CONVOLVEMATRIX_PROP_PRESERVE_ALPHA); + } + case FilterType::DISPLACEMENT_MAP: + switch (aIndex) { + CONVERT_PROP(DISPLACEMENT_MAP_SCALE, DISPLACEMENTMAP_PROP_SCALE); + CONVERT_PROP(DISPLACEMENT_MAP_X_CHANNEL, + DISPLACEMENTMAP_PROP_X_CHANNEL_SELECT); + CONVERT_PROP(DISPLACEMENT_MAP_Y_CHANNEL, + DISPLACEMENTMAP_PROP_Y_CHANNEL_SELECT); + } + break; + case FilterType::TURBULENCE: + switch (aIndex) { + CONVERT_PROP(TURBULENCE_BASE_FREQUENCY, TURBULENCE_PROP_BASE_FREQUENCY); + CONVERT_PROP(TURBULENCE_NUM_OCTAVES, TURBULENCE_PROP_NUM_OCTAVES); + CONVERT_PROP(TURBULENCE_SEED, TURBULENCE_PROP_SEED); + CONVERT_PROP(TURBULENCE_STITCHABLE, TURBULENCE_PROP_STITCHABLE); + CONVERT_PROP(TURBULENCE_TYPE, TURBULENCE_PROP_NOISE); + } + break; + case FilterType::ARITHMETIC_COMBINE: + switch (aIndex) { + CONVERT_PROP(ARITHMETIC_COMBINE_COEFFICIENTS, + ARITHMETICCOMPOSITE_PROP_COEFFICIENTS); + } + break; + case FilterType::COMPOSITE: + switch (aIndex) { CONVERT_PROP(COMPOSITE_OPERATOR, COMPOSITE_PROP_MODE); } + break; + case FilterType::GAUSSIAN_BLUR: + switch (aIndex) { + CONVERT_PROP(GAUSSIAN_BLUR_STD_DEVIATION, + GAUSSIANBLUR_PROP_STANDARD_DEVIATION); + } + break; + case FilterType::DIRECTIONAL_BLUR: + switch (aIndex) { + CONVERT_PROP(DIRECTIONAL_BLUR_STD_DEVIATION, + DIRECTIONALBLUR_PROP_STANDARD_DEVIATION); + CONVERT_PROP(DIRECTIONAL_BLUR_DIRECTION, DIRECTIONALBLUR_PROP_ANGLE); + } + break; + case FilterType::POINT_DIFFUSE: + switch (aIndex) { + CONVERT_PROP(POINT_DIFFUSE_DIFFUSE_CONSTANT, + POINTDIFFUSE_PROP_DIFFUSE_CONSTANT); + CONVERT_PROP(POINT_DIFFUSE_POSITION, POINTDIFFUSE_PROP_LIGHT_POSITION); + CONVERT_PROP(POINT_DIFFUSE_COLOR, POINTDIFFUSE_PROP_COLOR); + CONVERT_PROP(POINT_DIFFUSE_SURFACE_SCALE, + POINTDIFFUSE_PROP_SURFACE_SCALE); + CONVERT_PROP(POINT_DIFFUSE_KERNEL_UNIT_LENGTH, + POINTDIFFUSE_PROP_KERNEL_UNIT_LENGTH); + } + break; + case FilterType::SPOT_DIFFUSE: + switch (aIndex) { + CONVERT_PROP(SPOT_DIFFUSE_DIFFUSE_CONSTANT, + SPOTDIFFUSE_PROP_DIFFUSE_CONSTANT); + CONVERT_PROP(SPOT_DIFFUSE_POINTS_AT, SPOTDIFFUSE_PROP_POINTS_AT); + CONVERT_PROP(SPOT_DIFFUSE_FOCUS, SPOTDIFFUSE_PROP_FOCUS); + CONVERT_PROP(SPOT_DIFFUSE_LIMITING_CONE_ANGLE, + SPOTDIFFUSE_PROP_LIMITING_CONE_ANGLE); + CONVERT_PROP(SPOT_DIFFUSE_POSITION, SPOTDIFFUSE_PROP_LIGHT_POSITION); + CONVERT_PROP(SPOT_DIFFUSE_COLOR, SPOTDIFFUSE_PROP_COLOR); + CONVERT_PROP(SPOT_DIFFUSE_SURFACE_SCALE, + SPOTDIFFUSE_PROP_SURFACE_SCALE); + CONVERT_PROP(SPOT_DIFFUSE_KERNEL_UNIT_LENGTH, + SPOTDIFFUSE_PROP_KERNEL_UNIT_LENGTH); + } + break; + case FilterType::DISTANT_DIFFUSE: + switch (aIndex) { + CONVERT_PROP(DISTANT_DIFFUSE_DIFFUSE_CONSTANT, + DISTANTDIFFUSE_PROP_DIFFUSE_CONSTANT); + CONVERT_PROP(DISTANT_DIFFUSE_AZIMUTH, DISTANTDIFFUSE_PROP_AZIMUTH); + CONVERT_PROP(DISTANT_DIFFUSE_ELEVATION, DISTANTDIFFUSE_PROP_ELEVATION); + CONVERT_PROP(DISTANT_DIFFUSE_COLOR, DISTANTDIFFUSE_PROP_COLOR); + CONVERT_PROP(DISTANT_DIFFUSE_SURFACE_SCALE, + DISTANTDIFFUSE_PROP_SURFACE_SCALE); + CONVERT_PROP(DISTANT_DIFFUSE_KERNEL_UNIT_LENGTH, + DISTANTDIFFUSE_PROP_KERNEL_UNIT_LENGTH); + } + break; + case FilterType::POINT_SPECULAR: + switch (aIndex) { + CONVERT_PROP(POINT_SPECULAR_SPECULAR_CONSTANT, + POINTSPECULAR_PROP_SPECULAR_CONSTANT); + CONVERT_PROP(POINT_SPECULAR_SPECULAR_EXPONENT, + POINTSPECULAR_PROP_SPECULAR_EXPONENT); + CONVERT_PROP(POINT_SPECULAR_POSITION, + POINTSPECULAR_PROP_LIGHT_POSITION); + CONVERT_PROP(POINT_SPECULAR_COLOR, POINTSPECULAR_PROP_COLOR); + CONVERT_PROP(POINT_SPECULAR_SURFACE_SCALE, + POINTSPECULAR_PROP_SURFACE_SCALE); + CONVERT_PROP(POINT_SPECULAR_KERNEL_UNIT_LENGTH, + POINTSPECULAR_PROP_KERNEL_UNIT_LENGTH); + } + break; + case FilterType::SPOT_SPECULAR: + switch (aIndex) { + CONVERT_PROP(SPOT_SPECULAR_SPECULAR_CONSTANT, + SPOTSPECULAR_PROP_SPECULAR_CONSTANT); + CONVERT_PROP(SPOT_SPECULAR_SPECULAR_EXPONENT, + SPOTSPECULAR_PROP_SPECULAR_EXPONENT); + CONVERT_PROP(SPOT_SPECULAR_POINTS_AT, SPOTSPECULAR_PROP_POINTS_AT); + CONVERT_PROP(SPOT_SPECULAR_FOCUS, SPOTSPECULAR_PROP_FOCUS); + CONVERT_PROP(SPOT_SPECULAR_LIMITING_CONE_ANGLE, + SPOTSPECULAR_PROP_LIMITING_CONE_ANGLE); + CONVERT_PROP(SPOT_SPECULAR_POSITION, SPOTSPECULAR_PROP_LIGHT_POSITION); + CONVERT_PROP(SPOT_SPECULAR_COLOR, SPOTSPECULAR_PROP_COLOR); + CONVERT_PROP(SPOT_SPECULAR_SURFACE_SCALE, + SPOTSPECULAR_PROP_SURFACE_SCALE); + CONVERT_PROP(SPOT_SPECULAR_KERNEL_UNIT_LENGTH, + SPOTSPECULAR_PROP_KERNEL_UNIT_LENGTH); + } + break; + case FilterType::DISTANT_SPECULAR: + switch (aIndex) { + CONVERT_PROP(DISTANT_SPECULAR_SPECULAR_CONSTANT, + DISTANTSPECULAR_PROP_SPECULAR_CONSTANT); + CONVERT_PROP(DISTANT_SPECULAR_SPECULAR_EXPONENT, + DISTANTSPECULAR_PROP_SPECULAR_EXPONENT); + CONVERT_PROP(DISTANT_SPECULAR_AZIMUTH, DISTANTSPECULAR_PROP_AZIMUTH); + CONVERT_PROP(DISTANT_SPECULAR_ELEVATION, + DISTANTSPECULAR_PROP_ELEVATION); + CONVERT_PROP(DISTANT_SPECULAR_COLOR, DISTANTSPECULAR_PROP_COLOR); + CONVERT_PROP(DISTANT_SPECULAR_SURFACE_SCALE, + DISTANTSPECULAR_PROP_SURFACE_SCALE); + CONVERT_PROP(DISTANT_SPECULAR_KERNEL_UNIT_LENGTH, + DISTANTSPECULAR_PROP_KERNEL_UNIT_LENGTH); + } + break; + case FilterType::CROP: + switch (aIndex) { CONVERT_PROP(CROP_RECT, CROP_PROP_RECT); } + break; + default: + break; + } + + return UINT32_MAX; +} + +bool GetD2D1PropsForIntSize(FilterType aType, uint32_t aIndex, + UINT32* aPropWidth, UINT32* aPropHeight) { + switch (aType) { + case FilterType::MORPHOLOGY: + if (aIndex == ATT_MORPHOLOGY_RADII) { + *aPropWidth = D2D1_MORPHOLOGY_PROP_WIDTH; + *aPropHeight = D2D1_MORPHOLOGY_PROP_HEIGHT; + return true; + } + break; + default: + break; + } + return false; +} + +static inline REFCLSID GetCLDIDForFilterType(FilterType aType) { + switch (aType) { + case FilterType::OPACITY: + case FilterType::COLOR_MATRIX: + return CLSID_D2D1ColorMatrix; + case FilterType::TRANSFORM: + return CLSID_D2D12DAffineTransform; + case FilterType::BLEND: + return CLSID_D2D1Blend; + case FilterType::MORPHOLOGY: + return CLSID_D2D1Morphology; + case FilterType::FLOOD: + return CLSID_D2D1Flood; + case FilterType::TILE: + return CLSID_D2D1Tile; + case FilterType::TABLE_TRANSFER: + return CLSID_D2D1TableTransfer; + case FilterType::LINEAR_TRANSFER: + return CLSID_D2D1LinearTransfer; + case FilterType::DISCRETE_TRANSFER: + return CLSID_D2D1DiscreteTransfer; + case FilterType::GAMMA_TRANSFER: + return CLSID_D2D1GammaTransfer; + case FilterType::DISPLACEMENT_MAP: + return CLSID_D2D1DisplacementMap; + case FilterType::TURBULENCE: + return CLSID_D2D1Turbulence; + case FilterType::ARITHMETIC_COMBINE: + return CLSID_D2D1ArithmeticComposite; + case FilterType::COMPOSITE: + return CLSID_D2D1Composite; + case FilterType::GAUSSIAN_BLUR: + return CLSID_D2D1GaussianBlur; + case FilterType::DIRECTIONAL_BLUR: + return CLSID_D2D1DirectionalBlur; + case FilterType::POINT_DIFFUSE: + return CLSID_D2D1PointDiffuse; + case FilterType::POINT_SPECULAR: + return CLSID_D2D1PointSpecular; + case FilterType::SPOT_DIFFUSE: + return CLSID_D2D1SpotDiffuse; + case FilterType::SPOT_SPECULAR: + return CLSID_D2D1SpotSpecular; + case FilterType::DISTANT_DIFFUSE: + return CLSID_D2D1DistantDiffuse; + case FilterType::DISTANT_SPECULAR: + return CLSID_D2D1DistantSpecular; + case FilterType::CROP: + return CLSID_D2D1Crop; + case FilterType::PREMULTIPLY: + return CLSID_D2D1Premultiply; + case FilterType::UNPREMULTIPLY: + return CLSID_D2D1UnPremultiply; + default: + break; + } + return GUID_NULL; +} + +static bool IsTransferFilterType(FilterType aType) { + switch (aType) { + case FilterType::LINEAR_TRANSFER: + case FilterType::GAMMA_TRANSFER: + case FilterType::TABLE_TRANSFER: + case FilterType::DISCRETE_TRANSFER: + return true; + default: + return false; + } +} + +static bool HasUnboundedOutputRegion(FilterType aType) { + if (IsTransferFilterType(aType)) { + return true; + } + + switch (aType) { + case FilterType::COLOR_MATRIX: + case FilterType::POINT_DIFFUSE: + case FilterType::SPOT_DIFFUSE: + case FilterType::DISTANT_DIFFUSE: + case FilterType::POINT_SPECULAR: + case FilterType::SPOT_SPECULAR: + case FilterType::DISTANT_SPECULAR: + return true; + default: + return false; + } +} + +/* static */ +already_AddRefed<FilterNode> FilterNodeD2D1::Create(ID2D1DeviceContext* aDC, + FilterType aType) { + if (aType == FilterType::CONVOLVE_MATRIX) { + return MakeAndAddRef<FilterNodeConvolveD2D1>(aDC); + } + + RefPtr<ID2D1Effect> effect; + HRESULT hr; + + hr = aDC->CreateEffect(GetCLDIDForFilterType(aType), getter_AddRefs(effect)); + + if (FAILED(hr) || !effect) { + gfxCriticalErrorOnce() << "Failed to create effect for FilterType: " + << hexa(hr); + return nullptr; + } + + if (aType == FilterType::ARITHMETIC_COMBINE) { + effect->SetValue(D2D1_ARITHMETICCOMPOSITE_PROP_CLAMP_OUTPUT, TRUE); + } + + if (aType == FilterType::OPACITY) { + return MakeAndAddRef<FilterNodeOpacityD2D1>(effect, aType); + } + + RefPtr<FilterNodeD2D1> filter = new FilterNodeD2D1(effect, aType); + + if (HasUnboundedOutputRegion(aType)) { + // These filters can produce non-transparent output from transparent + // input pixels, and we want them to have an unbounded output region. + filter = new FilterNodeExtendInputAdapterD2D1(aDC, filter, aType); + } + + if (IsTransferFilterType(aType)) { + // Component transfer filters should appear to apply on unpremultiplied + // colors, but the D2D1 effects apply on premultiplied colors. + filter = new FilterNodePremultiplyAdapterD2D1(aDC, filter, aType); + } + + return filter.forget(); +} + +void FilterNodeD2D1::InitUnmappedProperties() { + switch (mType) { + case FilterType::COLOR_MATRIX: + mEffect->SetValue(D2D1_COLORMATRIX_PROP_CLAMP_OUTPUT, TRUE); + break; + case FilterType::TRANSFORM: + mEffect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_BORDER_MODE, + D2D1_BORDER_MODE_HARD); + break; + default: + break; + } +} + +void FilterNodeD2D1::SetInput(uint32_t aIndex, SourceSurface* aSurface) { + UINT32 input = GetD2D1InputForInput(mType, aIndex); + ID2D1Effect* effect = InputEffect(); + + if (mType == FilterType::COMPOSITE) { + UINT32 inputCount = effect->GetInputCount(); + + if (aIndex == inputCount - 1 && aSurface == nullptr) { + effect->SetInputCount(inputCount - 1); + } else if (aIndex >= inputCount && aSurface) { + effect->SetInputCount(aIndex + 1); + } + } + + auto inputCount = effect->GetInputCount(); + MOZ_RELEASE_ASSERT(input < inputCount); + + mInputSurfaces.resize(inputCount); + mInputFilters.resize(inputCount); + + // In order to convert aSurface into an ID2D1Image, we need to know what + // DrawTarget we paint into. However, the same FilterNode object can be + // used on different DrawTargets, so we need to hold on to the SourceSurface + // objects and delay the conversion until we're actually painted and know + // our target DrawTarget. + // The conversion happens in WillDraw(). + + mInputSurfaces[input] = aSurface; + mInputFilters[input] = nullptr; + + // Clear the existing image from the effect. + effect->SetInput(input, nullptr); +} + +void FilterNodeD2D1::SetInput(uint32_t aIndex, FilterNode* aFilter) { + UINT32 input = GetD2D1InputForInput(mType, aIndex); + ID2D1Effect* effect = InputEffect(); + + if (mType == FilterType::COMPOSITE) { + UINT32 inputCount = effect->GetInputCount(); + + if (aIndex == inputCount - 1 && aFilter == nullptr) { + effect->SetInputCount(inputCount - 1); + } else if (aIndex >= inputCount && aFilter) { + effect->SetInputCount(aIndex + 1); + } + } + + auto inputCount = effect->GetInputCount(); + MOZ_RELEASE_ASSERT(input < inputCount); + + if (aFilter && aFilter->GetBackendType() != FILTER_BACKEND_DIRECT2D1_1) { + gfxWarning() << "Unknown input FilterNode set on effect."; + MOZ_ASSERT(0); + return; + } + + FilterNodeD2D1* filter = static_cast<FilterNodeD2D1*>(aFilter); + + mInputSurfaces.resize(inputCount); + mInputFilters.resize(inputCount); + + // We hold on to the FilterNode object so that we can call WillDraw() on it. + mInputSurfaces[input] = nullptr; + mInputFilters[input] = filter; + + if (filter) { + effect->SetInputEffect(input, filter->OutputEffect()); + } +} + +void FilterNodeD2D1::WillDraw(DrawTarget* aDT) { + // Convert input SourceSurfaces into ID2D1Images and set them on the effect. + for (size_t inputIndex = 0; inputIndex < mInputSurfaces.size(); + inputIndex++) { + if (mInputSurfaces[inputIndex]) { + ID2D1Effect* effect = InputEffect(); + RefPtr<ID2D1Image> image = + GetImageForSourceSurface(aDT, mInputSurfaces[inputIndex]); + effect->SetInput(inputIndex, image); + } + } + + // Call WillDraw() on our input filters. + for (std::vector<RefPtr<FilterNodeD2D1>>::iterator it = mInputFilters.begin(); + it != mInputFilters.end(); it++) { + if (*it) { + (*it)->WillDraw(aDT); + } + } +} + +void FilterNodeD2D1::SetAttribute(uint32_t aIndex, uint32_t aValue) { + UINT32 input = GetD2D1PropForAttribute(mType, aIndex); + MOZ_ASSERT(input < mEffect->GetPropertyCount()); + + if (mType == FilterType::TURBULENCE && + aIndex == ATT_TURBULENCE_BASE_FREQUENCY) { + mEffect->SetValue(input, D2D1::Vector2F(FLOAT(aValue), FLOAT(aValue))); + return; + } else if (mType == FilterType::DIRECTIONAL_BLUR && + aIndex == ATT_DIRECTIONAL_BLUR_DIRECTION) { + mEffect->SetValue(input, aValue == BLUR_DIRECTION_X ? 0 : 90.0f); + return; + } + + mEffect->SetValue(input, ConvertValue(mType, aIndex, aValue)); +} + +void FilterNodeD2D1::SetAttribute(uint32_t aIndex, Float aValue) { + UINT32 input = GetD2D1PropForAttribute(mType, aIndex); + MOZ_ASSERT(input < mEffect->GetPropertyCount()); + + mEffect->SetValue(input, aValue); +} + +void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const Point& aValue) { + UINT32 input = GetD2D1PropForAttribute(mType, aIndex); + MOZ_ASSERT(input < mEffect->GetPropertyCount()); + + mEffect->SetValue(input, D2DPoint(aValue)); +} + +void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const Matrix5x4& aValue) { + UINT32 input = GetD2D1PropForAttribute(mType, aIndex); + MOZ_ASSERT(input < mEffect->GetPropertyCount()); + + mEffect->SetValue(input, D2DMatrix5x4(aValue)); +} + +void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const Point3D& aValue) { + UINT32 input = GetD2D1PropForAttribute(mType, aIndex); + MOZ_ASSERT(input < mEffect->GetPropertyCount()); + + mEffect->SetValue(input, D2DVector3D(aValue)); +} + +void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const Size& aValue) { + UINT32 input = GetD2D1PropForAttribute(mType, aIndex); + MOZ_ASSERT(input < mEffect->GetPropertyCount()); + + mEffect->SetValue(input, D2D1::Vector2F(aValue.width, aValue.height)); +} + +void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const IntSize& aValue) { + UINT32 widthProp, heightProp; + + if (!GetD2D1PropsForIntSize(mType, aIndex, &widthProp, &heightProp)) { + return; + } + + IntSize value = aValue; + ConvertValue(mType, aIndex, value); + + mEffect->SetValue(widthProp, (UINT)value.width); + mEffect->SetValue(heightProp, (UINT)value.height); +} + +void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const DeviceColor& aValue) { + UINT32 input = GetD2D1PropForAttribute(mType, aIndex); + MOZ_ASSERT(input < mEffect->GetPropertyCount()); + + switch (mType) { + case FilterType::POINT_DIFFUSE: + case FilterType::SPOT_DIFFUSE: + case FilterType::DISTANT_DIFFUSE: + case FilterType::POINT_SPECULAR: + case FilterType::SPOT_SPECULAR: + case FilterType::DISTANT_SPECULAR: + mEffect->SetValue(input, D2D1::Vector3F(aValue.r, aValue.g, aValue.b)); + break; + default: + mEffect->SetValue(input, + D2D1::Vector4F(aValue.r * aValue.a, aValue.g * aValue.a, + aValue.b * aValue.a, aValue.a)); + } +} + +void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const Rect& aValue) { + UINT32 input = GetD2D1PropForAttribute(mType, aIndex); + MOZ_ASSERT(input < mEffect->GetPropertyCount()); + + mEffect->SetValue(input, D2DRect(aValue)); +} + +void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const IntRect& aValue) { + if (mType == FilterType::TURBULENCE) { + MOZ_ASSERT(aIndex == ATT_TURBULENCE_RECT); + + mEffect->SetValue(D2D1_TURBULENCE_PROP_OFFSET, + D2D1::Vector2F(Float(aValue.X()), Float(aValue.Y()))); + mEffect->SetValue( + D2D1_TURBULENCE_PROP_SIZE, + D2D1::Vector2F(Float(aValue.Width()), Float(aValue.Height()))); + return; + } + + UINT32 input = GetD2D1PropForAttribute(mType, aIndex); + MOZ_ASSERT(input < mEffect->GetPropertyCount()); + + mEffect->SetValue(input, + D2D1::RectF(Float(aValue.X()), Float(aValue.Y()), + Float(aValue.XMost()), Float(aValue.YMost()))); +} + +void FilterNodeD2D1::SetAttribute(uint32_t aIndex, bool aValue) { + UINT32 input = GetD2D1PropForAttribute(mType, aIndex); + MOZ_ASSERT(input < mEffect->GetPropertyCount()); + + mEffect->SetValue(input, (BOOL)aValue); +} + +void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const Float* aValues, + uint32_t aSize) { + UINT32 input = GetD2D1PropForAttribute(mType, aIndex); + MOZ_ASSERT(input < mEffect->GetPropertyCount()); + + mEffect->SetValue(input, (BYTE*)aValues, sizeof(Float) * aSize); +} + +void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const IntPoint& aValue) { + UINT32 input = GetD2D1PropForAttribute(mType, aIndex); + MOZ_ASSERT(input < mEffect->GetPropertyCount()); + + mEffect->SetValue(input, D2DPoint(aValue)); +} + +void FilterNodeD2D1::SetAttribute(uint32_t aIndex, const Matrix& aMatrix) { + UINT32 input = GetD2D1PropForAttribute(mType, aIndex); + MOZ_ASSERT(input < mEffect->GetPropertyCount()); + + mEffect->SetValue(input, D2DMatrix(aMatrix)); +} + +void FilterNodeOpacityD2D1::SetAttribute(uint32_t aIndex, Float aValue) { + D2D1_MATRIX_5X4_F matrix = + D2D1::Matrix5x4F(aValue, 0, 0, 0, 0, aValue, 0, 0, 0, 0, aValue, 0, 0, 0, + 0, aValue, 0, 0, 0, 0); + + mEffect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, matrix); + mEffect->SetValue(D2D1_COLORMATRIX_PROP_ALPHA_MODE, + D2D1_COLORMATRIX_ALPHA_MODE_STRAIGHT); +} + +FilterNodeConvolveD2D1::FilterNodeConvolveD2D1(ID2D1DeviceContext* aDC) + : FilterNodeD2D1(nullptr, FilterType::CONVOLVE_MATRIX), + mEdgeMode(EDGE_MODE_DUPLICATE) { + // Correctly handling the interaction of edge mode and source rect is a bit + // tricky with D2D1 effects. We want the edge mode to only apply outside of + // the source rect (as specified by the ATT_CONVOLVE_MATRIX_SOURCE_RECT + // attribute). So if our input surface or filter is smaller than the source + // rect, we need to add transparency around it until we reach the edges of + // the source rect, and only then do any repeating or edge duplicating. + // Unfortunately, the border effect does not have a source rect attribute - + // it only looks at the output rect of its input filter or surface. So we use + // our custom ExtendInput effect to adjust the output rect of our input. + // All of this is only necessary when our edge mode is not EDGE_MODE_NONE, so + // we update the filter chain dynamically in UpdateChain(). + + HRESULT hr; + + hr = aDC->CreateEffect(CLSID_D2D1ConvolveMatrix, getter_AddRefs(mEffect)); + + if (FAILED(hr) || !mEffect) { + gfxWarning() << "Failed to create ConvolveMatrix filter!"; + return; + } + + mEffect->SetValue(D2D1_CONVOLVEMATRIX_PROP_BORDER_MODE, + D2D1_BORDER_MODE_SOFT); + + hr = aDC->CreateEffect(CLSID_ExtendInputEffect, + getter_AddRefs(mExtendInputEffect)); + + if (FAILED(hr) || !mExtendInputEffect) { + gfxWarning() << "Failed to create ConvolveMatrix filter!"; + return; + } + + hr = aDC->CreateEffect(CLSID_D2D1Border, getter_AddRefs(mBorderEffect)); + + if (FAILED(hr) || !mBorderEffect) { + gfxWarning() << "Failed to create ConvolveMatrix filter!"; + return; + } + + mBorderEffect->SetInputEffect(0, mExtendInputEffect.get()); + + UpdateChain(); + UpdateSourceRect(); +} + +void FilterNodeConvolveD2D1::SetInput(uint32_t aIndex, FilterNode* aFilter) { + FilterNodeD2D1::SetInput(aIndex, aFilter); + + UpdateChain(); +} + +void FilterNodeConvolveD2D1::SetAttribute(uint32_t aIndex, uint32_t aValue) { + if (aIndex != ATT_CONVOLVE_MATRIX_EDGE_MODE) { + return FilterNodeD2D1::SetAttribute(aIndex, aValue); + } + + mEdgeMode = (ConvolveMatrixEdgeMode)aValue; + + UpdateChain(); +} + +ID2D1Effect* FilterNodeConvolveD2D1::InputEffect() { + return mEdgeMode == EDGE_MODE_NONE ? mEffect.get() : mExtendInputEffect.get(); +} + +void FilterNodeConvolveD2D1::UpdateChain() { + // The shape of the filter graph: + // + // EDGE_MODE_NONE: + // input --> convolvematrix + // + // EDGE_MODE_DUPLICATE or EDGE_MODE_WRAP: + // input --> extendinput --> border --> convolvematrix + // + // mEffect is convolvematrix. + + if (mEdgeMode != EDGE_MODE_NONE) { + mEffect->SetInputEffect(0, mBorderEffect.get()); + } + + RefPtr<ID2D1Effect> inputEffect; + if (mInputFilters.size() > 0 && mInputFilters[0]) { + inputEffect = mInputFilters[0]->OutputEffect(); + } + InputEffect()->SetInputEffect(0, inputEffect); + + if (mEdgeMode == EDGE_MODE_DUPLICATE) { + mBorderEffect->SetValue(D2D1_BORDER_PROP_EDGE_MODE_X, + D2D1_BORDER_EDGE_MODE_CLAMP); + mBorderEffect->SetValue(D2D1_BORDER_PROP_EDGE_MODE_Y, + D2D1_BORDER_EDGE_MODE_CLAMP); + } else if (mEdgeMode == EDGE_MODE_WRAP) { + mBorderEffect->SetValue(D2D1_BORDER_PROP_EDGE_MODE_X, + D2D1_BORDER_EDGE_MODE_WRAP); + mBorderEffect->SetValue(D2D1_BORDER_PROP_EDGE_MODE_Y, + D2D1_BORDER_EDGE_MODE_WRAP); + } +} + +void FilterNodeConvolveD2D1::SetAttribute(uint32_t aIndex, + const IntSize& aValue) { + if (aIndex != ATT_CONVOLVE_MATRIX_KERNEL_SIZE) { + MOZ_ASSERT(false); + return; + } + + mKernelSize = aValue; + + mEffect->SetValue(D2D1_CONVOLVEMATRIX_PROP_KERNEL_SIZE_X, aValue.width); + mEffect->SetValue(D2D1_CONVOLVEMATRIX_PROP_KERNEL_SIZE_Y, aValue.height); + + UpdateOffset(); +} + +void FilterNodeConvolveD2D1::SetAttribute(uint32_t aIndex, + const IntPoint& aValue) { + if (aIndex != ATT_CONVOLVE_MATRIX_TARGET) { + MOZ_ASSERT(false); + return; + } + + mTarget = aValue; + + UpdateOffset(); +} + +void FilterNodeConvolveD2D1::SetAttribute(uint32_t aIndex, + const IntRect& aValue) { + if (aIndex != ATT_CONVOLVE_MATRIX_SOURCE_RECT) { + MOZ_ASSERT(false); + return; + } + + mSourceRect = aValue; + + UpdateSourceRect(); +} + +void FilterNodeConvolveD2D1::UpdateOffset() { + D2D1_VECTOR_2F vector = D2D1::Vector2F( + (Float(mKernelSize.width) - 1.0f) / 2.0f - Float(mTarget.x), + (Float(mKernelSize.height) - 1.0f) / 2.0f - Float(mTarget.y)); + + mEffect->SetValue(D2D1_CONVOLVEMATRIX_PROP_KERNEL_OFFSET, vector); +} + +void FilterNodeConvolveD2D1::UpdateSourceRect() { + mExtendInputEffect->SetValue( + EXTENDINPUT_PROP_OUTPUT_RECT, + D2D1::Vector4F(Float(mSourceRect.X()), Float(mSourceRect.Y()), + Float(mSourceRect.XMost()), Float(mSourceRect.YMost()))); +} + +FilterNodeExtendInputAdapterD2D1::FilterNodeExtendInputAdapterD2D1( + ID2D1DeviceContext* aDC, FilterNodeD2D1* aFilterNode, FilterType aType) + : FilterNodeD2D1(aFilterNode->MainEffect(), aType), + mWrappedFilterNode(aFilterNode) { + // We have an mEffect that looks at the bounds of the input effect, and we + // want mEffect to regard its input as unbounded. So we take the input, + // pipe it through an ExtendInput effect (which has an infinite output rect + // by default), and feed the resulting unbounded composition into mEffect. + + HRESULT hr; + + hr = aDC->CreateEffect(CLSID_ExtendInputEffect, + getter_AddRefs(mExtendInputEffect)); + + if (FAILED(hr) || !mExtendInputEffect) { + gfxWarning() << "Failed to create extend input effect for filter: " + << hexa(hr); + return; + } + + aFilterNode->InputEffect()->SetInputEffect(0, mExtendInputEffect.get()); +} + +FilterNodePremultiplyAdapterD2D1::FilterNodePremultiplyAdapterD2D1( + ID2D1DeviceContext* aDC, FilterNodeD2D1* aFilterNode, FilterType aType) + : FilterNodeD2D1(aFilterNode->MainEffect(), aType) { + // D2D1 component transfer effects do strange things when it comes to + // premultiplication. + // For our purposes we only need the transfer filters to apply straight to + // unpremultiplied source channels and output unpremultiplied results. + // However, the D2D1 effects are designed differently: They can apply to both + // premultiplied and unpremultiplied inputs, and they always premultiply + // their result - at least in those color channels that have not been + // disabled. + // In order to determine whether the input needs to be unpremultiplied as + // part of the transfer, the effect consults the alpha mode metadata of the + // input surface or the input effect. We don't have such a concept in Moz2D, + // and giving Moz2D users different results based on something that cannot be + // influenced through Moz2D APIs seems like a bad idea. + // We solve this by applying a premultiply effect to the input before feeding + // it into the transfer effect. The premultiply effect always premultiplies + // regardless of any alpha mode metadata on inputs, and it always marks its + // output as premultiplied so that the transfer effect will unpremultiply + // consistently. Feeding always-premultiplied input into the transfer effect + // also avoids another problem that would appear when individual color + // channels disable the transfer: In that case, the disabled channels would + // pass through unchanged in their unpremultiplied form and the other + // channels would be premultiplied, giving a mixed result. + // But since we now ensure that the input is premultiplied, disabled channels + // will pass premultiplied values through to the result, which is consistent + // with the enabled channels. + // We also add an unpremultiply effect that postprocesses the result of the + // transfer effect because getting unpremultiplied results from the transfer + // filters is part of the FilterNode API. + HRESULT hr; + + hr = aDC->CreateEffect(CLSID_D2D1Premultiply, + getter_AddRefs(mPrePremultiplyEffect)); + + if (FAILED(hr) || !mPrePremultiplyEffect) { + gfxWarning() << "Failed to create ComponentTransfer filter!"; + return; + } + + hr = aDC->CreateEffect(CLSID_D2D1UnPremultiply, + getter_AddRefs(mPostUnpremultiplyEffect)); + + if (FAILED(hr) || !mPostUnpremultiplyEffect) { + gfxWarning() << "Failed to create ComponentTransfer filter!"; + return; + } + + aFilterNode->InputEffect()->SetInputEffect(0, mPrePremultiplyEffect.get()); + mPostUnpremultiplyEffect->SetInputEffect(0, aFilterNode->OutputEffect()); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/FilterNodeD2D1.h b/gfx/2d/FilterNodeD2D1.h new file mode 100644 index 0000000000..a2f6e684f9 --- /dev/null +++ b/gfx/2d/FilterNodeD2D1.h @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_FILTERNODED2D1_H_ +#define MOZILLA_GFX_FILTERNODED2D1_H_ + +#include "2D.h" +#include "Filters.h" +#include <vector> +#include <windows.h> +#include <d2d1_1.h> +#include <cguid.h> + +namespace mozilla { +namespace gfx { + +class FilterNodeD2D1 : public FilterNode { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeD2D1, override) + + static already_AddRefed<FilterNode> Create(ID2D1DeviceContext* aDC, + FilterType aType); + + FilterNodeD2D1(ID2D1Effect* aEffect, FilterType aType) + : mEffect(aEffect), mType(aType) { + InitUnmappedProperties(); + } + + virtual FilterBackend GetBackendType() { return FILTER_BACKEND_DIRECT2D1_1; } + + virtual void SetInput(uint32_t aIndex, SourceSurface* aSurface); + virtual void SetInput(uint32_t aIndex, FilterNode* aFilter); + + virtual void SetAttribute(uint32_t aIndex, uint32_t aValue); + virtual void SetAttribute(uint32_t aIndex, Float aValue); + virtual void SetAttribute(uint32_t aIndex, const Point& aValue); + virtual void SetAttribute(uint32_t aIndex, const Matrix5x4& aValue); + virtual void SetAttribute(uint32_t aIndex, const Point3D& aValue); + virtual void SetAttribute(uint32_t aIndex, const Size& aValue); + virtual void SetAttribute(uint32_t aIndex, const IntSize& aValue); + virtual void SetAttribute(uint32_t aIndex, const DeviceColor& aValue); + virtual void SetAttribute(uint32_t aIndex, const Rect& aValue); + virtual void SetAttribute(uint32_t aIndex, const IntRect& aValue); + virtual void SetAttribute(uint32_t aIndex, bool aValue); + virtual void SetAttribute(uint32_t aIndex, const Float* aValues, + uint32_t aSize); + virtual void SetAttribute(uint32_t aIndex, const IntPoint& aValue); + virtual void SetAttribute(uint32_t aIndex, const Matrix& aValue); + + // Called by DrawTarget before it draws our OutputEffect, and recursively + // by the filter nodes that have this filter as one of their inputs. This + // gives us a chance to convert any input surfaces to the target format for + // the DrawTarget that we will draw to. + virtual void WillDraw(DrawTarget* aDT); + + virtual ID2D1Effect* MainEffect() { return mEffect.get(); } + virtual ID2D1Effect* InputEffect() { return mEffect.get(); } + virtual ID2D1Effect* OutputEffect() { return mEffect.get(); } + + protected: + friend class DrawTargetD2D1; + friend class DrawTargetD2D; + friend class FilterNodeConvolveD2D1; + + void InitUnmappedProperties(); + + RefPtr<ID2D1Effect> mEffect; + std::vector<RefPtr<FilterNodeD2D1>> mInputFilters; + std::vector<RefPtr<SourceSurface>> mInputSurfaces; + FilterType mType; + + private: + using FilterNode::SetAttribute; + using FilterNode::SetInput; +}; + +class FilterNodeConvolveD2D1 : public FilterNodeD2D1 { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeConvolveD2D1, override) + explicit FilterNodeConvolveD2D1(ID2D1DeviceContext* aDC); + + void SetInput(uint32_t aIndex, FilterNode* aFilter) override; + + void SetAttribute(uint32_t aIndex, uint32_t aValue) override; + void SetAttribute(uint32_t aIndex, const IntSize& aValue) override; + void SetAttribute(uint32_t aIndex, const IntPoint& aValue) override; + void SetAttribute(uint32_t aIndex, const IntRect& aValue) override; + + ID2D1Effect* InputEffect() override; + + private: + using FilterNode::SetAttribute; + using FilterNode::SetInput; + + void UpdateChain(); + void UpdateOffset(); + void UpdateSourceRect(); + + RefPtr<ID2D1Effect> mExtendInputEffect; + RefPtr<ID2D1Effect> mBorderEffect; + ConvolveMatrixEdgeMode mEdgeMode; + IntPoint mTarget; + IntSize mKernelSize; + IntRect mSourceRect; +}; + +class FilterNodeOpacityD2D1 : public FilterNodeD2D1 { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeOpacityD2D1, override) + FilterNodeOpacityD2D1(ID2D1Effect* aEffect, FilterType aType) + : FilterNodeD2D1(aEffect, aType) {} + + void SetAttribute(uint32_t aIndex, Float aValue) override; +}; + +class FilterNodeExtendInputAdapterD2D1 : public FilterNodeD2D1 { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeExtendInputAdapterD2D1, + override) + FilterNodeExtendInputAdapterD2D1(ID2D1DeviceContext* aDC, + FilterNodeD2D1* aFilterNode, + FilterType aType); + + ID2D1Effect* InputEffect() override { return mExtendInputEffect.get(); } + ID2D1Effect* OutputEffect() override { + return mWrappedFilterNode->OutputEffect(); + } + + private: + RefPtr<FilterNodeD2D1> mWrappedFilterNode; + RefPtr<ID2D1Effect> mExtendInputEffect; +}; + +class FilterNodePremultiplyAdapterD2D1 : public FilterNodeD2D1 { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodePremultiplyAdapterD2D1, + override) + FilterNodePremultiplyAdapterD2D1(ID2D1DeviceContext* aDC, + FilterNodeD2D1* aFilterNode, + FilterType aType); + + ID2D1Effect* InputEffect() override { return mPrePremultiplyEffect.get(); } + ID2D1Effect* OutputEffect() override { + return mPostUnpremultiplyEffect.get(); + } + + private: + RefPtr<ID2D1Effect> mPrePremultiplyEffect; + RefPtr<ID2D1Effect> mPostUnpremultiplyEffect; +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/FilterNodeSoftware.cpp b/gfx/2d/FilterNodeSoftware.cpp new file mode 100644 index 0000000000..0453d86869 --- /dev/null +++ b/gfx/2d/FilterNodeSoftware.cpp @@ -0,0 +1,3750 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <cmath> +#include "DataSurfaceHelpers.h" +#include "FilterNodeSoftware.h" +#include "2D.h" +#include "Tools.h" +#include "Blur.h" +#include <map> +#include "FilterProcessing.h" +#include "Logging.h" +#include "mozilla/PodOperations.h" +#include "mozilla/DebugOnly.h" + +// #define DEBUG_DUMP_SURFACES + +#ifdef DEBUG_DUMP_SURFACES +# include "gfxUtils.h" // not part of Moz2D +#endif + +namespace mozilla { +namespace gfx { + +namespace { + +/** + * This class provides a way to get a pow() results in constant-time. It works + * by caching 129 ((1 << sCacheIndexPrecisionBits) + 1) values for bases between + * 0 and 1 and a fixed exponent. + **/ +class PowCache { + public: + PowCache() : mNumPowTablePreSquares(-1) {} + + void CacheForExponent(Float aExponent) { + // Since we are in the world where we only care about + // input and results in [0,1], there is no point in + // dealing with non-positive exponents. + if (aExponent <= 0) { + mNumPowTablePreSquares = -1; + return; + } + int numPreSquares = 0; + while (numPreSquares < 5 && aExponent > (1 << (numPreSquares + 2))) { + numPreSquares++; + } + mNumPowTablePreSquares = numPreSquares; + for (size_t i = 0; i < sCacheSize; i++) { + // sCacheSize is chosen in such a way that a takes values + // from 0.0 to 1.0 inclusive. + Float a = i / Float(1 << sCacheIndexPrecisionBits); + MOZ_ASSERT(0.0f <= a && a <= 1.0f, + "We only want to cache for bases between 0 and 1."); + + for (int j = 0; j < mNumPowTablePreSquares; j++) { + a = sqrt(a); + } + uint32_t cachedInt = pow(a, aExponent) * (1 << sOutputIntPrecisionBits); + MOZ_ASSERT(cachedInt < (1 << (sizeof(mPowTable[i]) * 8)), + "mPowCache integer type too small"); + + mPowTable[i] = cachedInt; + } + } + + // Only call Pow() if HasPowerTable() would return true, to avoid complicating + // this code and having it just return (1 << sOutputIntPrecisionBits)) + uint16_t Pow(uint16_t aBase) { + MOZ_ASSERT(HasPowerTable()); + // Results should be similar to what the following code would produce: + // Float x = Float(aBase) / (1 << sInputIntPrecisionBits); + // return uint16_t(pow(x, aExponent) * (1 << sOutputIntPrecisionBits)); + + MOZ_ASSERT(aBase <= (1 << sInputIntPrecisionBits), + "aBase needs to be between 0 and 1!"); + + uint32_t a = aBase; + for (int j = 0; j < mNumPowTablePreSquares; j++) { + a = a * a >> sInputIntPrecisionBits; + } + uint32_t i = a >> (sInputIntPrecisionBits - sCacheIndexPrecisionBits); + MOZ_ASSERT(i < sCacheSize, "out-of-bounds mPowTable access"); + return mPowTable[i]; + } + + static const int sInputIntPrecisionBits = 15; + static const int sOutputIntPrecisionBits = 15; + static const int sCacheIndexPrecisionBits = 7; + + inline bool HasPowerTable() const { return mNumPowTablePreSquares >= 0; } + + private: + static const size_t sCacheSize = (1 << sCacheIndexPrecisionBits) + 1; + + int mNumPowTablePreSquares; + uint16_t mPowTable[sCacheSize]; +}; + +class PointLightSoftware { + public: + bool SetAttribute(uint32_t aIndex, Float) { return false; } + bool SetAttribute(uint32_t aIndex, const Point3D&); + void Prepare() {} + Point3D GetVectorToLight(const Point3D& aTargetPoint); + uint32_t GetColor(uint32_t aLightColor, const Point3D& aVectorToLight); + + private: + Point3D mPosition; +}; + +class SpotLightSoftware { + public: + SpotLightSoftware(); + bool SetAttribute(uint32_t aIndex, Float); + bool SetAttribute(uint32_t aIndex, const Point3D&); + void Prepare(); + Point3D GetVectorToLight(const Point3D& aTargetPoint); + uint32_t GetColor(uint32_t aLightColor, const Point3D& aVectorToLight); + + private: + Point3D mPosition; + Point3D mPointsAt; + Point3D mVectorFromFocusPointToLight; + Float mSpecularFocus; + Float mLimitingConeAngle; + Float mLimitingConeCos; + PowCache mPowCache; +}; + +class DistantLightSoftware { + public: + DistantLightSoftware(); + bool SetAttribute(uint32_t aIndex, Float); + bool SetAttribute(uint32_t aIndex, const Point3D&) { return false; } + void Prepare(); + Point3D GetVectorToLight(const Point3D& aTargetPoint); + uint32_t GetColor(uint32_t aLightColor, const Point3D& aVectorToLight); + + private: + Float mAzimuth; + Float mElevation; + Point3D mVectorToLight; +}; + +class DiffuseLightingSoftware { + public: + DiffuseLightingSoftware(); + bool SetAttribute(uint32_t aIndex, Float); + void Prepare() {} + uint32_t LightPixel(const Point3D& aNormal, const Point3D& aVectorToLight, + uint32_t aColor); + + private: + Float mDiffuseConstant; +}; + +class SpecularLightingSoftware { + public: + SpecularLightingSoftware(); + bool SetAttribute(uint32_t aIndex, Float); + void Prepare(); + uint32_t LightPixel(const Point3D& aNormal, const Point3D& aVectorToLight, + uint32_t aColor); + + private: + Float mSpecularConstant; + Float mSpecularExponent; + uint32_t mSpecularConstantInt; + PowCache mPowCache; +}; + +} // unnamed namespace + +// from xpcom/ds/nsMathUtils.h +static int32_t NS_lround(double x) { + return x >= 0.0 ? int32_t(x + 0.5) : int32_t(x - 0.5); +} + +static already_AddRefed<DataSourceSurface> CloneAligned( + DataSourceSurface* aSource) { + return CreateDataSourceSurfaceByCloning(aSource); +} + +static void FillRectWithPixel(DataSourceSurface* aSurface, + const IntRect& aFillRect, IntPoint aPixelPos) { + MOZ_ASSERT(!aFillRect.Overflows()); + MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aFillRect), + "aFillRect needs to be completely inside the surface"); + MOZ_ASSERT(SurfaceContainsPoint(aSurface, aPixelPos), + "aPixelPos needs to be inside the surface"); + + DataSourceSurface::ScopedMap surfMap(aSurface, DataSourceSurface::READ_WRITE); + if (MOZ2D_WARN_IF(!surfMap.IsMapped())) { + return; + } + uint8_t* sourcePixelData = + DataAtOffset(aSurface, surfMap.GetMappedSurface(), aPixelPos); + uint8_t* data = + DataAtOffset(aSurface, surfMap.GetMappedSurface(), aFillRect.TopLeft()); + int bpp = BytesPerPixel(aSurface->GetFormat()); + + // Fill the first row by hand. + if (bpp == 4) { + uint32_t sourcePixel = *(uint32_t*)sourcePixelData; + for (int32_t x = 0; x < aFillRect.Width(); x++) { + *((uint32_t*)data + x) = sourcePixel; + } + } else if (BytesPerPixel(aSurface->GetFormat()) == 1) { + uint8_t sourcePixel = *sourcePixelData; + memset(data, sourcePixel, aFillRect.Width()); + } + + // Copy the first row into the other rows. + for (int32_t y = 1; y < aFillRect.Height(); y++) { + PodCopy(data + y * surfMap.GetStride(), data, aFillRect.Width() * bpp); + } +} + +static void FillRectWithVerticallyRepeatingHorizontalStrip( + DataSourceSurface* aSurface, const IntRect& aFillRect, + const IntRect& aSampleRect) { + MOZ_ASSERT(!aFillRect.Overflows()); + MOZ_ASSERT(!aSampleRect.Overflows()); + MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aFillRect), + "aFillRect needs to be completely inside the surface"); + MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aSampleRect), + "aSampleRect needs to be completely inside the surface"); + + DataSourceSurface::ScopedMap surfMap(aSurface, DataSourceSurface::READ_WRITE); + if (MOZ2D_WARN_IF(!surfMap.IsMapped())) { + return; + } + + uint8_t* sampleData = + DataAtOffset(aSurface, surfMap.GetMappedSurface(), aSampleRect.TopLeft()); + uint8_t* data = + DataAtOffset(aSurface, surfMap.GetMappedSurface(), aFillRect.TopLeft()); + if (BytesPerPixel(aSurface->GetFormat()) == 4) { + for (int32_t y = 0; y < aFillRect.Height(); y++) { + PodCopy((uint32_t*)data, (uint32_t*)sampleData, aFillRect.Width()); + data += surfMap.GetStride(); + } + } else if (BytesPerPixel(aSurface->GetFormat()) == 1) { + for (int32_t y = 0; y < aFillRect.Height(); y++) { + PodCopy(data, sampleData, aFillRect.Width()); + data += surfMap.GetStride(); + } + } +} + +static void FillRectWithHorizontallyRepeatingVerticalStrip( + DataSourceSurface* aSurface, const IntRect& aFillRect, + const IntRect& aSampleRect) { + MOZ_ASSERT(!aFillRect.Overflows()); + MOZ_ASSERT(!aSampleRect.Overflows()); + MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aFillRect), + "aFillRect needs to be completely inside the surface"); + MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aSampleRect), + "aSampleRect needs to be completely inside the surface"); + + DataSourceSurface::ScopedMap surfMap(aSurface, DataSourceSurface::READ_WRITE); + if (MOZ2D_WARN_IF(!surfMap.IsMapped())) { + return; + } + + uint8_t* sampleData = + DataAtOffset(aSurface, surfMap.GetMappedSurface(), aSampleRect.TopLeft()); + uint8_t* data = + DataAtOffset(aSurface, surfMap.GetMappedSurface(), aFillRect.TopLeft()); + if (BytesPerPixel(aSurface->GetFormat()) == 4) { + for (int32_t y = 0; y < aFillRect.Height(); y++) { + int32_t sampleColor = *((uint32_t*)sampleData); + for (int32_t x = 0; x < aFillRect.Width(); x++) { + *((uint32_t*)data + x) = sampleColor; + } + data += surfMap.GetStride(); + sampleData += surfMap.GetStride(); + } + } else if (BytesPerPixel(aSurface->GetFormat()) == 1) { + for (int32_t y = 0; y < aFillRect.Height(); y++) { + uint8_t sampleColor = *sampleData; + memset(data, sampleColor, aFillRect.Width()); + data += surfMap.GetStride(); + sampleData += surfMap.GetStride(); + } + } +} + +static void DuplicateEdges(DataSourceSurface* aSurface, + const IntRect& aFromRect) { + MOZ_ASSERT(!aFromRect.Overflows()); + MOZ_ASSERT(IntRect(IntPoint(), aSurface->GetSize()).Contains(aFromRect), + "aFromRect needs to be completely inside the surface"); + + IntSize size = aSurface->GetSize(); + IntRect fill; + IntRect sampleRect; + for (int32_t ix = 0; ix < 3; ix++) { + switch (ix) { + case 0: + fill.SetRectX(0, aFromRect.X()); + sampleRect.SetRectX(fill.XMost(), 1); + break; + case 1: + fill.SetRectX(aFromRect.X(), aFromRect.Width()); + sampleRect.SetRectX(fill.X(), fill.Width()); + break; + case 2: + fill.MoveToX(aFromRect.XMost()); + fill.SetRightEdge(size.width); + sampleRect.SetRectX(fill.X() - 1, 1); + break; + } + if (fill.Width() <= 0) { + continue; + } + bool xIsMiddle = (ix == 1); + for (int32_t iy = 0; iy < 3; iy++) { + switch (iy) { + case 0: + fill.SetRectY(0, aFromRect.Y()); + sampleRect.SetRectY(fill.YMost(), 1); + break; + case 1: + fill.SetRectY(aFromRect.Y(), aFromRect.Height()); + sampleRect.SetRectY(fill.Y(), fill.Height()); + break; + case 2: + fill.MoveToY(aFromRect.YMost()); + fill.SetBottomEdge(size.height); + sampleRect.SetRectY(fill.Y() - 1, 1); + break; + } + if (fill.Height() <= 0) { + continue; + } + bool yIsMiddle = (iy == 1); + if (!xIsMiddle && !yIsMiddle) { + // Corner + FillRectWithPixel(aSurface, fill, sampleRect.TopLeft()); + } + if (xIsMiddle && !yIsMiddle) { + // Top middle or bottom middle + FillRectWithVerticallyRepeatingHorizontalStrip(aSurface, fill, + sampleRect); + } + if (!xIsMiddle && yIsMiddle) { + // Left middle or right middle + FillRectWithHorizontallyRepeatingVerticalStrip(aSurface, fill, + sampleRect); + } + } + } +} + +static IntPoint TileIndex(const IntRect& aFirstTileRect, + const IntPoint& aPoint) { + return IntPoint(int32_t(floor(double(aPoint.x - aFirstTileRect.X()) / + aFirstTileRect.Width())), + int32_t(floor(double(aPoint.y - aFirstTileRect.Y()) / + aFirstTileRect.Height()))); +} + +static void TileSurface(DataSourceSurface* aSource, DataSourceSurface* aTarget, + const IntPoint& aOffset) { + IntRect sourceRect(aOffset, aSource->GetSize()); + IntRect targetRect(IntPoint(0, 0), aTarget->GetSize()); + IntPoint startIndex = TileIndex(sourceRect, targetRect.TopLeft()); + IntPoint endIndex = TileIndex(sourceRect, targetRect.BottomRight()); + + for (int32_t ix = startIndex.x; ix <= endIndex.x; ix++) { + for (int32_t iy = startIndex.y; iy <= endIndex.y; iy++) { + IntPoint destPoint(sourceRect.X() + ix * sourceRect.Width(), + sourceRect.Y() + iy * sourceRect.Height()); + IntRect destRect(destPoint, sourceRect.Size()); + destRect = destRect.Intersect(targetRect); + IntRect srcRect = destRect - destPoint; + CopyRect(aSource, aTarget, srcRect, destRect.TopLeft()); + } + } +} + +static already_AddRefed<DataSourceSurface> GetDataSurfaceInRect( + SourceSurface* aSurface, const IntRect& aSurfaceRect, + const IntRect& aDestRect, ConvolveMatrixEdgeMode aEdgeMode) { + MOZ_ASSERT(aSurface ? aSurfaceRect.Size() == aSurface->GetSize() + : aSurfaceRect.IsEmpty()); + + if (aSurfaceRect.Overflows() || aDestRect.Overflows()) { + // We can't rely on the intersection calculations below to make sense when + // XMost() or YMost() overflow. Bail out. + return nullptr; + } + + IntRect sourceRect = aSurfaceRect; + + if (sourceRect.IsEqualEdges(aDestRect)) { + return aSurface ? aSurface->GetDataSurface() : nullptr; + } + + IntRect intersect = sourceRect.Intersect(aDestRect); + + // create rects that are in surface local space. + IntRect intersectInSourceSpace = intersect - sourceRect.TopLeft(); + IntRect intersectInDestSpace = intersect - aDestRect.TopLeft(); + SurfaceFormat format = + aSurface ? aSurface->GetFormat() : SurfaceFormat(SurfaceFormat::B8G8R8A8); + + RefPtr<DataSourceSurface> target = + Factory::CreateDataSourceSurface(aDestRect.Size(), format, true); + if (MOZ2D_WARN_IF(!target)) { + return nullptr; + } + + if (!aSurface) { + return target.forget(); + } + + RefPtr<DataSourceSurface> dataSource = aSurface->GetDataSurface(); + MOZ_ASSERT(dataSource); + + if (aEdgeMode == EDGE_MODE_WRAP) { + TileSurface(dataSource, target, intersectInDestSpace.TopLeft()); + return target.forget(); + } + + CopyRect(dataSource, target, intersectInSourceSpace, + intersectInDestSpace.TopLeft()); + + if (aEdgeMode == EDGE_MODE_DUPLICATE) { + DuplicateEdges(target, intersectInDestSpace); + } + + return target.forget(); +} + +/* static */ +already_AddRefed<FilterNode> FilterNodeSoftware::Create(FilterType aType) { + RefPtr<FilterNodeSoftware> filter; + switch (aType) { + case FilterType::BLEND: + filter = new FilterNodeBlendSoftware(); + break; + case FilterType::TRANSFORM: + filter = new FilterNodeTransformSoftware(); + break; + case FilterType::MORPHOLOGY: + filter = new FilterNodeMorphologySoftware(); + break; + case FilterType::COLOR_MATRIX: + filter = new FilterNodeColorMatrixSoftware(); + break; + case FilterType::FLOOD: + filter = new FilterNodeFloodSoftware(); + break; + case FilterType::TILE: + filter = new FilterNodeTileSoftware(); + break; + case FilterType::TABLE_TRANSFER: + filter = new FilterNodeTableTransferSoftware(); + break; + case FilterType::DISCRETE_TRANSFER: + filter = new FilterNodeDiscreteTransferSoftware(); + break; + case FilterType::LINEAR_TRANSFER: + filter = new FilterNodeLinearTransferSoftware(); + break; + case FilterType::GAMMA_TRANSFER: + filter = new FilterNodeGammaTransferSoftware(); + break; + case FilterType::CONVOLVE_MATRIX: + filter = new FilterNodeConvolveMatrixSoftware(); + break; + case FilterType::DISPLACEMENT_MAP: + filter = new FilterNodeDisplacementMapSoftware(); + break; + case FilterType::TURBULENCE: + filter = new FilterNodeTurbulenceSoftware(); + break; + case FilterType::ARITHMETIC_COMBINE: + filter = new FilterNodeArithmeticCombineSoftware(); + break; + case FilterType::COMPOSITE: + filter = new FilterNodeCompositeSoftware(); + break; + case FilterType::GAUSSIAN_BLUR: + filter = new FilterNodeGaussianBlurSoftware(); + break; + case FilterType::DIRECTIONAL_BLUR: + filter = new FilterNodeDirectionalBlurSoftware(); + break; + case FilterType::CROP: + filter = new FilterNodeCropSoftware(); + break; + case FilterType::PREMULTIPLY: + filter = new FilterNodePremultiplySoftware(); + break; + case FilterType::UNPREMULTIPLY: + filter = new FilterNodeUnpremultiplySoftware(); + break; + case FilterType::OPACITY: + filter = new FilterNodeOpacitySoftware(); + break; + case FilterType::POINT_DIFFUSE: + filter = new FilterNodeLightingSoftware<PointLightSoftware, + DiffuseLightingSoftware>( + "FilterNodeLightingSoftware<PointLight, DiffuseLighting>"); + break; + case FilterType::POINT_SPECULAR: + filter = new FilterNodeLightingSoftware<PointLightSoftware, + SpecularLightingSoftware>( + "FilterNodeLightingSoftware<PointLight, SpecularLighting>"); + break; + case FilterType::SPOT_DIFFUSE: + filter = new FilterNodeLightingSoftware<SpotLightSoftware, + DiffuseLightingSoftware>( + "FilterNodeLightingSoftware<SpotLight, DiffuseLighting>"); + break; + case FilterType::SPOT_SPECULAR: + filter = new FilterNodeLightingSoftware<SpotLightSoftware, + SpecularLightingSoftware>( + "FilterNodeLightingSoftware<SpotLight, SpecularLighting>"); + break; + case FilterType::DISTANT_DIFFUSE: + filter = new FilterNodeLightingSoftware<DistantLightSoftware, + DiffuseLightingSoftware>( + "FilterNodeLightingSoftware<DistantLight, DiffuseLighting>"); + break; + case FilterType::DISTANT_SPECULAR: + filter = new FilterNodeLightingSoftware<DistantLightSoftware, + SpecularLightingSoftware>( + "FilterNodeLightingSoftware<DistantLight, SpecularLighting>"); + break; + } + return filter.forget(); +} + +void FilterNodeSoftware::Draw(DrawTarget* aDrawTarget, const Rect& aSourceRect, + const Point& aDestPoint, + const DrawOptions& aOptions) { +#ifdef DEBUG_DUMP_SURFACES + printf("<style>section{margin:10px;}</style><pre>\nRendering filter %s...\n", + GetName()); +#endif + + Rect renderRect = aSourceRect; + renderRect.RoundOut(); + IntRect renderIntRect; + if (!renderRect.ToIntRect(&renderIntRect)) { +#ifdef DEBUG_DUMP_SURFACES + printf("render rect overflowed, not painting anything\n"); + printf("</pre>\n"); +#endif + return; + } + + IntRect outputRect = GetOutputRectInRect(renderIntRect); + if (outputRect.Overflows()) { +#ifdef DEBUG_DUMP_SURFACES + printf("output rect overflowed, not painting anything\n"); + printf("</pre>\n"); +#endif + return; + } + + RefPtr<DataSourceSurface> result; + if (!outputRect.IsEmpty()) { + result = GetOutput(outputRect); + } + + if (!result) { + // Null results are allowed and treated as transparent. Don't draw anything. +#ifdef DEBUG_DUMP_SURFACES + printf("output returned null\n"); + printf("</pre>\n"); +#endif + return; + } + +#ifdef DEBUG_DUMP_SURFACES + printf("output from %s:\n", GetName()); + printf("<img src='"); + gfxUtils::DumpAsDataURL(result); + printf("'>\n"); + printf("</pre>\n"); +#endif + + Point sourceToDestOffset = aDestPoint - aSourceRect.TopLeft(); + Rect renderedSourceRect = Rect(outputRect).Intersect(aSourceRect); + Rect renderedDestRect = renderedSourceRect + sourceToDestOffset; + if (result->GetFormat() == SurfaceFormat::A8) { + // Interpret the result as having implicitly black color channels. + aDrawTarget->PushClipRect(renderedDestRect); + aDrawTarget->MaskSurface( + ColorPattern(DeviceColor::MaskOpaqueBlack()), result, + Point(outputRect.TopLeft()) + sourceToDestOffset, aOptions); + aDrawTarget->PopClip(); + } else { + aDrawTarget->DrawSurface(result, renderedDestRect, + renderedSourceRect - Point(outputRect.TopLeft()), + DrawSurfaceOptions(), aOptions); + } +} + +already_AddRefed<DataSourceSurface> FilterNodeSoftware::GetOutput( + const IntRect& aRect) { + MOZ_ASSERT(GetOutputRectInRect(aRect).Contains(aRect)); + + if (aRect.Overflows()) { + return nullptr; + } + + IntRect cachedRect; + IntRect requestedRect; + RefPtr<DataSourceSurface> cachedOutput; + + // Retrieve a cached surface if we have one and it can + // satisfy this request, or else request a rect we will compute and cache + if (!mCachedRect.Contains(aRect)) { + RequestRect(aRect); + requestedRect = mRequestedRect; + } else { + MOZ_ASSERT(mCachedOutput, "cached rect but no cached output?"); + cachedRect = mCachedRect; + cachedOutput = mCachedOutput; + } + + if (!cachedOutput) { + // Compute the output + cachedOutput = Render(requestedRect); + + // Update the cache for future requests + mCachedOutput = cachedOutput; + if (!mCachedOutput) { + mCachedRect = IntRect(); + mRequestedRect = IntRect(); + return nullptr; + } + mCachedRect = requestedRect; + mRequestedRect = IntRect(); + + cachedRect = mCachedRect; + } + + return GetDataSurfaceInRect(cachedOutput, cachedRect, aRect, EDGE_MODE_NONE); +} + +void FilterNodeSoftware::RequestRect(const IntRect& aRect) { + if (mRequestedRect.Contains(aRect)) { + // Bail out now. Otherwise pathological filters can spend time exponential + // in the number of primitives, e.g. if each primitive takes the + // previous primitive as its two inputs. + return; + } + mRequestedRect = mRequestedRect.Union(aRect); + RequestFromInputsForRect(aRect); +} + +IntRect FilterNodeSoftware::MapInputRectToSource(uint32_t aInputEnumIndex, + const IntRect& aRect, + const IntRect& aMax, + FilterNode* aSourceNode) { + int32_t inputIndex = InputIndex(aInputEnumIndex); + if (inputIndex < 0) { + gfxDevCrash(LogReason::FilterInputError) + << "Invalid input " << inputIndex << " vs. " << NumberOfSetInputs(); + return aMax; + } + if ((uint32_t)inputIndex < NumberOfSetInputs()) { + RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex]; + // If we have any input filters call into them to do the mapping, + // otherwise we can assume an input surface will be used + // and just return aRect. + if (filter) { + return filter->MapRectToSource(aRect, aMax, aSourceNode); + } + } + // We have an input surface instead of a filter + // so check if we're the target node. + if (this == aSourceNode) { + return aRect; + } + return IntRect(); +} + +void FilterNodeSoftware::RequestInputRect(uint32_t aInputEnumIndex, + const IntRect& aRect) { + if (aRect.Overflows()) { + return; + } + + int32_t inputIndex = InputIndex(aInputEnumIndex); + if (inputIndex < 0 || (uint32_t)inputIndex >= NumberOfSetInputs()) { + gfxDevCrash(LogReason::FilterInputError) + << "Invalid input " << inputIndex << " vs. " << NumberOfSetInputs(); + return; + } + if (mInputSurfaces[inputIndex]) { + return; + } + RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex]; + MOZ_ASSERT(filter, "missing input"); + + filter->RequestRect(filter->GetOutputRectInRect(aRect)); +} + +SurfaceFormat FilterNodeSoftware::DesiredFormat(SurfaceFormat aCurrentFormat, + FormatHint aFormatHint) { + if (aCurrentFormat == SurfaceFormat::A8 && aFormatHint == CAN_HANDLE_A8) { + return SurfaceFormat::A8; + } + return SurfaceFormat::B8G8R8A8; +} + +already_AddRefed<DataSourceSurface> +FilterNodeSoftware::GetInputDataSourceSurface( + uint32_t aInputEnumIndex, const IntRect& aRect, FormatHint aFormatHint, + ConvolveMatrixEdgeMode aEdgeMode, + const IntRect* aTransparencyPaddedSourceRect) { + if (aRect.Overflows()) { + return nullptr; + } + +#ifdef DEBUG_DUMP_SURFACES + printf( + "<section><h1>GetInputDataSourceSurface with aRect: %d, %d, %d, " + "%d</h1>\n", + aRect.x, aRect.y, aRect.Width(), aRect.Height()); +#endif + int32_t inputIndex = InputIndex(aInputEnumIndex); + if (inputIndex < 0 || (uint32_t)inputIndex >= NumberOfSetInputs()) { + gfxDevCrash(LogReason::FilterInputData) + << "Invalid data " << inputIndex << " vs. " << NumberOfSetInputs(); + return nullptr; + } + + if (aRect.IsEmpty()) { + return nullptr; + } + + RefPtr<SourceSurface> surface; + IntRect surfaceRect; + + if (mInputSurfaces[inputIndex]) { + // Input from input surface + surface = mInputSurfaces[inputIndex]; +#ifdef DEBUG_DUMP_SURFACES + printf("input from input surface:\n"); +#endif + surfaceRect = surface->GetRect(); + } else { + // Input from input filter +#ifdef DEBUG_DUMP_SURFACES + printf("getting input from input filter %s...\n", + mInputFilters[inputIndex]->GetName()); +#endif + RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex]; + MOZ_ASSERT(filter, "missing input"); + IntRect inputFilterOutput = filter->GetOutputRectInRect(aRect); + if (!inputFilterOutput.IsEmpty()) { + surface = filter->GetOutput(inputFilterOutput); + } +#ifdef DEBUG_DUMP_SURFACES + printf("input from input filter %s:\n", + mInputFilters[inputIndex]->GetName()); +#endif + surfaceRect = inputFilterOutput; + MOZ_ASSERT(!surface || surfaceRect.Size() == surface->GetSize()); + } + + if (surface && surface->GetFormat() == SurfaceFormat::UNKNOWN) { +#ifdef DEBUG_DUMP_SURFACES + printf("wrong input format</section>\n\n"); +#endif + return nullptr; + } + + if (!surfaceRect.IsEmpty() && !surface) { +#ifdef DEBUG_DUMP_SURFACES + printf(" -- no input --</section>\n\n"); +#endif + return nullptr; + } + + if (aTransparencyPaddedSourceRect && + !aTransparencyPaddedSourceRect->IsEmpty()) { + IntRect srcRect = aTransparencyPaddedSourceRect->Intersect(aRect); + surface = + GetDataSurfaceInRect(surface, surfaceRect, srcRect, EDGE_MODE_NONE); + if (surface) { + surfaceRect = srcRect; + } else { + // Padding the surface with transparency failed, probably due to size + // restrictions. Since |surface| is now null, set the surfaceRect to + // empty so that we're consistent. + surfaceRect.SetEmpty(); + } + } + + RefPtr<DataSourceSurface> result = + GetDataSurfaceInRect(surface, surfaceRect, aRect, aEdgeMode); + + if (result) { + // TODO: This isn't safe since we don't have a guarantee + // that future Maps will have the same stride + DataSourceSurface::MappedSurface map; + if (result->Map(DataSourceSurface::READ, &map)) { + // Unmap immediately since CloneAligned hasn't been updated + // to use the Map API yet. We can still read the stride/data + // values as long as we don't try to dereference them. + result->Unmap(); + if (map.mStride != GetAlignedStride<16>(map.mStride, 1) || + reinterpret_cast<uintptr_t>(map.mData) % 16 != 0) { + // Align unaligned surface. + result = CloneAligned(result); + } + } else { + result = nullptr; + } + } + + if (!result) { +#ifdef DEBUG_DUMP_SURFACES + printf(" -- no input --</section>\n\n"); +#endif + return nullptr; + } + + SurfaceFormat currentFormat = result->GetFormat(); + if (DesiredFormat(currentFormat, aFormatHint) == SurfaceFormat::B8G8R8A8 && + currentFormat != SurfaceFormat::B8G8R8A8) { + result = FilterProcessing::ConvertToB8G8R8A8(result); + } + +#ifdef DEBUG_DUMP_SURFACES + printf("<img src='"); + gfxUtils::DumpAsDataURL(result); + printf("'></section>"); +#endif + + MOZ_ASSERT(!result || result->GetSize() == aRect.Size(), + "wrong surface size"); + + return result.forget(); +} + +IntRect FilterNodeSoftware::GetInputRectInRect(uint32_t aInputEnumIndex, + const IntRect& aInRect) { + if (aInRect.Overflows()) { + return IntRect(); + } + + int32_t inputIndex = InputIndex(aInputEnumIndex); + if (inputIndex < 0 || (uint32_t)inputIndex >= NumberOfSetInputs()) { + gfxDevCrash(LogReason::FilterInputRect) + << "Invalid rect " << inputIndex << " vs. " << NumberOfSetInputs(); + return IntRect(); + } + if (mInputSurfaces[inputIndex]) { + return aInRect.Intersect(mInputSurfaces[inputIndex]->GetRect()); + } + RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex]; + MOZ_ASSERT(filter, "missing input"); + return filter->GetOutputRectInRect(aInRect); +} + +size_t FilterNodeSoftware::NumberOfSetInputs() { + return std::max(mInputSurfaces.size(), mInputFilters.size()); +} + +void FilterNodeSoftware::AddInvalidationListener( + FilterInvalidationListener* aListener) { + MOZ_ASSERT(aListener, "null listener"); + mInvalidationListeners.push_back(aListener); +} + +void FilterNodeSoftware::RemoveInvalidationListener( + FilterInvalidationListener* aListener) { + MOZ_ASSERT(aListener, "null listener"); + std::vector<FilterInvalidationListener*>::iterator it = std::find( + mInvalidationListeners.begin(), mInvalidationListeners.end(), aListener); + mInvalidationListeners.erase(it); +} + +void FilterNodeSoftware::FilterInvalidated(FilterNodeSoftware* aFilter) { + Invalidate(); +} + +void FilterNodeSoftware::Invalidate() { + mCachedOutput = nullptr; + mCachedRect = IntRect(); + for (std::vector<FilterInvalidationListener*>::iterator it = + mInvalidationListeners.begin(); + it != mInvalidationListeners.end(); it++) { + (*it)->FilterInvalidated(this); + } +} + +FilterNodeSoftware::FilterNodeSoftware() {} + +FilterNodeSoftware::~FilterNodeSoftware() { + MOZ_ASSERT( + mInvalidationListeners.empty(), + "All invalidation listeners should have unsubscribed themselves by now!"); + + for (std::vector<RefPtr<FilterNodeSoftware> >::iterator it = + mInputFilters.begin(); + it != mInputFilters.end(); it++) { + if (*it) { + (*it)->RemoveInvalidationListener(this); + } + } +} + +void FilterNodeSoftware::SetInput(uint32_t aIndex, FilterNode* aFilter) { + if (aFilter && aFilter->GetBackendType() != FILTER_BACKEND_SOFTWARE) { + MOZ_ASSERT(false, "can only take software filters as inputs"); + return; + } + SetInput(aIndex, nullptr, static_cast<FilterNodeSoftware*>(aFilter)); +} + +void FilterNodeSoftware::SetInput(uint32_t aIndex, SourceSurface* aSurface) { + SetInput(aIndex, aSurface, nullptr); +} + +void FilterNodeSoftware::SetInput(uint32_t aInputEnumIndex, + SourceSurface* aSurface, + FilterNodeSoftware* aFilter) { + int32_t inputIndex = InputIndex(aInputEnumIndex); + if (inputIndex < 0) { + gfxDevCrash(LogReason::FilterInputSet) << "Invalid set " << inputIndex; + return; + } + if ((uint32_t)inputIndex >= NumberOfSetInputs()) { + mInputSurfaces.resize(inputIndex + 1); + mInputFilters.resize(inputIndex + 1); + } + mInputSurfaces[inputIndex] = aSurface; + if (mInputFilters[inputIndex]) { + mInputFilters[inputIndex]->RemoveInvalidationListener(this); + } + if (aFilter) { + aFilter->AddInvalidationListener(this); + } + mInputFilters[inputIndex] = aFilter; + if (!aSurface && !aFilter && (size_t)inputIndex == NumberOfSetInputs()) { + mInputSurfaces.resize(inputIndex); + mInputFilters.resize(inputIndex); + } + Invalidate(); +} + +FilterNodeBlendSoftware::FilterNodeBlendSoftware() + : mBlendMode(BLEND_MODE_MULTIPLY) {} + +int32_t FilterNodeBlendSoftware::InputIndex(uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_BLEND_IN: + return 0; + case IN_BLEND_IN2: + return 1; + default: + return -1; + } +} + +void FilterNodeBlendSoftware::SetAttribute(uint32_t aIndex, + uint32_t aBlendMode) { + MOZ_ASSERT(aIndex == ATT_BLEND_BLENDMODE); + mBlendMode = static_cast<BlendMode>(aBlendMode); + Invalidate(); +} + +static CompositionOp ToBlendOp(BlendMode aOp) { + switch (aOp) { + case BLEND_MODE_MULTIPLY: + return CompositionOp::OP_MULTIPLY; + case BLEND_MODE_SCREEN: + return CompositionOp::OP_SCREEN; + case BLEND_MODE_OVERLAY: + return CompositionOp::OP_OVERLAY; + case BLEND_MODE_DARKEN: + return CompositionOp::OP_DARKEN; + case BLEND_MODE_LIGHTEN: + return CompositionOp::OP_LIGHTEN; + case BLEND_MODE_COLOR_DODGE: + return CompositionOp::OP_COLOR_DODGE; + case BLEND_MODE_COLOR_BURN: + return CompositionOp::OP_COLOR_BURN; + case BLEND_MODE_HARD_LIGHT: + return CompositionOp::OP_HARD_LIGHT; + case BLEND_MODE_SOFT_LIGHT: + return CompositionOp::OP_SOFT_LIGHT; + case BLEND_MODE_DIFFERENCE: + return CompositionOp::OP_DIFFERENCE; + case BLEND_MODE_EXCLUSION: + return CompositionOp::OP_EXCLUSION; + case BLEND_MODE_HUE: + return CompositionOp::OP_HUE; + case BLEND_MODE_SATURATION: + return CompositionOp::OP_SATURATION; + case BLEND_MODE_COLOR: + return CompositionOp::OP_COLOR; + case BLEND_MODE_LUMINOSITY: + return CompositionOp::OP_LUMINOSITY; + } + + MOZ_ASSERT_UNREACHABLE("Unexpected BlendMode"); + return CompositionOp::OP_OVER; +} + +already_AddRefed<DataSourceSurface> FilterNodeBlendSoftware::Render( + const IntRect& aRect) { + RefPtr<DataSourceSurface> input1 = + GetInputDataSourceSurface(IN_BLEND_IN, aRect, NEED_COLOR_CHANNELS); + RefPtr<DataSourceSurface> input2 = + GetInputDataSourceSurface(IN_BLEND_IN2, aRect, NEED_COLOR_CHANNELS); + + // Null inputs need to be treated as transparent. + + // First case: both are transparent. + if (!input1 && !input2) { + // Then the result is transparent, too. + return nullptr; + } + + // Second case: one of them is transparent. Return the non-transparent one. + if (!input1 || !input2) { + return input1 ? input1.forget() : input2.forget(); + } + + // Third case: both are non-transparent. + // Apply normal filtering. + RefPtr<DataSourceSurface> target = + FilterProcessing::ApplyBlending(input1, input2, mBlendMode); + if (target != nullptr) { + return target.forget(); + } + + IntSize size = input1->GetSize(); + target = Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8); + if (MOZ2D_WARN_IF(!target)) { + return nullptr; + } + + CopyRect(input1, target, IntRect(IntPoint(), size), IntPoint()); + + // This needs to stay in scope until the draw target has been flushed. + DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::READ_WRITE); + if (MOZ2D_WARN_IF(!targetMap.IsMapped())) { + return nullptr; + } + + RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData( + BackendType::SKIA, targetMap.GetData(), target->GetSize(), + targetMap.GetStride(), target->GetFormat()); + + if (!dt) { + gfxWarning() + << "FilterNodeBlendSoftware::Render failed in CreateDrawTargetForData"; + return nullptr; + } + + Rect r(0, 0, size.width, size.height); + dt->DrawSurface(input2, r, r, DrawSurfaceOptions(), + DrawOptions(1.0f, ToBlendOp(mBlendMode))); + dt->Flush(); + return target.forget(); +} + +void FilterNodeBlendSoftware::RequestFromInputsForRect(const IntRect& aRect) { + RequestInputRect(IN_BLEND_IN, aRect); + RequestInputRect(IN_BLEND_IN2, aRect); +} + +IntRect FilterNodeBlendSoftware::MapRectToSource(const IntRect& aRect, + const IntRect& aMax, + FilterNode* aSourceNode) { + IntRect result = MapInputRectToSource(IN_BLEND_IN, aRect, aMax, aSourceNode); + result.OrWith(MapInputRectToSource(IN_BLEND_IN2, aRect, aMax, aSourceNode)); + return result; +} + +IntRect FilterNodeBlendSoftware::GetOutputRectInRect(const IntRect& aRect) { + return GetInputRectInRect(IN_BLEND_IN, aRect) + .Union(GetInputRectInRect(IN_BLEND_IN2, aRect)) + .Intersect(aRect); +} + +FilterNodeTransformSoftware::FilterNodeTransformSoftware() + : mSamplingFilter(SamplingFilter::GOOD) {} + +int32_t FilterNodeTransformSoftware::InputIndex(uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_TRANSFORM_IN: + return 0; + default: + return -1; + } +} + +void FilterNodeTransformSoftware::SetAttribute(uint32_t aIndex, + uint32_t aFilter) { + MOZ_ASSERT(aIndex == ATT_TRANSFORM_FILTER); + mSamplingFilter = static_cast<SamplingFilter>(aFilter); + Invalidate(); +} + +void FilterNodeTransformSoftware::SetAttribute(uint32_t aIndex, + const Matrix& aMatrix) { + MOZ_ASSERT(aIndex == ATT_TRANSFORM_MATRIX); + mMatrix = aMatrix; + Invalidate(); +} + +IntRect FilterNodeTransformSoftware::SourceRectForOutputRect( + const IntRect& aRect) { + if (aRect.IsEmpty()) { + return IntRect(); + } + + Matrix inverted(mMatrix); + if (!inverted.Invert()) { + return IntRect(); + } + + Rect neededRect = inverted.TransformBounds(Rect(aRect)); + neededRect.RoundOut(); + IntRect neededIntRect; + if (!neededRect.ToIntRect(&neededIntRect)) { + return IntRect(); + } + return GetInputRectInRect(IN_TRANSFORM_IN, neededIntRect); +} + +IntRect FilterNodeTransformSoftware::MapRectToSource(const IntRect& aRect, + const IntRect& aMax, + FilterNode* aSourceNode) { + if (aRect.IsEmpty()) { + return IntRect(); + } + + Matrix inverted(mMatrix); + if (!inverted.Invert()) { + return aMax; + } + + Rect neededRect = inverted.TransformBounds(Rect(aRect)); + neededRect.RoundOut(); + IntRect neededIntRect; + if (!neededRect.ToIntRect(&neededIntRect)) { + return aMax; + } + return MapInputRectToSource(IN_TRANSFORM_IN, neededIntRect, aMax, + aSourceNode); +} + +already_AddRefed<DataSourceSurface> FilterNodeTransformSoftware::Render( + const IntRect& aRect) { + IntRect srcRect = SourceRectForOutputRect(aRect); + + RefPtr<DataSourceSurface> input = + GetInputDataSourceSurface(IN_TRANSFORM_IN, srcRect); + + if (!input) { + return nullptr; + } + + Matrix transform = Matrix::Translation(srcRect.X(), srcRect.Y()) * mMatrix * + Matrix::Translation(-aRect.X(), -aRect.Y()); + if (transform.IsIdentity() && srcRect.Size() == aRect.Size()) { + return input.forget(); + } + + RefPtr<DataSourceSurface> surf = + Factory::CreateDataSourceSurface(aRect.Size(), input->GetFormat(), true); + + if (!surf) { + return nullptr; + } + + DataSourceSurface::MappedSurface mapping; + if (!surf->Map(DataSourceSurface::MapType::WRITE, &mapping)) { + gfxCriticalError() + << "FilterNodeTransformSoftware::Render failed to map surface"; + return nullptr; + } + + RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData( + BackendType::SKIA, mapping.mData, surf->GetSize(), mapping.mStride, + surf->GetFormat()); + if (!dt) { + gfxWarning() << "FilterNodeTransformSoftware::Render failed in " + "CreateDrawTargetForData"; + return nullptr; + } + + Rect r(0, 0, srcRect.Width(), srcRect.Height()); + dt->SetTransform(transform); + dt->DrawSurface(input, r, r, DrawSurfaceOptions(mSamplingFilter)); + + dt->Flush(); + surf->Unmap(); + return surf.forget(); +} + +void FilterNodeTransformSoftware::RequestFromInputsForRect( + const IntRect& aRect) { + RequestInputRect(IN_TRANSFORM_IN, SourceRectForOutputRect(aRect)); +} + +IntRect FilterNodeTransformSoftware::GetOutputRectInRect(const IntRect& aRect) { + IntRect srcRect = SourceRectForOutputRect(aRect); + if (srcRect.IsEmpty()) { + return IntRect(); + } + + Rect outRect = mMatrix.TransformBounds(Rect(srcRect)); + outRect.RoundOut(); + IntRect outIntRect; + if (!outRect.ToIntRect(&outIntRect)) { + return IntRect(); + } + return outIntRect.Intersect(aRect); +} + +FilterNodeMorphologySoftware::FilterNodeMorphologySoftware() + : mOperator(MORPHOLOGY_OPERATOR_ERODE) {} + +int32_t FilterNodeMorphologySoftware::InputIndex(uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_MORPHOLOGY_IN: + return 0; + default: + return -1; + } +} + +void FilterNodeMorphologySoftware::SetAttribute(uint32_t aIndex, + const IntSize& aRadii) { + MOZ_ASSERT(aIndex == ATT_MORPHOLOGY_RADII); + mRadii.width = std::min(std::max(aRadii.width, 0), 100000); + mRadii.height = std::min(std::max(aRadii.height, 0), 100000); + Invalidate(); +} + +void FilterNodeMorphologySoftware::SetAttribute(uint32_t aIndex, + uint32_t aOperator) { + MOZ_ASSERT(aIndex == ATT_MORPHOLOGY_OPERATOR); + mOperator = static_cast<MorphologyOperator>(aOperator); + Invalidate(); +} + +static already_AddRefed<DataSourceSurface> ApplyMorphology( + const IntRect& aSourceRect, DataSourceSurface* aInput, + const IntRect& aDestRect, int32_t rx, int32_t ry, + MorphologyOperator aOperator) { + IntRect srcRect = aSourceRect - aDestRect.TopLeft(); + IntRect destRect = aDestRect - aDestRect.TopLeft(); + IntRect tmpRect(destRect.X(), srcRect.Y(), destRect.Width(), + srcRect.Height()); +#ifdef DEBUG + IntMargin margin = srcRect - destRect; + MOZ_ASSERT(margin.top >= ry && margin.right >= rx && margin.bottom >= ry && + margin.left >= rx, + "insufficient margin"); +#endif + + RefPtr<DataSourceSurface> tmp; + if (rx == 0) { + tmp = aInput; + } else { + tmp = Factory::CreateDataSourceSurface(tmpRect.Size(), + SurfaceFormat::B8G8R8A8); + if (MOZ2D_WARN_IF(!tmp)) { + return nullptr; + } + + DataSourceSurface::ScopedMap sourceMap(aInput, DataSourceSurface::READ); + DataSourceSurface::ScopedMap tmpMap(tmp, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!sourceMap.IsMapped() || !tmpMap.IsMapped())) { + return nullptr; + } + uint8_t* sourceData = DataAtOffset(aInput, sourceMap.GetMappedSurface(), + destRect.TopLeft() - srcRect.TopLeft()); + uint8_t* tmpData = DataAtOffset(tmp, tmpMap.GetMappedSurface(), + destRect.TopLeft() - tmpRect.TopLeft()); + + FilterProcessing::ApplyMorphologyHorizontal( + sourceData, sourceMap.GetStride(), tmpData, tmpMap.GetStride(), tmpRect, + rx, aOperator); + } + + RefPtr<DataSourceSurface> dest; + if (ry == 0) { + dest = tmp; + } else { + dest = Factory::CreateDataSourceSurface(destRect.Size(), + SurfaceFormat::B8G8R8A8); + if (MOZ2D_WARN_IF(!dest)) { + return nullptr; + } + + DataSourceSurface::ScopedMap tmpMap(tmp, DataSourceSurface::READ); + DataSourceSurface::ScopedMap destMap(dest, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!tmpMap.IsMapped() || !destMap.IsMapped())) { + return nullptr; + } + int32_t tmpStride = tmpMap.GetStride(); + uint8_t* tmpData = DataAtOffset(tmp, tmpMap.GetMappedSurface(), + destRect.TopLeft() - tmpRect.TopLeft()); + + int32_t destStride = destMap.GetStride(); + uint8_t* destData = destMap.GetData(); + + FilterProcessing::ApplyMorphologyVertical( + tmpData, tmpStride, destData, destStride, destRect, ry, aOperator); + } + + return dest.forget(); +} + +already_AddRefed<DataSourceSurface> FilterNodeMorphologySoftware::Render( + const IntRect& aRect) { + IntRect srcRect = aRect; + srcRect.Inflate(mRadii); + + RefPtr<DataSourceSurface> input = + GetInputDataSourceSurface(IN_MORPHOLOGY_IN, srcRect, NEED_COLOR_CHANNELS); + if (!input) { + return nullptr; + } + + int32_t rx = mRadii.width; + int32_t ry = mRadii.height; + + if (rx == 0 && ry == 0) { + return input.forget(); + } + + return ApplyMorphology(srcRect, input, aRect, rx, ry, mOperator); +} + +void FilterNodeMorphologySoftware::RequestFromInputsForRect( + const IntRect& aRect) { + IntRect srcRect = aRect; + srcRect.Inflate(mRadii); + RequestInputRect(IN_MORPHOLOGY_IN, srcRect); +} + +IntRect FilterNodeMorphologySoftware::GetOutputRectInRect( + const IntRect& aRect) { + IntRect inflatedSourceRect = aRect; + inflatedSourceRect.Inflate(mRadii); + IntRect inputRect = GetInputRectInRect(IN_MORPHOLOGY_IN, inflatedSourceRect); + if (mOperator == MORPHOLOGY_OPERATOR_ERODE) { + inputRect.Deflate(mRadii); + } else { + inputRect.Inflate(mRadii); + } + return inputRect.Intersect(aRect); +} + +int32_t FilterNodeColorMatrixSoftware::InputIndex(uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_COLOR_MATRIX_IN: + return 0; + default: + return -1; + } +} + +void FilterNodeColorMatrixSoftware::SetAttribute(uint32_t aIndex, + const Matrix5x4& aMatrix) { + MOZ_ASSERT(aIndex == ATT_COLOR_MATRIX_MATRIX); + mMatrix = aMatrix; + Invalidate(); +} + +void FilterNodeColorMatrixSoftware::SetAttribute(uint32_t aIndex, + uint32_t aAlphaMode) { + MOZ_ASSERT(aIndex == ATT_COLOR_MATRIX_ALPHA_MODE); + mAlphaMode = (AlphaMode)aAlphaMode; + Invalidate(); +} + +static already_AddRefed<DataSourceSurface> Premultiply( + DataSourceSurface* aSurface) { + if (aSurface->GetFormat() == SurfaceFormat::A8) { + RefPtr<DataSourceSurface> surface(aSurface); + return surface.forget(); + } + + IntSize size = aSurface->GetSize(); + RefPtr<DataSourceSurface> target = + Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8); + if (MOZ2D_WARN_IF(!target)) { + return nullptr; + } + + DataSourceSurface::ScopedMap inputMap(aSurface, DataSourceSurface::READ); + DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!inputMap.IsMapped() || !targetMap.IsMapped())) { + return nullptr; + } + + uint8_t* inputData = inputMap.GetData(); + int32_t inputStride = inputMap.GetStride(); + uint8_t* targetData = targetMap.GetData(); + int32_t targetStride = targetMap.GetStride(); + + FilterProcessing::DoPremultiplicationCalculation( + size, targetData, targetStride, inputData, inputStride); + + return target.forget(); +} + +static already_AddRefed<DataSourceSurface> Unpremultiply( + DataSourceSurface* aSurface) { + if (aSurface->GetFormat() == SurfaceFormat::A8) { + RefPtr<DataSourceSurface> surface(aSurface); + return surface.forget(); + } + + IntSize size = aSurface->GetSize(); + RefPtr<DataSourceSurface> target = + Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8); + if (MOZ2D_WARN_IF(!target)) { + return nullptr; + } + + DataSourceSurface::ScopedMap inputMap(aSurface, DataSourceSurface::READ); + DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!inputMap.IsMapped() || !targetMap.IsMapped())) { + return nullptr; + } + + uint8_t* inputData = inputMap.GetData(); + int32_t inputStride = inputMap.GetStride(); + uint8_t* targetData = targetMap.GetData(); + int32_t targetStride = targetMap.GetStride(); + + FilterProcessing::DoUnpremultiplicationCalculation( + size, targetData, targetStride, inputData, inputStride); + + return target.forget(); +} + +static already_AddRefed<DataSourceSurface> Opacity(DataSourceSurface* aSurface, + Float aValue) { + if (aValue == 1.0f) { + RefPtr<DataSourceSurface> surface(aSurface); + return surface.forget(); + } + + IntSize size = aSurface->GetSize(); + RefPtr<DataSourceSurface> target = + Factory::CreateDataSourceSurface(size, aSurface->GetFormat()); + if (MOZ2D_WARN_IF(!target)) { + return nullptr; + } + + DataSourceSurface::ScopedMap inputMap(aSurface, DataSourceSurface::READ); + DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!inputMap.IsMapped() || !targetMap.IsMapped())) { + return nullptr; + } + + uint8_t* inputData = inputMap.GetData(); + int32_t inputStride = inputMap.GetStride(); + uint8_t* targetData = targetMap.GetData(); + int32_t targetStride = targetMap.GetStride(); + + if (aSurface->GetFormat() == SurfaceFormat::A8) { + FilterProcessing::DoOpacityCalculationA8(size, targetData, targetStride, + inputData, inputStride, aValue); + } else { + MOZ_ASSERT(aSurface->GetFormat() == SurfaceFormat::B8G8R8A8); + FilterProcessing::DoOpacityCalculation(size, targetData, targetStride, + inputData, inputStride, aValue); + } + + return target.forget(); +} + +already_AddRefed<DataSourceSurface> FilterNodeColorMatrixSoftware::Render( + const IntRect& aRect) { + RefPtr<DataSourceSurface> input = + GetInputDataSourceSurface(IN_COLOR_MATRIX_IN, aRect, NEED_COLOR_CHANNELS); + if (!input) { + return nullptr; + } + + if (mAlphaMode == ALPHA_MODE_PREMULTIPLIED) { + input = Unpremultiply(input); + } + + RefPtr<DataSourceSurface> result = + FilterProcessing::ApplyColorMatrix(input, mMatrix); + + if (mAlphaMode == ALPHA_MODE_PREMULTIPLIED) { + result = Premultiply(result); + } + + return result.forget(); +} + +void FilterNodeColorMatrixSoftware::RequestFromInputsForRect( + const IntRect& aRect) { + RequestInputRect(IN_COLOR_MATRIX_IN, aRect); +} + +IntRect FilterNodeColorMatrixSoftware::MapRectToSource( + const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { + return MapInputRectToSource(IN_COLOR_MATRIX_IN, aRect, aMax, aSourceNode); +} + +IntRect FilterNodeColorMatrixSoftware::GetOutputRectInRect( + const IntRect& aRect) { + if (mMatrix._54 > 0.0f) { + return aRect; + } + return GetInputRectInRect(IN_COLOR_MATRIX_IN, aRect); +} + +void FilterNodeFloodSoftware::SetAttribute(uint32_t aIndex, + const DeviceColor& aColor) { + MOZ_ASSERT(aIndex == ATT_FLOOD_COLOR); + mColor = aColor; + Invalidate(); +} + +static uint32_t ColorToBGRA(const DeviceColor& aColor) { + union { + uint32_t color; + uint8_t components[4]; + }; + components[B8G8R8A8_COMPONENT_BYTEOFFSET_R] = + NS_lround(aColor.r * aColor.a * 255.0f); + components[B8G8R8A8_COMPONENT_BYTEOFFSET_G] = + NS_lround(aColor.g * aColor.a * 255.0f); + components[B8G8R8A8_COMPONENT_BYTEOFFSET_B] = + NS_lround(aColor.b * aColor.a * 255.0f); + components[B8G8R8A8_COMPONENT_BYTEOFFSET_A] = NS_lround(aColor.a * 255.0f); + return color; +} + +static SurfaceFormat FormatForColor(DeviceColor aColor) { + if (aColor.r == 0 && aColor.g == 0 && aColor.b == 0) { + return SurfaceFormat::A8; + } + return SurfaceFormat::B8G8R8A8; +} + +already_AddRefed<DataSourceSurface> FilterNodeFloodSoftware::Render( + const IntRect& aRect) { + SurfaceFormat format = FormatForColor(mColor); + RefPtr<DataSourceSurface> target = + Factory::CreateDataSourceSurface(aRect.Size(), format); + if (MOZ2D_WARN_IF(!target)) { + return nullptr; + } + + DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!targetMap.IsMapped())) { + return nullptr; + } + + uint8_t* targetData = targetMap.GetData(); + int32_t stride = targetMap.GetStride(); + + if (format == SurfaceFormat::B8G8R8A8) { + uint32_t color = ColorToBGRA(mColor); + for (int32_t y = 0; y < aRect.Height(); y++) { + for (int32_t x = 0; x < aRect.Width(); x++) { + *((uint32_t*)targetData + x) = color; + } + PodZero(&targetData[aRect.Width() * 4], stride - aRect.Width() * 4); + targetData += stride; + } + } else if (format == SurfaceFormat::A8) { + uint8_t alpha = NS_lround(mColor.a * 255.0f); + for (int32_t y = 0; y < aRect.Height(); y++) { + for (int32_t x = 0; x < aRect.Width(); x++) { + targetData[x] = alpha; + } + PodZero(&targetData[aRect.Width()], stride - aRect.Width()); + targetData += stride; + } + } else { + gfxDevCrash(LogReason::FilterInputFormat) + << "Bad format in flood render " << (int)format; + return nullptr; + } + + return target.forget(); +} + +// Override GetOutput to get around caching. Rendering simple floods is +// comparatively fast. +already_AddRefed<DataSourceSurface> FilterNodeFloodSoftware::GetOutput( + const IntRect& aRect) { + return Render(aRect); +} + +IntRect FilterNodeFloodSoftware::MapRectToSource(const IntRect& aRect, + const IntRect& aMax, + FilterNode* aSourceNode) { + return IntRect(); +} + +IntRect FilterNodeFloodSoftware::GetOutputRectInRect(const IntRect& aRect) { + if (mColor.a == 0.0f) { + return IntRect(); + } + return aRect; +} + +int32_t FilterNodeTileSoftware::InputIndex(uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_TILE_IN: + return 0; + default: + return -1; + } +} + +void FilterNodeTileSoftware::SetAttribute(uint32_t aIndex, + const IntRect& aSourceRect) { + MOZ_ASSERT(aIndex == ATT_TILE_SOURCE_RECT); + mSourceRect.SetRect(int32_t(aSourceRect.X()), int32_t(aSourceRect.Y()), + int32_t(aSourceRect.Width()), + int32_t(aSourceRect.Height())); + Invalidate(); +} + +namespace { +struct CompareIntRects { + bool operator()(const IntRect& a, const IntRect& b) const { + if (a.X() != b.X()) { + return a.X() < b.X(); + } + if (a.Y() != b.Y()) { + return a.Y() < b.Y(); + } + if (a.Width() != b.Width()) { + return a.Width() < b.Width(); + } + return a.Height() < b.Height(); + } +}; + +} // namespace + +already_AddRefed<DataSourceSurface> FilterNodeTileSoftware::Render( + const IntRect& aRect) { + if (mSourceRect.IsEmpty()) { + return nullptr; + } + + if (mSourceRect.Contains(aRect)) { + return GetInputDataSourceSurface(IN_TILE_IN, aRect); + } + + RefPtr<DataSourceSurface> target; + + typedef std::map<IntRect, RefPtr<DataSourceSurface>, CompareIntRects> + InputMap; + InputMap inputs; + + IntPoint startIndex = TileIndex(mSourceRect, aRect.TopLeft()); + IntPoint endIndex = TileIndex(mSourceRect, aRect.BottomRight()); + for (int32_t ix = startIndex.x; ix <= endIndex.x; ix++) { + for (int32_t iy = startIndex.y; iy <= endIndex.y; iy++) { + IntPoint sourceToDestOffset(ix * mSourceRect.Width(), + iy * mSourceRect.Height()); + IntRect destRect = aRect.Intersect(mSourceRect + sourceToDestOffset); + IntRect srcRect = destRect - sourceToDestOffset; + if (srcRect.IsEmpty()) { + continue; + } + + RefPtr<DataSourceSurface> input; + InputMap::iterator it = inputs.find(srcRect); + if (it == inputs.end()) { + input = GetInputDataSourceSurface(IN_TILE_IN, srcRect); + inputs[srcRect] = input; + } else { + input = it->second; + } + if (!input) { + return nullptr; + } + if (!target) { + // We delay creating the target until now because we want to use the + // same format as our input filter, and we do not actually know the + // input format before we call GetInputDataSourceSurface. + target = + Factory::CreateDataSourceSurface(aRect.Size(), input->GetFormat()); + if (MOZ2D_WARN_IF(!target)) { + return nullptr; + } + } + + if (input->GetFormat() != target->GetFormat()) { + // Different rectangles of the input can have different formats. If + // that happens, just convert everything to B8G8R8A8. + target = FilterProcessing::ConvertToB8G8R8A8(target); + input = FilterProcessing::ConvertToB8G8R8A8(input); + if (MOZ2D_WARN_IF(!target) || MOZ2D_WARN_IF(!input)) { + return nullptr; + } + } + + CopyRect(input, target, srcRect - srcRect.TopLeft(), + destRect.TopLeft() - aRect.TopLeft()); + } + } + + return target.forget(); +} + +void FilterNodeTileSoftware::RequestFromInputsForRect(const IntRect& aRect) { + // Do not request anything. + // Source rects for the tile filter can be discontinuous with large gaps + // between them. Requesting those from our input filter might cause it to + // render the whole bounding box of all of them, which would be wasteful. +} + +IntRect FilterNodeTileSoftware::GetOutputRectInRect(const IntRect& aRect) { + return aRect; +} + +FilterNodeComponentTransferSoftware::FilterNodeComponentTransferSoftware() + : mDisableR(true), mDisableG(true), mDisableB(true), mDisableA(true) {} + +void FilterNodeComponentTransferSoftware::SetAttribute(uint32_t aIndex, + bool aDisable) { + switch (aIndex) { + case ATT_TRANSFER_DISABLE_R: + mDisableR = aDisable; + break; + case ATT_TRANSFER_DISABLE_G: + mDisableG = aDisable; + break; + case ATT_TRANSFER_DISABLE_B: + mDisableB = aDisable; + break; + case ATT_TRANSFER_DISABLE_A: + mDisableA = aDisable; + break; + default: + MOZ_CRASH("GFX: FilterNodeComponentTransferSoftware::SetAttribute"); + } + Invalidate(); +} + +void FilterNodeComponentTransferSoftware::GenerateLookupTable( + ptrdiff_t aComponent, uint8_t aTables[4][256], bool aDisabled) { + if (aDisabled) { + for (int32_t i = 0; i < 256; ++i) { + aTables[aComponent][i] = i; + } + } else { + FillLookupTable(aComponent, aTables[aComponent]); + } +} + +template <uint32_t BytesPerPixel> +static void TransferComponents( + DataSourceSurface* aInput, DataSourceSurface* aTarget, + const uint8_t aLookupTables[BytesPerPixel][256]) { + MOZ_ASSERT(aInput->GetFormat() == aTarget->GetFormat(), "different formats"); + IntSize size = aInput->GetSize(); + + DataSourceSurface::ScopedMap sourceMap(aInput, DataSourceSurface::READ); + DataSourceSurface::ScopedMap targetMap(aTarget, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!sourceMap.IsMapped() || !targetMap.IsMapped())) { + return; + } + + uint8_t* sourceData = sourceMap.GetData(); + int32_t sourceStride = sourceMap.GetStride(); + uint8_t* targetData = targetMap.GetData(); + int32_t targetStride = targetMap.GetStride(); + + MOZ_ASSERT(sourceStride <= targetStride, "target smaller than source"); + + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.width; x++) { + uint32_t sourceIndex = y * sourceStride + x * BytesPerPixel; + uint32_t targetIndex = y * targetStride + x * BytesPerPixel; + for (uint32_t i = 0; i < BytesPerPixel; i++) { + targetData[targetIndex + i] = + aLookupTables[i][sourceData[sourceIndex + i]]; + } + } + + // Zero padding to keep valgrind happy. + PodZero(&targetData[y * targetStride + size.width * BytesPerPixel], + targetStride - size.width * BytesPerPixel); + } +} + +static bool IsAllZero(const uint8_t aLookupTable[256]) { + for (int32_t i = 0; i < 256; i++) { + if (aLookupTable[i] != 0) { + return false; + } + } + return true; +} + +already_AddRefed<DataSourceSurface> FilterNodeComponentTransferSoftware::Render( + const IntRect& aRect) { + if (mDisableR && mDisableG && mDisableB && mDisableA) { + return GetInputDataSourceSurface(IN_TRANSFER_IN, aRect); + } + + uint8_t lookupTables[4][256]; + GenerateLookupTable(B8G8R8A8_COMPONENT_BYTEOFFSET_R, lookupTables, mDisableR); + GenerateLookupTable(B8G8R8A8_COMPONENT_BYTEOFFSET_G, lookupTables, mDisableG); + GenerateLookupTable(B8G8R8A8_COMPONENT_BYTEOFFSET_B, lookupTables, mDisableB); + GenerateLookupTable(B8G8R8A8_COMPONENT_BYTEOFFSET_A, lookupTables, mDisableA); + + bool needColorChannels = + lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_R][0] != 0 || + lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_G][0] != 0 || + lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_B][0] != 0; + + FormatHint pref = needColorChannels ? NEED_COLOR_CHANNELS : CAN_HANDLE_A8; + + RefPtr<DataSourceSurface> input = + GetInputDataSourceSurface(IN_TRANSFER_IN, aRect, pref); + if (!input) { + return nullptr; + } + + if (input->GetFormat() == SurfaceFormat::B8G8R8A8 && !needColorChannels) { + bool colorChannelsBecomeBlack = + IsAllZero(lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_R]) && + IsAllZero(lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_G]) && + IsAllZero(lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_B]); + + if (colorChannelsBecomeBlack) { + input = FilterProcessing::ExtractAlpha(input); + } + } + + SurfaceFormat format = input->GetFormat(); + if (format == SurfaceFormat::A8 && mDisableA) { + return input.forget(); + } + + RefPtr<DataSourceSurface> target = + Factory::CreateDataSourceSurface(aRect.Size(), format); + if (MOZ2D_WARN_IF(!target)) { + return nullptr; + } + + if (format == SurfaceFormat::A8) { + TransferComponents<1>(input, target, + &lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_A]); + } else { + TransferComponents<4>(input, target, lookupTables); + } + + return target.forget(); +} + +void FilterNodeComponentTransferSoftware::RequestFromInputsForRect( + const IntRect& aRect) { + RequestInputRect(IN_TRANSFER_IN, aRect); +} + +IntRect FilterNodeComponentTransferSoftware::MapRectToSource( + const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { + return MapInputRectToSource(IN_TRANSFER_IN, aRect, aMax, aSourceNode); +} + +IntRect FilterNodeComponentTransferSoftware::GetOutputRectInRect( + const IntRect& aRect) { + if (mDisableA) { + return GetInputRectInRect(IN_TRANSFER_IN, aRect); + } + return aRect; +} + +int32_t FilterNodeComponentTransferSoftware::InputIndex( + uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_TRANSFER_IN: + return 0; + default: + return -1; + } +} + +void FilterNodeTableTransferSoftware::SetAttribute(uint32_t aIndex, + const Float* aFloat, + uint32_t aSize) { + std::vector<Float> table(aFloat, aFloat + aSize); + switch (aIndex) { + case ATT_TABLE_TRANSFER_TABLE_R: + mTableR = table; + break; + case ATT_TABLE_TRANSFER_TABLE_G: + mTableG = table; + break; + case ATT_TABLE_TRANSFER_TABLE_B: + mTableB = table; + break; + case ATT_TABLE_TRANSFER_TABLE_A: + mTableA = table; + break; + default: + MOZ_CRASH("GFX: FilterNodeTableTransferSoftware::SetAttribute"); + } + Invalidate(); +} + +void FilterNodeTableTransferSoftware::FillLookupTable(ptrdiff_t aComponent, + uint8_t aTable[256]) { + switch (aComponent) { + case B8G8R8A8_COMPONENT_BYTEOFFSET_R: + FillLookupTableImpl(mTableR, aTable); + break; + case B8G8R8A8_COMPONENT_BYTEOFFSET_G: + FillLookupTableImpl(mTableG, aTable); + break; + case B8G8R8A8_COMPONENT_BYTEOFFSET_B: + FillLookupTableImpl(mTableB, aTable); + break; + case B8G8R8A8_COMPONENT_BYTEOFFSET_A: + FillLookupTableImpl(mTableA, aTable); + break; + default: + MOZ_ASSERT(false, "unknown component"); + break; + } +} + +void FilterNodeTableTransferSoftware::FillLookupTableImpl( + std::vector<Float>& aTableValues, uint8_t aTable[256]) { + uint32_t tvLength = aTableValues.size(); + if (tvLength < 2) { + return; + } + + for (size_t i = 0; i < 256; i++) { + uint32_t k = (i * (tvLength - 1)) / 255; + Float v1 = aTableValues[k]; + Float v2 = aTableValues[std::min(k + 1, tvLength - 1)]; + int32_t val = int32_t(255 * (v1 + (i / 255.0f - k / float(tvLength - 1)) * + (tvLength - 1) * (v2 - v1))); + val = std::min(255, val); + val = std::max(0, val); + aTable[i] = val; + } +} + +void FilterNodeDiscreteTransferSoftware::SetAttribute(uint32_t aIndex, + const Float* aFloat, + uint32_t aSize) { + std::vector<Float> discrete(aFloat, aFloat + aSize); + switch (aIndex) { + case ATT_DISCRETE_TRANSFER_TABLE_R: + mTableR = discrete; + break; + case ATT_DISCRETE_TRANSFER_TABLE_G: + mTableG = discrete; + break; + case ATT_DISCRETE_TRANSFER_TABLE_B: + mTableB = discrete; + break; + case ATT_DISCRETE_TRANSFER_TABLE_A: + mTableA = discrete; + break; + default: + MOZ_CRASH("GFX: FilterNodeDiscreteTransferSoftware::SetAttribute"); + } + Invalidate(); +} + +void FilterNodeDiscreteTransferSoftware::FillLookupTable(ptrdiff_t aComponent, + uint8_t aTable[256]) { + switch (aComponent) { + case B8G8R8A8_COMPONENT_BYTEOFFSET_R: + FillLookupTableImpl(mTableR, aTable); + break; + case B8G8R8A8_COMPONENT_BYTEOFFSET_G: + FillLookupTableImpl(mTableG, aTable); + break; + case B8G8R8A8_COMPONENT_BYTEOFFSET_B: + FillLookupTableImpl(mTableB, aTable); + break; + case B8G8R8A8_COMPONENT_BYTEOFFSET_A: + FillLookupTableImpl(mTableA, aTable); + break; + default: + MOZ_ASSERT(false, "unknown component"); + break; + } +} + +void FilterNodeDiscreteTransferSoftware::FillLookupTableImpl( + std::vector<Float>& aTableValues, uint8_t aTable[256]) { + uint32_t tvLength = aTableValues.size(); + if (tvLength < 1) { + return; + } + + for (size_t i = 0; i < 256; i++) { + uint32_t k = (i * tvLength) / 255; + k = std::min(k, tvLength - 1); + Float v = aTableValues[k]; + int32_t val = NS_lround(255 * v); + val = std::min(255, val); + val = std::max(0, val); + aTable[i] = val; + } +} + +FilterNodeLinearTransferSoftware::FilterNodeLinearTransferSoftware() + : mSlopeR(0), + mSlopeG(0), + mSlopeB(0), + mSlopeA(0), + mInterceptR(0), + mInterceptG(0), + mInterceptB(0), + mInterceptA(0) {} + +void FilterNodeLinearTransferSoftware::SetAttribute(uint32_t aIndex, + Float aValue) { + switch (aIndex) { + case ATT_LINEAR_TRANSFER_SLOPE_R: + mSlopeR = aValue; + break; + case ATT_LINEAR_TRANSFER_INTERCEPT_R: + mInterceptR = aValue; + break; + case ATT_LINEAR_TRANSFER_SLOPE_G: + mSlopeG = aValue; + break; + case ATT_LINEAR_TRANSFER_INTERCEPT_G: + mInterceptG = aValue; + break; + case ATT_LINEAR_TRANSFER_SLOPE_B: + mSlopeB = aValue; + break; + case ATT_LINEAR_TRANSFER_INTERCEPT_B: + mInterceptB = aValue; + break; + case ATT_LINEAR_TRANSFER_SLOPE_A: + mSlopeA = aValue; + break; + case ATT_LINEAR_TRANSFER_INTERCEPT_A: + mInterceptA = aValue; + break; + default: + MOZ_CRASH("GFX: FilterNodeLinearTransferSoftware::SetAttribute"); + } + Invalidate(); +} + +void FilterNodeLinearTransferSoftware::FillLookupTable(ptrdiff_t aComponent, + uint8_t aTable[256]) { + switch (aComponent) { + case B8G8R8A8_COMPONENT_BYTEOFFSET_R: + FillLookupTableImpl(mSlopeR, mInterceptR, aTable); + break; + case B8G8R8A8_COMPONENT_BYTEOFFSET_G: + FillLookupTableImpl(mSlopeG, mInterceptG, aTable); + break; + case B8G8R8A8_COMPONENT_BYTEOFFSET_B: + FillLookupTableImpl(mSlopeB, mInterceptB, aTable); + break; + case B8G8R8A8_COMPONENT_BYTEOFFSET_A: + FillLookupTableImpl(mSlopeA, mInterceptA, aTable); + break; + default: + MOZ_ASSERT(false, "unknown component"); + break; + } +} + +void FilterNodeLinearTransferSoftware::FillLookupTableImpl( + Float aSlope, Float aIntercept, uint8_t aTable[256]) { + for (size_t i = 0; i < 256; i++) { + int32_t val = NS_lround(aSlope * i + 255 * aIntercept); + val = std::min(255, val); + val = std::max(0, val); + aTable[i] = val; + } +} + +FilterNodeGammaTransferSoftware::FilterNodeGammaTransferSoftware() + : mAmplitudeR(0), + mAmplitudeG(0), + mAmplitudeB(0), + mAmplitudeA(0), + mExponentR(0), + mExponentG(0), + mExponentB(0), + mExponentA(0), + mOffsetR(0.0), + mOffsetG(0.0), + mOffsetB(0.0), + mOffsetA(0.0) {} + +void FilterNodeGammaTransferSoftware::SetAttribute(uint32_t aIndex, + Float aValue) { + switch (aIndex) { + case ATT_GAMMA_TRANSFER_AMPLITUDE_R: + mAmplitudeR = aValue; + break; + case ATT_GAMMA_TRANSFER_EXPONENT_R: + mExponentR = aValue; + break; + case ATT_GAMMA_TRANSFER_OFFSET_R: + mOffsetR = aValue; + break; + case ATT_GAMMA_TRANSFER_AMPLITUDE_G: + mAmplitudeG = aValue; + break; + case ATT_GAMMA_TRANSFER_EXPONENT_G: + mExponentG = aValue; + break; + case ATT_GAMMA_TRANSFER_OFFSET_G: + mOffsetG = aValue; + break; + case ATT_GAMMA_TRANSFER_AMPLITUDE_B: + mAmplitudeB = aValue; + break; + case ATT_GAMMA_TRANSFER_EXPONENT_B: + mExponentB = aValue; + break; + case ATT_GAMMA_TRANSFER_OFFSET_B: + mOffsetB = aValue; + break; + case ATT_GAMMA_TRANSFER_AMPLITUDE_A: + mAmplitudeA = aValue; + break; + case ATT_GAMMA_TRANSFER_EXPONENT_A: + mExponentA = aValue; + break; + case ATT_GAMMA_TRANSFER_OFFSET_A: + mOffsetA = aValue; + break; + default: + MOZ_CRASH("GFX: FilterNodeGammaTransferSoftware::SetAttribute"); + } + Invalidate(); +} + +void FilterNodeGammaTransferSoftware::FillLookupTable(ptrdiff_t aComponent, + uint8_t aTable[256]) { + switch (aComponent) { + case B8G8R8A8_COMPONENT_BYTEOFFSET_R: + FillLookupTableImpl(mAmplitudeR, mExponentR, mOffsetR, aTable); + break; + case B8G8R8A8_COMPONENT_BYTEOFFSET_G: + FillLookupTableImpl(mAmplitudeG, mExponentG, mOffsetG, aTable); + break; + case B8G8R8A8_COMPONENT_BYTEOFFSET_B: + FillLookupTableImpl(mAmplitudeB, mExponentB, mOffsetB, aTable); + break; + case B8G8R8A8_COMPONENT_BYTEOFFSET_A: + FillLookupTableImpl(mAmplitudeA, mExponentA, mOffsetA, aTable); + break; + default: + MOZ_ASSERT(false, "unknown component"); + break; + } +} + +void FilterNodeGammaTransferSoftware::FillLookupTableImpl(Float aAmplitude, + Float aExponent, + Float aOffset, + uint8_t aTable[256]) { + for (size_t i = 0; i < 256; i++) { + int32_t val = + NS_lround(255 * (aAmplitude * pow(i / 255.0f, aExponent) + aOffset)); + val = std::min(255, val); + val = std::max(0, val); + aTable[i] = val; + } +} + +FilterNodeConvolveMatrixSoftware::FilterNodeConvolveMatrixSoftware() + : mDivisor(0), + mBias(0), + mEdgeMode(EDGE_MODE_DUPLICATE), + mPreserveAlpha(false) {} + +int32_t FilterNodeConvolveMatrixSoftware::InputIndex(uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_CONVOLVE_MATRIX_IN: + return 0; + default: + return -1; + } +} + +void FilterNodeConvolveMatrixSoftware::SetAttribute( + uint32_t aIndex, const IntSize& aKernelSize) { + MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_KERNEL_SIZE); + mKernelSize = aKernelSize; + Invalidate(); +} + +void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex, + const Float* aMatrix, + uint32_t aSize) { + MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_KERNEL_MATRIX); + mKernelMatrix = std::vector<Float>(aMatrix, aMatrix + aSize); + Invalidate(); +} + +void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex, + Float aValue) { + switch (aIndex) { + case ATT_CONVOLVE_MATRIX_DIVISOR: + mDivisor = aValue; + break; + case ATT_CONVOLVE_MATRIX_BIAS: + mBias = aValue; + break; + default: + MOZ_CRASH("GFX: FilterNodeConvolveMatrixSoftware::SetAttribute"); + } + Invalidate(); +} + +void FilterNodeConvolveMatrixSoftware::SetAttribute( + uint32_t aIndex, const Size& aKernelUnitLength) { + switch (aIndex) { + case ATT_CONVOLVE_MATRIX_KERNEL_UNIT_LENGTH: + mKernelUnitLength = aKernelUnitLength; + break; + default: + MOZ_CRASH("GFX: FilterNodeConvolveMatrixSoftware::SetAttribute"); + } + Invalidate(); +} + +void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex, + const IntPoint& aTarget) { + MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_TARGET); + mTarget = aTarget; + Invalidate(); +} + +void FilterNodeConvolveMatrixSoftware::SetAttribute( + uint32_t aIndex, const IntRect& aSourceRect) { + MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_SOURCE_RECT); + mSourceRect = aSourceRect; + Invalidate(); +} + +void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex, + uint32_t aEdgeMode) { + MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_EDGE_MODE); + mEdgeMode = static_cast<ConvolveMatrixEdgeMode>(aEdgeMode); + Invalidate(); +} + +void FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex, + bool aPreserveAlpha) { + MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_PRESERVE_ALPHA); + mPreserveAlpha = aPreserveAlpha; + Invalidate(); +} + +#ifdef DEBUG +static inline void DebugOnlyCheckColorSamplingAccess( + const uint8_t* aSampleAddress, const uint8_t* aBoundsBegin, + const uint8_t* aBoundsEnd) { + MOZ_ASSERT(aSampleAddress >= aBoundsBegin, "accessing before start"); + MOZ_ASSERT(aSampleAddress < aBoundsEnd, "accessing after end"); +} +#else +# define DebugOnlyCheckColorSamplingAccess(address, boundsBegin, boundsEnd) +#endif + +static inline uint8_t ColorComponentAtPoint(const uint8_t* aData, + ptrdiff_t aStride, + const uint8_t* aBoundsBegin, + const uint8_t* aBoundsEnd, + int32_t x, int32_t y, ptrdiff_t bpp, + ptrdiff_t c) { + DebugOnlyCheckColorSamplingAccess(&aData[y * aStride + bpp * x + c], + aBoundsBegin, aBoundsEnd); + return aData[y * aStride + bpp * x + c]; +} + +static inline int32_t ColorAtPoint(const uint8_t* aData, ptrdiff_t aStride, + const uint8_t* aBoundsBegin, + const uint8_t* aBoundsEnd, int32_t x, + int32_t y) { + DebugOnlyCheckColorSamplingAccess(aData + y * aStride + 4 * x, aBoundsBegin, + aBoundsEnd); + return *(uint32_t*)(aData + y * aStride + 4 * x); +} + +// Accepts fractional x & y and does bilinear interpolation. +// Only call this if the pixel (floor(x)+1, floor(y)+1) is accessible. +static inline uint8_t ColorComponentAtPoint( + const uint8_t* aData, ptrdiff_t aStride, const uint8_t* aBoundsBegin, + const uint8_t* aBoundsEnd, Float x, Float y, ptrdiff_t bpp, ptrdiff_t c) { + const uint32_t f = 256; + const int32_t lx = floor(x); + const int32_t ly = floor(y); + const int32_t tux = uint32_t((x - lx) * f); + const int32_t tlx = f - tux; + const int32_t tuy = uint32_t((y - ly) * f); + const int32_t tly = f - tuy; + const uint8_t& cll = ColorComponentAtPoint(aData, aStride, aBoundsBegin, + aBoundsEnd, lx, ly, bpp, c); + const uint8_t& cul = ColorComponentAtPoint(aData, aStride, aBoundsBegin, + aBoundsEnd, lx + 1, ly, bpp, c); + const uint8_t& clu = ColorComponentAtPoint(aData, aStride, aBoundsBegin, + aBoundsEnd, lx, ly + 1, bpp, c); + const uint8_t& cuu = ColorComponentAtPoint( + aData, aStride, aBoundsBegin, aBoundsEnd, lx + 1, ly + 1, bpp, c); + return ((cll * tlx + cul * tux) * tly + (clu * tlx + cuu * tux) * tuy + + f * f / 2) / + (f * f); +} + +static int32_t ClampToNonZero(int32_t a) { return a * (a >= 0); } + +template <typename CoordType> +static void ConvolvePixel(const uint8_t* aSourceData, uint8_t* aTargetData, + int32_t aWidth, int32_t aHeight, + int32_t aSourceStride, int32_t aTargetStride, + const uint8_t* aSourceBegin, + const uint8_t* aSourceEnd, int32_t aX, int32_t aY, + const int32_t* aKernel, int32_t aBias, int32_t shiftL, + int32_t shiftR, bool aPreserveAlpha, int32_t aOrderX, + int32_t aOrderY, int32_t aTargetX, int32_t aTargetY, + CoordType aKernelUnitLengthX, + CoordType aKernelUnitLengthY) { + int32_t sum[4] = {0, 0, 0, 0}; + int32_t offsets[4] = { + B8G8R8A8_COMPONENT_BYTEOFFSET_R, B8G8R8A8_COMPONENT_BYTEOFFSET_G, + B8G8R8A8_COMPONENT_BYTEOFFSET_B, B8G8R8A8_COMPONENT_BYTEOFFSET_A}; + int32_t channels = aPreserveAlpha ? 3 : 4; + int32_t roundingAddition = shiftL == 0 ? 0 : 1 << (shiftL - 1); + + for (int32_t y = 0; y < aOrderY; y++) { + CoordType sampleY = aY + (y - aTargetY) * aKernelUnitLengthY; + for (int32_t x = 0; x < aOrderX; x++) { + CoordType sampleX = aX + (x - aTargetX) * aKernelUnitLengthX; + for (int32_t i = 0; i < channels; i++) { + sum[i] += + aKernel[aOrderX * y + x] * + ColorComponentAtPoint(aSourceData, aSourceStride, aSourceBegin, + aSourceEnd, sampleX, sampleY, 4, offsets[i]); + } + } + } + for (int32_t i = 0; i < channels; i++) { + int32_t clamped = + umin(ClampToNonZero(sum[i] + aBias), 255 << shiftL >> shiftR); + aTargetData[aY * aTargetStride + 4 * aX + offsets[i]] = + (clamped + roundingAddition) << shiftR >> shiftL; + } + if (aPreserveAlpha) { + aTargetData[aY * aTargetStride + 4 * aX + B8G8R8A8_COMPONENT_BYTEOFFSET_A] = + aSourceData[aY * aSourceStride + 4 * aX + + B8G8R8A8_COMPONENT_BYTEOFFSET_A]; + } +} + +already_AddRefed<DataSourceSurface> FilterNodeConvolveMatrixSoftware::Render( + const IntRect& aRect) { + if (mKernelUnitLength.width == floor(mKernelUnitLength.width) && + mKernelUnitLength.height == floor(mKernelUnitLength.height)) { + return DoRender(aRect, (int32_t)mKernelUnitLength.width, + (int32_t)mKernelUnitLength.height); + } + return DoRender(aRect, mKernelUnitLength.width, mKernelUnitLength.height); +} + +static std::vector<Float> ReversedVector(const std::vector<Float>& aVector) { + size_t length = aVector.size(); + std::vector<Float> result(length, 0); + for (size_t i = 0; i < length; i++) { + result[length - 1 - i] = aVector[i]; + } + return result; +} + +static std::vector<Float> ScaledVector(const std::vector<Float>& aVector, + Float aDivisor) { + size_t length = aVector.size(); + std::vector<Float> result(length, 0); + for (size_t i = 0; i < length; i++) { + result[i] = aVector[i] / aDivisor; + } + return result; +} + +static Float MaxVectorSum(const std::vector<Float>& aVector) { + Float sum = 0; + size_t length = aVector.size(); + for (size_t i = 0; i < length; i++) { + if (aVector[i] > 0) { + sum += aVector[i]; + } + } + return sum; +} + +// Returns shiftL and shiftR in such a way that +// a << shiftL >> shiftR is roughly a * aFloat. +static void TranslateDoubleToShifts(double aDouble, int32_t& aShiftL, + int32_t& aShiftR) { + aShiftL = 0; + aShiftR = 0; + if (aDouble <= 0) { + MOZ_CRASH("GFX: TranslateDoubleToShifts"); + } + if (aDouble < 1) { + while (1 << (aShiftR + 1) < 1 / aDouble) { + aShiftR++; + } + } else { + while (1 << (aShiftL + 1) < aDouble) { + aShiftL++; + } + } +} + +template <typename CoordType> +already_AddRefed<DataSourceSurface> FilterNodeConvolveMatrixSoftware::DoRender( + const IntRect& aRect, CoordType aKernelUnitLengthX, + CoordType aKernelUnitLengthY) { + if (mKernelSize.width <= 0 || mKernelSize.height <= 0 || + mKernelMatrix.size() != + uint32_t(mKernelSize.width * mKernelSize.height) || + !IntRect(IntPoint(0, 0), mKernelSize).Contains(mTarget) || + mDivisor == 0) { + return Factory::CreateDataSourceSurface(aRect.Size(), + SurfaceFormat::B8G8R8A8, true); + } + + IntRect srcRect = InflatedSourceRect(aRect); + + // Inflate the source rect by another pixel because the bilinear filtering in + // ColorComponentAtPoint may want to access the margins. + srcRect.Inflate(1); + + RefPtr<DataSourceSurface> input = + GetInputDataSourceSurface(IN_CONVOLVE_MATRIX_IN, srcRect, + NEED_COLOR_CHANNELS, mEdgeMode, &mSourceRect); + + if (!input) { + return nullptr; + } + + RefPtr<DataSourceSurface> target = Factory::CreateDataSourceSurface( + aRect.Size(), SurfaceFormat::B8G8R8A8, true); + if (MOZ2D_WARN_IF(!target)) { + return nullptr; + } + + IntPoint offset = aRect.TopLeft() - srcRect.TopLeft(); + + DataSourceSurface::ScopedMap sourceMap(input, DataSourceSurface::READ); + DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!sourceMap.IsMapped() || !targetMap.IsMapped())) { + return nullptr; + } + + uint8_t* sourceData = + DataAtOffset(input, sourceMap.GetMappedSurface(), offset); + int32_t sourceStride = sourceMap.GetStride(); + uint8_t* sourceBegin = sourceMap.GetData(); + uint8_t* sourceEnd = sourceBegin + sourceStride * input->GetSize().height; + uint8_t* targetData = targetMap.GetData(); + int32_t targetStride = targetMap.GetStride(); + + // Why exactly are we reversing the kernel? + std::vector<Float> kernel = ReversedVector(mKernelMatrix); + kernel = ScaledVector(kernel, mDivisor); + Float maxResultAbs = std::max(MaxVectorSum(kernel) + mBias, + MaxVectorSum(ScaledVector(kernel, -1)) - mBias); + maxResultAbs = std::max(maxResultAbs, 1.0f); + + double idealFactor = INT32_MAX / 2.0 / maxResultAbs / 255.0 * 0.999; + MOZ_ASSERT(255.0 * maxResultAbs * idealFactor <= INT32_MAX / 2.0, + "badly chosen float-to-int scale"); + int32_t shiftL, shiftR; + TranslateDoubleToShifts(idealFactor, shiftL, shiftR); + double factorFromShifts = Float(1 << shiftL) / Float(1 << shiftR); + MOZ_ASSERT(255.0 * maxResultAbs * factorFromShifts <= INT32_MAX / 2.0, + "badly chosen float-to-int scale"); + + int32_t* intKernel = new int32_t[kernel.size()]; + for (size_t i = 0; i < kernel.size(); i++) { + intKernel[i] = NS_lround(kernel[i] * factorFromShifts); + } + int32_t bias = NS_lround(mBias * 255 * factorFromShifts); + + for (int32_t y = 0; y < aRect.Height(); y++) { + for (int32_t x = 0; x < aRect.Width(); x++) { + ConvolvePixel(sourceData, targetData, aRect.Width(), aRect.Height(), + sourceStride, targetStride, sourceBegin, sourceEnd, x, y, + intKernel, bias, shiftL, shiftR, mPreserveAlpha, + mKernelSize.width, mKernelSize.height, mTarget.x, mTarget.y, + aKernelUnitLengthX, aKernelUnitLengthY); + } + } + delete[] intKernel; + + return target.forget(); +} + +void FilterNodeConvolveMatrixSoftware::RequestFromInputsForRect( + const IntRect& aRect) { + RequestInputRect(IN_CONVOLVE_MATRIX_IN, InflatedSourceRect(aRect)); +} + +IntRect FilterNodeConvolveMatrixSoftware::MapRectToSource( + const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { + return MapInputRectToSource(IN_CONVOLVE_MATRIX_IN, InflatedSourceRect(aRect), + aMax, aSourceNode); +} + +IntRect FilterNodeConvolveMatrixSoftware::InflatedSourceRect( + const IntRect& aDestRect) { + if (aDestRect.IsEmpty()) { + return IntRect(); + } + + IntMargin margin; + margin.left = static_cast<int32_t>(ceil(mTarget.x * mKernelUnitLength.width)); + margin.top = static_cast<int32_t>(ceil(mTarget.y * mKernelUnitLength.height)); + margin.right = static_cast<int32_t>( + ceil((mKernelSize.width - mTarget.x - 1) * mKernelUnitLength.width)); + margin.bottom = static_cast<int32_t>( + 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 = static_cast<int32_t>( + ceil((mKernelSize.width - mTarget.x - 1) * mKernelUnitLength.width)); + margin.top = static_cast<int32_t>( + ceil((mKernelSize.height - mTarget.y - 1) * mKernelUnitLength.height)); + margin.right = + static_cast<int32_t>(ceil(mTarget.x * mKernelUnitLength.width)); + margin.bottom = + static_cast<int32_t>(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_CONVOLVE_MATRIX_IN, srcRequest); + return InflatedDestRect(srcOutput).Intersect(aRect); +} + +FilterNodeDisplacementMapSoftware::FilterNodeDisplacementMapSoftware() + : mScale(0.0f), mChannelX(COLOR_CHANNEL_R), mChannelY(COLOR_CHANNEL_G) {} + +int32_t FilterNodeDisplacementMapSoftware::InputIndex( + uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_DISPLACEMENT_MAP_IN: + return 0; + case IN_DISPLACEMENT_MAP_IN2: + return 1; + default: + return -1; + } +} + +void FilterNodeDisplacementMapSoftware::SetAttribute(uint32_t aIndex, + Float aScale) { + MOZ_ASSERT(aIndex == ATT_DISPLACEMENT_MAP_SCALE); + mScale = aScale; + Invalidate(); +} + +void FilterNodeDisplacementMapSoftware::SetAttribute(uint32_t aIndex, + uint32_t aValue) { + switch (aIndex) { + case ATT_DISPLACEMENT_MAP_X_CHANNEL: + mChannelX = static_cast<ColorChannel>(aValue); + break; + case ATT_DISPLACEMENT_MAP_Y_CHANNEL: + mChannelY = static_cast<ColorChannel>(aValue); + break; + default: + MOZ_CRASH("GFX: FilterNodeDisplacementMapSoftware::SetAttribute"); + } + Invalidate(); +} + +already_AddRefed<DataSourceSurface> FilterNodeDisplacementMapSoftware::Render( + const IntRect& aRect) { + IntRect srcRect = InflatedSourceOrDestRect(aRect); + RefPtr<DataSourceSurface> input = GetInputDataSourceSurface( + IN_DISPLACEMENT_MAP_IN, srcRect, NEED_COLOR_CHANNELS); + RefPtr<DataSourceSurface> map = GetInputDataSourceSurface( + IN_DISPLACEMENT_MAP_IN2, aRect, NEED_COLOR_CHANNELS); + RefPtr<DataSourceSurface> target = + Factory::CreateDataSourceSurface(aRect.Size(), SurfaceFormat::B8G8R8A8); + if (MOZ2D_WARN_IF(!(input && map && target))) { + return nullptr; + } + + IntPoint offset = aRect.TopLeft() - srcRect.TopLeft(); + + DataSourceSurface::ScopedMap inputMap(input, DataSourceSurface::READ); + DataSourceSurface::ScopedMap mapMap(map, DataSourceSurface::READ); + DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!(inputMap.IsMapped() && mapMap.IsMapped() && + targetMap.IsMapped()))) { + return nullptr; + } + + uint8_t* sourceData = + DataAtOffset(input, inputMap.GetMappedSurface(), offset); + int32_t sourceStride = inputMap.GetStride(); + uint8_t* sourceBegin = inputMap.GetData(); + uint8_t* sourceEnd = sourceBegin + sourceStride * input->GetSize().height; + uint8_t* mapData = mapMap.GetData(); + int32_t mapStride = mapMap.GetStride(); + uint8_t* targetData = targetMap.GetData(); + int32_t targetStride = targetMap.GetStride(); + + static const ptrdiff_t channelMap[4] = { + B8G8R8A8_COMPONENT_BYTEOFFSET_R, B8G8R8A8_COMPONENT_BYTEOFFSET_G, + B8G8R8A8_COMPONENT_BYTEOFFSET_B, B8G8R8A8_COMPONENT_BYTEOFFSET_A}; + uint16_t xChannel = channelMap[mChannelX]; + uint16_t yChannel = channelMap[mChannelY]; + + float scaleOver255 = mScale / 255.0f; + float scaleAdjustment = -0.5f * mScale; + + for (int32_t y = 0; y < aRect.Height(); y++) { + for (int32_t x = 0; x < aRect.Width(); x++) { + uint32_t mapIndex = y * mapStride + 4 * x; + uint32_t targIndex = y * targetStride + 4 * x; + int32_t sourceX = + x + scaleOver255 * mapData[mapIndex + xChannel] + scaleAdjustment; + int32_t sourceY = + y + scaleOver255 * mapData[mapIndex + yChannel] + scaleAdjustment; + *(uint32_t*)(targetData + targIndex) = ColorAtPoint( + sourceData, sourceStride, sourceBegin, sourceEnd, sourceX, sourceY); + } + + // Keep valgrind happy. + PodZero(&targetData[y * targetStride + 4 * aRect.Width()], + targetStride - 4 * aRect.Width()); + } + + return target.forget(); +} + +void FilterNodeDisplacementMapSoftware::RequestFromInputsForRect( + const IntRect& aRect) { + RequestInputRect(IN_DISPLACEMENT_MAP_IN, InflatedSourceOrDestRect(aRect)); + RequestInputRect(IN_DISPLACEMENT_MAP_IN2, aRect); +} + +IntRect FilterNodeDisplacementMapSoftware::MapRectToSource( + const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { + IntRect result = + MapInputRectToSource(IN_DISPLACEMENT_MAP_IN, + InflatedSourceOrDestRect(aRect), aMax, aSourceNode); + result.OrWith( + MapInputRectToSource(IN_DISPLACEMENT_MAP_IN2, aRect, aMax, aSourceNode)); + return result; +} + +IntRect FilterNodeDisplacementMapSoftware::InflatedSourceOrDestRect( + const IntRect& aDestOrSourceRect) { + IntRect sourceOrDestRect = aDestOrSourceRect; + sourceOrDestRect.Inflate(ceil(fabs(mScale) / 2)); + return sourceOrDestRect; +} + +IntRect FilterNodeDisplacementMapSoftware::GetOutputRectInRect( + const IntRect& aRect) { + IntRect srcRequest = InflatedSourceOrDestRect(aRect); + IntRect srcOutput = GetInputRectInRect(IN_DISPLACEMENT_MAP_IN, srcRequest); + return InflatedSourceOrDestRect(srcOutput).Intersect(aRect); +} + +FilterNodeTurbulenceSoftware::FilterNodeTurbulenceSoftware() + : mNumOctaves(0), + mSeed(0), + mStitchable(false), + mType(TURBULENCE_TYPE_TURBULENCE) {} + +int32_t FilterNodeTurbulenceSoftware::InputIndex(uint32_t aInputEnumIndex) { + return -1; +} + +void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex, + const Size& aBaseFrequency) { + switch (aIndex) { + case ATT_TURBULENCE_BASE_FREQUENCY: + mBaseFrequency = aBaseFrequency; + break; + default: + MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute"); + break; + } + Invalidate(); +} + +void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex, + const IntRect& aRect) { + switch (aIndex) { + case ATT_TURBULENCE_RECT: + mRenderRect = aRect; + break; + default: + MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute"); + break; + } + Invalidate(); +} + +void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex, + bool aStitchable) { + MOZ_ASSERT(aIndex == ATT_TURBULENCE_STITCHABLE); + mStitchable = aStitchable; + Invalidate(); +} + +void FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex, + uint32_t aValue) { + switch (aIndex) { + case ATT_TURBULENCE_NUM_OCTAVES: + mNumOctaves = aValue; + break; + case ATT_TURBULENCE_SEED: + mSeed = aValue; + break; + case ATT_TURBULENCE_TYPE: + mType = static_cast<TurbulenceType>(aValue); + break; + default: + MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute"); + break; + } + Invalidate(); +} + +already_AddRefed<DataSourceSurface> FilterNodeTurbulenceSoftware::Render( + const IntRect& aRect) { + return FilterProcessing::RenderTurbulence( + aRect.Size(), aRect.TopLeft(), mBaseFrequency, mSeed, mNumOctaves, mType, + mStitchable, Rect(mRenderRect)); +} + +IntRect FilterNodeTurbulenceSoftware::GetOutputRectInRect( + const IntRect& aRect) { + return aRect.Intersect(mRenderRect); +} + +IntRect FilterNodeTurbulenceSoftware::MapRectToSource(const IntRect& aRect, + const IntRect& aMax, + FilterNode* aSourceNode) { + return IntRect(); +} + +FilterNodeArithmeticCombineSoftware::FilterNodeArithmeticCombineSoftware() + : mK1(0), mK2(0), mK3(0), mK4(0) {} + +int32_t FilterNodeArithmeticCombineSoftware::InputIndex( + uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_ARITHMETIC_COMBINE_IN: + return 0; + case IN_ARITHMETIC_COMBINE_IN2: + return 1; + default: + return -1; + } +} + +void FilterNodeArithmeticCombineSoftware::SetAttribute(uint32_t aIndex, + const Float* aFloat, + uint32_t aSize) { + MOZ_ASSERT(aIndex == ATT_ARITHMETIC_COMBINE_COEFFICIENTS); + MOZ_RELEASE_ASSERT(aSize == 4); + + mK1 = aFloat[0]; + mK2 = aFloat[1]; + mK3 = aFloat[2]; + mK4 = aFloat[3]; + + Invalidate(); +} + +already_AddRefed<DataSourceSurface> FilterNodeArithmeticCombineSoftware::Render( + const IntRect& aRect) { + RefPtr<DataSourceSurface> input1 = GetInputDataSourceSurface( + IN_ARITHMETIC_COMBINE_IN, aRect, NEED_COLOR_CHANNELS); + RefPtr<DataSourceSurface> input2 = GetInputDataSourceSurface( + IN_ARITHMETIC_COMBINE_IN2, aRect, NEED_COLOR_CHANNELS); + if (!input1 && !input2) { + return nullptr; + } + + // If one input is null, treat it as transparent by adjusting the factors. + Float k1 = mK1, k2 = mK2, k3 = mK3, k4 = mK4; + if (!input1) { + k1 = 0.0f; + k2 = 0.0f; + input1 = input2; + } + + if (!input2) { + k1 = 0.0f; + k3 = 0.0f; + input2 = input1; + } + + return FilterProcessing::ApplyArithmeticCombine(input1, input2, k1, k2, k3, + k4); +} + +void FilterNodeArithmeticCombineSoftware::RequestFromInputsForRect( + const IntRect& aRect) { + RequestInputRect(IN_ARITHMETIC_COMBINE_IN, aRect); + RequestInputRect(IN_ARITHMETIC_COMBINE_IN2, aRect); +} + +IntRect FilterNodeArithmeticCombineSoftware::MapRectToSource( + const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { + IntRect result = + MapInputRectToSource(IN_ARITHMETIC_COMBINE_IN, aRect, aMax, aSourceNode); + result.OrWith(MapInputRectToSource(IN_ARITHMETIC_COMBINE_IN2, aRect, aMax, + aSourceNode)); + return result; +} + +IntRect FilterNodeArithmeticCombineSoftware::GetOutputRectInRect( + const IntRect& aRect) { + if (mK4 > 0.0f) { + return aRect; + } + IntRect rectFrom1 = + GetInputRectInRect(IN_ARITHMETIC_COMBINE_IN, aRect).Intersect(aRect); + IntRect rectFrom2 = + GetInputRectInRect(IN_ARITHMETIC_COMBINE_IN2, aRect).Intersect(aRect); + IntRect result; + if (mK1 > 0.0f) { + result = rectFrom1.Intersect(rectFrom2); + } + if (mK2 > 0.0f) { + result = result.Union(rectFrom1); + } + if (mK3 > 0.0f) { + result = result.Union(rectFrom2); + } + return result; +} + +FilterNodeCompositeSoftware::FilterNodeCompositeSoftware() + : mOperator(COMPOSITE_OPERATOR_OVER) {} + +int32_t FilterNodeCompositeSoftware::InputIndex(uint32_t aInputEnumIndex) { + return aInputEnumIndex - IN_COMPOSITE_IN_START; +} + +void FilterNodeCompositeSoftware::SetAttribute(uint32_t aIndex, + uint32_t aCompositeOperator) { + MOZ_ASSERT(aIndex == ATT_COMPOSITE_OPERATOR); + mOperator = static_cast<CompositeOperator>(aCompositeOperator); + Invalidate(); +} + +already_AddRefed<DataSourceSurface> FilterNodeCompositeSoftware::Render( + const IntRect& aRect) { + RefPtr<DataSourceSurface> start = GetInputDataSourceSurface( + IN_COMPOSITE_IN_START, aRect, NEED_COLOR_CHANNELS); + RefPtr<DataSourceSurface> dest = Factory::CreateDataSourceSurface( + aRect.Size(), SurfaceFormat::B8G8R8A8, true); + if (MOZ2D_WARN_IF(!dest)) { + return nullptr; + } + + if (start) { + CopyRect(start, dest, aRect - aRect.TopLeft(), IntPoint()); + } + + for (size_t inputIndex = 1; inputIndex < NumberOfSetInputs(); inputIndex++) { + RefPtr<DataSourceSurface> input = GetInputDataSourceSurface( + IN_COMPOSITE_IN_START + inputIndex, aRect, NEED_COLOR_CHANNELS); + if (input) { + FilterProcessing::ApplyComposition(input, dest, mOperator); + } else { + // We need to treat input as transparent. Depending on the composite + // operator, different things happen to dest. + switch (mOperator) { + case COMPOSITE_OPERATOR_OVER: + case COMPOSITE_OPERATOR_ATOP: + case COMPOSITE_OPERATOR_XOR: + case COMPOSITE_OPERATOR_LIGHTER: + // dest is unchanged. + break; + case COMPOSITE_OPERATOR_OUT: + // dest is now transparent, but it can become non-transparent again + // when compositing additional inputs. + ClearDataSourceSurface(dest); + break; + case COMPOSITE_OPERATOR_IN: + // Transparency always wins. We're completely transparent now and + // no additional input can get rid of that transparency. + return nullptr; + } + } + } + return dest.forget(); +} + +void FilterNodeCompositeSoftware::RequestFromInputsForRect( + const IntRect& aRect) { + for (size_t inputIndex = 0; inputIndex < NumberOfSetInputs(); inputIndex++) { + RequestInputRect(IN_COMPOSITE_IN_START + inputIndex, aRect); + } +} + +IntRect FilterNodeCompositeSoftware::MapRectToSource(const IntRect& aRect, + const IntRect& aMax, + FilterNode* aSourceNode) { + IntRect result; + for (size_t inputIndex = 0; inputIndex < NumberOfSetInputs(); inputIndex++) { + result.OrWith(MapInputRectToSource(IN_COMPOSITE_IN_START + inputIndex, + aRect, aMax, aSourceNode)); + } + return result; +} + +IntRect FilterNodeCompositeSoftware::GetOutputRectInRect(const IntRect& aRect) { + IntRect rect; + for (size_t inputIndex = 0; inputIndex < NumberOfSetInputs(); inputIndex++) { + IntRect inputRect = + GetInputRectInRect(IN_COMPOSITE_IN_START + inputIndex, aRect); + if (mOperator == COMPOSITE_OPERATOR_IN && inputIndex > 0) { + rect = rect.Intersect(inputRect); + } else { + rect = rect.Union(inputRect); + } + } + return rect; +} + +int32_t FilterNodeBlurXYSoftware::InputIndex(uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_GAUSSIAN_BLUR_IN: + return 0; + default: + return -1; + } +} + +already_AddRefed<DataSourceSurface> FilterNodeBlurXYSoftware::Render( + const IntRect& aRect) { + Size sigmaXY = StdDeviationXY(); + IntSize d = + AlphaBoxBlur::CalculateBlurRadius(Point(sigmaXY.width, sigmaXY.height)); + + if (d.width == 0 && d.height == 0) { + return GetInputDataSourceSurface(IN_GAUSSIAN_BLUR_IN, aRect); + } + + IntRect srcRect = InflatedSourceOrDestRect(aRect); + RefPtr<DataSourceSurface> input = + GetInputDataSourceSurface(IN_GAUSSIAN_BLUR_IN, srcRect); + if (!input) { + return nullptr; + } + + RefPtr<DataSourceSurface> target; + Rect r(0, 0, srcRect.Width(), srcRect.Height()); + + if (input->GetFormat() == SurfaceFormat::A8) { + target = + Factory::CreateDataSourceSurface(srcRect.Size(), SurfaceFormat::A8); + if (MOZ2D_WARN_IF(!target)) { + return nullptr; + } + CopyRect(input, target, IntRect(IntPoint(), input->GetSize()), IntPoint()); + + DataSourceSurface::ScopedMap targetMap(target, + DataSourceSurface::READ_WRITE); + if (MOZ2D_WARN_IF(!targetMap.IsMapped())) { + return nullptr; + } + AlphaBoxBlur blur(r, targetMap.GetStride(), sigmaXY.width, sigmaXY.height); + blur.Blur(targetMap.GetData()); + } else { + RefPtr<DataSourceSurface> channel0, channel1, channel2, channel3; + FilterProcessing::SeparateColorChannels(input, channel0, channel1, channel2, + channel3); + if (MOZ2D_WARN_IF(!(channel0 && channel1 && channel2 && channel3))) { + return nullptr; + } + { + DataSourceSurface::ScopedMap channel0Map(channel0, + DataSourceSurface::READ_WRITE); + DataSourceSurface::ScopedMap channel1Map(channel1, + DataSourceSurface::READ_WRITE); + DataSourceSurface::ScopedMap channel2Map(channel2, + DataSourceSurface::READ_WRITE); + DataSourceSurface::ScopedMap channel3Map(channel3, + DataSourceSurface::READ_WRITE); + if (MOZ2D_WARN_IF(!(channel0Map.IsMapped() && channel1Map.IsMapped() && + channel2Map.IsMapped() && channel3Map.IsMapped()))) { + return nullptr; + } + + AlphaBoxBlur blur(r, channel0Map.GetStride(), sigmaXY.width, + sigmaXY.height); + blur.Blur(channel0Map.GetData()); + blur.Blur(channel1Map.GetData()); + blur.Blur(channel2Map.GetData()); + blur.Blur(channel3Map.GetData()); + } + target = FilterProcessing::CombineColorChannels(channel0, channel1, + channel2, channel3); + } + + return GetDataSurfaceInRect(target, srcRect, aRect, EDGE_MODE_NONE); +} + +void FilterNodeBlurXYSoftware::RequestFromInputsForRect(const IntRect& aRect) { + RequestInputRect(IN_GAUSSIAN_BLUR_IN, InflatedSourceOrDestRect(aRect)); +} + +IntRect FilterNodeBlurXYSoftware::MapRectToSource(const IntRect& aRect, + const IntRect& aMax, + FilterNode* aSourceNode) { + return MapInputRectToSource( + IN_GAUSSIAN_BLUR_IN, InflatedSourceOrDestRect(aRect), aMax, aSourceNode); +} + +IntRect FilterNodeBlurXYSoftware::InflatedSourceOrDestRect( + const IntRect& aDestRect) { + Size sigmaXY = StdDeviationXY(); + IntSize d = + AlphaBoxBlur::CalculateBlurRadius(Point(sigmaXY.width, sigmaXY.height)); + IntRect srcRect = aDestRect; + srcRect.Inflate(d); + return srcRect; +} + +IntRect FilterNodeBlurXYSoftware::GetOutputRectInRect(const IntRect& aRect) { + IntRect srcRequest = InflatedSourceOrDestRect(aRect); + IntRect srcOutput = GetInputRectInRect(IN_GAUSSIAN_BLUR_IN, srcRequest); + return InflatedSourceOrDestRect(srcOutput).Intersect(aRect); +} + +FilterNodeGaussianBlurSoftware::FilterNodeGaussianBlurSoftware() + : mStdDeviation(0) {} + +static float ClampStdDeviation(float aStdDeviation) { + // Cap software blur radius for performance reasons. + return std::min(std::max(0.0f, aStdDeviation), 100.0f); +} + +void FilterNodeGaussianBlurSoftware::SetAttribute(uint32_t aIndex, + float aStdDeviation) { + switch (aIndex) { + case ATT_GAUSSIAN_BLUR_STD_DEVIATION: + mStdDeviation = ClampStdDeviation(aStdDeviation); + break; + default: + MOZ_CRASH("GFX: FilterNodeGaussianBlurSoftware::SetAttribute"); + } + Invalidate(); +} + +Size FilterNodeGaussianBlurSoftware::StdDeviationXY() { + return Size(mStdDeviation, mStdDeviation); +} + +FilterNodeDirectionalBlurSoftware::FilterNodeDirectionalBlurSoftware() + : mStdDeviation(0.0), mBlurDirection(BLUR_DIRECTION_X) {} + +void FilterNodeDirectionalBlurSoftware::SetAttribute(uint32_t aIndex, + Float aStdDeviation) { + switch (aIndex) { + case ATT_DIRECTIONAL_BLUR_STD_DEVIATION: + mStdDeviation = ClampStdDeviation(aStdDeviation); + break; + default: + MOZ_CRASH("GFX: FilterNodeDirectionalBlurSoftware::SetAttribute"); + } + Invalidate(); +} + +void FilterNodeDirectionalBlurSoftware::SetAttribute(uint32_t aIndex, + uint32_t aBlurDirection) { + switch (aIndex) { + case ATT_DIRECTIONAL_BLUR_DIRECTION: + mBlurDirection = (BlurDirection)aBlurDirection; + break; + default: + MOZ_CRASH("GFX: FilterNodeDirectionalBlurSoftware::SetAttribute"); + } + Invalidate(); +} + +Size FilterNodeDirectionalBlurSoftware::StdDeviationXY() { + float sigmaX = mBlurDirection == BLUR_DIRECTION_X ? mStdDeviation : 0; + float sigmaY = mBlurDirection == BLUR_DIRECTION_Y ? mStdDeviation : 0; + return Size(sigmaX, sigmaY); +} + +int32_t FilterNodeCropSoftware::InputIndex(uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_CROP_IN: + return 0; + default: + return -1; + } +} + +void FilterNodeCropSoftware::SetAttribute(uint32_t aIndex, + const Rect& aSourceRect) { + MOZ_ASSERT(aIndex == ATT_CROP_RECT); + Rect srcRect = aSourceRect; + srcRect.Round(); + if (!srcRect.ToIntRect(&mCropRect)) { + mCropRect = IntRect(); + } + Invalidate(); +} + +already_AddRefed<DataSourceSurface> FilterNodeCropSoftware::Render( + const IntRect& aRect) { + return GetInputDataSourceSurface(IN_CROP_IN, aRect.Intersect(mCropRect)); +} + +void FilterNodeCropSoftware::RequestFromInputsForRect(const IntRect& aRect) { + RequestInputRect(IN_CROP_IN, aRect.Intersect(mCropRect)); +} + +IntRect FilterNodeCropSoftware::MapRectToSource(const IntRect& aRect, + const IntRect& aMax, + FilterNode* aSourceNode) { + return MapInputRectToSource(IN_CROP_IN, aRect.Intersect(mCropRect), aMax, + aSourceNode); +} + +IntRect FilterNodeCropSoftware::GetOutputRectInRect(const IntRect& aRect) { + return GetInputRectInRect(IN_CROP_IN, aRect).Intersect(mCropRect); +} + +int32_t FilterNodePremultiplySoftware::InputIndex(uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_PREMULTIPLY_IN: + return 0; + default: + return -1; + } +} + +already_AddRefed<DataSourceSurface> FilterNodePremultiplySoftware::Render( + const IntRect& aRect) { + RefPtr<DataSourceSurface> input = + GetInputDataSourceSurface(IN_PREMULTIPLY_IN, aRect); + return input ? Premultiply(input) : nullptr; +} + +void FilterNodePremultiplySoftware::RequestFromInputsForRect( + const IntRect& aRect) { + RequestInputRect(IN_PREMULTIPLY_IN, aRect); +} + +IntRect FilterNodePremultiplySoftware::MapRectToSource( + const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { + return MapInputRectToSource(IN_PREMULTIPLY_IN, aRect, aMax, aSourceNode); +} + +IntRect FilterNodePremultiplySoftware::GetOutputRectInRect( + const IntRect& aRect) { + return GetInputRectInRect(IN_PREMULTIPLY_IN, aRect); +} + +int32_t FilterNodeUnpremultiplySoftware::InputIndex(uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_UNPREMULTIPLY_IN: + return 0; + default: + return -1; + } +} + +already_AddRefed<DataSourceSurface> FilterNodeUnpremultiplySoftware::Render( + const IntRect& aRect) { + RefPtr<DataSourceSurface> input = + GetInputDataSourceSurface(IN_UNPREMULTIPLY_IN, aRect); + return input ? Unpremultiply(input) : nullptr; +} + +void FilterNodeUnpremultiplySoftware::RequestFromInputsForRect( + const IntRect& aRect) { + RequestInputRect(IN_UNPREMULTIPLY_IN, aRect); +} + +IntRect FilterNodeUnpremultiplySoftware::MapRectToSource( + const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { + return MapInputRectToSource(IN_UNPREMULTIPLY_IN, aRect, aMax, aSourceNode); +} + +IntRect FilterNodeUnpremultiplySoftware::GetOutputRectInRect( + const IntRect& aRect) { + return GetInputRectInRect(IN_UNPREMULTIPLY_IN, aRect); +} + +void FilterNodeOpacitySoftware::SetAttribute(uint32_t aIndex, Float aValue) { + MOZ_ASSERT(aIndex == ATT_OPACITY_VALUE); + mValue = aValue; + Invalidate(); +} + +int32_t FilterNodeOpacitySoftware::InputIndex(uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_OPACITY_IN: + return 0; + default: + return -1; + } +} + +already_AddRefed<DataSourceSurface> FilterNodeOpacitySoftware::Render( + const IntRect& aRect) { + RefPtr<DataSourceSurface> input = + GetInputDataSourceSurface(IN_OPACITY_IN, aRect); + return input ? Opacity(input, mValue) : nullptr; +} + +void FilterNodeOpacitySoftware::RequestFromInputsForRect(const IntRect& aRect) { + RequestInputRect(IN_OPACITY_IN, aRect); +} + +IntRect FilterNodeOpacitySoftware::MapRectToSource(const IntRect& aRect, + const IntRect& aMax, + FilterNode* aSourceNode) { + return MapInputRectToSource(IN_OPACITY_IN, aRect, aMax, aSourceNode); +} + +IntRect FilterNodeOpacitySoftware::GetOutputRectInRect(const IntRect& aRect) { + return GetInputRectInRect(IN_OPACITY_IN, aRect); +} + +bool PointLightSoftware::SetAttribute(uint32_t aIndex, const Point3D& aPoint) { + switch (aIndex) { + case ATT_POINT_LIGHT_POSITION: + mPosition = aPoint; + break; + default: + return false; + } + return true; +} + +SpotLightSoftware::SpotLightSoftware() + : mSpecularFocus(0), mLimitingConeAngle(0), mLimitingConeCos(1) {} + +bool SpotLightSoftware::SetAttribute(uint32_t aIndex, const Point3D& aPoint) { + switch (aIndex) { + case ATT_SPOT_LIGHT_POSITION: + mPosition = aPoint; + break; + case ATT_SPOT_LIGHT_POINTS_AT: + mPointsAt = aPoint; + break; + default: + return false; + } + return true; +} + +bool SpotLightSoftware::SetAttribute(uint32_t aIndex, Float aValue) { + switch (aIndex) { + case ATT_SPOT_LIGHT_LIMITING_CONE_ANGLE: + mLimitingConeAngle = aValue; + break; + case ATT_SPOT_LIGHT_FOCUS: + mSpecularFocus = aValue; + break; + default: + return false; + } + return true; +} + +DistantLightSoftware::DistantLightSoftware() : mAzimuth(0), mElevation(0) {} + +bool DistantLightSoftware::SetAttribute(uint32_t aIndex, Float aValue) { + switch (aIndex) { + case ATT_DISTANT_LIGHT_AZIMUTH: + mAzimuth = aValue; + break; + case ATT_DISTANT_LIGHT_ELEVATION: + mElevation = aValue; + break; + default: + return false; + } + return true; +} + +static inline Point3D Normalized(const Point3D& vec) { + Point3D copy(vec); + copy.Normalize(); + return copy; +} + +template <typename LightType, typename LightingType> +FilterNodeLightingSoftware<LightType, LightingType>::FilterNodeLightingSoftware( + const char* aTypeName) + : mSurfaceScale(0) +#if defined(MOZILLA_INTERNAL_API) && defined(NS_BUILD_REFCNT_LOGGING) + , + mTypeName(aTypeName) +#endif +{ +} + +template <typename LightType, typename LightingType> +int32_t FilterNodeLightingSoftware<LightType, LightingType>::InputIndex( + uint32_t aInputEnumIndex) { + switch (aInputEnumIndex) { + case IN_LIGHTING_IN: + return 0; + default: + return -1; + } +} + +template <typename LightType, typename LightingType> +void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute( + uint32_t aIndex, const Point3D& aPoint) { + if (mLight.SetAttribute(aIndex, aPoint)) { + Invalidate(); + return; + } + MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute point"); +} + +template <typename LightType, typename LightingType> +void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute( + uint32_t aIndex, Float aValue) { + if (mLight.SetAttribute(aIndex, aValue) || + mLighting.SetAttribute(aIndex, aValue)) { + Invalidate(); + return; + } + switch (aIndex) { + case ATT_LIGHTING_SURFACE_SCALE: + mSurfaceScale = std::fpclassify(aValue) == FP_SUBNORMAL ? 0.0 : aValue; + break; + default: + MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute float"); + } + Invalidate(); +} + +template <typename LightType, typename LightingType> +void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute( + uint32_t aIndex, const Size& aKernelUnitLength) { + switch (aIndex) { + case ATT_LIGHTING_KERNEL_UNIT_LENGTH: + mKernelUnitLength = aKernelUnitLength; + break; + default: + MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute size"); + } + Invalidate(); +} + +template <typename LightType, typename LightingType> +void FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute( + uint32_t aIndex, const DeviceColor& aColor) { + MOZ_ASSERT(aIndex == ATT_LIGHTING_COLOR); + mColor = aColor; + Invalidate(); +} + +template <typename LightType, typename LightingType> +IntRect +FilterNodeLightingSoftware<LightType, LightingType>::GetOutputRectInRect( + const IntRect& aRect) { + return aRect; +} + +Point3D PointLightSoftware::GetVectorToLight(const Point3D& aTargetPoint) { + return Normalized(mPosition - aTargetPoint); +} + +uint32_t PointLightSoftware::GetColor(uint32_t aLightColor, + const Point3D& aVectorToLight) { + return aLightColor; +} + +void SpotLightSoftware::Prepare() { + mVectorFromFocusPointToLight = Normalized(mPointsAt - mPosition); + mLimitingConeCos = + std::max<double>(cos(mLimitingConeAngle * M_PI / 180.0), 0.0); + mPowCache.CacheForExponent(mSpecularFocus); +} + +Point3D SpotLightSoftware::GetVectorToLight(const Point3D& aTargetPoint) { + return Normalized(mPosition - aTargetPoint); +} + +uint32_t SpotLightSoftware::GetColor(uint32_t aLightColor, + const Point3D& aVectorToLight) { + union { + uint32_t color; + uint8_t colorC[4]; + }; + + Float dot = -aVectorToLight.DotProduct(mVectorFromFocusPointToLight); + if (!mPowCache.HasPowerTable()) { + dot *= (dot >= mLimitingConeCos); + color = aLightColor; + colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_R] *= dot; + colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_G] *= dot; + colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_B] *= dot; + } else { + color = aLightColor; + uint16_t doti = dot * (dot >= 0) * (1 << PowCache::sInputIntPrecisionBits); + uint32_t tmp = mPowCache.Pow(doti) * (dot >= mLimitingConeCos); + MOZ_ASSERT(tmp <= (1 << PowCache::sOutputIntPrecisionBits), + "pow() result must not exceed 1.0"); + colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_R] = + uint8_t((colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_R] * tmp) >> + PowCache::sOutputIntPrecisionBits); + colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_G] = + uint8_t((colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_G] * tmp) >> + PowCache::sOutputIntPrecisionBits); + colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_B] = + uint8_t((colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_B] * tmp) >> + PowCache::sOutputIntPrecisionBits); + } + colorC[B8G8R8A8_COMPONENT_BYTEOFFSET_A] = 255; + return color; +} + +void DistantLightSoftware::Prepare() { + const double radPerDeg = M_PI / 180.0; + mVectorToLight.x = cos(mAzimuth * radPerDeg) * cos(mElevation * radPerDeg); + mVectorToLight.y = sin(mAzimuth * radPerDeg) * cos(mElevation * radPerDeg); + mVectorToLight.z = sin(mElevation * radPerDeg); +} + +Point3D DistantLightSoftware::GetVectorToLight(const Point3D& aTargetPoint) { + return mVectorToLight; +} + +uint32_t DistantLightSoftware::GetColor(uint32_t aLightColor, + const Point3D& aVectorToLight) { + return aLightColor; +} + +template <typename CoordType> +static Point3D GenerateNormal(const uint8_t* data, int32_t stride, + uint8_t* boundsBegin, uint8_t* boundsEnd, + int32_t x, int32_t y, float surfaceScale, + CoordType dx, CoordType dy) { + const uint8_t* index = data + y * stride + x; + + CoordType zero = 0; + + // See this for source of constants: + // http://www.w3.org/TR/SVG11/filters.html#feDiffuseLightingElement + int16_t normalX = -1 * ColorComponentAtPoint(index, stride, boundsBegin, + boundsEnd, -dx, -dy, 1, 0) + + 1 * ColorComponentAtPoint(index, stride, boundsBegin, + boundsEnd, dx, -dy, 1, 0) + + -2 * ColorComponentAtPoint(index, stride, boundsBegin, + boundsEnd, -dx, zero, 1, 0) + + 2 * ColorComponentAtPoint(index, stride, boundsBegin, + boundsEnd, dx, zero, 1, 0) + + -1 * ColorComponentAtPoint(index, stride, boundsBegin, + boundsEnd, -dx, dy, 1, 0) + + 1 * ColorComponentAtPoint(index, stride, boundsBegin, + boundsEnd, dx, dy, 1, 0); + + int16_t normalY = -1 * ColorComponentAtPoint(index, stride, boundsBegin, + boundsEnd, -dx, -dy, 1, 0) + + -2 * ColorComponentAtPoint(index, stride, boundsBegin, + boundsEnd, zero, -dy, 1, 0) + + -1 * ColorComponentAtPoint(index, stride, boundsBegin, + boundsEnd, dx, -dy, 1, 0) + + 1 * ColorComponentAtPoint(index, stride, boundsBegin, + boundsEnd, -dx, dy, 1, 0) + + 2 * ColorComponentAtPoint(index, stride, boundsBegin, + boundsEnd, zero, dy, 1, 0) + + 1 * ColorComponentAtPoint(index, stride, boundsBegin, + boundsEnd, dx, dy, 1, 0); + + Point3D normal; + normal.x = -surfaceScale * normalX / 4.0f; + normal.y = -surfaceScale * normalY / 4.0f; + normal.z = 255; + return Normalized(normal); +} + +template <typename LightType, typename LightingType> +already_AddRefed<DataSourceSurface> +FilterNodeLightingSoftware<LightType, LightingType>::Render( + const IntRect& aRect) { + if (mKernelUnitLength.width == floor(mKernelUnitLength.width) && + mKernelUnitLength.height == floor(mKernelUnitLength.height)) { + return DoRender(aRect, (int32_t)mKernelUnitLength.width, + (int32_t)mKernelUnitLength.height); + } + return DoRender(aRect, mKernelUnitLength.width, mKernelUnitLength.height); +} + +template <typename LightType, typename LightingType> +void FilterNodeLightingSoftware< + LightType, LightingType>::RequestFromInputsForRect(const IntRect& aRect) { + IntRect srcRect = aRect; + srcRect.Inflate(ceil(mKernelUnitLength.width), + ceil(mKernelUnitLength.height)); + RequestInputRect(IN_LIGHTING_IN, srcRect); +} + +template <typename LightType, typename LightingType> +IntRect FilterNodeLightingSoftware<LightType, LightingType>::MapRectToSource( + const IntRect& aRect, const IntRect& aMax, FilterNode* aSourceNode) { + IntRect srcRect = aRect; + srcRect.Inflate(ceil(mKernelUnitLength.width), + ceil(mKernelUnitLength.height)); + return MapInputRectToSource(IN_LIGHTING_IN, srcRect, aMax, aSourceNode); +} + +template <typename LightType, typename LightingType> +template <typename CoordType> +already_AddRefed<DataSourceSurface> +FilterNodeLightingSoftware<LightType, LightingType>::DoRender( + const IntRect& aRect, CoordType aKernelUnitLengthX, + CoordType aKernelUnitLengthY) { + MOZ_ASSERT(aKernelUnitLengthX > 0, + "aKernelUnitLengthX can be a negative or zero value"); + MOZ_ASSERT(aKernelUnitLengthY > 0, + "aKernelUnitLengthY can be a negative or zero value"); + + IntRect srcRect = aRect; + IntSize size = aRect.Size(); + srcRect.Inflate(ceil(float(aKernelUnitLengthX)), + ceil(float(aKernelUnitLengthY))); + + // Inflate the source rect by another pixel because the bilinear filtering in + // ColorComponentAtPoint may want to access the margins. + srcRect.Inflate(1); + + RefPtr<DataSourceSurface> input = GetInputDataSourceSurface( + IN_LIGHTING_IN, srcRect, CAN_HANDLE_A8, EDGE_MODE_NONE); + + if (!input) { + return nullptr; + } + + if (input->GetFormat() != SurfaceFormat::A8) { + input = FilterProcessing::ExtractAlpha(input); + } + + RefPtr<DataSourceSurface> target = + Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8); + if (MOZ2D_WARN_IF(!target)) { + return nullptr; + } + + IntPoint offset = aRect.TopLeft() - srcRect.TopLeft(); + + DataSourceSurface::ScopedMap sourceMap(input, DataSourceSurface::READ); + DataSourceSurface::ScopedMap targetMap(target, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!(sourceMap.IsMapped() && targetMap.IsMapped()))) { + return nullptr; + } + + uint8_t* sourceData = + DataAtOffset(input, sourceMap.GetMappedSurface(), offset); + int32_t sourceStride = sourceMap.GetStride(); + uint8_t* sourceBegin = sourceMap.GetData(); + uint8_t* sourceEnd = sourceBegin + sourceStride * input->GetSize().height; + uint8_t* targetData = targetMap.GetData(); + int32_t targetStride = targetMap.GetStride(); + + uint32_t lightColor = ColorToBGRA(mColor); + mLight.Prepare(); + mLighting.Prepare(); + + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.width; x++) { + int32_t sourceIndex = y * sourceStride + x; + int32_t targetIndex = y * targetStride + 4 * x; + + Point3D normal = + GenerateNormal(sourceData, sourceStride, sourceBegin, sourceEnd, x, y, + mSurfaceScale, aKernelUnitLengthX, aKernelUnitLengthY); + + IntPoint pointInFilterSpace(aRect.X() + x, aRect.Y() + y); + Float Z = mSurfaceScale * sourceData[sourceIndex] / 255.0f; + Point3D pt(pointInFilterSpace.x, pointInFilterSpace.y, Z); + Point3D rayDir = mLight.GetVectorToLight(pt); + uint32_t color = mLight.GetColor(lightColor, rayDir); + + *(uint32_t*)(targetData + targetIndex) = + mLighting.LightPixel(normal, rayDir, color); + } + + // Zero padding to keep valgrind happy. + PodZero(&targetData[y * targetStride + 4 * size.width], + targetStride - 4 * size.width); + } + + return target.forget(); +} + +DiffuseLightingSoftware::DiffuseLightingSoftware() : mDiffuseConstant(0) {} + +bool DiffuseLightingSoftware::SetAttribute(uint32_t aIndex, Float aValue) { + switch (aIndex) { + case ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT: + mDiffuseConstant = aValue; + break; + default: + return false; + } + return true; +} + +uint32_t DiffuseLightingSoftware::LightPixel(const Point3D& aNormal, + const Point3D& aVectorToLight, + uint32_t aColor) { + Float dotNL = std::max(0.0f, aNormal.DotProduct(aVectorToLight)); + Float diffuseNL = mDiffuseConstant * dotNL; + + union { + uint32_t bgra; + uint8_t components[4]; + } color = {aColor}; + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B] = umin( + uint32_t(diffuseNL * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B]), + 255U); + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G] = umin( + uint32_t(diffuseNL * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G]), + 255U); + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R] = umin( + uint32_t(diffuseNL * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R]), + 255U); + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_A] = 255; + return color.bgra; +} + +SpecularLightingSoftware::SpecularLightingSoftware() + : mSpecularConstant(0), mSpecularExponent(0), mSpecularConstantInt(0) {} + +bool SpecularLightingSoftware::SetAttribute(uint32_t aIndex, Float aValue) { + switch (aIndex) { + case ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT: + mSpecularConstant = std::min(std::max(aValue, 0.0f), 255.0f); + break; + case ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT: + mSpecularExponent = std::min(std::max(aValue, 1.0f), 128.0f); + break; + default: + return false; + } + return true; +} + +void SpecularLightingSoftware::Prepare() { + mPowCache.CacheForExponent(mSpecularExponent); + mSpecularConstantInt = uint32_t(mSpecularConstant * (1 << 8)); +} + +uint32_t SpecularLightingSoftware::LightPixel(const Point3D& aNormal, + const Point3D& aVectorToLight, + uint32_t aColor) { + Point3D vectorToEye(0, 0, 1); + Point3D halfwayVector = aVectorToLight + vectorToEye; + Float halfwayLength = halfwayVector.Length(); + if (halfwayLength > 0) { + halfwayVector /= halfwayLength; + } + Float dotNH = aNormal.DotProduct(halfwayVector); + uint16_t dotNHi = + uint16_t(dotNH * (dotNH >= 0) * (1 << PowCache::sInputIntPrecisionBits)); + // The exponent for specular is in [1,128] range, so we don't need to check + // and optimize for the "default power table" scenario here. + MOZ_ASSERT(mPowCache.HasPowerTable()); + uint32_t specularNHi = + uint32_t(mSpecularConstantInt) * mPowCache.Pow(dotNHi) >> 8; + + union { + uint32_t bgra; + uint8_t components[4]; + } color = {aColor}; + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B] = + umin((specularNHi * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B]) >> + PowCache::sOutputIntPrecisionBits, + 255U); + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G] = + umin((specularNHi * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G]) >> + PowCache::sOutputIntPrecisionBits, + 255U); + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R] = + umin((specularNHi * color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R]) >> + PowCache::sOutputIntPrecisionBits, + 255U); + + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_A] = + umax(color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_B], + umax(color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_G], + color.components[B8G8R8A8_COMPONENT_BYTEOFFSET_R])); + return color.bgra; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/FilterNodeSoftware.h b/gfx/2d/FilterNodeSoftware.h new file mode 100644 index 0000000000..25b2481bfe --- /dev/null +++ b/gfx/2d/FilterNodeSoftware.h @@ -0,0 +1,780 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_FILTERNODESOFTWARE_H_ +#define _MOZILLA_GFX_FILTERNODESOFTWARE_H_ + +#include "Filters.h" +#include "mozilla/Mutex.h" +#include <vector> + +namespace mozilla { +namespace gfx { + +class DataSourceSurface; +class DrawTarget; +struct DrawOptions; +class FilterNodeSoftware; + +/** + * Can be attached to FilterNodeSoftware instances using + * AddInvalidationListener. FilterInvalidated is called whenever the output of + * the observed filter may have changed; that is, whenever cached GetOutput() + * results (and results derived from them) need to discarded. + */ +class FilterInvalidationListener { + public: + virtual void FilterInvalidated(FilterNodeSoftware* aFilter) = 0; +}; + +/** + * This is the base class for the software (i.e. pure CPU, non-accelerated) + * FilterNode implementation. The software implementation is backend-agnostic, + * so it can be used as a fallback for all DrawTarget implementations. + */ +class FilterNodeSoftware : public FilterNode, + public FilterInvalidationListener { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeSoftware, override) + FilterNodeSoftware(); + virtual ~FilterNodeSoftware(); + + // Factory method, intended to be called from DrawTarget*::CreateFilter. + static already_AddRefed<FilterNode> Create(FilterType aType); + + // Draw the filter, intended to be called by DrawTarget*::DrawFilter. + void Draw(DrawTarget* aDrawTarget, const Rect& aSourceRect, + const Point& aDestPoint, const DrawOptions& aOptions); + + FilterBackend GetBackendType() override { return FILTER_BACKEND_SOFTWARE; } + void SetInput(uint32_t aIndex, SourceSurface* aSurface) override; + void SetInput(uint32_t aIndex, FilterNode* aFilter) override; + + virtual const char* GetName() { return "Unknown"; } + + void AddInvalidationListener(FilterInvalidationListener* aListener); + void RemoveInvalidationListener(FilterInvalidationListener* aListener); + + // FilterInvalidationListener implementation + void FilterInvalidated(FilterNodeSoftware* aFilter) override; + + protected: + // The following methods are intended to be overriden by subclasses. + + /** + * Translates a *FilterInputs enum value into an index for the + * mInputFilters / mInputSurfaces arrays. Returns -1 for invalid inputs. + * If somebody calls SetInput(enumValue, input) with an enumValue for which + * InputIndex(enumValue) is -1, we abort. + */ + virtual int32_t InputIndex(uint32_t aInputEnumIndex) { return -1; } + + /** + * Every filter node has an output rect, which can also be infinite. The + * output rect can depend on the values of any set attributes and on the + * output rects of any input filters or surfaces. + * This method returns the intersection of the filter's output rect with + * aInRect. Filters with unconstrained output always return aInRect. + */ + virtual IntRect GetOutputRectInRect(const IntRect& aInRect) = 0; + + /** + * Return a surface with the rendered output which is of size aRect.Size(). + * aRect is required to be a subrect of this filter's output rect; in other + * words, aRect == GetOutputRectInRect(aRect) must always be true. + * May return nullptr in error conditions or for an empty aRect. + * Implementations are not required to allocate a new surface and may even + * pass through input surfaces unchanged. + * Callers need to treat the returned surface as immutable. + */ + virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) = 0; + + /** + * Call RequestRect (see below) on any input filters with the desired input + * rect, so that the input filter knows what to cache the next time it + * renders. + */ + virtual void RequestFromInputsForRect(const IntRect& aRect) {} + + /** + * This method provides a caching default implementation but can be overriden + * by subclasses that don't want to cache their output. Those classes should + * call Render(aRect) directly from here. + */ + virtual already_AddRefed<DataSourceSurface> GetOutput(const IntRect& aRect); + + // The following methods are non-virtual helper methods. + + /** + * Format hints for GetInputDataSourceSurface. Some callers of + * GetInputDataSourceSurface can handle both B8G8R8A8 and A8 surfaces, these + * should pass CAN_HANDLE_A8 in order to avoid unnecessary conversions. + * Callers that can only handle B8G8R8A8 surfaces pass NEED_COLOR_CHANNELS. + */ + enum FormatHint { CAN_HANDLE_A8, NEED_COLOR_CHANNELS }; + + /** + * Returns SurfaceFormat::B8G8R8A8 or SurfaceFormat::A8, depending on the + * current surface format and the format hint. + */ + SurfaceFormat DesiredFormat(SurfaceFormat aCurrentFormat, + FormatHint aFormatHint); + + /** + * Intended to be called by FilterNodeSoftware::Render implementations. + * Returns a surface of size aRect.Size() or nullptr in error conditions. The + * returned surface contains the output of the specified input filter or + * input surface in aRect. If aRect extends beyond the input filter's output + * rect (or the input surface's dimensions), the remaining area is filled + * according to aEdgeMode: The default, EDGE_MODE_NONE, simply pads with + * transparent black. + * If non-null, the returned surface is guaranteed to be of SurfaceFormat::A8 + * or SurfaceFormat::B8G8R8A8. If aFormatHint is NEED_COLOR_CHANNELS, the + * returned surface is guaranteed to be of SurfaceFormat::B8G8R8A8 always. + * Each pixel row of the returned surface is guaranteed to be 16-byte aligned. + */ + already_AddRefed<DataSourceSurface> GetInputDataSourceSurface( + uint32_t aInputEnumIndex, const IntRect& aRect, + FormatHint aFormatHint = CAN_HANDLE_A8, + ConvolveMatrixEdgeMode aEdgeMode = EDGE_MODE_NONE, + const IntRect* aTransparencyPaddedSourceRect = nullptr); + + /** + * Returns the intersection of the input filter's or surface's output rect + * with aInRect. + */ + IntRect GetInputRectInRect(uint32_t aInputEnumIndex, const IntRect& aInRect); + + /** + * Calls RequestRect on the specified input, if it's a filter. + */ + void RequestInputRect(uint32_t aInputEnumIndex, const IntRect& aRect); + + /** + * Calls MapRectToSource on the specified input, if it's a filter. + */ + IntRect MapInputRectToSource(uint32_t aInputEnumIndex, const IntRect& aRect, + const IntRect& aMax, FilterNode* aSourceNode); + + /** + * Returns the number of set input filters or surfaces. Needed for filters + * which can have an arbitrary number of inputs. + */ + size_t NumberOfSetInputs(); + + /** + * Discard the cached surface that was stored in the GetOutput default + * implementation. Needs to be called whenever attributes or inputs are set + * that might change the result of a Render() call. + */ + void Invalidate(); + + /** + * Called in order to let this filter know what to cache during the next + * GetOutput call. Expected to call RequestRect on this filter's input + * filters. + */ + void RequestRect(const IntRect& aRect); + + /** + * Set input filter and clear input surface for this input index, or set + * input surface and clear input filter. One of aSurface and aFilter should + * be null. + */ + void SetInput(uint32_t aIndex, SourceSurface* aSurface, + FilterNodeSoftware* aFilter); + + protected: + /** + * mInputSurfaces / mInputFilters: For each input index, either a surface or + * a filter is set, and the other is null. + */ + std::vector<RefPtr<SourceSurface> > mInputSurfaces; + std::vector<RefPtr<FilterNodeSoftware> > mInputFilters; + + /** + * Weak pointers to our invalidation listeners, i.e. to those filters who + * have this filter as an input. Invalidation listeners are required to + * unsubscribe themselves from us when they let go of their reference to us. + * This ensures that the pointers in this array are never stale. + */ + std::vector<FilterInvalidationListener*> mInvalidationListeners; + + /** + * Stores the rect which we want to render and cache on the next call to + * GetOutput. + */ + IntRect mRequestedRect; + + /** + * Stores our cached output. + */ + IntRect mCachedRect; + RefPtr<DataSourceSurface> mCachedOutput; +}; + +// Subclasses for specific filters. + +class FilterNodeTransformSoftware : public FilterNodeSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeTransformSoftware, override) + FilterNodeTransformSoftware(); + const char* GetName() override { return "Transform"; } + using FilterNodeSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, uint32_t aGraphicsFilter) override; + void SetAttribute(uint32_t aIndex, const Matrix& aMatrix) override; + IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax, + FilterNode* aSourceNode) override; + + protected: + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + int32_t InputIndex(uint32_t aInputEnumIndex) override; + void RequestFromInputsForRect(const IntRect& aRect) override; + IntRect SourceRectForOutputRect(const IntRect& aRect); + + private: + Matrix mMatrix; + SamplingFilter mSamplingFilter; +}; + +class FilterNodeBlendSoftware : public FilterNodeSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeBlendSoftware, override) + FilterNodeBlendSoftware(); + const char* GetName() override { return "Blend"; } + using FilterNodeSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, uint32_t aBlendMode) override; + IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax, + FilterNode* aSourceNode) override; + + protected: + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + int32_t InputIndex(uint32_t aInputEnumIndex) override; + void RequestFromInputsForRect(const IntRect& aRect) override; + + private: + BlendMode mBlendMode; +}; + +class FilterNodeMorphologySoftware : public FilterNodeSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeMorphologySoftware, + override) + FilterNodeMorphologySoftware(); + const char* GetName() override { return "Morphology"; } + using FilterNodeSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, const IntSize& aRadii) override; + void SetAttribute(uint32_t aIndex, uint32_t aOperator) override; + + protected: + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + int32_t InputIndex(uint32_t aInputEnumIndex) override; + void RequestFromInputsForRect(const IntRect& aRect) override; + + private: + IntSize mRadii; + MorphologyOperator mOperator; +}; + +class FilterNodeColorMatrixSoftware : public FilterNodeSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeColorMatrixSoftware, + override) + const char* GetName() override { return "ColorMatrix"; } + using FilterNodeSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, const Matrix5x4& aMatrix) override; + void SetAttribute(uint32_t aIndex, uint32_t aAlphaMode) override; + + protected: + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + int32_t InputIndex(uint32_t aInputEnumIndex) override; + void RequestFromInputsForRect(const IntRect& aRect) override; + IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax, + FilterNode* aSourceNode) override; + + private: + Matrix5x4 mMatrix; + AlphaMode mAlphaMode; +}; + +class FilterNodeFloodSoftware : public FilterNodeSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeFloodSoftware, override) + const char* GetName() override { return "Flood"; } + using FilterNodeSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, const DeviceColor& aColor) override; + IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax, + FilterNode* aSourceNode) override; + + protected: + already_AddRefed<DataSourceSurface> GetOutput(const IntRect& aRect) override; + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + + private: + DeviceColor mColor; +}; + +class FilterNodeTileSoftware : public FilterNodeSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeTileSoftware, override) + const char* GetName() override { return "Tile"; } + using FilterNodeSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, const IntRect& aSourceRect) override; + + protected: + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + int32_t InputIndex(uint32_t aInputEnumIndex) override; + void RequestFromInputsForRect(const IntRect& aRect) override; + + private: + IntRect mSourceRect; +}; + +/** + * Baseclass for the four different component transfer filters. + */ +class FilterNodeComponentTransferSoftware : public FilterNodeSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeComponentTransferSoftware, + override) + FilterNodeComponentTransferSoftware(); + + using FilterNodeSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, bool aDisable) override; + IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax, + FilterNode* aSourceNode) override; + + protected: + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + int32_t InputIndex(uint32_t aInputEnumIndex) override; + void RequestFromInputsForRect(const IntRect& aRect) override; + virtual void GenerateLookupTable(ptrdiff_t aComponent, + uint8_t aTables[4][256], bool aDisabled); + virtual void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) = 0; + + bool mDisableR; + bool mDisableG; + bool mDisableB; + bool mDisableA; +}; + +class FilterNodeTableTransferSoftware + : public FilterNodeComponentTransferSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeTableTransferSoftware, + override) + const char* GetName() override { return "TableTransfer"; } + using FilterNodeComponentTransferSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, const Float* aFloat, + uint32_t aSize) override; + + protected: + void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override; + + private: + void FillLookupTableImpl(std::vector<Float>& aTableValues, + uint8_t aTable[256]); + + std::vector<Float> mTableR; + std::vector<Float> mTableG; + std::vector<Float> mTableB; + std::vector<Float> mTableA; +}; + +class FilterNodeDiscreteTransferSoftware + : public FilterNodeComponentTransferSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeDiscreteTransferSoftware, + override) + const char* GetName() override { return "DiscreteTransfer"; } + using FilterNodeComponentTransferSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, const Float* aFloat, + uint32_t aSize) override; + + protected: + void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override; + + private: + void FillLookupTableImpl(std::vector<Float>& aTableValues, + uint8_t aTable[256]); + + std::vector<Float> mTableR; + std::vector<Float> mTableG; + std::vector<Float> mTableB; + std::vector<Float> mTableA; +}; + +class FilterNodeLinearTransferSoftware + : public FilterNodeComponentTransferSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeLinearTransformSoftware, + override) + FilterNodeLinearTransferSoftware(); + const char* GetName() override { return "LinearTransfer"; } + using FilterNodeComponentTransferSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, Float aValue) override; + + protected: + void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override; + + private: + void FillLookupTableImpl(Float aSlope, Float aIntercept, uint8_t aTable[256]); + + Float mSlopeR; + Float mSlopeG; + Float mSlopeB; + Float mSlopeA; + Float mInterceptR; + Float mInterceptG; + Float mInterceptB; + Float mInterceptA; +}; + +class FilterNodeGammaTransferSoftware + : public FilterNodeComponentTransferSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeGammaTransferSoftware, + override) + FilterNodeGammaTransferSoftware(); + const char* GetName() override { return "GammaTransfer"; } + using FilterNodeComponentTransferSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, Float aValue) override; + + protected: + void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) override; + + private: + void FillLookupTableImpl(Float aAmplitude, Float aExponent, Float aOffset, + uint8_t aTable[256]); + + Float mAmplitudeR; + Float mAmplitudeG; + Float mAmplitudeB; + Float mAmplitudeA; + Float mExponentR; + Float mExponentG; + Float mExponentB; + Float mExponentA; + Float mOffsetR; + Float mOffsetG; + Float mOffsetB; + Float mOffsetA; +}; + +class FilterNodeConvolveMatrixSoftware : public FilterNodeSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeConvolveMatrixSoftware, + override) + FilterNodeConvolveMatrixSoftware(); + const char* GetName() override { return "ConvolveMatrix"; } + using FilterNodeSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, const IntSize& aKernelSize) override; + void SetAttribute(uint32_t aIndex, const Float* aMatrix, + uint32_t aSize) override; + void SetAttribute(uint32_t aIndex, Float aValue) override; + void SetAttribute(uint32_t aIndex, const Size& aKernelUnitLength) override; + void SetAttribute(uint32_t aIndex, const IntRect& aSourceRect) override; + void SetAttribute(uint32_t aIndex, const IntPoint& aTarget) override; + void SetAttribute(uint32_t aIndex, uint32_t aEdgeMode) override; + void SetAttribute(uint32_t aIndex, bool aPreserveAlpha) override; + IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax, + FilterNode* aSourceNode) override; + + protected: + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + int32_t InputIndex(uint32_t aInputEnumIndex) override; + void RequestFromInputsForRect(const IntRect& aRect) override; + + private: + template <typename CoordType> + already_AddRefed<DataSourceSurface> DoRender(const IntRect& aRect, + CoordType aKernelUnitLengthX, + CoordType aKernelUnitLengthY); + + IntRect InflatedSourceRect(const IntRect& aDestRect); + IntRect InflatedDestRect(const IntRect& aSourceRect); + + IntSize mKernelSize; + std::vector<Float> mKernelMatrix; + Float mDivisor; + Float mBias; + IntPoint mTarget; + IntRect mSourceRect; + ConvolveMatrixEdgeMode mEdgeMode; + Size mKernelUnitLength; + bool mPreserveAlpha; +}; + +class FilterNodeDisplacementMapSoftware : public FilterNodeSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeDisplacementMapSoftware, + override) + FilterNodeDisplacementMapSoftware(); + const char* GetName() override { return "DisplacementMap"; } + using FilterNodeSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, Float aScale) override; + void SetAttribute(uint32_t aIndex, uint32_t aValue) override; + IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax, + FilterNode* aSourceNode) override; + + protected: + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + int32_t InputIndex(uint32_t aInputEnumIndex) override; + void RequestFromInputsForRect(const IntRect& aRect) override; + + private: + IntRect InflatedSourceOrDestRect(const IntRect& aDestOrSourceRect); + + Float mScale; + ColorChannel mChannelX; + ColorChannel mChannelY; +}; + +class FilterNodeTurbulenceSoftware : public FilterNodeSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeTurbulenceSoftware, + override) + FilterNodeTurbulenceSoftware(); + const char* GetName() override { return "Turbulence"; } + using FilterNodeSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, const Size& aSize) override; + void SetAttribute(uint32_t aIndex, const IntRect& aRenderRect) override; + void SetAttribute(uint32_t aIndex, bool aStitchable) override; + void SetAttribute(uint32_t aIndex, uint32_t aValue) override; + IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax, + FilterNode* aSourceNode) override; + + protected: + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + int32_t InputIndex(uint32_t aInputEnumIndex) override; + + private: + IntRect mRenderRect; + Size mBaseFrequency; + uint32_t mNumOctaves; + uint32_t mSeed; + bool mStitchable; + TurbulenceType mType; +}; + +class FilterNodeArithmeticCombineSoftware : public FilterNodeSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeArithmeticCombineSoftware, + override) + FilterNodeArithmeticCombineSoftware(); + const char* GetName() override { return "ArithmeticCombine"; } + using FilterNodeSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, const Float* aFloat, + uint32_t aSize) override; + IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax, + FilterNode* aSourceNode) override; + + protected: + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + int32_t InputIndex(uint32_t aInputEnumIndex) override; + void RequestFromInputsForRect(const IntRect& aRect) override; + + private: + Float mK1; + Float mK2; + Float mK3; + Float mK4; +}; + +class FilterNodeCompositeSoftware : public FilterNodeSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeCompositeSoftware, override) + FilterNodeCompositeSoftware(); + const char* GetName() override { return "Composite"; } + using FilterNodeSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, uint32_t aOperator) override; + + protected: + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + int32_t InputIndex(uint32_t aInputEnumIndex) override; + void RequestFromInputsForRect(const IntRect& aRect) override; + IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax, + FilterNode* aSourceNode) override; + + private: + CompositeOperator mOperator; +}; + +// Base class for FilterNodeGaussianBlurSoftware and +// FilterNodeDirectionalBlurSoftware. +class FilterNodeBlurXYSoftware : public FilterNodeSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeBlurXYSoftware, override) + protected: + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + int32_t InputIndex(uint32_t aInputEnumIndex) override; + IntRect InflatedSourceOrDestRect(const IntRect& aDestRect); + void RequestFromInputsForRect(const IntRect& aRect) override; + IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax, + FilterNode* aSourceNode) override; + + // Implemented by subclasses. + virtual Size StdDeviationXY() = 0; +}; + +class FilterNodeGaussianBlurSoftware : public FilterNodeBlurXYSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeGaussianBlurSoftware, + override) + FilterNodeGaussianBlurSoftware(); + const char* GetName() override { return "GaussianBlur"; } + using FilterNodeSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, Float aStdDeviation) override; + + protected: + Size StdDeviationXY() override; + + private: + Float mStdDeviation; +}; + +class FilterNodeDirectionalBlurSoftware : public FilterNodeBlurXYSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeDirectionalBlurSoftware, + override) + FilterNodeDirectionalBlurSoftware(); + const char* GetName() override { return "DirectionalBlur"; } + using FilterNodeSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, Float aStdDeviation) override; + void SetAttribute(uint32_t aIndex, uint32_t aBlurDirection) override; + + protected: + Size StdDeviationXY() override; + + private: + Float mStdDeviation; + BlurDirection mBlurDirection; +}; + +class FilterNodeCropSoftware : public FilterNodeSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeCropSoftware, override) + const char* GetName() override { return "Crop"; } + using FilterNodeSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, const Rect& aSourceRect) override; + + protected: + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + int32_t InputIndex(uint32_t aInputEnumIndex) override; + void RequestFromInputsForRect(const IntRect& aRect) override; + IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax, + FilterNode* aSourceNode) override; + + private: + IntRect mCropRect; +}; + +class FilterNodePremultiplySoftware : public FilterNodeSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodePremultiplySoftware, + override) + const char* GetName() override { return "Premultiply"; } + + protected: + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + int32_t InputIndex(uint32_t aInputEnumIndex) override; + void RequestFromInputsForRect(const IntRect& aRect) override; + IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax, + FilterNode* aSourceNode) override; +}; + +class FilterNodeUnpremultiplySoftware : public FilterNodeSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeUnpremultiplySoftware, + override) + const char* GetName() override { return "Unpremultiply"; } + + protected: + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + int32_t InputIndex(uint32_t aInputEnumIndex) override; + void RequestFromInputsForRect(const IntRect& aRect) override; + IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax, + FilterNode* aSourceNode) override; +}; + +class FilterNodeOpacitySoftware : public FilterNodeSoftware { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeOpacitySoftware, override) + const char* GetName() override { return "Opacity"; } + using FilterNodeSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, Float aValue) override; + IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax, + FilterNode* aSourceNode) override; + + protected: + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + int32_t InputIndex(uint32_t aInputEnumIndex) override; + void RequestFromInputsForRect(const IntRect& aRect) override; + + Float mValue = 1.0f; +}; + +template <typename LightType, typename LightingType> +class FilterNodeLightingSoftware : public FilterNodeSoftware { + public: +#if defined(MOZILLA_INTERNAL_API) && defined(NS_BUILD_REFCNT_LOGGING) + // Helpers for refcounted + const char* typeName() const override { return mTypeName; } + size_t typeSize() const override { return sizeof(*this); } +#endif + explicit FilterNodeLightingSoftware(const char* aTypeName); + const char* GetName() override { return "Lighting"; } + using FilterNodeSoftware::SetAttribute; + void SetAttribute(uint32_t aIndex, Float) override; + void SetAttribute(uint32_t aIndex, const Size&) override; + void SetAttribute(uint32_t aIndex, const Point3D&) override; + void SetAttribute(uint32_t aIndex, const DeviceColor&) override; + IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax, + FilterNode* aSourceNode) override; + + protected: + already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override; + IntRect GetOutputRectInRect(const IntRect& aRect) override; + int32_t InputIndex(uint32_t aInputEnumIndex) override; + void RequestFromInputsForRect(const IntRect& aRect) override; + + private: + template <typename CoordType> + already_AddRefed<DataSourceSurface> DoRender(const IntRect& aRect, + CoordType aKernelUnitLengthX, + CoordType aKernelUnitLengthY); + + LightType mLight; + LightingType mLighting; + Float mSurfaceScale; + Size mKernelUnitLength; + DeviceColor mColor; +#if defined(MOZILLA_INTERNAL_API) && defined(NS_BUILD_REFCNT_LOGGING) + const char* mTypeName; +#endif +}; + +} // namespace gfx +} // namespace mozilla + +#endif // _MOZILLA_GFX_FILTERNODESOFTWARE_H_ diff --git a/gfx/2d/FilterProcessing.cpp b/gfx/2d/FilterProcessing.cpp new file mode 100644 index 0000000000..4f26cd6249 --- /dev/null +++ b/gfx/2d/FilterProcessing.cpp @@ -0,0 +1,282 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FilterProcessing.h" +#include "Logging.h" + +namespace mozilla { +namespace gfx { + +already_AddRefed<DataSourceSurface> FilterProcessing::ExtractAlpha( + DataSourceSurface* aSource) { + IntSize size = aSource->GetSize(); + RefPtr<DataSourceSurface> alpha = + Factory::CreateDataSourceSurface(size, SurfaceFormat::A8); + if (MOZ2D_WARN_IF(!alpha)) { + return nullptr; + } + + DataSourceSurface::ScopedMap sourceMap(aSource, DataSourceSurface::READ); + DataSourceSurface::ScopedMap alphaMap(alpha, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!sourceMap.IsMapped() || !alphaMap.IsMapped())) { + return nullptr; + } + + uint8_t* sourceData = sourceMap.GetData(); + int32_t sourceStride = sourceMap.GetStride(); + uint8_t* alphaData = alphaMap.GetData(); + int32_t alphaStride = alphaMap.GetStride(); + + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + ExtractAlpha_SSE2(size, sourceData, sourceStride, alphaData, alphaStride); +#endif + } else { + ExtractAlpha_Scalar(size, sourceData, sourceStride, alphaData, alphaStride); + } + + return alpha.forget(); +} + +already_AddRefed<DataSourceSurface> FilterProcessing::ConvertToB8G8R8A8( + SourceSurface* aSurface) { + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + return ConvertToB8G8R8A8_SSE2(aSurface); +#endif + } + return ConvertToB8G8R8A8_Scalar(aSurface); +} + +already_AddRefed<DataSourceSurface> FilterProcessing::ApplyBlending( + DataSourceSurface* aInput1, DataSourceSurface* aInput2, + BlendMode aBlendMode) { + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + return ApplyBlending_SSE2(aInput1, aInput2, aBlendMode); +#endif + } + return nullptr; +} + +void FilterProcessing::ApplyMorphologyHorizontal( + uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOp) { + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + ApplyMorphologyHorizontal_SSE2(aSourceData, aSourceStride, aDestData, + aDestStride, aDestRect, aRadius, aOp); +#endif + } else { + ApplyMorphologyHorizontal_Scalar(aSourceData, aSourceStride, aDestData, + aDestStride, aDestRect, aRadius, aOp); + } +} + +void FilterProcessing::ApplyMorphologyVertical( + uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOp) { + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + ApplyMorphologyVertical_SSE2(aSourceData, aSourceStride, aDestData, + aDestStride, aDestRect, aRadius, aOp); +#endif + } else { + ApplyMorphologyVertical_Scalar(aSourceData, aSourceStride, aDestData, + aDestStride, aDestRect, aRadius, aOp); + } +} + +already_AddRefed<DataSourceSurface> FilterProcessing::ApplyColorMatrix( + DataSourceSurface* aInput, const Matrix5x4& aMatrix) { + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + return ApplyColorMatrix_SSE2(aInput, aMatrix); +#endif + } + return ApplyColorMatrix_Scalar(aInput, aMatrix); +} + +void FilterProcessing::ApplyComposition(DataSourceSurface* aSource, + DataSourceSurface* aDest, + CompositeOperator aOperator) { + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + ApplyComposition_SSE2(aSource, aDest, aOperator); +#endif + } else { + ApplyComposition_Scalar(aSource, aDest, aOperator); + } +} + +void FilterProcessing::SeparateColorChannels( + DataSourceSurface* aSource, RefPtr<DataSourceSurface>& aChannel0, + RefPtr<DataSourceSurface>& aChannel1, RefPtr<DataSourceSurface>& aChannel2, + RefPtr<DataSourceSurface>& aChannel3) { + IntSize size = aSource->GetSize(); + aChannel0 = Factory::CreateDataSourceSurface(size, SurfaceFormat::A8); + aChannel1 = Factory::CreateDataSourceSurface(size, SurfaceFormat::A8); + aChannel2 = Factory::CreateDataSourceSurface(size, SurfaceFormat::A8); + aChannel3 = Factory::CreateDataSourceSurface(size, SurfaceFormat::A8); + if (MOZ2D_WARN_IF(!(aChannel0 && aChannel1 && aChannel2 && aChannel3))) { + return; + } + + DataSourceSurface::ScopedMap sourceMap(aSource, DataSourceSurface::READ); + DataSourceSurface::ScopedMap channel0Map(aChannel0, DataSourceSurface::WRITE); + DataSourceSurface::ScopedMap channel1Map(aChannel1, DataSourceSurface::WRITE); + DataSourceSurface::ScopedMap channel2Map(aChannel2, DataSourceSurface::WRITE); + DataSourceSurface::ScopedMap channel3Map(aChannel3, DataSourceSurface::WRITE); + if (MOZ2D_WARN_IF(!(sourceMap.IsMapped() && channel0Map.IsMapped() && + channel1Map.IsMapped() && channel2Map.IsMapped() && + channel3Map.IsMapped()))) { + return; + } + uint8_t* sourceData = sourceMap.GetData(); + int32_t sourceStride = sourceMap.GetStride(); + uint8_t* channel0Data = channel0Map.GetData(); + uint8_t* channel1Data = channel1Map.GetData(); + uint8_t* channel2Data = channel2Map.GetData(); + uint8_t* channel3Data = channel3Map.GetData(); + int32_t channelStride = channel0Map.GetStride(); + + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + SeparateColorChannels_SSE2(size, sourceData, sourceStride, channel0Data, + channel1Data, channel2Data, channel3Data, + channelStride); +#endif + } else { + SeparateColorChannels_Scalar(size, sourceData, sourceStride, channel0Data, + channel1Data, channel2Data, channel3Data, + channelStride); + } +} + +already_AddRefed<DataSourceSurface> FilterProcessing::CombineColorChannels( + DataSourceSurface* aChannel0, DataSourceSurface* aChannel1, + DataSourceSurface* aChannel2, DataSourceSurface* aChannel3) { + IntSize size = aChannel0->GetSize(); + RefPtr<DataSourceSurface> result = + Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8); + if (MOZ2D_WARN_IF(!result)) { + return nullptr; + } + DataSourceSurface::ScopedMap resultMap(result, DataSourceSurface::WRITE); + DataSourceSurface::ScopedMap channel0Map(aChannel0, DataSourceSurface::READ); + DataSourceSurface::ScopedMap channel1Map(aChannel1, DataSourceSurface::READ); + DataSourceSurface::ScopedMap channel2Map(aChannel2, DataSourceSurface::READ); + DataSourceSurface::ScopedMap channel3Map(aChannel3, DataSourceSurface::READ); + if (MOZ2D_WARN_IF(!(resultMap.IsMapped() && channel0Map.IsMapped() && + channel1Map.IsMapped() && channel2Map.IsMapped() && + channel3Map.IsMapped()))) { + return nullptr; + } + int32_t resultStride = resultMap.GetStride(); + uint8_t* resultData = resultMap.GetData(); + int32_t channelStride = channel0Map.GetStride(); + uint8_t* channel0Data = channel0Map.GetData(); + uint8_t* channel1Data = channel1Map.GetData(); + uint8_t* channel2Data = channel2Map.GetData(); + uint8_t* channel3Data = channel3Map.GetData(); + + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + CombineColorChannels_SSE2(size, resultStride, resultData, channelStride, + channel0Data, channel1Data, channel2Data, + channel3Data); +#endif + } else { + CombineColorChannels_Scalar(size, resultStride, resultData, channelStride, + channel0Data, channel1Data, channel2Data, + channel3Data); + } + + return result.forget(); +} + +void FilterProcessing::DoPremultiplicationCalculation(const IntSize& aSize, + uint8_t* aTargetData, + int32_t aTargetStride, + uint8_t* aSourceData, + int32_t aSourceStride) { + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + DoPremultiplicationCalculation_SSE2(aSize, aTargetData, aTargetStride, + aSourceData, aSourceStride); +#endif + } else { + DoPremultiplicationCalculation_Scalar(aSize, aTargetData, aTargetStride, + aSourceData, aSourceStride); + } +} + +void FilterProcessing::DoUnpremultiplicationCalculation(const IntSize& aSize, + uint8_t* aTargetData, + int32_t aTargetStride, + uint8_t* aSourceData, + int32_t aSourceStride) { + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + DoUnpremultiplicationCalculation_SSE2(aSize, aTargetData, aTargetStride, + aSourceData, aSourceStride); +#endif + } else { + DoUnpremultiplicationCalculation_Scalar(aSize, aTargetData, aTargetStride, + aSourceData, aSourceStride); + } +} + +void FilterProcessing::DoOpacityCalculation( + const IntSize& aSize, uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride, Float aValue) { + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + DoOpacityCalculation_SSE2(aSize, aTargetData, aTargetStride, aSourceData, + aSourceStride, aValue); +#endif + } else { + DoOpacityCalculation_Scalar(aSize, aTargetData, aTargetStride, aSourceData, + aSourceStride, aValue); + } +} + +void FilterProcessing::DoOpacityCalculationA8( + const IntSize& aSize, uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride, Float aValue) { + DoOpacityCalculationA8_Scalar(aSize, aTargetData, aTargetStride, aSourceData, + aSourceStride, aValue); +} + +already_AddRefed<DataSourceSurface> FilterProcessing::RenderTurbulence( + const IntSize& aSize, const Point& aOffset, const Size& aBaseFrequency, + int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, + const Rect& aTileRect) { + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + return RenderTurbulence_SSE2(aSize, aOffset, aBaseFrequency, aSeed, + aNumOctaves, aType, aStitch, aTileRect); +#endif + } + return RenderTurbulence_Scalar(aSize, aOffset, aBaseFrequency, aSeed, + aNumOctaves, aType, aStitch, aTileRect); +} + +already_AddRefed<DataSourceSurface> FilterProcessing::ApplyArithmeticCombine( + DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1, + Float aK2, Float aK3, Float aK4) { + if (Factory::HasSSE2()) { +#ifdef USE_SSE2 + return ApplyArithmeticCombine_SSE2(aInput1, aInput2, aK1, aK2, aK3, aK4); +#endif + } + return ApplyArithmeticCombine_Scalar(aInput1, aInput2, aK1, aK2, aK3, aK4); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/FilterProcessing.h b/gfx/2d/FilterProcessing.h new file mode 100644 index 0000000000..d6beb62611 --- /dev/null +++ b/gfx/2d/FilterProcessing.h @@ -0,0 +1,209 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_FILTERPROCESSING_H_ +#define _MOZILLA_GFX_FILTERPROCESSING_H_ + +#include "2D.h" +#include "Filters.h" + +namespace mozilla { +namespace gfx { + +const ptrdiff_t B8G8R8A8_COMPONENT_BYTEOFFSET_B = 0; +const ptrdiff_t B8G8R8A8_COMPONENT_BYTEOFFSET_G = 1; +const ptrdiff_t B8G8R8A8_COMPONENT_BYTEOFFSET_R = 2; +const ptrdiff_t B8G8R8A8_COMPONENT_BYTEOFFSET_A = 3; + +class FilterProcessing { + public: + // Fast approximate division by 255. It has the property that + // for all 0 <= v <= 255*255, FastDivideBy255(v) == v/255. + // But it only uses two adds and two shifts instead of an + // integer division (which is expensive on many processors). + template <class B, class A> + static B FastDivideBy255(A v) { + return ((v << 8) + v + 255) >> 16; + } + + static already_AddRefed<DataSourceSurface> ExtractAlpha( + DataSourceSurface* aSource); + static already_AddRefed<DataSourceSurface> ConvertToB8G8R8A8( + SourceSurface* aSurface); + static already_AddRefed<DataSourceSurface> ApplyBlending( + DataSourceSurface* aInput1, DataSourceSurface* aInput2, + BlendMode aBlendMode); + static void ApplyMorphologyHorizontal(uint8_t* aSourceData, + int32_t aSourceStride, + uint8_t* aDestData, int32_t aDestStride, + const IntRect& aDestRect, + int32_t aRadius, + MorphologyOperator aOperator); + static void ApplyMorphologyVertical(uint8_t* aSourceData, + int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, + const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOperator); + static already_AddRefed<DataSourceSurface> ApplyColorMatrix( + DataSourceSurface* aInput, const Matrix5x4& aMatrix); + static void ApplyComposition(DataSourceSurface* aSource, + DataSourceSurface* aDest, + CompositeOperator aOperator); + static void SeparateColorChannels(DataSourceSurface* aSource, + RefPtr<DataSourceSurface>& aChannel0, + RefPtr<DataSourceSurface>& aChannel1, + RefPtr<DataSourceSurface>& aChannel2, + RefPtr<DataSourceSurface>& aChannel3); + static already_AddRefed<DataSourceSurface> CombineColorChannels( + DataSourceSurface* aChannel0, DataSourceSurface* aChannel1, + DataSourceSurface* aChannel2, DataSourceSurface* aChannel3); + static void DoPremultiplicationCalculation(const IntSize& aSize, + uint8_t* aTargetData, + int32_t aTargetStride, + uint8_t* aSourceData, + int32_t aSourceStride); + static void DoUnpremultiplicationCalculation(const IntSize& aSize, + uint8_t* aTargetData, + int32_t aTargetStride, + uint8_t* aSourceData, + int32_t aSourceStride); + static void DoOpacityCalculation(const IntSize& aSize, uint8_t* aTargetData, + int32_t aTargetStride, uint8_t* aSourceData, + int32_t aSourceStride, Float aValue); + static void DoOpacityCalculationA8(const IntSize& aSize, uint8_t* aTargetData, + int32_t aTargetStride, + uint8_t* aSourceData, + int32_t aSourceStride, Float aValue); + static already_AddRefed<DataSourceSurface> RenderTurbulence( + const IntSize& aSize, const Point& aOffset, const Size& aBaseFrequency, + int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, + const Rect& aTileRect); + static already_AddRefed<DataSourceSurface> ApplyArithmeticCombine( + DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1, + Float aK2, Float aK3, Float aK4); + + protected: + static void ExtractAlpha_Scalar(const IntSize& size, uint8_t* sourceData, + int32_t sourceStride, uint8_t* alphaData, + int32_t alphaStride); + static already_AddRefed<DataSourceSurface> ConvertToB8G8R8A8_Scalar( + SourceSurface* aSurface); + static void ApplyMorphologyHorizontal_Scalar( + uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOperator); + static void ApplyMorphologyVertical_Scalar( + uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOperator); + static already_AddRefed<DataSourceSurface> ApplyColorMatrix_Scalar( + DataSourceSurface* aInput, const Matrix5x4& aMatrix); + static void ApplyComposition_Scalar(DataSourceSurface* aSource, + DataSourceSurface* aDest, + CompositeOperator aOperator); + + static void SeparateColorChannels_Scalar( + const IntSize& size, uint8_t* sourceData, int32_t sourceStride, + uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, + uint8_t* channel3Data, int32_t channelStride); + static void CombineColorChannels_Scalar( + const IntSize& size, int32_t resultStride, uint8_t* resultData, + int32_t channelStride, uint8_t* channel0Data, uint8_t* channel1Data, + uint8_t* channel2Data, uint8_t* channel3Data); + static void DoPremultiplicationCalculation_Scalar(const IntSize& aSize, + uint8_t* aTargetData, + int32_t aTargetStride, + uint8_t* aSourceData, + int32_t aSourceStride); + static void DoUnpremultiplicationCalculation_Scalar(const IntSize& aSize, + uint8_t* aTargetData, + int32_t aTargetStride, + uint8_t* aSourceData, + int32_t aSourceStride); + static void DoOpacityCalculation_Scalar(const IntSize& aSize, + uint8_t* aTargetData, + int32_t aTargetStride, + uint8_t* aSourceData, + int32_t aSourceStride, Float aValue); + static void DoOpacityCalculationA8_Scalar( + const IntSize& aSize, uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride, Float aValue); + static already_AddRefed<DataSourceSurface> RenderTurbulence_Scalar( + const IntSize& aSize, const Point& aOffset, const Size& aBaseFrequency, + int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, + const Rect& aTileRect); + static already_AddRefed<DataSourceSurface> ApplyArithmeticCombine_Scalar( + DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1, + Float aK2, Float aK3, Float aK4); + +#ifdef USE_SSE2 + static void ExtractAlpha_SSE2(const IntSize& size, uint8_t* sourceData, + int32_t sourceStride, uint8_t* alphaData, + int32_t alphaStride); + static already_AddRefed<DataSourceSurface> ConvertToB8G8R8A8_SSE2( + SourceSurface* aSurface); + static already_AddRefed<DataSourceSurface> ApplyBlending_SSE2( + DataSourceSurface* aInput1, DataSourceSurface* aInput2, + BlendMode aBlendMode); + static void ApplyMorphologyHorizontal_SSE2( + uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOperator); + static void ApplyMorphologyVertical_SSE2( + uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOperator); + static already_AddRefed<DataSourceSurface> ApplyColorMatrix_SSE2( + DataSourceSurface* aInput, const Matrix5x4& aMatrix); + static void ApplyComposition_SSE2(DataSourceSurface* aSource, + DataSourceSurface* aDest, + CompositeOperator aOperator); + static void SeparateColorChannels_SSE2( + const IntSize& size, uint8_t* sourceData, int32_t sourceStride, + uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, + uint8_t* channel3Data, int32_t channelStride); + static void CombineColorChannels_SSE2( + const IntSize& size, int32_t resultStride, uint8_t* resultData, + int32_t channelStride, uint8_t* channel0Data, uint8_t* channel1Data, + uint8_t* channel2Data, uint8_t* channel3Data); + static void DoPremultiplicationCalculation_SSE2(const IntSize& aSize, + uint8_t* aTargetData, + int32_t aTargetStride, + uint8_t* aSourceData, + int32_t aSourceStride); + static void DoUnpremultiplicationCalculation_SSE2(const IntSize& aSize, + uint8_t* aTargetData, + int32_t aTargetStride, + uint8_t* aSourceData, + int32_t aSourceStride); + static void DoOpacityCalculation_SSE2(const IntSize& aSize, + uint8_t* aTargetData, + int32_t aTargetStride, + uint8_t* aSourceData, + int32_t aSourceStride, Float aValue); + static already_AddRefed<DataSourceSurface> RenderTurbulence_SSE2( + const IntSize& aSize, const Point& aOffset, const Size& aBaseFrequency, + int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, + const Rect& aTileRect); + static already_AddRefed<DataSourceSurface> ApplyArithmeticCombine_SSE2( + DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1, + Float aK2, Float aK3, Float aK4); +#endif +}; + +// Constant-time max and min functions for unsigned arguments +static inline unsigned umax(unsigned a, unsigned b) { + return a - ((a - b) & -(a < b)); +} + +static inline unsigned umin(unsigned a, unsigned b) { + return a - ((a - b) & -(a > b)); +} + +} // namespace gfx +} // namespace mozilla + +#endif // _MOZILLA_GFX_FILTERPROCESSING_H_ diff --git a/gfx/2d/FilterProcessingSIMD-inl.h b/gfx/2d/FilterProcessingSIMD-inl.h new file mode 100644 index 0000000000..81f30cfc9e --- /dev/null +++ b/gfx/2d/FilterProcessingSIMD-inl.h @@ -0,0 +1,1299 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FilterProcessing.h" + +#include "SIMD.h" +#include "SVGTurbulenceRenderer-inl.h" + +namespace mozilla { +namespace gfx { + +template <typename u8x16_t> +inline already_AddRefed<DataSourceSurface> ConvertToB8G8R8A8_SIMD( + SourceSurface* aSurface) { + IntSize size = aSurface->GetSize(); + RefPtr<DataSourceSurface> output = + Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8); + if (!output) { + return nullptr; + } + + RefPtr<DataSourceSurface> input = aSurface->GetDataSurface(); + DataSourceSurface::ScopedMap inputMap(input, DataSourceSurface::READ); + DataSourceSurface::ScopedMap outputMap(output, DataSourceSurface::READ_WRITE); + uint8_t* inputData = inputMap.GetData(); + uint8_t* outputData = outputMap.GetData(); + int32_t inputStride = inputMap.GetStride(); + int32_t outputStride = outputMap.GetStride(); + switch (input->GetFormat()) { + case SurfaceFormat::B8G8R8A8: + output = input; + break; + case SurfaceFormat::B8G8R8X8: + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.width; x++) { + int32_t inputIndex = y * inputStride + 4 * x; + int32_t outputIndex = y * outputStride + 4 * x; + outputData[outputIndex + 0] = inputData[inputIndex + 0]; + outputData[outputIndex + 1] = inputData[inputIndex + 1]; + outputData[outputIndex + 2] = inputData[inputIndex + 2]; + outputData[outputIndex + 3] = 255; + } + } + break; + case SurfaceFormat::R8G8B8A8: + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.width; x++) { + int32_t inputIndex = y * inputStride + 4 * x; + int32_t outputIndex = y * outputStride + 4 * x; + outputData[outputIndex + 2] = inputData[inputIndex + 0]; + outputData[outputIndex + 1] = inputData[inputIndex + 1]; + outputData[outputIndex + 0] = inputData[inputIndex + 2]; + outputData[outputIndex + 3] = inputData[inputIndex + 3]; + } + } + break; + case SurfaceFormat::R8G8B8X8: + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.width; x++) { + int32_t inputIndex = y * inputStride + 4 * x; + int32_t outputIndex = y * outputStride + 4 * x; + outputData[outputIndex + 2] = inputData[inputIndex + 0]; + outputData[outputIndex + 1] = inputData[inputIndex + 1]; + outputData[outputIndex + 0] = inputData[inputIndex + 2]; + outputData[outputIndex + 3] = 255; + } + } + break; + case SurfaceFormat::A8: + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.width; x += 16) { + int32_t inputIndex = y * inputStride + x; + int32_t outputIndex = y * outputStride + 4 * x; + u8x16_t p1To16 = simd::Load8<u8x16_t>(&inputData[inputIndex]); + // Turn AAAAAAAAAAAAAAAA into four chunks of 000A000A000A000A by + // interleaving with 0000000000000000 twice. + u8x16_t zero = simd::FromZero8<u8x16_t>(); + u8x16_t p1To8 = simd::InterleaveLo8(zero, p1To16); + u8x16_t p9To16 = simd::InterleaveHi8(zero, p1To16); + u8x16_t p1To4 = simd::InterleaveLo8(zero, p1To8); + u8x16_t p5To8 = simd::InterleaveHi8(zero, p1To8); + u8x16_t p9To12 = simd::InterleaveLo8(zero, p9To16); + u8x16_t p13To16 = simd::InterleaveHi8(zero, p9To16); + simd::Store8(&outputData[outputIndex], p1To4); + if ((x + 4) * 4 < outputStride) { + simd::Store8(&outputData[outputIndex + 4 * 4], p5To8); + } + if ((x + 8) * 4 < outputStride) { + simd::Store8(&outputData[outputIndex + 4 * 8], p9To12); + } + if ((x + 12) * 4 < outputStride) { + simd::Store8(&outputData[outputIndex + 4 * 12], p13To16); + } + } + } + break; + default: + output = nullptr; + break; + } + return output.forget(); +} + +template <typename u8x16_t> +inline void ExtractAlpha_SIMD(const IntSize& size, uint8_t* sourceData, + int32_t sourceStride, uint8_t* alphaData, + int32_t alphaStride) { + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.width; x += 16) { + // Process 16 pixels at a time. + // Turn up to four chunks of BGRABGRABGRABGRA into one chunk of + // AAAAAAAAAAAAAAAA. + int32_t sourceIndex = y * sourceStride + 4 * x; + int32_t targetIndex = y * alphaStride + x; + + u8x16_t bgrabgrabgrabgra2 = simd::FromZero8<u8x16_t>(); + u8x16_t bgrabgrabgrabgra3 = simd::FromZero8<u8x16_t>(); + u8x16_t bgrabgrabgrabgra4 = simd::FromZero8<u8x16_t>(); + + u8x16_t bgrabgrabgrabgra1 = + simd::Load8<u8x16_t>(&sourceData[sourceIndex]); + if (4 * (x + 4) < sourceStride) { + bgrabgrabgrabgra2 = + simd::Load8<u8x16_t>(&sourceData[sourceIndex + 4 * 4]); + } + if (4 * (x + 8) < sourceStride) { + bgrabgrabgrabgra3 = + simd::Load8<u8x16_t>(&sourceData[sourceIndex + 4 * 8]); + } + if (4 * (x + 12) < sourceStride) { + bgrabgrabgrabgra4 = + simd::Load8<u8x16_t>(&sourceData[sourceIndex + 4 * 12]); + } + + u8x16_t bbggrraabbggrraa1 = + simd::InterleaveLo8(bgrabgrabgrabgra1, bgrabgrabgrabgra3); + u8x16_t bbggrraabbggrraa2 = + simd::InterleaveHi8(bgrabgrabgrabgra1, bgrabgrabgrabgra3); + u8x16_t bbggrraabbggrraa3 = + simd::InterleaveLo8(bgrabgrabgrabgra2, bgrabgrabgrabgra4); + u8x16_t bbggrraabbggrraa4 = + simd::InterleaveHi8(bgrabgrabgrabgra2, bgrabgrabgrabgra4); + u8x16_t bbbbggggrrrraaaa1 = + simd::InterleaveLo8(bbggrraabbggrraa1, bbggrraabbggrraa3); + u8x16_t bbbbggggrrrraaaa2 = + simd::InterleaveHi8(bbggrraabbggrraa1, bbggrraabbggrraa3); + u8x16_t bbbbggggrrrraaaa3 = + simd::InterleaveLo8(bbggrraabbggrraa2, bbggrraabbggrraa4); + u8x16_t bbbbggggrrrraaaa4 = + simd::InterleaveHi8(bbggrraabbggrraa2, bbggrraabbggrraa4); + u8x16_t rrrrrrrraaaaaaaa1 = + simd::InterleaveHi8(bbbbggggrrrraaaa1, bbbbggggrrrraaaa3); + u8x16_t rrrrrrrraaaaaaaa2 = + simd::InterleaveHi8(bbbbggggrrrraaaa2, bbbbggggrrrraaaa4); + u8x16_t aaaaaaaaaaaaaaaa = + simd::InterleaveHi8(rrrrrrrraaaaaaaa1, rrrrrrrraaaaaaaa2); + + simd::Store8(&alphaData[targetIndex], aaaaaaaaaaaaaaaa); + } + } +} + +// This function calculates the result color values for four pixels, but for +// only two color channels - either b & r or g & a. However, the a result will +// not be used. +// source and dest each contain 8 values, either bbbb gggg or rrrr aaaa. +// sourceAlpha and destAlpha are of the form aaaa aaaa, where each aaaa is the +// alpha of all four pixels (and both aaaa's are the same). +// blendendComponent1 and blendedComponent2 are the out parameters. +template <typename i16x8_t, typename i32x4_t, uint32_t aBlendMode> +inline void BlendTwoComponentsOfFourPixels(i16x8_t source, i16x8_t sourceAlpha, + i16x8_t dest, + const i16x8_t& destAlpha, + i32x4_t& blendedComponent1, + i32x4_t& blendedComponent2) { + i16x8_t x255 = simd::FromI16<i16x8_t>(255); + + switch (aBlendMode) { + case BLEND_MODE_MULTIPLY: { + // val = ((255 - destAlpha) * source + (255 - sourceAlpha + source) * + // dest); + i16x8_t twoFiftyFiveMinusDestAlpha = simd::Sub16(x255, destAlpha); + i16x8_t twoFiftyFiveMinusSourceAlpha = simd::Sub16(x255, sourceAlpha); + i16x8_t twoFiftyFiveMinusSourceAlphaPlusSource = + simd::Add16(twoFiftyFiveMinusSourceAlpha, source); + + i16x8_t sourceInterleavedWithDest1 = simd::InterleaveLo16(source, dest); + i16x8_t leftFactor1 = simd::InterleaveLo16( + twoFiftyFiveMinusDestAlpha, twoFiftyFiveMinusSourceAlphaPlusSource); + blendedComponent1 = + simd::MulAdd16x8x2To32x4(sourceInterleavedWithDest1, leftFactor1); + blendedComponent1 = simd::FastDivideBy255(blendedComponent1); + + i16x8_t sourceInterleavedWithDest2 = simd::InterleaveHi16(source, dest); + i16x8_t leftFactor2 = simd::InterleaveHi16( + twoFiftyFiveMinusDestAlpha, twoFiftyFiveMinusSourceAlphaPlusSource); + blendedComponent2 = + simd::MulAdd16x8x2To32x4(sourceInterleavedWithDest2, leftFactor2); + blendedComponent2 = simd::FastDivideBy255(blendedComponent2); + + break; + } + + case BLEND_MODE_SCREEN: { + // val = 255 * (source + dest) + (0 - dest) * source; + i16x8_t sourcePlusDest = simd::Add16(source, dest); + i16x8_t zeroMinusDest = simd::Sub16(simd::FromI16<i16x8_t>(0), dest); + + i16x8_t twoFiftyFiveInterleavedWithZeroMinusDest1 = + simd::InterleaveLo16(x255, zeroMinusDest); + i16x8_t sourcePlusDestInterleavedWithSource1 = + simd::InterleaveLo16(sourcePlusDest, source); + blendedComponent1 = + simd::MulAdd16x8x2To32x4(twoFiftyFiveInterleavedWithZeroMinusDest1, + sourcePlusDestInterleavedWithSource1); + blendedComponent1 = simd::FastDivideBy255(blendedComponent1); + + i16x8_t twoFiftyFiveInterleavedWithZeroMinusDest2 = + simd::InterleaveHi16(x255, zeroMinusDest); + i16x8_t sourcePlusDestInterleavedWithSource2 = + simd::InterleaveHi16(sourcePlusDest, source); + blendedComponent2 = + simd::MulAdd16x8x2To32x4(twoFiftyFiveInterleavedWithZeroMinusDest2, + sourcePlusDestInterleavedWithSource2); + blendedComponent2 = simd::FastDivideBy255(blendedComponent2); + + break; + } + + case BLEND_MODE_DARKEN: + case BLEND_MODE_LIGHTEN: { + // Darken: + // val = min((255 - destAlpha) * source + 255 * dest, + // 255 * source + (255 - sourceAlpha) * dest); + // + // Lighten: + // val = max((255 - destAlpha) * source + 255 * dest, + // 255 * source + (255 - sourceAlpha) * dest); + + i16x8_t twoFiftyFiveMinusDestAlpha = simd::Sub16(x255, destAlpha); + i16x8_t twoFiftyFiveMinusSourceAlpha = simd::Sub16(x255, sourceAlpha); + + i16x8_t twoFiftyFiveMinusDestAlphaInterleavedWithTwoFiftyFive1 = + simd::InterleaveLo16(twoFiftyFiveMinusDestAlpha, x255); + i16x8_t twoFiftyFiveInterleavedWithTwoFiftyFiveMinusSourceAlpha1 = + simd::InterleaveLo16(x255, twoFiftyFiveMinusSourceAlpha); + i16x8_t sourceInterleavedWithDest1 = simd::InterleaveLo16(source, dest); + i32x4_t product1_1 = simd::MulAdd16x8x2To32x4( + twoFiftyFiveMinusDestAlphaInterleavedWithTwoFiftyFive1, + sourceInterleavedWithDest1); + i32x4_t product1_2 = simd::MulAdd16x8x2To32x4( + twoFiftyFiveInterleavedWithTwoFiftyFiveMinusSourceAlpha1, + sourceInterleavedWithDest1); + blendedComponent1 = aBlendMode == BLEND_MODE_DARKEN + ? simd::Min32(product1_1, product1_2) + : simd::Max32(product1_1, product1_2); + blendedComponent1 = simd::FastDivideBy255(blendedComponent1); + + i16x8_t twoFiftyFiveMinusDestAlphaInterleavedWithTwoFiftyFive2 = + simd::InterleaveHi16(twoFiftyFiveMinusDestAlpha, x255); + i16x8_t twoFiftyFiveInterleavedWithTwoFiftyFiveMinusSourceAlpha2 = + simd::InterleaveHi16(x255, twoFiftyFiveMinusSourceAlpha); + i16x8_t sourceInterleavedWithDest2 = simd::InterleaveHi16(source, dest); + i32x4_t product2_1 = simd::MulAdd16x8x2To32x4( + twoFiftyFiveMinusDestAlphaInterleavedWithTwoFiftyFive2, + sourceInterleavedWithDest2); + i32x4_t product2_2 = simd::MulAdd16x8x2To32x4( + twoFiftyFiveInterleavedWithTwoFiftyFiveMinusSourceAlpha2, + sourceInterleavedWithDest2); + blendedComponent2 = aBlendMode == BLEND_MODE_DARKEN + ? simd::Min32(product2_1, product2_2) + : simd::Max32(product2_1, product2_2); + blendedComponent2 = simd::FastDivideBy255(blendedComponent2); + + break; + } + } +} + +// The alpha channel is subject to a different calculation than the RGB +// channels, and this calculation is the same for all blend modes: +// resultAlpha * 255 = 255 * 255 - (255 - sourceAlpha) * (255 - destAlpha) +template <typename i16x8_t, typename i32x4_t> +inline i32x4_t BlendAlphaOfFourPixels(i16x8_t s_rrrraaaa1234, + i16x8_t d_rrrraaaa1234) { + // clang-format off + // We're using MulAdd16x8x2To32x4, so we need to interleave our factors + // appropriately. The calculation is rewritten as follows: + // resultAlpha[0] * 255 = 255 * 255 - (255 - sourceAlpha[0]) * (255 - destAlpha[0]) + // = 255 * 255 + (255 - sourceAlpha[0]) * (destAlpha[0] - 255) + // = (255 - 0) * (510 - 255) + (255 - sourceAlpha[0]) * (destAlpha[0] - 255) + // = MulAdd(255 - IntLv(0, sourceAlpha), IntLv(510, destAlpha) - 255)[0] + // clang-format on + i16x8_t zeroInterleavedWithSourceAlpha = + simd::InterleaveHi16(simd::FromI16<i16x8_t>(0), s_rrrraaaa1234); + i16x8_t fiveTenInterleavedWithDestAlpha = + simd::InterleaveHi16(simd::FromI16<i16x8_t>(510), d_rrrraaaa1234); + i16x8_t f1 = + simd::Sub16(simd::FromI16<i16x8_t>(255), zeroInterleavedWithSourceAlpha); + i16x8_t f2 = + simd::Sub16(fiveTenInterleavedWithDestAlpha, simd::FromI16<i16x8_t>(255)); + return simd::FastDivideBy255(simd::MulAdd16x8x2To32x4(f1, f2)); +} + +template <typename u8x16_t, typename i16x8_t> +inline void UnpackAndShuffleComponents(u8x16_t bgrabgrabgrabgra1234, + i16x8_t& bbbbgggg1234, + i16x8_t& rrrraaaa1234) { + // bgrabgrabgrabgra1234 -> bbbbgggg1234, rrrraaaa1234 + i16x8_t bgrabgra12 = simd::UnpackLo8x8ToI16x8(bgrabgrabgrabgra1234); + i16x8_t bgrabgra34 = simd::UnpackHi8x8ToI16x8(bgrabgrabgrabgra1234); + i16x8_t bbggrraa13 = simd::InterleaveLo16(bgrabgra12, bgrabgra34); + i16x8_t bbggrraa24 = simd::InterleaveHi16(bgrabgra12, bgrabgra34); + bbbbgggg1234 = simd::InterleaveLo16(bbggrraa13, bbggrraa24); + rrrraaaa1234 = simd::InterleaveHi16(bbggrraa13, bbggrraa24); +} + +template <typename i32x4_t, typename i16x8_t, typename u8x16_t> +inline u8x16_t ShuffleAndPackComponents(i32x4_t bbbb1234, i32x4_t gggg1234, + i32x4_t rrrr1234, + const i32x4_t& aaaa1234) { + // bbbb1234, gggg1234, rrrr1234, aaaa1234 -> bgrabgrabgrabgra1234 + i16x8_t bbbbgggg1234 = simd::PackAndSaturate32To16(bbbb1234, gggg1234); + i16x8_t rrrraaaa1234 = simd::PackAndSaturate32To16(rrrr1234, aaaa1234); + i16x8_t brbrbrbr1234 = simd::InterleaveLo16(bbbbgggg1234, rrrraaaa1234); + i16x8_t gagagaga1234 = simd::InterleaveHi16(bbbbgggg1234, rrrraaaa1234); + i16x8_t bgrabgra12 = simd::InterleaveLo16(brbrbrbr1234, gagagaga1234); + i16x8_t bgrabgra34 = simd::InterleaveHi16(brbrbrbr1234, gagagaga1234); + return simd::PackAndSaturate16To8(bgrabgra12, bgrabgra34); +} + +template <typename i32x4_t, typename i16x8_t, typename u8x16_t, BlendMode mode> +inline void ApplyBlending_SIMD(const DataSourceSurface::ScopedMap& aInputMap1, + const DataSourceSurface::ScopedMap& aInputMap2, + const DataSourceSurface::ScopedMap& aOutputMap, + const IntSize& aSize) { + uint8_t* source1Data = aInputMap1.GetData(); + uint8_t* source2Data = aInputMap2.GetData(); + uint8_t* targetData = aOutputMap.GetData(); + int32_t targetStride = aOutputMap.GetStride(); + int32_t source1Stride = aInputMap1.GetStride(); + int32_t source2Stride = aInputMap2.GetStride(); + + for (int32_t y = 0; y < aSize.height; y++) { + for (int32_t x = 0; x < aSize.width; x += 4) { + int32_t targetIndex = y * targetStride + 4 * x; + int32_t source1Index = y * source1Stride + 4 * x; + int32_t source2Index = y * source2Stride + 4 * x; + + u8x16_t s1234 = simd::Load8<u8x16_t>(&source2Data[source2Index]); + u8x16_t d1234 = simd::Load8<u8x16_t>(&source1Data[source1Index]); + + // The blending calculation for the RGB channels all need access to the + // alpha channel of their pixel, and the alpha calculation is different, + // so it makes sense to separate by channel. + + i16x8_t s_bbbbgggg1234, s_rrrraaaa1234; + i16x8_t d_bbbbgggg1234, d_rrrraaaa1234; + UnpackAndShuffleComponents(s1234, s_bbbbgggg1234, s_rrrraaaa1234); + UnpackAndShuffleComponents(d1234, d_bbbbgggg1234, d_rrrraaaa1234); + i16x8_t s_aaaaaaaa1234 = simd::Shuffle32<3, 2, 3, 2>(s_rrrraaaa1234); + i16x8_t d_aaaaaaaa1234 = simd::Shuffle32<3, 2, 3, 2>(d_rrrraaaa1234); + + // We only use blendedB, blendedG and blendedR. + i32x4_t blendedB, blendedG, blendedR, blendedA; + BlendTwoComponentsOfFourPixels<i16x8_t, i32x4_t, mode>( + s_bbbbgggg1234, s_aaaaaaaa1234, d_bbbbgggg1234, d_aaaaaaaa1234, + blendedB, blendedG); + BlendTwoComponentsOfFourPixels<i16x8_t, i32x4_t, mode>( + s_rrrraaaa1234, s_aaaaaaaa1234, d_rrrraaaa1234, d_aaaaaaaa1234, + blendedR, blendedA); + + // Throw away blendedA and overwrite it with the correct blended alpha. + blendedA = BlendAlphaOfFourPixels<i16x8_t, i32x4_t>(s_rrrraaaa1234, + d_rrrraaaa1234); + + u8x16_t result1234 = ShuffleAndPackComponents<i32x4_t, i16x8_t, u8x16_t>( + blendedB, blendedG, blendedR, blendedA); + simd::Store8(&targetData[targetIndex], result1234); + } + } +} + +template <typename i32x4_t, typename i16x8_t, typename u8x16_t, BlendMode mode> +inline already_AddRefed<DataSourceSurface> ApplyBlending_SIMD( + DataSourceSurface* aInput1, DataSourceSurface* aInput2) { + IntSize size = aInput1->GetSize(); + RefPtr<DataSourceSurface> target = + Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8); + if (!target) { + return nullptr; + } + + DataSourceSurface::ScopedMap inputMap1(aInput1, DataSourceSurface::READ); + DataSourceSurface::ScopedMap outputMap(target, DataSourceSurface::READ_WRITE); + if (aInput1->Equals(aInput2)) { + ApplyBlending_SIMD<i32x4_t, i16x8_t, u8x16_t, mode>(inputMap1, inputMap1, + outputMap, size); + } else { + DataSourceSurface::ScopedMap inputMap2(aInput2, DataSourceSurface::READ); + ApplyBlending_SIMD<i32x4_t, i16x8_t, u8x16_t, mode>(inputMap1, inputMap2, + outputMap, size); + } + + return target.forget(); +} + +template <typename i32x4_t, typename i16x8_t, typename u8x16_t> +static already_AddRefed<DataSourceSurface> ApplyBlending_SIMD( + DataSourceSurface* aInput1, DataSourceSurface* aInput2, + BlendMode aBlendMode) { + switch (aBlendMode) { + case BLEND_MODE_MULTIPLY: + return ApplyBlending_SIMD<i32x4_t, i16x8_t, u8x16_t, BLEND_MODE_MULTIPLY>( + aInput1, aInput2); + case BLEND_MODE_SCREEN: + return ApplyBlending_SIMD<i32x4_t, i16x8_t, u8x16_t, BLEND_MODE_SCREEN>( + aInput1, aInput2); + case BLEND_MODE_DARKEN: + return ApplyBlending_SIMD<i32x4_t, i16x8_t, u8x16_t, BLEND_MODE_DARKEN>( + aInput1, aInput2); + case BLEND_MODE_LIGHTEN: + return ApplyBlending_SIMD<i32x4_t, i16x8_t, u8x16_t, BLEND_MODE_LIGHTEN>( + aInput1, aInput2); + default: + return nullptr; + } +} + +template <MorphologyOperator Operator, typename u8x16_t> +static u8x16_t Morph8(u8x16_t a, u8x16_t b) { + return Operator == MORPHOLOGY_OPERATOR_ERODE ? simd::Min8(a, b) + : simd::Max8(a, b); +} + +// Set every pixel to the per-component minimum or maximum of the pixels around +// it that are up to aRadius pixels away from it (horizontally). +template <MorphologyOperator op, typename i16x8_t, typename u8x16_t> +inline void ApplyMorphologyHorizontal_SIMD( + uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius) { + static_assert( + op == MORPHOLOGY_OPERATOR_ERODE || op == MORPHOLOGY_OPERATOR_DILATE, + "unexpected morphology operator"); + + int32_t kernelSize = aRadius + 1 + aRadius; + MOZ_ASSERT(kernelSize >= 3, "don't call this with aRadius <= 0"); + MOZ_ASSERT(kernelSize % 4 == 1 || kernelSize % 4 == 3); + int32_t completeKernelSizeForFourPixels = kernelSize + 3; + MOZ_ASSERT(completeKernelSizeForFourPixels % 4 == 0 || + completeKernelSizeForFourPixels % 4 == 2); + + // aSourceData[-aRadius] and aDestData[0] are both aligned to 16 bytes, just + // the way we need them to be. + + IntRect sourceRect = aDestRect; + sourceRect.Inflate(aRadius, 0); + + for (int32_t y = aDestRect.Y(); y < aDestRect.YMost(); y++) { + int32_t kernelStartX = aDestRect.X() - aRadius; + for (int32_t x = aDestRect.X(); x < aDestRect.XMost(); + x += 4, kernelStartX += 4) { + // We process four pixels (16 color values) at a time. + // aSourceData[0] points to the pixel located at aDestRect.TopLeft(); + // source values can be read beyond that because the source is extended + // by aRadius pixels. + + int32_t sourceIndex = y * aSourceStride + 4 * kernelStartX; + u8x16_t p1234 = simd::Load8<u8x16_t>(&aSourceData[sourceIndex]); + u8x16_t m1234 = p1234; + + for (int32_t i = 4; i < completeKernelSizeForFourPixels; i += 4) { + u8x16_t p5678 = + (kernelStartX + i < sourceRect.XMost()) + ? simd::Load8<u8x16_t>(&aSourceData[sourceIndex + 4 * i]) + : simd::FromZero8<u8x16_t>(); + u8x16_t p2345 = simd::Rotate8<4>(p1234, p5678); + u8x16_t p3456 = simd::Rotate8<8>(p1234, p5678); + m1234 = Morph8<op, u8x16_t>(m1234, p2345); + m1234 = Morph8<op, u8x16_t>(m1234, p3456); + if (i + 2 < completeKernelSizeForFourPixels) { + u8x16_t p4567 = simd::Rotate8<12>(p1234, p5678); + m1234 = Morph8<op, u8x16_t>(m1234, p4567); + m1234 = Morph8<op, u8x16_t>(m1234, p5678); + } + p1234 = p5678; + } + + int32_t destIndex = y * aDestStride + 4 * x; + simd::Store8(&aDestData[destIndex], m1234); + } + } +} + +template <typename i16x8_t, typename u8x16_t> +inline void ApplyMorphologyHorizontal_SIMD( + uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOp) { + if (aOp == MORPHOLOGY_OPERATOR_ERODE) { + ApplyMorphologyHorizontal_SIMD<MORPHOLOGY_OPERATOR_ERODE, i16x8_t, u8x16_t>( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius); + } else { + ApplyMorphologyHorizontal_SIMD<MORPHOLOGY_OPERATOR_DILATE, i16x8_t, + u8x16_t>( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius); + } +} + +// Set every pixel to the per-component minimum or maximum of the pixels around +// it that are up to aRadius pixels away from it (vertically). +template <MorphologyOperator op, typename i16x8_t, typename u8x16_t> +static void ApplyMorphologyVertical_SIMD( + uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius) { + static_assert( + op == MORPHOLOGY_OPERATOR_ERODE || op == MORPHOLOGY_OPERATOR_DILATE, + "unexpected morphology operator"); + + int32_t startY = aDestRect.Y() - aRadius; + int32_t endY = aDestRect.Y() + aRadius; + for (int32_t y = aDestRect.Y(); y < aDestRect.YMost(); + y++, startY++, endY++) { + for (int32_t x = aDestRect.X(); x < aDestRect.XMost(); x += 4) { + int32_t sourceIndex = startY * aSourceStride + 4 * x; + u8x16_t u = simd::Load8<u8x16_t>(&aSourceData[sourceIndex]); + sourceIndex += aSourceStride; + for (int32_t iy = startY + 1; iy <= endY; + iy++, sourceIndex += aSourceStride) { + u8x16_t u2 = simd::Load8<u8x16_t>(&aSourceData[sourceIndex]); + u = Morph8<op, u8x16_t>(u, u2); + } + + int32_t destIndex = y * aDestStride + 4 * x; + simd::Store8(&aDestData[destIndex], u); + } + } +} + +template <typename i16x8_t, typename u8x16_t> +inline void ApplyMorphologyVertical_SIMD( + uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOp) { + if (aOp == MORPHOLOGY_OPERATOR_ERODE) { + ApplyMorphologyVertical_SIMD<MORPHOLOGY_OPERATOR_ERODE, i16x8_t, u8x16_t>( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius); + } else { + ApplyMorphologyVertical_SIMD<MORPHOLOGY_OPERATOR_DILATE, i16x8_t, u8x16_t>( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius); + } +} + +template <typename i32x4_t, typename i16x8_t> +static i32x4_t ColorMatrixMultiply(i16x8_t p, i16x8_t rows_bg, i16x8_t rows_ra, + const i32x4_t& bias) { + // int16_t p[8] == { b, g, r, a, b, g, r, a }. + // int16_t rows_bg[8] == { bB, bG, bR, bA, gB, gG, gR, gA }. + // int16_t rows_ra[8] == { rB, rG, rR, rA, aB, aG, aR, aA }. + // int32_t bias[4] == { _B, _G, _R, _A }. + + i32x4_t sum = bias; + + // int16_t bg[8] = { b, g, b, g, b, g, b, g }; + i16x8_t bg = simd::ShuffleHi16<1, 0, 1, 0>(simd::ShuffleLo16<1, 0, 1, 0>(p)); + // int32_t prodsum_bg[4] = + // { b * bB + g * gB, b * bG + g * gG, b * bR + g * gR, b * bA + g * gA } + i32x4_t prodsum_bg = simd::MulAdd16x8x2To32x4(bg, rows_bg); + sum = simd::Add32(sum, prodsum_bg); + + // uint16_t ra[8] = { r, a, r, a, r, a, r, a }; + i16x8_t ra = simd::ShuffleHi16<3, 2, 3, 2>(simd::ShuffleLo16<3, 2, 3, 2>(p)); + // int32_t prodsum_ra[4] = + // { r * rB + a * aB, r * rG + a * aG, r * rR + a * aR, r * rA + a * aA } + i32x4_t prodsum_ra = simd::MulAdd16x8x2To32x4(ra, rows_ra); + sum = simd::Add32(sum, prodsum_ra); + + // int32_t sum[4] == { b * bB + g * gB + r * rB + a * aB + _B, ... }. + return sum; +} + +template <typename i32x4_t, typename i16x8_t, typename u8x16_t> +static already_AddRefed<DataSourceSurface> ApplyColorMatrix_SIMD( + DataSourceSurface* aInput, const Matrix5x4& aMatrix) { + IntSize size = aInput->GetSize(); + RefPtr<DataSourceSurface> target = + Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8); + if (!target) { + return nullptr; + } + + DataSourceSurface::ScopedMap inputMap(aInput, DataSourceSurface::READ); + DataSourceSurface::ScopedMap outputMap(target, DataSourceSurface::READ_WRITE); + + uint8_t* sourceData = inputMap.GetData(); + uint8_t* targetData = outputMap.GetData(); + int32_t sourceStride = inputMap.GetStride(); + int32_t targetStride = outputMap.GetStride(); + + const int16_t factor = 128; + const Float floatElementMax = INT16_MAX / factor; // 255 + MOZ_ASSERT((floatElementMax * factor) <= INT16_MAX, + "badly chosen float-to-int scale"); + + const Float* floats = &aMatrix._11; + + ptrdiff_t componentOffsets[4] = { + B8G8R8A8_COMPONENT_BYTEOFFSET_R, B8G8R8A8_COMPONENT_BYTEOFFSET_G, + B8G8R8A8_COMPONENT_BYTEOFFSET_B, B8G8R8A8_COMPONENT_BYTEOFFSET_A}; + + // We store the color matrix in rows_bgra in the following format: + // { bB, bG, bR, bA, gB, gG, gR, gA }. + // { bB, gB, bG, gG, bR, gR, bA, gA } + // The way this is interleaved allows us to use the intrinsic _mm_madd_epi16 + // which works especially well for our use case. + int16_t rows_bgra[2][8]; + for (size_t rowIndex = 0; rowIndex < 4; rowIndex++) { + for (size_t colIndex = 0; colIndex < 4; colIndex++) { + const Float& floatMatrixElement = floats[rowIndex * 4 + colIndex]; + Float clampedFloatMatrixElement = std::min( + std::max(floatMatrixElement, -floatElementMax), floatElementMax); + int16_t scaledIntMatrixElement = + int16_t(clampedFloatMatrixElement * factor + 0.5); + int8_t bg_or_ra = componentOffsets[rowIndex] / 2; + int8_t g_or_a = componentOffsets[rowIndex] % 2; + int8_t B_or_G_or_R_or_A = componentOffsets[colIndex]; + rows_bgra[bg_or_ra][B_or_G_or_R_or_A * 2 + g_or_a] = + scaledIntMatrixElement; + } + } + + int32_t rowBias[4]; + Float biasMax = (INT32_MAX - 4 * 255 * INT16_MAX) / (factor * 255); + for (size_t colIndex = 0; colIndex < 4; colIndex++) { + size_t rowIndex = 4; + const Float& floatMatrixElement = floats[rowIndex * 4 + colIndex]; + Float clampedFloatMatrixElement = + std::min(std::max(floatMatrixElement, -biasMax), biasMax); + int32_t scaledIntMatrixElement = + int32_t(clampedFloatMatrixElement * factor * 255 + 0.5); + rowBias[componentOffsets[colIndex]] = scaledIntMatrixElement; + } + + i16x8_t row_bg_v = simd::FromI16<i16x8_t>( + rows_bgra[0][0], rows_bgra[0][1], rows_bgra[0][2], rows_bgra[0][3], + rows_bgra[0][4], rows_bgra[0][5], rows_bgra[0][6], rows_bgra[0][7]); + + i16x8_t row_ra_v = simd::FromI16<i16x8_t>( + rows_bgra[1][0], rows_bgra[1][1], rows_bgra[1][2], rows_bgra[1][3], + rows_bgra[1][4], rows_bgra[1][5], rows_bgra[1][6], rows_bgra[1][7]); + + i32x4_t rowsBias_v = + simd::From32<i32x4_t>(rowBias[0], rowBias[1], rowBias[2], rowBias[3]); + + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.width; x += 4) { + MOZ_ASSERT(sourceStride >= 4 * (x + 4), + "need to be able to read 4 pixels at this position"); + MOZ_ASSERT(targetStride >= 4 * (x + 4), + "need to be able to write 4 pixels at this position"); + int32_t sourceIndex = y * sourceStride + 4 * x; + int32_t targetIndex = y * targetStride + 4 * x; + + // We load 4 pixels, unpack them, process them 1 pixel at a time, and + // finally pack and store the 4 result pixels. + + u8x16_t p1234 = simd::Load8<u8x16_t>(&sourceData[sourceIndex]); + + // Splat needed to get each pixel twice into i16x8 + i16x8_t p11 = simd::UnpackLo8x8ToI16x8(simd::Splat32On8<0>(p1234)); + i16x8_t p22 = simd::UnpackLo8x8ToI16x8(simd::Splat32On8<1>(p1234)); + i16x8_t p33 = simd::UnpackLo8x8ToI16x8(simd::Splat32On8<2>(p1234)); + i16x8_t p44 = simd::UnpackLo8x8ToI16x8(simd::Splat32On8<3>(p1234)); + + i32x4_t result_p1 = + ColorMatrixMultiply(p11, row_bg_v, row_ra_v, rowsBias_v); + i32x4_t result_p2 = + ColorMatrixMultiply(p22, row_bg_v, row_ra_v, rowsBias_v); + i32x4_t result_p3 = + ColorMatrixMultiply(p33, row_bg_v, row_ra_v, rowsBias_v); + i32x4_t result_p4 = + ColorMatrixMultiply(p44, row_bg_v, row_ra_v, rowsBias_v); + + static_assert(factor == 1 << 7, + "Please adapt the calculation in the lines below for a " + "different factor."); + u8x16_t result_p1234 = simd::PackAndSaturate32To8( + simd::ShiftRight32<7>(result_p1), simd::ShiftRight32<7>(result_p2), + simd::ShiftRight32<7>(result_p3), simd::ShiftRight32<7>(result_p4)); + simd::Store8(&targetData[targetIndex], result_p1234); + } + } + + return target.forget(); +} + +// source / dest: bgra bgra +// sourceAlpha / destAlpha: aaaa aaaa +// result: bgra bgra +template <typename i32x4_t, typename u16x8_t, uint32_t aCompositeOperator> +static inline u16x8_t CompositeTwoPixels(u16x8_t source, u16x8_t sourceAlpha, + u16x8_t dest, + const u16x8_t& destAlpha) { + u16x8_t x255 = simd::FromU16<u16x8_t>(255); + + switch (aCompositeOperator) { + case COMPOSITE_OPERATOR_OVER: { + // val = dest * (255 - sourceAlpha) + source * 255; + u16x8_t twoFiftyFiveMinusSourceAlpha = simd::Sub16(x255, sourceAlpha); + + u16x8_t destSourceInterleaved1 = simd::InterleaveLo16(dest, source); + u16x8_t rightFactor1 = + simd::InterleaveLo16(twoFiftyFiveMinusSourceAlpha, x255); + i32x4_t result1 = + simd::MulAdd16x8x2To32x4(destSourceInterleaved1, rightFactor1); + + u16x8_t destSourceInterleaved2 = simd::InterleaveHi16(dest, source); + u16x8_t rightFactor2 = + simd::InterleaveHi16(twoFiftyFiveMinusSourceAlpha, x255); + i32x4_t result2 = + simd::MulAdd16x8x2To32x4(destSourceInterleaved2, rightFactor2); + + return simd::PackAndSaturate32ToU16(simd::FastDivideBy255(result1), + simd::FastDivideBy255(result2)); + } + + case COMPOSITE_OPERATOR_IN: { + // val = source * destAlpha; + return simd::FastDivideBy255_16(simd::Mul16(source, destAlpha)); + } + + case COMPOSITE_OPERATOR_OUT: { + // val = source * (255 - destAlpha); + u16x8_t prod = simd::Mul16(source, simd::Sub16(x255, destAlpha)); + return simd::FastDivideBy255_16(prod); + } + + case COMPOSITE_OPERATOR_ATOP: { + // val = dest * (255 - sourceAlpha) + source * destAlpha; + u16x8_t twoFiftyFiveMinusSourceAlpha = simd::Sub16(x255, sourceAlpha); + + u16x8_t destSourceInterleaved1 = simd::InterleaveLo16(dest, source); + u16x8_t rightFactor1 = + simd::InterleaveLo16(twoFiftyFiveMinusSourceAlpha, destAlpha); + i32x4_t result1 = + simd::MulAdd16x8x2To32x4(destSourceInterleaved1, rightFactor1); + + u16x8_t destSourceInterleaved2 = simd::InterleaveHi16(dest, source); + u16x8_t rightFactor2 = + simd::InterleaveHi16(twoFiftyFiveMinusSourceAlpha, destAlpha); + i32x4_t result2 = + simd::MulAdd16x8x2To32x4(destSourceInterleaved2, rightFactor2); + + return simd::PackAndSaturate32ToU16(simd::FastDivideBy255(result1), + simd::FastDivideBy255(result2)); + } + + case COMPOSITE_OPERATOR_XOR: { + // val = dest * (255 - sourceAlpha) + source * (255 - destAlpha); + u16x8_t twoFiftyFiveMinusSourceAlpha = simd::Sub16(x255, sourceAlpha); + u16x8_t twoFiftyFiveMinusDestAlpha = simd::Sub16(x255, destAlpha); + + u16x8_t destSourceInterleaved1 = simd::InterleaveLo16(dest, source); + u16x8_t rightFactor1 = simd::InterleaveLo16(twoFiftyFiveMinusSourceAlpha, + twoFiftyFiveMinusDestAlpha); + i32x4_t result1 = + simd::MulAdd16x8x2To32x4(destSourceInterleaved1, rightFactor1); + + u16x8_t destSourceInterleaved2 = simd::InterleaveHi16(dest, source); + u16x8_t rightFactor2 = simd::InterleaveHi16(twoFiftyFiveMinusSourceAlpha, + twoFiftyFiveMinusDestAlpha); + i32x4_t result2 = + simd::MulAdd16x8x2To32x4(destSourceInterleaved2, rightFactor2); + + return simd::PackAndSaturate32ToU16(simd::FastDivideBy255(result1), + simd::FastDivideBy255(result2)); + } + + case COMPOSITE_OPERATOR_LIGHTER: { + // val = dest * sourceAlpha + source * destAlpha; + u16x8_t destSourceInterleaved1 = simd::InterleaveLo16(dest, source); + u16x8_t rightFactor1 = simd::InterleaveLo16(sourceAlpha, destAlpha); + i32x4_t result1 = + simd::MulAdd16x8x2To32x4(destSourceInterleaved1, rightFactor1); + + u16x8_t destSourceInterleaved2 = simd::InterleaveHi16(dest, source); + u16x8_t rightFactor2 = simd::InterleaveHi16(sourceAlpha, destAlpha); + i32x4_t result2 = + simd::MulAdd16x8x2To32x4(destSourceInterleaved2, rightFactor2); + + return simd::PackAndSaturate32ToU16(simd::FastDivideBy255(result1), + simd::FastDivideBy255(result2)); + } + + default: + return simd::FromU16<u16x8_t>(0); + } +} + +template <typename i32x4_t, typename u16x8_t, typename u8x16_t, uint32_t op> +static void ApplyComposition(DataSourceSurface* aSource, + DataSourceSurface* aDest) { + IntSize size = aDest->GetSize(); + + DataSourceSurface::ScopedMap input(aSource, DataSourceSurface::READ); + DataSourceSurface::ScopedMap output(aDest, DataSourceSurface::READ_WRITE); + + uint8_t* sourceData = input.GetData(); + uint8_t* destData = output.GetData(); + uint32_t sourceStride = input.GetStride(); + uint32_t destStride = output.GetStride(); + + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.width; x += 4) { + uint32_t sourceIndex = y * sourceStride + 4 * x; + uint32_t destIndex = y * destStride + 4 * x; + + u8x16_t s1234 = simd::Load8<u8x16_t>(&sourceData[sourceIndex]); + u8x16_t d1234 = simd::Load8<u8x16_t>(&destData[destIndex]); + + u16x8_t s12 = simd::UnpackLo8x8ToU16x8(s1234); + u16x8_t d12 = simd::UnpackLo8x8ToU16x8(d1234); + u16x8_t sa12 = simd::Splat16<3, 3>(s12); + u16x8_t da12 = simd::Splat16<3, 3>(d12); + u16x8_t result12 = + CompositeTwoPixels<i32x4_t, u16x8_t, op>(s12, sa12, d12, da12); + + u16x8_t s34 = simd::UnpackHi8x8ToU16x8(s1234); + u16x8_t d34 = simd::UnpackHi8x8ToU16x8(d1234); + u16x8_t sa34 = simd::Splat16<3, 3>(s34); + u16x8_t da34 = simd::Splat16<3, 3>(d34); + u16x8_t result34 = + CompositeTwoPixels<i32x4_t, u16x8_t, op>(s34, sa34, d34, da34); + + u8x16_t result1234 = simd::PackAndSaturate16To8(result12, result34); + simd::Store8(&destData[destIndex], result1234); + } + } +} + +template <typename i32x4_t, typename i16x8_t, typename u8x16_t> +static void ApplyComposition_SIMD(DataSourceSurface* aSource, + DataSourceSurface* aDest, + CompositeOperator aOperator) { + switch (aOperator) { + case COMPOSITE_OPERATOR_OVER: + ApplyComposition<i32x4_t, i16x8_t, u8x16_t, COMPOSITE_OPERATOR_OVER>( + aSource, aDest); + break; + case COMPOSITE_OPERATOR_IN: + ApplyComposition<i32x4_t, i16x8_t, u8x16_t, COMPOSITE_OPERATOR_IN>( + aSource, aDest); + break; + case COMPOSITE_OPERATOR_OUT: + ApplyComposition<i32x4_t, i16x8_t, u8x16_t, COMPOSITE_OPERATOR_OUT>( + aSource, aDest); + break; + case COMPOSITE_OPERATOR_ATOP: + ApplyComposition<i32x4_t, i16x8_t, u8x16_t, COMPOSITE_OPERATOR_ATOP>( + aSource, aDest); + break; + case COMPOSITE_OPERATOR_XOR: + ApplyComposition<i32x4_t, i16x8_t, u8x16_t, COMPOSITE_OPERATOR_XOR>( + aSource, aDest); + break; + case COMPOSITE_OPERATOR_LIGHTER: + ApplyComposition<i32x4_t, i16x8_t, u8x16_t, COMPOSITE_OPERATOR_LIGHTER>( + aSource, aDest); + break; + default: + MOZ_CRASH("GFX: Incomplete switch"); + } +} + +template <typename u8x16_t> +static void SeparateColorChannels_SIMD( + const IntSize& size, uint8_t* sourceData, int32_t sourceStride, + uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, + uint8_t* channel3Data, int32_t channelStride) { + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.width; x += 16) { + // Process 16 pixels at a time. + int32_t sourceIndex = y * sourceStride + 4 * x; + int32_t targetIndex = y * channelStride + x; + + u8x16_t bgrabgrabgrabgra2 = simd::FromZero8<u8x16_t>(); + u8x16_t bgrabgrabgrabgra3 = simd::FromZero8<u8x16_t>(); + u8x16_t bgrabgrabgrabgra4 = simd::FromZero8<u8x16_t>(); + + u8x16_t bgrabgrabgrabgra1 = + simd::Load8<u8x16_t>(&sourceData[sourceIndex]); + if (4 * (x + 4) < sourceStride) { + bgrabgrabgrabgra2 = + simd::Load8<u8x16_t>(&sourceData[sourceIndex + 4 * 4]); + } + if (4 * (x + 8) < sourceStride) { + bgrabgrabgrabgra3 = + simd::Load8<u8x16_t>(&sourceData[sourceIndex + 4 * 8]); + } + if (4 * (x + 12) < sourceStride) { + bgrabgrabgrabgra4 = + simd::Load8<u8x16_t>(&sourceData[sourceIndex + 4 * 12]); + } + + u8x16_t bbggrraabbggrraa1 = + simd::InterleaveLo8(bgrabgrabgrabgra1, bgrabgrabgrabgra3); + u8x16_t bbggrraabbggrraa2 = + simd::InterleaveHi8(bgrabgrabgrabgra1, bgrabgrabgrabgra3); + u8x16_t bbggrraabbggrraa3 = + simd::InterleaveLo8(bgrabgrabgrabgra2, bgrabgrabgrabgra4); + u8x16_t bbggrraabbggrraa4 = + simd::InterleaveHi8(bgrabgrabgrabgra2, bgrabgrabgrabgra4); + u8x16_t bbbbggggrrrraaaa1 = + simd::InterleaveLo8(bbggrraabbggrraa1, bbggrraabbggrraa3); + u8x16_t bbbbggggrrrraaaa2 = + simd::InterleaveHi8(bbggrraabbggrraa1, bbggrraabbggrraa3); + u8x16_t bbbbggggrrrraaaa3 = + simd::InterleaveLo8(bbggrraabbggrraa2, bbggrraabbggrraa4); + u8x16_t bbbbggggrrrraaaa4 = + simd::InterleaveHi8(bbggrraabbggrraa2, bbggrraabbggrraa4); + u8x16_t bbbbbbbbgggggggg1 = + simd::InterleaveLo8(bbbbggggrrrraaaa1, bbbbggggrrrraaaa3); + u8x16_t rrrrrrrraaaaaaaa1 = + simd::InterleaveHi8(bbbbggggrrrraaaa1, bbbbggggrrrraaaa3); + u8x16_t bbbbbbbbgggggggg2 = + simd::InterleaveLo8(bbbbggggrrrraaaa2, bbbbggggrrrraaaa4); + u8x16_t rrrrrrrraaaaaaaa2 = + simd::InterleaveHi8(bbbbggggrrrraaaa2, bbbbggggrrrraaaa4); + u8x16_t bbbbbbbbbbbbbbbb = + simd::InterleaveLo8(bbbbbbbbgggggggg1, bbbbbbbbgggggggg2); + u8x16_t gggggggggggggggg = + simd::InterleaveHi8(bbbbbbbbgggggggg1, bbbbbbbbgggggggg2); + u8x16_t rrrrrrrrrrrrrrrr = + simd::InterleaveLo8(rrrrrrrraaaaaaaa1, rrrrrrrraaaaaaaa2); + u8x16_t aaaaaaaaaaaaaaaa = + simd::InterleaveHi8(rrrrrrrraaaaaaaa1, rrrrrrrraaaaaaaa2); + + simd::Store8(&channel0Data[targetIndex], bbbbbbbbbbbbbbbb); + simd::Store8(&channel1Data[targetIndex], gggggggggggggggg); + simd::Store8(&channel2Data[targetIndex], rrrrrrrrrrrrrrrr); + simd::Store8(&channel3Data[targetIndex], aaaaaaaaaaaaaaaa); + } + } +} + +template <typename u8x16_t> +static void CombineColorChannels_SIMD( + const IntSize& size, int32_t resultStride, uint8_t* resultData, + int32_t channelStride, uint8_t* channel0Data, uint8_t* channel1Data, + uint8_t* channel2Data, uint8_t* channel3Data) { + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.width; x += 16) { + // Process 16 pixels at a time. + int32_t resultIndex = y * resultStride + 4 * x; + int32_t channelIndex = y * channelStride + x; + + u8x16_t bbbbbbbbbbbbbbbb = + simd::Load8<u8x16_t>(&channel0Data[channelIndex]); + u8x16_t gggggggggggggggg = + simd::Load8<u8x16_t>(&channel1Data[channelIndex]); + u8x16_t rrrrrrrrrrrrrrrr = + simd::Load8<u8x16_t>(&channel2Data[channelIndex]); + u8x16_t aaaaaaaaaaaaaaaa = + simd::Load8<u8x16_t>(&channel3Data[channelIndex]); + + u8x16_t brbrbrbrbrbrbrbr1 = + simd::InterleaveLo8(bbbbbbbbbbbbbbbb, rrrrrrrrrrrrrrrr); + u8x16_t brbrbrbrbrbrbrbr2 = + simd::InterleaveHi8(bbbbbbbbbbbbbbbb, rrrrrrrrrrrrrrrr); + u8x16_t gagagagagagagaga1 = + simd::InterleaveLo8(gggggggggggggggg, aaaaaaaaaaaaaaaa); + u8x16_t gagagagagagagaga2 = + simd::InterleaveHi8(gggggggggggggggg, aaaaaaaaaaaaaaaa); + + u8x16_t bgrabgrabgrabgra1 = + simd::InterleaveLo8(brbrbrbrbrbrbrbr1, gagagagagagagaga1); + u8x16_t bgrabgrabgrabgra2 = + simd::InterleaveHi8(brbrbrbrbrbrbrbr1, gagagagagagagaga1); + u8x16_t bgrabgrabgrabgra3 = + simd::InterleaveLo8(brbrbrbrbrbrbrbr2, gagagagagagagaga2); + u8x16_t bgrabgrabgrabgra4 = + simd::InterleaveHi8(brbrbrbrbrbrbrbr2, gagagagagagagaga2); + + simd::Store8(&resultData[resultIndex], bgrabgrabgrabgra1); + if (4 * (x + 4) < resultStride) { + simd::Store8(&resultData[resultIndex + 4 * 4], bgrabgrabgrabgra2); + } + if (4 * (x + 8) < resultStride) { + simd::Store8(&resultData[resultIndex + 8 * 4], bgrabgrabgrabgra3); + } + if (4 * (x + 12) < resultStride) { + simd::Store8(&resultData[resultIndex + 12 * 4], bgrabgrabgrabgra4); + } + } + } +} + +template <typename i32x4_t, typename u16x8_t, typename u8x16_t> +static void DoPremultiplicationCalculation_SIMD(const IntSize& aSize, + uint8_t* aTargetData, + int32_t aTargetStride, + uint8_t* aSourceData, + int32_t aSourceStride) { + const u8x16_t alphaMask = simd::From8<u8x16_t>(0, 0, 0, 0xff, 0, 0, 0, 0xff, + 0, 0, 0, 0xff, 0, 0, 0, 0xff); + for (int32_t y = 0; y < aSize.height; y++) { + for (int32_t x = 0; x < aSize.width; x += 4) { + int32_t inputIndex = y * aSourceStride + 4 * x; + int32_t targetIndex = y * aTargetStride + 4 * x; + + u8x16_t p1234 = simd::Load8<u8x16_t>(&aSourceData[inputIndex]); + u16x8_t p12 = simd::UnpackLo8x8ToU16x8(p1234); + u16x8_t p34 = simd::UnpackHi8x8ToU16x8(p1234); + + // Multiply all components with alpha. + p12 = simd::Mul16(p12, simd::Splat16<3, 3>(p12)); + p34 = simd::Mul16(p34, simd::Splat16<3, 3>(p34)); + + // Divide by 255 and pack. + u8x16_t result = simd::PackAndSaturate16To8( + simd::FastDivideBy255_16(p12), simd::FastDivideBy255_16(p34)); + + // Get the original alpha channel value back from p1234. + result = simd::Pick(alphaMask, result, p1234); + + simd::Store8(&aTargetData[targetIndex], result); + } + } +} + +// We use a table of precomputed factors for unpremultiplying. +// We want to compute round(r / (alpha / 255.0f)) for arbitrary values of +// r and alpha in constant time. This table of factors has the property that +// (r * sAlphaFactors[alpha] + 128) >> 8 roughly gives the result we want (with +// a maximum deviation of 1). +// +// sAlphaFactors[alpha] == round(255.0 * (1 << 8) / alpha) +// +// This table has been created using the python code +// ", ".join("%d" % (round(255.0 * 256 / alpha) if alpha > 0 else 0) for alpha +// in range(256)) +static const uint16_t sAlphaFactors[256] = { + 0, 65280, 32640, 21760, 16320, 13056, 10880, 9326, 8160, 7253, 6528, + 5935, 5440, 5022, 4663, 4352, 4080, 3840, 3627, 3436, 3264, 3109, + 2967, 2838, 2720, 2611, 2511, 2418, 2331, 2251, 2176, 2106, 2040, + 1978, 1920, 1865, 1813, 1764, 1718, 1674, 1632, 1592, 1554, 1518, + 1484, 1451, 1419, 1389, 1360, 1332, 1306, 1280, 1255, 1232, 1209, + 1187, 1166, 1145, 1126, 1106, 1088, 1070, 1053, 1036, 1020, 1004, + 989, 974, 960, 946, 933, 919, 907, 894, 882, 870, 859, + 848, 837, 826, 816, 806, 796, 787, 777, 768, 759, 750, + 742, 733, 725, 717, 710, 702, 694, 687, 680, 673, 666, + 659, 653, 646, 640, 634, 628, 622, 616, 610, 604, 599, + 593, 588, 583, 578, 573, 568, 563, 558, 553, 549, 544, + 540, 535, 531, 526, 522, 518, 514, 510, 506, 502, 498, + 495, 491, 487, 484, 480, 476, 473, 470, 466, 463, 460, + 457, 453, 450, 447, 444, 441, 438, 435, 432, 429, 427, + 424, 421, 418, 416, 413, 411, 408, 405, 403, 400, 398, + 396, 393, 391, 389, 386, 384, 382, 380, 377, 375, 373, + 371, 369, 367, 365, 363, 361, 359, 357, 355, 353, 351, + 349, 347, 345, 344, 342, 340, 338, 336, 335, 333, 331, + 330, 328, 326, 325, 323, 322, 320, 318, 317, 315, 314, + 312, 311, 309, 308, 306, 305, 304, 302, 301, 299, 298, + 297, 295, 294, 293, 291, 290, 289, 288, 286, 285, 284, + 283, 281, 280, 279, 278, 277, 275, 274, 273, 272, 271, + 270, 269, 268, 266, 265, 264, 263, 262, 261, 260, 259, + 258, 257, 256}; + +template <typename u16x8_t, typename u8x16_t> +static void DoUnpremultiplicationCalculation_SIMD(const IntSize& aSize, + uint8_t* aTargetData, + int32_t aTargetStride, + uint8_t* aSourceData, + int32_t aSourceStride) { + for (int32_t y = 0; y < aSize.height; y++) { + for (int32_t x = 0; x < aSize.width; x += 4) { + int32_t inputIndex = y * aSourceStride + 4 * x; + int32_t targetIndex = y * aTargetStride + 4 * x; + union { + u8x16_t p1234; + uint8_t u8[4][4]; + }; + p1234 = simd::Load8<u8x16_t>(&aSourceData[inputIndex]); + + // Prepare the alpha factors. + uint16_t aF1 = sAlphaFactors[u8[0][B8G8R8A8_COMPONENT_BYTEOFFSET_A]]; + uint16_t aF2 = sAlphaFactors[u8[1][B8G8R8A8_COMPONENT_BYTEOFFSET_A]]; + uint16_t aF3 = sAlphaFactors[u8[2][B8G8R8A8_COMPONENT_BYTEOFFSET_A]]; + uint16_t aF4 = sAlphaFactors[u8[3][B8G8R8A8_COMPONENT_BYTEOFFSET_A]]; + u16x8_t aF12 = + simd::FromU16<u16x8_t>(aF1, aF1, aF1, 1 << 8, aF2, aF2, aF2, 1 << 8); + u16x8_t aF34 = + simd::FromU16<u16x8_t>(aF3, aF3, aF3, 1 << 8, aF4, aF4, aF4, 1 << 8); + + u16x8_t p12 = simd::UnpackLo8x8ToU16x8(p1234); + u16x8_t p34 = simd::UnpackHi8x8ToU16x8(p1234); + + // Multiply with the alpha factors, add 128 for rounding, and shift right + // by 8 bits. + p12 = simd::ShiftRight16<8>( + simd::Add16(simd::Mul16(p12, aF12), simd::FromU16<u16x8_t>(128))); + p34 = simd::ShiftRight16<8>( + simd::Add16(simd::Mul16(p34, aF34), simd::FromU16<u16x8_t>(128))); + + u8x16_t result = simd::PackAndSaturate16To8(p12, p34); + simd::Store8(&aTargetData[targetIndex], result); + } + } +} + +template <typename u16x8_t, typename u8x16_t> +static void DoOpacityCalculation_SIMD(const IntSize& aSize, + uint8_t* aTargetData, + int32_t aTargetStride, + uint8_t* aSourceData, + int32_t aSourceStride, Float aOpacity) { + uint8_t alphaValue = uint8_t(roundf(255.f * aOpacity)); + u16x8_t alphaValues = + simd::FromU16<u16x8_t>(alphaValue, alphaValue, alphaValue, alphaValue, + alphaValue, alphaValue, alphaValue, alphaValue); + for (int32_t y = 0; y < aSize.height; y++) { + for (int32_t x = 0; x < aSize.width; x += 4) { + int32_t inputIndex = y * aSourceStride + 4 * x; + int32_t targetIndex = y * aTargetStride + 4 * x; + + u8x16_t p1234 = simd::Load8<u8x16_t>(&aSourceData[inputIndex]); + u16x8_t p12 = simd::UnpackLo8x8ToU16x8(p1234); + u16x8_t p34 = simd::UnpackHi8x8ToU16x8(p1234); + + // Multiply all components with alpha. + p12 = simd::Mul16(p12, alphaValues); + p34 = simd::Mul16(p34, alphaValues); + + // Divide by 255 and pack. + u8x16_t result = simd::PackAndSaturate16To8(simd::ShiftRight16<8>(p12), + simd::ShiftRight16<8>(p34)); + + simd::Store8(&aTargetData[targetIndex], result); + } + } +} + +template <typename f32x4_t, typename i32x4_t, typename u8x16_t> +static already_AddRefed<DataSourceSurface> RenderTurbulence_SIMD( + const IntSize& aSize, const Point& aOffset, const Size& aBaseFrequency, + int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, + const Rect& aTileRect) { +#define RETURN_TURBULENCE(Type, Stitch) \ + SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t> renderer( \ + aBaseFrequency, aSeed, aNumOctaves, aTileRect); \ + return renderer.Render(aSize, aOffset); + + switch (aType) { + case TURBULENCE_TYPE_TURBULENCE: { + if (aStitch) { + RETURN_TURBULENCE(TURBULENCE_TYPE_TURBULENCE, true); + } + RETURN_TURBULENCE(TURBULENCE_TYPE_TURBULENCE, false); + } + case TURBULENCE_TYPE_FRACTAL_NOISE: { + if (aStitch) { + RETURN_TURBULENCE(TURBULENCE_TYPE_FRACTAL_NOISE, true); + } + RETURN_TURBULENCE(TURBULENCE_TYPE_FRACTAL_NOISE, false); + } + } + return nullptr; +#undef RETURN_TURBULENCE +} + +// k1 * in1 * in2 + k2 * in1 + k3 * in2 + k4 +template <typename i32x4_t, typename i16x8_t> +static MOZ_ALWAYS_INLINE i16x8_t ArithmeticCombineTwoPixels( + i16x8_t in1, i16x8_t in2, const i16x8_t& k1And4, const i16x8_t& k2And3) { + // Calculate input product: inProd = (in1 * in2) / 255. + i32x4_t inProd_1, inProd_2; + simd::Mul16x4x2x2To32x4x2(in1, in2, inProd_1, inProd_2); + i16x8_t inProd = simd::PackAndSaturate32To16(simd::FastDivideBy255(inProd_1), + simd::FastDivideBy255(inProd_2)); + + // Calculate k1 * ((in1 * in2) / 255) + (k4/128) * 128 + i16x8_t oneTwentyEight = simd::FromI16<i16x8_t>(128); + i16x8_t inProd1AndOneTwentyEight = + simd::InterleaveLo16(inProd, oneTwentyEight); + i16x8_t inProd2AndOneTwentyEight = + simd::InterleaveHi16(inProd, oneTwentyEight); + i32x4_t inProdTimesK1PlusK4_1 = + simd::MulAdd16x8x2To32x4(k1And4, inProd1AndOneTwentyEight); + i32x4_t inProdTimesK1PlusK4_2 = + simd::MulAdd16x8x2To32x4(k1And4, inProd2AndOneTwentyEight); + + // Calculate k2 * in1 + k3 * in2 + i16x8_t in12_1 = simd::InterleaveLo16(in1, in2); + i16x8_t in12_2 = simd::InterleaveHi16(in1, in2); + i32x4_t inTimesK2K3_1 = simd::MulAdd16x8x2To32x4(k2And3, in12_1); + i32x4_t inTimesK2K3_2 = simd::MulAdd16x8x2To32x4(k2And3, in12_2); + + // Sum everything up and truncate the fractional part. + i32x4_t result_1 = + simd::ShiftRight32<7>(simd::Add32(inProdTimesK1PlusK4_1, inTimesK2K3_1)); + i32x4_t result_2 = + simd::ShiftRight32<7>(simd::Add32(inProdTimesK1PlusK4_2, inTimesK2K3_2)); + return simd::PackAndSaturate32To16(result_1, result_2); +} + +template <typename i32x4_t, typename i16x8_t, typename u8x16_t> +static void ApplyArithmeticCombine_SIMD( + const DataSourceSurface::ScopedMap& aInputMap1, + const DataSourceSurface::ScopedMap& aInputMap2, + const DataSourceSurface::ScopedMap& aOutputMap, const IntSize& aSize, + Float aK1, Float aK2, Float aK3, Float aK4) { + uint8_t* source1Data = aInputMap1.GetData(); + uint8_t* source2Data = aInputMap2.GetData(); + uint8_t* targetData = aOutputMap.GetData(); + uint32_t source1Stride = aInputMap1.GetStride(); + uint32_t source2Stride = aInputMap2.GetStride(); + uint32_t targetStride = aOutputMap.GetStride(); + + // The arithmetic combine filter does the following calculation: + // result = k1 * in1 * in2 + k2 * in1 + k3 * in2 + k4 + // + // Or, with in1/2 integers between 0 and 255: + // result = (k1 * in1 * in2) / 255 + k2 * in1 + k3 * in2 + k4 * 255 + // + // We want the whole calculation to happen in integer, with 16-bit factors. + // So we convert our factors to fixed-point with precision 1.8.7. + // K4 is premultiplied with 255, and it will be multiplied with 128 later + // during the actual calculation, because premultiplying it with 255 * 128 + // would overflow int16. + + i16x8_t k1 = simd::FromI16<i16x8_t>( + int16_t(floorf(std::min(std::max(aK1, -255.0f), 255.0f) * 128 + 0.5f))); + i16x8_t k2 = simd::FromI16<i16x8_t>( + int16_t(floorf(std::min(std::max(aK2, -255.0f), 255.0f) * 128 + 0.5f))); + i16x8_t k3 = simd::FromI16<i16x8_t>( + int16_t(floorf(std::min(std::max(aK3, -255.0f), 255.0f) * 128 + 0.5f))); + i16x8_t k4 = simd::FromI16<i16x8_t>( + int16_t(floorf(std::min(std::max(aK4, -128.0f), 128.0f) * 255 + 0.5f))); + + i16x8_t k1And4 = simd::InterleaveLo16(k1, k4); + i16x8_t k2And3 = simd::InterleaveLo16(k2, k3); + + for (int32_t y = 0; y < aSize.height; y++) { + for (int32_t x = 0; x < aSize.width; x += 4) { + uint32_t source1Index = y * source1Stride + 4 * x; + uint32_t source2Index = y * source2Stride + 4 * x; + uint32_t targetIndex = y * targetStride + 4 * x; + + // Load and unpack. + u8x16_t in1 = simd::Load8<u8x16_t>(&source1Data[source1Index]); + u8x16_t in2 = simd::Load8<u8x16_t>(&source2Data[source2Index]); + i16x8_t in1_12 = simd::UnpackLo8x8ToI16x8(in1); + i16x8_t in1_34 = simd::UnpackHi8x8ToI16x8(in1); + i16x8_t in2_12 = simd::UnpackLo8x8ToI16x8(in2); + i16x8_t in2_34 = simd::UnpackHi8x8ToI16x8(in2); + + // Multiply and add. + i16x8_t result_12 = ArithmeticCombineTwoPixels<i32x4_t, i16x8_t>( + in1_12, in2_12, k1And4, k2And3); + i16x8_t result_34 = ArithmeticCombineTwoPixels<i32x4_t, i16x8_t>( + in1_34, in2_34, k1And4, k2And3); + + // Pack and store. + simd::Store8(&targetData[targetIndex], + simd::PackAndSaturate16To8(result_12, result_34)); + } + } +} + +template <typename i32x4_t, typename i16x8_t, typename u8x16_t> +static already_AddRefed<DataSourceSurface> ApplyArithmeticCombine_SIMD( + DataSourceSurface* aInput1, DataSourceSurface* aInput2, Float aK1, + Float aK2, Float aK3, Float aK4) { + IntSize size = aInput1->GetSize(); + RefPtr<DataSourceSurface> target = + Factory::CreateDataSourceSurface(size, SurfaceFormat::B8G8R8A8); + if (!target) { + return nullptr; + } + + DataSourceSurface::ScopedMap inputMap1(aInput1, DataSourceSurface::READ); + DataSourceSurface::ScopedMap outputMap(target, DataSourceSurface::READ_WRITE); + + if (aInput1->Equals(aInput2)) { + ApplyArithmeticCombine_SIMD<i32x4_t, i16x8_t, u8x16_t>( + inputMap1, inputMap1, outputMap, size, aK1, aK2, aK3, aK4); + } else { + DataSourceSurface::ScopedMap inputMap2(aInput2, DataSourceSurface::READ); + ApplyArithmeticCombine_SIMD<i32x4_t, i16x8_t, u8x16_t>( + inputMap1, inputMap2, outputMap, size, aK1, aK2, aK3, aK4); + } + + return target.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/FilterProcessingSSE2.cpp b/gfx/2d/FilterProcessingSSE2.cpp new file mode 100644 index 0000000000..0b9e0aa3d1 --- /dev/null +++ b/gfx/2d/FilterProcessingSSE2.cpp @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define SIMD_COMPILE_SSE2 + +#include "FilterProcessingSIMD-inl.h" + +#ifndef USE_SSE2 +static_assert( + false, "If this file is built, FilterProcessing.h should know about it!"); +#endif + +namespace mozilla::gfx { + +void FilterProcessing::ExtractAlpha_SSE2(const IntSize& size, + uint8_t* sourceData, + int32_t sourceStride, + uint8_t* alphaData, + int32_t alphaStride) { + ExtractAlpha_SIMD<__m128i>(size, sourceData, sourceStride, alphaData, + alphaStride); +} + +already_AddRefed<DataSourceSurface> FilterProcessing::ConvertToB8G8R8A8_SSE2( + SourceSurface* aSurface) { + return ConvertToB8G8R8A8_SIMD<__m128i>(aSurface); +} + +already_AddRefed<DataSourceSurface> FilterProcessing::ApplyBlending_SSE2( + DataSourceSurface* aInput1, DataSourceSurface* aInput2, + BlendMode aBlendMode) { + return ApplyBlending_SIMD<__m128i, __m128i, __m128i>(aInput1, aInput2, + aBlendMode); +} + +void FilterProcessing::ApplyMorphologyHorizontal_SSE2( + uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOp) { + ApplyMorphologyHorizontal_SIMD<__m128i, __m128i>(aSourceData, aSourceStride, + aDestData, aDestStride, + aDestRect, aRadius, aOp); +} + +void FilterProcessing::ApplyMorphologyVertical_SSE2( + uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOp) { + ApplyMorphologyVertical_SIMD<__m128i, __m128i>(aSourceData, aSourceStride, + aDestData, aDestStride, + aDestRect, aRadius, aOp); +} + +already_AddRefed<DataSourceSurface> FilterProcessing::ApplyColorMatrix_SSE2( + DataSourceSurface* aInput, const Matrix5x4& aMatrix) { + return ApplyColorMatrix_SIMD<__m128i, __m128i, __m128i>(aInput, aMatrix); +} + +void FilterProcessing::ApplyComposition_SSE2(DataSourceSurface* aSource, + DataSourceSurface* aDest, + CompositeOperator aOperator) { + return ApplyComposition_SIMD<__m128i, __m128i, __m128i>(aSource, aDest, + aOperator); +} + +void FilterProcessing::SeparateColorChannels_SSE2( + const IntSize& size, uint8_t* sourceData, int32_t sourceStride, + uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, + uint8_t* channel3Data, int32_t channelStride) { + SeparateColorChannels_SIMD<__m128i>(size, sourceData, sourceStride, + channel0Data, channel1Data, channel2Data, + channel3Data, channelStride); +} + +void FilterProcessing::CombineColorChannels_SSE2( + const IntSize& size, int32_t resultStride, uint8_t* resultData, + int32_t channelStride, uint8_t* channel0Data, uint8_t* channel1Data, + uint8_t* channel2Data, uint8_t* channel3Data) { + CombineColorChannels_SIMD<__m128i>(size, resultStride, resultData, + channelStride, channel0Data, channel1Data, + channel2Data, channel3Data); +} + +void FilterProcessing::DoPremultiplicationCalculation_SSE2( + const IntSize& aSize, uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride) { + DoPremultiplicationCalculation_SIMD<__m128i, __m128i, __m128i>( + aSize, aTargetData, aTargetStride, aSourceData, aSourceStride); +} + +void FilterProcessing::DoUnpremultiplicationCalculation_SSE2( + const IntSize& aSize, uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride) { + DoUnpremultiplicationCalculation_SIMD<__m128i, __m128i>( + aSize, aTargetData, aTargetStride, aSourceData, aSourceStride); +} + +void FilterProcessing::DoOpacityCalculation_SSE2( + const IntSize& aSize, uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride, Float aValue) { + DoOpacityCalculation_SIMD<__m128i, __m128i>( + aSize, aTargetData, aTargetStride, aSourceData, aSourceStride, aValue); +} + +already_AddRefed<DataSourceSurface> FilterProcessing::RenderTurbulence_SSE2( + const IntSize& aSize, const Point& aOffset, const Size& aBaseFrequency, + int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, + const Rect& aTileRect) { + return RenderTurbulence_SIMD<__m128, __m128i, __m128i>( + aSize, aOffset, aBaseFrequency, aSeed, aNumOctaves, aType, aStitch, + aTileRect); +} + +already_AddRefed<DataSourceSurface> +FilterProcessing::ApplyArithmeticCombine_SSE2(DataSourceSurface* aInput1, + DataSourceSurface* aInput2, + Float aK1, Float aK2, Float aK3, + Float aK4) { + return ApplyArithmeticCombine_SIMD<__m128i, __m128i, __m128i>( + aInput1, aInput2, aK1, aK2, aK3, aK4); +} + +} // namespace mozilla::gfx diff --git a/gfx/2d/FilterProcessingScalar.cpp b/gfx/2d/FilterProcessingScalar.cpp new file mode 100644 index 0000000000..9cb6040349 --- /dev/null +++ b/gfx/2d/FilterProcessingScalar.cpp @@ -0,0 +1,299 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define FILTER_PROCESSING_SCALAR + +#include "FilterProcessingSIMD-inl.h" +#include "Logging.h" + +namespace mozilla { +namespace gfx { + +void FilterProcessing::ExtractAlpha_Scalar(const IntSize& size, + uint8_t* sourceData, + int32_t sourceStride, + uint8_t* alphaData, + int32_t alphaStride) { + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.width; x++) { + int32_t sourceIndex = y * sourceStride + 4 * x; + int32_t targetIndex = y * alphaStride + x; + alphaData[targetIndex] = + sourceData[sourceIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A]; + } + } +} + +already_AddRefed<DataSourceSurface> FilterProcessing::ConvertToB8G8R8A8_Scalar( + SourceSurface* aSurface) { + return ConvertToB8G8R8A8_SIMD<simd::Scalaru8x16_t>(aSurface); +} + +template <MorphologyOperator Operator> +static void ApplyMorphologyHorizontal_Scalar( + uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius) { + static_assert(Operator == MORPHOLOGY_OPERATOR_ERODE || + Operator == MORPHOLOGY_OPERATOR_DILATE, + "unexpected morphology operator"); + + for (int32_t y = aDestRect.Y(); y < aDestRect.YMost(); y++) { + int32_t startX = aDestRect.X() - aRadius; + int32_t endX = aDestRect.X() + aRadius; + for (int32_t x = aDestRect.X(); x < aDestRect.XMost(); + x++, startX++, endX++) { + int32_t sourceIndex = y * aSourceStride + 4 * startX; + uint8_t u[4]; + for (size_t i = 0; i < 4; i++) { + u[i] = aSourceData[sourceIndex + i]; + } + sourceIndex += 4; + for (int32_t ix = startX + 1; ix <= endX; ix++, sourceIndex += 4) { + for (size_t i = 0; i < 4; i++) { + if (Operator == MORPHOLOGY_OPERATOR_ERODE) { + u[i] = umin(u[i], aSourceData[sourceIndex + i]); + } else { + u[i] = umax(u[i], aSourceData[sourceIndex + i]); + } + } + } + + int32_t destIndex = y * aDestStride + 4 * x; + for (size_t i = 0; i < 4; i++) { + aDestData[destIndex + i] = u[i]; + } + } + } +} + +void FilterProcessing::ApplyMorphologyHorizontal_Scalar( + uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOp) { + if (aOp == MORPHOLOGY_OPERATOR_ERODE) { + gfx::ApplyMorphologyHorizontal_Scalar<MORPHOLOGY_OPERATOR_ERODE>( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius); + } else { + gfx::ApplyMorphologyHorizontal_Scalar<MORPHOLOGY_OPERATOR_DILATE>( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius); + } +} + +template <MorphologyOperator Operator> +static void ApplyMorphologyVertical_Scalar( + uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius) { + static_assert(Operator == MORPHOLOGY_OPERATOR_ERODE || + Operator == MORPHOLOGY_OPERATOR_DILATE, + "unexpected morphology operator"); + + int32_t startY = aDestRect.Y() - aRadius; + int32_t endY = aDestRect.Y() + aRadius; + for (int32_t y = aDestRect.Y(); y < aDestRect.YMost(); + y++, startY++, endY++) { + for (int32_t x = aDestRect.X(); x < aDestRect.XMost(); x++) { + int32_t sourceIndex = startY * aSourceStride + 4 * x; + uint8_t u[4]; + for (size_t i = 0; i < 4; i++) { + u[i] = aSourceData[sourceIndex + i]; + } + sourceIndex += aSourceStride; + for (int32_t iy = startY + 1; iy <= endY; + iy++, sourceIndex += aSourceStride) { + for (size_t i = 0; i < 4; i++) { + if (Operator == MORPHOLOGY_OPERATOR_ERODE) { + u[i] = umin(u[i], aSourceData[sourceIndex + i]); + } else { + u[i] = umax(u[i], aSourceData[sourceIndex + i]); + } + } + } + + int32_t destIndex = y * aDestStride + 4 * x; + for (size_t i = 0; i < 4; i++) { + aDestData[destIndex + i] = u[i]; + } + } + } +} + +void FilterProcessing::ApplyMorphologyVertical_Scalar( + uint8_t* aSourceData, int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntRect& aDestRect, int32_t aRadius, + MorphologyOperator aOp) { + if (aOp == MORPHOLOGY_OPERATOR_ERODE) { + gfx::ApplyMorphologyVertical_Scalar<MORPHOLOGY_OPERATOR_ERODE>( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius); + } else { + gfx::ApplyMorphologyVertical_Scalar<MORPHOLOGY_OPERATOR_DILATE>( + aSourceData, aSourceStride, aDestData, aDestStride, aDestRect, aRadius); + } +} + +already_AddRefed<DataSourceSurface> FilterProcessing::ApplyColorMatrix_Scalar( + DataSourceSurface* aInput, const Matrix5x4& aMatrix) { + return ApplyColorMatrix_SIMD<simd::Scalari32x4_t, simd::Scalari16x8_t, + simd::Scalaru8x16_t>(aInput, aMatrix); +} + +void FilterProcessing::ApplyComposition_Scalar(DataSourceSurface* aSource, + DataSourceSurface* aDest, + CompositeOperator aOperator) { + return ApplyComposition_SIMD<simd::Scalari32x4_t, simd::Scalaru16x8_t, + simd::Scalaru8x16_t>(aSource, aDest, aOperator); +} + +void FilterProcessing::SeparateColorChannels_Scalar( + const IntSize& size, uint8_t* sourceData, int32_t sourceStride, + uint8_t* channel0Data, uint8_t* channel1Data, uint8_t* channel2Data, + uint8_t* channel3Data, int32_t channelStride) { + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.width; x++) { + int32_t sourceIndex = y * sourceStride + 4 * x; + int32_t targetIndex = y * channelStride + x; + channel0Data[targetIndex] = sourceData[sourceIndex]; + channel1Data[targetIndex] = sourceData[sourceIndex + 1]; + channel2Data[targetIndex] = sourceData[sourceIndex + 2]; + channel3Data[targetIndex] = sourceData[sourceIndex + 3]; + } + } +} + +void FilterProcessing::CombineColorChannels_Scalar( + const IntSize& size, int32_t resultStride, uint8_t* resultData, + int32_t channelStride, uint8_t* channel0Data, uint8_t* channel1Data, + uint8_t* channel2Data, uint8_t* channel3Data) { + for (int32_t y = 0; y < size.height; y++) { + for (int32_t x = 0; x < size.width; x++) { + int32_t resultIndex = y * resultStride + 4 * x; + int32_t channelIndex = y * channelStride + x; + resultData[resultIndex] = channel0Data[channelIndex]; + resultData[resultIndex + 1] = channel1Data[channelIndex]; + resultData[resultIndex + 2] = channel2Data[channelIndex]; + resultData[resultIndex + 3] = channel3Data[channelIndex]; + } + } +} + +void FilterProcessing::DoPremultiplicationCalculation_Scalar( + const IntSize& aSize, uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride) { + for (int32_t y = 0; y < aSize.height; y++) { + for (int32_t x = 0; x < aSize.width; x++) { + int32_t inputIndex = y * aSourceStride + 4 * x; + int32_t targetIndex = y * aTargetStride + 4 * x; + uint8_t alpha = aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A]; + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_R] = + FastDivideBy255<uint8_t>( + aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_R] * + alpha); + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_G] = + FastDivideBy255<uint8_t>( + aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_G] * + alpha); + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_B] = + FastDivideBy255<uint8_t>( + aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_B] * + alpha); + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A] = alpha; + } + } +} + +void FilterProcessing::DoUnpremultiplicationCalculation_Scalar( + const IntSize& aSize, uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride) { + for (int32_t y = 0; y < aSize.height; y++) { + for (int32_t x = 0; x < aSize.width; x++) { + int32_t inputIndex = y * aSourceStride + 4 * x; + int32_t targetIndex = y * aTargetStride + 4 * x; + uint8_t alpha = aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A]; + uint16_t alphaFactor = sAlphaFactors[alpha]; + // inputColor * alphaFactor + 128 is guaranteed to fit into uint16_t + // because the input is premultiplied and thus inputColor <= inputAlpha. + // The maximum value this can attain is 65520 (which is less than 65535) + // for color == alpha == 244: + // 244 * sAlphaFactors[244] + 128 == 244 * 268 + 128 == 65520 + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_R] = + (aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_R] * + alphaFactor + + 128) >> + 8; + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_G] = + (aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_G] * + alphaFactor + + 128) >> + 8; + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_B] = + (aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_B] * + alphaFactor + + 128) >> + 8; + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A] = alpha; + } + } +} + +void FilterProcessing::DoOpacityCalculation_Scalar( + const IntSize& aSize, uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride, Float aValue) { + uint8_t alpha = uint8_t(roundf(255.f * aValue)); + for (int32_t y = 0; y < aSize.height; y++) { + for (int32_t x = 0; x < aSize.width; x++) { + int32_t inputIndex = y * aSourceStride + 4 * x; + int32_t targetIndex = y * aTargetStride + 4 * x; + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_R] = + (aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_R] * alpha) >> + 8; + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_G] = + (aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_G] * alpha) >> + 8; + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_B] = + (aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_B] * alpha) >> + 8; + aTargetData[targetIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A] = + (aSourceData[inputIndex + B8G8R8A8_COMPONENT_BYTEOFFSET_A] * alpha) >> + 8; + } + } +} + +void FilterProcessing::DoOpacityCalculationA8_Scalar( + const IntSize& aSize, uint8_t* aTargetData, int32_t aTargetStride, + uint8_t* aSourceData, int32_t aSourceStride, Float aValue) { + uint8_t alpha = uint8_t(255.f * aValue); + for (int32_t y = 0; y < aSize.height; y++) { + for (int32_t x = 0; x < aSize.width; x++) { + int32_t inputIndex = y * aSourceStride; + int32_t targetIndex = y * aTargetStride; + aTargetData[targetIndex] = + FastDivideBy255<uint8_t>(aSourceData[inputIndex] * alpha); + } + } +} + +already_AddRefed<DataSourceSurface> FilterProcessing::RenderTurbulence_Scalar( + const IntSize& aSize, const Point& aOffset, const Size& aBaseFrequency, + int32_t aSeed, int aNumOctaves, TurbulenceType aType, bool aStitch, + const Rect& aTileRect) { + return RenderTurbulence_SIMD<simd::Scalarf32x4_t, simd::Scalari32x4_t, + simd::Scalaru8x16_t>( + aSize, aOffset, aBaseFrequency, aSeed, aNumOctaves, aType, aStitch, + aTileRect); +} + +already_AddRefed<DataSourceSurface> +FilterProcessing::ApplyArithmeticCombine_Scalar(DataSourceSurface* aInput1, + DataSourceSurface* aInput2, + Float aK1, Float aK2, Float aK3, + Float aK4) { + return ApplyArithmeticCombine_SIMD<simd::Scalari32x4_t, simd::Scalari16x8_t, + simd::Scalaru8x16_t>(aInput1, aInput2, aK1, + aK2, aK3, aK4); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/Filters.h b/gfx/2d/Filters.h new file mode 100644 index 0000000000..a9f77ce6aa --- /dev/null +++ b/gfx/2d/Filters.h @@ -0,0 +1,446 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_FILTERS_H_ +#define MOZILLA_GFX_FILTERS_H_ + +#include "Types.h" +#include "mozilla/RefPtr.h" + +#include "Point.h" +#include "Matrix.h" +#include <vector> + +namespace mozilla { +namespace gfx { + +class SourceSurface; + +enum FilterBackend { + FILTER_BACKEND_SOFTWARE = 0, + FILTER_BACKEND_DIRECT2D1_1, + FILTER_BACKEND_RECORDING, + FILTER_BACKEND_CAPTURE +}; + +enum TransformFilterAtts { + ATT_TRANSFORM_MATRIX = 0, // Matrix + ATT_TRANSFORM_FILTER // Filter +}; + +enum TransformFilterInputs { IN_TRANSFORM_IN = 0 }; + +enum BlendFilterAtts { + ATT_BLEND_BLENDMODE = 0 // uint32_t +}; + +enum BlendMode { + BLEND_MODE_MULTIPLY = 0, + BLEND_MODE_SCREEN, + BLEND_MODE_DARKEN, + BLEND_MODE_LIGHTEN, + BLEND_MODE_OVERLAY, + BLEND_MODE_COLOR_DODGE, + BLEND_MODE_COLOR_BURN, + BLEND_MODE_HARD_LIGHT, + BLEND_MODE_SOFT_LIGHT, + BLEND_MODE_DIFFERENCE, + BLEND_MODE_EXCLUSION, + BLEND_MODE_HUE, + BLEND_MODE_SATURATION, + BLEND_MODE_COLOR, + BLEND_MODE_LUMINOSITY +}; + +enum BlendFilterInputs { IN_BLEND_IN = 0, IN_BLEND_IN2 }; + +enum MorphologyFilterAtts { + ATT_MORPHOLOGY_RADII = 0, // IntSize + ATT_MORPHOLOGY_OPERATOR // MorphologyOperator +}; + +enum MorphologyOperator { + MORPHOLOGY_OPERATOR_ERODE = 0, + MORPHOLOGY_OPERATOR_DILATE +}; + +enum MorphologyFilterInputs { IN_MORPHOLOGY_IN = 0 }; + +enum AlphaMode { ALPHA_MODE_PREMULTIPLIED = 0, ALPHA_MODE_STRAIGHT }; + +enum ColorMatrixFilterAtts { + ATT_COLOR_MATRIX_MATRIX = 0, // Matrix5x4 + ATT_COLOR_MATRIX_ALPHA_MODE // AlphaMode +}; + +enum ColorMatrixFilterInputs { IN_COLOR_MATRIX_IN = 0 }; + +enum FloodFilterAtts { + ATT_FLOOD_COLOR = 0 // Color +}; + +enum FloodFilterInputs { IN_FLOOD_IN = 0 }; + +enum TileFilterAtts { + ATT_TILE_SOURCE_RECT = 0 // IntRect +}; + +enum TileFilterInputs { IN_TILE_IN = 0 }; + +enum TransferAtts { + ATT_TRANSFER_DISABLE_R = 0, // bool + ATT_TRANSFER_DISABLE_G, // bool + ATT_TRANSFER_DISABLE_B, // bool + ATT_TRANSFER_DISABLE_A // bool +}; + +enum TransferInputs { IN_TRANSFER_IN = 0 }; + +enum TableTransferAtts { + ATT_TABLE_TRANSFER_DISABLE_R = ATT_TRANSFER_DISABLE_R, + ATT_TABLE_TRANSFER_DISABLE_G = ATT_TRANSFER_DISABLE_G, + ATT_TABLE_TRANSFER_DISABLE_B = ATT_TRANSFER_DISABLE_B, + ATT_TABLE_TRANSFER_DISABLE_A = ATT_TRANSFER_DISABLE_A, + ATT_TABLE_TRANSFER_TABLE_R, // Float[] + ATT_TABLE_TRANSFER_TABLE_G, // Float[] + ATT_TABLE_TRANSFER_TABLE_B, // Float[] + ATT_TABLE_TRANSFER_TABLE_A // Float[] +}; + +enum TableTransferInputs { IN_TABLE_TRANSFER_IN = IN_TRANSFER_IN }; + +enum DiscreteTransferAtts { + ATT_DISCRETE_TRANSFER_DISABLE_R = ATT_TRANSFER_DISABLE_R, + ATT_DISCRETE_TRANSFER_DISABLE_G = ATT_TRANSFER_DISABLE_G, + ATT_DISCRETE_TRANSFER_DISABLE_B = ATT_TRANSFER_DISABLE_B, + ATT_DISCRETE_TRANSFER_DISABLE_A = ATT_TRANSFER_DISABLE_A, + ATT_DISCRETE_TRANSFER_TABLE_R, // Float[] + ATT_DISCRETE_TRANSFER_TABLE_G, // Float[] + ATT_DISCRETE_TRANSFER_TABLE_B, // Float[] + ATT_DISCRETE_TRANSFER_TABLE_A // Float[] +}; + +enum DiscreteTransferInputs { IN_DISCRETE_TRANSFER_IN = IN_TRANSFER_IN }; + +enum LinearTransferAtts { + ATT_LINEAR_TRANSFER_DISABLE_R = ATT_TRANSFER_DISABLE_R, + ATT_LINEAR_TRANSFER_DISABLE_G = ATT_TRANSFER_DISABLE_G, + ATT_LINEAR_TRANSFER_DISABLE_B = ATT_TRANSFER_DISABLE_B, + ATT_LINEAR_TRANSFER_DISABLE_A = ATT_TRANSFER_DISABLE_A, + ATT_LINEAR_TRANSFER_SLOPE_R, // Float + ATT_LINEAR_TRANSFER_SLOPE_G, // Float + ATT_LINEAR_TRANSFER_SLOPE_B, // Float + ATT_LINEAR_TRANSFER_SLOPE_A, // Float + ATT_LINEAR_TRANSFER_INTERCEPT_R, // Float + ATT_LINEAR_TRANSFER_INTERCEPT_G, // Float + ATT_LINEAR_TRANSFER_INTERCEPT_B, // Float + ATT_LINEAR_TRANSFER_INTERCEPT_A // Float +}; + +enum LinearTransferInputs { IN_LINEAR_TRANSFER_IN = IN_TRANSFER_IN }; + +enum GammaTransferAtts { + ATT_GAMMA_TRANSFER_DISABLE_R = ATT_TRANSFER_DISABLE_R, + ATT_GAMMA_TRANSFER_DISABLE_G = ATT_TRANSFER_DISABLE_G, + ATT_GAMMA_TRANSFER_DISABLE_B = ATT_TRANSFER_DISABLE_B, + ATT_GAMMA_TRANSFER_DISABLE_A = ATT_TRANSFER_DISABLE_A, + ATT_GAMMA_TRANSFER_AMPLITUDE_R, // Float + ATT_GAMMA_TRANSFER_AMPLITUDE_G, // Float + ATT_GAMMA_TRANSFER_AMPLITUDE_B, // Float + ATT_GAMMA_TRANSFER_AMPLITUDE_A, // Float + ATT_GAMMA_TRANSFER_EXPONENT_R, // Float + ATT_GAMMA_TRANSFER_EXPONENT_G, // Float + ATT_GAMMA_TRANSFER_EXPONENT_B, // Float + ATT_GAMMA_TRANSFER_EXPONENT_A, // Float + ATT_GAMMA_TRANSFER_OFFSET_R, // Float + ATT_GAMMA_TRANSFER_OFFSET_G, // Float + ATT_GAMMA_TRANSFER_OFFSET_B, // Float + ATT_GAMMA_TRANSFER_OFFSET_A // Float +}; + +enum GammaTransferInputs { IN_GAMMA_TRANSFER_IN = IN_TRANSFER_IN }; + +enum ConvolveMatrixAtts { + ATT_CONVOLVE_MATRIX_KERNEL_SIZE = 0, // IntSize + ATT_CONVOLVE_MATRIX_KERNEL_MATRIX, // Float[] + ATT_CONVOLVE_MATRIX_DIVISOR, // Float + ATT_CONVOLVE_MATRIX_BIAS, // Float + ATT_CONVOLVE_MATRIX_TARGET, // IntPoint + ATT_CONVOLVE_MATRIX_SOURCE_RECT, // IntRect + ATT_CONVOLVE_MATRIX_EDGE_MODE, // ConvolveMatrixEdgeMode + ATT_CONVOLVE_MATRIX_KERNEL_UNIT_LENGTH, // Size + ATT_CONVOLVE_MATRIX_PRESERVE_ALPHA, // bool +}; + +enum ConvolveMatrixEdgeMode { + EDGE_MODE_DUPLICATE = 0, + EDGE_MODE_WRAP, + EDGE_MODE_NONE +}; + +enum ConvolveMatrixInputs { IN_CONVOLVE_MATRIX_IN = 0 }; + +enum DisplacementMapAtts { + ATT_DISPLACEMENT_MAP_SCALE = 0, // Float + ATT_DISPLACEMENT_MAP_X_CHANNEL, // ColorChannel + ATT_DISPLACEMENT_MAP_Y_CHANNEL // ColorChannel +}; + +enum ColorChannel { + COLOR_CHANNEL_R = 0, + COLOR_CHANNEL_G, + COLOR_CHANNEL_B, + COLOR_CHANNEL_A +}; + +enum DisplacementMapInputs { + IN_DISPLACEMENT_MAP_IN = 0, + IN_DISPLACEMENT_MAP_IN2 +}; + +enum TurbulenceAtts { + ATT_TURBULENCE_BASE_FREQUENCY = 0, // Size + ATT_TURBULENCE_NUM_OCTAVES, // uint32_t + ATT_TURBULENCE_SEED, // uint32_t + ATT_TURBULENCE_STITCHABLE, // bool + ATT_TURBULENCE_TYPE, // TurbulenceType + ATT_TURBULENCE_RECT // IntRect +}; + +enum TurbulenceType { + TURBULENCE_TYPE_TURBULENCE = 0, + TURBULENCE_TYPE_FRACTAL_NOISE +}; + +enum ArithmeticCombineAtts { + ATT_ARITHMETIC_COMBINE_COEFFICIENTS = 0 // Float[4] +}; + +enum ArithmeticCombineInputs { + IN_ARITHMETIC_COMBINE_IN = 0, + IN_ARITHMETIC_COMBINE_IN2 +}; + +enum CompositeAtts { + ATT_COMPOSITE_OPERATOR = 0 // CompositeOperator +}; + +enum CompositeOperator { + COMPOSITE_OPERATOR_OVER = 0, + COMPOSITE_OPERATOR_IN, + COMPOSITE_OPERATOR_OUT, + COMPOSITE_OPERATOR_ATOP, + COMPOSITE_OPERATOR_XOR, + COMPOSITE_OPERATOR_LIGHTER +}; + +enum CompositeInputs { + // arbitrary number of inputs + IN_COMPOSITE_IN_START = 0 +}; + +enum GaussianBlurAtts { + ATT_GAUSSIAN_BLUR_STD_DEVIATION = 0 // Float +}; + +enum GaussianBlurInputs { IN_GAUSSIAN_BLUR_IN = 0 }; + +enum DirectionalBlurAtts { + ATT_DIRECTIONAL_BLUR_STD_DEVIATION = 0, // Float + ATT_DIRECTIONAL_BLUR_DIRECTION // BlurDirection +}; + +enum BlurDirection { BLUR_DIRECTION_X = 0, BLUR_DIRECTION_Y }; + +enum DirectionalBlurInputs { IN_DIRECTIONAL_BLUR_IN = 0 }; + +enum LightingAtts { + ATT_POINT_LIGHT_POSITION = 0, // Point3D + + ATT_SPOT_LIGHT_POSITION, // Point3D + ATT_SPOT_LIGHT_POINTS_AT, // Point3D + ATT_SPOT_LIGHT_FOCUS, // Float + ATT_SPOT_LIGHT_LIMITING_CONE_ANGLE, // Float + + ATT_DISTANT_LIGHT_AZIMUTH, // Float + ATT_DISTANT_LIGHT_ELEVATION, // Float + + ATT_LIGHTING_COLOR, // Color + ATT_LIGHTING_SURFACE_SCALE, // Float + ATT_LIGHTING_KERNEL_UNIT_LENGTH, // Size + + ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT, // Float + + ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT, // Float + ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT // Float +}; + +enum LightingInputs { IN_LIGHTING_IN = 0 }; + +enum PointDiffuseAtts { + ATT_POINT_DIFFUSE_POSITION = ATT_POINT_LIGHT_POSITION, + ATT_POINT_DIFFUSE_COLOR = ATT_LIGHTING_COLOR, + ATT_POINT_DIFFUSE_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE, + ATT_POINT_DIFFUSE_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH, + ATT_POINT_DIFFUSE_DIFFUSE_CONSTANT = ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT +}; + +enum PointDiffuseInputs { IN_POINT_DIFFUSE_IN = IN_LIGHTING_IN }; + +enum SpotDiffuseAtts { + ATT_SPOT_DIFFUSE_POSITION = ATT_SPOT_LIGHT_POSITION, + ATT_SPOT_DIFFUSE_POINTS_AT = ATT_SPOT_LIGHT_POINTS_AT, + ATT_SPOT_DIFFUSE_FOCUS = ATT_SPOT_LIGHT_FOCUS, + ATT_SPOT_DIFFUSE_LIMITING_CONE_ANGLE = ATT_SPOT_LIGHT_LIMITING_CONE_ANGLE, + ATT_SPOT_DIFFUSE_COLOR = ATT_LIGHTING_COLOR, + ATT_SPOT_DIFFUSE_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE, + ATT_SPOT_DIFFUSE_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH, + ATT_SPOT_DIFFUSE_DIFFUSE_CONSTANT = ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT +}; + +enum SpotDiffuseInputs { IN_SPOT_DIFFUSE_IN = IN_LIGHTING_IN }; + +enum DistantDiffuseAtts { + ATT_DISTANT_DIFFUSE_AZIMUTH = ATT_DISTANT_LIGHT_AZIMUTH, + ATT_DISTANT_DIFFUSE_ELEVATION = ATT_DISTANT_LIGHT_ELEVATION, + ATT_DISTANT_DIFFUSE_COLOR = ATT_LIGHTING_COLOR, + ATT_DISTANT_DIFFUSE_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE, + ATT_DISTANT_DIFFUSE_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH, + ATT_DISTANT_DIFFUSE_DIFFUSE_CONSTANT = ATT_DIFFUSE_LIGHTING_DIFFUSE_CONSTANT +}; + +enum DistantDiffuseInputs { IN_DISTANT_DIFFUSE_IN = IN_LIGHTING_IN }; + +enum PointSpecularAtts { + ATT_POINT_SPECULAR_POSITION = ATT_POINT_LIGHT_POSITION, + ATT_POINT_SPECULAR_COLOR = ATT_LIGHTING_COLOR, + ATT_POINT_SPECULAR_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE, + ATT_POINT_SPECULAR_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH, + ATT_POINT_SPECULAR_SPECULAR_CONSTANT = + ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT, + ATT_POINT_SPECULAR_SPECULAR_EXPONENT = ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT +}; + +enum PointSpecularInputs { IN_POINT_SPECULAR_IN = IN_LIGHTING_IN }; + +enum SpotSpecularAtts { + ATT_SPOT_SPECULAR_POSITION = ATT_SPOT_LIGHT_POSITION, + ATT_SPOT_SPECULAR_POINTS_AT = ATT_SPOT_LIGHT_POINTS_AT, + ATT_SPOT_SPECULAR_FOCUS = ATT_SPOT_LIGHT_FOCUS, + ATT_SPOT_SPECULAR_LIMITING_CONE_ANGLE = ATT_SPOT_LIGHT_LIMITING_CONE_ANGLE, + ATT_SPOT_SPECULAR_COLOR = ATT_LIGHTING_COLOR, + ATT_SPOT_SPECULAR_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE, + ATT_SPOT_SPECULAR_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH, + ATT_SPOT_SPECULAR_SPECULAR_CONSTANT = ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT, + ATT_SPOT_SPECULAR_SPECULAR_EXPONENT = ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT +}; + +enum SpotSpecularInputs { IN_SPOT_SPECULAR_IN = IN_LIGHTING_IN }; + +enum DistantSpecularAtts { + ATT_DISTANT_SPECULAR_AZIMUTH = ATT_DISTANT_LIGHT_AZIMUTH, + ATT_DISTANT_SPECULAR_ELEVATION = ATT_DISTANT_LIGHT_ELEVATION, + ATT_DISTANT_SPECULAR_COLOR = ATT_LIGHTING_COLOR, + ATT_DISTANT_SPECULAR_SURFACE_SCALE = ATT_LIGHTING_SURFACE_SCALE, + ATT_DISTANT_SPECULAR_KERNEL_UNIT_LENGTH = ATT_LIGHTING_KERNEL_UNIT_LENGTH, + ATT_DISTANT_SPECULAR_SPECULAR_CONSTANT = + ATT_SPECULAR_LIGHTING_SPECULAR_CONSTANT, + ATT_DISTANT_SPECULAR_SPECULAR_EXPONENT = + ATT_SPECULAR_LIGHTING_SPECULAR_EXPONENT +}; + +enum DistantSpecularInputs { IN_DISTANT_SPECULAR_IN = IN_LIGHTING_IN }; + +enum CropAtts { + ATT_CROP_RECT = 0 // Rect +}; + +enum CropInputs { IN_CROP_IN = 0 }; + +enum PremultiplyInputs { IN_PREMULTIPLY_IN = 0 }; + +enum UnpremultiplyInputs { IN_UNPREMULTIPLY_IN = 0 }; + +enum OpacityAtts { ATT_OPACITY_VALUE = 0 }; + +enum OpacityInputs { IN_OPACITY_IN = 0 }; + +class FilterNode : public external::AtomicRefCounted<FilterNode> { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNode) + virtual ~FilterNode() = default; + + virtual FilterBackend GetBackendType() = 0; + + virtual void SetInput(uint32_t aIndex, SourceSurface* aSurface) { + MOZ_CRASH("GFX: FilterNode"); + } + virtual void SetInput(uint32_t aIndex, FilterNode* aFilter) { + MOZ_CRASH("GFX: FilterNode"); + } + + virtual void SetAttribute(uint32_t aIndex, bool) { + MOZ_CRASH("GFX: FilterNode"); + } + virtual void SetAttribute(uint32_t aIndex, uint32_t) { + MOZ_CRASH("GFX: FilterNode"); + } + virtual void SetAttribute(uint32_t aIndex, Float) { + MOZ_CRASH("GFX: FilterNode"); + } + virtual void SetAttribute(uint32_t aIndex, const Size&) { + MOZ_CRASH("GFX: FilterNode"); + } + virtual void SetAttribute(uint32_t aIndex, const IntSize&) { + MOZ_CRASH("GFX: FilterNode"); + } + virtual void SetAttribute(uint32_t aIndex, const IntPoint&) { + MOZ_CRASH("GFX: FilterNode"); + } + virtual void SetAttribute(uint32_t aIndex, const Rect&) { + MOZ_CRASH("GFX: FilterNode"); + } + virtual void SetAttribute(uint32_t aIndex, const IntRect&) { + MOZ_CRASH("GFX: FilterNode"); + } + virtual void SetAttribute(uint32_t aIndex, const Point&) { + MOZ_CRASH("GFX: FilterNode"); + } + virtual void SetAttribute(uint32_t aIndex, const Matrix&) { + MOZ_CRASH("GFX: FilterNode"); + } + virtual void SetAttribute(uint32_t aIndex, const Matrix5x4&) { + MOZ_CRASH("GFX: FilterNode"); + } + virtual void SetAttribute(uint32_t aIndex, const Point3D&) { + MOZ_CRASH("GFX: FilterNode"); + } + virtual void SetAttribute(uint32_t aIndex, const DeviceColor&) { + MOZ_CRASH("GFX: FilterNode"); + } + virtual void SetAttribute(uint32_t aIndex, const Float* aFloat, + uint32_t aSize) { + MOZ_CRASH("GFX: FilterNode"); + } + + /** Maps a rectangle in filter space back to a rectangle in the space of + * aSourceNode's first input. aSourceNode should not have an input + * assigned when calling this function. */ + virtual IntRect MapRectToSource(const IntRect& aRect, const IntRect& aMax, + FilterNode* aSourceNode) { + return aMax; + } + + protected: + friend class Factory; + + FilterNode() = default; +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/FontVariation.h b/gfx/2d/FontVariation.h new file mode 100644 index 0000000000..04a880d4fb --- /dev/null +++ b/gfx/2d/FontVariation.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_FONTVARIATION_H_ +#define MOZILLA_GFX_FONTVARIATION_H_ + +#include <stdint.h> +#include "mozilla/FloatingPoint.h" + +namespace mozilla::gfx { + +// An OpenType variation tag and value pair +struct FontVariation { + uint32_t mTag; + float mValue; + + bool operator==(const FontVariation& aOther) const { + return mTag == aOther.mTag && + NumbersAreBitwiseIdentical(mValue, aOther.mValue); + } +}; + +} // namespace mozilla::gfx + +#endif /* MOZILLA_GFX_FONTVARIATION_H_ */ diff --git a/gfx/2d/GenericRefCounted.h b/gfx/2d/GenericRefCounted.h new file mode 100644 index 0000000000..4462c94230 --- /dev/null +++ b/gfx/2d/GenericRefCounted.h @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This header provides virtual, non-templated alternatives to MFBT's +// RefCounted<T>. It intentionally uses MFBT coding style with the intention of +// moving there should there be other use cases for it. + +#ifndef MOZILLA_GENERICREFCOUNTED_H_ +#define MOZILLA_GENERICREFCOUNTED_H_ + +#include <type_traits> + +#include "mozilla/RefPtr.h" +#include "mozilla/RefCounted.h" + +namespace mozilla { + +/** + * Common base class for GenericRefCounted and GenericAtomicRefCounted. + * + * Having this shared base class, common to both the atomic and non-atomic + * cases, allows to have RefPtr's that don't care about whether the + * objects they're managing have atomic refcounts or not. + */ +class GenericRefCountedBase { + protected: + virtual ~GenericRefCountedBase() = default; + + public: + // AddRef() and Release() method names are for compatibility with nsRefPtr. + virtual void AddRef() = 0; + + virtual void Release() = 0; + + // ref() and deref() method names are for compatibility with wtf::RefPtr. + // No virtual keywords here: if a subclass wants to override the refcounting + // mechanism, it is welcome to do so by overriding AddRef() and Release(). + void ref() { AddRef(); } + void deref() { Release(); } + +#ifdef MOZ_REFCOUNTED_LEAK_CHECKING + virtual const char* typeName() const = 0; + virtual size_t typeSize() const = 0; +#endif +}; + +namespace detail { + +template <RefCountAtomicity Atomicity> +class GenericRefCounted : public GenericRefCountedBase { + protected: + GenericRefCounted() : refCnt(0) {} + + virtual ~GenericRefCounted() { MOZ_ASSERT(refCnt == detail::DEAD); } + + public: + virtual void AddRef() override { + // Note: this method must be thread safe for GenericAtomicRefCounted. + MOZ_ASSERT(int32_t(refCnt) >= 0); + MozRefCountType cnt = ++refCnt; + detail::RefCountLogger::logAddRef(this, cnt); + } + + virtual void Release() override { + // Note: this method must be thread safe for GenericAtomicRefCounted. + MOZ_ASSERT(int32_t(refCnt) > 0); + detail::RefCountLogger::ReleaseLogger logger{this}; + MozRefCountType cnt = --refCnt; + // Note: it's not safe to touch |this| after decrementing the refcount, + // except for below. + logger.logRelease(cnt); + if (0 == cnt) { + // Because we have atomically decremented the refcount above, only + // one thread can get a 0 count here, so as long as we can assume that + // everything else in the system is accessing this object through + // RefPtrs, it's safe to access |this| here. +#ifdef DEBUG + refCnt = detail::DEAD; +#endif + delete this; + } + } + + MozRefCountType refCount() const { return refCnt; } + bool hasOneRef() const { + MOZ_ASSERT(refCnt > 0); + return refCnt == 1; + } + + private: + std::conditional_t<Atomicity == AtomicRefCount, Atomic<MozRefCountType>, + MozRefCountType> + refCnt; +}; + +} // namespace detail + +/** + * This reference-counting base class is virtual instead of + * being templated, which is useful in cases where one needs + * genericity at binary code level, but comes at the cost + * of a moderate performance and size overhead, like anything virtual. + */ +class GenericRefCounted + : public detail::GenericRefCounted<detail::NonAtomicRefCount> {}; + +/** + * GenericAtomicRefCounted is like GenericRefCounted, with an atomically updated + * reference counter. + */ +class GenericAtomicRefCounted + : public detail::GenericRefCounted<detail::AtomicRefCount> {}; + +} // namespace mozilla + +#endif diff --git a/gfx/2d/GradientStopsD2D.h b/gfx/2d/GradientStopsD2D.h new file mode 100644 index 0000000000..20e60eb1d0 --- /dev/null +++ b/gfx/2d/GradientStopsD2D.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_GRADIENTSTOPSD2D_H_ +#define MOZILLA_GFX_GRADIENTSTOPSD2D_H_ + +#include "2D.h" + +#include <d2d1.h> + +namespace mozilla { +namespace gfx { + +class GradientStopsD2D : public GradientStops { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsD2D, override) + + GradientStopsD2D(ID2D1GradientStopCollection* aStopCollection, + ID3D11Device* aDevice) + : mStopCollection(aStopCollection), mDevice(aDevice) {} + + virtual BackendType GetBackendType() const { return BackendType::DIRECT2D; } + + bool IsValid() const final { + return mDevice == Factory::GetDirect3D11Device(); + } + + private: + friend class DrawTargetD2D; + friend class DrawTargetD2D1; + + mutable RefPtr<ID2D1GradientStopCollection> mStopCollection; + RefPtr<ID3D11Device> mDevice; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_GRADIENTSTOPSD2D_H_ */ diff --git a/gfx/2d/Helpers.h b/gfx/2d/Helpers.h new file mode 100644 index 0000000000..349e99973a --- /dev/null +++ b/gfx/2d/Helpers.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_2D_HELPERS_H_ +#define MOZILLA_GFX_2D_HELPERS_H_ + +#include "2D.h" + +namespace mozilla { +namespace gfx { + +class AutoRestoreTransform final { + public: + AutoRestoreTransform() = default; + + explicit AutoRestoreTransform(DrawTarget* aTarget) + : mDrawTarget(aTarget), mOldTransform(aTarget->GetTransform()) {} + + void Init(DrawTarget* aTarget) { + MOZ_ASSERT(!mDrawTarget || aTarget == mDrawTarget); + if (!mDrawTarget) { + mDrawTarget = aTarget; + mOldTransform = aTarget->GetTransform(); + } + } + + ~AutoRestoreTransform() { + if (mDrawTarget) { + mDrawTarget->SetTransform(mOldTransform); + } + } + + private: + RefPtr<DrawTarget> mDrawTarget; + Matrix mOldTransform; +}; + +class AutoPopClips final { + public: + explicit AutoPopClips(DrawTarget* aTarget) + : mDrawTarget(aTarget), mPushCount(0) { + MOZ_ASSERT(mDrawTarget); + } + + ~AutoPopClips() { PopAll(); } + + void PushClip(const Path* aPath) { + mDrawTarget->PushClip(aPath); + ++mPushCount; + } + + void PushClipRect(const Rect& aRect) { + mDrawTarget->PushClipRect(aRect); + ++mPushCount; + } + + void PopClip() { + MOZ_ASSERT(mPushCount > 0); + mDrawTarget->PopClip(); + --mPushCount; + } + + void PopAll() { + while (mPushCount-- > 0) { + mDrawTarget->PopClip(); + } + } + + private: + RefPtr<DrawTarget> mDrawTarget; + int32_t mPushCount; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // MOZILLA_GFX_2D_HELPERS_H_ diff --git a/gfx/2d/HelpersCairo.h b/gfx/2d/HelpersCairo.h new file mode 100644 index 0000000000..e3c707944b --- /dev/null +++ b/gfx/2d/HelpersCairo.h @@ -0,0 +1,333 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_HELPERSCAIRO_H_ +#define MOZILLA_GFX_HELPERSCAIRO_H_ + +#include "2D.h" +#include "cairo.h" +#include "Logging.h" + +namespace mozilla { +namespace gfx { + +static inline cairo_operator_t GfxOpToCairoOp(CompositionOp op) { + switch (op) { + case CompositionOp::OP_CLEAR: + return CAIRO_OPERATOR_CLEAR; + case CompositionOp::OP_OVER: + return CAIRO_OPERATOR_OVER; + case CompositionOp::OP_ADD: + return CAIRO_OPERATOR_ADD; + case CompositionOp::OP_ATOP: + return CAIRO_OPERATOR_ATOP; + case CompositionOp::OP_OUT: + return CAIRO_OPERATOR_OUT; + case CompositionOp::OP_IN: + return CAIRO_OPERATOR_IN; + case CompositionOp::OP_SOURCE: + return CAIRO_OPERATOR_SOURCE; + case CompositionOp::OP_DEST_IN: + return CAIRO_OPERATOR_DEST_IN; + case CompositionOp::OP_DEST_OUT: + return CAIRO_OPERATOR_DEST_OUT; + case CompositionOp::OP_DEST_OVER: + return CAIRO_OPERATOR_DEST_OVER; + case CompositionOp::OP_DEST_ATOP: + return CAIRO_OPERATOR_DEST_ATOP; + case CompositionOp::OP_XOR: + return CAIRO_OPERATOR_XOR; + case CompositionOp::OP_MULTIPLY: + return CAIRO_OPERATOR_MULTIPLY; + case CompositionOp::OP_SCREEN: + return CAIRO_OPERATOR_SCREEN; + case CompositionOp::OP_OVERLAY: + return CAIRO_OPERATOR_OVERLAY; + case CompositionOp::OP_DARKEN: + return CAIRO_OPERATOR_DARKEN; + case CompositionOp::OP_LIGHTEN: + return CAIRO_OPERATOR_LIGHTEN; + case CompositionOp::OP_COLOR_DODGE: + return CAIRO_OPERATOR_COLOR_DODGE; + case CompositionOp::OP_COLOR_BURN: + return CAIRO_OPERATOR_COLOR_BURN; + case CompositionOp::OP_HARD_LIGHT: + return CAIRO_OPERATOR_HARD_LIGHT; + case CompositionOp::OP_SOFT_LIGHT: + return CAIRO_OPERATOR_SOFT_LIGHT; + case CompositionOp::OP_DIFFERENCE: + return CAIRO_OPERATOR_DIFFERENCE; + case CompositionOp::OP_EXCLUSION: + return CAIRO_OPERATOR_EXCLUSION; + case CompositionOp::OP_HUE: + return CAIRO_OPERATOR_HSL_HUE; + case CompositionOp::OP_SATURATION: + return CAIRO_OPERATOR_HSL_SATURATION; + case CompositionOp::OP_COLOR: + return CAIRO_OPERATOR_HSL_COLOR; + case CompositionOp::OP_LUMINOSITY: + return CAIRO_OPERATOR_HSL_LUMINOSITY; + case CompositionOp::OP_COUNT: + break; + } + + return CAIRO_OPERATOR_OVER; +} + +static inline cairo_antialias_t GfxAntialiasToCairoAntialias( + AntialiasMode antialias) { + switch (antialias) { + case AntialiasMode::NONE: + return CAIRO_ANTIALIAS_NONE; + case AntialiasMode::GRAY: + return CAIRO_ANTIALIAS_GRAY; + case AntialiasMode::SUBPIXEL: + return CAIRO_ANTIALIAS_SUBPIXEL; + default: + return CAIRO_ANTIALIAS_DEFAULT; + } +} + +static inline AntialiasMode CairoAntialiasToGfxAntialias( + cairo_antialias_t aAntialias) { + switch (aAntialias) { + case CAIRO_ANTIALIAS_NONE: + return AntialiasMode::NONE; + case CAIRO_ANTIALIAS_GRAY: + return AntialiasMode::GRAY; + case CAIRO_ANTIALIAS_SUBPIXEL: + return AntialiasMode::SUBPIXEL; + default: + return AntialiasMode::DEFAULT; + } +} + +static inline cairo_filter_t GfxSamplingFilterToCairoFilter( + SamplingFilter filter) { + switch (filter) { + case SamplingFilter::GOOD: + return CAIRO_FILTER_GOOD; + case SamplingFilter::LINEAR: + return CAIRO_FILTER_BILINEAR; + case SamplingFilter::POINT: + return CAIRO_FILTER_NEAREST; + default: + MOZ_CRASH("GFX: bad Cairo filter"); + } + + return CAIRO_FILTER_BILINEAR; +} + +static inline cairo_extend_t GfxExtendToCairoExtend(ExtendMode extend) { + switch (extend) { + case ExtendMode::CLAMP: + return CAIRO_EXTEND_PAD; + // Cairo doesn't support tiling in only 1 direction, + // So we have to fallback and tile in both. + case ExtendMode::REPEAT_X: + case ExtendMode::REPEAT_Y: + case ExtendMode::REPEAT: + return CAIRO_EXTEND_REPEAT; + case ExtendMode::REFLECT: + return CAIRO_EXTEND_REFLECT; + } + + return CAIRO_EXTEND_PAD; +} + +static inline cairo_format_t GfxFormatToCairoFormat(SurfaceFormat format) { + switch (format) { + case SurfaceFormat::A8R8G8B8_UINT32: + return CAIRO_FORMAT_ARGB32; + case SurfaceFormat::X8R8G8B8_UINT32: + return CAIRO_FORMAT_RGB24; + case SurfaceFormat::A8: + return CAIRO_FORMAT_A8; + case SurfaceFormat::R5G6B5_UINT16: + return CAIRO_FORMAT_RGB16_565; + default: + gfxCriticalError() << "Unknown image format " << (int)format; + return CAIRO_FORMAT_ARGB32; + } +} + +static inline cairo_format_t CairoContentToCairoFormat( + cairo_content_t content) { + switch (content) { + case CAIRO_CONTENT_COLOR: + return CAIRO_FORMAT_RGB24; + case CAIRO_CONTENT_ALPHA: + return CAIRO_FORMAT_A8; + case CAIRO_CONTENT_COLOR_ALPHA: + return CAIRO_FORMAT_ARGB32; + default: + gfxCriticalError() << "Unknown cairo content type " << (int)content; + return CAIRO_FORMAT_A8; // least likely to cause OOB reads + } +} + +static inline cairo_content_t GfxFormatToCairoContent(SurfaceFormat format) { + switch (format) { + case SurfaceFormat::A8R8G8B8_UINT32: + return CAIRO_CONTENT_COLOR_ALPHA; + case SurfaceFormat::X8R8G8B8_UINT32: + case SurfaceFormat::R5G6B5_UINT16: // fall through + return CAIRO_CONTENT_COLOR; + case SurfaceFormat::A8: + return CAIRO_CONTENT_ALPHA; + default: + gfxCriticalError() << "Unknown image content format " << (int)format; + return CAIRO_CONTENT_COLOR_ALPHA; + } +} + +static inline cairo_line_join_t GfxLineJoinToCairoLineJoin(JoinStyle style) { + switch (style) { + case JoinStyle::BEVEL: + return CAIRO_LINE_JOIN_BEVEL; + case JoinStyle::ROUND: + return CAIRO_LINE_JOIN_ROUND; + case JoinStyle::MITER: + return CAIRO_LINE_JOIN_MITER; + case JoinStyle::MITER_OR_BEVEL: + return CAIRO_LINE_JOIN_MITER; + } + + return CAIRO_LINE_JOIN_MITER; +} + +static inline cairo_line_cap_t GfxLineCapToCairoLineCap(CapStyle style) { + switch (style) { + case CapStyle::BUTT: + return CAIRO_LINE_CAP_BUTT; + case CapStyle::ROUND: + return CAIRO_LINE_CAP_ROUND; + case CapStyle::SQUARE: + return CAIRO_LINE_CAP_SQUARE; + } + + return CAIRO_LINE_CAP_BUTT; +} + +static inline SurfaceFormat CairoContentToGfxFormat(cairo_content_t content) { + switch (content) { + case CAIRO_CONTENT_COLOR_ALPHA: + return SurfaceFormat::A8R8G8B8_UINT32; + case CAIRO_CONTENT_COLOR: + // BEWARE! format may be 565 + return SurfaceFormat::X8R8G8B8_UINT32; + case CAIRO_CONTENT_ALPHA: + return SurfaceFormat::A8; + } + + return SurfaceFormat::B8G8R8A8; +} + +static inline SurfaceFormat CairoFormatToGfxFormat(cairo_format_t format) { + switch (format) { + case CAIRO_FORMAT_ARGB32: + return SurfaceFormat::A8R8G8B8_UINT32; + case CAIRO_FORMAT_RGB24: + return SurfaceFormat::X8R8G8B8_UINT32; + case CAIRO_FORMAT_A8: + return SurfaceFormat::A8; + case CAIRO_FORMAT_RGB16_565: + return SurfaceFormat::R5G6B5_UINT16; + default: + gfxCriticalError() << "Unknown cairo format " << format; + return SurfaceFormat::UNKNOWN; + } +} + +static inline FontHinting CairoHintingToGfxHinting( + cairo_hint_style_t aHintStyle) { + switch (aHintStyle) { + case CAIRO_HINT_STYLE_NONE: + return FontHinting::NONE; + case CAIRO_HINT_STYLE_SLIGHT: + return FontHinting::LIGHT; + case CAIRO_HINT_STYLE_MEDIUM: + return FontHinting::NORMAL; + case CAIRO_HINT_STYLE_FULL: + return FontHinting::FULL; + default: + return FontHinting::NORMAL; + } +} + +SurfaceFormat GfxFormatForCairoSurface(cairo_surface_t* surface); + +static inline void GfxMatrixToCairoMatrix(const Matrix& mat, + cairo_matrix_t& retval) { + cairo_matrix_init(&retval, mat._11, mat._12, mat._21, mat._22, mat._31, + mat._32); +} + +static inline void SetCairoStrokeOptions(cairo_t* aCtx, + const StrokeOptions& aStrokeOptions) { + cairo_set_line_width(aCtx, aStrokeOptions.mLineWidth); + + cairo_set_miter_limit(aCtx, aStrokeOptions.mMiterLimit); + + if (aStrokeOptions.mDashPattern) { + // Convert array of floats to array of doubles + std::vector<double> dashes(aStrokeOptions.mDashLength); + bool nonZero = false; + for (size_t i = 0; i < aStrokeOptions.mDashLength; ++i) { + if (aStrokeOptions.mDashPattern[i] != 0) { + nonZero = true; + } + dashes[i] = aStrokeOptions.mDashPattern[i]; + } + // Avoid all-zero patterns that would trigger the CAIRO_STATUS_INVALID_DASH + // context error state. + if (nonZero) { + cairo_set_dash(aCtx, &dashes[0], aStrokeOptions.mDashLength, + aStrokeOptions.mDashOffset); + } + } + + cairo_set_line_join(aCtx, + GfxLineJoinToCairoLineJoin(aStrokeOptions.mLineJoin)); + + cairo_set_line_cap(aCtx, GfxLineCapToCairoLineCap(aStrokeOptions.mLineCap)); +} + +static inline cairo_fill_rule_t GfxFillRuleToCairoFillRule(FillRule rule) { + switch (rule) { + case FillRule::FILL_WINDING: + return CAIRO_FILL_RULE_WINDING; + case FillRule::FILL_EVEN_ODD: + return CAIRO_FILL_RULE_EVEN_ODD; + } + + return CAIRO_FILL_RULE_WINDING; +} + +// RAII class for temporarily changing the cairo matrix transform. It will use +// the given matrix transform while it is in scope. When it goes out of scope +// it will put the cairo context back the way it was. + +class CairoTempMatrix { + public: + CairoTempMatrix(cairo_t* aCtx, const Matrix& aMatrix) : mCtx(aCtx) { + cairo_get_matrix(aCtx, &mSaveMatrix); + cairo_matrix_t matrix; + GfxMatrixToCairoMatrix(aMatrix, matrix); + cairo_set_matrix(aCtx, &matrix); + } + + ~CairoTempMatrix() { cairo_set_matrix(mCtx, &mSaveMatrix); } + + private: + cairo_t* mCtx; + cairo_matrix_t mSaveMatrix; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_HELPERSCAIRO_H_ */ diff --git a/gfx/2d/HelpersD2D.h b/gfx/2d/HelpersD2D.h new file mode 100644 index 0000000000..1c24edeaf0 --- /dev/null +++ b/gfx/2d/HelpersD2D.h @@ -0,0 +1,981 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_HELPERSD2D_H_ +#define MOZILLA_GFX_HELPERSD2D_H_ + +#include <d2d1_1.h> + +#include <vector> + +#include <dwrite.h> +#include "2D.h" +#include "Logging.h" +#include "ImageScaling.h" + +#include "ScaledFontDWrite.h" + +#undef min +#undef max + +namespace mozilla { +namespace gfx { + +RefPtr<ID2D1Factory1> D2DFactory(); + +static inline D2D1_POINT_2F D2DPoint(const Point& aPoint) { + return D2D1::Point2F(aPoint.x, aPoint.y); +} + +static inline D2D1_SIZE_U D2DIntSize(const IntSize& aSize) { + return D2D1::SizeU(aSize.width, aSize.height); +} + +template <typename T> +static inline D2D1_RECT_F D2DRect(const T& aRect) { + return D2D1::RectF(aRect.X(), aRect.Y(), aRect.XMost(), aRect.YMost()); +} + +static inline D2D1_ROUNDED_RECT D2DRoundedRect(const RoundedRect& aRect) { + return D2D1::RoundedRect(D2DRect(aRect.rect), + aRect.corners.BottomLeft().width, + aRect.corners.BottomLeft().height); +} + +static inline D2D1_EXTEND_MODE D2DExtend(ExtendMode aExtendMode, Axis aAxis) { + D2D1_EXTEND_MODE extend; + switch (aExtendMode) { + case ExtendMode::REPEAT: + extend = D2D1_EXTEND_MODE_WRAP; + break; + case ExtendMode::REPEAT_X: { + extend = aAxis == Axis::X_AXIS ? D2D1_EXTEND_MODE_WRAP + : D2D1_EXTEND_MODE_CLAMP; + break; + } + case ExtendMode::REPEAT_Y: { + extend = aAxis == Axis::Y_AXIS ? D2D1_EXTEND_MODE_WRAP + : D2D1_EXTEND_MODE_CLAMP; + break; + } + case ExtendMode::REFLECT: + extend = D2D1_EXTEND_MODE_MIRROR; + break; + default: + extend = D2D1_EXTEND_MODE_CLAMP; + } + + return extend; +} + +static inline D2D1_BITMAP_INTERPOLATION_MODE D2DFilter( + const SamplingFilter aSamplingFilter) { + switch (aSamplingFilter) { + case SamplingFilter::POINT: + return D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR; + default: + return D2D1_BITMAP_INTERPOLATION_MODE_LINEAR; + } +} + +static inline D2D1_INTERPOLATION_MODE D2DInterpolationMode( + const SamplingFilter aSamplingFilter) { + switch (aSamplingFilter) { + case SamplingFilter::POINT: + return D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR; + default: + return D2D1_INTERPOLATION_MODE_LINEAR; + } +} + +static inline D2D1_MATRIX_5X4_F D2DMatrix5x4(const Matrix5x4& aMatrix) { + return D2D1::Matrix5x4F(aMatrix._11, aMatrix._12, aMatrix._13, aMatrix._14, + aMatrix._21, aMatrix._22, aMatrix._23, aMatrix._24, + aMatrix._31, aMatrix._32, aMatrix._33, aMatrix._34, + aMatrix._41, aMatrix._42, aMatrix._43, aMatrix._44, + aMatrix._51, aMatrix._52, aMatrix._53, aMatrix._54); +} + +static inline D2D1_VECTOR_3F D2DVector3D(const Point3D& aPoint) { + return D2D1::Vector3F(aPoint.x, aPoint.y, aPoint.z); +} + +static inline D2D1_ANTIALIAS_MODE D2DAAMode(AntialiasMode aMode) { + switch (aMode) { + case AntialiasMode::NONE: + return D2D1_ANTIALIAS_MODE_ALIASED; + default: + return D2D1_ANTIALIAS_MODE_PER_PRIMITIVE; + } +} + +static inline D2D1_MATRIX_3X2_F D2DMatrix(const Matrix& aTransform) { + return D2D1::Matrix3x2F(aTransform._11, aTransform._12, aTransform._21, + aTransform._22, aTransform._31, aTransform._32); +} + +static inline D2D1_COLOR_F D2DColor(const DeviceColor& aColor) { + return D2D1::ColorF(aColor.r, aColor.g, aColor.b, aColor.a); +} + +static inline IntSize ToIntSize(const D2D1_SIZE_U& aSize) { + return IntSize(aSize.width, aSize.height); +} + +static inline SurfaceFormat ToPixelFormat(const DXGI_FORMAT& aFormat) { + switch (aFormat) { + case DXGI_FORMAT_A8_UNORM: + case DXGI_FORMAT_R8_UNORM: + return SurfaceFormat::A8; + default: + return SurfaceFormat::B8G8R8A8; + } +} + +static inline SurfaceFormat ToPixelFormat(const D2D1_PIXEL_FORMAT& aFormat) { + switch (aFormat.format) { + case DXGI_FORMAT_A8_UNORM: + case DXGI_FORMAT_R8_UNORM: + return SurfaceFormat::A8; + case DXGI_FORMAT_B8G8R8A8_UNORM: + if (aFormat.alphaMode == D2D1_ALPHA_MODE_IGNORE) { + return SurfaceFormat::B8G8R8X8; + } else { + return SurfaceFormat::B8G8R8A8; + } + default: + return SurfaceFormat::B8G8R8A8; + } +} + +static inline Rect ToRect(const D2D1_RECT_F& aRect) { + return Rect(aRect.left, aRect.top, aRect.right - aRect.left, + aRect.bottom - aRect.top); +} + +static inline Matrix ToMatrix(const D2D1_MATRIX_3X2_F& aTransform) { + return Matrix(aTransform._11, aTransform._12, aTransform._21, aTransform._22, + aTransform._31, aTransform._32); +} + +static inline Point ToPoint(const D2D1_POINT_2F& aPoint) { + return Point(aPoint.x, aPoint.y); +} + +static inline DXGI_FORMAT DXGIFormat(SurfaceFormat aFormat) { + switch (aFormat) { + case SurfaceFormat::B8G8R8A8: + return DXGI_FORMAT_B8G8R8A8_UNORM; + case SurfaceFormat::B8G8R8X8: + return DXGI_FORMAT_B8G8R8A8_UNORM; + case SurfaceFormat::A8: + return DXGI_FORMAT_A8_UNORM; + default: + return DXGI_FORMAT_UNKNOWN; + } +} + +static inline D2D1_ALPHA_MODE D2DAlphaModeForFormat(SurfaceFormat aFormat) { + switch (aFormat) { + case SurfaceFormat::B8G8R8X8: + return D2D1_ALPHA_MODE_IGNORE; + default: + return D2D1_ALPHA_MODE_PREMULTIPLIED; + } +} + +static inline D2D1_PIXEL_FORMAT D2DPixelFormat(SurfaceFormat aFormat) { + return D2D1::PixelFormat(DXGIFormat(aFormat), D2DAlphaModeForFormat(aFormat)); +} + +static inline bool D2DSupportsCompositeMode(CompositionOp aOp) { + switch (aOp) { + case CompositionOp::OP_OVER: + case CompositionOp::OP_ADD: + case CompositionOp::OP_ATOP: + case CompositionOp::OP_OUT: + case CompositionOp::OP_IN: + case CompositionOp::OP_SOURCE: + case CompositionOp::OP_DEST_IN: + case CompositionOp::OP_DEST_OUT: + case CompositionOp::OP_DEST_OVER: + case CompositionOp::OP_DEST_ATOP: + case CompositionOp::OP_XOR: + case CompositionOp::OP_CLEAR: + return true; + default: + return false; + } +} + +static inline D2D1_COMPOSITE_MODE D2DCompositionMode(CompositionOp aOp) { + switch (aOp) { + case CompositionOp::OP_OVER: + return D2D1_COMPOSITE_MODE_SOURCE_OVER; + case CompositionOp::OP_ADD: + return D2D1_COMPOSITE_MODE_PLUS; + case CompositionOp::OP_ATOP: + return D2D1_COMPOSITE_MODE_SOURCE_ATOP; + case CompositionOp::OP_OUT: + return D2D1_COMPOSITE_MODE_SOURCE_OUT; + case CompositionOp::OP_IN: + return D2D1_COMPOSITE_MODE_SOURCE_IN; + case CompositionOp::OP_SOURCE: + return D2D1_COMPOSITE_MODE_SOURCE_COPY; + case CompositionOp::OP_DEST_IN: + return D2D1_COMPOSITE_MODE_DESTINATION_IN; + case CompositionOp::OP_DEST_OUT: + return D2D1_COMPOSITE_MODE_DESTINATION_OUT; + case CompositionOp::OP_DEST_OVER: + return D2D1_COMPOSITE_MODE_DESTINATION_OVER; + case CompositionOp::OP_DEST_ATOP: + return D2D1_COMPOSITE_MODE_DESTINATION_ATOP; + case CompositionOp::OP_XOR: + return D2D1_COMPOSITE_MODE_XOR; + case CompositionOp::OP_CLEAR: + return D2D1_COMPOSITE_MODE_DESTINATION_OUT; + default: + return D2D1_COMPOSITE_MODE_SOURCE_OVER; + } +} + +static inline D2D1_BLEND_MODE D2DBlendMode(CompositionOp aOp) { + switch (aOp) { + case CompositionOp::OP_MULTIPLY: + return D2D1_BLEND_MODE_MULTIPLY; + case CompositionOp::OP_SCREEN: + return D2D1_BLEND_MODE_SCREEN; + case CompositionOp::OP_OVERLAY: + return D2D1_BLEND_MODE_OVERLAY; + case CompositionOp::OP_DARKEN: + return D2D1_BLEND_MODE_DARKEN; + case CompositionOp::OP_LIGHTEN: + return D2D1_BLEND_MODE_LIGHTEN; + case CompositionOp::OP_COLOR_DODGE: + return D2D1_BLEND_MODE_COLOR_DODGE; + case CompositionOp::OP_COLOR_BURN: + return D2D1_BLEND_MODE_COLOR_BURN; + case CompositionOp::OP_HARD_LIGHT: + return D2D1_BLEND_MODE_HARD_LIGHT; + case CompositionOp::OP_SOFT_LIGHT: + return D2D1_BLEND_MODE_SOFT_LIGHT; + case CompositionOp::OP_DIFFERENCE: + return D2D1_BLEND_MODE_DIFFERENCE; + case CompositionOp::OP_EXCLUSION: + return D2D1_BLEND_MODE_EXCLUSION; + case CompositionOp::OP_HUE: + return D2D1_BLEND_MODE_HUE; + case CompositionOp::OP_SATURATION: + return D2D1_BLEND_MODE_SATURATION; + case CompositionOp::OP_COLOR: + return D2D1_BLEND_MODE_COLOR; + case CompositionOp::OP_LUMINOSITY: + return D2D1_BLEND_MODE_LUMINOSITY; + default: + return D2D1_BLEND_MODE_MULTIPLY; + } +} + +static inline bool D2DSupportsPrimitiveBlendMode(CompositionOp aOp) { + switch (aOp) { + case CompositionOp::OP_OVER: + // case CompositionOp::OP_SOURCE: + // case CompositionOp::OP_DARKEN: + case CompositionOp::OP_ADD: + return true; + default: + return false; + } +} + +static inline D2D1_PRIMITIVE_BLEND D2DPrimitiveBlendMode(CompositionOp aOp) { + switch (aOp) { + case CompositionOp::OP_OVER: + return D2D1_PRIMITIVE_BLEND_SOURCE_OVER; + // D2D1_PRIMITIVE_BLEND_COPY should leave pixels out of the source's + // bounds unchanged, but doesn't- breaking unbounded ops. + // D2D1_PRIMITIVE_BLEND_MIN doesn't quite work like darken either, as it + // accounts for the source alpha. + // + // case CompositionOp::OP_SOURCE: + // return D2D1_PRIMITIVE_BLEND_COPY; + // case CompositionOp::OP_DARKEN: + // return D2D1_PRIMITIVE_BLEND_MIN; + case CompositionOp::OP_ADD: + return D2D1_PRIMITIVE_BLEND_ADD; + default: + return D2D1_PRIMITIVE_BLEND_SOURCE_OVER; + } +} + +static inline bool IsPatternSupportedByD2D( + const Pattern& aPattern, CompositionOp aOp = CompositionOp::OP_OVER) { + if (aOp == CompositionOp::OP_CLEAR) { + return true; + } + + if (aPattern.GetType() == PatternType::CONIC_GRADIENT) { + return false; + } + + if (aPattern.GetType() != PatternType::RADIAL_GRADIENT) { + return true; + } + + const RadialGradientPattern* pat = + static_cast<const RadialGradientPattern*>(&aPattern); + + if (pat->mRadius1 != 0) { + return false; + } + + Point diff = pat->mCenter2 - pat->mCenter1; + + if (sqrt(diff.x.value * diff.x.value + diff.y.value * diff.y.value) >= + pat->mRadius2) { + // Inner point lies outside the circle. + return false; + } + + return true; +} + +/** + * This structure is used to pass rectangles to our shader constant. We can use + * this for passing rectangular areas to SetVertexShaderConstant. In the format + * of a 4 component float(x,y,width,height). Our vertex shader can then use + * this to construct rectangular positions from the 0,0-1,1 quad that we source + * it with. + */ +struct ShaderConstantRectD3D10 { + float mX, mY, mWidth, mHeight; + ShaderConstantRectD3D10(float aX, float aY, float aWidth, float aHeight) + : mX(aX), mY(aY), mWidth(aWidth), mHeight(aHeight) {} + + // For easy passing to SetVertexShaderConstantF. + operator float*() { return &mX; } +}; + +static inline DWRITE_MATRIX DWriteMatrixFromMatrix(Matrix& aMatrix) { + DWRITE_MATRIX mat; + mat.m11 = aMatrix._11; + mat.m12 = aMatrix._12; + mat.m21 = aMatrix._21; + mat.m22 = aMatrix._22; + mat.dx = aMatrix._31; + mat.dy = aMatrix._32; + return mat; +} + +class AutoDWriteGlyphRun : public DWRITE_GLYPH_RUN { + static const unsigned kNumAutoGlyphs = 256; + + public: + AutoDWriteGlyphRun() { glyphCount = 0; } + + ~AutoDWriteGlyphRun() { + if (glyphCount > kNumAutoGlyphs) { + delete[] glyphIndices; + delete[] glyphAdvances; + delete[] glyphOffsets; + } + } + + void allocate(unsigned aNumGlyphs) { + glyphCount = aNumGlyphs; + if (aNumGlyphs <= kNumAutoGlyphs) { + glyphIndices = &mAutoIndices[0]; + glyphAdvances = &mAutoAdvances[0]; + glyphOffsets = &mAutoOffsets[0]; + } else { + glyphIndices = new UINT16[aNumGlyphs]; + glyphAdvances = new FLOAT[aNumGlyphs]; + glyphOffsets = new DWRITE_GLYPH_OFFSET[aNumGlyphs]; + } + } + + private: + DWRITE_GLYPH_OFFSET mAutoOffsets[kNumAutoGlyphs]; + FLOAT mAutoAdvances[kNumAutoGlyphs]; + UINT16 mAutoIndices[kNumAutoGlyphs]; +}; + +static inline void DWriteGlyphRunFromGlyphs(const GlyphBuffer& aGlyphs, + ScaledFontDWrite* aFont, + AutoDWriteGlyphRun* run) { + run->allocate(aGlyphs.mNumGlyphs); + + FLOAT* advances = const_cast<FLOAT*>(run->glyphAdvances); + UINT16* indices = const_cast<UINT16*>(run->glyphIndices); + DWRITE_GLYPH_OFFSET* offsets = + const_cast<DWRITE_GLYPH_OFFSET*>(run->glyphOffsets); + + memset(advances, 0, sizeof(FLOAT) * aGlyphs.mNumGlyphs); + for (unsigned int i = 0; i < aGlyphs.mNumGlyphs; i++) { + indices[i] = aGlyphs.mGlyphs[i].mIndex; + offsets[i].advanceOffset = aGlyphs.mGlyphs[i].mPosition.x; + offsets[i].ascenderOffset = -aGlyphs.mGlyphs[i].mPosition.y; + } + + run->bidiLevel = 0; + run->fontFace = aFont->mFontFace; + run->fontEmSize = aFont->GetSize(); + run->glyphCount = aGlyphs.mNumGlyphs; + run->isSideways = FALSE; +} + +static inline already_AddRefed<ID2D1Geometry> ConvertRectToGeometry( + const D2D1_RECT_F& aRect) { + RefPtr<ID2D1RectangleGeometry> rectGeom; + D2DFactory()->CreateRectangleGeometry(&aRect, getter_AddRefs(rectGeom)); + return rectGeom.forget(); +} + +static inline already_AddRefed<ID2D1Geometry> GetTransformedGeometry( + ID2D1Geometry* aGeometry, const D2D1_MATRIX_3X2_F& aTransform) { + RefPtr<ID2D1PathGeometry> tmpGeometry; + D2DFactory()->CreatePathGeometry(getter_AddRefs(tmpGeometry)); + RefPtr<ID2D1GeometrySink> currentSink; + tmpGeometry->Open(getter_AddRefs(currentSink)); + aGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, + aTransform, currentSink); + currentSink->Close(); + return tmpGeometry.forget(); +} + +static inline already_AddRefed<ID2D1Geometry> IntersectGeometry( + ID2D1Geometry* aGeometryA, ID2D1Geometry* aGeometryB) { + RefPtr<ID2D1PathGeometry> pathGeom; + D2DFactory()->CreatePathGeometry(getter_AddRefs(pathGeom)); + RefPtr<ID2D1GeometrySink> sink; + pathGeom->Open(getter_AddRefs(sink)); + aGeometryA->CombineWithGeometry(aGeometryB, D2D1_COMBINE_MODE_INTERSECT, + nullptr, sink); + sink->Close(); + + return pathGeom.forget(); +} + +static inline already_AddRefed<ID2D1StrokeStyle> CreateStrokeStyleForOptions( + const StrokeOptions& aStrokeOptions) { + RefPtr<ID2D1StrokeStyle> style; + + D2D1_CAP_STYLE capStyle; + D2D1_LINE_JOIN joinStyle; + + switch (aStrokeOptions.mLineCap) { + case CapStyle::BUTT: + capStyle = D2D1_CAP_STYLE_FLAT; + break; + case CapStyle::ROUND: + capStyle = D2D1_CAP_STYLE_ROUND; + break; + case CapStyle::SQUARE: + capStyle = D2D1_CAP_STYLE_SQUARE; + break; + } + + switch (aStrokeOptions.mLineJoin) { + case JoinStyle::MITER: + joinStyle = D2D1_LINE_JOIN_MITER; + break; + case JoinStyle::MITER_OR_BEVEL: + joinStyle = D2D1_LINE_JOIN_MITER_OR_BEVEL; + break; + case JoinStyle::ROUND: + joinStyle = D2D1_LINE_JOIN_ROUND; + break; + case JoinStyle::BEVEL: + joinStyle = D2D1_LINE_JOIN_BEVEL; + break; + } + + HRESULT hr; + // We need to check mDashLength in addition to mDashPattern here since if + // mDashPattern is set but mDashLength is zero then the stroke will fail to + // paint. + if (aStrokeOptions.mDashLength > 0 && aStrokeOptions.mDashPattern) { + typedef std::vector<Float> FloatVector; + // D2D "helpfully" multiplies the dash pattern by the line width. + // That's not what cairo does, or is what <canvas>'s dash wants. + // So fix the multiplication in advance. + Float lineWidth = aStrokeOptions.mLineWidth; + FloatVector dash(aStrokeOptions.mDashPattern, + aStrokeOptions.mDashPattern + aStrokeOptions.mDashLength); + for (FloatVector::iterator it = dash.begin(); it != dash.end(); ++it) { + *it /= lineWidth; + } + + hr = D2DFactory()->CreateStrokeStyle( + D2D1::StrokeStyleProperties( + capStyle, capStyle, capStyle, joinStyle, aStrokeOptions.mMiterLimit, + D2D1_DASH_STYLE_CUSTOM, aStrokeOptions.mDashOffset / lineWidth), + &dash[0], // data() is not C++98, although it's in recent gcc + // and VC10's STL + dash.size(), getter_AddRefs(style)); + } else { + hr = D2DFactory()->CreateStrokeStyle( + D2D1::StrokeStyleProperties(capStyle, capStyle, capStyle, joinStyle, + aStrokeOptions.mMiterLimit), + nullptr, 0, getter_AddRefs(style)); + } + + if (FAILED(hr)) { + gfxWarning() << "Failed to create Direct2D stroke style."; + } + + return style.forget(); +} + +// This creates a (partially) uploaded bitmap for a DataSourceSurface. It +// uploads the minimum requirement and possibly downscales. It adjusts the +// input Matrix to compensate. +static inline already_AddRefed<ID2D1Bitmap> CreatePartialBitmapForSurface( + DataSourceSurface* aSurface, const Matrix& aDestinationTransform, + const IntSize& aDestinationSize, ExtendMode aExtendMode, + Matrix& aSourceTransform, ID2D1RenderTarget* aRT, + const IntRect* aSourceRect = nullptr) { + RefPtr<ID2D1Bitmap> bitmap; + + // This is where things get complicated. The source surface was + // created for a surface that was too large to fit in a texture. + // We'll need to figure out if we can work with a partial upload + // or downsample in software. + + Matrix transform = aDestinationTransform; + Matrix invTransform = transform = aSourceTransform * transform; + if (!invTransform.Invert()) { + // Singular transform, nothing to be drawn. + return nullptr; + } + + Rect rect(0, 0, Float(aDestinationSize.width), + Float(aDestinationSize.height)); + + // Calculate the rectangle of the source mapped to our surface. + rect = invTransform.TransformBounds(rect); + rect.RoundOut(); + + IntSize size = aSurface->GetSize(); + + Rect uploadRect(0, 0, Float(size.width), Float(size.height)); + if (aSourceRect) { + uploadRect = Rect(aSourceRect->X(), aSourceRect->Y(), aSourceRect->Width(), + aSourceRect->Height()); + } + + // Limit the uploadRect as much as possible without supporting discontiguous + // uploads + // + // clang-format off + // region we will paint from + // uploadRect + // .---------------. .---------------. resulting uploadRect + // | |rect | | + // | .---------. .----. .----. .---------------. + // | | | ----> | | | | ----> | | + // | '---------' '----' '----' '---------------' + // '---------------' '---------------' + // clang-format on + // + // + + int Bpp = BytesPerPixel(aSurface->GetFormat()); + + if (uploadRect.Contains(rect)) { + // Extend mode is irrelevant, the displayed rect is completely contained + // by the source bitmap. + uploadRect = rect; + } else if (aExtendMode == ExtendMode::CLAMP && uploadRect.Intersects(rect)) { + // Calculate the rectangle on the source bitmap that touches our + // surface, and upload that, for ExtendMode::CLAMP we can actually guarantee + // correct behaviour in this case. + uploadRect = uploadRect.Intersect(rect); + + // We now proceed to check if we can limit at least one dimension of the + // upload rect safely without looking at extend mode. + } else if (rect.X() >= 0 && rect.XMost() < size.width) { + uploadRect.MoveToX(rect.X()); + uploadRect.SetWidth(rect.Width()); + } else if (rect.Y() >= 0 && rect.YMost() < size.height) { + uploadRect.MoveToY(rect.Y()); + uploadRect.SetHeight(rect.Height()); + } + + if (uploadRect.IsEmpty()) { + // Nothing to be drawn. + return nullptr; + } + + if (uploadRect.Width() <= aRT->GetMaximumBitmapSize() && + uploadRect.Height() <= aRT->GetMaximumBitmapSize()) { + { + // Scope to auto-Unmap() |mapping|. + DataSourceSurface::ScopedMap mapping(aSurface, DataSourceSurface::READ); + if (MOZ2D_WARN_IF(!mapping.IsMapped())) { + return nullptr; + } + + // A partial upload will suffice. + aRT->CreateBitmap( + D2D1::SizeU(uint32_t(uploadRect.Width()), + uint32_t(uploadRect.Height())), + mapping.GetData() + int(uploadRect.X()) * Bpp + + int(uploadRect.Y()) * mapping.GetStride(), + mapping.GetStride(), + D2D1::BitmapProperties(D2DPixelFormat(aSurface->GetFormat())), + getter_AddRefs(bitmap)); + } + + aSourceTransform.PreTranslate(uploadRect.X(), uploadRect.Y()); + + return bitmap.forget(); + } else { + if (Bpp != 4) { + // This shouldn't actually happen in practice! + MOZ_ASSERT(false); + return nullptr; + } + + { + // Scope to auto-Unmap() |mapping|. + DataSourceSurface::ScopedMap mapping(aSurface, DataSourceSurface::READ); + if (MOZ2D_WARN_IF(!mapping.IsMapped())) { + return nullptr; + } + ImageHalfScaler scaler(mapping.GetData(), mapping.GetStride(), size); + + // Calculate the maximum width/height of the image post transform. + Point topRight = transform.TransformPoint(Point(Float(size.width), 0)); + Point topLeft = transform.TransformPoint(Point(0, 0)); + Point bottomRight = transform.TransformPoint( + Point(Float(size.width), Float(size.height))); + Point bottomLeft = transform.TransformPoint(Point(0, Float(size.height))); + + IntSize scaleSize; + + scaleSize.width = int32_t(std::max(Distance(topRight, topLeft), + Distance(bottomRight, bottomLeft))); + scaleSize.height = int32_t(std::max(Distance(topRight, bottomRight), + Distance(topLeft, bottomLeft))); + + if (unsigned(scaleSize.width) > aRT->GetMaximumBitmapSize()) { + // Ok, in this case we'd really want a downscale of a part of the + // bitmap, perhaps we can do this later but for simplicity let's do + // something different here and assume it's good enough, this should be + // rare! + scaleSize.width = 4095; + } + if (unsigned(scaleSize.height) > aRT->GetMaximumBitmapSize()) { + scaleSize.height = 4095; + } + + scaler.ScaleForSize(scaleSize); + + IntSize newSize = scaler.GetSize(); + + if (newSize.IsEmpty()) { + return nullptr; + } + + aRT->CreateBitmap( + D2D1::SizeU(newSize.width, newSize.height), scaler.GetScaledData(), + scaler.GetStride(), + D2D1::BitmapProperties(D2DPixelFormat(aSurface->GetFormat())), + getter_AddRefs(bitmap)); + + aSourceTransform.PreScale(Float(size.width) / newSize.width, + Float(size.height) / newSize.height); + } + return bitmap.forget(); + } +} + +static inline void AddRectToSink(ID2D1GeometrySink* aSink, + const D2D1_RECT_F& aRect) { + aSink->BeginFigure(D2D1::Point2F(aRect.left, aRect.top), + D2D1_FIGURE_BEGIN_FILLED); + aSink->AddLine(D2D1::Point2F(aRect.right, aRect.top)); + aSink->AddLine(D2D1::Point2F(aRect.right, aRect.bottom)); + aSink->AddLine(D2D1::Point2F(aRect.left, aRect.bottom)); + aSink->EndFigure(D2D1_FIGURE_END_CLOSED); +} + +class DCCommandSink : public ID2D1CommandSink { + public: + explicit DCCommandSink(ID2D1DeviceContext* aCtx) : mCtx(aCtx) {} + + HRESULT STDMETHODCALLTYPE QueryInterface(const IID& aIID, void** aPtr) { + if (!aPtr) { + return E_POINTER; + } + + if (aIID == IID_IUnknown) { + *aPtr = static_cast<IUnknown*>(this); + return S_OK; + } else if (aIID == IID_ID2D1CommandSink) { + *aPtr = static_cast<ID2D1CommandSink*>(this); + return S_OK; + } + + return E_NOINTERFACE; + } + + ULONG STDMETHODCALLTYPE AddRef() { return 1; } + + ULONG STDMETHODCALLTYPE Release() { return 1; } + + STDMETHODIMP BeginDraw() { + // We don't want to do anything here! + return S_OK; + } + STDMETHODIMP EndDraw() { + // We don't want to do anything here! + return S_OK; + } + + STDMETHODIMP SetAntialiasMode(D2D1_ANTIALIAS_MODE antialiasMode) { + mCtx->SetAntialiasMode(antialiasMode); + return S_OK; + } + + STDMETHODIMP SetTags(D2D1_TAG tag1, D2D1_TAG tag2) { + mCtx->SetTags(tag1, tag2); + return S_OK; + } + + STDMETHODIMP SetTextAntialiasMode( + D2D1_TEXT_ANTIALIAS_MODE textAntialiasMode) { + mCtx->SetTextAntialiasMode(textAntialiasMode); + return S_OK; + } + + STDMETHODIMP SetTextRenderingParams( + _In_opt_ IDWriteRenderingParams* textRenderingParams) { + mCtx->SetTextRenderingParams(textRenderingParams); + return S_OK; + } + + STDMETHODIMP SetTransform(_In_ CONST D2D1_MATRIX_3X2_F* transform) { + mCtx->SetTransform(transform); + return S_OK; + } + + STDMETHODIMP SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND primitiveBlend) { + mCtx->SetPrimitiveBlend(primitiveBlend); + return S_OK; + } + + STDMETHODIMP SetUnitMode(D2D1_UNIT_MODE unitMode) { + mCtx->SetUnitMode(unitMode); + return S_OK; + } + + STDMETHODIMP Clear(_In_opt_ CONST D2D1_COLOR_F* color) { + mCtx->Clear(color); + return S_OK; + } + + STDMETHODIMP DrawGlyphRun( + D2D1_POINT_2F baselineOrigin, _In_ CONST DWRITE_GLYPH_RUN* glyphRun, + _In_opt_ CONST DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription, + _In_ ID2D1Brush* foregroundBrush, DWRITE_MEASURING_MODE measuringMode) { + mCtx->DrawGlyphRun(baselineOrigin, glyphRun, glyphRunDescription, + foregroundBrush, measuringMode); + return S_OK; + } + + STDMETHODIMP DrawLine(D2D1_POINT_2F point0, D2D1_POINT_2F point1, + _In_ ID2D1Brush* brush, FLOAT strokeWidth, + _In_opt_ ID2D1StrokeStyle* strokeStyle) { + mCtx->DrawLine(point0, point1, brush, strokeWidth, strokeStyle); + return S_OK; + } + + STDMETHODIMP DrawGeometry(_In_ ID2D1Geometry* geometry, + _In_ ID2D1Brush* brush, FLOAT strokeWidth, + _In_opt_ ID2D1StrokeStyle* strokeStyle) { + mCtx->DrawGeometry(geometry, brush, strokeWidth, strokeStyle); + return S_OK; + } + + STDMETHODIMP DrawRectangle(_In_ CONST D2D1_RECT_F* rect, + _In_ ID2D1Brush* brush, FLOAT strokeWidth, + _In_opt_ ID2D1StrokeStyle* strokeStyle) { + mCtx->DrawRectangle(rect, brush, strokeWidth, strokeStyle); + return S_OK; + } + + STDMETHODIMP DrawBitmap( + _In_ ID2D1Bitmap* bitmap, + _In_opt_ CONST D2D1_RECT_F* destinationRectangle, FLOAT opacity, + D2D1_INTERPOLATION_MODE interpolationMode, + _In_opt_ CONST D2D1_RECT_F* sourceRectangle, + _In_opt_ CONST D2D1_MATRIX_4X4_F* perspectiveTransform) { + mCtx->DrawBitmap(bitmap, destinationRectangle, opacity, interpolationMode, + sourceRectangle, perspectiveTransform); + return S_OK; + } + + STDMETHODIMP DrawImage(_In_ ID2D1Image* image, + _In_opt_ CONST D2D1_POINT_2F* targetOffset, + _In_opt_ CONST D2D1_RECT_F* imageRectangle, + D2D1_INTERPOLATION_MODE interpolationMode, + D2D1_COMPOSITE_MODE compositeMode) { + mCtx->DrawImage(image, targetOffset, imageRectangle, interpolationMode, + compositeMode); + return S_OK; + } + + STDMETHODIMP DrawGdiMetafile(_In_ ID2D1GdiMetafile* gdiMetafile, + _In_opt_ CONST D2D1_POINT_2F* targetOffset) { + mCtx->DrawGdiMetafile(gdiMetafile, targetOffset); + return S_OK; + } + + STDMETHODIMP FillMesh(_In_ ID2D1Mesh* mesh, _In_ ID2D1Brush* brush) { + mCtx->FillMesh(mesh, brush); + return S_OK; + } + + STDMETHODIMP FillOpacityMask(_In_ ID2D1Bitmap* opacityMask, + _In_ ID2D1Brush* brush, + _In_opt_ CONST D2D1_RECT_F* destinationRectangle, + _In_opt_ CONST D2D1_RECT_F* sourceRectangle) { + mCtx->FillOpacityMask(opacityMask, brush, destinationRectangle, + sourceRectangle); + return S_OK; + } + + STDMETHODIMP FillGeometry(_In_ ID2D1Geometry* geometry, + _In_ ID2D1Brush* brush, + _In_opt_ ID2D1Brush* opacityBrush) { + mCtx->FillGeometry(geometry, brush, opacityBrush); + return S_OK; + } + + STDMETHODIMP FillRectangle(_In_ CONST D2D1_RECT_F* rect, + _In_ ID2D1Brush* brush) { + mCtx->FillRectangle(rect, brush); + return S_OK; + } + + STDMETHODIMP PushAxisAlignedClip(_In_ CONST D2D1_RECT_F* clipRect, + D2D1_ANTIALIAS_MODE antialiasMode) { + mCtx->PushAxisAlignedClip(clipRect, antialiasMode); + return S_OK; + } + + STDMETHODIMP PushLayer(_In_ CONST D2D1_LAYER_PARAMETERS1* layerParameters1, + _In_opt_ ID2D1Layer* layer) { + mCtx->PushLayer(layerParameters1, layer); + return S_OK; + } + + STDMETHODIMP PopAxisAlignedClip() { + mCtx->PopAxisAlignedClip(); + return S_OK; + } + + STDMETHODIMP PopLayer() { + mCtx->PopLayer(); + return S_OK; + } + + ID2D1DeviceContext* mCtx; +}; + +class MOZ_STACK_CLASS AutoRestoreFP final { + public: + AutoRestoreFP() { + // save the current floating point control word + _controlfp_s(&savedFPSetting, 0, 0); + UINT unused; + // set the floating point control word to its default value + _controlfp_s(&unused, _CW_DEFAULT, MCW_PC); + } + ~AutoRestoreFP() { + UINT unused; + // restore the saved floating point control word + _controlfp_s(&unused, savedFPSetting, MCW_PC); + } + + private: + UINT savedFPSetting; +}; + +// Note that overrides of ID2D1SimplifiedGeometrySink methods in this class may +// get called from D2D with nonstandard floating point settings (see comments in +// bug 1134549) - use AutoRestoreFP to reset the floating point control word to +// what we expect +class StreamingGeometrySink : public ID2D1SimplifiedGeometrySink { + public: + explicit StreamingGeometrySink(PathSink* aSink) : mSink(aSink) {} + + HRESULT STDMETHODCALLTYPE QueryInterface(const IID& aIID, void** aPtr) { + if (!aPtr) { + return E_POINTER; + } + + if (aIID == IID_IUnknown) { + *aPtr = static_cast<IUnknown*>(this); + return S_OK; + } else if (aIID == IID_ID2D1SimplifiedGeometrySink) { + *aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this); + return S_OK; + } + + return E_NOINTERFACE; + } + + ULONG STDMETHODCALLTYPE AddRef() { return 1; } + + ULONG STDMETHODCALLTYPE Release() { return 1; } + + // We ignore SetFillMode, this depends on the destination sink. + STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode) { return; } + STDMETHOD_(void, BeginFigure) + (D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) { + AutoRestoreFP resetFloatingPoint; + mSink->MoveTo(ToPoint(aPoint)); + } + STDMETHOD_(void, AddLines)(const D2D1_POINT_2F* aLines, UINT aCount) { + AutoRestoreFP resetFloatingPoint; + for (UINT i = 0; i < aCount; i++) { + mSink->LineTo(ToPoint(aLines[i])); + } + } + STDMETHOD_(void, AddBeziers) + (const D2D1_BEZIER_SEGMENT* aSegments, UINT aCount) { + AutoRestoreFP resetFloatingPoint; + for (UINT i = 0; i < aCount; i++) { + mSink->BezierTo(ToPoint(aSegments[i].point1), + ToPoint(aSegments[i].point2), + ToPoint(aSegments[i].point3)); + } + } + STDMETHOD(Close)() { /* Should never be called! */ + return S_OK; + } + STDMETHOD_(void, SetSegmentFlags) + (D2D1_PATH_SEGMENT aFlags) { /* Should never be called! */ + } + + STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd) { + AutoRestoreFP resetFloatingPoint; + if (aEnd == D2D1_FIGURE_END_CLOSED) { + return mSink->Close(); + } + } + + private: + PathSink* mSink; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_HELPERSD2D_H_ */ diff --git a/gfx/2d/HelpersSkia.h b/gfx/2d/HelpersSkia.h new file mode 100644 index 0000000000..7f7bf6fbc1 --- /dev/null +++ b/gfx/2d/HelpersSkia.h @@ -0,0 +1,360 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_HELPERSSKIA_H_ +#define MOZILLA_GFX_HELPERSSKIA_H_ + +#include "2D.h" +#include "skia/include/core/SkCanvas.h" +#include "skia/include/core/SkPathEffect.h" +#include "skia/include/core/SkPathTypes.h" +#include "skia/include/core/SkShader.h" +#include "skia/include/effects/SkDashPathEffect.h" +#include "mozilla/Assertions.h" +#include <cmath> +#include <vector> +#include "nsDebug.h" + +namespace mozilla { +namespace gfx { + +static inline SkColorType GfxFormatToSkiaColorType(SurfaceFormat format) { + switch (format) { + case SurfaceFormat::B8G8R8A8: + return kBGRA_8888_SkColorType; + case SurfaceFormat::B8G8R8X8: + // We probably need to do something here. + return kBGRA_8888_SkColorType; + case SurfaceFormat::R5G6B5_UINT16: + return kRGB_565_SkColorType; + case SurfaceFormat::A8: + return kAlpha_8_SkColorType; + case SurfaceFormat::R8G8B8A8: + return kRGBA_8888_SkColorType; + case SurfaceFormat::A8R8G8B8: + MOZ_DIAGNOSTIC_ASSERT(false, "A8R8G8B8 unsupported by Skia"); + return kRGBA_8888_SkColorType; + default: + MOZ_DIAGNOSTIC_ASSERT(false, "Unknown surface format"); + return kRGBA_8888_SkColorType; + } +} + +static inline SurfaceFormat SkiaColorTypeToGfxFormat( + SkColorType aColorType, SkAlphaType aAlphaType = kPremul_SkAlphaType) { + switch (aColorType) { + case kBGRA_8888_SkColorType: + return aAlphaType == kOpaque_SkAlphaType ? SurfaceFormat::B8G8R8X8 + : SurfaceFormat::B8G8R8A8; + case kRGB_565_SkColorType: + return SurfaceFormat::R5G6B5_UINT16; + case kAlpha_8_SkColorType: + return SurfaceFormat::A8; + default: + return SurfaceFormat::B8G8R8A8; + } +} + +static inline SkAlphaType GfxFormatToSkiaAlphaType(SurfaceFormat format) { + switch (format) { + case SurfaceFormat::B8G8R8X8: + case SurfaceFormat::R5G6B5_UINT16: + return kOpaque_SkAlphaType; + default: + return kPremul_SkAlphaType; + } +} + +static inline SkImageInfo MakeSkiaImageInfo(const IntSize& aSize, + SurfaceFormat aFormat) { + return SkImageInfo::Make(aSize.width, aSize.height, + GfxFormatToSkiaColorType(aFormat), + GfxFormatToSkiaAlphaType(aFormat)); +} + +static inline void GfxMatrixToSkiaMatrix(const Matrix& mat, SkMatrix& retval) { + retval.setAll(SkFloatToScalar(mat._11), SkFloatToScalar(mat._21), + SkFloatToScalar(mat._31), SkFloatToScalar(mat._12), + SkFloatToScalar(mat._22), SkFloatToScalar(mat._32), 0, 0, + SK_Scalar1); +} + +static inline void GfxMatrixToSkiaMatrix(const Matrix4x4& aMatrix, + SkMatrix& aResult) { + aResult.setAll(SkFloatToScalar(aMatrix._11), SkFloatToScalar(aMatrix._21), + SkFloatToScalar(aMatrix._41), SkFloatToScalar(aMatrix._12), + SkFloatToScalar(aMatrix._22), SkFloatToScalar(aMatrix._42), + SkFloatToScalar(aMatrix._14), SkFloatToScalar(aMatrix._24), + SkFloatToScalar(aMatrix._44)); +} + +static inline SkPaint::Cap CapStyleToSkiaCap(CapStyle aCap) { + switch (aCap) { + case CapStyle::BUTT: + return SkPaint::kButt_Cap; + case CapStyle::ROUND: + return SkPaint::kRound_Cap; + case CapStyle::SQUARE: + return SkPaint::kSquare_Cap; + } + return SkPaint::kDefault_Cap; +} + +static inline SkPaint::Join JoinStyleToSkiaJoin(JoinStyle aJoin) { + switch (aJoin) { + case JoinStyle::BEVEL: + return SkPaint::kBevel_Join; + case JoinStyle::ROUND: + return SkPaint::kRound_Join; + case JoinStyle::MITER: + case JoinStyle::MITER_OR_BEVEL: + return SkPaint::kMiter_Join; + } + return SkPaint::kDefault_Join; +} + +static inline bool StrokeOptionsToPaint(SkPaint& aPaint, + const StrokeOptions& aOptions, + bool aUsePathEffects = true) { + // Skia renders 0 width strokes with a width of 1 (and in black), + // so we should just skip the draw call entirely. + // Skia does not handle non-finite line widths. + if (!aOptions.mLineWidth || !std::isfinite(aOptions.mLineWidth)) { + return false; + } + aPaint.setStrokeWidth(SkFloatToScalar(aOptions.mLineWidth)); + aPaint.setStrokeMiter(SkFloatToScalar(aOptions.mMiterLimit)); + aPaint.setStrokeCap(CapStyleToSkiaCap(aOptions.mLineCap)); + aPaint.setStrokeJoin(JoinStyleToSkiaJoin(aOptions.mLineJoin)); + + if (aOptions.mDashLength > 0 && aUsePathEffects) { + // Skia only supports dash arrays that are multiples of 2. + uint32_t dashCount; + + if (aOptions.mDashLength % 2 == 0) { + dashCount = aOptions.mDashLength; + } else { + dashCount = aOptions.mDashLength * 2; + } + + std::vector<SkScalar> pattern; + pattern.resize(dashCount); + + for (uint32_t i = 0; i < dashCount; i++) { + pattern[i] = + SkFloatToScalar(aOptions.mDashPattern[i % aOptions.mDashLength]); + } + + auto dash = SkDashPathEffect::Make(&pattern.front(), dashCount, + SkFloatToScalar(aOptions.mDashOffset)); + aPaint.setPathEffect(dash); + } + + aPaint.setStyle(SkPaint::kStroke_Style); + return true; +} + +static inline SkBlendMode GfxOpToSkiaOp(CompositionOp op) { + switch (op) { + case CompositionOp::OP_CLEAR: + return SkBlendMode::kClear; + case CompositionOp::OP_OVER: + return SkBlendMode::kSrcOver; + case CompositionOp::OP_ADD: + return SkBlendMode::kPlus; + case CompositionOp::OP_ATOP: + return SkBlendMode::kSrcATop; + case CompositionOp::OP_OUT: + return SkBlendMode::kSrcOut; + case CompositionOp::OP_IN: + return SkBlendMode::kSrcIn; + case CompositionOp::OP_SOURCE: + return SkBlendMode::kSrc; + case CompositionOp::OP_DEST_IN: + return SkBlendMode::kDstIn; + case CompositionOp::OP_DEST_OUT: + return SkBlendMode::kDstOut; + case CompositionOp::OP_DEST_OVER: + return SkBlendMode::kDstOver; + case CompositionOp::OP_DEST_ATOP: + return SkBlendMode::kDstATop; + case CompositionOp::OP_XOR: + return SkBlendMode::kXor; + case CompositionOp::OP_MULTIPLY: + return SkBlendMode::kMultiply; + case CompositionOp::OP_SCREEN: + return SkBlendMode::kScreen; + case CompositionOp::OP_OVERLAY: + return SkBlendMode::kOverlay; + case CompositionOp::OP_DARKEN: + return SkBlendMode::kDarken; + case CompositionOp::OP_LIGHTEN: + return SkBlendMode::kLighten; + case CompositionOp::OP_COLOR_DODGE: + return SkBlendMode::kColorDodge; + case CompositionOp::OP_COLOR_BURN: + return SkBlendMode::kColorBurn; + case CompositionOp::OP_HARD_LIGHT: + return SkBlendMode::kHardLight; + case CompositionOp::OP_SOFT_LIGHT: + return SkBlendMode::kSoftLight; + case CompositionOp::OP_DIFFERENCE: + return SkBlendMode::kDifference; + case CompositionOp::OP_EXCLUSION: + return SkBlendMode::kExclusion; + case CompositionOp::OP_HUE: + return SkBlendMode::kHue; + case CompositionOp::OP_SATURATION: + return SkBlendMode::kSaturation; + case CompositionOp::OP_COLOR: + return SkBlendMode::kColor; + case CompositionOp::OP_LUMINOSITY: + return SkBlendMode::kLuminosity; + case CompositionOp::OP_COUNT: + break; + } + + return SkBlendMode::kSrcOver; +} + +/* There's quite a bit of inconsistency about + * whether float colors should be rounded with .5f. + * We choose to do it to match cairo which also + * happens to match the Direct3D specs */ +static inline U8CPU ColorFloatToByte(Float color) { + // XXX: do a better job converting to int + return U8CPU(color * 255.f + .5f); +}; + +static inline SkColor ColorToSkColor(const DeviceColor& color, Float aAlpha) { + return SkColorSetARGB(ColorFloatToByte(color.a * aAlpha), + ColorFloatToByte(color.r), ColorFloatToByte(color.g), + ColorFloatToByte(color.b)); +} + +static inline SkPoint PointToSkPoint(const Point& aPoint) { + return SkPoint::Make(SkFloatToScalar(aPoint.x), SkFloatToScalar(aPoint.y)); +} + +static inline SkRect RectToSkRect(const Rect& aRect) { + return SkRect::MakeXYWH( + SkFloatToScalar(aRect.X()), SkFloatToScalar(aRect.Y()), + SkFloatToScalar(aRect.Width()), SkFloatToScalar(aRect.Height())); +} + +static inline SkRect IntRectToSkRect(const IntRect& aRect) { + return SkRect::MakeXYWH(SkIntToScalar(aRect.X()), SkIntToScalar(aRect.Y()), + SkIntToScalar(aRect.Width()), + SkIntToScalar(aRect.Height())); +} + +static inline SkIRect RectToSkIRect(const Rect& aRect) { + return SkIRect::MakeXYWH(int32_t(aRect.X()), int32_t(aRect.Y()), + int32_t(aRect.Width()), int32_t(aRect.Height())); +} + +static inline SkIRect IntRectToSkIRect(const IntRect& aRect) { + return SkIRect::MakeXYWH(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); +} + +static inline IntRect SkIRectToIntRect(const SkIRect& aRect) { + return IntRect(aRect.x(), aRect.y(), aRect.width(), aRect.height()); +} + +static inline Point SkPointToPoint(const SkPoint& aPoint) { + return Point(SkScalarToFloat(aPoint.x()), SkScalarToFloat(aPoint.y())); +} + +static inline Rect SkRectToRect(const SkRect& aRect) { + return Rect(SkScalarToFloat(aRect.x()), SkScalarToFloat(aRect.y()), + SkScalarToFloat(aRect.width()), SkScalarToFloat(aRect.height())); +} + +static inline SkTileMode ExtendModeToTileMode(ExtendMode aMode, Axis aAxis) { + switch (aMode) { + case ExtendMode::CLAMP: + return SkTileMode::kClamp; + case ExtendMode::REPEAT: + return SkTileMode::kRepeat; + case ExtendMode::REFLECT: + return SkTileMode::kMirror; + case ExtendMode::REPEAT_X: { + return aAxis == Axis::X_AXIS ? SkTileMode::kRepeat : SkTileMode::kClamp; + } + case ExtendMode::REPEAT_Y: { + return aAxis == Axis::Y_AXIS ? SkTileMode::kRepeat : SkTileMode::kClamp; + } + } + return SkTileMode::kClamp; +} + +static inline SkFontHinting GfxHintingToSkiaHinting(FontHinting aHinting) { + switch (aHinting) { + case FontHinting::NONE: + return SkFontHinting::kNone; + case FontHinting::LIGHT: + return SkFontHinting::kSlight; + case FontHinting::NORMAL: + return SkFontHinting::kNormal; + case FontHinting::FULL: + return SkFontHinting::kFull; + } + return SkFontHinting::kNormal; +} + +static inline FillRule GetFillRule(SkPathFillType aFillType) { + switch (aFillType) { + case SkPathFillType::kWinding: + return FillRule::FILL_WINDING; + case SkPathFillType::kEvenOdd: + return FillRule::FILL_EVEN_ODD; + case SkPathFillType::kInverseWinding: + case SkPathFillType::kInverseEvenOdd: + default: + NS_WARNING("Unsupported fill type\n"); + break; + } + + return FillRule::FILL_EVEN_ODD; +} + +/** + * Returns true if the canvas is backed by pixels. Returns false if the canvas + * wraps an SkPDFDocument, for example. + * + * Note: It is not clear whether the test used to implement this function may + * result in it returning false in some circumstances even when the canvas + * _is_ pixel backed. In other words maybe it is possible for such a canvas to + * have kUnknown_SkPixelGeometry? + */ +static inline bool IsBackedByPixels(const SkCanvas* aCanvas) { + SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + if (!aCanvas->getProps(&props) || + props.pixelGeometry() == kUnknown_SkPixelGeometry) { + return false; + } + return true; +} + +/** + * Computes appropriate resolution scale to be used with SkPath::getFillPath + * based on the scaling of the supplied transform. + */ +float ComputeResScaleForStroking(const Matrix& aTransform); + +/** + * This is a wrapper around SkGeometry's SkConic that can be used to convert + * conic sections in an SkPath to a sequence of quadratic curves. The quads + * vector is organized such that for the Nth quad, it's control points are + * 2*N, 2*N+1, 2*N+2. This function returns the resulting number of quads. + */ +int ConvertConicToQuads(const Point& aP0, const Point& aP1, const Point& aP2, + float aWeight, std::vector<Point>& aQuads); + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_HELPERSSKIA_H_ */ diff --git a/gfx/2d/HelpersWinFonts.h b/gfx/2d/HelpersWinFonts.h new file mode 100644 index 0000000000..9bfa69c9b7 --- /dev/null +++ b/gfx/2d/HelpersWinFonts.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +namespace mozilla { +namespace gfx { + +extern BYTE sSystemTextQuality; + +static BYTE GetSystemTextQuality() { return sSystemTextQuality; } + +static AntialiasMode GetSystemDefaultAAMode() { + AntialiasMode defaultMode = AntialiasMode::SUBPIXEL; + + switch (GetSystemTextQuality()) { + case CLEARTYPE_QUALITY: + defaultMode = AntialiasMode::SUBPIXEL; + break; + case ANTIALIASED_QUALITY: + defaultMode = AntialiasMode::GRAY; + break; + case DEFAULT_QUALITY: + defaultMode = AntialiasMode::NONE; + break; + } + + return defaultMode; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/ImageScaling.cpp b/gfx/2d/ImageScaling.cpp new file mode 100644 index 0000000000..25b1e88283 --- /dev/null +++ b/gfx/2d/ImageScaling.cpp @@ -0,0 +1,245 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageScaling.h" +#include "2D.h" +#include "DataSurfaceHelpers.h" + +#include <math.h> +#include <algorithm> + +namespace mozilla { +namespace gfx { + +inline uint32_t Avg2x2(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { + // Prepare half-adder work + uint32_t sum = a ^ b ^ c; + uint32_t carry = (a & b) | (a & c) | (b & c); + + // Before shifting, mask lower order bits of each byte to avoid underflow. + uint32_t mask = 0xfefefefe; + + // Add d to sum and divide by 2. + sum = (((sum ^ d) & mask) >> 1) + (sum & d); + + // Sum is now shifted into place relative to carry, add them together. + return (((sum ^ carry) & mask) >> 1) + (sum & carry); +} + +inline uint32_t Avg2(uint32_t a, uint32_t b) { + // Prepare half-adder work + uint32_t sum = a ^ b; + uint32_t carry = (a & b); + + // Before shifting, mask lower order bits of each byte to avoid underflow. + uint32_t mask = 0xfefefefe; + + // Add d to sum and divide by 2. + return ((sum & mask) >> 1) + carry; +} + +void ImageHalfScaler::ScaleForSize(const IntSize& aSize) { + uint32_t horizontalDownscales = 0; + uint32_t verticalDownscales = 0; + + IntSize scaleSize = mOrigSize; + while ((scaleSize.height / 2) > aSize.height) { + verticalDownscales++; + scaleSize.height /= 2; + } + + while ((scaleSize.width / 2) > aSize.width) { + horizontalDownscales++; + scaleSize.width /= 2; + } + + if (scaleSize == mOrigSize) { + return; + } + + delete[] mDataStorage; + + IntSize internalSurfSize; + internalSurfSize.width = std::max(scaleSize.width, mOrigSize.width / 2); + internalSurfSize.height = std::max(scaleSize.height, mOrigSize.height / 2); + + size_t bufLen = 0; + mStride = GetAlignedStride<16>(internalSurfSize.width, 4); + if (mStride > 0) { + // Allocate 15 bytes extra to make sure we can get 16 byte alignment. We + // should add tools for this, see bug 751696. + bufLen = + BufferSizeFromStrideAndHeight(mStride, internalSurfSize.height, 15); + } + + if (bufLen == 0) { + mSize.SizeTo(0, 0); + mDataStorage = nullptr; + return; + } + mDataStorage = new uint8_t[bufLen]; + + if (uintptr_t(mDataStorage) % 16) { + // Our storage does not start at a 16-byte boundary. Make sure mData does! + mData = (uint8_t*)(uintptr_t(mDataStorage) + + (16 - (uintptr_t(mDataStorage) % 16))); + } else { + mData = mDataStorage; + } + + mSize = scaleSize; + + /* The surface we sample from might not be even sized, if it's not we will + * ignore the last row/column. This means we lose some data but it keeps the + * code very simple. There's also no perfect answer that provides a better + * solution. + */ + IntSize currentSampledSize = mOrigSize; + uint32_t currentSampledStride = mOrigStride; + uint8_t* currentSampledData = mOrigData; + + while (verticalDownscales && horizontalDownscales) { + if (currentSampledSize.width % 2) { + currentSampledSize.width -= 1; + } + if (currentSampledSize.height % 2) { + currentSampledSize.height -= 1; + } + + HalfImage2D(currentSampledData, currentSampledStride, currentSampledSize, + mData, mStride); + + verticalDownscales--; + horizontalDownscales--; + currentSampledSize.width /= 2; + currentSampledSize.height /= 2; + currentSampledData = mData; + currentSampledStride = mStride; + } + + while (verticalDownscales) { + if (currentSampledSize.height % 2) { + currentSampledSize.height -= 1; + } + + HalfImageVertical(currentSampledData, currentSampledStride, + currentSampledSize, mData, mStride); + + verticalDownscales--; + currentSampledSize.height /= 2; + currentSampledData = mData; + currentSampledStride = mStride; + } + + while (horizontalDownscales) { + if (currentSampledSize.width % 2) { + currentSampledSize.width -= 1; + } + + HalfImageHorizontal(currentSampledData, currentSampledStride, + currentSampledSize, mData, mStride); + + horizontalDownscales--; + currentSampledSize.width /= 2; + currentSampledData = mData; + currentSampledStride = mStride; + } +} + +void ImageHalfScaler::HalfImage2D(uint8_t* aSource, int32_t aSourceStride, + const IntSize& aSourceSize, uint8_t* aDest, + uint32_t aDestStride) { +#ifdef USE_SSE2 + if (Factory::HasSSE2()) { + HalfImage2D_SSE2(aSource, aSourceStride, aSourceSize, aDest, aDestStride); + } else +#endif + { + HalfImage2D_C(aSource, aSourceStride, aSourceSize, aDest, aDestStride); + } +} + +void ImageHalfScaler::HalfImageVertical(uint8_t* aSource, int32_t aSourceStride, + const IntSize& aSourceSize, + uint8_t* aDest, uint32_t aDestStride) { +#ifdef USE_SSE2 + if (Factory::HasSSE2()) { + HalfImageVertical_SSE2(aSource, aSourceStride, aSourceSize, aDest, + aDestStride); + } else +#endif + { + HalfImageVertical_C(aSource, aSourceStride, aSourceSize, aDest, + aDestStride); + } +} + +void ImageHalfScaler::HalfImageHorizontal(uint8_t* aSource, + int32_t aSourceStride, + const IntSize& aSourceSize, + uint8_t* aDest, + uint32_t aDestStride) { +#ifdef USE_SSE2 + if (Factory::HasSSE2()) { + HalfImageHorizontal_SSE2(aSource, aSourceStride, aSourceSize, aDest, + aDestStride); + } else +#endif + { + HalfImageHorizontal_C(aSource, aSourceStride, aSourceSize, aDest, + aDestStride); + } +} + +void ImageHalfScaler::HalfImage2D_C(uint8_t* aSource, int32_t aSourceStride, + const IntSize& aSourceSize, uint8_t* aDest, + uint32_t aDestStride) { + for (int y = 0; y < aSourceSize.height; y += 2) { + uint32_t* storage = (uint32_t*)(aDest + (y / 2) * aDestStride); + for (int x = 0; x < aSourceSize.width; x += 2) { + uint8_t* upperRow = aSource + (y * aSourceStride + x * 4); + uint8_t* lowerRow = aSource + ((y + 1) * aSourceStride + x * 4); + + *storage++ = Avg2x2(*(uint32_t*)upperRow, *((uint32_t*)upperRow + 1), + *(uint32_t*)lowerRow, *((uint32_t*)lowerRow + 1)); + } + } +} + +void ImageHalfScaler::HalfImageVertical_C(uint8_t* aSource, + int32_t aSourceStride, + const IntSize& aSourceSize, + uint8_t* aDest, + uint32_t aDestStride) { + for (int y = 0; y < aSourceSize.height; y += 2) { + uint32_t* storage = (uint32_t*)(aDest + (y / 2) * aDestStride); + for (int x = 0; x < aSourceSize.width; x++) { + uint32_t* upperRow = (uint32_t*)(aSource + (y * aSourceStride + x * 4)); + uint32_t* lowerRow = + (uint32_t*)(aSource + ((y + 1) * aSourceStride + x * 4)); + + *storage++ = Avg2(*upperRow, *lowerRow); + } + } +} + +void ImageHalfScaler::HalfImageHorizontal_C(uint8_t* aSource, + int32_t aSourceStride, + const IntSize& aSourceSize, + uint8_t* aDest, + uint32_t aDestStride) { + for (int y = 0; y < aSourceSize.height; y++) { + uint32_t* storage = (uint32_t*)(aDest + y * aDestStride); + for (int x = 0; x < aSourceSize.width; x += 2) { + uint32_t* pixels = (uint32_t*)(aSource + (y * aSourceStride + x * 4)); + + *storage++ = Avg2(*pixels, *(pixels + 1)); + } + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/ImageScaling.h b/gfx/2d/ImageScaling.h new file mode 100644 index 0000000000..446b5287c6 --- /dev/null +++ b/gfx/2d/ImageScaling.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_IMAGESCALING_H +#define _MOZILLA_GFX_IMAGESCALING_H + +#include "Types.h" + +#include <vector> +#include "Point.h" + +namespace mozilla { +namespace gfx { + +class ImageHalfScaler { + public: + ImageHalfScaler(uint8_t* aData, int32_t aStride, const IntSize& aSize) + : mOrigData(aData), + mOrigStride(aStride), + mOrigSize(aSize), + mDataStorage(nullptr), + mData(nullptr), + mStride(0) {} + + ~ImageHalfScaler() { delete[] mDataStorage; } + + void ScaleForSize(const IntSize& aSize); + + uint8_t* GetScaledData() const { return mData; } + IntSize GetSize() const { return mSize; } + uint32_t GetStride() const { return mStride; } + + private: + void HalfImage2D(uint8_t* aSource, int32_t aSourceStride, + const IntSize& aSourceSize, uint8_t* aDest, + uint32_t aDestStride); + void HalfImageVertical(uint8_t* aSource, int32_t aSourceStride, + const IntSize& aSourceSize, uint8_t* aDest, + uint32_t aDestStride); + void HalfImageHorizontal(uint8_t* aSource, int32_t aSourceStride, + const IntSize& aSourceSize, uint8_t* aDest, + uint32_t aDestStride); + + // This is our SSE2 scaling function. Our destination must always be 16-byte + // aligned and use a 16-byte aligned stride. + void HalfImage2D_SSE2(uint8_t* aSource, int32_t aSourceStride, + const IntSize& aSourceSize, uint8_t* aDest, + uint32_t aDestStride); + void HalfImageVertical_SSE2(uint8_t* aSource, int32_t aSourceStride, + const IntSize& aSourceSize, uint8_t* aDest, + uint32_t aDestStride); + void HalfImageHorizontal_SSE2(uint8_t* aSource, int32_t aSourceStride, + const IntSize& aSourceSize, uint8_t* aDest, + uint32_t aDestStride); + + void HalfImage2D_C(uint8_t* aSource, int32_t aSourceStride, + const IntSize& aSourceSize, uint8_t* aDest, + uint32_t aDestStride); + void HalfImageVertical_C(uint8_t* aSource, int32_t aSourceStride, + const IntSize& aSourceSize, uint8_t* aDest, + uint32_t aDestStride); + void HalfImageHorizontal_C(uint8_t* aSource, int32_t aSourceStride, + const IntSize& aSourceSize, uint8_t* aDest, + uint32_t aDestStride); + + uint8_t* mOrigData; + int32_t mOrigStride; + IntSize mOrigSize; + + uint8_t* mDataStorage; + // Guaranteed 16-byte aligned + uint8_t* mData; + IntSize mSize; + // Guaranteed 16-byte aligned + uint32_t mStride; +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/ImageScalingSSE2.cpp b/gfx/2d/ImageScalingSSE2.cpp new file mode 100644 index 0000000000..f901641eaf --- /dev/null +++ b/gfx/2d/ImageScalingSSE2.cpp @@ -0,0 +1,333 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageScaling.h" +#include "mozilla/Attributes.h" + +#include "SSEHelpers.h" + +/* The functions below use the following system for averaging 4 pixels: + * + * The first observation is that a half-adder is implemented as follows: + * R = S + 2C or in the case of a and b (a ^ b) + ((a & b) << 1); + * + * This can be trivially extended to three pixels by observaring that when + * doing (a ^ b ^ c) as the sum, the carry is simply the bitwise-or of the + * carries of the individual numbers, since the sum of 3 bits can only ever + * have a carry of one. + * + * We then observe that the average is then ((carry << 1) + sum) >> 1, or, + * assuming eliminating overflows and underflows, carry + (sum >> 1). + * + * We now average our existing sum with the fourth number, so we get: + * sum2 = (sum + d) >> 1 or (sum >> 1) + (d >> 1). + * + * We now observe that our sum has been moved into place relative to the + * carry, so we can now average with the carry to get the final 4 input + * average: avg = (sum2 + carry) >> 1; + * + * Or to reverse the proof: + * avg = ((sum >> 1) + carry + d >> 1) >> 1 + * avg = ((a + b + c) >> 1 + d >> 1) >> 1 + * avg = ((a + b + c + d) >> 2) + * + * An additional fact used in the SSE versions is the concept that we can + * trivially convert a rounded average to a truncated average: + * + * We have: + * f(a, b) = (a + b + 1) >> 1 + * + * And want: + * g(a, b) = (a + b) >> 1 + * + * Observe: + * ~f(~a, ~b) == ~((~a + ~b + 1) >> 1) + * == ~((-a - 1 + -b - 1 + 1) >> 1) + * == ~((-a - 1 + -b) >> 1) + * == ~((-(a + b) - 1) >> 1) + * == ~((~(a + b)) >> 1) + * == (a + b) >> 1 + * == g(a, b) + */ + +MOZ_ALWAYS_INLINE __m128i _mm_not_si128(__m128i arg) { + __m128i minusone = _mm_set1_epi32(0xffffffff); + return _mm_xor_si128(arg, minusone); +} + +/* We have to pass pointers here, MSVC does not allow passing more than 3 + * __m128i arguments on the stack. And it does not allow 16-byte aligned + * stack variables. This inlines properly on MSVC 2010. It does -not- inline + * with just the inline directive. + */ +MOZ_ALWAYS_INLINE __m128i avg_sse2_8x2(__m128i* a, __m128i* b, __m128i* c, + __m128i* d) { +#define shuf1 _MM_SHUFFLE(2, 0, 2, 0) +#define shuf2 _MM_SHUFFLE(3, 1, 3, 1) + +// This cannot be an inline function as the __Imm argument to _mm_shuffle_ps +// needs to be a compile time constant. +#define shuffle_si128(arga, argb, imm) \ + _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps((arga)), \ + _mm_castsi128_ps((argb)), (imm))); + + __m128i t = shuffle_si128(*a, *b, shuf1); + *b = shuffle_si128(*a, *b, shuf2); + *a = t; + t = shuffle_si128(*c, *d, shuf1); + *d = shuffle_si128(*c, *d, shuf2); + *c = t; + +#undef shuf1 +#undef shuf2 +#undef shuffle_si128 + + __m128i sum = _mm_xor_si128(*a, _mm_xor_si128(*b, *c)); + + __m128i carry = + _mm_or_si128(_mm_and_si128(*a, *b), + _mm_or_si128(_mm_and_si128(*a, *c), _mm_and_si128(*b, *c))); + + sum = _mm_avg_epu8(_mm_not_si128(sum), _mm_not_si128(*d)); + + return _mm_not_si128(_mm_avg_epu8(sum, _mm_not_si128(carry))); +} + +MOZ_ALWAYS_INLINE __m128i avg_sse2_4x2_4x1(__m128i a, __m128i b) { + return _mm_not_si128(_mm_avg_epu8(_mm_not_si128(a), _mm_not_si128(b))); +} + +MOZ_ALWAYS_INLINE __m128i avg_sse2_8x1_4x1(__m128i a, __m128i b) { + __m128i t = _mm_castps_si128(_mm_shuffle_ps( + _mm_castsi128_ps(a), _mm_castsi128_ps(b), _MM_SHUFFLE(3, 1, 3, 1))); + b = _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(a), _mm_castsi128_ps(b), + _MM_SHUFFLE(2, 0, 2, 0))); + a = t; + + return _mm_not_si128(_mm_avg_epu8(_mm_not_si128(a), _mm_not_si128(b))); +} + +MOZ_ALWAYS_INLINE uint32_t Avg2x2(uint32_t a, uint32_t b, uint32_t c, + uint32_t d) { + uint32_t sum = a ^ b ^ c; + uint32_t carry = (a & b) | (a & c) | (b & c); + + uint32_t mask = 0xfefefefe; + + // Not having a byte based average instruction means we should mask to avoid + // underflow. + sum = (((sum ^ d) & mask) >> 1) + (sum & d); + + return (((sum ^ carry) & mask) >> 1) + (sum & carry); +} + +// Simple 2 pixel average version of the function above. +MOZ_ALWAYS_INLINE uint32_t Avg2(uint32_t a, uint32_t b) { + uint32_t sum = a ^ b; + uint32_t carry = (a & b); + + uint32_t mask = 0xfefefefe; + + return ((sum & mask) >> 1) + carry; +} + +namespace mozilla::gfx { + +void ImageHalfScaler::HalfImage2D_SSE2(uint8_t* aSource, int32_t aSourceStride, + const IntSize& aSourceSize, + uint8_t* aDest, uint32_t aDestStride) { + const int Bpp = 4; + + for (int y = 0; y < aSourceSize.height; y += 2) { + __m128i* storage = (__m128i*)(aDest + (y / 2) * aDestStride); + int x = 0; + // Run a loop depending on alignment. + if (!(uintptr_t(aSource + (y * aSourceStride)) % 16) && + !(uintptr_t(aSource + ((y + 1) * aSourceStride)) % 16)) { + for (; x < (aSourceSize.width - 7); x += 8) { + __m128i* upperRow = (__m128i*)(aSource + (y * aSourceStride + x * Bpp)); + __m128i* lowerRow = + (__m128i*)(aSource + ((y + 1) * aSourceStride + x * Bpp)); + + __m128i a = _mm_load_si128(upperRow); + __m128i b = _mm_load_si128(upperRow + 1); + __m128i c = _mm_load_si128(lowerRow); + __m128i d = _mm_load_si128(lowerRow + 1); + + *storage++ = avg_sse2_8x2(&a, &b, &c, &d); + } + } else if (!(uintptr_t(aSource + (y * aSourceStride)) % 16)) { + for (; x < (aSourceSize.width - 7); x += 8) { + __m128i* upperRow = (__m128i*)(aSource + (y * aSourceStride + x * Bpp)); + __m128i* lowerRow = + (__m128i*)(aSource + ((y + 1) * aSourceStride + x * Bpp)); + + __m128i a = _mm_load_si128(upperRow); + __m128i b = _mm_load_si128(upperRow + 1); + __m128i c = loadUnaligned128(lowerRow); + __m128i d = loadUnaligned128(lowerRow + 1); + + *storage++ = avg_sse2_8x2(&a, &b, &c, &d); + } + } else if (!(uintptr_t(aSource + ((y + 1) * aSourceStride)) % 16)) { + for (; x < (aSourceSize.width - 7); x += 8) { + __m128i* upperRow = (__m128i*)(aSource + (y * aSourceStride + x * Bpp)); + __m128i* lowerRow = + (__m128i*)(aSource + ((y + 1) * aSourceStride + x * Bpp)); + + __m128i a = loadUnaligned128((__m128i*)upperRow); + __m128i b = loadUnaligned128((__m128i*)upperRow + 1); + __m128i c = _mm_load_si128((__m128i*)lowerRow); + __m128i d = _mm_load_si128((__m128i*)lowerRow + 1); + + *storage++ = avg_sse2_8x2(&a, &b, &c, &d); + } + } else { + for (; x < (aSourceSize.width - 7); x += 8) { + __m128i* upperRow = (__m128i*)(aSource + (y * aSourceStride + x * Bpp)); + __m128i* lowerRow = + (__m128i*)(aSource + ((y + 1) * aSourceStride + x * Bpp)); + + __m128i a = loadUnaligned128(upperRow); + __m128i b = loadUnaligned128(upperRow + 1); + __m128i c = loadUnaligned128(lowerRow); + __m128i d = loadUnaligned128(lowerRow + 1); + + *storage++ = avg_sse2_8x2(&a, &b, &c, &d); + } + } + + uint32_t* unalignedStorage = (uint32_t*)storage; + // Take care of the final pixels, we know there's an even number of pixels + // in the source rectangle. We use a 2x2 'simd' implementation for this. + // + // Potentially we only have to do this in the last row since overflowing + // 8 pixels in an earlier row would appear to be harmless as it doesn't + // touch invalid memory. Even when reading and writing to the same surface. + // in practice we only do this when doing an additional downscale pass, and + // in this situation we have unused stride to write into harmlessly. + // I do not believe the additional code complexity would be worth it though. + for (; x < aSourceSize.width; x += 2) { + uint8_t* upperRow = aSource + (y * aSourceStride + x * Bpp); + uint8_t* lowerRow = aSource + ((y + 1) * aSourceStride + x * Bpp); + + *unalignedStorage++ = + Avg2x2(*(uint32_t*)upperRow, *((uint32_t*)upperRow + 1), + *(uint32_t*)lowerRow, *((uint32_t*)lowerRow + 1)); + } + } +} + +void ImageHalfScaler::HalfImageVertical_SSE2(uint8_t* aSource, + int32_t aSourceStride, + const IntSize& aSourceSize, + uint8_t* aDest, + uint32_t aDestStride) { + for (int y = 0; y < aSourceSize.height; y += 2) { + __m128i* storage = (__m128i*)(aDest + (y / 2) * aDestStride); + int x = 0; + // Run a loop depending on alignment. + if (!(uintptr_t(aSource + (y * aSourceStride)) % 16) && + !(uintptr_t(aSource + ((y + 1) * aSourceStride)) % 16)) { + for (; x < (aSourceSize.width - 3); x += 4) { + uint8_t* upperRow = aSource + (y * aSourceStride + x * 4); + uint8_t* lowerRow = aSource + ((y + 1) * aSourceStride + x * 4); + + __m128i a = _mm_load_si128((__m128i*)upperRow); + __m128i b = _mm_load_si128((__m128i*)lowerRow); + + *storage++ = avg_sse2_4x2_4x1(a, b); + } + } else if (!(uintptr_t(aSource + (y * aSourceStride)) % 16)) { + // This line doesn't align well. + for (; x < (aSourceSize.width - 3); x += 4) { + uint8_t* upperRow = aSource + (y * aSourceStride + x * 4); + uint8_t* lowerRow = aSource + ((y + 1) * aSourceStride + x * 4); + + __m128i a = _mm_load_si128((__m128i*)upperRow); + __m128i b = loadUnaligned128((__m128i*)lowerRow); + + *storage++ = avg_sse2_4x2_4x1(a, b); + } + } else if (!(uintptr_t(aSource + ((y + 1) * aSourceStride)) % 16)) { + for (; x < (aSourceSize.width - 3); x += 4) { + uint8_t* upperRow = aSource + (y * aSourceStride + x * 4); + uint8_t* lowerRow = aSource + ((y + 1) * aSourceStride + x * 4); + + __m128i a = loadUnaligned128((__m128i*)upperRow); + __m128i b = _mm_load_si128((__m128i*)lowerRow); + + *storage++ = avg_sse2_4x2_4x1(a, b); + } + } else { + for (; x < (aSourceSize.width - 3); x += 4) { + uint8_t* upperRow = aSource + (y * aSourceStride + x * 4); + uint8_t* lowerRow = aSource + ((y + 1) * aSourceStride + x * 4); + + __m128i a = loadUnaligned128((__m128i*)upperRow); + __m128i b = loadUnaligned128((__m128i*)lowerRow); + + *storage++ = avg_sse2_4x2_4x1(a, b); + } + } + + uint32_t* unalignedStorage = (uint32_t*)storage; + // Take care of the final pixels, we know there's an even number of pixels + // in the source rectangle. + // + // Similar overflow considerations are valid as in the previous function. + for (; x < aSourceSize.width; x++) { + uint8_t* upperRow = aSource + (y * aSourceStride + x * 4); + uint8_t* lowerRow = aSource + ((y + 1) * aSourceStride + x * 4); + + *unalignedStorage++ = Avg2(*(uint32_t*)upperRow, *(uint32_t*)lowerRow); + } + } +} + +void ImageHalfScaler::HalfImageHorizontal_SSE2(uint8_t* aSource, + int32_t aSourceStride, + const IntSize& aSourceSize, + uint8_t* aDest, + uint32_t aDestStride) { + for (int y = 0; y < aSourceSize.height; y++) { + __m128i* storage = (__m128i*)(aDest + (y * aDestStride)); + int x = 0; + // Run a loop depending on alignment. + if (!(uintptr_t(aSource + (y * aSourceStride)) % 16)) { + for (; x < (aSourceSize.width - 7); x += 8) { + __m128i* pixels = (__m128i*)(aSource + (y * aSourceStride + x * 4)); + + __m128i a = _mm_load_si128(pixels); + __m128i b = _mm_load_si128(pixels + 1); + + *storage++ = avg_sse2_8x1_4x1(a, b); + } + } else { + for (; x < (aSourceSize.width - 7); x += 8) { + __m128i* pixels = (__m128i*)(aSource + (y * aSourceStride + x * 4)); + + __m128i a = loadUnaligned128(pixels); + __m128i b = loadUnaligned128(pixels + 1); + + *storage++ = avg_sse2_8x1_4x1(a, b); + } + } + + uint32_t* unalignedStorage = (uint32_t*)storage; + // Take care of the final pixels, we know there's an even number of pixels + // in the source rectangle. + // + // Similar overflow considerations are valid as in the previous function. + for (; x < aSourceSize.width; x += 2) { + uint32_t* pixels = (uint32_t*)(aSource + (y * aSourceStride + x * 4)); + + *unalignedStorage++ = Avg2(*pixels, *(pixels + 1)); + } + } +} + +} // namespace mozilla::gfx diff --git a/gfx/2d/InlineTranslator.cpp b/gfx/2d/InlineTranslator.cpp new file mode 100644 index 0000000000..7eef63f2db --- /dev/null +++ b/gfx/2d/InlineTranslator.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InlineTranslator.h" +#include "RecordedEventImpl.h" + +#include "mozilla/gfx/RecordingTypes.h" + +using namespace mozilla::gfx; + +namespace mozilla::gfx { + +InlineTranslator::InlineTranslator() : mFontContext(nullptr) {} + +InlineTranslator::InlineTranslator(DrawTarget* aDT, void* aFontContext) + : mBaseDT(aDT), mFontContext(aFontContext) {} + +bool InlineTranslator::TranslateRecording(char* aData, size_t aLen) { + MemReader reader(aData, aLen); + + uint32_t magicInt; + ReadElement(reader, magicInt); + if (magicInt != mozilla::gfx::kMagicInt) { + mError = "Magic"; + return false; + } + + uint16_t majorRevision; + ReadElement(reader, majorRevision); + if (majorRevision != kMajorRevision) { + mError = "Major"; + return false; + } + + uint16_t minorRevision; + ReadElement(reader, minorRevision); + if (minorRevision > kMinorRevision) { + mError = "Minor"; + return false; + } + + uint8_t eventType = RecordedEvent::EventType::INVALID; + ReadElement(reader, eventType); + while (reader.good()) { + bool success = RecordedEvent::DoWithEvent( + reader, static_cast<RecordedEvent::EventType>(eventType), + [&](RecordedEvent* recordedEvent) -> bool { + // Make sure that the whole event was read from the stream + // successfully. + if (!reader.good()) { + mError = " READ"; + return false; + } + + if (!recordedEvent->PlayEvent(this)) { + mError = " PLAY"; + return false; + } + + return true; + }); + if (!success) { + mError = RecordedEvent::GetEventName( + static_cast<RecordedEvent::EventType>(eventType)) + + mError; + return false; + } + + ReadElement(reader, eventType); + } + + return true; +} + +already_AddRefed<DrawTarget> InlineTranslator::CreateDrawTarget( + ReferencePtr aRefPtr, const gfx::IntSize& aSize, + gfx::SurfaceFormat aFormat) { + MOZ_ASSERT(mBaseDT, "mBaseDT has not been initialized."); + + RefPtr<DrawTarget> drawTarget = mBaseDT; + AddDrawTarget(aRefPtr, drawTarget); + return drawTarget.forget(); +} + +already_AddRefed<SourceSurface> InlineTranslator::LookupExternalSurface( + uint64_t aKey) { + if (!mExternalSurfaces) { + return nullptr; + } + RefPtr<SourceSurface> surface = mExternalSurfaces->Get(aKey); + return surface.forget(); +} + +} // namespace mozilla::gfx diff --git a/gfx/2d/InlineTranslator.h b/gfx/2d/InlineTranslator.h new file mode 100644 index 0000000000..f29e28113a --- /dev/null +++ b/gfx/2d/InlineTranslator.h @@ -0,0 +1,200 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layout_InlineTranslator_h +#define mozilla_layout_InlineTranslator_h + +#include <string> + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Filters.h" +#include "mozilla/gfx/RecordedEvent.h" + +namespace mozilla { +namespace gfx { + +using gfx::DrawTarget; +using gfx::FilterNode; +using gfx::GradientStops; +using gfx::NativeFontResource; +using gfx::Path; +using gfx::ReferencePtr; +using gfx::ScaledFont; +using gfx::SourceSurface; +using gfx::Translator; + +class InlineTranslator : public Translator { + public: + InlineTranslator(); + + explicit InlineTranslator(DrawTarget* aDT, void* aFontContext = nullptr); + + bool TranslateRecording(char*, size_t len); + + void SetExternalSurfaces( + nsRefPtrHashtable<nsUint64HashKey, SourceSurface>* aExternalSurfaces) { + mExternalSurfaces = aExternalSurfaces; + } + void SetReferenceDrawTargetTransform(const Matrix& aTransform) { + mBaseDTTransform = aTransform; + } + + DrawTarget* LookupDrawTarget(ReferencePtr aRefPtr) final { + DrawTarget* result = mDrawTargets.GetWeak(aRefPtr); + MOZ_ASSERT(result); + return result; + } + + Path* LookupPath(ReferencePtr aRefPtr) final { + Path* result = mPaths.GetWeak(aRefPtr); + MOZ_ASSERT(result); + return result; + } + + SourceSurface* LookupSourceSurface(ReferencePtr aRefPtr) final { + SourceSurface* result = mSourceSurfaces.GetWeak(aRefPtr); + MOZ_ASSERT(result); + return result; + } + + FilterNode* LookupFilterNode(ReferencePtr aRefPtr) final { + FilterNode* result = mFilterNodes.GetWeak(aRefPtr); + MOZ_ASSERT(result); + return result; + } + + already_AddRefed<GradientStops> LookupGradientStops( + ReferencePtr aRefPtr) final { + return mGradientStops.Get(aRefPtr); + } + + ScaledFont* LookupScaledFont(ReferencePtr aRefPtr) final { + ScaledFont* result = mScaledFonts.GetWeak(aRefPtr); + MOZ_ASSERT(result); + return result; + } + + UnscaledFont* LookupUnscaledFont(ReferencePtr aRefPtr) final { + UnscaledFont* result = mUnscaledFonts.GetWeak(aRefPtr); + MOZ_ASSERT(result); + return result; + } + + NativeFontResource* LookupNativeFontResource(uint64_t aKey) final { + NativeFontResource* result = mNativeFontResources.GetWeak(aKey); + MOZ_ASSERT(result); + return result; + } + + already_AddRefed<SourceSurface> LookupExternalSurface(uint64_t aKey) override; + + void AddDrawTarget(ReferencePtr aRefPtr, DrawTarget* aDT) final { + mDrawTargets.InsertOrUpdate(aRefPtr, RefPtr{aDT}); + } + + void AddPath(ReferencePtr aRefPtr, Path* aPath) final { + mPaths.InsertOrUpdate(aRefPtr, RefPtr{aPath}); + } + + void AddSourceSurface(ReferencePtr aRefPtr, + SourceSurface* aSurface) override { + mSourceSurfaces.InsertOrUpdate(aRefPtr, RefPtr{aSurface}); + } + + void AddFilterNode(ReferencePtr aRefPtr, FilterNode* aFilter) final { + mFilterNodes.InsertOrUpdate(aRefPtr, RefPtr{aFilter}); + } + + void AddGradientStops(ReferencePtr aRefPtr, GradientStops* aStops) final { + mGradientStops.InsertOrUpdate(aRefPtr, RefPtr{aStops}); + } + + void AddScaledFont(ReferencePtr aRefPtr, ScaledFont* aScaledFont) final { + mScaledFonts.InsertOrUpdate(aRefPtr, RefPtr{aScaledFont}); + } + + void AddUnscaledFont(ReferencePtr aRefPtr, + UnscaledFont* aUnscaledFont) final { + mUnscaledFonts.InsertOrUpdate(aRefPtr, RefPtr{aUnscaledFont}); + } + + void AddNativeFontResource(uint64_t aKey, + NativeFontResource* aScaledFontResouce) final { + mNativeFontResources.InsertOrUpdate(aKey, RefPtr{aScaledFontResouce}); + } + + void RemoveDrawTarget(ReferencePtr aRefPtr) override { + ReferencePtr currentDT = mCurrentDT; + if (currentDT == aRefPtr) { + mCurrentDT = nullptr; + } + mDrawTargets.Remove(aRefPtr); + } + + bool SetCurrentDrawTarget(ReferencePtr aRefPtr) override { + mCurrentDT = mDrawTargets.GetWeak(aRefPtr); + return !!mCurrentDT; + } + + void RemovePath(ReferencePtr aRefPtr) final { mPaths.Remove(aRefPtr); } + + void RemoveSourceSurface(ReferencePtr aRefPtr) override { + mSourceSurfaces.Remove(aRefPtr); + } + + void RemoveFilterNode(ReferencePtr aRefPtr) final { + mFilterNodes.Remove(aRefPtr); + } + + void RemoveGradientStops(ReferencePtr aRefPtr) final { + mGradientStops.Remove(aRefPtr); + } + + void RemoveScaledFont(ReferencePtr aRefPtr) final { + mScaledFonts.Remove(aRefPtr); + } + + void RemoveUnscaledFont(ReferencePtr aRefPtr) final { + mUnscaledFonts.Remove(aRefPtr); + } + + already_AddRefed<DrawTarget> CreateDrawTarget( + ReferencePtr aRefPtr, const gfx::IntSize& aSize, + gfx::SurfaceFormat aFormat) override; + + mozilla::gfx::DrawTarget* GetReferenceDrawTarget() final { + MOZ_ASSERT(mBaseDT, "mBaseDT has not been initialized."); + return mBaseDT; + } + Matrix GetReferenceDrawTargetTransform() final { return mBaseDTTransform; } + + void* GetFontContext() final { return mFontContext; } + std::string GetError() { return mError; } + + protected: + RefPtr<DrawTarget> mBaseDT; + Matrix mBaseDTTransform; + nsRefPtrHashtable<nsPtrHashKey<void>, DrawTarget> mDrawTargets; + + private: + void* mFontContext; + std::string mError; + + nsRefPtrHashtable<nsPtrHashKey<void>, Path> mPaths; + nsRefPtrHashtable<nsPtrHashKey<void>, SourceSurface> mSourceSurfaces; + nsRefPtrHashtable<nsPtrHashKey<void>, FilterNode> mFilterNodes; + nsRefPtrHashtable<nsPtrHashKey<void>, GradientStops> mGradientStops; + nsRefPtrHashtable<nsPtrHashKey<void>, ScaledFont> mScaledFonts; + nsRefPtrHashtable<nsPtrHashKey<void>, UnscaledFont> mUnscaledFonts; + nsRefPtrHashtable<nsUint64HashKey, NativeFontResource> mNativeFontResources; + nsRefPtrHashtable<nsUint64HashKey, SourceSurface>* mExternalSurfaces = + nullptr; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // mozilla_layout_InlineTranslator_h diff --git a/gfx/2d/IterableArena.h b/gfx/2d/IterableArena.h new file mode 100644 index 0000000000..6f90e7d603 --- /dev/null +++ b/gfx/2d/IterableArena.h @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_ITERABLEARENA_H_ +#define MOZILLA_GFX_ITERABLEARENA_H_ + +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include <utility> +#include <vector> + +#include "mozilla/Assertions.h" +#include "mozilla/gfx/Logging.h" + +namespace mozilla { +namespace gfx { + +/// A simple pool allocator for plain data structures. +/// +/// Beware that the pool will not attempt to run the destructors. It is the +/// responsibility of the user of this class to either use objects with no +/// destructor or to manually call the allocated objects destructors. +/// If the pool is growable, its allocated objects must be safely moveable in +/// in memory (through memcpy). +class IterableArena { + protected: + struct Header { + size_t mBlocSize; + }; + + public: + enum ArenaType { FIXED_SIZE, GROWABLE }; + + IterableArena(ArenaType aType, size_t aStorageSize) + : mSize(aStorageSize), mCursor(0), mIsGrowable(aType == GROWABLE) { + if (mSize == 0) { + mSize = 128; + } + + mStorage = (uint8_t*)malloc(mSize); + if (mStorage == nullptr) { + gfxCriticalError() << "Not enough Memory allocate a memory pool of size " + << aStorageSize; + MOZ_CRASH("GFX: Out of memory IterableArena"); + } + } + + ~IterableArena() { free(mStorage); } + + /// Constructs a new item in the pool and returns a positive offset in case of + /// success. + /// + /// The offset never changes even if the storage is reallocated, so users + /// of this class should prefer storing offsets rather than direct pointers + /// to the allocated objects. + /// Alloc can cause the storage to be reallocated if the pool was initialized + /// with IterableArena::GROWABLE. + /// If for any reason the pool fails to allocate enough space for the new item + /// Alloc returns a negative offset and the object's constructor is not + /// called. + template <typename T, typename... Args> + ptrdiff_t Alloc(Args&&... aArgs) { + void* storage = nullptr; + auto offset = AllocRaw(sizeof(T), &storage); + if (offset < 0) { + return offset; + } + new (storage) T(std::forward<Args>(aArgs)...); + return offset; + } + + ptrdiff_t AllocRaw(size_t aSize, void** aOutPtr = nullptr) { + const size_t blocSize = AlignedSize(sizeof(Header) + aSize); + + if (AlignedSize(mCursor + blocSize) > mSize) { + if (!mIsGrowable) { + return -1; + } + + size_t newSize = mSize * 2; + while (AlignedSize(mCursor + blocSize) > newSize) { + newSize *= 2; + } + + uint8_t* newStorage = (uint8_t*)realloc(mStorage, newSize); + if (!newStorage) { + gfxCriticalError() + << "Not enough Memory to grow the memory pool, size: " << newSize; + return -1; + } + + mStorage = newStorage; + mSize = newSize; + } + ptrdiff_t offset = mCursor; + GetHeader(offset)->mBlocSize = blocSize; + mCursor += blocSize; + if (aOutPtr) { + *aOutPtr = GetStorage(offset); + } + return offset; + } + + /// Get access to an allocated item at a given offset (only use offsets + /// returned by Alloc or AllocRaw). + /// + /// If the pool is growable, the returned pointer is only valid temporarily. + /// The underlying storage can be reallocated in Alloc or AllocRaw, so do not + /// keep these pointers around and store the offset instead. + void* GetStorage(ptrdiff_t offset = 0) { + MOZ_ASSERT(offset >= 0); + MOZ_ASSERT(offset < mCursor); + return offset >= 0 ? mStorage + offset + sizeof(Header) : nullptr; + } + + /// Clears the storage without running any destructor and without deallocating + /// it. + void Clear() { mCursor = 0; } + + /// Iterate over the elements allocated in this pool. + /// + /// Takes a lambda or function object accepting a void* as parameter. + template <typename Func> + void ForEach(Func cb) { + Iterator it; + while (void* ptr = it.Next(this)) { + cb(ptr); + } + } + + /// A simple iterator over an arena. + class Iterator { + public: + Iterator() : mCursor(0) {} + + void* Next(IterableArena* aArena) { + if (mCursor >= aArena->mCursor) { + return nullptr; + } + void* result = aArena->GetStorage(mCursor); + const size_t blocSize = aArena->GetHeader(mCursor)->mBlocSize; + MOZ_ASSERT(blocSize != 0); + mCursor += blocSize; + return result; + } + + private: + ptrdiff_t mCursor; + }; + + protected: + Header* GetHeader(ptrdiff_t offset) { return (Header*)(mStorage + offset); } + + size_t AlignedSize(size_t aSize) const { + const size_t alignment = sizeof(uintptr_t); + return aSize + (alignment - (aSize % alignment)) % alignment; + } + + uint8_t* mStorage; + uint32_t mSize; + ptrdiff_t mCursor; + bool mIsGrowable; + + friend class Iterator; +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/Logging.h b/gfx/2d/Logging.h new file mode 100644 index 0000000000..8af720cffa --- /dev/null +++ b/gfx/2d/Logging.h @@ -0,0 +1,965 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_LOGGING_H_ +#define MOZILLA_GFX_LOGGING_H_ + +#include <string> +#include <sstream> +#include <stdio.h> +#include <vector> + +#ifdef MOZ_LOGGING +# include "mozilla/Logging.h" +#endif + +#if defined(MOZ_WIDGET_ANDROID) +# include "nsDebug.h" +#endif +#include "2D.h" +#include "mozilla/Atomics.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "Point.h" +#include "BaseRect.h" +#include "Matrix.h" +#include "LoggingConstants.h" + +#if defined(MOZ_LOGGING) +extern GFX2D_API mozilla::LogModule* GetGFX2DLog(); +#endif + +namespace mozilla { +namespace gfx { + +#if defined(MOZ_LOGGING) +inline mozilla::LogLevel PRLogLevelForLevel(int aLevel) { + switch (aLevel) { + case LOG_CRITICAL: + return LogLevel::Error; + case LOG_WARNING: + return LogLevel::Warning; + case LOG_DEBUG: + return LogLevel::Debug; + case LOG_DEBUG_PRLOG: + return LogLevel::Debug; + case LOG_EVERYTHING: + return LogLevel::Error; + } + return LogLevel::Debug; +} +#endif + +/// Graphics logging is available in both debug and release builds and is +/// controlled with a gfx.logging.level preference. If not set, the default +/// for the preference is 5 in the debug builds, 1 in the release builds. +/// +/// gfxDebug only works in the debug builds, and is used for information +/// level messages, helping with debugging. In addition to only working +/// in the debug builds, the value of the above preference of 3 or higher +/// is required. +/// +/// gfxWarning messages are available in both debug and release builds, +/// on by default in the debug builds, and off by default in the release builds. +/// Setting the preference gfx.logging.level to a value of 2 or higher will +/// show the warnings. +/// +/// gfxCriticalError is available in debug and release builds by default. +/// It is only unavailable if gfx.logging.level is set to 0 (or less.) +/// It outputs the message to stderr or equivalent, like gfxWarning. +/// In the event of a crash, the crash report is annotated with first and +/// the last few of these errors, under the key GraphicsCriticalError. +/// The total number of errors stored in the crash report is controlled +/// by preference gfx.logging.crash.length. +/// +/// On platforms that support MOZ_LOGGING, the story is slightly more involved. +/// In that case, unless gfx.logging.level is set to 4 or higher, the output +/// is further controlled by the "gfx2d" logging module. However, in the case +/// where such module would disable the output, in all but gfxDebug cases, +/// we will still send a printf. + +// The range is due to the values set in Histograms.json +enum class LogReason : int { + MustBeMoreThanThis = -1, + // Start. Do not insert, always add at end. If you remove items, + // make sure the other items retain their values. + D3D11InvalidCallDeviceRemoved = 0, + D3D11InvalidCall, + D3DLockTimeout, + D3D10FinalizeFrame, + D3D11FinalizeFrame, + D3D10SyncLock, + D3D11SyncLock, + D2D1NoWriteMap, + JobStatusError, + FilterInputError, + FilterInputData, // 10 + FilterInputRect, + FilterInputSet, + FilterInputFormat, + FilterNodeD2D1Target, + FilterNodeD2D1Backend, + SourceSurfaceIncompatible, + GlyphAllocFailedCairo, + GlyphAllocFailedCG, + InvalidRect, + CannotDraw3D, // 20 + IncompatibleBasicTexturedEffect, + InvalidFont, + PAllocTextureBackendMismatch, + GetFontFileDataFailed, + MessageChannelCloseFailure, + MessageChannelInvalidHandle, + TextureAliveAfterShutdown, + InvalidContext, + InvalidCommandList, + AsyncTransactionTimeout, // 30 + TextureCreation, + InvalidCacheSurface, + AlphaWithBasicClient, + UnbalancedClipStack, + ProcessingError, + InvalidDrawTarget, + NativeFontResourceNotFound, + UnscaledFontNotFound, + ScaledFontNotFound, + InvalidLayerType, // 40 + // End + MustBeLessThanThis = 101, +}; + +struct BasicLogger { + // For efficiency, this method exists and copies the logic of the + // OutputMessage below. If making any changes here, also make it + // in the appropriate places in that method. + static bool ShouldOutputMessage(int aLevel) { + if (StaticPrefs::gfx_logging_level() >= aLevel) { +#if defined(MOZ_WIDGET_ANDROID) + return true; +#else +# if defined(MOZ_LOGGING) + if (MOZ_LOG_TEST(GetGFX2DLog(), PRLogLevelForLevel(aLevel))) { + return true; + } else +# endif + if ((StaticPrefs::gfx_logging_level() >= LOG_DEBUG_PRLOG) || + (aLevel < LOG_DEBUG)) { + return true; + } +#endif + } + return false; + } + + // Only for really critical errors. + static void CrashAction(LogReason aReason) {} + + static void OutputMessage(const std::string& aString, int aLevel, + bool aNoNewline) { + // This behavior (the higher the preference, the more we log) + // is consistent with what prlog does in general. Note that if prlog + // is in the build, but disabled, we will printf if the preferences + // requires us to log something. + // + // If making any logic changes to this method, you should probably + // make the corresponding change in the ShouldOutputMessage method + // above. + if (StaticPrefs::gfx_logging_level() >= aLevel) { +#if defined(MOZ_WIDGET_ANDROID) + printf_stderr("%s%s", aString.c_str(), aNoNewline ? "" : "\n"); +#else +# if defined(MOZ_LOGGING) + if (MOZ_LOG_TEST(GetGFX2DLog(), PRLogLevelForLevel(aLevel))) { + MOZ_LOG(GetGFX2DLog(), PRLogLevelForLevel(aLevel), + ("%s%s", aString.c_str(), aNoNewline ? "" : "\n")); + } else +# endif + if ((StaticPrefs::gfx_logging_level() >= LOG_DEBUG_PRLOG) || + (aLevel < LOG_DEBUG)) { + printf("%s%s", aString.c_str(), aNoNewline ? "" : "\n"); + } +#endif + } + } +}; + +struct CriticalLogger { + static void OutputMessage(const std::string& aString, int aLevel, + bool aNoNewline); + static void CrashAction(LogReason aReason); +}; + +// The int is the index of the Log call; if the number of logs exceeds some +// preset capacity we may not get all of them, so the indices help figure out +// which ones we did save. The double is expected to be the "TimeDuration", +// time in seconds since the process creation. +typedef std::tuple<int32_t, std::string, double> LoggingRecordEntry; + +// Implement this interface and init the Factory with an instance to +// forward critical logs. +typedef std::vector<LoggingRecordEntry> LoggingRecord; +class LogForwarder { + public: + virtual ~LogForwarder() = default; + virtual void Log(const std::string& aString) = 0; + virtual void CrashAction(LogReason aReason) = 0; + virtual bool UpdateStringsVector(const std::string& aString) = 0; + + // Provide a copy of the logs to the caller. + virtual LoggingRecord LoggingRecordCopy() = 0; +}; + +class NoLog { + public: + NoLog() = default; + ~NoLog() = default; + + // No-op + MOZ_IMPLICIT NoLog(const NoLog&) = default; + + template <typename T> + NoLog& operator<<(const T& aLogText) { + return *this; + } +}; + +enum class LogOptions : int { + NoNewline = 0x01, + AutoPrefix = 0x02, + AssertOnCall = 0x04, + CrashAction = 0x08, +}; + +template <typename T> +struct Hexa { + explicit Hexa(T aVal) : mVal(aVal) {} + T mVal; +}; +template <typename T> +Hexa<T> hexa(T val) { + return Hexa<T>(val); +} + +#ifdef WIN32 +void LogWStr(const wchar_t* aStr, std::stringstream& aOut); +#endif + +template <int L, typename Logger = BasicLogger> +class Log final { + public: + // The default is to have the prefix, have the new line, and for critical + // logs assert on each call. + static int DefaultOptions(bool aWithAssert = true) { + return (int(LogOptions::AutoPrefix) | + (aWithAssert ? int(LogOptions::AssertOnCall) : 0)); + } + + // Note that we're calling BasicLogger::ShouldOutputMessage, rather than + // Logger::ShouldOutputMessage. Since we currently don't have a different + // version of that method for different loggers, this is OK. Once we do, + // change BasicLogger::ShouldOutputMessage to Logger::ShouldOutputMessage. + explicit Log(int aOptions = Log::DefaultOptions(L == LOG_CRITICAL), + LogReason aReason = LogReason::MustBeMoreThanThis) + : mOptions(0), mLogIt(false) { + Init(aOptions, BasicLogger::ShouldOutputMessage(L), aReason); + } + + ~Log() { Flush(); } + + void Flush() { + if (MOZ_LIKELY(!LogIt())) return; + + std::string str = mMessage.str(); + if (!str.empty()) { + WriteLog(str); + } + mMessage.str(""); + } + + Log& operator<<(char aChar) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aChar; + } + return *this; + } + Log& operator<<(const std::string& aLogText) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aLogText; + } + return *this; + } + Log& operator<<(const char aStr[]) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << static_cast<const char*>(aStr); + } + return *this; + } +#ifdef WIN32 + Log& operator<<(const wchar_t aWStr[]) { + if (MOZ_UNLIKELY(LogIt())) { + LogWStr(aWStr, mMessage); + } + return *this; + } +#endif + Log& operator<<(bool aBool) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << (aBool ? "true" : "false"); + } + return *this; + } + Log& operator<<(int aInt) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aInt; + } + return *this; + } + Log& operator<<(unsigned int aInt) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aInt; + } + return *this; + } + Log& operator<<(long aLong) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aLong; + } + return *this; + } + Log& operator<<(unsigned long aLong) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aLong; + } + return *this; + } + Log& operator<<(long long aLong) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aLong; + } + return *this; + } + Log& operator<<(unsigned long long aLong) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aLong; + } + return *this; + } + Log& operator<<(Float aFloat) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aFloat; + } + return *this; + } + Log& operator<<(double aDouble) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << aDouble; + } + return *this; + } + Log& operator<<(const sRGBColor& aColor) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << "sRGBColor(" << aColor.r << ", " << aColor.g << ", " + << aColor.b << ", " << aColor.a << ")"; + } + return *this; + } + Log& operator<<(const DeviceColor& aColor) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << "DeviceColor(" << aColor.r << ", " << aColor.g << ", " + << aColor.b << ", " << aColor.a << ")"; + } + return *this; + } + template <typename T, typename Sub, typename Coord> + Log& operator<<(const BasePoint<T, Sub, Coord>& aPoint) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << "Point" << aPoint; + } + return *this; + } + template <typename T, typename Sub, typename Coord> + Log& operator<<(const BaseSize<T, Sub, Coord>& aSize) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << "Size(" << aSize.width << "," << aSize.height << ")"; + } + return *this; + } + template <typename T, typename Sub, typename Point, typename SizeT, + typename Margin> + Log& operator<<(const BaseRect<T, Sub, Point, SizeT, Margin>& aRect) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << "Rect" << aRect; + } + return *this; + } + Log& operator<<(const Matrix& aMatrix) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << "Matrix(" << aMatrix._11 << " " << aMatrix._12 << " ; " + << aMatrix._21 << " " << aMatrix._22 << " ; " << aMatrix._31 + << " " << aMatrix._32 << ")"; + } + return *this; + } + template <typename T> + Log& operator<<(Hexa<T> aHex) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << std::showbase << std::hex << aHex.mVal << std::noshowbase + << std::dec; + } + return *this; + } + + Log& operator<<(const SourceSurface* aSurface) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << "SourceSurface(" << (void*)(aSurface) << ")"; + } + return *this; + } + Log& operator<<(const Path* aPath) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << "Path(" << (void*)(aPath) << ")"; + } + return *this; + } + Log& operator<<(const Pattern* aPattern) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << "Pattern(" << (void*)(aPattern) << ")"; + } + return *this; + } + Log& operator<<(const ScaledFont* aFont) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << "ScaledFont(" << (void*)(aFont) << ")"; + } + return *this; + } + Log& operator<<(const FilterNode* aFilter) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << "FilterNode(" << (void*)(aFilter) << ")"; + } + return *this; + } + Log& operator<<(const DrawOptions& aOptions) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << "DrawOptions(" << aOptions.mAlpha << ", "; + (*this) << aOptions.mCompositionOp; + mMessage << ", "; + (*this) << aOptions.mAntialiasMode; + mMessage << ")"; + } + return *this; + } + Log& operator<<(const DrawSurfaceOptions& aOptions) { + if (MOZ_UNLIKELY(LogIt())) { + mMessage << "DrawSurfaceOptions("; + (*this) << aOptions.mSamplingFilter; + mMessage << ", "; + (*this) << aOptions.mSamplingBounds; + mMessage << ")"; + } + return *this; + } + + Log& operator<<(SamplingBounds aBounds) { + if (MOZ_UNLIKELY(LogIt())) { + switch (aBounds) { + case SamplingBounds::UNBOUNDED: + mMessage << "SamplingBounds::UNBOUNDED"; + break; + case SamplingBounds::BOUNDED: + mMessage << "SamplingBounds::BOUNDED"; + break; + default: + mMessage << "Invalid SamplingBounds (" << (int)aBounds << ")"; + break; + } + } + return *this; + } + Log& operator<<(SamplingFilter aFilter) { + if (MOZ_UNLIKELY(LogIt())) { + switch (aFilter) { + case SamplingFilter::GOOD: + mMessage << "SamplingFilter::GOOD"; + break; + case SamplingFilter::LINEAR: + mMessage << "SamplingFilter::LINEAR"; + break; + case SamplingFilter::POINT: + mMessage << "SamplingFilter::POINT"; + break; + default: + mMessage << "Invalid SamplingFilter (" << (int)aFilter << ")"; + break; + } + } + return *this; + } + Log& operator<<(AntialiasMode aMode) { + if (MOZ_UNLIKELY(LogIt())) { + switch (aMode) { + case AntialiasMode::NONE: + mMessage << "AntialiasMode::NONE"; + break; + case AntialiasMode::GRAY: + mMessage << "AntialiasMode::GRAY"; + break; + case AntialiasMode::SUBPIXEL: + mMessage << "AntialiasMode::SUBPIXEL"; + break; + case AntialiasMode::DEFAULT: + mMessage << "AntialiasMode::DEFAULT"; + break; + default: + mMessage << "Invalid AntialiasMode (" << (int)aMode << ")"; + break; + } + } + return *this; + } + Log& operator<<(CompositionOp aOp) { + if (MOZ_UNLIKELY(LogIt())) { + switch (aOp) { + case CompositionOp::OP_CLEAR: + mMessage << "CompositionOp::OP_CLEAR"; + break; + case CompositionOp::OP_OVER: + mMessage << "CompositionOp::OP_OVER"; + break; + case CompositionOp::OP_ADD: + mMessage << "CompositionOp::OP_ADD"; + break; + case CompositionOp::OP_ATOP: + mMessage << "CompositionOp::OP_ATOP"; + break; + case CompositionOp::OP_OUT: + mMessage << "CompositionOp::OP_OUT"; + break; + case CompositionOp::OP_IN: + mMessage << "CompositionOp::OP_IN"; + break; + case CompositionOp::OP_SOURCE: + mMessage << "CompositionOp::OP_SOURCE"; + break; + case CompositionOp::OP_DEST_IN: + mMessage << "CompositionOp::OP_DEST_IN"; + break; + case CompositionOp::OP_DEST_OUT: + mMessage << "CompositionOp::OP_DEST_OUT"; + break; + case CompositionOp::OP_DEST_OVER: + mMessage << "CompositionOp::OP_DEST_OVER"; + break; + case CompositionOp::OP_DEST_ATOP: + mMessage << "CompositionOp::OP_DEST_ATOP"; + break; + case CompositionOp::OP_XOR: + mMessage << "CompositionOp::OP_XOR"; + break; + case CompositionOp::OP_MULTIPLY: + mMessage << "CompositionOp::OP_MULTIPLY"; + break; + case CompositionOp::OP_SCREEN: + mMessage << "CompositionOp::OP_SCREEN"; + break; + case CompositionOp::OP_OVERLAY: + mMessage << "CompositionOp::OP_OVERLAY"; + break; + case CompositionOp::OP_DARKEN: + mMessage << "CompositionOp::OP_DARKEN"; + break; + case CompositionOp::OP_LIGHTEN: + mMessage << "CompositionOp::OP_LIGHTEN"; + break; + case CompositionOp::OP_COLOR_DODGE: + mMessage << "CompositionOp::OP_COLOR_DODGE"; + break; + case CompositionOp::OP_COLOR_BURN: + mMessage << "CompositionOp::OP_COLOR_BURN"; + break; + case CompositionOp::OP_HARD_LIGHT: + mMessage << "CompositionOp::OP_HARD_LIGHT"; + break; + case CompositionOp::OP_SOFT_LIGHT: + mMessage << "CompositionOp::OP_SOFT_LIGHT"; + break; + case CompositionOp::OP_DIFFERENCE: + mMessage << "CompositionOp::OP_DIFFERENCE"; + break; + case CompositionOp::OP_EXCLUSION: + mMessage << "CompositionOp::OP_EXCLUSION"; + break; + case CompositionOp::OP_HUE: + mMessage << "CompositionOp::OP_HUE"; + break; + case CompositionOp::OP_SATURATION: + mMessage << "CompositionOp::OP_SATURATION"; + break; + case CompositionOp::OP_COLOR: + mMessage << "CompositionOp::OP_COLOR"; + break; + case CompositionOp::OP_LUMINOSITY: + mMessage << "CompositionOp::OP_LUMINOSITY"; + break; + case CompositionOp::OP_COUNT: + mMessage << "CompositionOp::OP_COUNT"; + break; + default: + mMessage << "Invalid CompositionOp (" << (int)aOp << ")"; + break; + } + } + return *this; + } + Log& operator<<(SurfaceFormat aFormat) { + if (MOZ_UNLIKELY(LogIt())) { + switch (aFormat) { + case SurfaceFormat::B8G8R8A8: + mMessage << "SurfaceFormat::B8G8R8A8"; + break; + case SurfaceFormat::B8G8R8X8: + mMessage << "SurfaceFormat::B8G8R8X8"; + break; + case SurfaceFormat::R8G8B8A8: + mMessage << "SurfaceFormat::R8G8B8A8"; + break; + case SurfaceFormat::R8G8B8X8: + mMessage << "SurfaceFormat::R8G8B8X8"; + break; + case SurfaceFormat::R5G6B5_UINT16: + mMessage << "SurfaceFormat::R5G6B5_UINT16"; + break; + case SurfaceFormat::A8: + mMessage << "SurfaceFormat::A8"; + break; + case SurfaceFormat::YUV: + mMessage << "SurfaceFormat::YUV"; + break; + case SurfaceFormat::UNKNOWN: + mMessage << "SurfaceFormat::UNKNOWN"; + break; + default: + mMessage << "Invalid SurfaceFormat (" << (int)aFormat << ")"; + break; + } + } + return *this; + } + + Log& operator<<(SurfaceType aType) { + if (MOZ_UNLIKELY(LogIt())) { + switch (aType) { + case SurfaceType::DATA: + mMessage << "SurfaceType::DATA"; + break; + case SurfaceType::D2D1_BITMAP: + mMessage << "SurfaceType::D2D1_BITMAP"; + break; + case SurfaceType::D2D1_DRAWTARGET: + mMessage << "SurfaceType::D2D1_DRAWTARGET"; + break; + case SurfaceType::CAIRO: + mMessage << "SurfaceType::CAIRO"; + break; + case SurfaceType::CAIRO_IMAGE: + mMessage << "SurfaceType::CAIRO_IMAGE"; + break; + case SurfaceType::COREGRAPHICS_IMAGE: + mMessage << "SurfaceType::COREGRAPHICS_IMAGE"; + break; + case SurfaceType::COREGRAPHICS_CGCONTEXT: + mMessage << "SurfaceType::COREGRAPHICS_CGCONTEXT"; + break; + case SurfaceType::SKIA: + mMessage << "SurfaceType::SKIA"; + break; + case SurfaceType::D2D1_1_IMAGE: + mMessage << "SurfaceType::D2D1_1_IMAGE"; + break; + case SurfaceType::RECORDING: + mMessage << "SurfaceType::RECORDING"; + break; + case SurfaceType::DATA_SHARED: + mMessage << "SurfaceType::DATA_SHARED"; + break; + case SurfaceType::DATA_RECYCLING_SHARED: + mMessage << "SurfaceType::DATA_RECYCLING_SHARED"; + break; + case SurfaceType::DATA_ALIGNED: + mMessage << "SurfaceType::DATA_ALIGNED"; + break; + case SurfaceType::DATA_SHARED_WRAPPER: + mMessage << "SurfaceType::DATA_SHARED_WRAPPER"; + break; + case SurfaceType::DATA_MAPPED: + mMessage << "SurfaceType::DATA_MAPPED"; + break; + case SurfaceType::WEBGL: + mMessage << "SurfaceType::WEBGL"; + break; + default: + mMessage << "Invalid SurfaceType (" << (int)aType << ")"; + break; + } + } + return *this; + } + + inline bool LogIt() const { return mLogIt; } + inline bool NoNewline() const { + return mOptions & int(LogOptions::NoNewline); + } + inline bool AutoPrefix() const { + return mOptions & int(LogOptions::AutoPrefix); + } + inline bool ValidReason() const { + return (int)mReason > (int)LogReason::MustBeMoreThanThis && + (int)mReason < (int)LogReason::MustBeLessThanThis; + } + + // We do not want this version to do any work, and stringstream can't be + // copied anyway. It does come in handy for the "Once" macro defined below. + MOZ_IMPLICIT Log(const Log& log) { Init(log.mOptions, false, log.mReason); } + + private: + // Initialization common to two constructors + void Init(int aOptions, bool aLogIt, LogReason aReason) { + mOptions = aOptions; + mReason = aReason; + mLogIt = aLogIt; + if (mLogIt) { + if (AutoPrefix()) { + if (mOptions & int(LogOptions::AssertOnCall)) { + mMessage << "[GFX" << L; + } else { + mMessage << "[GFX" << L << "-"; + } + } + if ((mOptions & int(LogOptions::CrashAction)) && ValidReason()) { + mMessage << " " << (int)mReason; + } + if (AutoPrefix()) { + mMessage << "]: "; + } + } + } + + void WriteLog(const std::string& aString) { + if (MOZ_UNLIKELY(LogIt())) { + Logger::OutputMessage(aString, L, NoNewline()); + // Assert if required. We don't have a three parameter MOZ_ASSERT + // so use the underlying functions instead (see bug 1281702): +#ifdef DEBUG + if (mOptions & int(LogOptions::AssertOnCall)) { + MOZ_ReportAssertionFailure(aString.c_str(), __FILE__, __LINE__); + MOZ_CRASH("GFX: An assert from the graphics logger"); + } +#endif + if ((mOptions & int(LogOptions::CrashAction)) && ValidReason()) { + Logger::CrashAction(mReason); + } + } + } + + std::stringstream mMessage; + int mOptions; + LogReason mReason; + bool mLogIt; +}; + +typedef Log<LOG_DEBUG> DebugLog; +typedef Log<LOG_WARNING> WarningLog; +typedef Log<LOG_CRITICAL, CriticalLogger> CriticalLog; + +// Macro to glue names to get us less chance of name clashing. +#if defined GFX_LOGGING_GLUE1 || defined GFX_LOGGING_GLUE +# error "Clash of the macro GFX_LOGGING_GLUE1 or GFX_LOGGING_GLUE" +#endif +#define GFX_LOGGING_GLUE1(x, y) x##y +#define GFX_LOGGING_GLUE(x, y) GFX_LOGGING_GLUE1(x, y) + +// This log goes into crash reports, use with care. +#define gfxCriticalError mozilla::gfx::CriticalLog +#define gfxCriticalErrorOnce \ + static gfxCriticalError GFX_LOGGING_GLUE(sOnceAtLine, __LINE__) = \ + gfxCriticalError + +// This is a shortcut for errors we want logged in crash reports/about support +// but we do not want asserting. These are available in all builds, so it is +// not worth trying to do magic to avoid matching the syntax of +// gfxCriticalError. +// So, this one is used as +// gfxCriticalNote << "Something to report and not assert"; +// while the critical error is +// gfxCriticalError() << "Something to report and assert"; +#define gfxCriticalNote \ + gfxCriticalError(gfxCriticalError::DefaultOptions(false)) +#define gfxCriticalNoteOnce \ + static gfxCriticalError GFX_LOGGING_GLUE(sOnceAtLine, __LINE__) = \ + gfxCriticalNote + +// The "once" versions will only trigger the first time through. You can do +// this: gfxCriticalErrorOnce() << "This message only shows up once; instead of +// the usual: static bool firstTime = true; if (firstTime) { +// firstTime = false; +// gfxCriticalError() << "This message only shows up once; +// } +#if defined(DEBUG) +# define gfxDebug mozilla::gfx::DebugLog +# define gfxDebugOnce \ + static gfxDebug GFX_LOGGING_GLUE(sOnceAtLine, __LINE__) = gfxDebug +#else +# define gfxDebug \ + if (1) \ + ; \ + else \ + mozilla::gfx::NoLog +# define gfxDebugOnce \ + if (1) \ + ; \ + else \ + mozilla::gfx::NoLog +#endif + +// Have gfxWarning available (behind a runtime preference) +#define gfxWarning mozilla::gfx::WarningLog +#define gfxWarningOnce \ + static gfxWarning GFX_LOGGING_GLUE(sOnceAtLine, __LINE__) = gfxWarning + +// In the debug build, this is equivalent to the default gfxCriticalError. +// In the non-debug build, on nightly and dev edition, it will MOZ_CRASH. +// On beta and release versions, it will telemetry count, but proceed. +// +// You should create a (new) enum in the LogReason and use it for the reason +// parameter to ensure uniqueness. +#define gfxDevCrash(reason) \ + gfxCriticalError(int(gfx::LogOptions::AutoPrefix) | \ + int(gfx::LogOptions::AssertOnCall) | \ + int(gfx::LogOptions::CrashAction), \ + (reason)) + +// See nsDebug.h and the NS_WARN_IF macro + +#ifdef __cplusplus +// For now, have MOZ2D_ERROR_IF available in debug and non-debug builds +inline bool MOZ2D_error_if_impl(bool aCondition, const char* aExpr, + const char* aFile, int32_t aLine) { + if (MOZ_UNLIKELY(aCondition)) { + gfxCriticalError() << aExpr << " at " << aFile << ":" << aLine; + } + return aCondition; +} +# define MOZ2D_ERROR_IF(condition) \ + MOZ2D_error_if_impl(condition, #condition, __FILE__, __LINE__) + +# ifdef DEBUG +inline bool MOZ2D_warn_if_impl(bool aCondition, const char* aExpr, + const char* aFile, int32_t aLine) { + if (MOZ_UNLIKELY(aCondition)) { + gfxWarning() << aExpr << " at " << aFile << ":" << aLine; + } + return aCondition; +} +# define MOZ2D_WARN_IF(condition) \ + MOZ2D_warn_if_impl(condition, #condition, __FILE__, __LINE__) +# else +# define MOZ2D_WARN_IF(condition) (bool)(condition) +# endif +#endif + +const int INDENT_PER_LEVEL = 2; + +template <int Level = LOG_DEBUG> +class TreeLog { + public: + explicit TreeLog(const std::string& aPrefix = "") + : mLog(int(LogOptions::NoNewline)), + mPrefix(aPrefix), + mDepth(0), + mStartOfLine(true), + mConditionedOnPref(false), + mPrefFunction(nullptr) {} + + template <typename T> + TreeLog& operator<<(const T& aObject) { + if (mConditionedOnPref && !mPrefFunction()) { + return *this; + } + if (mStartOfLine) { + if (!mPrefix.empty()) { + mLog << '[' << mPrefix << "] "; + } + mLog << std::string(mDepth * INDENT_PER_LEVEL, ' '); + mStartOfLine = false; + } + mLog << aObject; + if (EndsInNewline(aObject)) { + // Don't indent right here as the user may change the indent + // between now and the first output to the next line. + mLog.Flush(); + mStartOfLine = true; + } + return *this; + } + + void IncreaseIndent() { ++mDepth; } + void DecreaseIndent() { + MOZ_ASSERT(mDepth > 0); + --mDepth; + } + + void ConditionOnPrefFunction(bool (*aPrefFunction)()) { + mConditionedOnPref = true; + mPrefFunction = aPrefFunction; + } + + private: + Log<Level> mLog; + std::string mPrefix; + uint32_t mDepth; + bool mStartOfLine; + bool mConditionedOnPref; + bool (*mPrefFunction)(); + + template <typename T> + static bool EndsInNewline(const T& aObject) { + return false; + } + + static bool EndsInNewline(const std::string& aString) { + return !aString.empty() && aString[aString.length() - 1] == '\n'; + } + + static bool EndsInNewline(char aChar) { return aChar == '\n'; } + + static bool EndsInNewline(const char* aString) { + return EndsInNewline(std::string(aString)); + } +}; + +template <int Level = LOG_DEBUG> +class TreeAutoIndent final { + public: + explicit TreeAutoIndent(TreeLog<Level>& aTreeLog) : mTreeLog(aTreeLog) { + mTreeLog.IncreaseIndent(); + } + + TreeAutoIndent(const TreeAutoIndent& aTreeAutoIndent) + : mTreeLog(aTreeAutoIndent.mTreeLog) { + mTreeLog.IncreaseIndent(); + } + + TreeAutoIndent& operator=(const TreeAutoIndent& aTreeAutoIndent) = delete; + + ~TreeAutoIndent() { mTreeLog.DecreaseIndent(); } + + private: + TreeLog<Level>& mTreeLog; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_LOGGING_H_ */ diff --git a/gfx/2d/LoggingConstants.h b/gfx/2d/LoggingConstants.h new file mode 100644 index 0000000000..cede5d3e87 --- /dev/null +++ b/gfx/2d/LoggingConstants.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_LOGGING_CONSTANTS_H_ +#define MOZILLA_GFX_LOGGING_CONSTANTS_H_ + +namespace mozilla { +namespace gfx { + +// Attempting to be consistent with prlog values, but that isn't critical +// (and note that 5 has a special meaning - see the description +// with LoggingPrefs::sGfxLogLevel) +const int LOG_CRITICAL = 1; +const int LOG_WARNING = 2; +const int LOG_DEBUG = 3; +const int LOG_DEBUG_PRLOG = 4; +const int LOG_EVERYTHING = 5; // This needs to be the highest value + +#if defined(DEBUG) +const int LOG_DEFAULT = LOG_EVERYTHING; +#else +const int LOG_DEFAULT = LOG_CRITICAL; +#endif + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_LOGGING_CONSTANTS_H_ */ diff --git a/gfx/2d/LuminanceNEON.cpp b/gfx/2d/LuminanceNEON.cpp new file mode 100644 index 0000000000..cb84336268 --- /dev/null +++ b/gfx/2d/LuminanceNEON.cpp @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <arm_neon.h> +#include "LuminanceNEON.h" + +using namespace mozilla::gfx; + +/** + * Byte offsets of channels in a native packed gfxColor or cairo image surface. + */ +#ifdef IS_BIG_ENDIAN +# define GFX_ARGB32_OFFSET_A 0 +# define GFX_ARGB32_OFFSET_R 1 +# define GFX_ARGB32_OFFSET_G 2 +# define GFX_ARGB32_OFFSET_B 3 +#else +# define GFX_ARGB32_OFFSET_A 3 +# define GFX_ARGB32_OFFSET_R 2 +# define GFX_ARGB32_OFFSET_G 1 +# define GFX_ARGB32_OFFSET_B 0 +#endif + +void ComputesRGBLuminanceMask_NEON(const uint8_t* aSourceData, + int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, const IntSize& aSize, + float aOpacity) { + int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity + int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity + int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721 + const uint8_t* sourcePixel = aSourceData; + int32_t sourceOffset = aSourceStride - 4 * aSize.width; + uint8_t* destPixel = aDestData; + int32_t destOffset = aDestStride - aSize.width; + + sourcePixel = aSourceData; + int32_t remainderWidth = aSize.width % 8; + int32_t roundedWidth = aSize.width - remainderWidth; + uint16x8_t temp; + uint8x8_t gray; + uint8x8_t redVector = vdup_n_u8(redFactor); + uint8x8_t greenVector = vdup_n_u8(greenFactor); + uint8x8_t blueVector = vdup_n_u8(blueFactor); + uint8x8_t fullBitVector = vdup_n_u8(255); + uint8x8_t oneVector = vdup_n_u8(1); + for (int32_t y = 0; y < aSize.height; y++) { + // Calculate luminance by neon with 8 pixels per loop + for (int32_t x = 0; x < roundedWidth; x += 8) { + uint8x8x4_t argb = vld4_u8(sourcePixel); + temp = vmull_u8(argb.val[GFX_ARGB32_OFFSET_R], + redVector); // temp = red * redFactor + temp = vmlal_u8(temp, argb.val[GFX_ARGB32_OFFSET_G], + greenVector); // temp += green * greenFactor + temp = vmlal_u8(temp, argb.val[GFX_ARGB32_OFFSET_B], + blueVector); // temp += blue * blueFactor + gray = vshrn_n_u16(temp, 8); // gray = temp >> 8 + + // Check alpha value + uint8x8_t alphaVector = + vtst_u8(argb.val[GFX_ARGB32_OFFSET_A], fullBitVector); + gray = vmul_u8(gray, vand_u8(alphaVector, oneVector)); + + // Put the result to the 8 pixels + vst1_u8(destPixel, gray); + sourcePixel += 8 * 4; + destPixel += 8; + } + + // Calculate the rest pixels of the line by cpu + for (int32_t x = 0; x < remainderWidth; x++) { + if (sourcePixel[GFX_ARGB32_OFFSET_A] > 0) { + *destPixel = (redFactor * sourcePixel[GFX_ARGB32_OFFSET_R] + + greenFactor * sourcePixel[GFX_ARGB32_OFFSET_G] + + blueFactor * sourcePixel[GFX_ARGB32_OFFSET_B]) >> + 8; + } else { + *destPixel = 0; + } + sourcePixel += 4; + destPixel++; + } + sourcePixel += sourceOffset; + destPixel += destOffset; + } +} diff --git a/gfx/2d/LuminanceNEON.h b/gfx/2d/LuminanceNEON.h new file mode 100644 index 0000000000..1af0d039dd --- /dev/null +++ b/gfx/2d/LuminanceNEON.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __LUMINANCENEON_H__ +#define __LUMINANCENEON_H__ + +#include "mozilla/gfx/Point.h" + +void ComputesRGBLuminanceMask_NEON(const uint8_t* aSourceData, + int32_t aSourceStride, uint8_t* aDestData, + int32_t aDestStride, + const mozilla::gfx::IntSize& aSize, + float aOpacity); + +#endif /* __LUMINANCENEON_H__ */ diff --git a/gfx/2d/MMIHelpers.h b/gfx/2d/MMIHelpers.h new file mode 100644 index 0000000000..c110a47f57 --- /dev/null +++ b/gfx/2d/MMIHelpers.h @@ -0,0 +1,232 @@ +/* + ============================================================================ + Name : MMIHelpers.h + Author : Heiher <r@hev.cc> + Version : 0.0.1 + Copyright : Copyright (c) 2015 everyone. + Description : The helpers for x86 SSE to Loongson MMI. + ============================================================================ + */ + +#ifndef __MMI_HELPERS_H__ +#define __MMI_HELPERS_H__ + +#define __mm_packxxxx(_f, _D, _d, _s, _t) \ + #_f " %[" #_t "], %[" #_d "h], %[" #_s "h] \n\t" #_f " %[" #_D "l], %[" #_d \ + "l], %[" #_s \ + "l] \n\t" \ + "punpckhwd %[" #_D "h], %[" #_D "l], %[" #_t \ + "] \n\t" \ + "punpcklwd %[" #_D "l], %[" #_D "l], %[" #_t "] \n\t" + +#define _mm_or(_D, _d, _s) \ + "or %[" #_D "h], %[" #_d "h], %[" #_s \ + "h] \n\t" \ + "or %[" #_D "l], %[" #_d "l], %[" #_s "l] \n\t" + +#define _mm_xor(_D, _d, _s) \ + "xor %[" #_D "h], %[" #_d "h], %[" #_s \ + "h] \n\t" \ + "xor %[" #_D "l], %[" #_d "l], %[" #_s "l] \n\t" + +#define _mm_and(_D, _d, _s) \ + "and %[" #_D "h], %[" #_d "h], %[" #_s \ + "h] \n\t" \ + "and %[" #_D "l], %[" #_d "l], %[" #_s "l] \n\t" + +/* SSE: pandn */ +#define _mm_pandn(_D, _d, _s) \ + "pandn %[" #_D "h], %[" #_d "h], %[" #_s \ + "h] \n\t" \ + "pandn %[" #_D "l], %[" #_d "l], %[" #_s "l] \n\t" + +/* SSE: pshuflw */ +#define _mm_pshuflh(_D, _d, _s) \ + "mov.d %[" #_D "h], %[" #_d \ + "h] \n\t" \ + "pshufh %[" #_D "l], %[" #_d "l], %[" #_s "] \n\t" + +/* SSE: psllw (bits) */ +#define _mm_psllh(_D, _d, _s) \ + "psllh %[" #_D "h], %[" #_d "h], %[" #_s \ + "] \n\t" \ + "psllh %[" #_D "l], %[" #_d "l], %[" #_s "] \n\t" + +/* SSE: pslld (bits) */ +#define _mm_psllw(_D, _d, _s) \ + "psllw %[" #_D "h], %[" #_d "h], %[" #_s \ + "] \n\t" \ + "psllw %[" #_D "l], %[" #_d "l], %[" #_s "] \n\t" + +/* SSE: psllq (bits) */ +#define _mm_pslld(_D, _d, _s) \ + "dsll %[" #_D "h], %[" #_d "h], %[" #_s \ + "] \n\t" \ + "dsll %[" #_D "l], %[" #_d "l], %[" #_s "] \n\t" + +/* SSE: pslldq (bytes) */ +#define _mm_psllq(_D, _d, _s, _s64, _tf) \ + "subu %[" #_tf "], %[" #_s64 "], %[" #_s \ + "] \n\t" \ + "dsrl %[" #_tf "], %[" #_d "l], %[" #_tf \ + "] \n\t" \ + "dsll %[" #_D "h], %[" #_d "h], %[" #_s \ + "] \n\t" \ + "dsll %[" #_D "l], %[" #_d "l], %[" #_s \ + "] \n\t" \ + "or %[" #_D "h], %[" #_D "h], %[" #_tf "] \n\t" + +/* SSE: psrlw (bits) */ +#define _mm_psrlh(_D, _d, _s) \ + "psrlh %[" #_D "h], %[" #_d "h], %[" #_s \ + "] \n\t" \ + "psrlh %[" #_D "l], %[" #_d "l], %[" #_s "] \n\t" + +/* SSE: psrld (bits) */ +#define _mm_psrlw(_D, _d, _s) \ + "psrlw %[" #_D "h], %[" #_d "h], %[" #_s \ + "] \n\t" \ + "psrlw %[" #_D "l], %[" #_d "l], %[" #_s "] \n\t" + +/* SSE: psrlq (bits) */ +#define _mm_psrld(_D, _d, _s) \ + "dsrl %[" #_D "h], %[" #_d "h], %[" #_s \ + "] \n\t" \ + "dsrl %[" #_D "l], %[" #_d "l], %[" #_s "] \n\t" + +/* SSE: psrldq (bytes) */ +#define _mm_psrlq(_D, _d, _s, _s64, _tf) \ + "subu %[" #_tf "], %[" #_s64 "], %[" #_s \ + "] \n\t" \ + "dsll %[" #_tf "], %[" #_d "h], %[" #_tf \ + "] \n\t" \ + "dsrl %[" #_D "h], %[" #_d "h], %[" #_s \ + "] \n\t" \ + "dsrl %[" #_D "l], %[" #_d "l], %[" #_s \ + "] \n\t" \ + "or %[" #_D "l], %[" #_D "l], %[" #_tf "] \n\t" + +/* SSE: psrad */ +#define _mm_psraw(_D, _d, _s) \ + "psraw %[" #_D "h], %[" #_d "h], %[" #_s \ + "] \n\t" \ + "psraw %[" #_D "l], %[" #_d "l], %[" #_s "] \n\t" + +/* SSE: paddb */ +#define _mm_paddb(_D, _d, _s) \ + "paddb %[" #_D "h], %[" #_d "h], %[" #_s \ + "h] \n\t" \ + "paddb %[" #_D "l], %[" #_d "l], %[" #_s "l] \n\t" + +/* SSE: paddw */ +#define _mm_paddh(_D, _d, _s) \ + "paddh %[" #_D "h], %[" #_d "h], %[" #_s \ + "h] \n\t" \ + "paddh %[" #_D "l], %[" #_d "l], %[" #_s "l] \n\t" + +/* SSE: paddd */ +#define _mm_paddw(_D, _d, _s) \ + "paddw %[" #_D "h], %[" #_d "h], %[" #_s \ + "h] \n\t" \ + "paddw %[" #_D "l], %[" #_d "l], %[" #_s "l] \n\t" + +/* SSE: paddq */ +#define _mm_paddd(_D, _d, _s) \ + "dadd %[" #_D "h], %[" #_d "h], %[" #_s \ + "h] \n\t" \ + "dadd %[" #_D "l], %[" #_d "l], %[" #_s "l] \n\t" + +/* SSE: psubw */ +#define _mm_psubh(_D, _d, _s) \ + "psubh %[" #_D "h], %[" #_d "h], %[" #_s \ + "h] \n\t" \ + "psubh %[" #_D "l], %[" #_d "l], %[" #_s "l] \n\t" + +/* SSE: psubd */ +#define _mm_psubw(_D, _d, _s) \ + "psubw %[" #_D "h], %[" #_d "h], %[" #_s \ + "h] \n\t" \ + "psubw %[" #_D "l], %[" #_d "l], %[" #_s "l] \n\t" + +/* SSE: pmaxub */ +#define _mm_pmaxub(_D, _d, _s) \ + "pmaxub %[" #_D "h], %[" #_d "h], %[" #_s \ + "h] \n\t" \ + "pmaxub %[" #_D "l], %[" #_d "l], %[" #_s "l] \n\t" + +/* SSE: pmullw */ +#define _mm_pmullh(_D, _d, _s) \ + "pmullh %[" #_D "h], %[" #_d "h], %[" #_s \ + "h] \n\t" \ + "pmullh %[" #_D "l], %[" #_d "l], %[" #_s "l] \n\t" + +/* SSE: pmulhw */ +#define _mm_pmulhh(_D, _d, _s) \ + "pmulhh %[" #_D "h], %[" #_d "h], %[" #_s \ + "h] \n\t" \ + "pmulhh %[" #_D "l], %[" #_d "l], %[" #_s "l] \n\t" + +/* SSE: pmuludq */ +#define _mm_pmuluw(_D, _d, _s) \ + "pmuluw %[" #_D "h], %[" #_d "h], %[" #_s \ + "h] \n\t" \ + "pmuluw %[" #_D "l], %[" #_d "l], %[" #_s "l] \n\t" + +/* SSE: packsswb */ +#define _mm_packsshb(_D, _d, _s, _t) __mm_packxxxx(packsshb, _D, _d, _s, _t) + +/* SSE: packssdw */ +#define _mm_packsswh(_D, _d, _s, _t) __mm_packxxxx(packsswh, _D, _d, _s, _t) + +/* SSE: packuswb */ +#define _mm_packushb(_D, _d, _s, _t) __mm_packxxxx(packushb, _D, _d, _s, _t) + +/* SSE: punpcklbw */ +#define _mm_punpcklbh(_D, _d, _s) \ + "punpckhbh %[" #_D "h], %[" #_d "l], %[" #_s \ + "l] \n\t" \ + "punpcklbh %[" #_D "l], %[" #_d "l], %[" #_s "l] \n\t" + +/* SSE: punpcklwd */ +#define _mm_punpcklhw(_D, _d, _s) \ + "punpckhhw %[" #_D "h], %[" #_d "l], %[" #_s \ + "l] \n\t" \ + "punpcklhw %[" #_D "l], %[" #_d "l], %[" #_s "l] \n\t" + +/* SSE: punpckldq */ +#define _mm_punpcklwd(_D, _d, _s) \ + "punpckhwd %[" #_D "h], %[" #_d "l], %[" #_s \ + "l] \n\t" \ + "punpcklwd %[" #_D "l], %[" #_d "l], %[" #_s "l] \n\t" + +/* SSE: punpcklqdq */ +#define _mm_punpckldq(_D, _d, _s) \ + "mov.d %[" #_D "h], %[" #_s \ + "l] \n\t" \ + "mov.d %[" #_D "l], %[" #_d "l] \n\t" + +/* SSE: punpckhbw */ +#define _mm_punpckhbh(_D, _d, _s) \ + "punpcklbh %[" #_D "l], %[" #_d "h], %[" #_s \ + "h] \n\t" \ + "punpckhbh %[" #_D "h], %[" #_d "h], %[" #_s "h] \n\t" + +/* SSE: punpckhwd */ +#define _mm_punpckhhw(_D, _d, _s) \ + "punpcklhw %[" #_D "l], %[" #_d "h], %[" #_s \ + "h] \n\t" \ + "punpckhhw %[" #_D "h], %[" #_d "h], %[" #_s "h] \n\t" + +/* SSE: punpckhdq */ +#define _mm_punpckhwd(_D, _d, _s) \ + "punpcklwd %[" #_D "l], %[" #_d "h], %[" #_s \ + "h] \n\t" \ + "punpckhwd %[" #_D "h], %[" #_d "h], %[" #_s "h] \n\t" + +/* SSE: punpckhqdq */ +#define _mm_punpckhdq(_D, _d, _s) \ + "mov.d %[" #_D "l], %[" #_d \ + "h] \n\t" \ + "mov.d %[" #_D "h], %[" #_s "h] \n\t" + +#endif /* __MMI_HELPERS_H__ */ diff --git a/gfx/2d/MacIOSurface.cpp b/gfx/2d/MacIOSurface.cpp new file mode 100644 index 0000000000..8c1c24bc10 --- /dev/null +++ b/gfx/2d/MacIOSurface.cpp @@ -0,0 +1,621 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MacIOSurface.h" +#include <OpenGL/gl.h> +#include <OpenGL/CGLIOSurface.h> +#include <QuartzCore/QuartzCore.h> +#include "GLConsts.h" +#include "GLContextCGL.h" +#include "gfxMacUtils.h" +#include "nsPrintfCString.h" +#include "mozilla/Assertions.h" +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/StaticPrefs_gfx.h" + +using namespace mozilla; + +MacIOSurface::MacIOSurface(CFTypeRefPtr<IOSurfaceRef> aIOSurfaceRef, + bool aHasAlpha, gfx::YUVColorSpace aColorSpace) + : mIOSurfaceRef(std::move(aIOSurfaceRef)), + mHasAlpha(aHasAlpha), + mColorSpace(aColorSpace) { + IncrementUseCount(); +} + +MacIOSurface::~MacIOSurface() { + MOZ_RELEASE_ASSERT(!IsLocked(), "Destroying locked surface"); + DecrementUseCount(); +} + +void AddDictionaryInt(const CFTypeRefPtr<CFMutableDictionaryRef>& aDict, + const void* aType, uint32_t aValue) { + auto cfValue = CFTypeRefPtr<CFNumberRef>::WrapUnderCreateRule( + ::CFNumberCreate(nullptr, kCFNumberSInt32Type, &aValue)); + ::CFDictionaryAddValue(aDict.get(), aType, cfValue.get()); +} + +void SetSizeProperties(const CFTypeRefPtr<CFMutableDictionaryRef>& aDict, + int aWidth, int aHeight, int aBytesPerPixel) { + AddDictionaryInt(aDict, kIOSurfaceWidth, aWidth); + AddDictionaryInt(aDict, kIOSurfaceHeight, aHeight); + ::CFDictionaryAddValue(aDict.get(), kIOSurfaceIsGlobal, kCFBooleanTrue); + AddDictionaryInt(aDict, kIOSurfaceBytesPerElement, aBytesPerPixel); + + size_t bytesPerRow = + IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, aWidth * aBytesPerPixel); + AddDictionaryInt(aDict, kIOSurfaceBytesPerRow, bytesPerRow); + + // Add a SIMD register worth of extra bytes to the end of the allocation for + // SWGL. + size_t totalBytes = + IOSurfaceAlignProperty(kIOSurfaceAllocSize, aHeight * bytesPerRow + 16); + AddDictionaryInt(aDict, kIOSurfaceAllocSize, totalBytes); +} + +/* static */ +already_AddRefed<MacIOSurface> MacIOSurface::CreateIOSurface(int aWidth, + int aHeight, + bool aHasAlpha) { + auto props = CFTypeRefPtr<CFMutableDictionaryRef>::WrapUnderCreateRule( + ::CFDictionaryCreateMutable(kCFAllocatorDefault, 4, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + if (!props) return nullptr; + + MOZ_ASSERT((size_t)aWidth <= GetMaxWidth()); + MOZ_ASSERT((size_t)aHeight <= GetMaxHeight()); + + int32_t bytesPerElem = 4; + SetSizeProperties(props, aWidth, aHeight, bytesPerElem); + + AddDictionaryInt(props, kIOSurfacePixelFormat, + (uint32_t)kCVPixelFormatType_32BGRA); + + CFTypeRefPtr<IOSurfaceRef> surfaceRef = + CFTypeRefPtr<IOSurfaceRef>::WrapUnderCreateRule( + ::IOSurfaceCreate(props.get())); + + if (StaticPrefs::gfx_color_management_native_srgb()) { + IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceColorSpace"), + kCGColorSpaceSRGB); + } + + if (!surfaceRef) { + return nullptr; + } + + RefPtr<MacIOSurface> ioSurface = + new MacIOSurface(std::move(surfaceRef), aHasAlpha); + + return ioSurface.forget(); +} + +size_t CreatePlaneDictionary(CFTypeRefPtr<CFMutableDictionaryRef>& aDict, + const gfx::IntSize& aSize, size_t aOffset, + size_t aBytesPerPixel) { + size_t bytesPerRow = IOSurfaceAlignProperty(kIOSurfacePlaneBytesPerRow, + aSize.width * aBytesPerPixel); + // Add a SIMD register worth of extra bytes to the end of the allocation for + // SWGL. + size_t totalBytes = IOSurfaceAlignProperty(kIOSurfacePlaneSize, + aSize.height * bytesPerRow + 16); + + aDict = CFTypeRefPtr<CFMutableDictionaryRef>::WrapUnderCreateRule( + ::CFDictionaryCreateMutable(kCFAllocatorDefault, 4, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + AddDictionaryInt(aDict, kIOSurfacePlaneWidth, aSize.width); + AddDictionaryInt(aDict, kIOSurfacePlaneHeight, aSize.height); + AddDictionaryInt(aDict, kIOSurfacePlaneBytesPerRow, bytesPerRow); + AddDictionaryInt(aDict, kIOSurfacePlaneOffset, aOffset); + AddDictionaryInt(aDict, kIOSurfacePlaneSize, totalBytes); + AddDictionaryInt(aDict, kIOSurfacePlaneBytesPerElement, aBytesPerPixel); + + return totalBytes; +} + +/* static */ +already_AddRefed<MacIOSurface> MacIOSurface::CreateNV12OrP010Surface( + const IntSize& aYSize, const IntSize& aCbCrSize, YUVColorSpace aColorSpace, + TransferFunction aTransferFunction, ColorRange aColorRange, + ColorDepth aColorDepth) { + MOZ_ASSERT(aColorSpace == YUVColorSpace::BT601 || + aColorSpace == YUVColorSpace::BT709 || + aColorSpace == YUVColorSpace::BT2020); + MOZ_ASSERT(aColorRange == ColorRange::LIMITED || + aColorRange == ColorRange::FULL); + MOZ_ASSERT(aColorDepth == ColorDepth::COLOR_8 || + aColorDepth == ColorDepth::COLOR_10); + + auto props = CFTypeRefPtr<CFMutableDictionaryRef>::WrapUnderCreateRule( + ::CFDictionaryCreateMutable(kCFAllocatorDefault, 4, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + if (!props) return nullptr; + + MOZ_ASSERT((size_t)aYSize.width <= GetMaxWidth()); + MOZ_ASSERT((size_t)aYSize.height <= GetMaxHeight()); + + AddDictionaryInt(props, kIOSurfaceWidth, aYSize.width); + AddDictionaryInt(props, kIOSurfaceHeight, aYSize.height); + ::CFDictionaryAddValue(props.get(), kIOSurfaceIsGlobal, kCFBooleanTrue); + + if (aColorRange == ColorRange::LIMITED) { + if (aColorDepth == ColorDepth::COLOR_8) { + AddDictionaryInt( + props, kIOSurfacePixelFormat, + (uint32_t)kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange); + } else { + AddDictionaryInt( + props, kIOSurfacePixelFormat, + (uint32_t)kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange); + } + } else { + if (aColorDepth == ColorDepth::COLOR_8) { + AddDictionaryInt( + props, kIOSurfacePixelFormat, + (uint32_t)kCVPixelFormatType_420YpCbCr8BiPlanarFullRange); + } else { + AddDictionaryInt( + props, kIOSurfacePixelFormat, + (uint32_t)kCVPixelFormatType_420YpCbCr10BiPlanarFullRange); + } + } + + size_t bytesPerPixel = (aColorDepth == ColorDepth::COLOR_8) ? 1 : 2; + + CFTypeRefPtr<CFMutableDictionaryRef> planeProps[2]; + size_t yPlaneBytes = + CreatePlaneDictionary(planeProps[0], aYSize, 0, bytesPerPixel); + size_t cbCrOffset = + IOSurfaceAlignProperty(kIOSurfacePlaneOffset, yPlaneBytes); + size_t cbCrPlaneBytes = CreatePlaneDictionary(planeProps[1], aCbCrSize, + cbCrOffset, bytesPerPixel * 2); + size_t totalBytes = + IOSurfaceAlignProperty(kIOSurfaceAllocSize, cbCrOffset + cbCrPlaneBytes); + + AddDictionaryInt(props, kIOSurfaceAllocSize, totalBytes); + + auto array = CFTypeRefPtr<CFArrayRef>::WrapUnderCreateRule( + CFArrayCreate(kCFAllocatorDefault, (const void**)planeProps, 2, + &kCFTypeArrayCallBacks)); + ::CFDictionaryAddValue(props.get(), kIOSurfacePlaneInfo, array.get()); + + CFTypeRefPtr<IOSurfaceRef> surfaceRef = + CFTypeRefPtr<IOSurfaceRef>::WrapUnderCreateRule( + ::IOSurfaceCreate(props.get())); + + if (!surfaceRef) { + return nullptr; + } + + // Setup the correct YCbCr conversion matrix, color primaries, and transfer + // functions on the IOSurface, in case we pass this directly to CoreAnimation. + // For keys and values, we'd like to use values specified by the API, but + // those are only defined for CVImageBuffers. Luckily, when an image buffer is + // converted into an IOSurface, the keys are transformed but the values are + // the same. Since we are creating the IOSurface directly, we use hard-coded + // keys derived from inspecting the extracted IOSurfaces in the copying case, + // but we use the API-defined values from CVImageBuffer. + if (aColorSpace == YUVColorSpace::BT601) { + IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceYCbCrMatrix"), + kCVImageBufferYCbCrMatrix_ITU_R_601_4); + } else if (aColorSpace == YUVColorSpace::BT709) { + IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceYCbCrMatrix"), + kCVImageBufferYCbCrMatrix_ITU_R_709_2); + IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceColorPrimaries"), + kCVImageBufferColorPrimaries_ITU_R_709_2); + } else { + IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceYCbCrMatrix"), + kCVImageBufferYCbCrMatrix_ITU_R_2020); + IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceColorPrimaries"), + kCVImageBufferColorPrimaries_ITU_R_2020); + } + + // Transfer function is applied independently from the colorSpace. + IOSurfaceSetValue( + surfaceRef.get(), CFSTR("IOSurfaceTransferFunction"), + gfxMacUtils::CFStringForTransferFunction(aTransferFunction)); + + // Override the color space to be the same as the main display, so that + // CoreAnimation won't try to do any color correction (from the IOSurface + // space, to the display). In the future we may want to try specifying this + // correctly, but probably only once we do the same for videos drawn through + // our gfx code. + auto colorSpace = CFTypeRefPtr<CGColorSpaceRef>::WrapUnderCreateRule( + CGDisplayCopyColorSpace(CGMainDisplayID())); + auto colorData = CFTypeRefPtr<CFDataRef>::WrapUnderCreateRule( + CGColorSpaceCopyICCData(colorSpace.get())); + IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceColorSpace"), + colorData.get()); + + RefPtr<MacIOSurface> ioSurface = + new MacIOSurface(std::move(surfaceRef), false, aColorSpace); + + return ioSurface.forget(); +} + +/* static */ +already_AddRefed<MacIOSurface> MacIOSurface::CreateYUV422Surface( + const IntSize& aSize, YUVColorSpace aColorSpace, ColorRange aColorRange) { + MOZ_ASSERT(aColorSpace == YUVColorSpace::BT601 || + aColorSpace == YUVColorSpace::BT709); + MOZ_ASSERT(aColorRange == ColorRange::LIMITED || + aColorRange == ColorRange::FULL); + + auto props = CFTypeRefPtr<CFMutableDictionaryRef>::WrapUnderCreateRule( + ::CFDictionaryCreateMutable(kCFAllocatorDefault, 4, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + if (!props) return nullptr; + + MOZ_ASSERT((size_t)aSize.width <= GetMaxWidth()); + MOZ_ASSERT((size_t)aSize.height <= GetMaxHeight()); + + SetSizeProperties(props, aSize.width, aSize.height, 2); + + if (aColorRange == ColorRange::LIMITED) { + AddDictionaryInt(props, kIOSurfacePixelFormat, + (uint32_t)kCVPixelFormatType_422YpCbCr8_yuvs); + } else { + AddDictionaryInt(props, kIOSurfacePixelFormat, + (uint32_t)kCVPixelFormatType_422YpCbCr8FullRange); + } + + CFTypeRefPtr<IOSurfaceRef> surfaceRef = + CFTypeRefPtr<IOSurfaceRef>::WrapUnderCreateRule( + ::IOSurfaceCreate(props.get())); + + if (!surfaceRef) { + return nullptr; + } + + // Setup the correct YCbCr conversion matrix on the IOSurface, in case we pass + // this directly to CoreAnimation. + if (aColorSpace == YUVColorSpace::BT601) { + IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceYCbCrMatrix"), + CFSTR("ITU_R_601_4")); + } else { + IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceYCbCrMatrix"), + CFSTR("ITU_R_709_2")); + } + // Override the color space to be the same as the main display, so that + // CoreAnimation won't try to do any color correction (from the IOSurface + // space, to the display). In the future we may want to try specifying this + // correctly, but probably only once we do the same for videos drawn through + // our gfx code. + auto colorSpace = CFTypeRefPtr<CGColorSpaceRef>::WrapUnderCreateRule( + CGDisplayCopyColorSpace(CGMainDisplayID())); + auto colorData = CFTypeRefPtr<CFDataRef>::WrapUnderCreateRule( + CGColorSpaceCopyICCData(colorSpace.get())); + IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceColorSpace"), + colorData.get()); + + RefPtr<MacIOSurface> ioSurface = + new MacIOSurface(std::move(surfaceRef), false, aColorSpace); + + return ioSurface.forget(); +} + +/* static */ +already_AddRefed<MacIOSurface> MacIOSurface::LookupSurface( + IOSurfaceID aIOSurfaceID, bool aHasAlpha, gfx::YUVColorSpace aColorSpace) { + CFTypeRefPtr<IOSurfaceRef> surfaceRef = + CFTypeRefPtr<IOSurfaceRef>::WrapUnderCreateRule( + ::IOSurfaceLookup(aIOSurfaceID)); + if (!surfaceRef) return nullptr; + + RefPtr<MacIOSurface> ioSurface = + new MacIOSurface(std::move(surfaceRef), aHasAlpha, aColorSpace); + + return ioSurface.forget(); +} + +IOSurfaceID MacIOSurface::GetIOSurfaceID() const { + return ::IOSurfaceGetID(mIOSurfaceRef.get()); +} + +void* MacIOSurface::GetBaseAddress() const { + return ::IOSurfaceGetBaseAddress(mIOSurfaceRef.get()); +} + +void* MacIOSurface::GetBaseAddressOfPlane(size_t aPlaneIndex) const { + return ::IOSurfaceGetBaseAddressOfPlane(mIOSurfaceRef.get(), aPlaneIndex); +} + +size_t MacIOSurface::GetWidth(size_t plane) const { + return GetDevicePixelWidth(plane); +} + +size_t MacIOSurface::GetHeight(size_t plane) const { + return GetDevicePixelHeight(plane); +} + +size_t MacIOSurface::GetPlaneCount() const { + return ::IOSurfaceGetPlaneCount(mIOSurfaceRef.get()); +} + +/*static*/ +size_t MacIOSurface::GetMaxWidth() { + return ::IOSurfaceGetPropertyMaximum(kIOSurfaceWidth); +} + +/*static*/ +size_t MacIOSurface::GetMaxHeight() { + return ::IOSurfaceGetPropertyMaximum(kIOSurfaceHeight); +} + +size_t MacIOSurface::GetDevicePixelWidth(size_t plane) const { + return ::IOSurfaceGetWidthOfPlane(mIOSurfaceRef.get(), plane); +} + +size_t MacIOSurface::GetDevicePixelHeight(size_t plane) const { + return ::IOSurfaceGetHeightOfPlane(mIOSurfaceRef.get(), plane); +} + +size_t MacIOSurface::GetBytesPerRow(size_t plane) const { + return ::IOSurfaceGetBytesPerRowOfPlane(mIOSurfaceRef.get(), plane); +} + +size_t MacIOSurface::GetAllocSize() const { + return ::IOSurfaceGetAllocSize(mIOSurfaceRef.get()); +} + +OSType MacIOSurface::GetPixelFormat() const { + return ::IOSurfaceGetPixelFormat(mIOSurfaceRef.get()); +} + +void MacIOSurface::IncrementUseCount() { + ::IOSurfaceIncrementUseCount(mIOSurfaceRef.get()); +} + +void MacIOSurface::DecrementUseCount() { + ::IOSurfaceDecrementUseCount(mIOSurfaceRef.get()); +} + +void MacIOSurface::Lock(bool aReadOnly) { + MOZ_RELEASE_ASSERT(!mIsLocked, "double MacIOSurface lock"); + ::IOSurfaceLock(mIOSurfaceRef.get(), aReadOnly ? kIOSurfaceLockReadOnly : 0, + nullptr); + mIsLocked = true; +} + +void MacIOSurface::Unlock(bool aReadOnly) { + MOZ_RELEASE_ASSERT(mIsLocked, "MacIOSurface unlock without being locked"); + ::IOSurfaceUnlock(mIOSurfaceRef.get(), aReadOnly ? kIOSurfaceLockReadOnly : 0, + nullptr); + mIsLocked = false; +} + +using mozilla::gfx::ColorDepth; +using mozilla::gfx::IntSize; +using mozilla::gfx::SourceSurface; +using mozilla::gfx::SurfaceFormat; + +static void MacIOSurfaceBufferDeallocator(void* aClosure) { + MOZ_ASSERT(aClosure); + + delete[] static_cast<unsigned char*>(aClosure); +} + +already_AddRefed<SourceSurface> MacIOSurface::GetAsSurface() { + Lock(); + size_t bytesPerRow = GetBytesPerRow(); + size_t ioWidth = GetDevicePixelWidth(); + size_t ioHeight = GetDevicePixelHeight(); + + unsigned char* ioData = (unsigned char*)GetBaseAddress(); + auto* dataCpy = + new unsigned char[bytesPerRow * ioHeight / sizeof(unsigned char)]; + for (size_t i = 0; i < ioHeight; i++) { + memcpy(dataCpy + i * bytesPerRow, ioData + i * bytesPerRow, ioWidth * 4); + } + + Unlock(); + + SurfaceFormat format = HasAlpha() ? mozilla::gfx::SurfaceFormat::B8G8R8A8 + : mozilla::gfx::SurfaceFormat::B8G8R8X8; + + RefPtr<mozilla::gfx::DataSourceSurface> surf = + mozilla::gfx::Factory::CreateWrappingDataSourceSurface( + dataCpy, bytesPerRow, IntSize(ioWidth, ioHeight), format, + &MacIOSurfaceBufferDeallocator, static_cast<void*>(dataCpy)); + + return surf.forget(); +} + +already_AddRefed<mozilla::gfx::DrawTarget> MacIOSurface::GetAsDrawTargetLocked( + mozilla::gfx::BackendType aBackendType) { + MOZ_RELEASE_ASSERT( + IsLocked(), + "Only call GetAsDrawTargetLocked while the surface is locked."); + + size_t bytesPerRow = GetBytesPerRow(); + size_t ioWidth = GetDevicePixelWidth(); + size_t ioHeight = GetDevicePixelHeight(); + unsigned char* ioData = (unsigned char*)GetBaseAddress(); + SurfaceFormat format = GetFormat(); + return mozilla::gfx::Factory::CreateDrawTargetForData( + aBackendType, ioData, IntSize(ioWidth, ioHeight), bytesPerRow, format); +} + +SurfaceFormat MacIOSurface::GetFormat() const { + switch (GetPixelFormat()) { + case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: + case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange: + return SurfaceFormat::NV12; + case kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange: + case kCVPixelFormatType_420YpCbCr10BiPlanarFullRange: + return SurfaceFormat::P010; + case kCVPixelFormatType_422YpCbCr8_yuvs: + case kCVPixelFormatType_422YpCbCr8FullRange: + return SurfaceFormat::YUV422; + case kCVPixelFormatType_32BGRA: + return HasAlpha() ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8; + default: + MOZ_ASSERT_UNREACHABLE("Unknown format"); + return SurfaceFormat::B8G8R8A8; + } +} + +SurfaceFormat MacIOSurface::GetReadFormat() const { + SurfaceFormat format = GetFormat(); + if (format == SurfaceFormat::YUV422) { + return SurfaceFormat::R8G8B8X8; + } + return format; +} + +ColorDepth MacIOSurface::GetColorDepth() const { + switch (GetPixelFormat()) { + case kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange: + case kCVPixelFormatType_420YpCbCr10BiPlanarFullRange: + return ColorDepth::COLOR_10; + default: + return ColorDepth::COLOR_8; + } +} + +CGLError MacIOSurface::CGLTexImageIOSurface2D(CGLContextObj ctx, GLenum target, + GLenum internalFormat, + GLsizei width, GLsizei height, + GLenum format, GLenum type, + GLuint plane) const { + return ::CGLTexImageIOSurface2D(ctx, target, internalFormat, width, height, + format, type, mIOSurfaceRef.get(), plane); +} + +CGLError MacIOSurface::CGLTexImageIOSurface2D( + mozilla::gl::GLContext* aGL, CGLContextObj ctx, size_t plane, + mozilla::gfx::SurfaceFormat* aOutReadFormat) { + MOZ_ASSERT(plane >= 0); + bool isCompatibilityProfile = aGL->IsCompatibilityProfile(); + OSType pixelFormat = GetPixelFormat(); + + GLenum internalFormat; + GLenum format; + GLenum type; + if (pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange || + pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) { + MOZ_ASSERT(GetPlaneCount() == 2); + MOZ_ASSERT(plane < 2); + + // The LOCAL_GL_LUMINANCE and LOCAL_GL_LUMINANCE_ALPHA are the deprecated + // format. So, use LOCAL_GL_RED and LOCAL_GL_RB if we use core profile. + // https://www.khronos.org/opengl/wiki/Image_Format#Legacy_Image_Formats + if (plane == 0) { + internalFormat = format = + (isCompatibilityProfile) ? (LOCAL_GL_LUMINANCE) : (LOCAL_GL_RED); + } else { + internalFormat = format = + (isCompatibilityProfile) ? (LOCAL_GL_LUMINANCE_ALPHA) : (LOCAL_GL_RG); + } + type = LOCAL_GL_UNSIGNED_BYTE; + if (aOutReadFormat) { + *aOutReadFormat = mozilla::gfx::SurfaceFormat::NV12; + } + } else if (pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange || + pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarFullRange) { + MOZ_ASSERT(GetPlaneCount() == 2); + MOZ_ASSERT(plane < 2); + + // The LOCAL_GL_LUMINANCE and LOCAL_GL_LUMINANCE_ALPHA are the deprecated + // format. So, use LOCAL_GL_RED and LOCAL_GL_RB if we use core profile. + // https://www.khronos.org/opengl/wiki/Image_Format#Legacy_Image_Formats + if (plane == 0) { + internalFormat = format = + (isCompatibilityProfile) ? (LOCAL_GL_LUMINANCE) : (LOCAL_GL_RED); + } else { + internalFormat = format = + (isCompatibilityProfile) ? (LOCAL_GL_LUMINANCE_ALPHA) : (LOCAL_GL_RG); + } + type = LOCAL_GL_UNSIGNED_SHORT; + if (aOutReadFormat) { + *aOutReadFormat = mozilla::gfx::SurfaceFormat::P010; + } + } else if (pixelFormat == kCVPixelFormatType_422YpCbCr8_yuvs || + pixelFormat == kCVPixelFormatType_422YpCbCr8FullRange) { + MOZ_ASSERT(plane == 0); + // The YCBCR_422_APPLE ext is only available in compatibility profile. So, + // we should use RGB_422_APPLE for core profile. The difference between + // YCBCR_422_APPLE and RGB_422_APPLE is that the YCBCR_422_APPLE converts + // the YCbCr value to RGB with REC 601 conversion. But the RGB_422_APPLE + // doesn't contain color conversion. You should do the color conversion by + // yourself for RGB_422_APPLE. + // + // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_ycbcr_422.txt + // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_rgb_422.txt + if (isCompatibilityProfile) { + format = LOCAL_GL_YCBCR_422_APPLE; + if (aOutReadFormat) { + *aOutReadFormat = mozilla::gfx::SurfaceFormat::R8G8B8X8; + } + } else { + format = LOCAL_GL_RGB_422_APPLE; + if (aOutReadFormat) { + *aOutReadFormat = mozilla::gfx::SurfaceFormat::YUV422; + } + } + internalFormat = LOCAL_GL_RGB; + type = LOCAL_GL_UNSIGNED_SHORT_8_8_REV_APPLE; + } else { + MOZ_ASSERT(plane == 0); + + internalFormat = HasAlpha() ? LOCAL_GL_RGBA : LOCAL_GL_RGB; + format = LOCAL_GL_BGRA; + type = LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV; + if (aOutReadFormat) { + *aOutReadFormat = HasAlpha() ? mozilla::gfx::SurfaceFormat::R8G8B8A8 + : mozilla::gfx::SurfaceFormat::R8G8B8X8; + } + } + + auto err = + CGLTexImageIOSurface2D(ctx, LOCAL_GL_TEXTURE_RECTANGLE_ARB, + internalFormat, GetDevicePixelWidth(plane), + GetDevicePixelHeight(plane), format, type, plane); + if (err) { + const auto formatChars = (const char*)&pixelFormat; + const char formatStr[] = {formatChars[3], formatChars[2], formatChars[1], + formatChars[0], 0}; + const nsPrintfCString errStr( + "CGLTexImageIOSurface2D(context, target, 0x%04x," + " %u, %u, 0x%04x, 0x%04x, iosurfPtr, %u) -> %i", + internalFormat, uint32_t(GetDevicePixelWidth(plane)), + uint32_t(GetDevicePixelHeight(plane)), format, type, + (unsigned int)plane, err); + gfxCriticalError() << errStr.get() << " (iosurf format: " << formatStr + << ")"; + } + return err; +} + +void MacIOSurface::SetColorSpace(const mozilla::gfx::ColorSpace2 cs) const { + Maybe<CFStringRef> str; + switch (cs) { + case gfx::ColorSpace2::UNKNOWN: + break; + case gfx::ColorSpace2::SRGB: + str = Some(kCGColorSpaceSRGB); + break; + case gfx::ColorSpace2::DISPLAY_P3: + str = Some(kCGColorSpaceDisplayP3); + break; + case gfx::ColorSpace2::BT601_525: // Doesn't really have a better option. + case gfx::ColorSpace2::BT709: + str = Some(kCGColorSpaceITUR_709); + break; + case gfx::ColorSpace2::BT2020: + str = Some(kCGColorSpaceITUR_2020); + break; + } + if (str) { + IOSurfaceSetValue(mIOSurfaceRef.get(), CFSTR("IOSurfaceColorSpace"), *str); + } +} diff --git a/gfx/2d/MacIOSurface.h b/gfx/2d/MacIOSurface.h new file mode 100644 index 0000000000..ef176430d1 --- /dev/null +++ b/gfx/2d/MacIOSurface.h @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MacIOSurface_h__ +#define MacIOSurface_h__ +#ifdef XP_DARWIN +# include <CoreVideo/CoreVideo.h> +# include <IOSurface/IOSurface.h> +# include <QuartzCore/QuartzCore.h> +# include <dlfcn.h> + +# include "mozilla/gfx/Types.h" +# include "CFTypeRefPtr.h" + +namespace mozilla { +namespace gl { +class GLContext; +} +} // namespace mozilla + +struct _CGLContextObject; + +typedef _CGLContextObject* CGLContextObj; +typedef uint32_t IOSurfaceID; + +# ifdef XP_IOS +typedef kern_return_t IOReturn; +typedef int CGLError; +# endif + +# ifdef XP_MACOSX +# import <OpenGL/OpenGL.h> +# else +# import <OpenGLES/ES2/gl.h> +# endif + +# include "2D.h" +# include "mozilla/RefCounted.h" +# include "mozilla/RefPtr.h" + +class MacIOSurface final + : public mozilla::external::AtomicRefCounted<MacIOSurface> { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(MacIOSurface) + typedef mozilla::gfx::SourceSurface SourceSurface; + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::BackendType BackendType; + typedef mozilla::gfx::IntSize IntSize; + typedef mozilla::gfx::YUVColorSpace YUVColorSpace; + typedef mozilla::gfx::ColorSpace2 ColorSpace2; + typedef mozilla::gfx::TransferFunction TransferFunction; + typedef mozilla::gfx::ColorRange ColorRange; + typedef mozilla::gfx::ColorDepth ColorDepth; + + // The usage count of the IOSurface is increased by 1 during the lifetime + // of the MacIOSurface instance. + // MacIOSurface holds a reference to the corresponding IOSurface. + + static already_AddRefed<MacIOSurface> CreateIOSurface(int aWidth, int aHeight, + bool aHasAlpha = true); + static already_AddRefed<MacIOSurface> CreateNV12OrP010Surface( + const IntSize& aYSize, const IntSize& aCbCrSize, + YUVColorSpace aColorSpace, TransferFunction aTransferFunction, + ColorRange aColorRange, ColorDepth aColorDepth); + static already_AddRefed<MacIOSurface> CreateYUV422Surface( + const IntSize& aSize, YUVColorSpace aColorSpace, ColorRange aColorRange); + static void ReleaseIOSurface(MacIOSurface* aIOSurface); + static already_AddRefed<MacIOSurface> LookupSurface( + IOSurfaceID aSurfaceID, bool aHasAlpha = true, + mozilla::gfx::YUVColorSpace aColorSpace = + mozilla::gfx::YUVColorSpace::Identity); + + explicit MacIOSurface(CFTypeRefPtr<IOSurfaceRef> aIOSurfaceRef, + bool aHasAlpha = true, + mozilla::gfx::YUVColorSpace aColorSpace = + mozilla::gfx::YUVColorSpace::Identity); + + ~MacIOSurface(); + IOSurfaceID GetIOSurfaceID() const; + void* GetBaseAddress() const; + void* GetBaseAddressOfPlane(size_t planeIndex) const; + size_t GetPlaneCount() const; + OSType GetPixelFormat() const; + // GetWidth() and GetHeight() return values in "display pixels". A + // "display pixel" is the smallest fully addressable part of a display. + // But in HiDPI modes each "display pixel" corresponds to more than one + // device pixel. Use GetDevicePixel**() to get device pixels. + size_t GetWidth(size_t plane = 0) const; + size_t GetHeight(size_t plane = 0) const; + IntSize GetSize(size_t plane = 0) const { + return IntSize(GetWidth(plane), GetHeight(plane)); + } + size_t GetDevicePixelWidth(size_t plane = 0) const; + size_t GetDevicePixelHeight(size_t plane = 0) const; + size_t GetBytesPerRow(size_t plane = 0) const; + size_t GetAllocSize() const; + void Lock(bool aReadOnly = true); + void Unlock(bool aReadOnly = true); + bool IsLocked() const { return mIsLocked; } + void IncrementUseCount(); + void DecrementUseCount(); + bool HasAlpha() const { return mHasAlpha; } + mozilla::gfx::SurfaceFormat GetFormat() const; + mozilla::gfx::SurfaceFormat GetReadFormat() const; + mozilla::gfx::ColorDepth GetColorDepth() const; + // This would be better suited on MacIOSurfaceImage type, however due to the + // current data structure, this is not possible as only the IOSurfaceRef is + // being used across. + void SetYUVColorSpace(YUVColorSpace aColorSpace) { + mColorSpace = aColorSpace; + } + YUVColorSpace GetYUVColorSpace() const { return mColorSpace; } + bool IsFullRange() const { + OSType format = GetPixelFormat(); + return (format == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange || + format == kCVPixelFormatType_420YpCbCr10BiPlanarFullRange); + } + mozilla::gfx::ColorRange GetColorRange() const { + if (IsFullRange()) return mozilla::gfx::ColorRange::FULL; + return mozilla::gfx::ColorRange::LIMITED; + } + + // We would like to forward declare NSOpenGLContext, but it is an @interface + // and this file is also used from c++, so we use a void *. + CGLError CGLTexImageIOSurface2D( + mozilla::gl::GLContext* aGL, CGLContextObj ctxt, size_t plane, + mozilla::gfx::SurfaceFormat* aOutReadFormat = nullptr); + CGLError CGLTexImageIOSurface2D(CGLContextObj ctxt, GLenum target, + GLenum internalFormat, GLsizei width, + GLsizei height, GLenum format, GLenum type, + GLuint plane) const; + already_AddRefed<SourceSurface> GetAsSurface(); + + // Creates a DrawTarget that wraps the data in the IOSurface. Rendering to + // this DrawTarget directly manipulates the contents of the IOSurface. + // Only call when the surface is already locked for writing! + // The returned DrawTarget must only be used while the surface is still + // locked. + // Also, only call this if you're reasonably sure that the DrawTarget of the + // selected backend supports the IOSurface's SurfaceFormat. + already_AddRefed<DrawTarget> GetAsDrawTargetLocked(BackendType aBackendType); + + static size_t GetMaxWidth(); + static size_t GetMaxHeight(); + CFTypeRefPtr<IOSurfaceRef> GetIOSurfaceRef() { return mIOSurfaceRef; } + + void SetColorSpace(mozilla::gfx::ColorSpace2) const; + + ColorSpace2 mColorPrimaries = ColorSpace2::UNKNOWN; + + private: + CFTypeRefPtr<IOSurfaceRef> mIOSurfaceRef; + const bool mHasAlpha; + YUVColorSpace mColorSpace = YUVColorSpace::Identity; + bool mIsLocked = false; +}; + +#endif +#endif diff --git a/gfx/2d/Matrix.cpp b/gfx/2d/Matrix.cpp new file mode 100644 index 0000000000..cb8830c168 --- /dev/null +++ b/gfx/2d/Matrix.cpp @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Matrix.h" +#include "Quaternion.h" +#include "Tools.h" +#include <algorithm> +#include <ostream> +#include <math.h> +#include <float.h> // for FLT_EPSILON + +#include "mozilla/FloatingPoint.h" // for UnspecifiedNaN + +namespace mozilla { +namespace gfx { + +/* Force small values to zero. We do this to avoid having sin(360deg) + * evaluate to a tiny but nonzero value. + */ +double FlushToZero(double aVal) { + // XXX Is double precision really necessary here + if (-FLT_EPSILON < aVal && aVal < FLT_EPSILON) { + return 0.0f; + } else { + return aVal; + } +} + +/* Computes tan(aTheta). For values of aTheta such that tan(aTheta) is + * undefined or very large, SafeTangent returns a manageably large value + * of the correct sign. + */ +double SafeTangent(double aTheta) { + // XXX Is double precision really necessary here + const double kEpsilon = 0.0001; + + /* tan(theta) = sin(theta)/cos(theta); problems arise when + * cos(theta) is too close to zero. Limit cos(theta) to the + * range [-1, -epsilon] U [epsilon, 1]. + */ + + double sinTheta = sin(aTheta); + double cosTheta = cos(aTheta); + + if (cosTheta >= 0 && cosTheta < kEpsilon) { + cosTheta = kEpsilon; + } else if (cosTheta < 0 && cosTheta >= -kEpsilon) { + cosTheta = -kEpsilon; + } + return FlushToZero(sinTheta / cosTheta); +} + +template <> +Matrix Matrix::Rotation(Float aAngle) { + Matrix newMatrix; + + Float s = sinf(aAngle); + Float c = cosf(aAngle); + + newMatrix._11 = c; + newMatrix._12 = s; + newMatrix._21 = -s; + newMatrix._22 = c; + + return newMatrix; +} + +template <> +MatrixDouble MatrixDouble::Rotation(Double aAngle) { + MatrixDouble newMatrix; + + Double s = sin(aAngle); + Double c = cos(aAngle); + + newMatrix._11 = c; + newMatrix._12 = s; + newMatrix._21 = -s; + newMatrix._22 = c; + + return newMatrix; +} + +template <> +Matrix4x4 MatrixDouble::operator*(const Matrix4x4& aMatrix) const { + Matrix4x4 resultMatrix; + + resultMatrix._11 = this->_11 * aMatrix._11 + this->_12 * aMatrix._21; + resultMatrix._12 = this->_11 * aMatrix._12 + this->_12 * aMatrix._22; + resultMatrix._13 = this->_11 * aMatrix._13 + this->_12 * aMatrix._23; + resultMatrix._14 = this->_11 * aMatrix._14 + this->_12 * aMatrix._24; + + resultMatrix._21 = this->_21 * aMatrix._11 + this->_22 * aMatrix._21; + resultMatrix._22 = this->_21 * aMatrix._12 + this->_22 * aMatrix._22; + resultMatrix._23 = this->_21 * aMatrix._13 + this->_22 * aMatrix._23; + resultMatrix._24 = this->_21 * aMatrix._14 + this->_22 * aMatrix._24; + + resultMatrix._31 = aMatrix._31; + resultMatrix._32 = aMatrix._32; + resultMatrix._33 = aMatrix._33; + resultMatrix._34 = aMatrix._34; + + resultMatrix._41 = + this->_31 * aMatrix._11 + this->_32 * aMatrix._21 + aMatrix._41; + resultMatrix._42 = + this->_31 * aMatrix._12 + this->_32 * aMatrix._22 + aMatrix._42; + resultMatrix._43 = + this->_31 * aMatrix._13 + this->_32 * aMatrix._23 + aMatrix._43; + resultMatrix._44 = + this->_31 * aMatrix._14 + this->_32 * aMatrix._24 + aMatrix._44; + + return resultMatrix; +} + +// Intersect the polygon given by aPoints with the half space induced by +// aPlaneNormal and return the resulting polygon. The returned points are +// stored in aDestBuffer, and its meaningful subspan is returned. +template <typename F> +Span<Point4DTyped<UnknownUnits, F>> IntersectPolygon( + Span<Point4DTyped<UnknownUnits, F>> aPoints, + const Point4DTyped<UnknownUnits, F>& aPlaneNormal, + Span<Point4DTyped<UnknownUnits, F>> aDestBuffer) { + if (aPoints.Length() < 1 || aDestBuffer.Length() < 1) { + return {}; + } + + size_t nextIndex = 0; // aDestBuffer[nextIndex] is the next emitted point. + + // Iterate over the polygon edges. In each iteration the current edge + // is the edge from *prevPoint to point. If the two end points lie on + // different sides of the plane, we have an intersection. Otherwise, + // the edge is either completely "inside" the half-space created by + // the clipping plane, and we add curPoint, or it is completely + // "outside", and we discard curPoint. This loop can create duplicated + // points in the polygon. + const auto* prevPoint = &aPoints[aPoints.Length() - 1]; + F prevDot = aPlaneNormal.DotProduct(*prevPoint); + for (const auto& curPoint : aPoints) { + F curDot = aPlaneNormal.DotProduct(curPoint); + + if ((curDot >= 0.0) != (prevDot >= 0.0)) { + // An intersection with the clipping plane has been detected. + // Interpolate to find the intersecting curPoint and emit it. + F t = -prevDot / (curDot - prevDot); + aDestBuffer[nextIndex++] = curPoint * t + *prevPoint * (1.0 - t); + if (nextIndex >= aDestBuffer.Length()) { + break; + } + } + + if (curDot >= 0.0) { + // Emit any source points that are on the positive side of the + // clipping plane. + aDestBuffer[nextIndex++] = curPoint; + if (nextIndex >= aDestBuffer.Length()) { + break; + } + } + + prevPoint = &curPoint; + prevDot = curDot; + } + + return aDestBuffer.To(nextIndex); +} + +template Span<Point4DTyped<UnknownUnits, Float>> IntersectPolygon( + Span<Point4DTyped<UnknownUnits, Float>> aPoints, + const Point4DTyped<UnknownUnits, Float>& aPlaneNormal, + Span<Point4DTyped<UnknownUnits, Float>> aDestBuffer); +template Span<Point4DTyped<UnknownUnits, Double>> IntersectPolygon( + Span<Point4DTyped<UnknownUnits, Double>> aPoints, + const Point4DTyped<UnknownUnits, Double>& aPlaneNormal, + Span<Point4DTyped<UnknownUnits, Double>> aDestBuffer); + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/Matrix.h b/gfx/2d/Matrix.h new file mode 100644 index 0000000000..85de653a54 --- /dev/null +++ b/gfx/2d/Matrix.h @@ -0,0 +1,2374 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_MATRIX_H_ +#define MOZILLA_GFX_MATRIX_H_ + +#include "Types.h" +#include "Triangle.h" +#include "Rect.h" +#include "Point.h" +#include "Quaternion.h" +#include <iosfwd> +#include <math.h> +#include "mozilla/Attributes.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/gfx/ScaleFactors2D.h" +#include "mozilla/Span.h" + +namespace mozilla { +namespace gfx { + +static inline bool FuzzyEqual(Float aV1, Float aV2) { + // XXX - Check if fabs does the smart thing and just negates the sign bit. + return fabs(aV2 - aV1) < 1e-6; +} + +template <typename F> +Span<Point4DTyped<UnknownUnits, F>> IntersectPolygon( + Span<Point4DTyped<UnknownUnits, F>> aPoints, + const Point4DTyped<UnknownUnits, F>& aPlaneNormal, + Span<Point4DTyped<UnknownUnits, F>> aDestBuffer); + +template <class T> +using BaseMatrixScales = BaseScaleFactors2D<UnknownUnits, UnknownUnits, T>; + +using MatrixScales = BaseMatrixScales<float>; +using MatrixScalesDouble = BaseMatrixScales<double>; + +template <class T> +class BaseMatrix { + // Alias that maps to either Point or PointDouble depending on whether T is a + // float or a double. + typedef PointTyped<UnknownUnits, T> MatrixPoint; + // Same for size and rect + typedef SizeTyped<UnknownUnits, T> MatrixSize; + typedef RectTyped<UnknownUnits, T> MatrixRect; + + public: + BaseMatrix() : _11(1.0f), _12(0), _21(0), _22(1.0f), _31(0), _32(0) {} + BaseMatrix(T a11, T a12, T a21, T a22, T a31, T a32) + : _11(a11), _12(a12), _21(a21), _22(a22), _31(a31), _32(a32) {} + union { + struct { + T _11, _12; + T _21, _22; + T _31, _32; + }; + T components[6]; + }; + + template <class T2> + explicit BaseMatrix(const BaseMatrix<T2>& aOther) + : _11(aOther._11), + _12(aOther._12), + _21(aOther._21), + _22(aOther._22), + _31(aOther._31), + _32(aOther._32) {} + + MOZ_ALWAYS_INLINE BaseMatrix Copy() const { return BaseMatrix<T>(*this); } + + friend std::ostream& operator<<(std::ostream& aStream, + const BaseMatrix& aMatrix) { + if (aMatrix.IsIdentity()) { + return aStream << "[ I ]"; + } + return aStream << "[ " << aMatrix._11 << " " << aMatrix._12 << "; " + << aMatrix._21 << " " << aMatrix._22 << "; " << aMatrix._31 + << " " << aMatrix._32 << "; ]"; + } + + MatrixPoint TransformPoint(const MatrixPoint& aPoint) const { + MatrixPoint retPoint; + + retPoint.x = aPoint.x * _11 + aPoint.y * _21 + _31; + retPoint.y = aPoint.x * _12 + aPoint.y * _22 + _32; + + return retPoint; + } + + MatrixSize TransformSize(const MatrixSize& aSize) const { + MatrixSize retSize; + + retSize.width = aSize.width * _11 + aSize.height * _21; + retSize.height = aSize.width * _12 + aSize.height * _22; + + return retSize; + } + + /** + * In most cases you probably want to use TransformBounds. This function + * just transforms the top-left and size separately and constructs a rect + * from those results. + */ + MatrixRect TransformRect(const MatrixRect& aRect) const { + return MatrixRect(TransformPoint(aRect.TopLeft()), + TransformSize(aRect.Size())); + } + + GFX2D_API MatrixRect TransformBounds(const MatrixRect& aRect) const { + int i; + MatrixPoint quad[4]; + T min_x, max_x; + T min_y, max_y; + + quad[0] = TransformPoint(aRect.TopLeft()); + quad[1] = TransformPoint(aRect.TopRight()); + quad[2] = TransformPoint(aRect.BottomLeft()); + quad[3] = TransformPoint(aRect.BottomRight()); + + min_x = max_x = quad[0].x; + min_y = max_y = quad[0].y; + + for (i = 1; i < 4; i++) { + if (quad[i].x < min_x) min_x = quad[i].x; + if (quad[i].x > max_x) max_x = quad[i].x; + + if (quad[i].y < min_y) min_y = quad[i].y; + if (quad[i].y > max_y) max_y = quad[i].y; + } + + return MatrixRect(min_x, min_y, max_x - min_x, max_y - min_y); + } + + static BaseMatrix<T> Translation(T aX, T aY) { + return BaseMatrix<T>(1.0f, 0.0f, 0.0f, 1.0f, aX, aY); + } + + static BaseMatrix<T> Translation(MatrixPoint aPoint) { + return Translation(aPoint.x, aPoint.y); + } + + /** + * Apply a translation to this matrix. + * + * The "Pre" in this method's name means that the translation is applied + * -before- this matrix's existing transformation. That is, any vector that + * is multiplied by the resulting matrix will first be translated, then be + * transformed by the original transform. + * + * Calling this method will result in this matrix having the same value as + * the result of: + * + * BaseMatrix<T>::Translation(x, y) * this + * + * (Note that in performance critical code multiplying by the result of a + * Translation()/Scaling() call is not recommended since that results in a + * full matrix multiply involving 12 floating-point multiplications. Calling + * this method would be preferred since it only involves four floating-point + * multiplications.) + */ + BaseMatrix<T>& PreTranslate(T aX, T aY) { + _31 += _11 * aX + _21 * aY; + _32 += _12 * aX + _22 * aY; + + return *this; + } + + BaseMatrix<T>& PreTranslate(const MatrixPoint& aPoint) { + return PreTranslate(aPoint.x, aPoint.y); + } + + /** + * Similar to PreTranslate, but the translation is applied -after- this + * matrix's existing transformation instead of before it. + * + * This method is generally less used than PreTranslate since typically code + * want to adjust an existing user space to device space matrix to create a + * transform to device space from a -new- user space (translated from the + * previous user space). In that case consumers will need to use the Pre* + * variants of the matrix methods rather than using the Post* methods, since + * the Post* methods add a transform to the device space end of the + * transformation. + */ + BaseMatrix<T>& PostTranslate(T aX, T aY) { + _31 += aX; + _32 += aY; + return *this; + } + + BaseMatrix<T>& PostTranslate(const MatrixPoint& aPoint) { + return PostTranslate(aPoint.x, aPoint.y); + } + + static BaseMatrix<T> Scaling(T aScaleX, T aScaleY) { + return BaseMatrix<T>(aScaleX, 0.0f, 0.0f, aScaleY, 0.0f, 0.0f); + } + + static BaseMatrix<T> Scaling(const BaseMatrixScales<T>& scale) { + return Scaling(scale.xScale, scale.yScale); + } + + /** + * Similar to PreTranslate, but applies a scale instead of a translation. + */ + BaseMatrix<T>& PreScale(T aX, T aY) { + _11 *= aX; + _12 *= aX; + _21 *= aY; + _22 *= aY; + + return *this; + } + + BaseMatrix<T>& PreScale(const BaseMatrixScales<T>& scale) { + return PreScale(scale.xScale, scale.yScale); + } + + /** + * Similar to PostTranslate, but applies a scale instead of a translation. + */ + BaseMatrix<T>& PostScale(T aScaleX, T aScaleY) { + _11 *= aScaleX; + _12 *= aScaleY; + _21 *= aScaleX; + _22 *= aScaleY; + _31 *= aScaleX; + _32 *= aScaleY; + + return *this; + } + + GFX2D_API static BaseMatrix<T> Rotation(T aAngle); + + /** + * Similar to PreTranslate, but applies a rotation instead of a translation. + */ + BaseMatrix<T>& PreRotate(T aAngle) { + return *this = BaseMatrix<T>::Rotation(aAngle) * *this; + } + + bool Invert() { + // Compute co-factors. + T A = _22; + T B = -_21; + T C = _21 * _32 - _22 * _31; + T D = -_12; + T E = _11; + T F = _31 * _12 - _11 * _32; + + T det = Determinant(); + + if (!det) { + return false; + } + + T inv_det = 1 / det; + + _11 = inv_det * A; + _12 = inv_det * D; + _21 = inv_det * B; + _22 = inv_det * E; + _31 = inv_det * C; + _32 = inv_det * F; + + return true; + } + + BaseMatrix<T> Inverse() const { + BaseMatrix<T> clone = *this; + DebugOnly<bool> inverted = clone.Invert(); + MOZ_ASSERT(inverted, + "Attempted to get the inverse of a non-invertible matrix"); + return clone; + } + + T Determinant() const { return _11 * _22 - _12 * _21; } + + BaseMatrix<T> operator*(const BaseMatrix<T>& aMatrix) const { + BaseMatrix<T> resultMatrix; + + resultMatrix._11 = this->_11 * aMatrix._11 + this->_12 * aMatrix._21; + resultMatrix._12 = this->_11 * aMatrix._12 + this->_12 * aMatrix._22; + resultMatrix._21 = this->_21 * aMatrix._11 + this->_22 * aMatrix._21; + resultMatrix._22 = this->_21 * aMatrix._12 + this->_22 * aMatrix._22; + resultMatrix._31 = + this->_31 * aMatrix._11 + this->_32 * aMatrix._21 + aMatrix._31; + resultMatrix._32 = + this->_31 * aMatrix._12 + this->_32 * aMatrix._22 + aMatrix._32; + + return resultMatrix; + } + + BaseMatrix<T>& operator*=(const BaseMatrix<T>& aMatrix) { + *this = *this * aMatrix; + return *this; + } + + /** + * Multiplies *this with aMatrix and returns the result. + */ + Matrix4x4 operator*(const Matrix4x4& aMatrix) const; + + /** + * Multiplies in the opposite order to operator=*. + */ + BaseMatrix<T>& PreMultiply(const BaseMatrix<T>& aMatrix) { + *this = aMatrix * *this; + return *this; + } + + /** + * Please explicitly use either FuzzyEquals or ExactlyEquals. + */ + bool operator==(const BaseMatrix<T>& other) const = delete; + bool operator!=(const BaseMatrix<T>& other) const = delete; + + /* Returns true if the other matrix is fuzzy-equal to this matrix. + * Note that this isn't a cheap comparison! + */ + bool FuzzyEquals(const BaseMatrix<T>& o) const { + return FuzzyEqual(_11, o._11) && FuzzyEqual(_12, o._12) && + FuzzyEqual(_21, o._21) && FuzzyEqual(_22, o._22) && + FuzzyEqual(_31, o._31) && FuzzyEqual(_32, o._32); + } + + bool ExactlyEquals(const BaseMatrix<T>& o) const { + return _11 == o._11 && _12 == o._12 && _21 == o._21 && _22 == o._22 && + _31 == o._31 && _32 == o._32; + } + + /* Verifies that the matrix contains no Infs or NaNs. */ + bool IsFinite() const { + return std::isfinite(_11) && std::isfinite(_12) && std::isfinite(_21) && + std::isfinite(_22) && std::isfinite(_31) && std::isfinite(_32); + } + + /* Returns true if the matrix is a rectilinear transformation (i.e. + * grid-aligned rectangles are transformed to grid-aligned rectangles) + */ + bool IsRectilinear() const { + if (FuzzyEqual(_12, 0) && FuzzyEqual(_21, 0)) { + return true; + } else if (FuzzyEqual(_22, 0) && FuzzyEqual(_11, 0)) { + return true; + } + + return false; + } + + /** + * Returns true if the matrix is anything other than a straight + * translation by integers. + */ + bool HasNonIntegerTranslation() const { + return HasNonTranslation() || !FuzzyEqual(_31, floor(_31 + 0.5f)) || + !FuzzyEqual(_32, floor(_32 + 0.5f)); + } + + /** + * Returns true if the matrix only has an integer translation. + */ + bool HasOnlyIntegerTranslation() const { return !HasNonIntegerTranslation(); } + + /** + * Returns true if the matrix has any transform other + * than a straight translation. + */ + bool HasNonTranslation() const { + return !FuzzyEqual(_11, 1.0) || !FuzzyEqual(_22, 1.0) || + !FuzzyEqual(_12, 0.0) || !FuzzyEqual(_21, 0.0); + } + + /** + * Returns true if the matrix has any transform other + * than a translation or a -1 y scale (y axis flip) + */ + bool HasNonTranslationOrFlip() const { + return !FuzzyEqual(_11, 1.0) || + (!FuzzyEqual(_22, 1.0) && !FuzzyEqual(_22, -1.0)) || + !FuzzyEqual(_21, 0.0) || !FuzzyEqual(_12, 0.0); + } + + /* Returns true if the matrix is an identity matrix. + */ + bool IsIdentity() const { + return _11 == 1.0f && _12 == 0.0f && _21 == 0.0f && _22 == 1.0f && + _31 == 0.0f && _32 == 0.0f; + } + + /* Returns true if the matrix is singular. + */ + bool IsSingular() const { + T det = Determinant(); + return !std::isfinite(det) || det == 0; + } + + GFX2D_API BaseMatrix<T>& NudgeToIntegers() { + NudgeToInteger(&_11); + NudgeToInteger(&_12); + NudgeToInteger(&_21); + NudgeToInteger(&_22); + NudgeToInteger(&_31); + NudgeToInteger(&_32); + return *this; + } + + bool IsTranslation() const { + return FuzzyEqual(_11, 1.0f) && FuzzyEqual(_12, 0.0f) && + FuzzyEqual(_21, 0.0f) && FuzzyEqual(_22, 1.0f); + } + + static bool FuzzyIsInteger(T aValue) { + return FuzzyEqual(aValue, floorf(aValue + 0.5f)); + } + + bool IsIntegerTranslation() const { + return IsTranslation() && FuzzyIsInteger(_31) && FuzzyIsInteger(_32); + } + + bool IsAllIntegers() const { + return FuzzyIsInteger(_11) && FuzzyIsInteger(_12) && FuzzyIsInteger(_21) && + FuzzyIsInteger(_22) && FuzzyIsInteger(_31) && FuzzyIsInteger(_32); + } + + MatrixPoint GetTranslation() const { return MatrixPoint(_31, _32); } + + /** + * Returns true if matrix is multiple of 90 degrees rotation with flipping, + * scaling and translation. + */ + bool PreservesAxisAlignedRectangles() const { + return ((FuzzyEqual(_11, 0.0) && FuzzyEqual(_22, 0.0)) || + (FuzzyEqual(_12, 0.0) && FuzzyEqual(_21, 0.0))); + } + + /** + * Returns true if the matrix has any transform other + * than a translation or scale; this is, if there is + * rotation. + */ + bool HasNonAxisAlignedTransform() const { + return !FuzzyEqual(_21, 0.0) || !FuzzyEqual(_12, 0.0); + } + + /** + * Returns true if the matrix has negative scaling (i.e. flip). + */ + bool HasNegativeScaling() const { return (_11 < 0.0) || (_22 < 0.0); } + + /** + * Computes the scale factors of this matrix; that is, + * the amounts each basis vector is scaled by. + */ + BaseMatrixScales<T> ScaleFactors() const { + T det = Determinant(); + + if (det == 0.0) { + return BaseMatrixScales<T>(0.0, 0.0); + } + + MatrixSize sz = MatrixSize(1.0, 0.0); + sz = TransformSize(sz); + + T major = sqrt(sz.width * sz.width + sz.height * sz.height); + T minor = 0.0; + + // ignore mirroring + if (det < 0.0) { + det = -det; + } + + if (major) { + minor = det / major; + } + + return BaseMatrixScales<T>(major, minor); + } + + /** + * Returns true if the matrix preserves distances, i.e. a rigid transformation + * that doesn't change size or shape). Such a matrix has uniform unit scaling + * and an orthogonal basis. + */ + bool PreservesDistance() const { + return FuzzyEqual(_11 * _11 + _12 * _12, 1.0) && + FuzzyEqual(_21 * _21 + _22 * _22, 1.0) && + FuzzyEqual(_11 * _21 + _12 * _22, 0.0); + } +}; + +typedef BaseMatrix<Float> Matrix; +typedef BaseMatrix<Double> MatrixDouble; + +// Helper functions used by Matrix4x4Typed defined in Matrix.cpp +double SafeTangent(double aTheta); +double FlushToZero(double aVal); + +template <class Units, class F> +Point4DTyped<Units, F> ComputePerspectivePlaneIntercept( + const Point4DTyped<Units, F>& aFirst, + const Point4DTyped<Units, F>& aSecond) { + // This function will always return a point with a w value of 0. + // The X, Y, and Z components will point towards an infinite vanishing + // point. + + // We want to interpolate aFirst and aSecond to find the point intersecting + // with the w=0 plane. + + // Since we know what we want the w component to be, we can rearrange the + // interpolation equation and solve for t. + float t = -aFirst.w / (aSecond.w - aFirst.w); + + // Use t to find the remainder of the components + return aFirst + (aSecond - aFirst) * t; +} + +template <class SourceUnits, class TargetUnits, class T> +class Matrix4x4Typed { + public: + typedef PointTyped<SourceUnits, T> SourcePoint; + typedef PointTyped<TargetUnits, T> TargetPoint; + typedef Point3DTyped<SourceUnits, T> SourcePoint3D; + typedef Point3DTyped<TargetUnits, T> TargetPoint3D; + typedef Point4DTyped<SourceUnits, T> SourcePoint4D; + typedef Point4DTyped<TargetUnits, T> TargetPoint4D; + typedef RectTyped<SourceUnits, T> SourceRect; + typedef RectTyped<TargetUnits, T> TargetRect; + + Matrix4x4Typed() + : _11(1.0f), + _12(0.0f), + _13(0.0f), + _14(0.0f), + _21(0.0f), + _22(1.0f), + _23(0.0f), + _24(0.0f), + _31(0.0f), + _32(0.0f), + _33(1.0f), + _34(0.0f), + _41(0.0f), + _42(0.0f), + _43(0.0f), + _44(1.0f) {} + + Matrix4x4Typed(T a11, T a12, T a13, T a14, T a21, T a22, T a23, T a24, T a31, + T a32, T a33, T a34, T a41, T a42, T a43, T a44) + : _11(a11), + _12(a12), + _13(a13), + _14(a14), + _21(a21), + _22(a22), + _23(a23), + _24(a24), + _31(a31), + _32(a32), + _33(a33), + _34(a34), + _41(a41), + _42(a42), + _43(a43), + _44(a44) {} + + explicit Matrix4x4Typed(const T aArray[16]) { + memcpy(components, aArray, sizeof(components)); + } + + Matrix4x4Typed(const Matrix4x4Typed& aOther) { + memcpy(components, aOther.components, sizeof(components)); + } + + template <class T2> + explicit Matrix4x4Typed( + const Matrix4x4Typed<SourceUnits, TargetUnits, T2>& aOther) + : _11(aOther._11), + _12(aOther._12), + _13(aOther._13), + _14(aOther._14), + _21(aOther._21), + _22(aOther._22), + _23(aOther._23), + _24(aOther._24), + _31(aOther._31), + _32(aOther._32), + _33(aOther._33), + _34(aOther._34), + _41(aOther._41), + _42(aOther._42), + _43(aOther._43), + _44(aOther._44) {} + + union { + struct { + T _11, _12, _13, _14; + T _21, _22, _23, _24; + T _31, _32, _33, _34; + T _41, _42, _43, _44; + }; + T components[16]; + }; + + friend std::ostream& operator<<(std::ostream& aStream, + const Matrix4x4Typed& aMatrix) { + if (aMatrix.Is2D()) { + BaseMatrix<T> matrix = aMatrix.As2D(); + return aStream << matrix; + } + const T* f = &aMatrix._11; + aStream << "[ " << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3] << ';'; + f += 4; + aStream << ' ' << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3] << ';'; + f += 4; + aStream << ' ' << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3] << ';'; + f += 4; + aStream << ' ' << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3] + << "; ]"; + return aStream; + } + + Point4DTyped<UnknownUnits, T>& operator[](int aIndex) { + MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid matrix array index"); + return *reinterpret_cast<Point4DTyped<UnknownUnits, T>*>((&_11) + + 4 * aIndex); + } + const Point4DTyped<UnknownUnits, T>& operator[](int aIndex) const { + MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid matrix array index"); + return *reinterpret_cast<const Point4DTyped<UnknownUnits, T>*>((&_11) + + 4 * aIndex); + } + + // External code should avoid calling this, and instead use + // ViewAs() from UnitTransforms.h, which requires providing + // a justification. + template <typename NewMatrix4x4Typed> + [[nodiscard]] NewMatrix4x4Typed Cast() const { + return NewMatrix4x4Typed(_11, _12, _13, _14, _21, _22, _23, _24, _31, _32, + _33, _34, _41, _42, _43, _44); + } + + /** + * Returns true if the matrix is isomorphic to a 2D affine transformation. + */ + bool Is2D() const { + if (_13 != 0.0f || _14 != 0.0f || _23 != 0.0f || _24 != 0.0f || + _31 != 0.0f || _32 != 0.0f || _33 != 1.0f || _34 != 0.0f || + _43 != 0.0f || _44 != 1.0f) { + return false; + } + return true; + } + + bool Is2D(BaseMatrix<T>* aMatrix) const { + if (!Is2D()) { + return false; + } + if (aMatrix) { + aMatrix->_11 = _11; + aMatrix->_12 = _12; + aMatrix->_21 = _21; + aMatrix->_22 = _22; + aMatrix->_31 = _41; + aMatrix->_32 = _42; + } + return true; + } + + BaseMatrix<T> As2D() const { + MOZ_ASSERT(Is2D(), "Matrix is not a 2D affine transform"); + + return BaseMatrix<T>(_11, _12, _21, _22, _41, _42); + } + + bool CanDraw2D(BaseMatrix<T>* aMatrix = nullptr) const { + if (_14 != 0.0f || _24 != 0.0f || _44 != 1.0f) { + return false; + } + if (aMatrix) { + aMatrix->_11 = _11; + aMatrix->_12 = _12; + aMatrix->_21 = _21; + aMatrix->_22 = _22; + aMatrix->_31 = _41; + aMatrix->_32 = _42; + } + return true; + } + + Matrix4x4Typed& ProjectTo2D() { + _31 = 0.0f; + _32 = 0.0f; + _13 = 0.0f; + _23 = 0.0f; + _33 = 1.0f; + _43 = 0.0f; + _34 = 0.0f; + // Some matrices, such as those derived from perspective transforms, + // can modify _44 from 1, while leaving the rest of the fourth column + // (_14, _24) at 0. In this case, after resetting the third row and + // third column above, the value of _44 functions only to scale the + // coordinate transform divide by W. The matrix can be converted to + // a true 2D matrix by normalizing out the scaling effect of _44 on + // the remaining components ahead of time. + if (_14 == 0.0f && _24 == 0.0f && _44 != 1.0f && _44 != 0.0f) { + T scale = 1.0f / _44; + _11 *= scale; + _12 *= scale; + _21 *= scale; + _22 *= scale; + _41 *= scale; + _42 *= scale; + _44 = 1.0f; + } + return *this; + } + + template <class F> + Point4DTyped<TargetUnits, F> ProjectPoint( + const PointTyped<SourceUnits, F>& aPoint) const { + // Find a value for z that will transform to 0. + + // The transformed value of z is computed as: + // z' = aPoint.x * _13 + aPoint.y * _23 + z * _33 + _43; + + // Solving for z when z' = 0 gives us: + F z = -(aPoint.x * _13 + aPoint.y * _23 + _43) / _33; + + // Compute the transformed point + return this->TransformPoint( + Point4DTyped<SourceUnits, F>(aPoint.x, aPoint.y, z, 1)); + } + + template <class F> + RectTyped<TargetUnits, F> ProjectRectBounds( + const RectTyped<SourceUnits, F>& aRect, + const RectTyped<TargetUnits, F>& aClip) const { + // This function must never return std::numeric_limits<Float>::max() or any + // other arbitrary large value in place of inifinity. This often occurs + // when aRect is an inversed projection matrix or when aRect is transformed + // to be partly behind and in front of the camera (w=0 plane in homogenous + // coordinates) - See Bug 1035611 + + // Some call-sites will call RoundGfxRectToAppRect which clips both the + // extents and dimensions of the rect to be bounded by nscoord_MAX. + // If we return a Rect that, when converted to nscoords, has a width or + // height greater than nscoord_MAX, RoundGfxRectToAppRect will clip the + // overflow off both the min and max end of the rect after clipping the + // extents of the rect, resulting in a translation of the rect towards the + // infinite end. + + // The bounds returned by ProjectRectBounds are expected to be clipped only + // on the edges beyond the bounds of the coordinate system; otherwise, the + // clipped bounding box would be smaller than the correct one and result + // bugs such as incorrect culling (eg. Bug 1073056) + + // To address this without requiring all code to work in homogenous + // coordinates or interpret infinite values correctly, a specialized + // clipping function is integrated into ProjectRectBounds. + + // Callers should pass an aClip value that represents the extents to clip + // the result to, in the same coordinate system as aRect. + Point4DTyped<TargetUnits, F> points[4]; + + points[0] = ProjectPoint(aRect.TopLeft()); + points[1] = ProjectPoint(aRect.TopRight()); + points[2] = ProjectPoint(aRect.BottomRight()); + points[3] = ProjectPoint(aRect.BottomLeft()); + + F min_x = std::numeric_limits<F>::max(); + F min_y = std::numeric_limits<F>::max(); + F max_x = -std::numeric_limits<F>::max(); + F max_y = -std::numeric_limits<F>::max(); + + for (int i = 0; i < 4; i++) { + // Only use points that exist above the w=0 plane + if (points[i].HasPositiveWCoord()) { + PointTyped<TargetUnits, F> point2d = + aClip.ClampPoint(points[i].As2DPoint()); + min_x = std::min<F>(point2d.x, min_x); + max_x = std::max<F>(point2d.x, max_x); + min_y = std::min<F>(point2d.y, min_y); + max_y = std::max<F>(point2d.y, max_y); + } + + int next = (i == 3) ? 0 : i + 1; + if (points[i].HasPositiveWCoord() != points[next].HasPositiveWCoord()) { + // If the line between two points crosses the w=0 plane, then + // interpolate to find the point of intersection with the w=0 plane and + // use that instead. + Point4DTyped<TargetUnits, F> intercept = + ComputePerspectivePlaneIntercept(points[i], points[next]); + // Since intercept.w will always be 0 here, we interpret x,y,z as a + // direction towards an infinite vanishing point. + if (intercept.x < 0.0f) { + min_x = aClip.X(); + } else if (intercept.x > 0.0f) { + max_x = aClip.XMost(); + } + if (intercept.y < 0.0f) { + min_y = aClip.Y(); + } else if (intercept.y > 0.0f) { + max_y = aClip.YMost(); + } + } + } + + if (max_x < min_x || max_y < min_y) { + return RectTyped<TargetUnits, F>(0, 0, 0, 0); + } + + return RectTyped<TargetUnits, F>(min_x, min_y, max_x - min_x, + max_y - min_y); + } + + /** + * TransformAndClipBounds transforms aRect as a bounding box, while clipping + * the transformed bounds to the extents of aClip. + */ + template <class F> + RectTyped<TargetUnits, F> TransformAndClipBounds( + const RectTyped<SourceUnits, F>& aRect, + const RectTyped<TargetUnits, F>& aClip) const { + PointTyped<UnknownUnits, F> verts[kTransformAndClipRectMaxVerts]; + size_t vertCount = TransformAndClipRect(aRect, aClip, verts); + + F min_x = std::numeric_limits<F>::max(); + F min_y = std::numeric_limits<F>::max(); + F max_x = -std::numeric_limits<F>::max(); + F max_y = -std::numeric_limits<F>::max(); + for (size_t i = 0; i < vertCount; i++) { + min_x = std::min(min_x, verts[i].x.value); + max_x = std::max(max_x, verts[i].x.value); + min_y = std::min(min_y, verts[i].y.value); + max_y = std::max(max_y, verts[i].y.value); + } + + if (max_x < min_x || max_y < min_y) { + return RectTyped<TargetUnits, F>(0, 0, 0, 0); + } + + return RectTyped<TargetUnits, F>(min_x, min_y, max_x - min_x, + max_y - min_y); + } + + template <class F> + RectTyped<TargetUnits, F> TransformAndClipBounds( + const TriangleTyped<SourceUnits, F>& aTriangle, + const RectTyped<TargetUnits, F>& aClip) const { + return TransformAndClipBounds(aTriangle.BoundingBox(), aClip); + } + + /** + * TransformAndClipRect projects a rectangle and clips against view frustum + * clipping planes in homogenous space so that its projected vertices are + * constrained within the 2d rectangle passed in aClip. + * The resulting vertices are populated in aVerts. aVerts must be + * pre-allocated to hold at least kTransformAndClipRectMaxVerts Points. + * The vertex count is returned by TransformAndClipRect. It is possible to + * emit fewer than 3 vertices, indicating that aRect will not be visible + * within aClip. + */ + template <class F> + size_t TransformAndClipRect(const RectTyped<SourceUnits, F>& aRect, + const RectTyped<TargetUnits, F>& aClip, + PointTyped<TargetUnits, F>* aVerts) const { + typedef Point4DTyped<UnknownUnits, F> P4D; + + // The initial polygon is made up by the corners of aRect in homogenous + // space, mapped into the destination space of this transform. + P4D rectCorners[] = { + TransformPoint(P4D(aRect.X(), aRect.Y(), 0, 1)), + TransformPoint(P4D(aRect.XMost(), aRect.Y(), 0, 1)), + TransformPoint(P4D(aRect.XMost(), aRect.YMost(), 0, 1)), + TransformPoint(P4D(aRect.X(), aRect.YMost(), 0, 1)), + }; + + // Cut off pieces of the polygon that are outside of aClip (the "view + // frustrum"), by consecutively intersecting the polygon with the half space + // induced by the clipping plane for each side of aClip. + // View frustum clipping planes are described as normals originating from + // the 0,0,0,0 origin. + // Each pass can increase or decrease the number of points that make up the + // current clipped polygon. We double buffer the set of points, alternating + // between polygonBufA and polygonBufB. Duplicated points in the polygons + // are kept around until all clipping is done. The loop at the end filters + // out any consecutive duplicates. + P4D polygonBufA[kTransformAndClipRectMaxVerts]; + P4D polygonBufB[kTransformAndClipRectMaxVerts]; + + Span<P4D> polygon(rectCorners); + polygon = IntersectPolygon<F>(polygon, P4D(1.0, 0.0, 0.0, -aClip.X()), + polygonBufA); + polygon = IntersectPolygon<F>(polygon, P4D(-1.0, 0.0, 0.0, aClip.XMost()), + polygonBufB); + polygon = IntersectPolygon<F>(polygon, P4D(0.0, 1.0, 0.0, -aClip.Y()), + polygonBufA); + polygon = IntersectPolygon<F>(polygon, P4D(0.0, -1.0, 0.0, aClip.YMost()), + polygonBufB); + + size_t vertCount = 0; + for (const auto& srcPoint : polygon) { + PointTyped<TargetUnits, F> p; + if (srcPoint.w == 0.0) { + // If a point lies on the intersection of the clipping planes at + // (0,0,0,0), we must avoid a division by zero w component. + p = PointTyped<TargetUnits, F>(0.0, 0.0); + } else { + p = srcPoint.As2DPoint(); + } + // Emit only unique points + if (vertCount == 0 || p != aVerts[vertCount - 1]) { + aVerts[vertCount++] = p; + } + } + + return vertCount; + } + + static const int kTransformAndClipRectMaxVerts = 32; + + static Matrix4x4Typed From2D(const BaseMatrix<T>& aMatrix) { + Matrix4x4Typed matrix; + matrix._11 = aMatrix._11; + matrix._12 = aMatrix._12; + matrix._21 = aMatrix._21; + matrix._22 = aMatrix._22; + matrix._41 = aMatrix._31; + matrix._42 = aMatrix._32; + return matrix; + } + + bool Is2DIntegerTranslation() const { + return Is2D() && As2D().IsIntegerTranslation(); + } + + TargetPoint4D TransposeTransform4D(const SourcePoint4D& aPoint) const { + Float x = aPoint.x * _11 + aPoint.y * _12 + aPoint.z * _13 + aPoint.w * _14; + Float y = aPoint.x * _21 + aPoint.y * _22 + aPoint.z * _23 + aPoint.w * _24; + Float z = aPoint.x * _31 + aPoint.y * _32 + aPoint.z * _33 + aPoint.w * _34; + Float w = aPoint.x * _41 + aPoint.y * _42 + aPoint.z * _43 + aPoint.w * _44; + + return TargetPoint4D(x, y, z, w); + } + + template <class F> + Point4DTyped<TargetUnits, F> TransformPoint( + const Point4DTyped<SourceUnits, F>& aPoint) const { + Point4DTyped<TargetUnits, F> retPoint; + + retPoint.x = + aPoint.x * _11 + aPoint.y * _21 + aPoint.z * _31 + aPoint.w * _41; + retPoint.y = + aPoint.x * _12 + aPoint.y * _22 + aPoint.z * _32 + aPoint.w * _42; + retPoint.z = + aPoint.x * _13 + aPoint.y * _23 + aPoint.z * _33 + aPoint.w * _43; + retPoint.w = + aPoint.x * _14 + aPoint.y * _24 + aPoint.z * _34 + aPoint.w * _44; + + return retPoint; + } + + template <class F> + Point3DTyped<TargetUnits, F> TransformPoint( + const Point3DTyped<SourceUnits, F>& aPoint) const { + Point3DTyped<TargetUnits, F> result; + result.x = aPoint.x * _11 + aPoint.y * _21 + aPoint.z * _31 + _41; + result.y = aPoint.x * _12 + aPoint.y * _22 + aPoint.z * _32 + _42; + result.z = aPoint.x * _13 + aPoint.y * _23 + aPoint.z * _33 + _43; + + result /= (aPoint.x * _14 + aPoint.y * _24 + aPoint.z * _34 + _44); + + return result; + } + + template <class F> + PointTyped<TargetUnits, F> TransformPoint( + const PointTyped<SourceUnits, F>& aPoint) const { + Point4DTyped<SourceUnits, F> temp(aPoint.x, aPoint.y, 0, 1); + return TransformPoint(temp).As2DPoint(); + } + + template <class F> + GFX2D_API RectTyped<TargetUnits, F> TransformBounds( + const RectTyped<SourceUnits, F>& aRect) const { + PointTyped<TargetUnits, F> quad[4]; + F min_x, max_x; + F min_y, max_y; + + quad[0] = TransformPoint(aRect.TopLeft()); + quad[1] = TransformPoint(aRect.TopRight()); + quad[2] = TransformPoint(aRect.BottomLeft()); + quad[3] = TransformPoint(aRect.BottomRight()); + + min_x = max_x = quad[0].x; + min_y = max_y = quad[0].y; + + for (int i = 1; i < 4; i++) { + if (quad[i].x < min_x) { + min_x = quad[i].x; + } + if (quad[i].x > max_x) { + max_x = quad[i].x; + } + + if (quad[i].y < min_y) { + min_y = quad[i].y; + } + if (quad[i].y > max_y) { + max_y = quad[i].y; + } + } + + return RectTyped<TargetUnits, F>(min_x, min_y, max_x - min_x, + max_y - min_y); + } + + static Matrix4x4Typed Translation(T aX, T aY, T aZ) { + return Matrix4x4Typed(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, aX, aY, aZ, 1.0f); + } + + static Matrix4x4Typed Translation(const TargetPoint3D& aP) { + return Translation(aP.x, aP.y, aP.z); + } + + static Matrix4x4Typed Translation(const TargetPoint& aP) { + return Translation(aP.x, aP.y, 0); + } + + /** + * Apply a translation to this matrix. + * + * The "Pre" in this method's name means that the translation is applied + * -before- this matrix's existing transformation. That is, any vector that + * is multiplied by the resulting matrix will first be translated, then be + * transformed by the original transform. + * + * Calling this method will result in this matrix having the same value as + * the result of: + * + * Matrix4x4::Translation(x, y) * this + * + * (Note that in performance critical code multiplying by the result of a + * Translation()/Scaling() call is not recommended since that results in a + * full matrix multiply involving 64 floating-point multiplications. Calling + * this method would be preferred since it only involves 12 floating-point + * multiplications.) + */ + Matrix4x4Typed& PreTranslate(T aX, T aY, T aZ) { + _41 += aX * _11 + aY * _21 + aZ * _31; + _42 += aX * _12 + aY * _22 + aZ * _32; + _43 += aX * _13 + aY * _23 + aZ * _33; + _44 += aX * _14 + aY * _24 + aZ * _34; + + return *this; + } + + Matrix4x4Typed& PreTranslate(const Point3DTyped<UnknownUnits, T>& aPoint) { + return PreTranslate(aPoint.x, aPoint.y, aPoint.z); + } + + /** + * Similar to PreTranslate, but the translation is applied -after- this + * matrix's existing transformation instead of before it. + * + * This method is generally less used than PreTranslate since typically code + * wants to adjust an existing user space to device space matrix to create a + * transform to device space from a -new- user space (translated from the + * previous user space). In that case consumers will need to use the Pre* + * variants of the matrix methods rather than using the Post* methods, since + * the Post* methods add a transform to the device space end of the + * transformation. + */ + Matrix4x4Typed& PostTranslate(T aX, T aY, T aZ) { + _11 += _14 * aX; + _21 += _24 * aX; + _31 += _34 * aX; + _41 += _44 * aX; + _12 += _14 * aY; + _22 += _24 * aY; + _32 += _34 * aY; + _42 += _44 * aY; + _13 += _14 * aZ; + _23 += _24 * aZ; + _33 += _34 * aZ; + _43 += _44 * aZ; + + return *this; + } + + Matrix4x4Typed& PostTranslate(const TargetPoint3D& aPoint) { + return PostTranslate(aPoint.x, aPoint.y, aPoint.z); + } + + Matrix4x4Typed& PostTranslate(const TargetPoint& aPoint) { + return PostTranslate(aPoint.x, aPoint.y, 0); + } + + static Matrix4x4Typed Scaling(T aScaleX, T aScaleY, T aScaleZ) { + return Matrix4x4Typed(aScaleX, 0.0f, 0.0f, 0.0f, 0.0f, aScaleY, 0.0f, 0.0f, + 0.0f, 0.0f, aScaleZ, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f); + } + + /** + * Similar to PreTranslate, but applies a scale instead of a translation. + */ + Matrix4x4Typed& PreScale(T aX, T aY, T aZ) { + _11 *= aX; + _12 *= aX; + _13 *= aX; + _14 *= aX; + _21 *= aY; + _22 *= aY; + _23 *= aY; + _24 *= aY; + _31 *= aZ; + _32 *= aZ; + _33 *= aZ; + _34 *= aZ; + + return *this; + } + + template <typename NewSourceUnits> + [[nodiscard]] Matrix4x4Typed<NewSourceUnits, TargetUnits> PreScale( + const ScaleFactor<NewSourceUnits, SourceUnits>& aScale) const { + auto clone = Cast<Matrix4x4Typed<NewSourceUnits, TargetUnits>>(); + clone.PreScale(aScale.scale, aScale.scale, 1); + return clone; + } + + template <typename NewSourceUnits> + [[nodiscard]] Matrix4x4Typed<NewSourceUnits, TargetUnits> PreScale( + const BaseScaleFactors2D<NewSourceUnits, SourceUnits, T>& aScale) const { + auto clone = Cast<Matrix4x4Typed<NewSourceUnits, TargetUnits>>(); + clone.PreScale(aScale.xScale, aScale.yScale, 1); + return clone; + } + + /** + * Similar to PostTranslate, but applies a scale instead of a translation. + */ + Matrix4x4Typed& PostScale(T aScaleX, T aScaleY, T aScaleZ) { + _11 *= aScaleX; + _21 *= aScaleX; + _31 *= aScaleX; + _41 *= aScaleX; + _12 *= aScaleY; + _22 *= aScaleY; + _32 *= aScaleY; + _42 *= aScaleY; + _13 *= aScaleZ; + _23 *= aScaleZ; + _33 *= aScaleZ; + _43 *= aScaleZ; + + return *this; + } + + template <typename NewTargetUnits> + [[nodiscard]] Matrix4x4Typed<SourceUnits, NewTargetUnits> PostScale( + const ScaleFactor<TargetUnits, NewTargetUnits>& aScale) const { + auto clone = Cast<Matrix4x4Typed<SourceUnits, NewTargetUnits>>(); + clone.PostScale(aScale.scale, aScale.scale, 1); + return clone; + } + + template <typename NewTargetUnits> + [[nodiscard]] Matrix4x4Typed<SourceUnits, NewTargetUnits> PostScale( + const BaseScaleFactors2D<TargetUnits, NewTargetUnits, T>& aScale) const { + auto clone = Cast<Matrix4x4Typed<SourceUnits, NewTargetUnits>>(); + clone.PostScale(aScale.xScale, aScale.yScale, 1); + return clone; + } + + void SkewXY(T aSkew) { (*this)[1] += (*this)[0] * aSkew; } + + void SkewXZ(T aSkew) { (*this)[2] += (*this)[0] * aSkew; } + + void SkewYZ(T aSkew) { (*this)[2] += (*this)[1] * aSkew; } + + Matrix4x4Typed& ChangeBasis(const Point3DTyped<UnknownUnits, T>& aOrigin) { + return ChangeBasis(aOrigin.x, aOrigin.y, aOrigin.z); + } + + Matrix4x4Typed& ChangeBasis(T aX, T aY, T aZ) { + // Translate to the origin before applying this matrix + PreTranslate(-aX, -aY, -aZ); + + // Translate back into position after applying this matrix + PostTranslate(aX, aY, aZ); + + return *this; + } + + Matrix4x4Typed& Transpose() { + std::swap(_12, _21); + std::swap(_13, _31); + std::swap(_14, _41); + + std::swap(_23, _32); + std::swap(_24, _42); + + std::swap(_34, _43); + + return *this; + } + + bool operator==(const Matrix4x4Typed& o) const { + // XXX would be nice to memcmp here, but that breaks IEEE 754 semantics + return _11 == o._11 && _12 == o._12 && _13 == o._13 && _14 == o._14 && + _21 == o._21 && _22 == o._22 && _23 == o._23 && _24 == o._24 && + _31 == o._31 && _32 == o._32 && _33 == o._33 && _34 == o._34 && + _41 == o._41 && _42 == o._42 && _43 == o._43 && _44 == o._44; + } + + bool operator!=(const Matrix4x4Typed& o) const { return !((*this) == o); } + + Matrix4x4Typed& operator=(const Matrix4x4Typed& aOther) = default; + + template <typename NewTargetUnits> + Matrix4x4Typed<SourceUnits, NewTargetUnits, T> operator*( + const Matrix4x4Typed<TargetUnits, NewTargetUnits, T>& aMatrix) const { + Matrix4x4Typed<SourceUnits, NewTargetUnits, T> matrix; + + matrix._11 = _11 * aMatrix._11 + _12 * aMatrix._21 + _13 * aMatrix._31 + + _14 * aMatrix._41; + matrix._21 = _21 * aMatrix._11 + _22 * aMatrix._21 + _23 * aMatrix._31 + + _24 * aMatrix._41; + matrix._31 = _31 * aMatrix._11 + _32 * aMatrix._21 + _33 * aMatrix._31 + + _34 * aMatrix._41; + matrix._41 = _41 * aMatrix._11 + _42 * aMatrix._21 + _43 * aMatrix._31 + + _44 * aMatrix._41; + matrix._12 = _11 * aMatrix._12 + _12 * aMatrix._22 + _13 * aMatrix._32 + + _14 * aMatrix._42; + matrix._22 = _21 * aMatrix._12 + _22 * aMatrix._22 + _23 * aMatrix._32 + + _24 * aMatrix._42; + matrix._32 = _31 * aMatrix._12 + _32 * aMatrix._22 + _33 * aMatrix._32 + + _34 * aMatrix._42; + matrix._42 = _41 * aMatrix._12 + _42 * aMatrix._22 + _43 * aMatrix._32 + + _44 * aMatrix._42; + matrix._13 = _11 * aMatrix._13 + _12 * aMatrix._23 + _13 * aMatrix._33 + + _14 * aMatrix._43; + matrix._23 = _21 * aMatrix._13 + _22 * aMatrix._23 + _23 * aMatrix._33 + + _24 * aMatrix._43; + matrix._33 = _31 * aMatrix._13 + _32 * aMatrix._23 + _33 * aMatrix._33 + + _34 * aMatrix._43; + matrix._43 = _41 * aMatrix._13 + _42 * aMatrix._23 + _43 * aMatrix._33 + + _44 * aMatrix._43; + matrix._14 = _11 * aMatrix._14 + _12 * aMatrix._24 + _13 * aMatrix._34 + + _14 * aMatrix._44; + matrix._24 = _21 * aMatrix._14 + _22 * aMatrix._24 + _23 * aMatrix._34 + + _24 * aMatrix._44; + matrix._34 = _31 * aMatrix._14 + _32 * aMatrix._24 + _33 * aMatrix._34 + + _34 * aMatrix._44; + matrix._44 = _41 * aMatrix._14 + _42 * aMatrix._24 + _43 * aMatrix._34 + + _44 * aMatrix._44; + + return matrix; + } + + Matrix4x4Typed& operator*=( + const Matrix4x4Typed<TargetUnits, TargetUnits, T>& aMatrix) { + *this = *this * aMatrix; + return *this; + } + + /* Returns true if the matrix is an identity matrix. + */ + bool IsIdentity() const { + return _11 == 1.0f && _12 == 0.0f && _13 == 0.0f && _14 == 0.0f && + _21 == 0.0f && _22 == 1.0f && _23 == 0.0f && _24 == 0.0f && + _31 == 0.0f && _32 == 0.0f && _33 == 1.0f && _34 == 0.0f && + _41 == 0.0f && _42 == 0.0f && _43 == 0.0f && _44 == 1.0f; + } + + bool IsSingular() const { return Determinant() == 0.0; } + + T Determinant() const { + return _14 * _23 * _32 * _41 - _13 * _24 * _32 * _41 - + _14 * _22 * _33 * _41 + _12 * _24 * _33 * _41 + + _13 * _22 * _34 * _41 - _12 * _23 * _34 * _41 - + _14 * _23 * _31 * _42 + _13 * _24 * _31 * _42 + + _14 * _21 * _33 * _42 - _11 * _24 * _33 * _42 - + _13 * _21 * _34 * _42 + _11 * _23 * _34 * _42 + + _14 * _22 * _31 * _43 - _12 * _24 * _31 * _43 - + _14 * _21 * _32 * _43 + _11 * _24 * _32 * _43 + + _12 * _21 * _34 * _43 - _11 * _22 * _34 * _43 - + _13 * _22 * _31 * _44 + _12 * _23 * _31 * _44 + + _13 * _21 * _32 * _44 - _11 * _23 * _32 * _44 - + _12 * _21 * _33 * _44 + _11 * _22 * _33 * _44; + } + + // Invert() is not unit-correct. Prefer Inverse() where possible. + bool Invert() { + T det = Determinant(); + if (!det) { + return false; + } + + Matrix4x4Typed<SourceUnits, TargetUnits, T> result; + result._11 = _23 * _34 * _42 - _24 * _33 * _42 + _24 * _32 * _43 - + _22 * _34 * _43 - _23 * _32 * _44 + _22 * _33 * _44; + result._12 = _14 * _33 * _42 - _13 * _34 * _42 - _14 * _32 * _43 + + _12 * _34 * _43 + _13 * _32 * _44 - _12 * _33 * _44; + result._13 = _13 * _24 * _42 - _14 * _23 * _42 + _14 * _22 * _43 - + _12 * _24 * _43 - _13 * _22 * _44 + _12 * _23 * _44; + result._14 = _14 * _23 * _32 - _13 * _24 * _32 - _14 * _22 * _33 + + _12 * _24 * _33 + _13 * _22 * _34 - _12 * _23 * _34; + result._21 = _24 * _33 * _41 - _23 * _34 * _41 - _24 * _31 * _43 + + _21 * _34 * _43 + _23 * _31 * _44 - _21 * _33 * _44; + result._22 = _13 * _34 * _41 - _14 * _33 * _41 + _14 * _31 * _43 - + _11 * _34 * _43 - _13 * _31 * _44 + _11 * _33 * _44; + result._23 = _14 * _23 * _41 - _13 * _24 * _41 - _14 * _21 * _43 + + _11 * _24 * _43 + _13 * _21 * _44 - _11 * _23 * _44; + result._24 = _13 * _24 * _31 - _14 * _23 * _31 + _14 * _21 * _33 - + _11 * _24 * _33 - _13 * _21 * _34 + _11 * _23 * _34; + result._31 = _22 * _34 * _41 - _24 * _32 * _41 + _24 * _31 * _42 - + _21 * _34 * _42 - _22 * _31 * _44 + _21 * _32 * _44; + result._32 = _14 * _32 * _41 - _12 * _34 * _41 - _14 * _31 * _42 + + _11 * _34 * _42 + _12 * _31 * _44 - _11 * _32 * _44; + result._33 = _12 * _24 * _41 - _14 * _22 * _41 + _14 * _21 * _42 - + _11 * _24 * _42 - _12 * _21 * _44 + _11 * _22 * _44; + result._34 = _14 * _22 * _31 - _12 * _24 * _31 - _14 * _21 * _32 + + _11 * _24 * _32 + _12 * _21 * _34 - _11 * _22 * _34; + result._41 = _23 * _32 * _41 - _22 * _33 * _41 - _23 * _31 * _42 + + _21 * _33 * _42 + _22 * _31 * _43 - _21 * _32 * _43; + result._42 = _12 * _33 * _41 - _13 * _32 * _41 + _13 * _31 * _42 - + _11 * _33 * _42 - _12 * _31 * _43 + _11 * _32 * _43; + result._43 = _13 * _22 * _41 - _12 * _23 * _41 - _13 * _21 * _42 + + _11 * _23 * _42 + _12 * _21 * _43 - _11 * _22 * _43; + result._44 = _12 * _23 * _31 - _13 * _22 * _31 + _13 * _21 * _32 - + _11 * _23 * _32 - _12 * _21 * _33 + _11 * _22 * _33; + + result._11 /= det; + result._12 /= det; + result._13 /= det; + result._14 /= det; + result._21 /= det; + result._22 /= det; + result._23 /= det; + result._24 /= det; + result._31 /= det; + result._32 /= det; + result._33 /= det; + result._34 /= det; + result._41 /= det; + result._42 /= det; + result._43 /= det; + result._44 /= det; + *this = result; + + return true; + } + + Matrix4x4Typed<TargetUnits, SourceUnits, T> Inverse() const { + typedef Matrix4x4Typed<TargetUnits, SourceUnits, T> InvertedMatrix; + InvertedMatrix clone = Cast<InvertedMatrix>(); + DebugOnly<bool> inverted = clone.Invert(); + MOZ_ASSERT(inverted, + "Attempted to get the inverse of a non-invertible matrix"); + return clone; + } + + Maybe<Matrix4x4Typed<TargetUnits, SourceUnits, T>> MaybeInverse() const { + typedef Matrix4x4Typed<TargetUnits, SourceUnits, T> InvertedMatrix; + InvertedMatrix clone = Cast<InvertedMatrix>(); + if (clone.Invert()) { + return Some(clone); + } + return Nothing(); + } + + void Normalize() { + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + (*this)[i][j] /= (*this)[3][3]; + } + } + } + + bool FuzzyEqual(const Matrix4x4Typed& o) const { + return gfx::FuzzyEqual(_11, o._11) && gfx::FuzzyEqual(_12, o._12) && + gfx::FuzzyEqual(_13, o._13) && gfx::FuzzyEqual(_14, o._14) && + gfx::FuzzyEqual(_21, o._21) && gfx::FuzzyEqual(_22, o._22) && + gfx::FuzzyEqual(_23, o._23) && gfx::FuzzyEqual(_24, o._24) && + gfx::FuzzyEqual(_31, o._31) && gfx::FuzzyEqual(_32, o._32) && + gfx::FuzzyEqual(_33, o._33) && gfx::FuzzyEqual(_34, o._34) && + gfx::FuzzyEqual(_41, o._41) && gfx::FuzzyEqual(_42, o._42) && + gfx::FuzzyEqual(_43, o._43) && gfx::FuzzyEqual(_44, o._44); + } + + bool FuzzyEqualsMultiplicative(const Matrix4x4Typed& o) const { + return ::mozilla::FuzzyEqualsMultiplicative(_11, o._11) && + ::mozilla::FuzzyEqualsMultiplicative(_12, o._12) && + ::mozilla::FuzzyEqualsMultiplicative(_13, o._13) && + ::mozilla::FuzzyEqualsMultiplicative(_14, o._14) && + ::mozilla::FuzzyEqualsMultiplicative(_21, o._21) && + ::mozilla::FuzzyEqualsMultiplicative(_22, o._22) && + ::mozilla::FuzzyEqualsMultiplicative(_23, o._23) && + ::mozilla::FuzzyEqualsMultiplicative(_24, o._24) && + ::mozilla::FuzzyEqualsMultiplicative(_31, o._31) && + ::mozilla::FuzzyEqualsMultiplicative(_32, o._32) && + ::mozilla::FuzzyEqualsMultiplicative(_33, o._33) && + ::mozilla::FuzzyEqualsMultiplicative(_34, o._34) && + ::mozilla::FuzzyEqualsMultiplicative(_41, o._41) && + ::mozilla::FuzzyEqualsMultiplicative(_42, o._42) && + ::mozilla::FuzzyEqualsMultiplicative(_43, o._43) && + ::mozilla::FuzzyEqualsMultiplicative(_44, o._44); + } + + bool IsBackfaceVisible() const { + // Inverse()._33 < 0; + T det = Determinant(); + T __33 = _12 * _24 * _41 - _14 * _22 * _41 + _14 * _21 * _42 - + _11 * _24 * _42 - _12 * _21 * _44 + _11 * _22 * _44; + return (__33 * det) < 0; + } + + Matrix4x4Typed& NudgeToIntegersFixedEpsilon() { + NudgeToInteger(&_11); + NudgeToInteger(&_12); + NudgeToInteger(&_13); + NudgeToInteger(&_14); + NudgeToInteger(&_21); + NudgeToInteger(&_22); + NudgeToInteger(&_23); + NudgeToInteger(&_24); + NudgeToInteger(&_31); + NudgeToInteger(&_32); + NudgeToInteger(&_33); + NudgeToInteger(&_34); + static const float error = 1e-5f; + NudgeToInteger(&_41, error); + NudgeToInteger(&_42, error); + NudgeToInteger(&_43, error); + NudgeToInteger(&_44, error); + return *this; + } + + Point4D TransposedVector(int aIndex) const { + MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid matrix array index"); + return Point4DTyped<UnknownUnits, T>(*((&_11) + aIndex), *((&_21) + aIndex), + *((&_31) + aIndex), + *((&_41) + aIndex)); + } + + void SetTransposedVector(int aIndex, Point4DTyped<UnknownUnits, T>& aVector) { + MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid matrix array index"); + *((&_11) + aIndex) = aVector.x; + *((&_21) + aIndex) = aVector.y; + *((&_31) + aIndex) = aVector.z; + *((&_41) + aIndex) = aVector.w; + } + + bool Decompose(Point3DTyped<UnknownUnits, T>& translation, + BaseQuaternion<T>& rotation, + Point3DTyped<UnknownUnits, T>& scale) const { + // Ensure matrix can be normalized + if (gfx::FuzzyEqual(_44, 0.0f)) { + return false; + } + Matrix4x4Typed mat = *this; + mat.Normalize(); + if (HasPerspectiveComponent()) { + // We do not support projection matrices + return false; + } + + // Extract translation + translation.x = mat._41; + translation.y = mat._42; + translation.z = mat._43; + + // Remove translation + mat._41 = 0.0f; + mat._42 = 0.0f; + mat._43 = 0.0f; + + // Extract scale + scale.x = sqrtf(_11 * _11 + _21 * _21 + _31 * _31); + scale.y = sqrtf(_12 * _12 + _22 * _22 + _32 * _32); + scale.z = sqrtf(_13 * _13 + _23 * _23 + _33 * _33); + + // Remove scale + if (gfx::FuzzyEqual(scale.x, 0.0f) || gfx::FuzzyEqual(scale.y, 0.0f) || + gfx::FuzzyEqual(scale.z, 0.0f)) { + // We do not support matrices with a zero scale component + return false; + } + + // Extract rotation + rotation.SetFromRotationMatrix(this->ToUnknownMatrix()); + return true; + } + + // Sets this matrix to a rotation matrix given by aQuat. + // This quaternion *MUST* be normalized! + // Implemented in Quaternion.cpp + void SetRotationFromQuaternion(const BaseQuaternion<T>& q) { + const T x2 = q.x + q.x, y2 = q.y + q.y, z2 = q.z + q.z; + const T xx = q.x * x2, xy = q.x * y2, xz = q.x * z2; + const T yy = q.y * y2, yz = q.y * z2, zz = q.z * z2; + const T wx = q.w * x2, wy = q.w * y2, wz = q.w * z2; + + _11 = 1.0f - (yy + zz); + _21 = xy - wz; + _31 = xz + wy; + _41 = 0.0f; + + _12 = xy + wz; + _22 = 1.0f - (xx + zz); + _32 = yz - wx; + _42 = 0.0f; + + _13 = xz - wy; + _23 = yz + wx; + _33 = 1.0f - (xx + yy); + _43 = 0.0f; + + _14 = _42 = _43 = 0.0f; + _44 = 1.0f; + } + + // Set all the members of the matrix to NaN + void SetNAN() { + _11 = UnspecifiedNaN<T>(); + _21 = UnspecifiedNaN<T>(); + _31 = UnspecifiedNaN<T>(); + _41 = UnspecifiedNaN<T>(); + _12 = UnspecifiedNaN<T>(); + _22 = UnspecifiedNaN<T>(); + _32 = UnspecifiedNaN<T>(); + _42 = UnspecifiedNaN<T>(); + _13 = UnspecifiedNaN<T>(); + _23 = UnspecifiedNaN<T>(); + _33 = UnspecifiedNaN<T>(); + _43 = UnspecifiedNaN<T>(); + _14 = UnspecifiedNaN<T>(); + _24 = UnspecifiedNaN<T>(); + _34 = UnspecifiedNaN<T>(); + _44 = UnspecifiedNaN<T>(); + } + + // Verifies that the matrix contains no Infs or NaNs + bool IsFinite() const { + return std::isfinite(_11) && std::isfinite(_12) && std::isfinite(_13) && + std::isfinite(_14) && std::isfinite(_21) && std::isfinite(_22) && + std::isfinite(_23) && std::isfinite(_24) && std::isfinite(_31) && + std::isfinite(_32) && std::isfinite(_33) && std::isfinite(_34) && + std::isfinite(_41) && std::isfinite(_42) && std::isfinite(_43) && + std::isfinite(_44); + } + + void SkewXY(double aXSkew, double aYSkew) { + // XXX Is double precision really necessary here + T tanX = SafeTangent(aXSkew); + T tanY = SafeTangent(aYSkew); + T temp; + + temp = _11; + _11 += tanY * _21; + _21 += tanX * temp; + + temp = _12; + _12 += tanY * _22; + _22 += tanX * temp; + + temp = _13; + _13 += tanY * _23; + _23 += tanX * temp; + + temp = _14; + _14 += tanY * _24; + _24 += tanX * temp; + } + + void RotateX(double aTheta) { + // XXX Is double precision really necessary here + double cosTheta = FlushToZero(cos(aTheta)); + double sinTheta = FlushToZero(sin(aTheta)); + + T temp; + + temp = _21; + _21 = cosTheta * _21 + sinTheta * _31; + _31 = -sinTheta * temp + cosTheta * _31; + + temp = _22; + _22 = cosTheta * _22 + sinTheta * _32; + _32 = -sinTheta * temp + cosTheta * _32; + + temp = _23; + _23 = cosTheta * _23 + sinTheta * _33; + _33 = -sinTheta * temp + cosTheta * _33; + + temp = _24; + _24 = cosTheta * _24 + sinTheta * _34; + _34 = -sinTheta * temp + cosTheta * _34; + } + + void RotateY(double aTheta) { + // XXX Is double precision really necessary here + double cosTheta = FlushToZero(cos(aTheta)); + double sinTheta = FlushToZero(sin(aTheta)); + + T temp; + + temp = _11; + _11 = cosTheta * _11 + -sinTheta * _31; + _31 = sinTheta * temp + cosTheta * _31; + + temp = _12; + _12 = cosTheta * _12 + -sinTheta * _32; + _32 = sinTheta * temp + cosTheta * _32; + + temp = _13; + _13 = cosTheta * _13 + -sinTheta * _33; + _33 = sinTheta * temp + cosTheta * _33; + + temp = _14; + _14 = cosTheta * _14 + -sinTheta * _34; + _34 = sinTheta * temp + cosTheta * _34; + } + + void RotateZ(double aTheta) { + // XXX Is double precision really necessary here + double cosTheta = FlushToZero(cos(aTheta)); + double sinTheta = FlushToZero(sin(aTheta)); + + T temp; + + temp = _11; + _11 = cosTheta * _11 + sinTheta * _21; + _21 = -sinTheta * temp + cosTheta * _21; + + temp = _12; + _12 = cosTheta * _12 + sinTheta * _22; + _22 = -sinTheta * temp + cosTheta * _22; + + temp = _13; + _13 = cosTheta * _13 + sinTheta * _23; + _23 = -sinTheta * temp + cosTheta * _23; + + temp = _14; + _14 = cosTheta * _14 + sinTheta * _24; + _24 = -sinTheta * temp + cosTheta * _24; + } + + // Sets this matrix to a rotation matrix about a + // vector [x,y,z] by angle theta. The vector is normalized + // to a unit vector. + // https://drafts.csswg.org/css-transforms-2/#Rotate3dDefined + void SetRotateAxisAngle(double aX, double aY, double aZ, double aTheta) { + Point3DTyped<UnknownUnits, T> vector(aX, aY, aZ); + if (!vector.Length()) { + return; + } + vector.RobustNormalize(); + + double x = vector.x; + double y = vector.y; + double z = vector.z; + + double cosTheta = FlushToZero(cos(aTheta)); + double sinTheta = FlushToZero(sin(aTheta)); + + // sin(aTheta / 2) * cos(aTheta / 2) + double sc = sinTheta / 2; + // pow(sin(aTheta / 2), 2) + double sq = (1 - cosTheta) / 2; + + _11 = 1 - 2 * (y * y + z * z) * sq; + _12 = 2 * (x * y * sq + z * sc); + _13 = 2 * (x * z * sq - y * sc); + _14 = 0.0f; + _21 = 2 * (x * y * sq - z * sc); + _22 = 1 - 2 * (x * x + z * z) * sq; + _23 = 2 * (y * z * sq + x * sc); + _24 = 0.0f; + _31 = 2 * (x * z * sq + y * sc); + _32 = 2 * (y * z * sq - x * sc); + _33 = 1 - 2 * (x * x + y * y) * sq; + _34 = 0.0f; + _41 = 0.0f; + _42 = 0.0f; + _43 = 0.0f; + _44 = 1.0f; + } + + void Perspective(T aDepth) { + MOZ_ASSERT(aDepth > 0.0f, "Perspective must be positive!"); + _31 += -1.0 / aDepth * _41; + _32 += -1.0 / aDepth * _42; + _33 += -1.0 / aDepth * _43; + _34 += -1.0 / aDepth * _44; + } + + Point3D GetNormalVector() const { + // Define a plane in transformed space as the transformations + // of 3 points on the z=0 screen plane. + Point3DTyped<UnknownUnits, T> a = + TransformPoint(Point3DTyped<UnknownUnits, T>(0, 0, 0)); + Point3DTyped<UnknownUnits, T> b = + TransformPoint(Point3DTyped<UnknownUnits, T>(0, 1, 0)); + Point3DTyped<UnknownUnits, T> c = + TransformPoint(Point3DTyped<UnknownUnits, T>(1, 0, 0)); + + // Convert to two vectors on the surface of the plane. + Point3DTyped<UnknownUnits, T> ab = b - a; + Point3DTyped<UnknownUnits, T> ac = c - a; + + return ac.CrossProduct(ab); + } + + /** + * Returns true if the matrix has any transform other + * than a straight translation. + */ + bool HasNonTranslation() const { + return !gfx::FuzzyEqual(_11, 1.0) || !gfx::FuzzyEqual(_22, 1.0) || + !gfx::FuzzyEqual(_12, 0.0) || !gfx::FuzzyEqual(_21, 0.0) || + !gfx::FuzzyEqual(_13, 0.0) || !gfx::FuzzyEqual(_23, 0.0) || + !gfx::FuzzyEqual(_31, 0.0) || !gfx::FuzzyEqual(_32, 0.0) || + !gfx::FuzzyEqual(_33, 1.0); + } + + /** + * Returns true if the matrix is anything other than a straight + * translation by integers. + */ + bool HasNonIntegerTranslation() const { + return HasNonTranslation() || !gfx::FuzzyEqual(_41, floor(_41 + 0.5)) || + !gfx::FuzzyEqual(_42, floor(_42 + 0.5)) || + !gfx::FuzzyEqual(_43, floor(_43 + 0.5)); + } + + /** + * Return true if the matrix is with perspective (w). + */ + bool HasPerspectiveComponent() const { + return _14 != 0 || _24 != 0 || _34 != 0 || _44 != 1; + } + + /* Returns true if the matrix is a rectilinear transformation (i.e. + * grid-aligned rectangles are transformed to grid-aligned rectangles). + * This should only be called on 2D matrices. + */ + bool IsRectilinear() const { + MOZ_ASSERT(Is2D()); + if (gfx::FuzzyEqual(_12, 0) && gfx::FuzzyEqual(_21, 0)) { + return true; + } else if (gfx::FuzzyEqual(_22, 0) && gfx::FuzzyEqual(_11, 0)) { + return true; + } + return false; + } + + /** + * Convert between typed and untyped matrices. + */ + using UnknownMatrix = Matrix4x4Typed<UnknownUnits, UnknownUnits, T>; + UnknownMatrix ToUnknownMatrix() const { + return UnknownMatrix{_11, _12, _13, _14, _21, _22, _23, _24, + _31, _32, _33, _34, _41, _42, _43, _44}; + } + static Matrix4x4Typed FromUnknownMatrix(const UnknownMatrix& aUnknown) { + return Matrix4x4Typed{ + aUnknown._11, aUnknown._12, aUnknown._13, aUnknown._14, + aUnknown._21, aUnknown._22, aUnknown._23, aUnknown._24, + aUnknown._31, aUnknown._32, aUnknown._33, aUnknown._34, + aUnknown._41, aUnknown._42, aUnknown._43, aUnknown._44}; + } + /** + * For convenience, overload FromUnknownMatrix() for Maybe<Matrix>. + */ + static Maybe<Matrix4x4Typed> FromUnknownMatrix( + const Maybe<UnknownMatrix>& aUnknown) { + if (aUnknown.isSome()) { + return Some(FromUnknownMatrix(*aUnknown)); + } + return Nothing(); + } +}; + +typedef Matrix4x4Typed<UnknownUnits, UnknownUnits> Matrix4x4; +typedef Matrix4x4Typed<UnknownUnits, UnknownUnits, double> Matrix4x4Double; + +class Matrix5x4 { + public: + Matrix5x4() + : _11(1.0f), + _12(0), + _13(0), + _14(0), + _21(0), + _22(1.0f), + _23(0), + _24(0), + _31(0), + _32(0), + _33(1.0f), + _34(0), + _41(0), + _42(0), + _43(0), + _44(1.0f), + _51(0), + _52(0), + _53(0), + _54(0) {} + Matrix5x4(Float a11, Float a12, Float a13, Float a14, Float a21, Float a22, + Float a23, Float a24, Float a31, Float a32, Float a33, Float a34, + Float a41, Float a42, Float a43, Float a44, Float a51, Float a52, + Float a53, Float a54) + : _11(a11), + _12(a12), + _13(a13), + _14(a14), + _21(a21), + _22(a22), + _23(a23), + _24(a24), + _31(a31), + _32(a32), + _33(a33), + _34(a34), + _41(a41), + _42(a42), + _43(a43), + _44(a44), + _51(a51), + _52(a52), + _53(a53), + _54(a54) {} + + bool operator==(const Matrix5x4& o) const { + return _11 == o._11 && _12 == o._12 && _13 == o._13 && _14 == o._14 && + _21 == o._21 && _22 == o._22 && _23 == o._23 && _24 == o._24 && + _31 == o._31 && _32 == o._32 && _33 == o._33 && _34 == o._34 && + _41 == o._41 && _42 == o._42 && _43 == o._43 && _44 == o._44 && + _51 == o._51 && _52 == o._52 && _53 == o._53 && _54 == o._54; + } + + bool operator!=(const Matrix5x4& aMatrix) const { + return !(*this == aMatrix); + } + + Matrix5x4 operator*(const Matrix5x4& aMatrix) const { + Matrix5x4 resultMatrix; + + resultMatrix._11 = this->_11 * aMatrix._11 + this->_12 * aMatrix._21 + + this->_13 * aMatrix._31 + this->_14 * aMatrix._41; + resultMatrix._12 = this->_11 * aMatrix._12 + this->_12 * aMatrix._22 + + this->_13 * aMatrix._32 + this->_14 * aMatrix._42; + resultMatrix._13 = this->_11 * aMatrix._13 + this->_12 * aMatrix._23 + + this->_13 * aMatrix._33 + this->_14 * aMatrix._43; + resultMatrix._14 = this->_11 * aMatrix._14 + this->_12 * aMatrix._24 + + this->_13 * aMatrix._34 + this->_14 * aMatrix._44; + resultMatrix._21 = this->_21 * aMatrix._11 + this->_22 * aMatrix._21 + + this->_23 * aMatrix._31 + this->_24 * aMatrix._41; + resultMatrix._22 = this->_21 * aMatrix._12 + this->_22 * aMatrix._22 + + this->_23 * aMatrix._32 + this->_24 * aMatrix._42; + resultMatrix._23 = this->_21 * aMatrix._13 + this->_22 * aMatrix._23 + + this->_23 * aMatrix._33 + this->_24 * aMatrix._43; + resultMatrix._24 = this->_21 * aMatrix._14 + this->_22 * aMatrix._24 + + this->_23 * aMatrix._34 + this->_24 * aMatrix._44; + resultMatrix._31 = this->_31 * aMatrix._11 + this->_32 * aMatrix._21 + + this->_33 * aMatrix._31 + this->_34 * aMatrix._41; + resultMatrix._32 = this->_31 * aMatrix._12 + this->_32 * aMatrix._22 + + this->_33 * aMatrix._32 + this->_34 * aMatrix._42; + resultMatrix._33 = this->_31 * aMatrix._13 + this->_32 * aMatrix._23 + + this->_33 * aMatrix._33 + this->_34 * aMatrix._43; + resultMatrix._34 = this->_31 * aMatrix._14 + this->_32 * aMatrix._24 + + this->_33 * aMatrix._34 + this->_34 * aMatrix._44; + resultMatrix._41 = this->_41 * aMatrix._11 + this->_42 * aMatrix._21 + + this->_43 * aMatrix._31 + this->_44 * aMatrix._41; + resultMatrix._42 = this->_41 * aMatrix._12 + this->_42 * aMatrix._22 + + this->_43 * aMatrix._32 + this->_44 * aMatrix._42; + resultMatrix._43 = this->_41 * aMatrix._13 + this->_42 * aMatrix._23 + + this->_43 * aMatrix._33 + this->_44 * aMatrix._43; + resultMatrix._44 = this->_41 * aMatrix._14 + this->_42 * aMatrix._24 + + this->_43 * aMatrix._34 + this->_44 * aMatrix._44; + resultMatrix._51 = this->_51 * aMatrix._11 + this->_52 * aMatrix._21 + + this->_53 * aMatrix._31 + this->_54 * aMatrix._41 + + aMatrix._51; + resultMatrix._52 = this->_51 * aMatrix._12 + this->_52 * aMatrix._22 + + this->_53 * aMatrix._32 + this->_54 * aMatrix._42 + + aMatrix._52; + resultMatrix._53 = this->_51 * aMatrix._13 + this->_52 * aMatrix._23 + + this->_53 * aMatrix._33 + this->_54 * aMatrix._43 + + aMatrix._53; + resultMatrix._54 = this->_51 * aMatrix._14 + this->_52 * aMatrix._24 + + this->_53 * aMatrix._34 + this->_54 * aMatrix._44 + + aMatrix._54; + + return resultMatrix; + } + + Matrix5x4& operator*=(const Matrix5x4& aMatrix) { + *this = *this * aMatrix; + return *this; + } + + friend std::ostream& operator<<(std::ostream& aStream, + const Matrix5x4& aMatrix) { + const Float* f = &aMatrix._11; + aStream << "[ " << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3] << ';'; + f += 4; + aStream << ' ' << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3] << ';'; + f += 4; + aStream << ' ' << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3] << ';'; + f += 4; + aStream << ' ' << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3] << ';'; + f += 4; + aStream << ' ' << f[0] << ' ' << f[1] << ' ' << f[2] << ' ' << f[3] + << "; ]"; + return aStream; + } + + union { + struct { + Float _11, _12, _13, _14; + Float _21, _22, _23, _24; + Float _31, _32, _33, _34; + Float _41, _42, _43, _44; + Float _51, _52, _53, _54; + }; + Float components[20]; + }; +}; + +/* This Matrix class will carry one additional type field in order to + * track what type of 4x4 matrix we're dealing with, it can then execute + * simplified versions of certain operations when applicable. + * This does not allow access to the parent class directly, as a caller + * could then mutate the parent class without updating the type. + */ +template <typename SourceUnits, typename TargetUnits> +class Matrix4x4TypedFlagged + : protected Matrix4x4Typed<SourceUnits, TargetUnits> { + public: + using Parent = Matrix4x4Typed<SourceUnits, TargetUnits>; + using TargetPoint = PointTyped<TargetUnits>; + using Parent::_11; + using Parent::_12; + using Parent::_13; + using Parent::_14; + using Parent::_21; + using Parent::_22; + using Parent::_23; + using Parent::_24; + using Parent::_31; + using Parent::_32; + using Parent::_33; + using Parent::_34; + using Parent::_41; + using Parent::_42; + using Parent::_43; + using Parent::_44; + + Matrix4x4TypedFlagged() : mType(MatrixType::Identity) {} + + Matrix4x4TypedFlagged(Float a11, Float a12, Float a13, Float a14, Float a21, + Float a22, Float a23, Float a24, Float a31, Float a32, + Float a33, Float a34, Float a41, Float a42, Float a43, + Float a44) + : Parent(a11, a12, a13, a14, a21, a22, a23, a24, a31, a32, a33, a34, a41, + a42, a43, a44) { + Analyze(); + } + + MOZ_IMPLICIT Matrix4x4TypedFlagged(const Parent& aOther) : Parent(aOther) { + Analyze(); + } + + template <typename NewMatrix4x4TypedFlagged> + [[nodiscard]] NewMatrix4x4TypedFlagged Cast() const { + return NewMatrix4x4TypedFlagged(_11, _12, _13, _14, _21, _22, _23, _24, _31, + _32, _33, _34, _41, _42, _43, _44, mType); + } + + template <class F> + PointTyped<TargetUnits, F> TransformPoint( + const PointTyped<SourceUnits, F>& aPoint) const { + if (mType == MatrixType::Identity) { + return aPoint; + } + + if (mType == MatrixType::Simple) { + return TransformPointSimple(aPoint); + } + + return Parent::TransformPoint(aPoint); + } + + template <class F> + RectTyped<TargetUnits, F> TransformAndClipBounds( + const RectTyped<SourceUnits, F>& aRect, + const RectTyped<TargetUnits, F>& aClip) const { + if (mType == MatrixType::Identity) { + const RectTyped<SourceUnits, F>& clipped = aRect.Intersect(aClip); + return RectTyped<TargetUnits, F>(clipped.X(), clipped.Y(), + clipped.Width(), clipped.Height()); + } + + if (mType == MatrixType::Simple) { + PointTyped<UnknownUnits, F> p1 = TransformPointSimple(aRect.TopLeft()); + PointTyped<UnknownUnits, F> p2 = TransformPointSimple(aRect.TopRight()); + PointTyped<UnknownUnits, F> p3 = TransformPointSimple(aRect.BottomLeft()); + PointTyped<UnknownUnits, F> p4 = + TransformPointSimple(aRect.BottomRight()); + + F min_x = std::min(std::min(std::min(p1.x, p2.x), p3.x), p4.x); + F max_x = std::max(std::max(std::max(p1.x, p2.x), p3.x), p4.x); + F min_y = std::min(std::min(std::min(p1.y, p2.y), p3.y), p4.y); + F max_y = std::max(std::max(std::max(p1.y, p2.y), p3.y), p4.y); + + TargetPoint topLeft(std::max(min_x, aClip.x), std::max(min_y, aClip.y)); + F width = std::min(max_x, aClip.XMost()) - topLeft.x; + F height = std::min(max_y, aClip.YMost()) - topLeft.y; + + return RectTyped<TargetUnits, F>(topLeft.x, topLeft.y, width, height); + } + return Parent::TransformAndClipBounds(aRect, aClip); + } + + bool FuzzyEqual(const Parent& o) const { return Parent::FuzzyEqual(o); } + + bool FuzzyEqual(const Matrix4x4TypedFlagged& o) const { + if (mType == MatrixType::Identity && o.mType == MatrixType::Identity) { + return true; + } + return Parent::FuzzyEqual(o); + } + + Matrix4x4TypedFlagged& PreTranslate(Float aX, Float aY, Float aZ) { + if (mType == MatrixType::Identity) { + _41 = aX; + _42 = aY; + _43 = aZ; + + if (!aZ) { + mType = MatrixType::Simple; + return *this; + } + mType = MatrixType::Full; + return *this; + } + + Parent::PreTranslate(aX, aY, aZ); + + if (aZ != 0) { + mType = MatrixType::Full; + } + + return *this; + } + + Matrix4x4TypedFlagged& PostTranslate(Float aX, Float aY, Float aZ) { + if (mType == MatrixType::Identity) { + _41 = aX; + _42 = aY; + _43 = aZ; + + if (!aZ) { + mType = MatrixType::Simple; + return *this; + } + mType = MatrixType::Full; + return *this; + } + + Parent::PostTranslate(aX, aY, aZ); + + if (aZ != 0) { + mType = MatrixType::Full; + } + + return *this; + } + + Matrix4x4TypedFlagged& ChangeBasis(Float aX, Float aY, Float aZ) { + // Translate to the origin before applying this matrix + PreTranslate(-aX, -aY, -aZ); + + // Translate back into position after applying this matrix + PostTranslate(aX, aY, aZ); + + return *this; + } + + bool IsIdentity() const { return mType == MatrixType::Identity; } + + template <class F> + Point4DTyped<TargetUnits, F> ProjectPoint( + const PointTyped<SourceUnits, F>& aPoint) const { + if (mType == MatrixType::Identity) { + return Point4DTyped<TargetUnits, F>(aPoint.x, aPoint.y, 0, 1); + } + + if (mType == MatrixType::Simple) { + TargetPoint point = TransformPointSimple(aPoint); + return Point4DTyped<TargetUnits, F>(point.x, point.y, 0, 1); + } + + return Parent::ProjectPoint(aPoint); + } + + Matrix4x4TypedFlagged& ProjectTo2D() { + if (mType == MatrixType::Full) { + Parent::ProjectTo2D(); + } + return *this; + } + + bool IsSingular() const { + if (mType == MatrixType::Identity) { + return false; + } + return Parent::Determinant() == 0.0; + } + + bool Invert() { + if (mType == MatrixType::Identity) { + return true; + } + + return Parent::Invert(); + } + + Matrix4x4TypedFlagged<TargetUnits, SourceUnits> Inverse() const { + typedef Matrix4x4TypedFlagged<TargetUnits, SourceUnits> InvertedMatrix; + InvertedMatrix clone = Cast<InvertedMatrix>(); + if (mType == MatrixType::Identity) { + return clone; + } + DebugOnly<bool> inverted = clone.Invert(); + MOZ_ASSERT(inverted, + "Attempted to get the inverse of a non-invertible matrix"); + + // Inverting a 2D Matrix should result in a 2D matrix, ergo mType doesn't + // change. + return clone; + } + + template <typename NewTargetUnits> + bool operator==( + const Matrix4x4TypedFlagged<TargetUnits, NewTargetUnits>& aMatrix) const { + if (mType == MatrixType::Identity && + aMatrix.mType == MatrixType::Identity) { + return true; + } + // Depending on the usage it may make sense to compare more flags. + return Parent::operator==(aMatrix); + } + + template <typename NewTargetUnits> + bool operator!=( + const Matrix4x4TypedFlagged<TargetUnits, NewTargetUnits>& aMatrix) const { + if (mType == MatrixType::Identity && + aMatrix.mType == MatrixType::Identity) { + return false; + } + // Depending on the usage it may make sense to compare more flags. + return Parent::operator!=(aMatrix); + } + + template <typename NewTargetUnits> + Matrix4x4TypedFlagged<SourceUnits, NewTargetUnits> operator*( + const Matrix4x4Typed<TargetUnits, NewTargetUnits>& aMatrix) const { + if (mType == MatrixType::Identity) { + return aMatrix; + } + + if (mType == MatrixType::Simple) { + Matrix4x4TypedFlagged<SourceUnits, NewTargetUnits> matrix; + matrix._11 = _11 * aMatrix._11 + _12 * aMatrix._21; + matrix._21 = _21 * aMatrix._11 + _22 * aMatrix._21; + matrix._31 = aMatrix._31; + matrix._41 = _41 * aMatrix._11 + _42 * aMatrix._21 + aMatrix._41; + matrix._12 = _11 * aMatrix._12 + _12 * aMatrix._22; + matrix._22 = _21 * aMatrix._12 + _22 * aMatrix._22; + matrix._32 = aMatrix._32; + matrix._42 = _41 * aMatrix._12 + _42 * aMatrix._22 + aMatrix._42; + matrix._13 = _11 * aMatrix._13 + _12 * aMatrix._23; + matrix._23 = _21 * aMatrix._13 + _22 * aMatrix._23; + matrix._33 = aMatrix._33; + matrix._43 = _41 * aMatrix._13 + _42 * aMatrix._23 + aMatrix._43; + matrix._14 = _11 * aMatrix._14 + _12 * aMatrix._24; + matrix._24 = _21 * aMatrix._14 + _22 * aMatrix._24; + matrix._34 = aMatrix._34; + matrix._44 = _41 * aMatrix._14 + _42 * aMatrix._24 + aMatrix._44; + matrix.Analyze(); + return matrix; + } + + return Parent::operator*(aMatrix); + } + + template <typename NewTargetUnits> + Matrix4x4TypedFlagged<SourceUnits, NewTargetUnits> operator*( + const Matrix4x4TypedFlagged<TargetUnits, NewTargetUnits>& aMatrix) const { + if (mType == MatrixType::Identity) { + return aMatrix; + } + + if (aMatrix.mType == MatrixType::Identity) { + return Cast<Matrix4x4TypedFlagged<SourceUnits, NewTargetUnits>>(); + } + + if (mType == MatrixType::Simple && aMatrix.mType == MatrixType::Simple) { + Matrix4x4TypedFlagged<SourceUnits, NewTargetUnits> matrix; + matrix._11 = _11 * aMatrix._11 + _12 * aMatrix._21; + matrix._21 = _21 * aMatrix._11 + _22 * aMatrix._21; + matrix._41 = _41 * aMatrix._11 + _42 * aMatrix._21 + aMatrix._41; + matrix._12 = _11 * aMatrix._12 + _12 * aMatrix._22; + matrix._22 = _21 * aMatrix._12 + _22 * aMatrix._22; + matrix._42 = _41 * aMatrix._12 + _42 * aMatrix._22 + aMatrix._42; + matrix.mType = MatrixType::Simple; + return matrix; + } else if (mType == MatrixType::Simple) { + Matrix4x4TypedFlagged<SourceUnits, NewTargetUnits> matrix; + matrix._11 = _11 * aMatrix._11 + _12 * aMatrix._21; + matrix._21 = _21 * aMatrix._11 + _22 * aMatrix._21; + matrix._31 = aMatrix._31; + matrix._41 = _41 * aMatrix._11 + _42 * aMatrix._21 + aMatrix._41; + matrix._12 = _11 * aMatrix._12 + _12 * aMatrix._22; + matrix._22 = _21 * aMatrix._12 + _22 * aMatrix._22; + matrix._32 = aMatrix._32; + matrix._42 = _41 * aMatrix._12 + _42 * aMatrix._22 + aMatrix._42; + matrix._13 = _11 * aMatrix._13 + _12 * aMatrix._23; + matrix._23 = _21 * aMatrix._13 + _22 * aMatrix._23; + matrix._33 = aMatrix._33; + matrix._43 = _41 * aMatrix._13 + _42 * aMatrix._23 + aMatrix._43; + matrix._14 = _11 * aMatrix._14 + _12 * aMatrix._24; + matrix._24 = _21 * aMatrix._14 + _22 * aMatrix._24; + matrix._34 = aMatrix._34; + matrix._44 = _41 * aMatrix._14 + _42 * aMatrix._24 + aMatrix._44; + matrix.mType = MatrixType::Full; + return matrix; + } else if (aMatrix.mType == MatrixType::Simple) { + Matrix4x4TypedFlagged<SourceUnits, NewTargetUnits> matrix; + matrix._11 = _11 * aMatrix._11 + _12 * aMatrix._21 + _14 * aMatrix._41; + matrix._21 = _21 * aMatrix._11 + _22 * aMatrix._21 + _24 * aMatrix._41; + matrix._31 = _31 * aMatrix._11 + _32 * aMatrix._21 + _34 * aMatrix._41; + matrix._41 = _41 * aMatrix._11 + _42 * aMatrix._21 + _44 * aMatrix._41; + matrix._12 = _11 * aMatrix._12 + _12 * aMatrix._22 + _14 * aMatrix._42; + matrix._22 = _21 * aMatrix._12 + _22 * aMatrix._22 + _24 * aMatrix._42; + matrix._32 = _31 * aMatrix._12 + _32 * aMatrix._22 + _34 * aMatrix._42; + matrix._42 = _41 * aMatrix._12 + _42 * aMatrix._22 + _44 * aMatrix._42; + matrix._13 = _13; + matrix._23 = _23; + matrix._33 = _33; + matrix._43 = _43; + matrix._14 = _14; + matrix._24 = _24; + matrix._34 = _34; + matrix._44 = _44; + matrix.mType = MatrixType::Full; + return matrix; + } + + return Parent::operator*(aMatrix); + } + + bool Is2D() const { return mType != MatrixType::Full; } + + bool CanDraw2D(Matrix* aMatrix = nullptr) const { + if (mType != MatrixType::Full) { + if (aMatrix) { + aMatrix->_11 = _11; + aMatrix->_12 = _12; + aMatrix->_21 = _21; + aMatrix->_22 = _22; + aMatrix->_31 = _41; + aMatrix->_32 = _42; + } + return true; + } + return Parent::CanDraw2D(aMatrix); + } + + bool Is2D(Matrix* aMatrix) const { + if (!Is2D()) { + return false; + } + if (aMatrix) { + aMatrix->_11 = _11; + aMatrix->_12 = _12; + aMatrix->_21 = _21; + aMatrix->_22 = _22; + aMatrix->_31 = _41; + aMatrix->_32 = _42; + } + return true; + } + + template <class F> + RectTyped<TargetUnits, F> ProjectRectBounds( + const RectTyped<SourceUnits, F>& aRect, + const RectTyped<TargetUnits, F>& aClip) const { + return Parent::ProjectRectBounds(aRect, aClip); + } + + const Parent& GetMatrix() const { return *this; } + + private: + enum class MatrixType : uint8_t { + Identity, + Simple, // 2x3 Matrix + Full // 4x4 Matrix + }; + + Matrix4x4TypedFlagged(Float a11, Float a12, Float a13, Float a14, Float a21, + Float a22, Float a23, Float a24, Float a31, Float a32, + Float a33, Float a34, Float a41, Float a42, Float a43, + Float a44, + typename Matrix4x4TypedFlagged::MatrixType aType) + : Parent(a11, a12, a13, a14, a21, a22, a23, a24, a31, a32, a33, a34, a41, + a42, a43, a44) { + mType = aType; + } + static Matrix4x4TypedFlagged FromUnknownMatrix( + const Matrix4x4Flagged& aUnknown) { + return Matrix4x4TypedFlagged{ + aUnknown._11, aUnknown._12, aUnknown._13, aUnknown._14, aUnknown._21, + aUnknown._22, aUnknown._23, aUnknown._24, aUnknown._31, aUnknown._32, + aUnknown._33, aUnknown._34, aUnknown._41, aUnknown._42, aUnknown._43, + aUnknown._44, aUnknown.mType}; + } + Matrix4x4Flagged ToUnknownMatrix() const { + return Matrix4x4Flagged{_11, _12, _13, _14, _21, _22, _23, _24, _31, + _32, _33, _34, _41, _42, _43, _44, mType}; + } + + template <class F> + PointTyped<TargetUnits, F> TransformPointSimple( + const PointTyped<SourceUnits, F>& aPoint) const { + PointTyped<SourceUnits, F> temp; + temp.x = aPoint.x * _11 + aPoint.y * +_21 + _41; + temp.y = aPoint.x * _12 + aPoint.y * +_22 + _42; + return temp; + } + + void Analyze() { + if (Parent::IsIdentity()) { + mType = MatrixType::Identity; + return; + } + + if (Parent::Is2D()) { + mType = MatrixType::Simple; + return; + } + + mType = MatrixType::Full; + } + + MatrixType mType; +}; + +using Matrix4x4Flagged = Matrix4x4TypedFlagged<UnknownUnits, UnknownUnits>; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_MATRIX_H_ */ diff --git a/gfx/2d/MatrixFwd.h b/gfx/2d/MatrixFwd.h new file mode 100644 index 0000000000..494aadb887 --- /dev/null +++ b/gfx/2d/MatrixFwd.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_MATRIX_FWD_H_ +#define MOZILLA_GFX_MATRIX_FWD_H_ + +// Forward declare enough things to define the typedefs |Matrix| and +// |Matrix4x4|. + +namespace mozilla { +namespace gfx { + +template <class T> +class BaseMatrix; + +typedef float Float; +typedef BaseMatrix<Float> Matrix; + +typedef double Double; +typedef BaseMatrix<Double> MatrixDouble; + +struct UnknownUnits; + +template <class SourceUnits, class TargetUnits, class T = Float> +class Matrix4x4Typed; +template <class SourceUnits, class TargetUnits> +class Matrix4x4TypedFlagged; + +typedef Matrix4x4Typed<UnknownUnits, UnknownUnits> Matrix4x4; +typedef Matrix4x4Typed<UnknownUnits, UnknownUnits, double> Matrix4x4Double; +typedef Matrix4x4TypedFlagged<UnknownUnits, UnknownUnits> Matrix4x4Flagged; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/NativeFontResource.cpp b/gfx/2d/NativeFontResource.cpp new file mode 100644 index 0000000000..1c6a9fe0af --- /dev/null +++ b/gfx/2d/NativeFontResource.cpp @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "2D.h" +#include "nsIMemoryReporter.h" + +namespace mozilla { +namespace gfx { + +static Atomic<size_t> gTotalNativeFontResourceData; + +NativeFontResource::NativeFontResource(size_t aDataLength) + : mDataLength(aDataLength) { + gTotalNativeFontResourceData += mDataLength; +} + +NativeFontResource::~NativeFontResource() { + gTotalNativeFontResourceData -= mDataLength; +} + +// Memory reporter that estimates the amount of memory that is currently being +// allocated internally by various native font APIs for native font resources. +// The sanest way to do this, given that NativeFontResources can be created and +// used in many different threads or processes and given that such memory is +// implicitly allocated by the native APIs, is just to maintain a global atomic +// counter and report this value as such. +class NativeFontResourceDataMemoryReporter final : public nsIMemoryReporter { + ~NativeFontResourceDataMemoryReporter() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + MOZ_COLLECT_REPORT("explicit/gfx/native-font-resource-data", KIND_HEAP, + UNITS_BYTES, gTotalNativeFontResourceData, + "Total memory used by native font API resource data."); + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(NativeFontResourceDataMemoryReporter, nsIMemoryReporter) + +void NativeFontResource::RegisterMemoryReporter() { + RegisterStrongMemoryReporter(new NativeFontResourceDataMemoryReporter); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/NativeFontResourceDWrite.cpp b/gfx/2d/NativeFontResourceDWrite.cpp new file mode 100644 index 0000000000..e0b599fa76 --- /dev/null +++ b/gfx/2d/NativeFontResourceDWrite.cpp @@ -0,0 +1,269 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "NativeFontResourceDWrite.h" +#include "UnscaledFontDWrite.h" + +#include <unordered_map> + +#include "Logging.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticMutex.h" +#include "nsTArray.h" + +namespace mozilla { +namespace gfx { + +static StaticMutex sFontFileStreamsMutex MOZ_UNANNOTATED; +static uint64_t sNextFontFileKey = 0; +static std::unordered_map<uint64_t, IDWriteFontFileStream*> sFontFileStreams; + +class DWriteFontFileLoader : public IDWriteFontFileLoader { + public: + DWriteFontFileLoader() {} + + // IUnknown interface + IFACEMETHOD(QueryInterface)(IID const& iid, OUT void** ppObject) { + if (iid == __uuidof(IDWriteFontFileLoader)) { + *ppObject = static_cast<IDWriteFontFileLoader*>(this); + return S_OK; + } else if (iid == __uuidof(IUnknown)) { + *ppObject = static_cast<IUnknown*>(this); + return S_OK; + } else { + return E_NOINTERFACE; + } + } + + IFACEMETHOD_(ULONG, AddRef)() { return 1; } + + IFACEMETHOD_(ULONG, Release)() { return 1; } + + // IDWriteFontFileLoader methods + /** + * Important! Note the key here has to be a uint64_t that will have been + * generated by incrementing sNextFontFileKey. + */ + virtual HRESULT STDMETHODCALLTYPE CreateStreamFromKey( + void const* fontFileReferenceKey, UINT32 fontFileReferenceKeySize, + OUT IDWriteFontFileStream** fontFileStream); + + /** + * Gets the singleton loader instance. Note that when using this font + * loader, the key must be a uint64_t that has been generated by incrementing + * sNextFontFileKey. + * Also note that this is _not_ threadsafe. + */ + static IDWriteFontFileLoader* Instance() { + if (!mInstance) { + mInstance = new DWriteFontFileLoader(); + Factory::GetDWriteFactory()->RegisterFontFileLoader(mInstance); + } + return mInstance; + } + + private: + static IDWriteFontFileLoader* mInstance; +}; + +class DWriteFontFileStream final : public IDWriteFontFileStream { + public: + explicit DWriteFontFileStream(uint64_t aFontFileKey); + + /** + * Used by the FontFileLoader to create a new font stream, + * this font stream is created from data in memory. The memory + * passed may be released after object creation, it will be + * copied internally. + * + * @param aData Font data + */ + bool Initialize(uint8_t* aData, uint32_t aSize); + + // IUnknown interface + IFACEMETHOD(QueryInterface)(IID const& iid, OUT void** ppObject) { + if (iid == __uuidof(IDWriteFontFileStream)) { + *ppObject = static_cast<IDWriteFontFileStream*>(this); + return S_OK; + } else if (iid == __uuidof(IUnknown)) { + *ppObject = static_cast<IUnknown*>(this); + return S_OK; + } else { + return E_NOINTERFACE; + } + } + + IFACEMETHOD_(ULONG, AddRef)() { return ++mRefCnt; } + + IFACEMETHOD_(ULONG, Release)() { + uint32_t count = --mRefCnt; + if (count == 0) { + // Avoid locking unless necessary. Verify the refcount hasn't changed + // while locked. Delete within the scope of the lock when zero. + StaticMutexAutoLock lock(sFontFileStreamsMutex); + if (0 != mRefCnt) { + return mRefCnt; + } + delete this; + } + return count; + } + + // IDWriteFontFileStream methods + virtual HRESULT STDMETHODCALLTYPE + ReadFileFragment(void const** fragmentStart, UINT64 fileOffset, + UINT64 fragmentSize, OUT void** fragmentContext); + + virtual void STDMETHODCALLTYPE ReleaseFileFragment(void* fragmentContext); + + virtual HRESULT STDMETHODCALLTYPE GetFileSize(OUT UINT64* fileSize); + + virtual HRESULT STDMETHODCALLTYPE GetLastWriteTime(OUT UINT64* lastWriteTime); + + private: + nsTArray<uint8_t> mData; + Atomic<uint32_t> mRefCnt; + uint64_t mFontFileKey; + + ~DWriteFontFileStream(); +}; + +IDWriteFontFileLoader* DWriteFontFileLoader::mInstance = nullptr; + +HRESULT STDMETHODCALLTYPE DWriteFontFileLoader::CreateStreamFromKey( + const void* fontFileReferenceKey, UINT32 fontFileReferenceKeySize, + IDWriteFontFileStream** fontFileStream) { + if (!fontFileReferenceKey || !fontFileStream) { + return E_POINTER; + } + + StaticMutexAutoLock lock(sFontFileStreamsMutex); + uint64_t fontFileKey = *static_cast<const uint64_t*>(fontFileReferenceKey); + auto found = sFontFileStreams.find(fontFileKey); + if (found == sFontFileStreams.end()) { + *fontFileStream = nullptr; + return E_FAIL; + } + + found->second->AddRef(); + *fontFileStream = found->second; + return S_OK; +} + +DWriteFontFileStream::DWriteFontFileStream(uint64_t aFontFileKey) + : mRefCnt(0), mFontFileKey(aFontFileKey) {} + +DWriteFontFileStream::~DWriteFontFileStream() { + sFontFileStreams.erase(mFontFileKey); +} + +bool DWriteFontFileStream::Initialize(uint8_t* aData, uint32_t aSize) { + if (!mData.SetLength(aSize, fallible)) { + return false; + } + memcpy(mData.Elements(), aData, aSize); + return true; +} + +HRESULT STDMETHODCALLTYPE DWriteFontFileStream::GetFileSize(UINT64* fileSize) { + *fileSize = mData.Length(); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE +DWriteFontFileStream::GetLastWriteTime(UINT64* lastWriteTime) { + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE DWriteFontFileStream::ReadFileFragment( + const void** fragmentStart, UINT64 fileOffset, UINT64 fragmentSize, + void** fragmentContext) { + // We are required to do bounds checking. + if (fileOffset + fragmentSize > mData.Length()) { + return E_FAIL; + } + + // truncate the 64 bit fileOffset to size_t sized index into mData + size_t index = static_cast<size_t>(fileOffset); + + // We should be alive for the duration of this. + *fragmentStart = &mData[index]; + *fragmentContext = nullptr; + return S_OK; +} + +void STDMETHODCALLTYPE +DWriteFontFileStream::ReleaseFileFragment(void* fragmentContext) {} + +/* static */ +already_AddRefed<NativeFontResourceDWrite> NativeFontResourceDWrite::Create( + uint8_t* aFontData, uint32_t aDataLength) { + RefPtr<IDWriteFactory> factory = Factory::GetDWriteFactory(); + if (!factory) { + gfxWarning() << "Failed to get DWrite Factory."; + return nullptr; + } + + sFontFileStreamsMutex.Lock(); + uint64_t fontFileKey = sNextFontFileKey++; + RefPtr<DWriteFontFileStream> ffsRef = new DWriteFontFileStream(fontFileKey); + if (!ffsRef->Initialize(aFontData, aDataLength)) { + sFontFileStreamsMutex.Unlock(); + gfxWarning() << "Failed to create DWriteFontFileStream."; + return nullptr; + } + sFontFileStreams[fontFileKey] = ffsRef; + sFontFileStreamsMutex.Unlock(); + + RefPtr<IDWriteFontFile> fontFile; + HRESULT hr = factory->CreateCustomFontFileReference( + &fontFileKey, sizeof(fontFileKey), DWriteFontFileLoader::Instance(), + getter_AddRefs(fontFile)); + if (FAILED(hr)) { + gfxWarning() << "Failed to load font file from data!"; + return nullptr; + } + + BOOL isSupported; + DWRITE_FONT_FILE_TYPE fileType; + DWRITE_FONT_FACE_TYPE faceType; + UINT32 numberOfFaces; + hr = fontFile->Analyze(&isSupported, &fileType, &faceType, &numberOfFaces); + if (FAILED(hr) || !isSupported) { + gfxWarning() << "Font file is not supported."; + return nullptr; + } + + RefPtr<NativeFontResourceDWrite> fontResource = + new NativeFontResourceDWrite(factory, fontFile.forget(), ffsRef.forget(), + faceType, numberOfFaces, aDataLength); + return fontResource.forget(); +} + +already_AddRefed<UnscaledFont> NativeFontResourceDWrite::CreateUnscaledFont( + uint32_t aIndex, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength) { + if (aIndex >= mNumberOfFaces) { + gfxWarning() << "Font face index is too high for font resource."; + return nullptr; + } + + IDWriteFontFile* fontFile = mFontFile; + RefPtr<IDWriteFontFace> fontFace; + if (FAILED(mFactory->CreateFontFace(mFaceType, 1, &fontFile, aIndex, + DWRITE_FONT_SIMULATIONS_NONE, + getter_AddRefs(fontFace)))) { + gfxWarning() << "Failed to create font face from font file data."; + return nullptr; + } + + RefPtr<UnscaledFont> unscaledFont = new UnscaledFontDWrite(fontFace, nullptr); + + return unscaledFont.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/NativeFontResourceDWrite.h b/gfx/2d/NativeFontResourceDWrite.h new file mode 100644 index 0000000000..0f3feccb18 --- /dev/null +++ b/gfx/2d/NativeFontResourceDWrite.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_NativeFontResourceDWrite_h +#define mozilla_gfx_NativeFontResourceDWrite_h + +#include <dwrite.h> + +#include "2D.h" +#include "mozilla/AlreadyAddRefed.h" + +namespace mozilla { +namespace gfx { + +class NativeFontResourceDWrite final : public NativeFontResource { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(NativeFontResourceDWrite, override) + + /** + * Creates a NativeFontResourceDWrite if data is valid. Note aFontData will be + * copied if required and so can be released after calling. + * + * @param aFontData the SFNT data. + * @param aDataLength length of data. + * @return Referenced NativeFontResourceDWrite or nullptr if invalid. + */ + static already_AddRefed<NativeFontResourceDWrite> Create( + uint8_t* aFontData, uint32_t aDataLength); + + already_AddRefed<UnscaledFont> CreateUnscaledFont( + uint32_t aIndex, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength) final; + + private: + NativeFontResourceDWrite( + IDWriteFactory* aFactory, already_AddRefed<IDWriteFontFile> aFontFile, + already_AddRefed<IDWriteFontFileStream> aFontFileStream, + DWRITE_FONT_FACE_TYPE aFaceType, uint32_t aNumberOfFaces, + size_t aDataLength) + : NativeFontResource(aDataLength), + mFactory(aFactory), + mFontFile(aFontFile), + mFontFileStream(aFontFileStream), + mFaceType(aFaceType), + mNumberOfFaces(aNumberOfFaces) {} + + IDWriteFactory* mFactory; + RefPtr<IDWriteFontFile> mFontFile; + RefPtr<IDWriteFontFileStream> mFontFileStream; + DWRITE_FONT_FACE_TYPE mFaceType; + uint32_t mNumberOfFaces; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // mozilla_gfx_NativeFontResourceDWrite_h diff --git a/gfx/2d/NativeFontResourceFreeType.cpp b/gfx/2d/NativeFontResourceFreeType.cpp new file mode 100644 index 0000000000..e00dfafe21 --- /dev/null +++ b/gfx/2d/NativeFontResourceFreeType.cpp @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "NativeFontResourceFreeType.h" +#include "UnscaledFontFreeType.h" + +namespace mozilla::gfx { + +NativeFontResourceFreeType::NativeFontResourceFreeType( + UniquePtr<uint8_t[]>&& aFontData, uint32_t aDataLength, + FT_Library aFTLibrary) + : NativeFontResource(aDataLength), + mFontData(std::move(aFontData)), + mDataLength(aDataLength), + mFTLibrary(aFTLibrary) {} + +NativeFontResourceFreeType::~NativeFontResourceFreeType() = default; + +template <class T> +already_AddRefed<T> NativeFontResourceFreeType::CreateInternal( + uint8_t* aFontData, uint32_t aDataLength, FT_Library aFTLibrary) { + if (!aFontData || !aDataLength) { + return nullptr; + } + UniquePtr<uint8_t[]> fontData(new (fallible) uint8_t[aDataLength]); + if (!fontData) { + return nullptr; + } + memcpy(fontData.get(), aFontData, aDataLength); + + RefPtr<T> resource = new T(std::move(fontData), aDataLength, aFTLibrary); + return resource.forget(); +} + +#ifdef MOZ_WIDGET_ANDROID +already_AddRefed<NativeFontResourceFreeType> NativeFontResourceFreeType::Create( + uint8_t* aFontData, uint32_t aDataLength, FT_Library aFTLibrary) { + return CreateInternal<NativeFontResourceFreeType>(aFontData, aDataLength, + aFTLibrary); +} + +already_AddRefed<UnscaledFont> NativeFontResourceFreeType::CreateUnscaledFont( + uint32_t aIndex, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength) { + if (RefPtr<SharedFTFace> face = CloneFace()) { + return MakeAndAddRef<UnscaledFontFreeType>(std::move(face)); + } + return nullptr; +} +#endif + +already_AddRefed<SharedFTFace> NativeFontResourceFreeType::CloneFace( + int aFaceIndex) { + RefPtr<SharedFTFace> face = Factory::NewSharedFTFaceFromData( + mFTLibrary, mFontData.get(), mDataLength, aFaceIndex, this); + if (!face || + (FT_Select_Charmap(face->GetFace(), FT_ENCODING_UNICODE) != FT_Err_Ok && + FT_Select_Charmap(face->GetFace(), FT_ENCODING_MS_SYMBOL) != + FT_Err_Ok)) { + return nullptr; + } + return face.forget(); +} + +#ifdef MOZ_WIDGET_GTK +NativeFontResourceFontconfig::NativeFontResourceFontconfig( + UniquePtr<uint8_t[]>&& aFontData, uint32_t aDataLength, + FT_Library aFTLibrary) + : NativeFontResourceFreeType(std::move(aFontData), aDataLength, + aFTLibrary) {} + +already_AddRefed<UnscaledFont> NativeFontResourceFontconfig::CreateUnscaledFont( + uint32_t aIndex, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength) { + if (RefPtr<SharedFTFace> face = CloneFace()) { + return MakeAndAddRef<UnscaledFontFontconfig>(std::move(face)); + } + return nullptr; +} + +already_AddRefed<NativeFontResourceFontconfig> +NativeFontResourceFontconfig::Create(uint8_t* aFontData, uint32_t aDataLength, + FT_Library aFTLibrary) { + return CreateInternal<NativeFontResourceFontconfig>(aFontData, aDataLength, + aFTLibrary); +} +#endif + +} // namespace mozilla::gfx diff --git a/gfx/2d/NativeFontResourceFreeType.h b/gfx/2d/NativeFontResourceFreeType.h new file mode 100644 index 0000000000..381dfe0c0b --- /dev/null +++ b/gfx/2d/NativeFontResourceFreeType.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_NativeFontResourceFreeType_h +#define mozilla_gfx_NativeFontResourceFreeType_h + +#include "2D.h" + +#include <cairo-ft.h> +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace gfx { + +class NativeFontResourceFreeType + : public NativeFontResource, + public SharedFTFaceRefCountedData<NativeFontResourceFreeType> { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(NativeFontResourceFreeType, override) + +#ifdef MOZ_WIDGET_ANDROID + static already_AddRefed<NativeFontResourceFreeType> Create( + uint8_t* aFontData, uint32_t aDataLength, + FT_Library aFTLibrary = nullptr); + + already_AddRefed<UnscaledFont> CreateUnscaledFont( + uint32_t aIndex, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength) override; +#endif + + ~NativeFontResourceFreeType(); + + already_AddRefed<SharedFTFace> CloneFace(int aFaceIndex = 0) override; + + protected: + NativeFontResourceFreeType(UniquePtr<uint8_t[]>&& aFontData, + uint32_t aDataLength, + FT_Library aFTLibrary = nullptr); + + template <class T> + static already_AddRefed<T> CreateInternal(uint8_t* aFontData, + uint32_t aDataLength, + FT_Library aFTLibrary); + + UniquePtr<uint8_t[]> mFontData; + uint32_t mDataLength; + FT_Library mFTLibrary; +}; + +#ifdef MOZ_WIDGET_GTK +class NativeFontResourceFontconfig final : public NativeFontResourceFreeType { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(NativeFontResourceFontconfig, + override) + + static already_AddRefed<NativeFontResourceFontconfig> Create( + uint8_t* aFontData, uint32_t aDataLength, + FT_Library aFTLibrary = nullptr); + + already_AddRefed<UnscaledFont> CreateUnscaledFont( + uint32_t aIndex, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength) final; + + private: + friend class NativeFontResourceFreeType; + + NativeFontResourceFontconfig(UniquePtr<uint8_t[]>&& aFontData, + uint32_t aDataLength, + FT_Library aFTLibrary = nullptr); +}; +#endif + +} // namespace gfx +} // namespace mozilla + +#endif // mozilla_gfx_NativeFontResourceFreeType_h diff --git a/gfx/2d/NativeFontResourceGDI.cpp b/gfx/2d/NativeFontResourceGDI.cpp new file mode 100644 index 0000000000..f4a365da7a --- /dev/null +++ b/gfx/2d/NativeFontResourceGDI.cpp @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "NativeFontResourceGDI.h" + +#include "Logging.h" +#include "mozilla/RefPtr.h" +#include "ScaledFontWin.h" +#include "UnscaledFontGDI.h" + +namespace mozilla { +namespace gfx { + +/* static */ +already_AddRefed<NativeFontResourceGDI> NativeFontResourceGDI::Create( + uint8_t* aFontData, uint32_t aDataLength) { + DWORD numberOfFontsAdded; + HANDLE fontResourceHandle = + ::AddFontMemResourceEx(aFontData, aDataLength, 0, &numberOfFontsAdded); + if (!fontResourceHandle) { + gfxWarning() << "Failed to add memory font resource."; + return nullptr; + } + + RefPtr<NativeFontResourceGDI> fontResouce = + new NativeFontResourceGDI(fontResourceHandle, aDataLength); + + return fontResouce.forget(); +} + +NativeFontResourceGDI::~NativeFontResourceGDI() { + ::RemoveFontMemResourceEx(mFontResourceHandle); +} + +already_AddRefed<UnscaledFont> NativeFontResourceGDI::CreateUnscaledFont( + uint32_t aIndex, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength) { + if (aInstanceDataLength < sizeof(LOGFONT)) { + gfxWarning() << "GDI unscaled font instance data is truncated."; + return nullptr; + } + + const LOGFONT* logFont = reinterpret_cast<const LOGFONT*>(aInstanceData); + RefPtr<UnscaledFont> unscaledFont = new UnscaledFontGDI(*logFont); + return unscaledFont.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/NativeFontResourceGDI.h b/gfx/2d/NativeFontResourceGDI.h new file mode 100644 index 0000000000..7f71e8d28d --- /dev/null +++ b/gfx/2d/NativeFontResourceGDI.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_NativeFontResourceGDI_h +#define mozilla_gfx_NativeFontResourceGDI_h + +#include <windows.h> + +#include "2D.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Vector.h" + +namespace mozilla { +namespace gfx { + +class NativeFontResourceGDI final : public NativeFontResource { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(NativeFontResourceGDI, override) + + /** + * Creates a NativeFontResourceGDI if data is valid. Note aFontData will be + * copied if required and so can be released after calling. + * + * @param aFontData the SFNT data. + * @param aDataLength length of data. + * @return Referenced NativeFontResourceGDI or nullptr if invalid. + */ + static already_AddRefed<NativeFontResourceGDI> Create(uint8_t* aFontData, + uint32_t aDataLength); + + virtual ~NativeFontResourceGDI(); + + already_AddRefed<UnscaledFont> CreateUnscaledFont( + uint32_t aIndex, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength) final; + + private: + explicit NativeFontResourceGDI(HANDLE aFontResourceHandle, size_t aDataLength) + : NativeFontResource(aDataLength), + mFontResourceHandle(aFontResourceHandle) {} + + HANDLE mFontResourceHandle; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // mozilla_gfx_NativeFontResourceGDI_h diff --git a/gfx/2d/NativeFontResourceMac.cpp b/gfx/2d/NativeFontResourceMac.cpp new file mode 100644 index 0000000000..448db76726 --- /dev/null +++ b/gfx/2d/NativeFontResourceMac.cpp @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <unordered_map> +#include <unordered_set> +#include "NativeFontResourceMac.h" +#include "UnscaledFontMac.h" +#include "Types.h" + +#include "mozilla/RefPtr.h" +#include "mozilla/DataMutex.h" + +#ifdef MOZ_WIDGET_UIKIT +# include <CoreFoundation/CoreFoundation.h> +#endif + +#include "nsIMemoryReporter.h" + +namespace mozilla { +namespace gfx { + +#define FONT_NAME_MAX 32 +static StaticDataMutex<std::unordered_map<void*, nsAutoCStringN<FONT_NAME_MAX>>> + sWeakFontDataMap("WeakFonts"); + +void FontDataDeallocate(void*, void* info) { + auto fontMap = sWeakFontDataMap.Lock(); + fontMap->erase(info); + free(info); +} + +class NativeFontResourceMacReporter final : public nsIMemoryReporter { + ~NativeFontResourceMacReporter() = default; + + MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + auto fontMap = sWeakFontDataMap.Lock(); + + nsAutoCString path("explicit/gfx/native-font-resource-mac/font("); + + unsigned int unknownFontIndex = 0; + for (auto& i : *fontMap) { + nsAutoCString subPath(path); + + if (aAnonymize) { + subPath.AppendPrintf("<anonymized-%p>", this); + } else { + if (i.second.Length()) { + subPath.AppendLiteral("psname="); + subPath.Append(i.second); + } else { + subPath.AppendPrintf("Unknown(%d)", unknownFontIndex); + } + } + + size_t bytes = MallocSizeOf(i.first) + FONT_NAME_MAX; + + subPath.Append(")"); + + aHandleReport->Callback(""_ns, subPath, KIND_HEAP, UNITS_BYTES, bytes, + "Memory used by this native font."_ns, aData); + + unknownFontIndex++; + } + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(NativeFontResourceMacReporter, nsIMemoryReporter) + +void NativeFontResourceMac::RegisterMemoryReporter() { + RegisterStrongMemoryReporter(new NativeFontResourceMacReporter); +} + +/* static */ +already_AddRefed<NativeFontResourceMac> NativeFontResourceMac::Create( + uint8_t* aFontData, uint32_t aDataLength) { + uint8_t* fontData = (uint8_t*)malloc(aDataLength); + if (!fontData) { + return nullptr; + } + memcpy(fontData, aFontData, aDataLength); + CFAllocatorContext context = {0, fontData, nullptr, nullptr, + nullptr, nullptr, nullptr, FontDataDeallocate, + nullptr}; + CFAllocatorRef allocator = CFAllocatorCreate(kCFAllocatorDefault, &context); + + // We create a CFDataRef here that we'l hold until we've determined that we + // have a valid font. If and only if we can create a font from the data, + // we'll store the font data in our map. Whether or not the font is valid, + // we'll later release this CFDataRef. + CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, fontData, + aDataLength, allocator); + if (!data) { + free(fontData); + return nullptr; + } + + CTFontDescriptorRef ctFontDesc = + CTFontManagerCreateFontDescriptorFromData(data); + if (!ctFontDesc) { + CFRelease(data); + return nullptr; + } + + // creating the CGFontRef via the CTFont avoids the data being held alive + // in a cache. + CTFontRef ctFont = CTFontCreateWithFontDescriptor(ctFontDesc, 0, NULL); + + // Creating the CGFont from the CTFont prevents the font data from being + // held in the TDescriptorSource cache. This appears to be true even + // if we later create a CTFont from the CGFont. + CGFontRef fontRef = CTFontCopyGraphicsFont(ctFont, NULL); + CFRelease(ctFont); + + if (!fontRef) { + // Not a valid font; release the structures we've been holding. + CFRelease(data); + CFRelease(ctFontDesc); + return nullptr; + } + + // Determine the font name and store it with the font data in the map. + nsAutoCStringN<FONT_NAME_MAX> fontName; + + CFStringRef psname = CGFontCopyPostScriptName(fontRef); + if (psname) { + const char* cstr = CFStringGetCStringPtr(psname, kCFStringEncodingUTF8); + if (cstr) { + fontName.Assign(cstr); + } else { + char buf[FONT_NAME_MAX]; + if (CFStringGetCString(psname, buf, FONT_NAME_MAX, + kCFStringEncodingUTF8)) { + fontName.Assign(buf); + } + } + CFRelease(psname); + } + + { + auto fontMap = sWeakFontDataMap.Lock(); + void* key = (void*)fontData; + fontMap->insert({key, fontName}); + } + // It's now safe to release our CFDataRef. + CFRelease(data); + + // passes ownership of fontRef to the NativeFontResourceMac instance + RefPtr<NativeFontResourceMac> fontResource = + new NativeFontResourceMac(ctFontDesc, fontRef, aDataLength); + + return fontResource.forget(); +} + +already_AddRefed<UnscaledFont> NativeFontResourceMac::CreateUnscaledFont( + uint32_t aIndex, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength) { + RefPtr<UnscaledFont> unscaledFont = + new UnscaledFontMac(mFontDescRef, mFontRef, true); + + return unscaledFont.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/NativeFontResourceMac.h b/gfx/2d/NativeFontResourceMac.h new file mode 100644 index 0000000000..97683a8a4f --- /dev/null +++ b/gfx/2d/NativeFontResourceMac.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_NativeFontResourceMac_h +#define mozilla_gfx_NativeFontResourceMac_h + +#include "2D.h" +#include "mozilla/AlreadyAddRefed.h" +#include "ScaledFontMac.h" + +namespace mozilla { +namespace gfx { + +class NativeFontResourceMac final : public NativeFontResource { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(NativeFontResourceMac, override) + + static already_AddRefed<NativeFontResourceMac> Create(uint8_t* aFontData, + uint32_t aDataLength); + + already_AddRefed<UnscaledFont> CreateUnscaledFont( + uint32_t aIndex, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength) final; + + ~NativeFontResourceMac() { + CFRelease(mFontDescRef); + CFRelease(mFontRef); + } + + static void RegisterMemoryReporter(); + + private: + explicit NativeFontResourceMac(CTFontDescriptorRef aFontDescRef, + CGFontRef aFontRef, size_t aDataLength) + : NativeFontResource(aDataLength), + mFontDescRef(aFontDescRef), + mFontRef(aFontRef) {} + + CTFontDescriptorRef mFontDescRef; + CGFontRef mFontRef; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // mozilla_gfx_NativeFontResourceMac_h diff --git a/gfx/2d/NumericTools.h b/gfx/2d/NumericTools.h new file mode 100644 index 0000000000..d60cf1cc8d --- /dev/null +++ b/gfx/2d/NumericTools.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_NUMERICTOOLS_H_ +#define MOZILLA_GFX_NUMERICTOOLS_H_ + +#include <cstdint> + +namespace mozilla { + +// XXX - Move these into mfbt/MathAlgorithms.h? + +// Returns the largest multiple of aMultiplied that's <= x. +// Same as int32_t(floor(double(x) / aMultiplier)) * aMultiplier, +// but faster. +inline int32_t RoundDownToMultiple(int32_t x, int32_t aMultiplier) { + // We don't use float division + floor because that's hard for the compiler + // to optimize. + int mod = x % aMultiplier; + if (x > 0) { + return x - mod; + } + return mod ? x - aMultiplier - mod : x; +} + +// Returns the smallest multiple of aMultiplied that's >= x. +// Same as int32_t(ceil(double(x) / aMultiplier)) * aMultiplier, +// but faster. +inline int32_t RoundUpToMultiple(int32_t x, int32_t aMultiplier) { + int mod = x % aMultiplier; + if (x > 0) { + return mod ? x + aMultiplier - mod : x; + } + return x - mod; +} + +inline int32_t RoundToMultiple(int32_t x, int32_t aMultiplier) { + return RoundDownToMultiple(x + aMultiplier / 2, aMultiplier); +} + +} // namespace mozilla + +#endif /* MOZILLA_GFX_NUMERICTOOLS_H_ */ diff --git a/gfx/2d/Path.cpp b/gfx/2d/Path.cpp new file mode 100644 index 0000000000..a54e29789f --- /dev/null +++ b/gfx/2d/Path.cpp @@ -0,0 +1,552 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "2D.h" +#include "PathAnalysis.h" +#include "PathHelpers.h" + +namespace mozilla { +namespace gfx { + +static double CubicRoot(double aValue) { + if (aValue < 0.0) { + return -CubicRoot(-aValue); + } else { + return pow(aValue, 1.0 / 3.0); + } +} + +struct PointD : public BasePoint<double, PointD> { + typedef BasePoint<double, PointD> Super; + + PointD() = default; + PointD(double aX, double aY) : Super(aX, aY) {} + MOZ_IMPLICIT PointD(const Point& aPoint) : Super(aPoint.x, aPoint.y) {} + + Point ToPoint() const { + return Point(static_cast<Float>(x), static_cast<Float>(y)); + } +}; + +struct BezierControlPoints { + BezierControlPoints() = default; + BezierControlPoints(const PointD& aCP1, const PointD& aCP2, + const PointD& aCP3, const PointD& aCP4) + : mCP1(aCP1), mCP2(aCP2), mCP3(aCP3), mCP4(aCP4) {} + + PointD mCP1, mCP2, mCP3, mCP4; +}; + +void FlattenBezier(const BezierControlPoints& aPoints, PathSink* aSink, + double aTolerance); + +Path::Path() = default; + +Path::~Path() = default; + +Float Path::ComputeLength() { + EnsureFlattenedPath(); + return mFlattenedPath->ComputeLength(); +} + +Point Path::ComputePointAtLength(Float aLength, Point* aTangent) { + EnsureFlattenedPath(); + return mFlattenedPath->ComputePointAtLength(aLength, aTangent); +} + +void Path::EnsureFlattenedPath() { + if (!mFlattenedPath) { + mFlattenedPath = new FlattenedPath(); + StreamToSink(mFlattenedPath); + } +} + +// This is the maximum deviation we allow (with an additional ~20% margin of +// error) of the approximation from the actual Bezier curve. +const Float kFlatteningTolerance = 0.0001f; + +void FlattenedPath::MoveTo(const Point& aPoint) { + MOZ_ASSERT(!mCalculatedLength); + FlatPathOp op; + op.mType = FlatPathOp::OP_MOVETO; + op.mPoint = aPoint; + mPathOps.push_back(op); + + mBeginPoint = aPoint; +} + +void FlattenedPath::LineTo(const Point& aPoint) { + MOZ_ASSERT(!mCalculatedLength); + FlatPathOp op; + op.mType = FlatPathOp::OP_LINETO; + op.mPoint = aPoint; + mPathOps.push_back(op); +} + +void FlattenedPath::BezierTo(const Point& aCP1, const Point& aCP2, + const Point& aCP3) { + MOZ_ASSERT(!mCalculatedLength); + FlattenBezier(BezierControlPoints(CurrentPoint(), aCP1, aCP2, aCP3), this, + kFlatteningTolerance); +} + +void FlattenedPath::QuadraticBezierTo(const Point& aCP1, const Point& aCP2) { + MOZ_ASSERT(!mCalculatedLength); + // We need to elevate the degree of this quadratic B�zier to cubic, so we're + // going to add an intermediate control point, and recompute control point 1. + // The first and last control points remain the same. + // This formula can be found on http://fontforge.sourceforge.net/bezier.html + Point CP0 = CurrentPoint(); + Point CP1 = (CP0 + aCP1 * 2.0) / 3.0; + Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0; + Point CP3 = aCP2; + + BezierTo(CP1, CP2, CP3); +} + +void FlattenedPath::Close() { + MOZ_ASSERT(!mCalculatedLength); + LineTo(mBeginPoint); +} + +void FlattenedPath::Arc(const Point& aOrigin, float aRadius, float aStartAngle, + float aEndAngle, bool aAntiClockwise) { + ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle, + aAntiClockwise); +} + +Float FlattenedPath::ComputeLength() { + if (!mCalculatedLength) { + Point currentPoint; + + for (uint32_t i = 0; i < mPathOps.size(); i++) { + if (mPathOps[i].mType == FlatPathOp::OP_MOVETO) { + currentPoint = mPathOps[i].mPoint; + } else { + mCachedLength += Distance(currentPoint, mPathOps[i].mPoint); + currentPoint = mPathOps[i].mPoint; + } + } + + mCalculatedLength = true; + } + + return mCachedLength; +} + +Point FlattenedPath::ComputePointAtLength(Float aLength, Point* aTangent) { + if (aLength < mCursor.mLength) { + // If cursor is beyond the target length, reset to the beginning. + mCursor.Reset(); + } else { + // Adjust aLength to account for the position where we'll start searching. + aLength -= mCursor.mLength; + } + + while (mCursor.mIndex < mPathOps.size()) { + const auto& op = mPathOps[mCursor.mIndex]; + if (op.mType == FlatPathOp::OP_MOVETO) { + if (Distance(mCursor.mCurrentPoint, op.mPoint) > 0.0f) { + mCursor.mLastPointSinceMove = mCursor.mCurrentPoint; + } + mCursor.mCurrentPoint = op.mPoint; + } else { + Float segmentLength = Distance(mCursor.mCurrentPoint, op.mPoint); + + if (segmentLength) { + mCursor.mLastPointSinceMove = mCursor.mCurrentPoint; + if (segmentLength > aLength) { + Point currentVector = op.mPoint - mCursor.mCurrentPoint; + Point tangent = currentVector / segmentLength; + if (aTangent) { + *aTangent = tangent; + } + return mCursor.mCurrentPoint + tangent * aLength; + } + } + + aLength -= segmentLength; + mCursor.mLength += segmentLength; + mCursor.mCurrentPoint = op.mPoint; + } + mCursor.mIndex++; + } + + if (aTangent) { + Point currentVector = mCursor.mCurrentPoint - mCursor.mLastPointSinceMove; + if (auto h = hypotf(currentVector.x, currentVector.y)) { + *aTangent = currentVector / h; + } else { + *aTangent = Point(); + } + } + return mCursor.mCurrentPoint; +} + +// This function explicitly permits aControlPoints to refer to the same object +// as either of the other arguments. +static void SplitBezier(const BezierControlPoints& aControlPoints, + BezierControlPoints* aFirstSegmentControlPoints, + BezierControlPoints* aSecondSegmentControlPoints, + double t) { + MOZ_ASSERT(aSecondSegmentControlPoints); + + *aSecondSegmentControlPoints = aControlPoints; + + PointD cp1a = + aControlPoints.mCP1 + (aControlPoints.mCP2 - aControlPoints.mCP1) * t; + PointD cp2a = + aControlPoints.mCP2 + (aControlPoints.mCP3 - aControlPoints.mCP2) * t; + PointD cp1aa = cp1a + (cp2a - cp1a) * t; + PointD cp3a = + aControlPoints.mCP3 + (aControlPoints.mCP4 - aControlPoints.mCP3) * t; + PointD cp2aa = cp2a + (cp3a - cp2a) * t; + PointD cp1aaa = cp1aa + (cp2aa - cp1aa) * t; + aSecondSegmentControlPoints->mCP4 = aControlPoints.mCP4; + + if (aFirstSegmentControlPoints) { + aFirstSegmentControlPoints->mCP1 = aControlPoints.mCP1; + aFirstSegmentControlPoints->mCP2 = cp1a; + aFirstSegmentControlPoints->mCP3 = cp1aa; + aFirstSegmentControlPoints->mCP4 = cp1aaa; + } + aSecondSegmentControlPoints->mCP1 = cp1aaa; + aSecondSegmentControlPoints->mCP2 = cp2aa; + aSecondSegmentControlPoints->mCP3 = cp3a; +} + +static void FlattenBezierCurveSegment(const BezierControlPoints& aControlPoints, + PathSink* aSink, double aTolerance) { + /* The algorithm implemented here is based on: + * http://cis.usouthal.edu/~hain/general/Publications/Bezier/Bezier%20Offset%20Curves.pdf + * + * The basic premise is that for a small t the third order term in the + * equation of a cubic bezier curve is insignificantly small. This can + * then be approximated by a quadratic equation for which the maximum + * difference from a linear approximation can be much more easily determined. + */ + BezierControlPoints currentCP = aControlPoints; + + double t = 0; + double currentTolerance = aTolerance; + while (t < 1.0) { + PointD cp21 = currentCP.mCP2 - currentCP.mCP1; + PointD cp31 = currentCP.mCP3 - currentCP.mCP1; + + /* To remove divisions and check for divide-by-zero, this is optimized from: + * Float s3 = (cp31.x * cp21.y - cp31.y * cp21.x) / hypotf(cp21.x, cp21.y); + * t = 2 * Float(sqrt(aTolerance / (3. * std::abs(s3)))); + */ + double cp21x31 = cp31.x * cp21.y - cp31.y * cp21.x; + double h = hypot(cp21.x, cp21.y); + if (cp21x31 * h == 0) { + break; + } + + double s3inv = h / cp21x31; + t = 2 * sqrt(currentTolerance * std::abs(s3inv) / 3.); + currentTolerance *= 1 + aTolerance; + // Increase tolerance every iteration to prevent this loop from executing + // too many times. This approximates the length of large curves more + // roughly. In practice, aTolerance is the constant kFlatteningTolerance + // which has value 0.0001. With this value, it takes 6,932 splits to double + // currentTolerance (to 0.0002) and 23,028 splits to increase + // currentTolerance by an order of magnitude (to 0.001). + if (t >= 1.0) { + break; + } + + SplitBezier(currentCP, nullptr, ¤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.) { + cp21 = aControlPoints.mCP3 - aControlPoints.mCP1; + } + + if (cp21.x == 0. && cp21.y == 0.) { + // In this case s3 becomes lim[n->0] (cp41.x * n) / n - (cp41.y * n) / n = + // cp41.x - cp41.y. + double s3 = cp41.x - cp41.y; + + // Use the absolute value so that Min and Max will correspond with the + // minimum and maximum of the range. + if (s3 == 0) { + *aMin = -1.0; + *aMax = 2.0; + } else { + double r = CubicRoot(std::abs(aTolerance / s3)); + *aMin = aT - r; + *aMax = aT + r; + } + return; + } + + double s3 = (cp41.x * cp21.y - cp41.y * cp21.x) / hypot(cp21.x, cp21.y); + + if (s3 == 0) { + // This means within the precision we have it can be approximated + // infinitely by a linear segment. Deal with this by specifying the + // approximation range as extending beyond the entire curve. + *aMin = -1.0; + *aMax = 2.0; + return; + } + + double tf = CubicRoot(std::abs(aTolerance / s3)); + + *aMin = aT - tf * (1 - aT); + *aMax = aT + tf * (1 - aT); +} + +/* Find the inflection points of a bezier curve. Will return false if the + * curve is degenerate in such a way that it is best approximated by a straight + * line. + * + * The below algorithm was written by Jeff Muizelaar <jmuizelaar@mozilla.com>, + * explanation follows: + * + * The lower inflection point is returned in aT1, the higher one in aT2. In the + * case of a single inflection point this will be in aT1. + * + * The method is inspired by the algorithm in "analysis of in?ection points for + * planar cubic bezier curve" + * + * Here are some differences between this algorithm and versions discussed + * elsewhere in the literature: + * + * zhang et. al compute a0, d0 and e0 incrementally using the follow formula: + * + * Point a0 = CP2 - CP1 + * Point a1 = CP3 - CP2 + * Point a2 = CP4 - CP1 + * + * Point d0 = a1 - a0 + * Point d1 = a2 - a1 + + * Point e0 = d1 - d0 + * + * this avoids any multiplications and may or may not be faster than the + * approach take below. + * + * "fast, precise flattening of cubic bezier path and ofset curves" by hain et. + * al + * Point a = CP1 + 3 * CP2 - 3 * CP3 + CP4 + * Point b = 3 * CP1 - 6 * CP2 + 3 * CP3 + * Point c = -3 * CP1 + 3 * CP2 + * Point d = CP1 + * the a, b, c, d can be expressed in terms of a0, d0 and e0 defined above as: + * c = 3 * a0 + * b = 3 * d0 + * a = e0 + * + * + * a = 3a = a.y * b.x - a.x * b.y + * b = 3b = a.y * c.x - a.x * c.y + * c = 9c = b.y * c.x - b.x * c.y + * + * The additional multiples of 3 cancel each other out as show below: + * + * x = (-b + sqrt(b * b - 4 * a * c)) / (2 * a) + * x = (-3 * b + sqrt(3 * b * 3 * b - 4 * a * 3 * 9 * c / 3)) / (2 * 3 * a) + * x = 3 * (-b + sqrt(b * b - 4 * a * c)) / (2 * 3 * a) + * x = (-b + sqrt(b * b - 4 * a * c)) / (2 * a) + * + * I haven't looked into whether the formulation of the quadratic formula in + * hain has any numerical advantages over the one used below. + */ +static inline void FindInflectionPoints( + const BezierControlPoints& aControlPoints, double* aT1, double* aT2, + uint32_t* aCount) { + // Find inflection points. + // See www.faculty.idc.ac.il/arik/quality/appendixa.html for an explanation + // of this approach. + PointD A = aControlPoints.mCP2 - aControlPoints.mCP1; + PointD B = + aControlPoints.mCP3 - (aControlPoints.mCP2 * 2) + aControlPoints.mCP1; + PointD C = aControlPoints.mCP4 - (aControlPoints.mCP3 * 3) + + (aControlPoints.mCP2 * 3) - aControlPoints.mCP1; + + double a = B.x * C.y - B.y * C.x; + double b = A.x * C.y - A.y * C.x; + double c = A.x * B.y - A.y * B.x; + + if (a == 0) { + // Not a quadratic equation. + if (b == 0) { + // Instead of a linear acceleration change we have a constant + // acceleration change. This means the equation has no solution + // and there are no inflection points, unless the constant is 0. + // In that case the curve is a straight line, essentially that means + // the easiest way to deal with is is by saying there's an inflection + // point at t == 0. The inflection point approximation range found will + // automatically extend into infinity. + if (c == 0) { + *aCount = 1; + *aT1 = 0; + return; + } + *aCount = 0; + return; + } + *aT1 = -c / b; + *aCount = 1; + return; + } + + double discriminant = b * b - 4 * a * c; + + if (discriminant < 0) { + // No inflection points. + *aCount = 0; + } else if (discriminant == 0) { + *aCount = 1; + *aT1 = -b / (2 * a); + } else { + /* Use the following formula for computing the roots: + * + * q = -1/2 * (b + sign(b) * sqrt(b^2 - 4ac)) + * t1 = q / a + * t2 = c / q + */ + double q = sqrt(discriminant); + if (b < 0) { + q = b - q; + } else { + q = b + q; + } + q *= -1. / 2; + + *aT1 = q / a; + *aT2 = c / q; + if (*aT1 > *aT2) { + std::swap(*aT1, *aT2); + } + *aCount = 2; + } +} + +void FlattenBezier(const BezierControlPoints& aControlPoints, PathSink* aSink, + double aTolerance) { + double t1; + double t2; + uint32_t count; + + FindInflectionPoints(aControlPoints, &t1, &t2, &count); + + // Check that at least one of the inflection points is inside [0..1] + if (count == 0 || + ((t1 < 0.0 || t1 >= 1.0) && (count == 1 || (t2 < 0.0 || t2 >= 1.0)))) { + FlattenBezierCurveSegment(aControlPoints, aSink, aTolerance); + return; + } + + double t1min = t1, t1max = t1, t2min = t2, t2max = t2; + + BezierControlPoints remainingCP = aControlPoints; + + // For both inflection points, calulate the range where they can be linearly + // approximated if they are positioned within [0,1] + if (count > 0 && t1 >= 0 && t1 < 1.0) { + FindInflectionApproximationRange(aControlPoints, &t1min, &t1max, t1, + aTolerance); + } + if (count > 1 && t2 >= 0 && t2 < 1.0) { + FindInflectionApproximationRange(aControlPoints, &t2min, &t2max, t2, + aTolerance); + } + BezierControlPoints nextCPs = aControlPoints; + BezierControlPoints prevCPs; + + // Process ranges. [t1min, t1max] and [t2min, t2max] are approximated by line + // segments. + if (count == 1 && t1min <= 0 && t1max >= 1.0) { + // The whole range can be approximated by a line segment. + aSink->LineTo(aControlPoints.mCP4.ToPoint()); + return; + } + + if (t1min > 0) { + // Flatten the Bezier up until the first inflection point's approximation + // point. + SplitBezier(aControlPoints, &prevCPs, &remainingCP, t1min); + FlattenBezierCurveSegment(prevCPs, aSink, aTolerance); + } + if (t1max >= 0 && t1max < 1.0 && (count == 1 || t2min > t1max)) { + // The second inflection point's approximation range begins after the end + // of the first, approximate the first inflection point by a line and + // subsequently flatten up until the end or the next inflection point. + SplitBezier(aControlPoints, nullptr, &nextCPs, t1max); + + aSink->LineTo(nextCPs.mCP1.ToPoint()); + + if (count == 1 || (count > 1 && t2min >= 1.0)) { + // No more inflection points to deal with, flatten the rest of the curve. + FlattenBezierCurveSegment(nextCPs, aSink, aTolerance); + } + } else if (count > 1 && t2min > 1.0) { + // We've already concluded t2min <= t1max, so if this is true the + // approximation range for the first inflection point runs past the + // end of the curve, draw a line to the end and we're done. + aSink->LineTo(aControlPoints.mCP4.ToPoint()); + return; + } + + if (count > 1 && t2min < 1.0 && t2max > 0) { + if (t2min > 0 && t2min < t1max) { + // In this case the t2 approximation range starts inside the t1 + // approximation range. + SplitBezier(aControlPoints, nullptr, &nextCPs, t1max); + aSink->LineTo(nextCPs.mCP1.ToPoint()); + } else if (t2min > 0 && t1max > 0) { + SplitBezier(aControlPoints, nullptr, &nextCPs, t1max); + + // Find a control points describing the portion of the curve between t1max + // and t2min. + double t2mina = (t2min - t1max) / (1 - t1max); + SplitBezier(nextCPs, &prevCPs, &nextCPs, t2mina); + FlattenBezierCurveSegment(prevCPs, aSink, aTolerance); + } else if (t2min > 0) { + // We have nothing interesting before t2min, find that bit and flatten it. + SplitBezier(aControlPoints, &prevCPs, &nextCPs, t2min); + FlattenBezierCurveSegment(prevCPs, aSink, aTolerance); + } + if (t2max < 1.0) { + // Flatten the portion of the curve after t2max + SplitBezier(aControlPoints, nullptr, &nextCPs, t2max); + + // Draw a line to the start, this is the approximation between t2min and + // t2max. + aSink->LineTo(nextCPs.mCP1.ToPoint()); + FlattenBezierCurveSegment(nextCPs, aSink, aTolerance); + } else { + // Our approximation range extends beyond the end of the curve. + aSink->LineTo(aControlPoints.mCP4.ToPoint()); + return; + } + } +} + +Rect Path::GetFastBounds(const Matrix& aTransform, + const StrokeOptions* aStrokeOptions) const { + return aStrokeOptions ? GetStrokedBounds(*aStrokeOptions, aTransform) + : GetBounds(aTransform); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/PathAnalysis.h b/gfx/2d/PathAnalysis.h new file mode 100644 index 0000000000..5821b8839e --- /dev/null +++ b/gfx/2d/PathAnalysis.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "2D.h" +#include <vector> + +namespace mozilla { +namespace gfx { + +struct FlatPathOp { + enum OpType { + OP_MOVETO, + OP_LINETO, + }; + + OpType mType; + Point mPoint; +}; + +class FlattenedPath : public PathSink { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FlattenedPath, override) + + virtual void MoveTo(const Point& aPoint) override; + virtual void LineTo(const Point& aPoint) override; + virtual void BezierTo(const Point& aCP1, const Point& aCP2, + const Point& aCP3) override; + virtual void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) override; + virtual void Close() override; + virtual void Arc(const Point& aOrigin, float aRadius, float aStartAngle, + float aEndAngle, bool aAntiClockwise = false) override; + + virtual Point CurrentPoint() const override { + return mPathOps.empty() ? Point() : mPathOps[mPathOps.size() - 1].mPoint; + } + + Float ComputeLength(); + Point ComputePointAtLength(Float aLength, Point* aTangent); + + private: + Float mCachedLength = 0.0f; + bool mCalculatedLength = false; + + std::vector<FlatPathOp> mPathOps; + + // Used to accelerate ComputePointAtLength for the common case of iterating + // forward along the path. + struct { + uint32_t mIndex = 0; + Float mLength = 0.0f; + Point mCurrentPoint; + Point mLastPointSinceMove; + + void Reset() { + mIndex = 0; + mLength = 0.0f; + mCurrentPoint = Point(); + mLastPointSinceMove = Point(); + } + } mCursor; +}; + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/PathCairo.cpp b/gfx/2d/PathCairo.cpp new file mode 100644 index 0000000000..32c4da67ea --- /dev/null +++ b/gfx/2d/PathCairo.cpp @@ -0,0 +1,315 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PathCairo.h" +#include <math.h> +#include "DrawTargetCairo.h" +#include "Logging.h" +#include "PathHelpers.h" +#include "HelpersCairo.h" + +namespace mozilla { +namespace gfx { + +already_AddRefed<PathBuilder> PathBuilderCairo::Create(FillRule aFillRule) { + return MakeAndAddRef<PathBuilderCairo>(aFillRule); +} + +PathBuilderCairo::PathBuilderCairo(FillRule aFillRule) : mFillRule(aFillRule) {} + +void PathBuilderCairo::MoveTo(const Point& aPoint) { + cairo_path_data_t data; + data.header.type = CAIRO_PATH_MOVE_TO; + data.header.length = 2; + mPathData.push_back(data); + data.point.x = aPoint.x; + data.point.y = aPoint.y; + mPathData.push_back(data); + + mBeginPoint = mCurrentPoint = aPoint; +} + +void PathBuilderCairo::LineTo(const Point& aPoint) { + cairo_path_data_t data; + data.header.type = CAIRO_PATH_LINE_TO; + data.header.length = 2; + mPathData.push_back(data); + data.point.x = aPoint.x; + data.point.y = aPoint.y; + mPathData.push_back(data); + + mCurrentPoint = aPoint; +} + +void PathBuilderCairo::BezierTo(const Point& aCP1, const Point& aCP2, + const Point& aCP3) { + cairo_path_data_t data; + data.header.type = CAIRO_PATH_CURVE_TO; + data.header.length = 4; + mPathData.push_back(data); + data.point.x = aCP1.x; + data.point.y = aCP1.y; + mPathData.push_back(data); + data.point.x = aCP2.x; + data.point.y = aCP2.y; + mPathData.push_back(data); + data.point.x = aCP3.x; + data.point.y = aCP3.y; + mPathData.push_back(data); + + mCurrentPoint = aCP3; +} + +void PathBuilderCairo::QuadraticBezierTo(const Point& aCP1, const Point& aCP2) { + // We need to elevate the degree of this quadratic Bézier to cubic, so we're + // going to add an intermediate control point, and recompute control point 1. + // The first and last control points remain the same. + // This formula can be found on http://fontforge.sourceforge.net/bezier.html + Point CP0 = CurrentPoint(); + Point CP1 = (CP0 + aCP1 * 2.0) / 3.0; + Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0; + Point CP3 = aCP2; + + cairo_path_data_t data; + data.header.type = CAIRO_PATH_CURVE_TO; + data.header.length = 4; + mPathData.push_back(data); + data.point.x = CP1.x; + data.point.y = CP1.y; + mPathData.push_back(data); + data.point.x = CP2.x; + data.point.y = CP2.y; + mPathData.push_back(data); + data.point.x = CP3.x; + data.point.y = CP3.y; + mPathData.push_back(data); + + mCurrentPoint = aCP2; +} + +void PathBuilderCairo::Close() { + cairo_path_data_t data; + data.header.type = CAIRO_PATH_CLOSE_PATH; + data.header.length = 1; + mPathData.push_back(data); + + mCurrentPoint = mBeginPoint; +} + +void PathBuilderCairo::Arc(const Point& aOrigin, float aRadius, + float aStartAngle, float aEndAngle, + bool aAntiClockwise) { + ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle, + aAntiClockwise); +} + +already_AddRefed<Path> PathBuilderCairo::Finish() { + return MakeAndAddRef<PathCairo>(mFillRule, mPathData, mCurrentPoint, + mBeginPoint); +} + +PathCairo::PathCairo(FillRule aFillRule, + std::vector<cairo_path_data_t>& aPathData, + const Point& aCurrentPoint, const Point& aBeginPoint) + : mFillRule(aFillRule), + mContainingContext(nullptr), + mCurrentPoint(aCurrentPoint), + mBeginPoint(aBeginPoint) { + mPathData.swap(aPathData); +} + +PathCairo::PathCairo(cairo_t* aContext) + : mFillRule(FillRule::FILL_WINDING), mContainingContext(nullptr) { + cairo_path_t* path = cairo_copy_path(aContext); + + // XXX - mCurrentPoint is not properly set here, the same is true for the + // D2D Path code, we never require current point when hitting this codepath + // but this should be fixed. + for (int i = 0; i < path->num_data; i++) { + mPathData.push_back(path->data[i]); + } + + cairo_path_destroy(path); +} + +PathCairo::~PathCairo() { + if (mContainingContext) { + cairo_destroy(mContainingContext); + } +} + +already_AddRefed<PathBuilder> PathCairo::CopyToBuilder( + FillRule aFillRule) const { + RefPtr<PathBuilderCairo> builder = new PathBuilderCairo(aFillRule); + + builder->mPathData = mPathData; + builder->mCurrentPoint = mCurrentPoint; + builder->mBeginPoint = mBeginPoint; + + return builder.forget(); +} + +already_AddRefed<PathBuilder> PathCairo::TransformedCopyToBuilder( + const Matrix& aTransform, FillRule aFillRule) const { + RefPtr<PathBuilderCairo> builder = new PathBuilderCairo(aFillRule); + + AppendPathToBuilder(builder, &aTransform); + builder->mCurrentPoint = aTransform.TransformPoint(mCurrentPoint); + builder->mBeginPoint = aTransform.TransformPoint(mBeginPoint); + + return builder.forget(); +} + +bool PathCairo::ContainsPoint(const Point& aPoint, + const Matrix& aTransform) const { + Matrix inverse = aTransform; + inverse.Invert(); + Point transformed = inverse.TransformPoint(aPoint); + + EnsureContainingContext(aTransform); + + return cairo_in_fill(mContainingContext, transformed.x, transformed.y); +} + +bool PathCairo::StrokeContainsPoint(const StrokeOptions& aStrokeOptions, + const Point& aPoint, + const Matrix& aTransform) const { + Matrix inverse = aTransform; + inverse.Invert(); + Point transformed = inverse.TransformPoint(aPoint); + + EnsureContainingContext(aTransform); + + SetCairoStrokeOptions(mContainingContext, aStrokeOptions); + + return cairo_in_stroke(mContainingContext, transformed.x, transformed.y); +} + +Rect PathCairo::GetBounds(const Matrix& aTransform) const { + EnsureContainingContext(aTransform); + + double x1, y1, x2, y2; + + cairo_path_extents(mContainingContext, &x1, &y1, &x2, &y2); + Rect bounds(Float(x1), Float(y1), Float(x2 - x1), Float(y2 - y1)); + return aTransform.TransformBounds(bounds); +} + +Rect PathCairo::GetStrokedBounds(const StrokeOptions& aStrokeOptions, + const Matrix& aTransform) const { + EnsureContainingContext(aTransform); + + double x1, y1, x2, y2; + + SetCairoStrokeOptions(mContainingContext, aStrokeOptions); + + cairo_stroke_extents(mContainingContext, &x1, &y1, &x2, &y2); + Rect bounds((Float)x1, (Float)y1, (Float)(x2 - x1), (Float)(y2 - y1)); + return aTransform.TransformBounds(bounds); +} + +void PathCairo::StreamToSink(PathSink* aSink) const { + for (size_t i = 0; i < mPathData.size(); i++) { + switch (mPathData[i].header.type) { + case CAIRO_PATH_MOVE_TO: + i++; + aSink->MoveTo(Point(mPathData[i].point.x, mPathData[i].point.y)); + break; + case CAIRO_PATH_LINE_TO: + i++; + aSink->LineTo(Point(mPathData[i].point.x, mPathData[i].point.y)); + break; + case CAIRO_PATH_CURVE_TO: + aSink->BezierTo( + Point(mPathData[i + 1].point.x, mPathData[i + 1].point.y), + Point(mPathData[i + 2].point.x, mPathData[i + 2].point.y), + Point(mPathData[i + 3].point.x, mPathData[i + 3].point.y)); + i += 3; + break; + case CAIRO_PATH_CLOSE_PATH: + aSink->Close(); + break; + default: + // Corrupt path data! + MOZ_ASSERT(false); + } + } +} + +bool PathCairo::IsEmpty() const { + for (size_t i = 0; i < mPathData.size(); i++) { + switch (mPathData[i].header.type) { + case CAIRO_PATH_MOVE_TO: + break; + case CAIRO_PATH_CLOSE_PATH: + break; + default: + return false; + } + } + return true; +} + +void PathCairo::EnsureContainingContext(const Matrix& aTransform) const { + if (mContainingContext) { + if (mContainingTransform.ExactlyEquals(aTransform)) { + return; + } + } else { + mContainingContext = cairo_create(DrawTargetCairo::GetDummySurface()); + } + + mContainingTransform = aTransform; + + cairo_matrix_t mat; + GfxMatrixToCairoMatrix(mContainingTransform, mat); + cairo_set_matrix(mContainingContext, &mat); + + SetPathOnContext(mContainingContext); +} + +void PathCairo::SetPathOnContext(cairo_t* aContext) const { + // Needs the correct fill rule set. + cairo_set_fill_rule(aContext, GfxFillRuleToCairoFillRule(mFillRule)); + + cairo_new_path(aContext); + + if (!mPathData.empty()) { + cairo_path_t path; + path.data = const_cast<cairo_path_data_t*>(&mPathData.front()); + path.num_data = mPathData.size(); + path.status = CAIRO_STATUS_SUCCESS; + cairo_append_path(aContext, &path); + } +} + +void PathCairo::AppendPathToBuilder(PathBuilderCairo* aBuilder, + const Matrix* aTransform) const { + if (aTransform) { + size_t i = 0; + while (i < mPathData.size()) { + uint32_t pointCount = mPathData[i].header.length - 1; + aBuilder->mPathData.push_back(mPathData[i]); + i++; + for (uint32_t c = 0; c < pointCount; c++) { + cairo_path_data_t data; + Point newPoint = aTransform->TransformPoint( + Point(mPathData[i].point.x, mPathData[i].point.y)); + data.point.x = newPoint.x; + data.point.y = newPoint.y; + aBuilder->mPathData.push_back(data); + i++; + } + } + } else { + for (size_t i = 0; i < mPathData.size(); i++) { + aBuilder->mPathData.push_back(mPathData[i]); + } + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/PathCairo.h b/gfx/2d/PathCairo.h new file mode 100644 index 0000000000..1e58ef1512 --- /dev/null +++ b/gfx/2d/PathCairo.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PATH_CAIRO_H_ +#define MOZILLA_GFX_PATH_CAIRO_H_ + +#include "2D.h" +#include "cairo.h" +#include <vector> + +namespace mozilla { +namespace gfx { + +class PathCairo; + +class PathBuilderCairo : public PathBuilder { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderCairo, override) + + explicit PathBuilderCairo(FillRule aFillRule); + + void MoveTo(const Point& aPoint) override; + void LineTo(const Point& aPoint) override; + void BezierTo(const Point& aCP1, const Point& aCP2, + const Point& aCP3) override; + void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) override; + void Close() override; + void Arc(const Point& aOrigin, float aRadius, float aStartAngle, + float aEndAngle, bool aAntiClockwise = false) override; + already_AddRefed<Path> Finish() override; + + BackendType GetBackendType() const override { return BackendType::CAIRO; } + + bool IsActive() const override { return !mPathData.empty(); } + + static already_AddRefed<PathBuilder> Create(FillRule aFillRule); + + private: // data + friend class PathCairo; + + FillRule mFillRule; + std::vector<cairo_path_data_t> mPathData; +}; + +class PathCairo : public Path { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathCairo, override) + + PathCairo(FillRule aFillRule, std::vector<cairo_path_data_t>& aPathData, + const Point& aCurrentPoint, const Point& aBeginPoint); + explicit PathCairo(cairo_t* aContext); + virtual ~PathCairo(); + + BackendType GetBackendType() const override { return BackendType::CAIRO; } + + already_AddRefed<PathBuilder> CopyToBuilder( + FillRule aFillRule) const override; + already_AddRefed<PathBuilder> TransformedCopyToBuilder( + const Matrix& aTransform, FillRule aFillRule) const override; + + bool ContainsPoint(const Point& aPoint, + const Matrix& aTransform) const override; + + bool StrokeContainsPoint(const StrokeOptions& aStrokeOptions, + const Point& aPoint, + const Matrix& aTransform) const override; + + Rect GetBounds(const Matrix& aTransform = Matrix()) const override; + + Rect GetStrokedBounds(const StrokeOptions& aStrokeOptions, + const Matrix& aTransform = Matrix()) const override; + + void StreamToSink(PathSink* aSink) const override; + + FillRule GetFillRule() const override { return mFillRule; } + + void SetPathOnContext(cairo_t* aContext) const; + + void AppendPathToBuilder(PathBuilderCairo* aBuilder, + const Matrix* aTransform = nullptr) const; + + bool IsEmpty() const override; + + private: + void EnsureContainingContext(const Matrix& aTransform) const; + + FillRule mFillRule; + std::vector<cairo_path_data_t> mPathData; + mutable cairo_t* mContainingContext; + mutable Matrix mContainingTransform; + Point mCurrentPoint; + Point mBeginPoint; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PATH_CAIRO_H_ */ diff --git a/gfx/2d/PathD2D.cpp b/gfx/2d/PathD2D.cpp new file mode 100644 index 0000000000..de01b410a9 --- /dev/null +++ b/gfx/2d/PathD2D.cpp @@ -0,0 +1,430 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PathD2D.h" +#include "HelpersD2D.h" +#include <math.h> +#include "DrawTargetD2D1.h" +#include "Logging.h" +#include "PathHelpers.h" + +namespace mozilla { +namespace gfx { + +already_AddRefed<PathBuilder> PathBuilderD2D::Create(FillRule aFillRule) { + RefPtr<ID2D1PathGeometry> path; + HRESULT hr = + DrawTargetD2D1::factory()->CreatePathGeometry(getter_AddRefs(path)); + + if (FAILED(hr)) { + gfxWarning() << "Failed to create Direct2D Path Geometry. Code: " + << hexa(hr); + return nullptr; + } + + RefPtr<ID2D1GeometrySink> sink; + hr = path->Open(getter_AddRefs(sink)); + if (FAILED(hr)) { + gfxWarning() << "Failed to access Direct2D Path Geometry. Code: " + << hexa(hr); + return nullptr; + } + + if (aFillRule == FillRule::FILL_WINDING) { + sink->SetFillMode(D2D1_FILL_MODE_WINDING); + } + + return MakeAndAddRef<PathBuilderD2D>(sink, path, aFillRule, + BackendType::DIRECT2D1_1); +} + +// This class exists as a wrapper for ID2D1SimplifiedGeometry sink, it allows +// a geometry to be duplicated into a geometry sink, while removing the final +// figure end and thus allowing a figure that was implicitly closed to be +// continued. +class OpeningGeometrySink : public ID2D1SimplifiedGeometrySink { + public: + explicit OpeningGeometrySink(ID2D1SimplifiedGeometrySink* aSink) + : mSink(aSink), mNeedsFigureEnded(false) {} + + HRESULT STDMETHODCALLTYPE QueryInterface(const IID& aIID, void** aPtr) { + if (!aPtr) { + return E_POINTER; + } + + if (aIID == IID_IUnknown) { + *aPtr = static_cast<IUnknown*>(this); + return S_OK; + } else if (aIID == IID_ID2D1SimplifiedGeometrySink) { + *aPtr = static_cast<ID2D1SimplifiedGeometrySink*>(this); + return S_OK; + } + + return E_NOINTERFACE; + } + + ULONG STDMETHODCALLTYPE AddRef() { return 1; } + + ULONG STDMETHODCALLTYPE Release() { return 1; } + + // We ignore SetFillMode, the copier will decide. + STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode) { + EnsureFigureEnded(); + return; + } + STDMETHOD_(void, BeginFigure) + (D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) { + EnsureFigureEnded(); + return mSink->BeginFigure(aPoint, aBegin); + } + STDMETHOD_(void, AddLines)(const D2D1_POINT_2F* aLines, UINT aCount) { + EnsureFigureEnded(); + return mSink->AddLines(aLines, aCount); + } + STDMETHOD_(void, AddBeziers) + (const D2D1_BEZIER_SEGMENT* aSegments, UINT aCount) { + EnsureFigureEnded(); + return mSink->AddBeziers(aSegments, aCount); + } + STDMETHOD(Close)() { /* Should never be called! */ + return S_OK; + } + STDMETHOD_(void, SetSegmentFlags)(D2D1_PATH_SEGMENT aFlags) { + return mSink->SetSegmentFlags(aFlags); + } + + // This function is special - it's the reason this class exists. + // It needs to intercept the very last endfigure. So that a user can + // continue writing to this sink as if they never stopped. + STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd) { + if (aEnd == D2D1_FIGURE_END_CLOSED) { + return mSink->EndFigure(aEnd); + } else { + mNeedsFigureEnded = true; + } + } + + private: + void EnsureFigureEnded() { + if (mNeedsFigureEnded) { + mSink->EndFigure(D2D1_FIGURE_END_OPEN); + mNeedsFigureEnded = false; + } + } + + ID2D1SimplifiedGeometrySink* mSink; + bool mNeedsFigureEnded; +}; + +PathBuilderD2D::~PathBuilderD2D() {} + +void PathBuilderD2D::MoveTo(const Point& aPoint) { + if (mFigureActive) { + mSink->EndFigure(D2D1_FIGURE_END_OPEN); + mFigureActive = false; + } + EnsureActive(aPoint); + mCurrentPoint = aPoint; +} + +void PathBuilderD2D::LineTo(const Point& aPoint) { + EnsureActive(aPoint); + mSink->AddLine(D2DPoint(aPoint)); + + mCurrentPoint = aPoint; + mFigureEmpty = false; +} + +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; + mFigureEmpty = false; +} + +void PathBuilderD2D::QuadraticBezierTo(const Point& aCP1, const Point& aCP2) { + EnsureActive(aCP1); + mSink->AddQuadraticBezier( + D2D1::QuadraticBezierSegment(D2DPoint(aCP1), D2DPoint(aCP2))); + + mCurrentPoint = aCP2; + mFigureEmpty = false; +} + +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); + + // We want aEndAngle to come numerically after aStartAngle when taking into + // account the sweep direction so that our calculation of the arcSize below + // (large or small) works. + Float sweepDirection = aAntiClockwise ? -1.0f : 1.0f; + + Float arcSweepLeft = (aEndAngle - aStartAngle) * sweepDirection; + if (arcSweepLeft < 0) { + // This calculation moves aStartAngle by a multiple of 2*Pi so that it is + // the closest it can be to aEndAngle and still be numerically before + // aEndAngle when taking into account sweepDirection. + arcSweepLeft = Float(2.0f * M_PI) + fmodf(arcSweepLeft, Float(2.0f * M_PI)); + aStartAngle = aEndAngle - arcSweepLeft * sweepDirection; + } + + // 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 >= 1.9999 * M_PI) { + fullCircle = true; + aEndAngle = Float(aStartAngle + M_PI * 1.9999); + } else if (aStartAngle - aEndAngle >= 1.9999 * 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; + mFigureEmpty = false; +} + +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, mFigureEmpty, + mCurrentPoint, mFillRule, mBackendType); +} + +already_AddRefed<PathBuilder> PathD2D::CopyToBuilder(FillRule aFillRule) const { + return TransformedCopyToBuilder(Matrix(), aFillRule); +} + +already_AddRefed<PathBuilder> PathD2D::TransformedCopyToBuilder( + const Matrix& aTransform, FillRule aFillRule) const { + RefPtr<ID2D1PathGeometry> path; + HRESULT hr = + DrawTargetD2D1::factory()->CreatePathGeometry(getter_AddRefs(path)); + + if (FAILED(hr)) { + gfxWarning() << "Failed to create PathGeometry. Code: " << hexa(hr); + return nullptr; + } + + RefPtr<ID2D1GeometrySink> sink; + hr = path->Open(getter_AddRefs(sink)); + if (FAILED(hr)) { + gfxWarning() << "Failed to open Geometry for writing. Code: " << hexa(hr); + return nullptr; + } + + if (aFillRule == FillRule::FILL_WINDING) { + sink->SetFillMode(D2D1_FILL_MODE_WINDING); + } + + if (mEndedActive) { + OpeningGeometrySink wrapSink(sink); + hr = mGeometry->Simplify( + D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, + D2DMatrix(aTransform), &wrapSink); + } else { + hr = mGeometry->Simplify( + D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, + D2DMatrix(aTransform), sink); + } + if (FAILED(hr)) { + gfxWarning() << "Failed to simplify PathGeometry to tranformed copy. Code: " + << hexa(hr) << " Active: " << mEndedActive; + return nullptr; + } + + RefPtr<PathBuilderD2D> pathBuilder = + new PathBuilderD2D(sink, path, aFillRule, mBackendType); + + pathBuilder->mCurrentPoint = aTransform.TransformPoint(mEndPoint); + + if (mEndedActive) { + pathBuilder->mFigureActive = true; + } + + return pathBuilder.forget(); +} + +void PathD2D::StreamToSink(PathSink* aSink) const { + HRESULT hr; + + StreamingGeometrySink sink(aSink); + + hr = mGeometry->Simplify(D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, + D2D1::IdentityMatrix(), &sink); + + if (FAILED(hr)) { + gfxWarning() << "Failed to stream D2D path to sink. Code: " << hexa(hr); + return; + } +} + +bool PathD2D::ContainsPoint(const Point& aPoint, + const Matrix& aTransform) const { + if (!aTransform.Determinant()) { + // If the transform is not invertible, then don't consider point inside. + return false; + } + + BOOL result; + + HRESULT hr = mGeometry->FillContainsPoint( + D2DPoint(aPoint), D2DMatrix(aTransform), 0.001f, &result); + + if (FAILED(hr)) { + // Log + return false; + } + + return !!result; +} + +bool PathD2D::StrokeContainsPoint(const StrokeOptions& aStrokeOptions, + const Point& aPoint, + const Matrix& aTransform) const { + if (!aTransform.Determinant()) { + // If the transform is not invertible, then don't consider point inside. + return false; + } + + BOOL result; + + RefPtr<ID2D1StrokeStyle> strokeStyle = + CreateStrokeStyleForOptions(aStrokeOptions); + HRESULT hr = mGeometry->StrokeContainsPoint( + D2DPoint(aPoint), aStrokeOptions.mLineWidth, strokeStyle, + D2DMatrix(aTransform), &result); + + if (FAILED(hr)) { + // Log + return false; + } + + return !!result; +} + +Rect PathD2D::GetBounds(const Matrix& aTransform) const { + D2D1_RECT_F d2dBounds; + + HRESULT hr = mGeometry->GetBounds(D2DMatrix(aTransform), &d2dBounds); + + Rect bounds = ToRect(d2dBounds); + if (FAILED(hr) || !bounds.IsFinite()) { + gfxWarning() << "Failed to get stroked bounds for path. Code: " << hexa(hr); + return Rect(); + } + + return bounds; +} + +Rect PathD2D::GetStrokedBounds(const StrokeOptions& aStrokeOptions, + const Matrix& aTransform) const { + D2D1_RECT_F d2dBounds; + + RefPtr<ID2D1StrokeStyle> strokeStyle = + CreateStrokeStyleForOptions(aStrokeOptions); + HRESULT hr = + mGeometry->GetWidenedBounds(aStrokeOptions.mLineWidth, strokeStyle, + D2DMatrix(aTransform), &d2dBounds); + + Rect bounds = ToRect(d2dBounds); + if (FAILED(hr) || !bounds.IsFinite()) { + gfxWarning() << "Failed to get stroked bounds for path. Code: " << hexa(hr); + return Rect(); + } + + return bounds; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/PathD2D.h b/gfx/2d/PathD2D.h new file mode 100644 index 0000000000..27c900ad7d --- /dev/null +++ b/gfx/2d/PathD2D.h @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PATHD2D_H_ +#define MOZILLA_GFX_PATHD2D_H_ + +#include <d2d1.h> + +#include "2D.h" + +namespace mozilla { +namespace gfx { + +class PathD2D; + +class PathBuilderD2D : public PathBuilder { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderD2D, override) + PathBuilderD2D(ID2D1GeometrySink* aSink, ID2D1PathGeometry* aGeom, + FillRule aFillRule, BackendType aBackendType) + : mSink(aSink), + mGeometry(aGeom), + mFigureActive(false), + mFillRule(aFillRule), + mBackendType(aBackendType) {} + virtual ~PathBuilderD2D(); + + virtual void MoveTo(const Point& aPoint); + virtual void LineTo(const Point& aPoint); + virtual void BezierTo(const Point& aCP1, const Point& aCP2, + const Point& aCP3); + virtual void QuadraticBezierTo(const Point& aCP1, const Point& aCP2); + virtual void Close(); + virtual void Arc(const Point& aOrigin, Float aRadius, Float aStartAngle, + Float aEndAngle, bool aAntiClockwise = false); + + virtual already_AddRefed<Path> Finish(); + + virtual BackendType GetBackendType() const { return mBackendType; } + + ID2D1GeometrySink* GetSink() { return mSink; } + + bool IsFigureActive() const { return mFigureActive; } + + virtual bool IsActive() const { return IsFigureActive(); } + + static already_AddRefed<PathBuilder> Create(FillRule aFillRule); + + private: + friend class PathD2D; + + void EnsureActive(const Point& aPoint); + + RefPtr<ID2D1GeometrySink> mSink; + RefPtr<ID2D1PathGeometry> mGeometry; + + bool mFigureActive; + bool mFigureEmpty = true; + FillRule mFillRule; + BackendType mBackendType; +}; + +class PathD2D : public Path { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathD2D, override) + PathD2D(ID2D1PathGeometry* aGeometry, bool aEndedActive, bool aIsEmpty, + const Point& aEndPoint, FillRule aFillRule, BackendType aBackendType) + : mGeometry(aGeometry), + mEndedActive(aEndedActive), + mIsEmpty(aIsEmpty), + 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; } + + bool IsEmpty() const override { return mIsEmpty; } + + ID2D1Geometry* GetGeometry() { return mGeometry; } + + private: + friend class DrawTargetD2D; + friend class DrawTargetD2D1; + + mutable RefPtr<ID2D1PathGeometry> mGeometry; + bool mEndedActive; + bool mIsEmpty; + Point mEndPoint; + FillRule mFillRule; + BackendType mBackendType; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PATHD2D_H_ */ diff --git a/gfx/2d/PathHelpers.cpp b/gfx/2d/PathHelpers.cpp new file mode 100644 index 0000000000..66fce104b3 --- /dev/null +++ b/gfx/2d/PathHelpers.cpp @@ -0,0 +1,290 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PathHelpers.h" + +namespace mozilla { +namespace gfx { + +UserDataKey sDisablePixelSnapping; + +void AppendRectToPath(PathBuilder* aPathBuilder, const Rect& aRect, + bool aDrawClockwise) { + if (aDrawClockwise) { + aPathBuilder->MoveTo(aRect.TopLeft()); + aPathBuilder->LineTo(aRect.TopRight()); + aPathBuilder->LineTo(aRect.BottomRight()); + aPathBuilder->LineTo(aRect.BottomLeft()); + } else { + aPathBuilder->MoveTo(aRect.TopRight()); + aPathBuilder->LineTo(aRect.TopLeft()); + aPathBuilder->LineTo(aRect.BottomLeft()); + aPathBuilder->LineTo(aRect.BottomRight()); + } + aPathBuilder->Close(); +} + +void AppendRoundedRectToPath(PathBuilder* aPathBuilder, const Rect& aRect, + const RectCornerRadii& aRadii, bool aDrawClockwise, + const Maybe<Matrix>& aTransform) { + // For CW drawing, this looks like: + // + // ...******0** 1 C + // **** + // *** 2 + // ** + // * + // * + // 3 + // * + // * + // + // Where 0, 1, 2, 3 are the control points of the Bezier curve for + // the corner, and C is the actual corner point. + // + // At the start of the loop, the current point is assumed to be + // the point adjacent to the top left corner on the top + // horizontal. Note that corner indices start at the top left and + // continue clockwise, whereas in our loop i = 0 refers to the top + // right corner. + // + // When going CCW, the control points are swapped, and the first + // corner that's drawn is the top left (along with the top segment). + // + // There is considerable latitude in how one chooses the four + // control points for a Bezier curve approximation to an ellipse. + // For the overall path to be continuous and show no corner at the + // endpoints of the arc, points 0 and 3 must be at the ends of the + // straight segments of the rectangle; points 0, 1, and C must be + // collinear; and points 3, 2, and C must also be collinear. This + // leaves only two free parameters: the ratio of the line segments + // 01 and 0C, and the ratio of the line segments 32 and 3C. See + // the following papers for extensive discussion of how to choose + // these ratios: + // + // Dokken, Tor, et al. "Good approximation of circles by + // curvature-continuous Bezier curves." Computer-Aided + // Geometric Design 7(1990) 33--41. + // Goldapp, Michael. "Approximation of circular arcs by cubic + // polynomials." Computer-Aided Geometric Design 8(1991) 227--238. + // Maisonobe, Luc. "Drawing an elliptical arc using polylines, + // quadratic, or cubic Bezier curves." + // http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf + // + // We follow the approach in section 2 of Goldapp (least-error, + // Hermite-type approximation) and make both ratios equal to + // + // 2 2 + n - sqrt(2n + 28) + // alpha = - * --------------------- + // 3 n - 4 + // + // where n = 3( cbrt(sqrt(2)+1) - cbrt(sqrt(2)-1) ). + // + // This is the result of Goldapp's equation (10b) when the angle + // swept out by the arc is pi/2, and the parameter "a-bar" is the + // expression given immediately below equation (21). + // + // Using this value, the maximum radial error for a circle, as a + // fraction of the radius, is on the order of 0.2 x 10^-3. + // Neither Dokken nor Goldapp discusses error for a general + // ellipse; Maisonobe does, but his choice of control points + // follows different constraints, and Goldapp's expression for + // 'alpha' gives much smaller radial error, even for very flat + // ellipses, than Maisonobe's equivalent. + // + // For the various corners and for each axis, the sign of this + // constant changes, or it might be 0 -- it's multiplied by the + // appropriate multiplier from the list before using. + + const Float alpha = Float(0.55191497064665766025); + + typedef struct { + Float a, b; + } twoFloats; + + twoFloats cwCornerMults[4] = {{-1, 0}, // cc == clockwise + {0, -1}, + {+1, 0}, + {0, +1}}; + twoFloats ccwCornerMults[4] = {{+1, 0}, // ccw == counter-clockwise + {0, -1}, + {-1, 0}, + {0, +1}}; + + twoFloats* cornerMults = aDrawClockwise ? cwCornerMults : ccwCornerMults; + + Point cornerCoords[] = {aRect.TopLeft(), aRect.TopRight(), + aRect.BottomRight(), aRect.BottomLeft()}; + + Point pc, p0, p1, p2, p3; + + if (aDrawClockwise) { + Point pt(aRect.X() + aRadii[eCornerTopLeft].width, aRect.Y()); + if (aTransform) { + pt = aTransform->TransformPoint(pt); + } + aPathBuilder->MoveTo(pt); + } else { + Point pt(aRect.X() + aRect.Width() - aRadii[eCornerTopRight].width, + aRect.Y()); + if (aTransform) { + pt = aTransform->TransformPoint(pt); + } + aPathBuilder->MoveTo(pt); + } + + for (int i = 0; i < 4; ++i) { + // the corner index -- either 1 2 3 0 (cw) or 0 3 2 1 (ccw) + int c = aDrawClockwise ? ((i + 1) % 4) : ((4 - i) % 4); + + // i+2 and i+3 respectively. These are used to index into the corner + // multiplier table, and were deduced by calculating out the long form + // of each corner and finding a pattern in the signs and values. + int i2 = (i + 2) % 4; + int i3 = (i + 3) % 4; + + pc = cornerCoords[c]; + + if (aRadii[c].width > 0.0 && aRadii[c].height > 0.0) { + p0.x = pc.x + cornerMults[i].a * aRadii[c].width; + p0.y = pc.y + cornerMults[i].b * aRadii[c].height; + + p3.x = pc.x + cornerMults[i3].a * aRadii[c].width; + p3.y = pc.y + cornerMults[i3].b * aRadii[c].height; + + p1.x = p0.x + alpha * cornerMults[i2].a * aRadii[c].width; + p1.y = p0.y + alpha * cornerMults[i2].b * aRadii[c].height; + + p2.x = p3.x - alpha * cornerMults[i3].a * aRadii[c].width; + p2.y = p3.y - alpha * cornerMults[i3].b * aRadii[c].height; + + if (aTransform.isNothing()) { + aPathBuilder->LineTo(p0); + aPathBuilder->BezierTo(p1, p2, p3); + } else { + const Matrix& transform = *aTransform; + aPathBuilder->LineTo(transform.TransformPoint(p0)); + aPathBuilder->BezierTo(transform.TransformPoint(p1), + transform.TransformPoint(p2), + transform.TransformPoint(p3)); + } + } else { + if (aTransform.isNothing()) { + aPathBuilder->LineTo(pc); + } else { + aPathBuilder->LineTo(aTransform->TransformPoint(pc)); + } + } + } + + aPathBuilder->Close(); +} + +void AppendEllipseToPath(PathBuilder* aPathBuilder, const Point& aCenter, + const Size& aDimensions) { + Size halfDim = aDimensions / 2.f; + Rect rect(aCenter - Point(halfDim.width, halfDim.height), aDimensions); + RectCornerRadii radii(halfDim.width, halfDim.height); + + AppendRoundedRectToPath(aPathBuilder, rect, radii); +} + +bool SnapLineToDevicePixelsForStroking(Point& aP1, Point& aP2, + const DrawTarget& aDrawTarget, + Float aLineWidth) { + Matrix mat = aDrawTarget.GetTransform(); + if (mat.HasNonTranslation()) { + return false; + } + if (aP1.x != aP2.x && aP1.y != aP2.y) { + return false; // not a horizontal or vertical line + } + Point p1 = aP1 + mat.GetTranslation(); // into device space + Point p2 = aP2 + mat.GetTranslation(); + p1.Round(); + p2.Round(); + p1 -= mat.GetTranslation(); // back into user space + p2 -= mat.GetTranslation(); + + aP1 = p1; + aP2 = p2; + + bool lineWidthIsOdd = (int(aLineWidth) % 2) == 1; + if (lineWidthIsOdd) { + if (aP1.x == aP2.x) { + // snap vertical line, adding 0.5 to align it to be mid-pixel: + aP1 += Point(0.5, 0); + aP2 += Point(0.5, 0); + } else { + // snap horizontal line, adding 0.5 to align it to be mid-pixel: + aP1 += Point(0, 0.5); + aP2 += Point(0, 0.5); + } + } + return true; +} + +void StrokeSnappedEdgesOfRect(const Rect& aRect, DrawTarget& aDrawTarget, + const ColorPattern& aColor, + const StrokeOptions& aStrokeOptions) { + if (aRect.IsEmpty()) { + return; + } + + Point p1 = aRect.TopLeft(); + Point p2 = aRect.BottomLeft(); + SnapLineToDevicePixelsForStroking(p1, p2, aDrawTarget, + aStrokeOptions.mLineWidth); + aDrawTarget.StrokeLine(p1, p2, aColor, aStrokeOptions); + + p1 = aRect.BottomLeft(); + p2 = aRect.BottomRight(); + SnapLineToDevicePixelsForStroking(p1, p2, aDrawTarget, + aStrokeOptions.mLineWidth); + aDrawTarget.StrokeLine(p1, p2, aColor, aStrokeOptions); + + p1 = aRect.TopLeft(); + p2 = aRect.TopRight(); + SnapLineToDevicePixelsForStroking(p1, p2, aDrawTarget, + aStrokeOptions.mLineWidth); + aDrawTarget.StrokeLine(p1, p2, aColor, aStrokeOptions); + + p1 = aRect.TopRight(); + p2 = aRect.BottomRight(); + SnapLineToDevicePixelsForStroking(p1, p2, aDrawTarget, + aStrokeOptions.mLineWidth); + aDrawTarget.StrokeLine(p1, p2, aColor, aStrokeOptions); +} + +// The logic for this comes from _cairo_stroke_style_max_distance_from_path +Margin MaxStrokeExtents(const StrokeOptions& aStrokeOptions, + const Matrix& aTransform) { + double styleExpansionFactor = 0.5f; + + if (aStrokeOptions.mLineCap == CapStyle::SQUARE) { + styleExpansionFactor = M_SQRT1_2; + } + + if (aStrokeOptions.mLineJoin == JoinStyle::MITER && + styleExpansionFactor < M_SQRT2 * aStrokeOptions.mMiterLimit) { + styleExpansionFactor = M_SQRT2 * aStrokeOptions.mMiterLimit; + } + + styleExpansionFactor *= aStrokeOptions.mLineWidth; + + double dx = styleExpansionFactor * hypot(aTransform._11, aTransform._21); + double dy = styleExpansionFactor * hypot(aTransform._22, aTransform._12); + + // Even if the stroke only partially covers a pixel, it must still render to + // full pixels. Round up to compensate for this. + dx = ceil(dx); + dy = ceil(dy); + + return Margin(dy, dx, dy, dx); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/PathHelpers.h b/gfx/2d/PathHelpers.h new file mode 100644 index 0000000000..5fd6d45934 --- /dev/null +++ b/gfx/2d/PathHelpers.h @@ -0,0 +1,399 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PATHHELPERS_H_ +#define MOZILLA_GFX_PATHHELPERS_H_ + +#include "2D.h" +#include "UserData.h" + +#include <cmath> + +namespace mozilla { +namespace gfx { + +struct PathOp { + ~PathOp() = default; + + enum OpType { + OP_MOVETO = 0, + OP_LINETO, + OP_BEZIERTO, + OP_QUADRATICBEZIERTO, + OP_ARC, + OP_CLOSE + }; + + OpType mType; + Point mP1; +#if (!defined(__GNUC__) || __GNUC__ >= 7) && defined(__clang__) + PathOp() {} + + union { + struct { + Point mP2; + Point mP3; + }; + struct { + float mRadius; + float mStartAngle; + float mEndAngle; + bool mAntiClockwise; + }; + }; +#else + PathOp() = default; + + Point mP2; + Point mP3; + float mRadius; + float mStartAngle; + float mEndAngle; + bool mAntiClockwise; +#endif +}; + +const int32_t sPointCount[] = {1, 1, 3, 2, 0, 0}; + +// Kappa constant for 90-degree angle +const Float kKappaFactor = 0.55191497064665766025f; + +// Calculate kappa constant for partial curve. The sign of angle in the +// tangent will actually ensure this is negative for a counter clockwise +// sweep, so changing signs later isn't needed. +inline Float ComputeKappaFactor(Float aAngle) { + return (4.0f / 3.0f) * tanf(aAngle / 4.0f); +} + +/** + * Draws a partial arc <= 90 degrees given exact start and end points. + * Assumes that it is continuing from an already specified start point. + */ +template <typename T> +inline void PartialArcToBezier(T* aSink, const Point& aStartOffset, + const Point& aEndOffset, + const Matrix& aTransform, + Float aKappaFactor = kKappaFactor) { + Point cp1 = + aStartOffset + Point(-aStartOffset.y, aStartOffset.x) * aKappaFactor; + + Point cp2 = aEndOffset + Point(aEndOffset.y, -aEndOffset.x) * aKappaFactor; + + aSink->BezierTo(aTransform.TransformPoint(cp1), + aTransform.TransformPoint(cp2), + aTransform.TransformPoint(aEndOffset)); +} + +/** + * Draws an acute arc (<= 90 degrees) given exact start and end points. + * Specialized version avoiding kappa calculation. + */ +template <typename T> +inline void AcuteArcToBezier(T* aSink, const Point& aOrigin, + const Size& aRadius, const Point& aStartPoint, + const Point& aEndPoint, + Float aKappaFactor = kKappaFactor) { + aSink->LineTo(aStartPoint); + if (!aRadius.IsEmpty()) { + Float kappaX = aKappaFactor * aRadius.width / aRadius.height; + Float kappaY = aKappaFactor * aRadius.height / aRadius.width; + Point startOffset = aStartPoint - aOrigin; + Point endOffset = aEndPoint - aOrigin; + aSink->BezierTo( + aStartPoint + Point(-startOffset.y * kappaX, startOffset.x * kappaY), + aEndPoint + Point(endOffset.y * kappaX, -endOffset.x * kappaY), + aEndPoint); + } else if (aEndPoint != aStartPoint) { + aSink->LineTo(aEndPoint); + } +} + +/** + * Draws an acute arc (<= 90 degrees) given exact start and end points. + */ +template <typename T> +inline void AcuteArcToBezier(T* aSink, const Point& aOrigin, + const Size& aRadius, const Point& aStartPoint, + const Point& aEndPoint, Float aStartAngle, + Float aEndAngle) { + AcuteArcToBezier(aSink, aOrigin, aRadius, aStartPoint, aEndPoint, + ComputeKappaFactor(aEndAngle - aStartAngle)); +} + +template <typename T> +void ArcToBezier(T* aSink, const Point& aOrigin, const Size& aRadius, + float aStartAngle, float aEndAngle, bool aAntiClockwise, + float aRotation = 0.0f, const Matrix& aTransform = Matrix()) { + Float sweepDirection = aAntiClockwise ? -1.0f : 1.0f; + + // Calculate the total arc we're going to sweep. + Float arcSweepLeft = (aEndAngle - aStartAngle) * sweepDirection; + + // Clockwise we always sweep from the smaller to the larger angle, ccw + // it's vice versa. + if (arcSweepLeft < 0) { + // Rerverse sweep is modulo'd into range rather than clamped. + arcSweepLeft = Float(2.0f * M_PI) + fmodf(arcSweepLeft, Float(2.0f * M_PI)); + // Recalculate the start angle to land closer to end angle. + aStartAngle = aEndAngle - arcSweepLeft * sweepDirection; + } else if (arcSweepLeft > Float(2.0f * M_PI)) { + // Sweeping more than 2 * pi is a full circle. + arcSweepLeft = Float(2.0f * M_PI); + } + + Float currentStartAngle = aStartAngle; + Point currentStartOffset(cosf(aStartAngle), sinf(aStartAngle)); + Matrix transform = Matrix::Scaling(aRadius.width, aRadius.height); + if (aRotation != 0.0f) { + transform *= Matrix::Rotation(aRotation); + } + transform.PostTranslate(aOrigin); + transform *= aTransform; + aSink->LineTo(transform.TransformPoint(currentStartOffset)); + + while (arcSweepLeft > 0) { + Float currentEndAngle = + currentStartAngle + + std::min(arcSweepLeft, Float(M_PI / 2.0f)) * sweepDirection; + Point currentEndOffset(cosf(currentEndAngle), sinf(currentEndAngle)); + + PartialArcToBezier(aSink, currentStartOffset, currentEndOffset, transform, + ComputeKappaFactor(currentEndAngle - currentStartAngle)); + + // We guarantee here the current point is the start point of the next + // curve segment. + arcSweepLeft -= Float(M_PI / 2.0f); + currentStartAngle = currentEndAngle; + currentStartOffset = currentEndOffset; + } +} + +/* This is basically the ArcToBezier with the parameters for drawing a circle + * inlined which vastly simplifies it and avoids a bunch of transcedental + * function calls which should make it faster. */ +template <typename T> +void EllipseToBezier(T* aSink, const Point& aOrigin, const Size& aRadius) { + Matrix transform(aRadius.width, 0, 0, aRadius.height, aOrigin.x, aOrigin.y); + Point currentStartOffset(1, 0); + + aSink->LineTo(transform.TransformPoint(currentStartOffset)); + + for (int i = 0; i < 4; i++) { + // cos(x+pi/2) == -sin(x) + // sin(x+pi/2) == cos(x) + Point currentEndOffset(-currentStartOffset.y, currentStartOffset.x); + + PartialArcToBezier(aSink, currentStartOffset, currentEndOffset, transform); + + // We guarantee here the current point is the start point of the next + // curve segment. + currentStartOffset = currentEndOffset; + } +} + +/** + * Appends a path represending a rectangle to the path being built by + * aPathBuilder. + * + * aRect The rectangle to append. + * aDrawClockwise If set to true, the path will start at the left of the top + * left edge and draw clockwise. If set to false the path will + * start at the right of the top left edge and draw counter- + * clockwise. + */ +GFX2D_API void AppendRectToPath(PathBuilder* aPathBuilder, const Rect& aRect, + bool aDrawClockwise = true); + +inline already_AddRefed<Path> MakePathForRect(const DrawTarget& aDrawTarget, + const Rect& aRect, + bool aDrawClockwise = true) { + RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(); + AppendRectToPath(builder, aRect, aDrawClockwise); + return builder->Finish(); +} + +/** + * Appends a path represending a rounded rectangle to the path being built by + * aPathBuilder. + * + * aRect The rectangle to append. + * aCornerRadii Contains the radii of the top-left, top-right, bottom-right + * and bottom-left corners, in that order. + * aDrawClockwise If set to true, the path will start at the left of the top + * left edge and draw clockwise. If set to false the path will + * start at the right of the top left edge and draw counter- + * clockwise. + */ +GFX2D_API void AppendRoundedRectToPath( + PathBuilder* aPathBuilder, const Rect& aRect, const RectCornerRadii& aRadii, + bool aDrawClockwise = true, const Maybe<Matrix>& aTransform = Nothing()); + +inline already_AddRefed<Path> MakePathForRoundedRect( + const DrawTarget& aDrawTarget, const Rect& aRect, + const RectCornerRadii& aRadii, bool aDrawClockwise = true) { + RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(); + AppendRoundedRectToPath(builder, aRect, aRadii, aDrawClockwise); + return builder->Finish(); +} + +/** + * Appends a path represending an ellipse to the path being built by + * aPathBuilder. + * + * The ellipse extends aDimensions.width / 2.0 in the horizontal direction + * from aCenter, and aDimensions.height / 2.0 in the vertical direction. + */ +GFX2D_API void AppendEllipseToPath(PathBuilder* aPathBuilder, + const Point& aCenter, + const Size& aDimensions); + +inline already_AddRefed<Path> MakePathForEllipse(const DrawTarget& aDrawTarget, + const Point& aCenter, + const Size& aDimensions) { + RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(); + AppendEllipseToPath(builder, aCenter, aDimensions); + return builder->Finish(); +} + +inline already_AddRefed<Path> MakePathForCircle(const DrawTarget& aDrawTarget, + const Point& aCenter, + float aRadius) { + RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(); + builder->Arc(aCenter, aRadius, 0.0f, Float(2.0 * M_PI)); + builder->Close(); + return builder->Finish(); +} + +/** + * If aDrawTarget's transform only contains a translation, and if this line is + * a horizontal or vertical line, this function will snap the line's vertices + * to align with the device pixel grid so that stroking the line with a one + * pixel wide stroke will result in a crisp line that is not antialiased over + * two pixels across its width. + * + * @return Returns true if this function snaps aRect's vertices, else returns + * false. + */ +GFX2D_API bool SnapLineToDevicePixelsForStroking(Point& aP1, Point& aP2, + const DrawTarget& aDrawTarget, + Float aLineWidth); + +/** + * This function paints each edge of aRect separately, snapping the edges using + * SnapLineToDevicePixelsForStroking. Stroking the edges as separate paths + * helps ensure not only that the stroke spans a single row of device pixels if + * possible, but also that the ends of stroke dashes start and end on device + * pixels too. + */ +GFX2D_API void StrokeSnappedEdgesOfRect(const Rect& aRect, + DrawTarget& aDrawTarget, + const ColorPattern& aColor, + const StrokeOptions& aStrokeOptions); + +/** + * Return the margin, in device space, by which a stroke can extend beyond the + * rendered shape. + * @param aStrokeOptions The stroke options that the stroke is drawn with. + * @param aTransform The user space to device space transform. + * @return The stroke margin. + */ +GFX2D_API Margin MaxStrokeExtents(const StrokeOptions& aStrokeOptions, + const Matrix& aTransform); + +extern UserDataKey sDisablePixelSnapping; + +/** + * If aDrawTarget's transform only contains a translation or, if + * aAllowScaleOr90DegreeRotate is true, and/or a scale/90 degree rotation, this + * function will convert aRect to device space and snap it to device pixels. + * This function returns true if aRect is modified, otherwise it returns false. + * + * Note that the snapping is such that filling the rect using a DrawTarget + * which has the identity matrix as its transform will result in crisp edges. + * (That is, aRect will have integer values, aligning its edges between pixel + * boundaries.) If on the other hand you stroking the rect with an odd valued + * stroke width then the edges of the stroke will be antialiased (assuming an + * AntialiasMode that does antialiasing). + * + * Empty snaps are those which result in a rectangle of 0 area. If they are + * disallowed, an axis is left unsnapped if the rounding process results in a + * length of 0. + */ +inline bool UserToDevicePixelSnapped(Rect& aRect, const DrawTarget& aDrawTarget, + bool aAllowScaleOr90DegreeRotate = false, + bool aAllowEmptySnaps = true) { + if (aDrawTarget.GetUserData(&sDisablePixelSnapping)) { + return false; + } + + Matrix mat = aDrawTarget.GetTransform(); + + const Float epsilon = 0.0000001f; +#define WITHIN_E(a, b) (fabs((a) - (b)) < epsilon) + if (!aAllowScaleOr90DegreeRotate && + (!WITHIN_E(mat._11, 1.f) || !WITHIN_E(mat._22, 1.f) || + !WITHIN_E(mat._12, 0.f) || !WITHIN_E(mat._21, 0.f))) { + // We have non-translation, but only translation is allowed. + return false; + } +#undef WITHIN_E + + Point p1 = mat.TransformPoint(aRect.TopLeft()); + Point p2 = mat.TransformPoint(aRect.TopRight()); + Point p3 = mat.TransformPoint(aRect.BottomRight()); + + // Check that the rectangle is axis-aligned. For an axis-aligned rectangle, + // two opposite corners define the entire rectangle. So check if + // the axis-aligned rectangle with opposite corners p1 and p3 + // define an axis-aligned rectangle whose other corners are p2 and p4. + // We actually only need to check one of p2 and p4, since an affine + // transform maps parallelograms to parallelograms. + if (p2 == Point(p1.x, p3.y) || p2 == Point(p3.x, p1.y)) { + Point p1r = p1; + Point p3r = p3; + p1r.Round(); + p3r.Round(); + if (aAllowEmptySnaps || p1r.x != p3r.x) { + p1.x = p1r.x; + p3.x = p3r.x; + } + if (aAllowEmptySnaps || p1r.y != p3r.y) { + p1.y = p1r.y; + p3.y = p3r.y; + } + + aRect.MoveTo(Point(std::min(p1.x, p3.x), std::min(p1.y, p3.y))); + aRect.SizeTo(Size(std::max(p1.x, p3.x) - aRect.X(), + std::max(p1.y, p3.y) - aRect.Y())); + return true; + } + + return false; +} + +/** + * This function has the same behavior as UserToDevicePixelSnapped except that + * aRect is not transformed to device space. + */ +inline bool MaybeSnapToDevicePixels(Rect& aRect, const DrawTarget& aDrawTarget, + bool aAllowScaleOr90DegreeRotate = false, + bool aAllowEmptySnaps = true) { + if (UserToDevicePixelSnapped(aRect, aDrawTarget, aAllowScaleOr90DegreeRotate, + aAllowEmptySnaps)) { + // Since UserToDevicePixelSnapped returned true we know there is no + // rotation/skew in 'mat', so we can just use TransformBounds() here. + Matrix mat = aDrawTarget.GetTransform(); + mat.Invert(); + aRect = mat.TransformBounds(aRect); + return true; + } + return false; +} + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PATHHELPERS_H_ */ diff --git a/gfx/2d/PathRecording.cpp b/gfx/2d/PathRecording.cpp new file mode 100644 index 0000000000..2250986160 --- /dev/null +++ b/gfx/2d/PathRecording.cpp @@ -0,0 +1,398 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PathRecording.h" +#include "DrawEventRecorder.h" +#include "RecordedEventImpl.h" + +namespace mozilla { +namespace gfx { + +#define NEXT_PARAMS(_type) \ + const _type params = *reinterpret_cast<const _type*>(nextByte); \ + nextByte += sizeof(_type); + +bool PathOps::StreamToSink(PathSink& aPathSink) const { + if (mPathData.empty()) { + return true; + } + + const uint8_t* nextByte = mPathData.data(); + const uint8_t* end = nextByte + mPathData.size(); + while (nextByte < end) { + const OpType opType = *reinterpret_cast<const OpType*>(nextByte); + nextByte += sizeof(OpType); + switch (opType) { + case OpType::OP_MOVETO: { + NEXT_PARAMS(Point) + aPathSink.MoveTo(params); + break; + } + case OpType::OP_LINETO: { + NEXT_PARAMS(Point) + aPathSink.LineTo(params); + break; + } + case OpType::OP_BEZIERTO: { + NEXT_PARAMS(ThreePoints) + aPathSink.BezierTo(params.p1, params.p2, params.p3); + break; + } + case OpType::OP_QUADRATICBEZIERTO: { + NEXT_PARAMS(TwoPoints) + aPathSink.QuadraticBezierTo(params.p1, params.p2); + break; + } + case OpType::OP_ARC: { + NEXT_PARAMS(ArcParams) + aPathSink.Arc(params.origin, params.radius, params.startAngle, + params.endAngle, params.antiClockwise); + break; + } + case OpType::OP_CLOSE: + aPathSink.Close(); + break; + default: + return false; + } + } + + return true; +} + +#define CHECKED_NEXT_PARAMS(_type) \ + if (nextByte + sizeof(_type) > end) { \ + return false; \ + } \ + NEXT_PARAMS(_type) + +bool PathOps::CheckedStreamToSink(PathSink& aPathSink) const { + if (mPathData.empty()) { + return true; + } + + const uint8_t* nextByte = mPathData.data(); + const uint8_t* end = nextByte + mPathData.size(); + while (true) { + if (nextByte == end) { + break; + } + + if (nextByte + sizeof(OpType) > end) { + return false; + } + + const OpType opType = *reinterpret_cast<const OpType*>(nextByte); + nextByte += sizeof(OpType); + switch (opType) { + case OpType::OP_MOVETO: { + CHECKED_NEXT_PARAMS(Point) + aPathSink.MoveTo(params); + break; + } + case OpType::OP_LINETO: { + CHECKED_NEXT_PARAMS(Point) + aPathSink.LineTo(params); + break; + } + case OpType::OP_BEZIERTO: { + CHECKED_NEXT_PARAMS(ThreePoints) + aPathSink.BezierTo(params.p1, params.p2, params.p3); + break; + } + case OpType::OP_QUADRATICBEZIERTO: { + CHECKED_NEXT_PARAMS(TwoPoints) + aPathSink.QuadraticBezierTo(params.p1, params.p2); + break; + } + case OpType::OP_ARC: { + CHECKED_NEXT_PARAMS(ArcParams) + aPathSink.Arc(params.origin, params.radius, params.startAngle, + params.endAngle, params.antiClockwise); + break; + } + case OpType::OP_CLOSE: + aPathSink.Close(); + break; + default: + return false; + } + } + + return true; +} +#undef CHECKED_NEXT_PARAMS + +PathOps PathOps::TransformedCopy(const Matrix& aTransform) const { + PathOps newPathOps; + const uint8_t* nextByte = mPathData.data(); + const uint8_t* end = nextByte + mPathData.size(); + while (nextByte < end) { + const OpType opType = *reinterpret_cast<const OpType*>(nextByte); + nextByte += sizeof(OpType); + switch (opType) { + case OpType::OP_MOVETO: { + NEXT_PARAMS(Point) + newPathOps.MoveTo(aTransform.TransformPoint(params)); + break; + } + case OpType::OP_LINETO: { + NEXT_PARAMS(Point) + newPathOps.LineTo(aTransform.TransformPoint(params)); + break; + } + case OpType::OP_BEZIERTO: { + NEXT_PARAMS(ThreePoints) + newPathOps.BezierTo(aTransform.TransformPoint(params.p1), + aTransform.TransformPoint(params.p2), + aTransform.TransformPoint(params.p3)); + break; + } + case OpType::OP_QUADRATICBEZIERTO: { + NEXT_PARAMS(TwoPoints) + newPathOps.QuadraticBezierTo(aTransform.TransformPoint(params.p1), + aTransform.TransformPoint(params.p2)); + break; + } + case OpType::OP_ARC: { + NEXT_PARAMS(ArcParams) + ArcToBezier(&newPathOps, params.origin, + gfx::Size(params.radius, params.radius), params.startAngle, + params.endAngle, params.antiClockwise, 0.0f, aTransform); + break; + } + case OpType::OP_CLOSE: + newPathOps.Close(); + break; + default: + MOZ_CRASH("We control mOpTypes, so this should never happen."); + } + } + + return newPathOps; +} + +Maybe<Circle> PathOps::AsCircle() const { + if (mPathData.empty()) { + return Nothing(); + } + + const uint8_t* nextByte = mPathData.data(); + const uint8_t* end = nextByte + mPathData.size(); + const OpType opType = *reinterpret_cast<const OpType*>(nextByte); + nextByte += sizeof(OpType); + if (opType == OpType::OP_ARC) { + NEXT_PARAMS(ArcParams) + if (fabs(fabs(params.startAngle - params.endAngle) - 2 * M_PI) < 1e-6) { + // we have a full circle + if (nextByte < end) { + const OpType nextOpType = *reinterpret_cast<const OpType*>(nextByte); + nextByte += sizeof(OpType); + if (nextOpType == OpType::OP_CLOSE) { + if (nextByte == end) { + return Some(Circle{params.origin, params.radius, true}); + } + } + } else { + // the circle wasn't closed + return Some(Circle{params.origin, params.radius, false}); + } + } + } + + return Nothing(); +} + +Maybe<Line> PathOps::AsLine() const { + if (mPathData.empty()) { + return Nothing(); + } + + Line retval; + + const uint8_t* nextByte = mPathData.data(); + const uint8_t* end = nextByte + mPathData.size(); + OpType opType = *reinterpret_cast<const OpType*>(nextByte); + nextByte += sizeof(OpType); + + if (opType == OpType::OP_MOVETO) { + MOZ_ASSERT(nextByte != end); + + NEXT_PARAMS(Point) + retval.origin = params; + } else { + return Nothing(); + } + + if (nextByte >= end) { + return Nothing(); + } + + opType = *reinterpret_cast<const OpType*>(nextByte); + nextByte += sizeof(OpType); + + if (opType == OpType::OP_LINETO) { + MOZ_ASSERT(nextByte != end); + + NEXT_PARAMS(Point) + + if (nextByte == end) { + retval.destination = params; + return Some(retval); + } + } + + return Nothing(); +} +#undef NEXT_PARAMS + +size_t PathOps::NumberOfOps() const { + size_t size = 0; + const uint8_t* nextByte = mPathData.data(); + const uint8_t* end = nextByte + mPathData.size(); + while (nextByte < end) { + size++; + const OpType opType = *reinterpret_cast<const OpType*>(nextByte); + nextByte += sizeof(OpType); + switch (opType) { + case OpType::OP_MOVETO: + nextByte += sizeof(Point); + break; + case OpType::OP_LINETO: + nextByte += sizeof(Point); + break; + case OpType::OP_BEZIERTO: + nextByte += sizeof(ThreePoints); + break; + case OpType::OP_QUADRATICBEZIERTO: + nextByte += sizeof(TwoPoints); + break; + case OpType::OP_ARC: + nextByte += sizeof(ArcParams); + break; + case OpType::OP_CLOSE: + break; + default: + MOZ_CRASH("We control mOpTypes, so this should never happen."); + } + } + + return size; +} + +bool PathOps::IsEmpty() const { + const uint8_t* nextByte = mPathData.data(); + const uint8_t* end = nextByte + mPathData.size(); + while (nextByte < end) { + const OpType opType = *reinterpret_cast<const OpType*>(nextByte); + nextByte += sizeof(OpType); + switch (opType) { + case OpType::OP_MOVETO: + nextByte += sizeof(Point); + break; + case OpType::OP_CLOSE: + break; + default: + return false; + } + } + return true; +} + +void PathBuilderRecording::MoveTo(const Point& aPoint) { + mPathOps.MoveTo(aPoint); + mBeginPoint = aPoint; + mCurrentPoint = aPoint; +} + +void PathBuilderRecording::LineTo(const Point& aPoint) { + mPathOps.LineTo(aPoint); + mCurrentPoint = aPoint; +} + +void PathBuilderRecording::BezierTo(const Point& aCP1, const Point& aCP2, + const Point& aCP3) { + mPathOps.BezierTo(aCP1, aCP2, aCP3); + mCurrentPoint = aCP3; +} + +void PathBuilderRecording::QuadraticBezierTo(const Point& aCP1, + const Point& aCP2) { + mPathOps.QuadraticBezierTo(aCP1, aCP2); + mCurrentPoint = aCP2; +} + +void PathBuilderRecording::Close() { + mPathOps.Close(); + mCurrentPoint = mBeginPoint; +} + +void PathBuilderRecording::Arc(const Point& aOrigin, float aRadius, + float aStartAngle, float aEndAngle, + bool aAntiClockwise) { + mPathOps.Arc(aOrigin, aRadius, aStartAngle, aEndAngle, aAntiClockwise); + + mCurrentPoint = aOrigin + Point(cosf(aEndAngle), sinf(aEndAngle)) * aRadius; +} + +already_AddRefed<Path> PathBuilderRecording::Finish() { + return MakeAndAddRef<PathRecording>(mBackendType, std::move(mPathOps), + mFillRule, mCurrentPoint, mBeginPoint); +} + +PathRecording::PathRecording(BackendType aBackend, PathOps&& aOps, + FillRule aFillRule, const Point& aCurrentPoint, + const Point& aBeginPoint) + : mBackendType(aBackend), + mPathOps(std::move(aOps)), + mFillRule(aFillRule), + mCurrentPoint(aCurrentPoint), + mBeginPoint(aBeginPoint) {} + +PathRecording::~PathRecording() { + for (size_t i = 0; i < mStoredRecorders.size(); i++) { + mStoredRecorders[i]->RemoveStoredObject(this); + mStoredRecorders[i]->RecordEvent(RecordedPathDestruction(this)); + } +} + +void PathRecording::EnsurePath() const { + if (mPath) { + return; + } + if (RefPtr<PathBuilder> pathBuilder = + Factory::CreatePathBuilder(mBackendType, mFillRule)) { + if (!mPathOps.StreamToSink(*pathBuilder)) { + MOZ_ASSERT(false, "Failed to stream PathOps to PathBuilder"); + } else { + mPath = pathBuilder->Finish(); + MOZ_ASSERT(!!mPath, "Failed finishing Path from PathBuilder"); + } + } else { + MOZ_ASSERT(false, "Failed to create PathBuilder for PathRecording"); + } +} + +already_AddRefed<PathBuilder> PathRecording::CopyToBuilder( + FillRule aFillRule) const { + RefPtr<PathBuilderRecording> recording = + new PathBuilderRecording(mBackendType, PathOps(mPathOps), aFillRule); + recording->SetCurrentPoint(mCurrentPoint); + recording->SetBeginPoint(mBeginPoint); + return recording.forget(); +} + +already_AddRefed<PathBuilder> PathRecording::TransformedCopyToBuilder( + const Matrix& aTransform, FillRule aFillRule) const { + RefPtr<PathBuilderRecording> recording = new PathBuilderRecording( + mBackendType, mPathOps.TransformedCopy(aTransform), aFillRule); + recording->SetCurrentPoint(aTransform.TransformPoint(mCurrentPoint)); + recording->SetBeginPoint(aTransform.TransformPoint(mBeginPoint)); + return recording.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/PathRecording.h b/gfx/2d/PathRecording.h new file mode 100644 index 0000000000..f804381cb1 --- /dev/null +++ b/gfx/2d/PathRecording.h @@ -0,0 +1,263 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PATHRECORDING_H_ +#define MOZILLA_GFX_PATHRECORDING_H_ + +#include "2D.h" +#include <vector> +#include <ostream> + +#include "PathHelpers.h" +#include "RecordingTypes.h" + +namespace mozilla { +namespace gfx { + +struct Circle { + Point origin; + float radius; + bool closed = false; +}; + +struct Line { + Point origin; + Point destination; +}; + +class PathOps { + public: + PathOps() = default; + + template <class S> + explicit PathOps(S& aStream); + + PathOps(const PathOps& aOther) = default; + PathOps& operator=(const PathOps&) = delete; // assign using std::move()! + + PathOps(PathOps&& aOther) = default; + PathOps& operator=(PathOps&& aOther) = default; + + template <class S> + void Record(S& aStream) const; + + bool StreamToSink(PathSink& aPathSink) const; + + bool CheckedStreamToSink(PathSink& aPathSink) const; + + PathOps TransformedCopy(const Matrix& aTransform) const; + + size_t NumberOfOps() const; + + void MoveTo(const Point& aPoint) { AppendPathOp(OpType::OP_MOVETO, aPoint); } + + void LineTo(const Point& aPoint) { AppendPathOp(OpType::OP_LINETO, aPoint); } + + void BezierTo(const Point& aCP1, const Point& aCP2, const Point& aCP3) { + AppendPathOp(OpType::OP_BEZIERTO, ThreePoints{aCP1, aCP2, aCP3}); + } + + void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) { + AppendPathOp(OpType::OP_QUADRATICBEZIERTO, TwoPoints{aCP1, aCP2}); + } + + void Arc(const Point& aOrigin, float aRadius, float aStartAngle, + float aEndAngle, bool aAntiClockwise) { + AppendPathOp(OpType::OP_ARC, ArcParams{aOrigin, aRadius, aStartAngle, + aEndAngle, aAntiClockwise}); + } + + void Close() { + size_t oldSize = mPathData.size(); + mPathData.resize(oldSize + sizeof(OpType)); + *reinterpret_cast<OpType*>(mPathData.data() + oldSize) = OpType::OP_CLOSE; + } + + Maybe<Circle> AsCircle() const; + Maybe<Line> AsLine() const; + + bool IsActive() const { return !mPathData.empty(); } + + bool IsEmpty() const; + + private: + enum class OpType : uint32_t { + OP_MOVETO = 0, + OP_LINETO, + OP_BEZIERTO, + OP_QUADRATICBEZIERTO, + OP_ARC, + OP_CLOSE, + OP_INVALID + }; + + template <typename T> + void AppendPathOp(const OpType& aOpType, const T& aOpParams) { + size_t oldSize = mPathData.size(); + mPathData.resize(oldSize + sizeof(OpType) + sizeof(T)); + memcpy(mPathData.data() + oldSize, &aOpType, sizeof(OpType)); + oldSize += sizeof(OpType); + memcpy(mPathData.data() + oldSize, &aOpParams, sizeof(T)); + } + + struct TwoPoints { + Point p1; + Point p2; + }; + + struct ThreePoints { + Point p1; + Point p2; + Point p3; + }; + + struct ArcParams { + Point origin; + float radius; + float startAngle; + float endAngle; + bool antiClockwise; + }; + + std::vector<uint8_t> mPathData; +}; + +template <class S> +PathOps::PathOps(S& aStream) { + ReadVector(aStream, mPathData); +} + +template <class S> +inline void PathOps::Record(S& aStream) const { + WriteVector(aStream, mPathData); +} + +class PathRecording; +class DrawEventRecorderPrivate; + +class PathBuilderRecording final : public PathBuilder { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderRecording, override) + + PathBuilderRecording(BackendType aBackend, FillRule aFillRule) + : mBackendType(aBackend), mFillRule(aFillRule) {} + + PathBuilderRecording(BackendType aBackend, PathOps&& aPathOps, + FillRule aFillRule) + : mBackendType(aBackend), + mFillRule(aFillRule), + mPathOps(std::move(aPathOps)) {} + + /* Move the current point in the path, any figure currently being drawn will + * be considered closed during fill operations, however when stroking the + * closing line segment will not be drawn. + */ + void MoveTo(const Point& aPoint) final; + + /* Add a linesegment to the current figure */ + void LineTo(const Point& aPoint) final; + + /* Add a cubic bezier curve to the current figure */ + void BezierTo(const Point& aCP1, const Point& aCP2, const Point& aCP3) final; + + /* Add a quadratic bezier curve to the current figure */ + void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) final; + + /* Close the current figure, this will essentially generate a line segment + * from the current point to the starting point for the current figure + */ + void Close() final; + + /* Add an arc to the current figure */ + void Arc(const Point& aOrigin, float aRadius, float aStartAngle, + float aEndAngle, bool aAntiClockwise) final; + + already_AddRefed<Path> Finish() final; + + BackendType GetBackendType() const final { return BackendType::RECORDING; } + + bool IsActive() const final { return mPathOps.IsActive(); } + + private: + BackendType mBackendType; + FillRule mFillRule; + PathOps mPathOps; +}; + +class PathRecording final : public Path { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathRecording, override) + + PathRecording(BackendType aBackend, PathOps&& aOps, FillRule aFillRule, + const Point& aCurrentPoint, const Point& aBeginPoint); + + ~PathRecording(); + + BackendType GetBackendType() const final { return BackendType::RECORDING; } + already_AddRefed<PathBuilder> CopyToBuilder(FillRule aFillRule) const final; + already_AddRefed<PathBuilder> TransformedCopyToBuilder( + const Matrix& aTransform, FillRule aFillRule) const final; + bool ContainsPoint(const Point& aPoint, + const Matrix& aTransform) const final { + EnsurePath(); + return mPath->ContainsPoint(aPoint, aTransform); + } + bool StrokeContainsPoint(const StrokeOptions& aStrokeOptions, + const Point& aPoint, + const Matrix& aTransform) const final { + EnsurePath(); + return mPath->StrokeContainsPoint(aStrokeOptions, aPoint, aTransform); + } + + Rect GetBounds(const Matrix& aTransform = Matrix()) const final { + EnsurePath(); + return mPath->GetBounds(aTransform); + } + + Rect GetStrokedBounds(const StrokeOptions& aStrokeOptions, + const Matrix& aTransform = Matrix()) const final { + EnsurePath(); + return mPath->GetStrokedBounds(aStrokeOptions, aTransform); + } + + Maybe<Rect> AsRect() const final { + EnsurePath(); + return mPath->AsRect(); + } + + Maybe<Circle> AsCircle() const { return mPathOps.AsCircle(); } + Maybe<Line> AsLine() const { return mPathOps.AsLine(); } + + void StreamToSink(PathSink* aSink) const final { + mPathOps.StreamToSink(*aSink); + } + + FillRule GetFillRule() const final { return mFillRule; } + + bool IsEmpty() const final { return mPathOps.IsEmpty(); } + + private: + friend class DrawTargetWrapAndRecord; + friend class DrawTargetRecording; + friend class RecordedPathCreation; + + void EnsurePath() const; + + BackendType mBackendType; + mutable RefPtr<Path> mPath; + PathOps mPathOps; + FillRule mFillRule; + Point mCurrentPoint; + Point mBeginPoint; + + // Event recorders that have this path in their event stream. + std::vector<RefPtr<DrawEventRecorderPrivate>> mStoredRecorders; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PATHRECORDING_H_ */ diff --git a/gfx/2d/PathSkia.cpp b/gfx/2d/PathSkia.cpp new file mode 100644 index 0000000000..a7451d24ff --- /dev/null +++ b/gfx/2d/PathSkia.cpp @@ -0,0 +1,281 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PathSkia.h" +#include "HelpersSkia.h" +#include "PathHelpers.h" +#include "mozilla/UniquePtr.h" +#include "skia/include/core/SkPathUtils.h" +#include "skia/src/core/SkGeometry.h" + +namespace mozilla::gfx { + +already_AddRefed<PathBuilder> PathBuilderSkia::Create(FillRule aFillRule) { + return MakeAndAddRef<PathBuilderSkia>(aFillRule); +} + +PathBuilderSkia::PathBuilderSkia(const Matrix& aTransform, const SkPath& aPath, + FillRule aFillRule) + : mPath(aPath) { + SkMatrix matrix; + GfxMatrixToSkiaMatrix(aTransform, matrix); + mPath.transform(matrix); + SetFillRule(aFillRule); +} + +PathBuilderSkia::PathBuilderSkia(FillRule aFillRule) { SetFillRule(aFillRule); } + +void PathBuilderSkia::SetFillRule(FillRule aFillRule) { + mFillRule = aFillRule; + if (mFillRule == FillRule::FILL_WINDING) { + mPath.setFillType(SkPathFillType::kWinding); + } else { + mPath.setFillType(SkPathFillType::kEvenOdd); + } +} + +void PathBuilderSkia::MoveTo(const Point& aPoint) { + mPath.moveTo(SkFloatToScalar(aPoint.x), SkFloatToScalar(aPoint.y)); + mCurrentPoint = aPoint; + mBeginPoint = aPoint; +} + +void PathBuilderSkia::LineTo(const Point& aPoint) { + if (!mPath.countPoints()) { + MoveTo(aPoint); + } else { + mPath.lineTo(SkFloatToScalar(aPoint.x), SkFloatToScalar(aPoint.y)); + } + mCurrentPoint = aPoint; +} + +void PathBuilderSkia::BezierTo(const Point& aCP1, const Point& aCP2, + const Point& aCP3) { + if (!mPath.countPoints()) { + MoveTo(aCP1); + } + mPath.cubicTo(SkFloatToScalar(aCP1.x), SkFloatToScalar(aCP1.y), + SkFloatToScalar(aCP2.x), SkFloatToScalar(aCP2.y), + SkFloatToScalar(aCP3.x), SkFloatToScalar(aCP3.y)); + mCurrentPoint = aCP3; +} + +void PathBuilderSkia::QuadraticBezierTo(const Point& aCP1, const Point& aCP2) { + if (!mPath.countPoints()) { + MoveTo(aCP1); + } + mPath.quadTo(SkFloatToScalar(aCP1.x), SkFloatToScalar(aCP1.y), + SkFloatToScalar(aCP2.x), SkFloatToScalar(aCP2.y)); + mCurrentPoint = aCP2; +} + +void PathBuilderSkia::Close() { + mPath.close(); + mCurrentPoint = mBeginPoint; +} + +void PathBuilderSkia::Arc(const Point& aOrigin, float aRadius, + float aStartAngle, float aEndAngle, + bool aAntiClockwise) { + ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle, + aAntiClockwise); +} + +already_AddRefed<Path> PathBuilderSkia::Finish() { + RefPtr<Path> path = + MakeAndAddRef<PathSkia>(mPath, mFillRule, mCurrentPoint, mBeginPoint); + mCurrentPoint = Point(0.0, 0.0); + mBeginPoint = Point(0.0, 0.0); + return path.forget(); +} + +void PathBuilderSkia::AppendPath(const SkPath& aPath) { mPath.addPath(aPath); } + +already_AddRefed<PathBuilder> PathSkia::CopyToBuilder( + FillRule aFillRule) const { + return TransformedCopyToBuilder(Matrix(), aFillRule); +} + +already_AddRefed<PathBuilder> PathSkia::TransformedCopyToBuilder( + const Matrix& aTransform, FillRule aFillRule) const { + RefPtr<PathBuilderSkia> builder = + MakeAndAddRef<PathBuilderSkia>(aTransform, mPath, aFillRule); + + builder->mCurrentPoint = aTransform.TransformPoint(mCurrentPoint); + builder->mBeginPoint = aTransform.TransformPoint(mBeginPoint); + + return builder.forget(); +} + +static bool SkPathContainsPoint(const SkPath& aPath, const Point& aPoint, + const Matrix& aTransform) { + Matrix inverse = aTransform; + if (!inverse.Invert()) { + return false; + } + + SkPoint point = PointToSkPoint(inverse.TransformPoint(aPoint)); + return aPath.contains(point.fX, point.fY); +} + +bool PathSkia::ContainsPoint(const Point& aPoint, + const Matrix& aTransform) const { + if (!mPath.isFinite()) { + return false; + } + + return SkPathContainsPoint(mPath, aPoint, aTransform); +} + +bool PathSkia::GetFillPath(const StrokeOptions& aStrokeOptions, + const Matrix& aTransform, SkPath& aFillPath, + const Maybe<Rect>& aClipRect) const { + SkPaint paint; + if (!StrokeOptionsToPaint(paint, aStrokeOptions)) { + return false; + } + + SkMatrix skiaMatrix; + GfxMatrixToSkiaMatrix(aTransform, skiaMatrix); + + Maybe<SkRect> cullRect; + if (aClipRect.isSome()) { + cullRect = Some(RectToSkRect(aClipRect.ref())); + } + + return skpathutils::FillPathWithPaint(mPath, paint, &aFillPath, + cullRect.ptrOr(nullptr), skiaMatrix); +} + +bool PathSkia::StrokeContainsPoint(const StrokeOptions& aStrokeOptions, + const Point& aPoint, + const Matrix& aTransform) const { + if (!mPath.isFinite()) { + return false; + } + + SkPath strokePath; + if (!GetFillPath(aStrokeOptions, aTransform, strokePath)) { + return false; + } + + return SkPathContainsPoint(strokePath, aPoint, aTransform); +} + +Rect PathSkia::GetBounds(const Matrix& aTransform) const { + if (!mPath.isFinite()) { + return Rect(); + } + + Rect bounds = SkRectToRect(mPath.computeTightBounds()); + return aTransform.TransformBounds(bounds); +} + +Rect PathSkia::GetStrokedBounds(const StrokeOptions& aStrokeOptions, + const Matrix& aTransform) const { + if (!mPath.isFinite()) { + return Rect(); + } + + SkPath fillPath; + if (!GetFillPath(aStrokeOptions, aTransform, fillPath)) { + return Rect(); + } + + Rect bounds = SkRectToRect(fillPath.computeTightBounds()); + return aTransform.TransformBounds(bounds); +} + +Rect PathSkia::GetFastBounds(const Matrix& aTransform, + const StrokeOptions* aStrokeOptions) const { + if (!mPath.isFinite()) { + return Rect(); + } + SkRect bounds = mPath.getBounds(); + if (aStrokeOptions) { + // If the path is stroked, ensure that the bounds are inflated by any + // relevant options such as line width. Avoid using dash path effects + // for performance and to ensure computeFastStrokeBounds succeeds. + SkPaint paint; + if (!StrokeOptionsToPaint(paint, *aStrokeOptions, false)) { + return Rect(); + } + SkRect outBounds = SkRect::MakeEmpty(); + bounds = paint.computeFastStrokeBounds(bounds, &outBounds); + } + return aTransform.TransformBounds(SkRectToRect(bounds)); +} + +int ConvertConicToQuads(const Point& aP0, const Point& aP1, const Point& aP2, + float aWeight, std::vector<Point>& aQuads) { + SkConic conic(PointToSkPoint(aP0), PointToSkPoint(aP1), PointToSkPoint(aP2), + aWeight); + int pow2 = conic.computeQuadPOW2(0.25f); + aQuads.resize(1 + 2 * (1 << pow2)); + int numQuads = + conic.chopIntoQuadsPOW2(reinterpret_cast<SkPoint*>(&aQuads[0]), pow2); + if (numQuads < 1 << pow2) { + aQuads.resize(1 + 2 * numQuads); + } + return numQuads; +} + +void PathSkia::StreamToSink(PathSink* aSink) const { + SkPath::RawIter iter(mPath); + + SkPoint points[4]; + SkPath::Verb currentVerb; + while ((currentVerb = iter.next(points)) != SkPath::kDone_Verb) { + switch (currentVerb) { + case SkPath::kMove_Verb: + aSink->MoveTo(SkPointToPoint(points[0])); + break; + case SkPath::kLine_Verb: + aSink->LineTo(SkPointToPoint(points[1])); + break; + case SkPath::kCubic_Verb: + aSink->BezierTo(SkPointToPoint(points[1]), SkPointToPoint(points[2]), + SkPointToPoint(points[3])); + break; + case SkPath::kQuad_Verb: + aSink->QuadraticBezierTo(SkPointToPoint(points[1]), + SkPointToPoint(points[2])); + break; + case SkPath::kConic_Verb: { + std::vector<Point> quads; + int numQuads = ConvertConicToQuads( + SkPointToPoint(points[0]), SkPointToPoint(points[1]), + SkPointToPoint(points[2]), iter.conicWeight(), quads); + for (int i = 0; i < numQuads; i++) { + aSink->QuadraticBezierTo(quads[2 * i + 1], quads[2 * i + 2]); + } + break; + } + case SkPath::kClose_Verb: + aSink->Close(); + break; + default: + MOZ_ASSERT(false); + // Unexpected verb found in path! + } + } +} + +Maybe<Rect> PathSkia::AsRect() const { + SkRect rect; + if (mPath.isRect(&rect)) { + return Some(SkRectToRect(rect)); + } + return Nothing(); +} + +bool PathSkia::IsEmpty() const { + // Move/Close/Done segments are not included in the mask so as long as any + // flag is set, we know that the path is non-empty. + return mPath.getSegmentMasks() == 0; +} + +} // namespace mozilla::gfx diff --git a/gfx/2d/PathSkia.h b/gfx/2d/PathSkia.h new file mode 100644 index 0000000000..d69f56b05a --- /dev/null +++ b/gfx/2d/PathSkia.h @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PATH_SKIA_H_ +#define MOZILLA_GFX_PATH_SKIA_H_ + +#include "2D.h" +#include "skia/include/core/SkPath.h" + +namespace mozilla { +namespace gfx { + +class PathSkia; + +class PathBuilderSkia : public PathBuilder { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderSkia, override) + + PathBuilderSkia(const Matrix& aTransform, const SkPath& aPath, + FillRule aFillRule); + explicit PathBuilderSkia(FillRule aFillRule); + + void MoveTo(const Point& aPoint) override; + void LineTo(const Point& aPoint) override; + void BezierTo(const Point& aCP1, const Point& aCP2, + const Point& aCP3) override; + void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) override; + void Close() override; + void Arc(const Point& aOrigin, float aRadius, float aStartAngle, + float aEndAngle, bool aAntiClockwise = false) override; + already_AddRefed<Path> Finish() override; + + void AppendPath(const SkPath& aPath); + + BackendType GetBackendType() const override { return BackendType::SKIA; } + + bool IsActive() const override { return mPath.countPoints() > 0; } + + static already_AddRefed<PathBuilder> Create(FillRule aFillRule); + + private: + friend class PathSkia; + + void SetFillRule(FillRule aFillRule); + + SkPath mPath; + FillRule mFillRule; +}; + +class PathSkia : public Path { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathSkia, override) + + PathSkia(SkPath& aPath, FillRule aFillRule, Point aCurrentPoint = Point(), + Point aBeginPoint = Point()) + : mFillRule(aFillRule), + mCurrentPoint(aCurrentPoint), + mBeginPoint(aBeginPoint) { + mPath.swap(aPath); + } + + BackendType GetBackendType() const override { return BackendType::SKIA; } + + already_AddRefed<PathBuilder> CopyToBuilder( + FillRule aFillRule) const override; + already_AddRefed<PathBuilder> TransformedCopyToBuilder( + const Matrix& aTransform, FillRule aFillRule) const override; + + bool ContainsPoint(const Point& aPoint, + const Matrix& aTransform) const override; + + bool StrokeContainsPoint(const StrokeOptions& aStrokeOptions, + const Point& aPoint, + const Matrix& aTransform) const override; + + Rect GetBounds(const Matrix& aTransform = Matrix()) const override; + + Rect GetStrokedBounds(const StrokeOptions& aStrokeOptions, + const Matrix& aTransform = Matrix()) const override; + + Rect GetFastBounds( + const Matrix& aTransform = Matrix(), + const StrokeOptions* aStrokeOptions = nullptr) const override; + + void StreamToSink(PathSink* aSink) const override; + + FillRule GetFillRule() const override { return mFillRule; } + + const SkPath& GetPath() const { return mPath; } + + Maybe<Rect> AsRect() const override; + + bool GetFillPath(const StrokeOptions& aStrokeOptions, + const Matrix& aTransform, SkPath& aFillPath, + const Maybe<Rect>& aClipRect = Nothing()) const; + + bool IsEmpty() const override; + + private: + friend class DrawTargetSkia; + + SkPath mPath; + FillRule mFillRule; + Point mCurrentPoint; + Point mBeginPoint; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_PATH_SKIA_H_ */ diff --git a/gfx/2d/PatternHelpers.h b/gfx/2d/PatternHelpers.h new file mode 100644 index 0000000000..09b4cd4d93 --- /dev/null +++ b/gfx/2d/PatternHelpers.h @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_PATTERNHELPERS_H +#define _MOZILLA_GFX_PATTERNHELPERS_H + +#include "mozilla/Alignment.h" +#include "mozilla/gfx/2D.h" + +namespace mozilla { +namespace gfx { + +/** + * This class is used to allow general pattern creation functions to return + * any type of pattern via an out-paramater without allocating a pattern + * instance on the free-store (an instance of this class being created on the + * stack before passing it in to the creation function). Without this class + * writing pattern creation functions would be a pain since Pattern objects are + * not reference counted, making lifetime management of instances created on + * the free-store and returned from a creation function hazardous. Besides + * that, in the case that ColorPattern's are expected to be common, it is + * particularly desirable to avoid the overhead of allocating on the + * free-store. + */ +class GeneralPattern final { + public: + explicit GeneralPattern() = default; + + GeneralPattern(const GeneralPattern& aOther) {} + + ~GeneralPattern() { + if (mPattern) { + mPattern->~Pattern(); + } + } + + Pattern* Init(const Pattern& aPattern) { + MOZ_ASSERT(!mPattern); + switch (aPattern.GetType()) { + case PatternType::COLOR: + mPattern = new (mColorPattern.addr()) + ColorPattern(static_cast<const ColorPattern&>(aPattern)); + break; + case PatternType::LINEAR_GRADIENT: + mPattern = new (mLinearGradientPattern.addr()) LinearGradientPattern( + static_cast<const LinearGradientPattern&>(aPattern)); + break; + case PatternType::RADIAL_GRADIENT: + mPattern = new (mRadialGradientPattern.addr()) RadialGradientPattern( + static_cast<const RadialGradientPattern&>(aPattern)); + break; + case PatternType::CONIC_GRADIENT: + mPattern = new (mConicGradientPattern.addr()) ConicGradientPattern( + static_cast<const ConicGradientPattern&>(aPattern)); + break; + case PatternType::SURFACE: + mPattern = new (mSurfacePattern.addr()) + SurfacePattern(static_cast<const SurfacePattern&>(aPattern)); + break; + default: + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unknown pattern type"); + } + return mPattern; + } + + ColorPattern* InitColorPattern(const DeviceColor& aColor) { + MOZ_ASSERT(!mPattern); + mPattern = new (mColorPattern.addr()) ColorPattern(aColor); + return mColorPattern.addr(); + } + + LinearGradientPattern* InitLinearGradientPattern( + const Point& aBegin, const Point& aEnd, + already_AddRefed<GradientStops> aStops, + const Matrix& aMatrix = Matrix()) { + MOZ_ASSERT(!mPattern); + mPattern = new (mLinearGradientPattern.addr()) + LinearGradientPattern(aBegin, aEnd, std::move(aStops), aMatrix); + return mLinearGradientPattern.addr(); + } + + RadialGradientPattern* InitRadialGradientPattern( + const Point& aCenter1, const Point& aCenter2, Float aRadius1, + Float aRadius2, already_AddRefed<GradientStops> aStops, + const Matrix& aMatrix = Matrix()) { + MOZ_ASSERT(!mPattern); + mPattern = new (mRadialGradientPattern.addr()) RadialGradientPattern( + aCenter1, aCenter2, aRadius1, aRadius2, std::move(aStops), aMatrix); + return mRadialGradientPattern.addr(); + } + + ConicGradientPattern* InitConicGradientPattern( + const Point& aCenter, Float aAngle, Float aStartOffset, Float aEndOffset, + already_AddRefed<GradientStops> aStops, + const Matrix& aMatrix = Matrix()) { + MOZ_ASSERT(!mPattern); + mPattern = new (mConicGradientPattern.addr()) ConicGradientPattern( + aCenter, aAngle, aStartOffset, aEndOffset, std::move(aStops), aMatrix); + return mConicGradientPattern.addr(); + } + + SurfacePattern* InitSurfacePattern( + SourceSurface* aSourceSurface, ExtendMode aExtendMode, + const Matrix& aMatrix = Matrix(), + SamplingFilter aSamplingFilter = SamplingFilter::GOOD, + const IntRect& aSamplingRect = IntRect()) { + MOZ_ASSERT(!mPattern); + mPattern = new (mSurfacePattern.addr()) SurfacePattern( + aSourceSurface, aExtendMode, aMatrix, aSamplingFilter, aSamplingRect); + return mSurfacePattern.addr(); + } + + Pattern* GetPattern() { return mPattern; } + + const Pattern* GetPattern() const { return mPattern; } + + operator Pattern&() { + if (!mPattern) { + MOZ_CRASH("GFX: GeneralPattern not initialized"); + } + return *mPattern; + } + + private: + union { + AlignedStorage2<ColorPattern> mColorPattern; + AlignedStorage2<LinearGradientPattern> mLinearGradientPattern; + AlignedStorage2<RadialGradientPattern> mRadialGradientPattern; + AlignedStorage2<ConicGradientPattern> mConicGradientPattern; + AlignedStorage2<SurfacePattern> mSurfacePattern; + }; + Pattern* mPattern = nullptr; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // _MOZILLA_GFX_PATTERNHELPERS_H diff --git a/gfx/2d/Point.h b/gfx/2d/Point.h new file mode 100644 index 0000000000..9000cb4e9a --- /dev/null +++ b/gfx/2d/Point.h @@ -0,0 +1,405 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_POINT_H_ +#define MOZILLA_GFX_POINT_H_ + +#include "mozilla/Attributes.h" +#include "Types.h" +#include "Coord.h" +#include "BaseCoord.h" +#include "BasePoint.h" +#include "BasePoint3D.h" +#include "BasePoint4D.h" +#include "BaseSize.h" +#include "mozilla/Maybe.h" +#include "mozilla/gfx/NumericTools.h" + +#include <cmath> +#include <type_traits> + +namespace mozilla { + +template <typename> +struct IsPixel; + +template <> +struct IsPixel<gfx::UnknownUnits> : std::true_type {}; + +namespace gfx { + +/// Use this for parameters of functions to allow implicit conversions to +/// integer types but not floating point types. +/// We use this wrapper to prevent IntSize and IntPoint's constructors to +/// take foating point values as parameters, and not require their constructors +/// to have implementations for each permutation of integer types. +template <typename T> +struct IntParam { + constexpr MOZ_IMPLICIT IntParam(char val) : value(val) {} + constexpr MOZ_IMPLICIT IntParam(unsigned char val) : value(val) {} + constexpr MOZ_IMPLICIT IntParam(short val) : value(val) {} + constexpr MOZ_IMPLICIT IntParam(unsigned short val) : value(val) {} + constexpr MOZ_IMPLICIT IntParam(int val) : value(val) {} + constexpr MOZ_IMPLICIT IntParam(unsigned int val) : value(val) {} + constexpr MOZ_IMPLICIT IntParam(long val) : value(val) {} + constexpr MOZ_IMPLICIT IntParam(unsigned long val) : value(val) {} + constexpr MOZ_IMPLICIT IntParam(long long val) : value(val) {} + constexpr MOZ_IMPLICIT IntParam(unsigned long long val) : value(val) {} + template <typename Unit> + constexpr MOZ_IMPLICIT IntParam(IntCoordTyped<Unit> val) : value(val) {} + + // Disable the evil ones! + MOZ_IMPLICIT IntParam(float val) = delete; + MOZ_IMPLICIT IntParam(double val) = delete; + + T value; +}; + +template <class Units, class> +struct PointTyped; +template <class Units, class> +struct SizeTyped; + +template <class Units> +struct MOZ_EMPTY_BASES IntPointTyped + : public BasePoint<int32_t, IntPointTyped<Units>, IntCoordTyped<Units> >, + public Units { + static_assert(IsPixel<Units>::value, + "'Units' must be a coordinate system tag"); + + typedef IntParam<int32_t> ToInt; + typedef IntCoordTyped<Units> Coord; + typedef BasePoint<int32_t, IntPointTyped<Units>, IntCoordTyped<Units> > Super; + + constexpr IntPointTyped() : Super() { + static_assert(sizeof(IntPointTyped) == sizeof(int32_t) * 2, + "Would be unfortunate otherwise!"); + } + constexpr IntPointTyped(ToInt aX, ToInt aY) + : Super(Coord(aX.value), Coord(aY.value)) {} + + static IntPointTyped Round(float aX, float aY) { + return IntPointTyped(int32_t(floorf(aX + 0.5f)), + int32_t(floorf(aY + 0.5f))); + } + + static IntPointTyped Ceil(float aX, float aY) { + return IntPointTyped(int32_t(ceilf(aX)), int32_t(ceilf(aY))); + } + + static IntPointTyped Floor(float aX, float aY) { + return IntPointTyped(int32_t(floorf(aX)), int32_t(floorf(aY))); + } + + static IntPointTyped Truncate(float aX, float aY) { + return IntPointTyped(int32_t(aX), int32_t(aY)); + } + + static IntPointTyped Round(const PointTyped<Units, float>& aPoint); + static IntPointTyped Ceil(const PointTyped<Units, float>& aPoint); + static IntPointTyped Floor(const PointTyped<Units, float>& aPoint); + static IntPointTyped Truncate(const PointTyped<Units, float>& aPoint); + + // XXX When all of the code is ported, the following functions to convert to + // and from unknown types should be removed. + + static IntPointTyped FromUnknownPoint( + const IntPointTyped<UnknownUnits>& aPoint) { + return IntPointTyped<Units>(aPoint.x, aPoint.y); + } + + IntPointTyped<UnknownUnits> ToUnknownPoint() const { + return IntPointTyped<UnknownUnits>(this->x, this->y); + } + + IntPointTyped RoundedToMultiple(int32_t aMultiplier) const { + return {RoundToMultiple(this->x, aMultiplier), + RoundToMultiple(this->y, aMultiplier)}; + } +}; +typedef IntPointTyped<UnknownUnits> IntPoint; + +template <class Units, class F = Float> +struct MOZ_EMPTY_BASES PointTyped + : public BasePoint<F, PointTyped<Units, F>, CoordTyped<Units, F> >, + public Units { + static_assert(IsPixel<Units>::value, + "'Units' must be a coordinate system tag"); + + typedef CoordTyped<Units, F> Coord; + typedef BasePoint<F, PointTyped<Units, F>, CoordTyped<Units, F> > Super; + + constexpr PointTyped() : Super() { + static_assert(sizeof(PointTyped) == sizeof(F) * 2, + "Would be unfortunate otherwise!"); + } + constexpr PointTyped(F aX, F aY) : Super(Coord(aX), Coord(aY)) {} + // The mixed-type constructors (Float, Coord) and (Coord, Float) are needed to + // avoid ambiguities because Coord is implicitly convertible to Float. + constexpr PointTyped(F aX, Coord aY) : Super(Coord(aX), aY) {} + constexpr PointTyped(Coord aX, F aY) : Super(aX, Coord(aY)) {} + constexpr PointTyped(Coord aX, Coord aY) : Super(aX.value, aY.value) {} + constexpr MOZ_IMPLICIT PointTyped(const IntPointTyped<Units>& point) + : Super(F(point.x), F(point.y)) {} + + bool WithinEpsilonOf(const PointTyped<Units, F>& aPoint, F aEpsilon) const { + return fabs(aPoint.x - this->x) < aEpsilon && + fabs(aPoint.y - this->y) < aEpsilon; + } + + // XXX When all of the code is ported, the following functions to convert to + // and from unknown types should be removed. + + static PointTyped<Units, F> FromUnknownPoint( + const PointTyped<UnknownUnits, F>& aPoint) { + return PointTyped<Units, F>(aPoint.x, aPoint.y); + } + + PointTyped<UnknownUnits, F> ToUnknownPoint() const { + return PointTyped<UnknownUnits, F>(this->x, this->y); + } +}; +typedef PointTyped<UnknownUnits> Point; +typedef PointTyped<UnknownUnits, double> PointDouble; + +template <class Units> +IntPointTyped<Units> RoundedToInt(const PointTyped<Units>& aPoint) { + return IntPointTyped<Units>::Round(aPoint.x, aPoint.y); +} + +template <class Units> +IntPointTyped<Units> TruncatedToInt(const PointTyped<Units>& aPoint) { + return IntPointTyped<Units>::Truncate(aPoint.x, aPoint.y); +} + +template <class Units, class F = Float> +struct Point3DTyped : public BasePoint3D<F, Point3DTyped<Units, F> > { + static_assert(IsPixel<Units>::value, + "'Units' must be a coordinate system tag"); + + typedef BasePoint3D<F, Point3DTyped<Units, F> > Super; + + Point3DTyped() : Super() { + static_assert(sizeof(Point3DTyped) == sizeof(F) * 3, + "Would be unfortunate otherwise!"); + } + Point3DTyped(F aX, F aY, F aZ) : Super(aX, aY, aZ) {} + + // XXX When all of the code is ported, the following functions to convert to + // and from unknown types should be removed. + + static Point3DTyped<Units, F> FromUnknownPoint( + const Point3DTyped<UnknownUnits, F>& aPoint) { + return Point3DTyped<Units, F>(aPoint.x, aPoint.y, aPoint.z); + } + + Point3DTyped<UnknownUnits, F> ToUnknownPoint() const { + return Point3DTyped<UnknownUnits, F>(this->x, this->y, this->z); + } +}; +typedef Point3DTyped<UnknownUnits> Point3D; +typedef Point3DTyped<UnknownUnits, double> PointDouble3D; + +template <typename Units> +IntPointTyped<Units> IntPointTyped<Units>::Round( + const PointTyped<Units, float>& aPoint) { + return IntPointTyped::Round(aPoint.x, aPoint.y); +} + +template <typename Units> +IntPointTyped<Units> IntPointTyped<Units>::Ceil( + const PointTyped<Units, float>& aPoint) { + return IntPointTyped::Ceil(aPoint.x, aPoint.y); +} + +template <typename Units> +IntPointTyped<Units> IntPointTyped<Units>::Floor( + const PointTyped<Units, float>& aPoint) { + return IntPointTyped::Floor(aPoint.x, aPoint.y); +} + +template <typename Units> +IntPointTyped<Units> IntPointTyped<Units>::Truncate( + const PointTyped<Units, float>& aPoint) { + return IntPointTyped::Truncate(aPoint.x, aPoint.y); +} + +template <class Units, class F = Float> +struct Point4DTyped : public BasePoint4D<F, Point4DTyped<Units, F> > { + static_assert(IsPixel<Units>::value, + "'Units' must be a coordinate system tag"); + + typedef BasePoint4D<F, Point4DTyped<Units, F> > Super; + + Point4DTyped() : Super() { + static_assert(sizeof(Point4DTyped) == sizeof(F) * 4, + "Would be unfortunate otherwise!"); + } + Point4DTyped(F aX, F aY, F aZ, F aW) : Super(aX, aY, aZ, aW) {} + + explicit Point4DTyped(const Point3DTyped<Units, F>& aPoint) + : Super(aPoint.x, aPoint.y, aPoint.z, 1) {} + + // XXX When all of the code is ported, the following functions to convert to + // and from unknown types should be removed. + + static Point4DTyped<Units, F> FromUnknownPoint( + const Point4DTyped<UnknownUnits, F>& aPoint) { + return Point4DTyped<Units, F>(aPoint.x, aPoint.y, aPoint.z, aPoint.w); + } + + Point4DTyped<UnknownUnits, F> ToUnknownPoint() const { + return Point4DTyped<UnknownUnits, F>(this->x, this->y, this->z, this->w); + } + + PointTyped<Units, F> As2DPoint() const { + return PointTyped<Units, F>(this->x / this->w, this->y / this->w); + } + + Point3DTyped<Units, F> As3DPoint() const { + return Point3DTyped<Units, F>(this->x / this->w, this->y / this->w, + this->z / this->w); + } +}; +typedef Point4DTyped<UnknownUnits> Point4D; +typedef Point4DTyped<UnknownUnits, double> PointDouble4D; + +template <class Units> +struct MOZ_EMPTY_BASES IntSizeTyped + : public BaseSize<int32_t, IntSizeTyped<Units>, IntCoordTyped<Units> >, + public Units { + static_assert(IsPixel<Units>::value, + "'Units' must be a coordinate system tag"); + + typedef IntCoordTyped<Units> Coord; + typedef BaseSize<int32_t, IntSizeTyped<Units>, Coord> Super; + + constexpr IntSizeTyped() : Super() { + static_assert(sizeof(IntSizeTyped) == sizeof(int32_t) * 2, + "Would be unfortunate otherwise!"); + } + constexpr IntSizeTyped(Coord aWidth, Coord aHeight) + : Super(aWidth.value, aHeight.value) {} + + static IntSizeTyped Round(float aWidth, float aHeight) { + return IntSizeTyped(int32_t(floorf(aWidth + 0.5)), + int32_t(floorf(aHeight + 0.5))); + } + + static IntSizeTyped Truncate(float aWidth, float aHeight) { + return IntSizeTyped(int32_t(aWidth), int32_t(aHeight)); + } + + static IntSizeTyped Ceil(float aWidth, float aHeight) { + return IntSizeTyped(int32_t(ceil(aWidth)), int32_t(ceil(aHeight))); + } + + static IntSizeTyped Floor(float aWidth, float aHeight) { + return IntSizeTyped(int32_t(floorf(aWidth)), int32_t(floorf(aHeight))); + } + + static IntSizeTyped Round(const SizeTyped<Units, float>& aSize); + static IntSizeTyped Ceil(const SizeTyped<Units, float>& aSize); + static IntSizeTyped Floor(const SizeTyped<Units, float>& aSize); + static IntSizeTyped Truncate(const SizeTyped<Units, float>& aSize); + + IntSizeTyped TruncatedToMultiple(int32_t aMultiplier) const { + if (aMultiplier == 1) { + return *this; + } + return {RoundDownToMultiple(this->width, aMultiplier), + RoundDownToMultiple(this->height, aMultiplier)}; + } + + IntSizeTyped CeiledToMultiple(int32_t aMultiplier) const { + if (aMultiplier == 1) { + return *this; + } + return {RoundUpToMultiple(this->width, aMultiplier), + RoundUpToMultiple(this->height, aMultiplier)}; + } + + // XXX When all of the code is ported, the following functions to convert to + // and from unknown types should be removed. + + static IntSizeTyped FromUnknownSize(const IntSizeTyped<UnknownUnits>& aSize) { + return IntSizeTyped(aSize.width, aSize.height); + } + + IntSizeTyped<UnknownUnits> ToUnknownSize() const { + return IntSizeTyped<UnknownUnits>(this->width, this->height); + } +}; +typedef IntSizeTyped<UnknownUnits> IntSize; +typedef Maybe<IntSize> MaybeIntSize; + +template <class Units, class F = Float> +struct MOZ_EMPTY_BASES SizeTyped + : public BaseSize<F, SizeTyped<Units, F>, CoordTyped<Units, F> >, + public Units { + static_assert(IsPixel<Units>::value, + "'Units' must be a coordinate system tag"); + + typedef CoordTyped<Units, F> Coord; + typedef BaseSize<F, SizeTyped<Units, F>, Coord> Super; + + constexpr SizeTyped() : Super() { + static_assert(sizeof(SizeTyped) == sizeof(F) * 2, + "Would be unfortunate otherwise!"); + } + constexpr SizeTyped(Coord aWidth, Coord aHeight) : Super(aWidth, aHeight) {} + explicit SizeTyped(const IntSizeTyped<Units>& size) + : Super(F(size.width), F(size.height)) {} + + // XXX When all of the code is ported, the following functions to convert to + // and from unknown types should be removed. + + static SizeTyped<Units, F> FromUnknownSize( + const SizeTyped<UnknownUnits, F>& aSize) { + return SizeTyped<Units, F>(aSize.width, aSize.height); + } + + SizeTyped<UnknownUnits, F> ToUnknownSize() const { + return SizeTyped<UnknownUnits, F>(this->width, this->height); + } +}; +typedef SizeTyped<UnknownUnits> Size; +typedef SizeTyped<UnknownUnits, double> SizeDouble; + +template <class Units> +IntSizeTyped<Units> RoundedToInt(const SizeTyped<Units>& aSize) { + return IntSizeTyped<Units>(int32_t(floorf(aSize.width + 0.5f)), + int32_t(floorf(aSize.height + 0.5f))); +} + +template <typename Units> +IntSizeTyped<Units> IntSizeTyped<Units>::Round( + const SizeTyped<Units, float>& aSize) { + return IntSizeTyped::Round(aSize.width, aSize.height); +} + +template <typename Units> +IntSizeTyped<Units> IntSizeTyped<Units>::Ceil( + const SizeTyped<Units, float>& aSize) { + return IntSizeTyped::Ceil(aSize.width, aSize.height); +} + +template <typename Units> +IntSizeTyped<Units> IntSizeTyped<Units>::Floor( + const SizeTyped<Units, float>& aSize) { + return IntSizeTyped::Floor(aSize.width, aSize.height); +} + +template <typename Units> +IntSizeTyped<Units> IntSizeTyped<Units>::Truncate( + const SizeTyped<Units, float>& aSize) { + return IntSizeTyped::Truncate(aSize.width, aSize.height); +} + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_POINT_H_ */ diff --git a/gfx/2d/Polygon.h b/gfx/2d/Polygon.h new file mode 100644 index 0000000000..3de3d684f9 --- /dev/null +++ b/gfx/2d/Polygon.h @@ -0,0 +1,396 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_POLYGON_H +#define MOZILLA_GFX_POLYGON_H + +#include <initializer_list> +#include <utility> + +#include "Matrix.h" +#include "Point.h" +#include "Triangle.h" +#include "nsTArray.h" + +namespace mozilla { +namespace gfx { + +/** + * Calculates the w = 0 intersection point for the edge defined by + * |aFirst| and |aSecond|. + */ +template <class Units> +Point4DTyped<Units> CalculateEdgeIntersect(const Point4DTyped<Units>& aFirst, + const Point4DTyped<Units>& aSecond) { + static const float w = 0.00001f; + const float t = (w - aFirst.w) / (aSecond.w - aFirst.w); + return aFirst + (aSecond - aFirst) * t; +} + +/** + * Clips the polygon defined by |aPoints| so that there are no points with + * w <= 0. + */ +template <class Units> +nsTArray<Point4DTyped<Units>> ClipPointsAtInfinity( + const nsTArray<Point4DTyped<Units>>& aPoints) { + nsTArray<Point4DTyped<Units>> outPoints(aPoints.Length()); + + const size_t pointCount = aPoints.Length(); + for (size_t i = 0; i < pointCount; ++i) { + const Point4DTyped<Units>& first = aPoints[i]; + const Point4DTyped<Units>& second = aPoints[(i + 1) % pointCount]; + + if (!first.w || !second.w) { + // Skip edges at infinity. + continue; + } + + if (first.w > 0.0f) { + outPoints.AppendElement(first); + } + + if ((first.w <= 0.0f) ^ (second.w <= 0.0f)) { + outPoints.AppendElement(CalculateEdgeIntersect(first, second)); + } + } + + return outPoints; +} + +/** + * Calculates the distances between the points in |aPoints| and the plane + * defined by |aPlaneNormal| and |aPlanePoint|. + */ +template <class Units> +nsTArray<float> CalculatePointPlaneDistances( + const nsTArray<Point4DTyped<Units>>& aPoints, + const Point4DTyped<Units>& aPlaneNormal, + const Point4DTyped<Units>& aPlanePoint, size_t& aPos, size_t& aNeg) { + // Point classification might produce incorrect results due to numerical + // inaccuracies. Using an epsilon value makes the splitting plane "thicker". + const float epsilon = 0.05f; + + aPos = aNeg = 0; + nsTArray<float> distances(aPoints.Length()); + + for (const Point4DTyped<Units>& point : aPoints) { + float dot = (point - aPlanePoint).DotProduct(aPlaneNormal); + + if (dot > epsilon) { + aPos++; + } else if (dot < -epsilon) { + aNeg++; + } else { + // The point is within the thick plane. + dot = 0.0f; + } + + distances.AppendElement(dot); + } + + return distances; +} + +/** + * Clips the polygon defined by |aPoints|. The clipping uses previously + * calculated plane to point distances and the plane normal |aNormal|. + * The result of clipping is stored in |aBackPoints| and |aFrontPoints|. + */ +template <class Units> +void ClipPointsWithPlane(const nsTArray<Point4DTyped<Units>>& aPoints, + const Point4DTyped<Units>& aNormal, + const nsTArray<float>& aDots, + nsTArray<Point4DTyped<Units>>& aBackPoints, + nsTArray<Point4DTyped<Units>>& aFrontPoints) { + static const auto Sign = [](const float& f) { + if (f > 0.0f) return 1; + if (f < 0.0f) return -1; + return 0; + }; + + const size_t pointCount = aPoints.Length(); + for (size_t i = 0; i < pointCount; ++i) { + size_t j = (i + 1) % pointCount; + + const Point4DTyped<Units>& a = aPoints[i]; + const Point4DTyped<Units>& b = aPoints[j]; + const float dotA = aDots[i]; + const float dotB = aDots[j]; + + // The point is in front of or on the plane. + if (dotA >= 0) { + aFrontPoints.AppendElement(a); + } + + // The point is behind or on the plane. + if (dotA <= 0) { + aBackPoints.AppendElement(a); + } + + // If the sign of the dot products changes between two consecutive + // vertices, then the plane intersects with the polygon edge. + // The case where the polygon edge is within the plane is handled above. + if (Sign(dotA) && Sign(dotB) && Sign(dotA) != Sign(dotB)) { + // Calculate the line segment and plane intersection point. + const Point4DTyped<Units> ab = b - a; + const float dotAB = ab.DotProduct(aNormal); + const float t = -dotA / dotAB; + const Point4DTyped<Units> p = a + (ab * t); + + // Add the intersection point to both polygons. + aBackPoints.AppendElement(p); + aFrontPoints.AppendElement(p); + } + } +} + +/** + * PolygonTyped stores the points of a convex planar polygon. + */ +template <class Units> +class PolygonTyped { + typedef Point3DTyped<Units> Point3DType; + typedef Point4DTyped<Units> Point4DType; + + public: + PolygonTyped() = default; + + explicit PolygonTyped(const nsTArray<Point4DType>& aPoints, + const Point4DType& aNormal = DefaultNormal()) + : mNormal(aNormal), mPoints(aPoints) {} + + explicit PolygonTyped(nsTArray<Point4DType>&& aPoints, + const Point4DType& aNormal = DefaultNormal()) + : mNormal(aNormal), mPoints(std::move(aPoints)) {} + + explicit PolygonTyped(const std::initializer_list<Point4DType>& aPoints, + const Point4DType& aNormal = DefaultNormal()) + : mNormal(aNormal), mPoints(aPoints) { +#ifdef DEBUG + EnsurePlanarPolygon(); +#endif + } + + /** + * Returns the smallest 2D rectangle that can fully cover the polygon. + */ + RectTyped<Units> BoundingBox() const { + if (mPoints.IsEmpty()) { + return RectTyped<Units>(); + } + + float minX, maxX, minY, maxY; + minX = maxX = mPoints[0].x; + minY = maxY = mPoints[0].y; + + for (const Point4DType& point : mPoints) { + minX = std::min(point.x, minX); + maxX = std::max(point.x, maxX); + + minY = std::min(point.y, minY); + maxY = std::max(point.y, maxY); + } + + return RectTyped<Units>(minX, minY, maxX - minX, maxY - minY); + } + + /** + * Clips the polygon against the given 2D rectangle. + */ + PolygonTyped<Units> ClipPolygon(const RectTyped<Units>& aRect) const { + if (aRect.IsEmpty()) { + return PolygonTyped<Units>(); + } + + return ClipPolygon(FromRect(aRect)); + } + + /** + * Clips this polygon against |aPolygon| in 2D and returns a new polygon. + */ + PolygonTyped<Units> ClipPolygon(const PolygonTyped<Units>& aPolygon) const { + const nsTArray<Point4DType>& points = aPolygon.GetPoints(); + + if (mPoints.IsEmpty() || points.IsEmpty()) { + return PolygonTyped<Units>(); + } + + nsTArray<Point4DType> clippedPoints(mPoints.Clone()); + + size_t pos, neg; + nsTArray<Point4DType> backPoints(4), frontPoints(4); + + // Iterate over all the edges of the clipping polygon |aPolygon| and clip + // this polygon against the edges. + const size_t pointCount = points.Length(); + for (size_t i = 0; i < pointCount; ++i) { + const Point4DType p1 = points[(i + 1) % pointCount]; + const Point4DType p2 = points[i]; + + // Calculate the normal for the edge defined by |p1| and |p2|. + const Point4DType normal(p2.y - p1.y, p1.x - p2.x, 0.0f, 0.0f); + + // Calculate the distances between the points of the polygon and the + // plane defined by |aPolygon|. + const nsTArray<float> distances = + CalculatePointPlaneDistances(clippedPoints, normal, p1, pos, neg); + + backPoints.ClearAndRetainStorage(); + frontPoints.ClearAndRetainStorage(); + + // Clip the polygon points using the previously calculated distances. + ClipPointsWithPlane(clippedPoints, normal, distances, backPoints, + frontPoints); + + // Only use the points behind the clipping plane. + clippedPoints = std::move(backPoints); + + if (clippedPoints.Length() < 3) { + // The clipping created a polygon with no area. + return PolygonTyped<Units>(); + } + } + + return PolygonTyped<Units>(std::move(clippedPoints), mNormal); + } + + /** + * Returns a new polygon containing the bounds of the given 2D rectangle. + */ + static PolygonTyped<Units> FromRect(const RectTyped<Units>& aRect) { + nsTArray<Point4DType> points{ + Point4DType(aRect.X(), aRect.Y(), 0.0f, 1.0f), + Point4DType(aRect.X(), aRect.YMost(), 0.0f, 1.0f), + Point4DType(aRect.XMost(), aRect.YMost(), 0.0f, 1.0f), + Point4DType(aRect.XMost(), aRect.Y(), 0.0f, 1.0f)}; + + return PolygonTyped<Units>(std::move(points)); + } + + const Point4DType& GetNormal() const { return mNormal; } + + const nsTArray<Point4DType>& GetPoints() const { return mPoints; } + + bool IsEmpty() const { + // If the polygon has less than three points, it has no visible area. + return mPoints.Length() < 3; + } + + /** + * Returns a list of triangles covering the polygon. + */ + nsTArray<TriangleTyped<Units>> ToTriangles() const { + nsTArray<TriangleTyped<Units>> triangles; + + if (IsEmpty()) { + return triangles; + } + + // This fan triangulation method only works for convex polygons. + for (size_t i = 1; i < mPoints.Length() - 1; ++i) { + TriangleTyped<Units> triangle(Point(mPoints[0].x, mPoints[0].y), + Point(mPoints[i].x, mPoints[i].y), + Point(mPoints[i + 1].x, mPoints[i + 1].y)); + triangles.AppendElement(std::move(triangle)); + } + + return triangles; + } + + void TransformToLayerSpace(const Matrix4x4Typed<Units, Units>& aTransform) { + TransformPoints(aTransform, true); + mNormal = DefaultNormal(); + } + + void TransformToScreenSpace( + const Matrix4x4Typed<Units, Units>& aTransform, + const Matrix4x4Typed<Units, Units>& aInverseTransform) { + TransformPoints(aTransform, false); + + // Perspective projection transformation might produce points with w <= 0, + // so we need to clip these points. + mPoints = ClipPointsAtInfinity(mPoints); + + // Normal vectors should be transformed using inverse transpose. + mNormal = aInverseTransform.TransposeTransform4D(mNormal); + } + + void TransformToScreenSpace(const Matrix4x4Typed<Units, Units>& aTransform) { + MOZ_ASSERT(!aTransform.IsSingular()); + + TransformToScreenSpace(aTransform, aTransform.Inverse()); + } + + private: + static Point4DType DefaultNormal() { + return Point4DType(0.0f, 0.0f, 1.0f, 0.0f); + } + +#ifdef DEBUG + void EnsurePlanarPolygon() const { + if (mPoints.Length() <= 3) { + // Polygons with three or less points are guaranteed to be planar. + return; + } + + // This normal calculation method works only for planar polygons. + // The resulting normal vector will point towards the viewer when the + // polygon has a counter-clockwise winding order from the perspective + // of the viewer. + Point3DType normal; + const Point3DType p0 = mPoints[0].As3DPoint(); + + for (size_t i = 1; i < mPoints.Length() - 1; ++i) { + const Point3DType p1 = mPoints[i].As3DPoint(); + const Point3DType p2 = mPoints[i + 1].As3DPoint(); + + normal += (p1 - p0).CrossProduct(p2 - p0); + } + + // Ensure that at least one component is greater than zero. + // This avoids division by zero when normalizing the vector. + bool hasNonZeroComponent = std::abs(normal.x) > 0.0f || + std::abs(normal.y) > 0.0f || + std::abs(normal.z) > 0.0f; + + MOZ_ASSERT(hasNonZeroComponent); + + normal.Normalize(); + + // Ensure that the polygon is planar. + // http://mathworld.wolfram.com/Point-PlaneDistance.html + const float epsilon = 0.01f; + for (const Point4DType& point : mPoints) { + const Point3DType p1 = point.As3DPoint(); + const float d = normal.DotProduct(p1 - p0); + + MOZ_ASSERT(std::abs(d) < epsilon); + } + } +#endif + + void TransformPoints(const Matrix4x4Typed<Units, Units>& aTransform, + const bool aDivideByW) { + for (Point4DType& point : mPoints) { + point = aTransform.TransformPoint(point); + + if (aDivideByW && point.w > 0.0f) { + point = point / point.w; + } + } + } + + Point4DType mNormal; + CopyableTArray<Point4DType> mPoints; +}; + +typedef PolygonTyped<UnknownUnits> Polygon; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_POLYGON_H */ diff --git a/gfx/2d/Quaternion.cpp b/gfx/2d/Quaternion.cpp new file mode 100644 index 0000000000..1921f78ae1 --- /dev/null +++ b/gfx/2d/Quaternion.cpp @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Quaternion.h" +#include "Matrix.h" +#include "Tools.h" +#include <algorithm> +#include <ostream> +#include <math.h> + +namespace mozilla { +namespace gfx { + +std::ostream& operator<<(std::ostream& aStream, const Quaternion& aQuat) { + return aStream << "< " << aQuat.x << " " << aQuat.y << " " << aQuat.z << " " + << aQuat.w << ">"; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/Quaternion.h b/gfx/2d/Quaternion.h new file mode 100644 index 0000000000..a952612b04 --- /dev/null +++ b/gfx/2d/Quaternion.h @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_QUATERNION_H_ +#define MOZILLA_GFX_QUATERNION_H_ + +#include "Types.h" +#include <math.h> +#include <ostream> +#include "mozilla/Attributes.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/gfx/MatrixFwd.h" +#include "mozilla/gfx/Point.h" + +namespace mozilla { +namespace gfx { + +template <class T> +class BaseQuaternion { + public: + BaseQuaternion() : x(0.0f), y(0.0f), z(0.0f), w(1.0f) {} + + BaseQuaternion(T aX, T aY, T aZ, T aW) : x(aX), y(aY), z(aZ), w(aW) {} + + BaseQuaternion(const BaseQuaternion& aOther) { + x = aOther.x; + y = aOther.y; + z = aOther.z; + w = aOther.w; + } + + T x, y, z, w; + + template <class U> + friend std::ostream& operator<<(std::ostream& aStream, + const BaseQuaternion<U>& aQuat); + + void Set(T aX, T aY, T aZ, T aW) { + x = aX; + y = aY; + z = aZ; + w = aW; + } + + // Assumes upper 3x3 of aMatrix is a pure rotation matrix (no scaling) + void SetFromRotationMatrix( + const Matrix4x4Typed<UnknownUnits, UnknownUnits, T>& m) { + const T trace = m._11 + m._22 + m._33 + 1.0f; + + if (trace > 1e-4) { + const T s = 0.5f / sqrt(trace); + w = 0.25f / s; + x = (m._23 - m._32) * s; + y = (m._31 - m._13) * s; + z = (m._12 - m._21) * s; + } else if (m._11 > m._22 && m._11 > m._33) { + const T s = 2.0f * sqrt(1.0f + m._11 - m._22 - m._33); + w = (m._23 - m._32) / s; + x = 0.25f * s; + y = (m._21 + m._12) / s; + z = (m._31 + m._13) / s; + } else if (m._22 > m._33) { + const T s = 2.0 * sqrt(1.0f + m._22 - m._11 - m._33); + w = (m._31 - m._13) / s; + x = (m._21 + m._12) / s; + y = 0.25f * s; + z = (m._32 + m._23) / s; + } else { + const T s = 2.0 * sqrt(1.0f + m._33 - m._11 - m._22); + w = (m._12 - m._21) / s; + x = (m._31 + m._13) / s; + y = (m._32 + m._23) / s; + z = 0.25f * s; + } + + Normalize(); + } + + // result = this * aQuat + BaseQuaternion operator*(const BaseQuaternion& aQuat) const { + BaseQuaternion o; + const T bx = aQuat.x, by = aQuat.y, bz = aQuat.z, bw = aQuat.w; + + o.x = x * bw + w * bx + y * bz - z * by; + o.y = y * bw + w * by + z * bx - x * bz; + o.z = z * bw + w * bz + x * by - y * bx; + o.w = w * bw - x * bx - y * by - z * bz; + return o; + } + + BaseQuaternion& operator*=(const BaseQuaternion& aQuat) { + *this = *this * aQuat; + return *this; + } + + T Length() const { return sqrt(x * x + y * y + z * z + w * w); } + + BaseQuaternion& Conjugate() { + x *= -1.f; + y *= -1.f; + z *= -1.f; + return *this; + } + + BaseQuaternion& Normalize() { + T l = Length(); + if (l) { + l = 1.0f / l; + x *= l; + y *= l; + z *= l; + w *= l; + } else { + x = y = z = 0.f; + w = 1.f; + } + return *this; + } + + BaseQuaternion& Invert() { return Conjugate().Normalize(); } + + BaseQuaternion Inverse() const { + BaseQuaternion q = *this; + q.Invert(); + return q; + } + + Point3DTyped<UnknownUnits, T> RotatePoint( + const Point3DTyped<UnknownUnits, T>& aPoint) const { + T uvx = T(2.0) * (y * aPoint.z - z * aPoint.y); + T uvy = T(2.0) * (z * aPoint.x - x * aPoint.z); + T uvz = T(2.0) * (x * aPoint.y - y * aPoint.x); + + return Point3DTyped<UnknownUnits, T>( + aPoint.x + w * uvx + y * uvz - z * uvy, + aPoint.y + w * uvy + z * uvx - x * uvz, + aPoint.z + w * uvz + x * uvy - y * uvx); + } +}; + +typedef BaseQuaternion<Float> Quaternion; +typedef BaseQuaternion<Double> QuaternionDouble; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/RadialGradientEffectD2D1.cpp b/gfx/2d/RadialGradientEffectD2D1.cpp new file mode 100644 index 0000000000..34a5d9dcdb --- /dev/null +++ b/gfx/2d/RadialGradientEffectD2D1.cpp @@ -0,0 +1,405 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RadialGradientEffectD2D1.h" + +#include "Logging.h" + +#include "ShadersD2D1.h" +#include "HelpersD2D.h" + +#include <vector> + +#define TEXTW(x) L##x +#define XML(X) \ + TEXTW(#X) // This macro creates a single string from multiple lines of text. + +static const PCWSTR kXmlDescription = + XML( + <?xml version='1.0'?> + <Effect> + <!-- System Properties --> + <Property name='DisplayName' type='string' value='RadialGradientEffect'/> + <Property name='Author' type='string' value='Mozilla'/> + <Property name='Category' type='string' value='Pattern effects'/> + <Property name='Description' type='string' value='This effect is used to render radial gradients in a manner compliant with the 2D Canvas specification.'/> + <Inputs> + <Input name='Geometry'/> + </Inputs> + <Property name='StopCollection' type='iunknown'> + <Property name='DisplayName' type='string' value='Gradient stop collection'/> + </Property> + <Property name='Center1' type='vector2'> + <Property name='DisplayName' type='string' value='Inner circle center'/> + </Property> + <Property name='Center2' type='vector2'> + <Property name='DisplayName' type='string' value='Outer circle center'/> + </Property> + <Property name='Radius1' type='float'> + <Property name='DisplayName' type='string' value='Inner circle radius'/> + </Property> + <Property name='Radius2' type='float'> + <Property name='DisplayName' type='string' value='Outer circle radius'/> + </Property> + <Property name='Transform' type='matrix3x2'> + <Property name='DisplayName' type='string' value='Transform applied to the pattern'/> + </Property> + + </Effect> + ); + +// {FB947CDA-718E-40CC-AE7B-D255830D7D14} +static const GUID GUID_SampleRadialGradientPS = { + 0xfb947cda, + 0x718e, + 0x40cc, + {0xae, 0x7b, 0xd2, 0x55, 0x83, 0xd, 0x7d, 0x14}}; +// {2C468128-6546-453C-8E25-F2DF0DE10A0F} +static const GUID GUID_SampleRadialGradientA0PS = { + 0x2c468128, 0x6546, 0x453c, {0x8e, 0x25, 0xf2, 0xdf, 0xd, 0xe1, 0xa, 0xf}}; + +namespace mozilla { +namespace gfx { + +RadialGradientEffectD2D1::RadialGradientEffectD2D1() + : mRefCount(0), + mCenter1(D2D1::Vector2F(0, 0)), + mCenter2(D2D1::Vector2F(0, 0)), + mRadius1(0), + mRadius2(0), + mTransform(D2D1::IdentityMatrix()) + +{} + +IFACEMETHODIMP +RadialGradientEffectD2D1::Initialize(ID2D1EffectContext* pContextInternal, + ID2D1TransformGraph* pTransformGraph) { + HRESULT hr; + + hr = pContextInternal->LoadPixelShader(GUID_SampleRadialGradientPS, + SampleRadialGradientPS, + sizeof(SampleRadialGradientPS)); + + if (FAILED(hr)) { + return hr; + } + + hr = pContextInternal->LoadPixelShader(GUID_SampleRadialGradientA0PS, + SampleRadialGradientA0PS, + sizeof(SampleRadialGradientA0PS)); + + if (FAILED(hr)) { + return hr; + } + + hr = pTransformGraph->SetSingleTransformNode(this); + + if (FAILED(hr)) { + return hr; + } + + mEffectContext = pContextInternal; + + return S_OK; +} + +IFACEMETHODIMP +RadialGradientEffectD2D1::PrepareForRender(D2D1_CHANGE_TYPE changeType) { + if (changeType == D2D1_CHANGE_TYPE_NONE) { + return S_OK; + } + + // We'll need to inverse transform our pixel, precompute inverse here. + Matrix mat = ToMatrix(mTransform); + if (!mat.Invert()) { + // Singular + return S_OK; + } + + if (!mStopCollection) { + return S_OK; + } + + D2D1_POINT_2F dc = + D2D1::Point2F(mCenter2.x - mCenter1.x, mCenter2.y - mCenter1.y); + float dr = mRadius2 - mRadius1; + float A = dc.x * dc.x + dc.y * dc.y - dr * dr; + + HRESULT hr; + + if (A == 0) { + hr = mDrawInfo->SetPixelShader(GUID_SampleRadialGradientA0PS); + } else { + hr = mDrawInfo->SetPixelShader(GUID_SampleRadialGradientPS); + } + + if (FAILED(hr)) { + return hr; + } + + RefPtr<ID2D1ResourceTexture> tex = CreateGradientTexture(); + hr = mDrawInfo->SetResourceTexture(1, tex); + + if (FAILED(hr)) { + return hr; + } + + struct PSConstantBuffer { + float diff[3]; + float padding; + float center1[2]; + float A; + float radius1; + float sq_radius1; + float repeat_correct; + float allow_odd; + float padding2[1]; + float transform[8]; + }; + + PSConstantBuffer buffer = { + {dc.x, dc.y, dr}, + 0.0f, + {mCenter1.x, mCenter1.y}, + A, + mRadius1, + mRadius1 * mRadius1, + mStopCollection->GetExtendMode() != D2D1_EXTEND_MODE_CLAMP ? 1.0f : 0.0f, + mStopCollection->GetExtendMode() == D2D1_EXTEND_MODE_MIRROR ? 1.0f : 0.0f, + {0.0f}, + {mat._11, mat._21, mat._31, 0.0f, mat._12, mat._22, mat._32, 0.0f}}; + + hr = mDrawInfo->SetPixelShaderConstantBuffer((BYTE*)&buffer, sizeof(buffer)); + + if (FAILED(hr)) { + return hr; + } + + return S_OK; +} + +IFACEMETHODIMP +RadialGradientEffectD2D1::SetGraph(ID2D1TransformGraph* pGraph) { + return pGraph->SetSingleTransformNode(this); +} + +IFACEMETHODIMP_(ULONG) +RadialGradientEffectD2D1::AddRef() { return ++mRefCount; } + +IFACEMETHODIMP_(ULONG) +RadialGradientEffectD2D1::Release() { + if (!--mRefCount) { + delete this; + return 0; + } + return mRefCount; +} + +IFACEMETHODIMP +RadialGradientEffectD2D1::QueryInterface(const IID& aIID, void** aPtr) { + if (!aPtr) { + return E_POINTER; + } + + if (aIID == IID_IUnknown) { + *aPtr = static_cast<IUnknown*>(static_cast<ID2D1EffectImpl*>(this)); + } else if (aIID == IID_ID2D1EffectImpl) { + *aPtr = static_cast<ID2D1EffectImpl*>(this); + } else if (aIID == IID_ID2D1DrawTransform) { + *aPtr = static_cast<ID2D1DrawTransform*>(this); + } else if (aIID == IID_ID2D1Transform) { + *aPtr = static_cast<ID2D1Transform*>(this); + } else if (aIID == IID_ID2D1TransformNode) { + *aPtr = static_cast<ID2D1TransformNode*>(this); + } else { + return E_NOINTERFACE; + } + + static_cast<IUnknown*>(*aPtr)->AddRef(); + return S_OK; +} + +IFACEMETHODIMP +RadialGradientEffectD2D1::MapInputRectsToOutputRect( + const D2D1_RECT_L* pInputRects, const D2D1_RECT_L* pInputOpaqueSubRects, + UINT32 inputRectCount, D2D1_RECT_L* pOutputRect, + D2D1_RECT_L* pOutputOpaqueSubRect) { + if (inputRectCount != 1) { + return E_INVALIDARG; + } + + *pOutputRect = *pInputRects; + *pOutputOpaqueSubRect = *pInputOpaqueSubRects; + return S_OK; +} + +IFACEMETHODIMP +RadialGradientEffectD2D1::MapOutputRectToInputRects( + const D2D1_RECT_L* pOutputRect, D2D1_RECT_L* pInputRects, + UINT32 inputRectCount) const { + if (inputRectCount != 1) { + return E_INVALIDARG; + } + + *pInputRects = *pOutputRect; + return S_OK; +} + +IFACEMETHODIMP +RadialGradientEffectD2D1::MapInvalidRect( + UINT32 inputIndex, D2D1_RECT_L invalidInputRect, + D2D1_RECT_L* pInvalidOutputRect) const { + MOZ_ASSERT(inputIndex == 0); + + *pInvalidOutputRect = invalidInputRect; + return S_OK; +} + +IFACEMETHODIMP +RadialGradientEffectD2D1::SetDrawInfo(ID2D1DrawInfo* pDrawInfo) { + mDrawInfo = pDrawInfo; + return S_OK; +} + +HRESULT +RadialGradientEffectD2D1::Register(ID2D1Factory1* aFactory) { + D2D1_PROPERTY_BINDING bindings[] = { + D2D1_VALUE_TYPE_BINDING(L"StopCollection", + &RadialGradientEffectD2D1::SetStopCollection, + &RadialGradientEffectD2D1::GetStopCollection), + D2D1_VALUE_TYPE_BINDING(L"Center1", &RadialGradientEffectD2D1::SetCenter1, + &RadialGradientEffectD2D1::GetCenter1), + D2D1_VALUE_TYPE_BINDING(L"Center2", &RadialGradientEffectD2D1::SetCenter2, + &RadialGradientEffectD2D1::GetCenter2), + D2D1_VALUE_TYPE_BINDING(L"Radius1", &RadialGradientEffectD2D1::SetRadius1, + &RadialGradientEffectD2D1::GetRadius1), + D2D1_VALUE_TYPE_BINDING(L"Radius2", &RadialGradientEffectD2D1::SetRadius2, + &RadialGradientEffectD2D1::GetRadius2), + D2D1_VALUE_TYPE_BINDING(L"Transform", + &RadialGradientEffectD2D1::SetTransform, + &RadialGradientEffectD2D1::GetTransform)}; + HRESULT hr = aFactory->RegisterEffectFromString( + CLSID_RadialGradientEffect, kXmlDescription, bindings, + ARRAYSIZE(bindings), CreateEffect); + + if (FAILED(hr)) { + gfxWarning() << "Failed to register radial gradient effect."; + } + return hr; +} + +void RadialGradientEffectD2D1::Unregister(ID2D1Factory1* aFactory) { + aFactory->UnregisterEffect(CLSID_RadialGradientEffect); +} + +HRESULT __stdcall RadialGradientEffectD2D1::CreateEffect( + IUnknown** aEffectImpl) { + *aEffectImpl = static_cast<ID2D1EffectImpl*>(new RadialGradientEffectD2D1()); + (*aEffectImpl)->AddRef(); + + return S_OK; +} + +HRESULT +RadialGradientEffectD2D1::SetStopCollection(IUnknown* aStopCollection) { + if (SUCCEEDED(aStopCollection->QueryInterface( + (ID2D1GradientStopCollection**)getter_AddRefs(mStopCollection)))) { + return S_OK; + } + + return E_INVALIDARG; +} + +already_AddRefed<ID2D1ResourceTexture> +RadialGradientEffectD2D1::CreateGradientTexture() { + std::vector<D2D1_GRADIENT_STOP> rawStops; + rawStops.resize(mStopCollection->GetGradientStopCount()); + mStopCollection->GetGradientStops(&rawStops.front(), rawStops.size()); + + std::vector<unsigned char> textureData; + textureData.resize(4096 * 4); + unsigned char* texData = &textureData.front(); + + float prevColorPos = 0; + float nextColorPos = 1.0f; + D2D1_COLOR_F prevColor = rawStops[0].color; + D2D1_COLOR_F nextColor = prevColor; + + if (rawStops.size() >= 2) { + nextColor = rawStops[1].color; + nextColorPos = rawStops[1].position; + } + + uint32_t stopPosition = 2; + + // Not the most optimized way but this will do for now. + for (int i = 0; i < 4096; i++) { + // The 4095 seems a little counter intuitive, but we want the gradient + // color at offset 0 at the first pixel, and at offset 1.0f at the last + // pixel. + float pos = float(i) / 4095; + + while (pos > nextColorPos) { + prevColor = nextColor; + prevColorPos = nextColorPos; + if (rawStops.size() > stopPosition) { + nextColor = rawStops[stopPosition].color; + nextColorPos = rawStops[stopPosition++].position; + } else { + nextColorPos = 1.0f; + } + } + + float interp; + + if (nextColorPos != prevColorPos) { + interp = (pos - prevColorPos) / (nextColorPos - prevColorPos); + } else { + interp = 0; + } + + DeviceColor newColor(prevColor.r + (nextColor.r - prevColor.r) * interp, + prevColor.g + (nextColor.g - prevColor.g) * interp, + prevColor.b + (nextColor.b - prevColor.b) * interp, + prevColor.a + (nextColor.a - prevColor.a) * interp); + + // Note D2D expects RGBA here!! + texData[i * 4] = (unsigned char)(255.0f * newColor.r); + texData[i * 4 + 1] = (unsigned char)(255.0f * newColor.g); + texData[i * 4 + 2] = (unsigned char)(255.0f * newColor.b); + texData[i * 4 + 3] = (unsigned char)(255.0f * newColor.a); + } + + RefPtr<ID2D1ResourceTexture> tex; + + UINT32 width = 4096; + UINT32 stride = 4096 * 4; + D2D1_RESOURCE_TEXTURE_PROPERTIES props; + // Older shader models do not support 1D textures. So just use a width x 1 + // texture. + props.dimensions = 2; + UINT32 dims[] = {width, 1}; + props.extents = dims; + props.channelDepth = D2D1_CHANNEL_DEPTH_4; + props.bufferPrecision = D2D1_BUFFER_PRECISION_8BPC_UNORM; + props.filter = D2D1_FILTER_MIN_MAG_MIP_LINEAR; + D2D1_EXTEND_MODE extendMode[] = {mStopCollection->GetExtendMode(), + mStopCollection->GetExtendMode()}; + props.extendModes = extendMode; + + HRESULT hr = mEffectContext->CreateResourceTexture( + nullptr, &props, &textureData.front(), &stride, 4096 * 4, + getter_AddRefs(tex)); + + if (FAILED(hr)) { + gfxWarning() << "Failed to create resource texture: " << hexa(hr); + } + + return tex.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/RadialGradientEffectD2D1.h b/gfx/2d/RadialGradientEffectD2D1.h new file mode 100644 index 0000000000..a49671e6bd --- /dev/null +++ b/gfx/2d/RadialGradientEffectD2D1.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_RADIALGRADIENTEFFECTD2D1_H_ +#define MOZILLA_GFX_RADIALGRADIENTEFFECTD2D1_H_ + +#include <d2d1_1.h> +#include <d2d1effectauthor.h> +#include <d2d1effecthelpers.h> + +#include "2D.h" +#include "mozilla/Attributes.h" + +// {97143DC6-CBC4-4DD4-A8BA-13342B0BA46D} +DEFINE_GUID(CLSID_RadialGradientEffect, 0x97143dc6, 0xcbc4, 0x4dd4, 0xa8, 0xba, + 0x13, 0x34, 0x2b, 0xb, 0xa4, 0x6d); + +// Macro to keep our class nice and clean. +#define SIMPLE_PROP(type, name) \ + public: \ + HRESULT Set##name(type a##name) { \ + m##name = a##name; \ + return S_OK; \ + } \ + type Get##name() const { return m##name; } \ + \ + private: \ + type m##name; + +namespace mozilla { +namespace gfx { + +enum { + RADIAL_PROP_STOP_COLLECTION = 0, + RADIAL_PROP_CENTER_1, + RADIAL_PROP_CENTER_2, + RADIAL_PROP_RADIUS_1, + RADIAL_PROP_RADIUS_2, + RADIAL_PROP_TRANSFORM +}; + +class RadialGradientEffectD2D1 final : public ID2D1EffectImpl, + public ID2D1DrawTransform { + public: + // ID2D1EffectImpl + IFACEMETHODIMP Initialize(ID2D1EffectContext* pContextInternal, + ID2D1TransformGraph* pTransformGraph); + IFACEMETHODIMP PrepareForRender(D2D1_CHANGE_TYPE changeType); + IFACEMETHODIMP SetGraph(ID2D1TransformGraph* pGraph); + + // IUnknown + IFACEMETHODIMP_(ULONG) AddRef(); + IFACEMETHODIMP_(ULONG) Release(); + IFACEMETHODIMP QueryInterface(REFIID riid, void** ppOutput); + + // ID2D1Transform + IFACEMETHODIMP MapInputRectsToOutputRect( + const D2D1_RECT_L* pInputRects, const D2D1_RECT_L* pInputOpaqueSubRects, + UINT32 inputRectCount, D2D1_RECT_L* pOutputRect, + D2D1_RECT_L* pOutputOpaqueSubRect); + IFACEMETHODIMP MapOutputRectToInputRects(const D2D1_RECT_L* pOutputRect, + D2D1_RECT_L* pInputRects, + UINT32 inputRectCount) const; + IFACEMETHODIMP MapInvalidRect(UINT32 inputIndex, D2D1_RECT_L invalidInputRect, + D2D1_RECT_L* pInvalidOutputRect) const; + + // ID2D1TransformNode + IFACEMETHODIMP_(UINT32) GetInputCount() const { return 1; } + + // ID2D1DrawTransform + IFACEMETHODIMP SetDrawInfo(ID2D1DrawInfo* pDrawInfo); + + static HRESULT Register(ID2D1Factory1* aFactory); + static void Unregister(ID2D1Factory1* aFactory); + static HRESULT __stdcall CreateEffect(IUnknown** aEffectImpl); + + HRESULT SetStopCollection(IUnknown* aStopCollection); + IUnknown* GetStopCollection() const { return mStopCollection; } + + private: + already_AddRefed<ID2D1ResourceTexture> CreateGradientTexture(); + + RadialGradientEffectD2D1(); + + uint32_t mRefCount; + RefPtr<ID2D1GradientStopCollection> mStopCollection; + RefPtr<ID2D1EffectContext> mEffectContext; + RefPtr<ID2D1DrawInfo> mDrawInfo; + SIMPLE_PROP(D2D1_VECTOR_2F, Center1); + SIMPLE_PROP(D2D1_VECTOR_2F, Center2); + SIMPLE_PROP(FLOAT, Radius1); + SIMPLE_PROP(FLOAT, Radius2); + SIMPLE_PROP(D2D_MATRIX_3X2_F, Transform); +}; + +} // namespace gfx +} // namespace mozilla +#undef SIMPLE_PROP + +#endif diff --git a/gfx/2d/RecordedEvent.cpp b/gfx/2d/RecordedEvent.cpp new file mode 100644 index 0000000000..265ee1904b --- /dev/null +++ b/gfx/2d/RecordedEvent.cpp @@ -0,0 +1,217 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RecordedEventImpl.h" + +#include "PathRecording.h" +#include "RecordingTypes.h" +#include "Tools.h" +#include "Filters.h" +#include "Logging.h" +#include "ScaledFontBase.h" +#include "SFNTData.h" +#include "InlineTranslator.h" + +namespace mozilla { +namespace gfx { + +/* static */ +bool RecordedEvent::DoWithEventFromStream( + EventStream& aStream, EventType aType, + const std::function<bool(RecordedEvent*)>& aAction) { + return DoWithEvent(aStream, aType, aAction); +} + +/* static */ +bool RecordedEvent::DoWithEventFromReader( + MemReader& aReader, EventType aType, + const std::function<bool(RecordedEvent*)>& aAction) { + return DoWithEvent(aReader, aType, aAction); +} + +std::string RecordedEvent::GetEventName(EventType aType) { + switch (aType) { + case DRAWTARGETCREATION: + return "DrawTarget Creation"; + case DRAWTARGETDESTRUCTION: + return "DrawTarget Destruction"; + case FILLRECT: + return "FillRect"; + case STROKERECT: + return "StrokeRect"; + case STROKELINE: + return "StrokeLine"; + case CLEARRECT: + return "ClearRect"; + case COPYSURFACE: + return "CopySurface"; + case SETPERMITSUBPIXELAA: + return "SetPermitSubpixelAA"; + 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 STROKEGLYPHS: + return "StrokeGlyphs"; + case MASK: + return "Mask"; + case STROKE: + return "Stroke"; + case DRAWSURFACE: + return "DrawSurface"; + case DRAWDEPENDENTSURFACE: + return "DrawDependentSurface"; + case DRAWSURFACEWITHSHADOW: + return "DrawSurfaceWithShadow"; + case DRAWFILTER: + return "DrawFilter"; + case PATHCREATION: + return "PathCreation"; + case PATHDESTRUCTION: + return "PathDestruction"; + case SOURCESURFACECREATION: + return "SourceSurfaceCreation"; + case SOURCESURFACEDESTRUCTION: + return "SourceSurfaceDestruction"; + case FILTERNODECREATION: + return "FilterNodeCreation"; + case FILTERNODEDESTRUCTION: + return "FilterNodeDestruction"; + case GRADIENTSTOPSCREATION: + return "GradientStopsCreation"; + case GRADIENTSTOPSDESTRUCTION: + return "GradientStopsDestruction"; + case SNAPSHOT: + return "Snapshot"; + case SCALEDFONTCREATION: + return "ScaledFontCreation"; + case SCALEDFONTDESTRUCTION: + return "ScaledFontDestruction"; + case MASKSURFACE: + return "MaskSurface"; + case FILTERNODESETATTRIBUTE: + return "SetAttribute"; + case FILTERNODESETINPUT: + return "SetInput"; + case CREATESIMILARDRAWTARGET: + return "CreateSimilarDrawTarget"; + case FONTDATA: + return "FontData"; + case FONTDESC: + return "FontDescriptor"; + case PUSHLAYER: + return "PushLayer"; + case POPLAYER: + return "PopLayer"; + case UNSCALEDFONTCREATION: + return "UnscaledFontCreation"; + case UNSCALEDFONTDESTRUCTION: + return "UnscaledFontDestruction"; + case EXTERNALSURFACECREATION: + return "ExternalSourceSurfaceCreation"; + case LINK: + return "Link"; + case DESTINATION: + return "Destination"; + default: + return "Unknown"; + } +} + +template <class S> +void RecordedEvent::RecordUnscaledFontImpl(UnscaledFont* aUnscaledFont, + S& aOutput) { + RecordedFontData fontData(aUnscaledFont); + RecordedFontDetails fontDetails; + if (fontData.GetFontDetails(fontDetails)) { + // Try to serialise the whole font, just in case this is a web font that + // is not present on the system. + WriteElement(aOutput, fontData.mType); + fontData.RecordToStream(aOutput); + + auto r = RecordedUnscaledFontCreation(aUnscaledFont, fontDetails); + WriteElement(aOutput, r.mType); + r.RecordToStream(aOutput); + } else { + // If that fails, record just the font description and try to load it from + // the system on the other side. + RecordedFontDescriptor fontDesc(aUnscaledFont); + if (fontDesc.IsValid()) { + WriteElement(aOutput, fontDesc.RecordedEvent::mType); + fontDesc.RecordToStream(aOutput); + } else { + gfxWarning() + << "DrawTargetRecording::FillGlyphs failed to serialise UnscaledFont"; + } + } +} + +void RecordedEvent::RecordUnscaledFont(UnscaledFont* aUnscaledFont, + std::ostream* aOutput) { + RecordUnscaledFontImpl(aUnscaledFont, *aOutput); +} + +void RecordedEvent::RecordUnscaledFont(UnscaledFont* aUnscaledFont, + MemStream& aOutput) { + RecordUnscaledFontImpl(aUnscaledFont, aOutput); +} + +already_AddRefed<DrawTarget> Translator::CreateDrawTarget( + ReferencePtr aRefPtr, const IntSize& aSize, SurfaceFormat aFormat) { + RefPtr<DrawTarget> newDT = + GetReferenceDrawTarget()->CreateSimilarDrawTarget(aSize, aFormat); + AddDrawTarget(aRefPtr, newDT); + return newDT.forget(); +} + +void Translator::DrawDependentSurface(uint64_t aKey, const Rect& aRect) { + if (!mDependentSurfaces || !mCurrentDT) { + return; + } + + RefPtr<RecordedDependentSurface> recordedSurface = + mDependentSurfaces->Get(aKey); + if (!recordedSurface) { + return; + } + + // Construct a new translator, so we can recurse into translating this + // sub-recording into the same DT. Set an initial transform for the + // translator, so that all commands get moved into the rect we want to draw. + // + // Because the recording may have filtered out SetTransform calls with the + // same value, we need to call SetTransform here to ensure it gets called at + // least once with the translated matrix. + const Matrix oldTransform = mCurrentDT->GetTransform(); + + Matrix dependentTransform = oldTransform; + dependentTransform.PreTranslate(aRect.TopLeft()); + + mCurrentDT->PushClipRect(aRect); + mCurrentDT->SetTransform(dependentTransform); + + { + InlineTranslator translator(mCurrentDT, nullptr); + translator.SetReferenceDrawTargetTransform(dependentTransform); + translator.SetDependentSurfaces(mDependentSurfaces); + translator.TranslateRecording((char*)recordedSurface->mRecording.mData, + recordedSurface->mRecording.mLen); + } + + mCurrentDT->SetTransform(oldTransform); + mCurrentDT->PopClip(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/RecordedEvent.h b/gfx/2d/RecordedEvent.h new file mode 100644 index 0000000000..a8801ddd1f --- /dev/null +++ b/gfx/2d/RecordedEvent.h @@ -0,0 +1,536 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_RECORDEDEVENT_H_ +#define MOZILLA_GFX_RECORDEDEVENT_H_ + +#include <ostream> +#include <sstream> +#include <cstring> +#include <functional> +#include <vector> + +#include "RecordingTypes.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/ipc/ByteBuf.h" +#include "nsRefPtrHashtable.h" + +namespace mozilla { +namespace gfx { + +const uint32_t kMagicInt = 0xc001feed; + +// A change in major revision means a change in event binary format, causing +// loss of backwards compatibility. Old streams will not work in a player +// using a newer major revision. And new streams will not work in a player +// using an older major revision. +const uint16_t kMajorRevision = 10; +// A change in minor revision means additions of new events. New streams will +// not play in older players. +const uint16_t kMinorRevision = 3; + +struct ReferencePtr { + ReferencePtr() : mLongPtr(0) {} + + MOZ_IMPLICIT ReferencePtr(const void* aLongPtr) + : mLongPtr(uint64_t(aLongPtr)) {} + + template <typename T> + MOZ_IMPLICIT ReferencePtr(const RefPtr<T>& aPtr) + : mLongPtr(uint64_t(aPtr.get())) {} + + ReferencePtr& operator=(const void* aLongPtr) { + mLongPtr = uint64_t(aLongPtr); + return *this; + } + + template <typename T> + ReferencePtr& operator=(const RefPtr<T>& aPtr) { + mLongPtr = uint64_t(aPtr.get()); + return *this; + } + + operator void*() const { return (void*)mLongPtr; } + + uint64_t mLongPtr; +}; + +struct RecordedFontDetails { + uint64_t fontDataKey = 0; + uint32_t size = 0; + uint32_t index = 0; +}; + +struct RecordedDependentSurface { + NS_INLINE_DECL_REFCOUNTING(RecordedDependentSurface); + + RecordedDependentSurface(const IntSize& aSize, + mozilla::ipc::ByteBuf&& aRecording) + : mSize(aSize), mRecording(std::move(aRecording)) {} + + IntSize mSize; + mozilla::ipc::ByteBuf mRecording; + + private: + ~RecordedDependentSurface() = default; +}; + +// Used by the Azure drawing debugger (player2d) +inline std::string StringFromPtr(ReferencePtr aPtr) { + std::stringstream stream; + stream << aPtr; + return stream.str(); +} + +class Translator { + public: + virtual ~Translator() = default; + + virtual DrawTarget* LookupDrawTarget(ReferencePtr aRefPtr) = 0; + virtual Path* LookupPath(ReferencePtr aRefPtr) = 0; + virtual SourceSurface* LookupSourceSurface(ReferencePtr aRefPtr) = 0; + virtual FilterNode* LookupFilterNode(ReferencePtr aRefPtr) = 0; + virtual already_AddRefed<GradientStops> LookupGradientStops( + ReferencePtr aRefPtr) = 0; + virtual ScaledFont* LookupScaledFont(ReferencePtr aRefPtr) = 0; + virtual UnscaledFont* LookupUnscaledFont(ReferencePtr aRefPtr) = 0; + virtual NativeFontResource* LookupNativeFontResource(uint64_t aKey) = 0; + virtual already_AddRefed<SourceSurface> LookupExternalSurface(uint64_t aKey) { + return nullptr; + } + void DrawDependentSurface(uint64_t aKey, const Rect& aRect); + virtual void AddDrawTarget(ReferencePtr aRefPtr, DrawTarget* aDT) = 0; + virtual void RemoveDrawTarget(ReferencePtr aRefPtr) = 0; + virtual bool SetCurrentDrawTarget(ReferencePtr aRefPtr) = 0; + virtual void AddPath(ReferencePtr aRefPtr, Path* aPath) = 0; + virtual void RemovePath(ReferencePtr aRefPtr) = 0; + virtual void AddSourceSurface(ReferencePtr aRefPtr, SourceSurface* aPath) = 0; + virtual void RemoveSourceSurface(ReferencePtr aRefPtr) = 0; + virtual void AddFilterNode(mozilla::gfx::ReferencePtr aRefPtr, + FilterNode* aSurface) = 0; + virtual void RemoveFilterNode(mozilla::gfx::ReferencePtr aRefPtr) = 0; + + /** + * Get GradientStops compatible with the translation DrawTarget type. + * @param aRawStops array of raw gradient stops required + * @param aNumStops length of aRawStops + * @param aExtendMode extend mode required + * @return an already addrefed GradientStops for our DrawTarget type + */ + virtual already_AddRefed<GradientStops> GetOrCreateGradientStops( + DrawTarget* aDrawTarget, GradientStop* aRawStops, uint32_t aNumStops, + ExtendMode aExtendMode) { + return aDrawTarget->CreateGradientStops(aRawStops, aNumStops, aExtendMode); + } + virtual void AddGradientStops(ReferencePtr aRefPtr, GradientStops* aPath) = 0; + virtual void RemoveGradientStops(ReferencePtr aRefPtr) = 0; + virtual void AddScaledFont(ReferencePtr aRefPtr, ScaledFont* aScaledFont) = 0; + virtual void RemoveScaledFont(ReferencePtr aRefPtr) = 0; + virtual void AddUnscaledFont(ReferencePtr aRefPtr, + UnscaledFont* aUnscaledFont) = 0; + virtual void RemoveUnscaledFont(ReferencePtr aRefPtr) = 0; + virtual void AddNativeFontResource( + uint64_t aKey, NativeFontResource* aNativeFontResource) = 0; + + virtual already_AddRefed<DrawTarget> CreateDrawTarget(ReferencePtr aRefPtr, + const IntSize& aSize, + SurfaceFormat aFormat); + virtual DrawTarget* GetReferenceDrawTarget() = 0; + virtual Matrix GetReferenceDrawTargetTransform() { return Matrix(); } + virtual void* GetFontContext() { return nullptr; } + + void SetDependentSurfaces( + nsRefPtrHashtable<nsUint64HashKey, RecordedDependentSurface>* + aDependentSurfaces) { + mDependentSurfaces = aDependentSurfaces; + } + + DrawTarget* GetCurrentDrawTarget() const { return mCurrentDT; } + + nsRefPtrHashtable<nsUint64HashKey, RecordedDependentSurface>* + mDependentSurfaces = nullptr; + DrawTarget* mCurrentDT = nullptr; +}; + +struct ColorPatternStorage { + DeviceColor mColor; +}; + +struct LinearGradientPatternStorage { + Point mBegin; + Point mEnd; + ReferencePtr mStops; + Matrix mMatrix; +}; + +struct RadialGradientPatternStorage { + Point mCenter1; + Point mCenter2; + Float mRadius1; + Float mRadius2; + ReferencePtr mStops; + Matrix mMatrix; +}; + +struct ConicGradientPatternStorage { + Point mCenter; + Float mAngle; + Float mStartOffset; + Float mEndOffset; + ReferencePtr mStops; + Matrix mMatrix; +}; + +struct SurfacePatternStorage { + ExtendMode mExtend; + SamplingFilter mSamplingFilter; + ReferencePtr mSurface; + Matrix mMatrix; + IntRect mSamplingRect; +}; + +struct PatternStorage { + PatternType mType; + union { + char* mStorage; + char mColor[sizeof(ColorPatternStorage)]; + char mLinear[sizeof(LinearGradientPatternStorage)]; + char mRadial[sizeof(RadialGradientPatternStorage)]; + char mConic[sizeof(ConicGradientPatternStorage)]; + char mSurface[sizeof(SurfacePatternStorage)]; + }; +}; + +/* SizeCollector and MemWriter are used + * in a pair to first collect the size of the + * event that we're going to write and then + * to write it without checking each individual + * size. */ +struct SizeCollector { + SizeCollector() : mTotalSize(0) {} + void write(const char*, size_t s) { mTotalSize += s; } + size_t mTotalSize; +}; + +struct MemWriter { + constexpr explicit MemWriter(char* aPtr) : mPtr(aPtr) {} + void write(const char* aData, size_t aSize) { + memcpy(mPtr, aData, aSize); + mPtr += aSize; + } + char* mPtr; +}; + +// An istream like class for reading from memory +struct MemReader { + constexpr MemReader(char* aData, size_t aLen) + : mData(aData), mEnd(aData + aLen) {} + void read(char* s, std::streamsize n) { + if (n <= (mEnd - mData)) { + memcpy(s, mData, n); + mData += n; + } else { + // We've requested more data than is available + // set the Reader into an eof state + SetIsBad(); + } + } + bool eof() { return mData > mEnd; } + bool good() { return !eof(); } + void SetIsBad() { mData = mEnd + 1; } + + char* mData; + char* mEnd; +}; + +class ContiguousBuffer { + public: + ContiguousBuffer(char* aStart, size_t aSize) + : mWriter(aStart), mEnd(aStart + aSize) {} + + constexpr MOZ_IMPLICIT ContiguousBuffer(std::nullptr_t) : mWriter(nullptr) {} + + MemWriter& Writer() { return mWriter; } + + size_t SizeRemaining() { return mWriter.mPtr ? mEnd - mWriter.mPtr : 0; } + + bool IsValid() { return !!mWriter.mPtr; } + + private: + MemWriter mWriter; + char* mEnd = nullptr; +}; + +// Allows a derived class to provide guaranteed contiguous buffer. +class ContiguousBufferStream { + public: + /** + * Templated RecordEvent function so that we can record into the buffer + * quickly using MemWriter. + * + * @param aRecordedEvent the event to record + */ + template <class RE> + void RecordEvent(const RE* aRecordedEvent) { + SizeCollector size; + WriteElement(size, aRecordedEvent->GetType()); + aRecordedEvent->Record(size); + auto& buffer = GetContiguousBuffer(size.mTotalSize); + if (!buffer.IsValid()) { + return; + } + + MOZ_ASSERT(size.mTotalSize <= buffer.SizeRemaining()); + + WriteElement(buffer.Writer(), aRecordedEvent->GetType()); + aRecordedEvent->Record(buffer.Writer()); + IncrementEventCount(); + } + + protected: + /** + * Provide a contiguous buffer with at least aSize remaining. + */ + virtual ContiguousBuffer& GetContiguousBuffer(size_t aSize) = 0; + + virtual void IncrementEventCount() = 0; +}; + +struct MemStream { + char* mData; + size_t mLength; + size_t mCapacity; + bool mValid = true; + bool Resize(size_t aSize) { + if (!mValid) { + return false; + } + mLength = aSize; + if (mLength > mCapacity) { + mCapacity = mCapacity * 2; + // check if the doubled capacity is enough + // otherwise use double mLength + if (mLength > mCapacity) { + mCapacity = mLength * 2; + } + char* data = (char*)realloc(mData, mCapacity); + if (!data) { + free(mData); + } + mData = data; + } + if (mData) { + return true; + } + NS_ERROR("Failed to allocate MemStream!"); + mValid = false; + mLength = 0; + mCapacity = 0; + return false; + } + + void reset() { + free(mData); + mData = nullptr; + mValid = true; + mLength = 0; + mCapacity = 0; + } + + MemStream(const MemStream&) = delete; + MemStream(MemStream&&) = delete; + MemStream& operator=(const MemStream&) = delete; + MemStream& operator=(MemStream&&) = delete; + + void write(const char* aData, size_t aSize) { + if (Resize(mLength + aSize)) { + memcpy(mData + mLength - aSize, aData, aSize); + } + } + + MemStream() : mData(nullptr), mLength(0), mCapacity(0) {} + ~MemStream() { free(mData); } +}; + +class EventStream { + public: + virtual void write(const char* aData, size_t aSize) = 0; + virtual void read(char* aOut, size_t aSize) = 0; + virtual bool good() = 0; + virtual void SetIsBad() = 0; +}; + +class RecordedEvent { + public: + enum EventType : uint8_t { + INVALID = 0, + DRAWTARGETCREATION, + DRAWTARGETDESTRUCTION, + SETCURRENTDRAWTARGET, + FILLRECT, + STROKERECT, + STROKELINE, + STROKECIRCLE, + CLEARRECT, + COPYSURFACE, + SETPERMITSUBPIXELAA, + SETTRANSFORM, + PUSHCLIP, + PUSHCLIPRECT, + POPCLIP, + FILL, + FILLCIRCLE, + FILLGLYPHS, + STROKEGLYPHS, + MASK, + STROKE, + DRAWSURFACE, + DRAWDEPENDENTSURFACE, + DRAWSURFACEWITHSHADOW, + DRAWSHADOW, + PATHCREATION, + PATHDESTRUCTION, + SOURCESURFACECREATION, + SOURCESURFACEDESTRUCTION, + GRADIENTSTOPSCREATION, + GRADIENTSTOPSDESTRUCTION, + SNAPSHOT, + SCALEDFONTCREATION, + SCALEDFONTDESTRUCTION, + MASKSURFACE, + FILTERNODECREATION, + FILTERNODEDESTRUCTION, + DRAWFILTER, + FILTERNODESETATTRIBUTE, + FILTERNODESETINPUT, + CREATESIMILARDRAWTARGET, + CREATECLIPPEDDRAWTARGET, + CREATEDRAWTARGETFORFILTER, + FONTDATA, + FONTDESC, + PUSHLAYER, + PUSHLAYERWITHBLEND, + POPLAYER, + UNSCALEDFONTCREATION, + UNSCALEDFONTDESTRUCTION, + INTOLUMINANCE, + EXTRACTSUBRECT, + EXTERNALSURFACECREATION, + FLUSH, + DETACHALLSNAPSHOTS, + OPTIMIZESOURCESURFACE, + LINK, + DESTINATION, + LAST, + }; + + virtual ~RecordedEvent() = default; + + static std::string GetEventName(EventType aType); + + /** + * Play back this event using the translator. Note that derived classes + * should + * only return false when there is a fatal error, as it will probably mean + * the + * translation will abort. + * @param aTranslator Translator to be used for retrieving other referenced + * objects and making playback decisions. + * @return true unless a fatal problem has occurred and playback should + * abort. + */ + virtual bool PlayEvent(Translator* aTranslator) const { return true; } + + virtual void RecordToStream(std::ostream& aStream) const = 0; + virtual void RecordToStream(EventStream& aStream) const = 0; + virtual void RecordToStream(ContiguousBufferStream& aStream) const = 0; + virtual void RecordToStream(MemStream& aStream) const = 0; + + virtual void OutputSimpleEventInfo(std::stringstream& aStringStream) const {} + + template <class S> + void RecordPatternData(S& aStream, + const PatternStorage& aPatternStorage) const; + template <class S> + void ReadPatternData(S& aStream, PatternStorage& aPatternStorage) const; + void StorePattern(PatternStorage& aDestination, const Pattern& aSource) const; + template <class S> + void RecordStrokeOptions(S& aStream, + const StrokeOptions& aStrokeOptions) const; + template <class S> + void ReadStrokeOptions(S& aStream, StrokeOptions& aStrokeOptions); + + virtual std::string GetName() const = 0; + + virtual ReferencePtr GetDestinedDT() { return nullptr; } + + void OutputSimplePatternInfo(const PatternStorage& aStorage, + std::stringstream& aOutput) const; + + template <class S> + static bool DoWithEvent(S& aStream, EventType aType, + const std::function<bool(RecordedEvent*)>& aAction); + static bool DoWithEventFromStream( + EventStream& aStream, EventType aType, + const std::function<bool(RecordedEvent*)>& aAction); + static bool DoWithEventFromReader( + MemReader& aReader, EventType aType, + const std::function<bool(RecordedEvent*)>& aAction); + + EventType GetType() const { return (EventType)mType; } + + protected: + friend class DrawEventRecorderPrivate; + friend class DrawEventRecorderMemory; + static void RecordUnscaledFont(UnscaledFont* aUnscaledFont, + std::ostream* aOutput); + static void RecordUnscaledFont(UnscaledFont* aUnscaledFont, + MemStream& aOutput); + template <class S> + static void RecordUnscaledFontImpl(UnscaledFont* aUnscaledFont, S& aOutput); + + MOZ_IMPLICIT RecordedEvent(EventType aType) : mType(aType) {} + + EventType mType; + std::vector<Float> mDashPatternStorage; +}; + +template <class Derived> +class RecordedEventDerived : public RecordedEvent { + using RecordedEvent::RecordedEvent; + + public: + void RecordToStream(std::ostream& aStream) const override { + WriteElement(aStream, this->mType); + static_cast<const Derived*>(this)->Record(aStream); + } + void RecordToStream(EventStream& aStream) const override { + WriteElement(aStream, this->mType); + static_cast<const Derived*>(this)->Record(aStream); + } + void RecordToStream(ContiguousBufferStream& aStream) const final { + aStream.RecordEvent(static_cast<const Derived*>(this)); + } + void RecordToStream(MemStream& aStream) const override { + SizeCollector size; + WriteElement(size, this->mType); + static_cast<const Derived*>(this)->Record(size); + + if (!aStream.Resize(aStream.mLength + size.mTotalSize)) { + return; + } + + MemWriter writer(aStream.mData + aStream.mLength - size.mTotalSize); + WriteElement(writer, this->mType); + static_cast<const Derived*>(this)->Record(writer); + } +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/RecordedEventImpl.h b/gfx/2d/RecordedEventImpl.h new file mode 100644 index 0000000000..a880536bf8 --- /dev/null +++ b/gfx/2d/RecordedEventImpl.h @@ -0,0 +1,4439 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_RECORDEDEVENTIMPL_H_ +#define MOZILLA_GFX_RECORDEDEVENTIMPL_H_ + +#include "RecordedEvent.h" + +#include "PathRecording.h" +#include "RecordingTypes.h" +#include "Tools.h" +#include "Filters.h" +#include "Logging.h" +#include "ScaledFontBase.h" +#include "SFNTData.h" + +namespace mozilla { +namespace gfx { + +class RecordedDrawTargetCreation + : public RecordedEventDerived<RecordedDrawTargetCreation> { + public: + RecordedDrawTargetCreation(ReferencePtr aRefPtr, BackendType aType, + const IntRect& aRect, SurfaceFormat aFormat, + bool aHasExistingData = false, + SourceSurface* aExistingData = nullptr) + : RecordedEventDerived(DRAWTARGETCREATION), + mRefPtr(aRefPtr), + mBackendType(aType), + mRect(aRect), + mFormat(aFormat), + mHasExistingData(aHasExistingData), + mExistingData(aExistingData) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + virtual void OutputSimpleEventInfo( + std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "DrawTarget Creation"; } + + ReferencePtr mRefPtr; + BackendType mBackendType; + IntRect mRect; + SurfaceFormat mFormat; + bool mHasExistingData = false; + RefPtr<SourceSurface> mExistingData; + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedDrawTargetCreation(S& aStream); +}; + +class RecordedDrawTargetDestruction + : public RecordedEventDerived<RecordedDrawTargetDestruction> { + public: + MOZ_IMPLICIT RecordedDrawTargetDestruction(ReferencePtr aRefPtr) + : RecordedEventDerived(DRAWTARGETDESTRUCTION), + mRefPtr(aRefPtr), + mBackendType(BackendType::NONE) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "DrawTarget Destruction"; } + + ReferencePtr mRefPtr; + + BackendType mBackendType; + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedDrawTargetDestruction(S& aStream); +}; + +class RecordedSetCurrentDrawTarget + : public RecordedEventDerived<RecordedSetCurrentDrawTarget> { + public: + MOZ_IMPLICIT RecordedSetCurrentDrawTarget(ReferencePtr aRefPtr) + : RecordedEventDerived(SETCURRENTDRAWTARGET), mRefPtr(aRefPtr) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "SetCurrentDrawTarget"; } + + ReferencePtr mRefPtr; + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedSetCurrentDrawTarget(S& aStream); +}; + +class RecordedCreateSimilarDrawTarget + : public RecordedEventDerived<RecordedCreateSimilarDrawTarget> { + public: + RecordedCreateSimilarDrawTarget(ReferencePtr aRefPtr, const IntSize& aSize, + SurfaceFormat aFormat) + : RecordedEventDerived(CREATESIMILARDRAWTARGET), + mRefPtr(aRefPtr), + mSize(aSize), + mFormat(aFormat) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + virtual void OutputSimpleEventInfo( + std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "CreateSimilarDrawTarget"; } + + ReferencePtr mRefPtr; + IntSize mSize; + SurfaceFormat mFormat; + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedCreateSimilarDrawTarget(S& aStream); +}; + +class RecordedCreateClippedDrawTarget + : public RecordedEventDerived<RecordedCreateClippedDrawTarget> { + public: + RecordedCreateClippedDrawTarget(ReferencePtr aRefPtr, const Rect& aBounds, + SurfaceFormat aFormat) + : RecordedEventDerived(CREATECLIPPEDDRAWTARGET), + mRefPtr(aRefPtr), + mBounds(aBounds), + mFormat(aFormat) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + virtual void OutputSimpleEventInfo( + std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "CreateClippedDrawTarget"; } + + ReferencePtr mRefPtr; + Rect mBounds; + SurfaceFormat mFormat; + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedCreateClippedDrawTarget(S& aStream); +}; + +class RecordedCreateDrawTargetForFilter + : public RecordedEventDerived<RecordedCreateDrawTargetForFilter> { + public: + RecordedCreateDrawTargetForFilter(ReferencePtr aRefPtr, + const IntSize& aMaxSize, + SurfaceFormat aFormat, FilterNode* aFilter, + FilterNode* aSource, + const Rect& aSourceRect, + const Point& aDestPoint) + : RecordedEventDerived(CREATEDRAWTARGETFORFILTER), + mRefPtr(aRefPtr), + mMaxSize(aMaxSize), + mFormat(aFormat), + mFilter(aFilter), + mSource(aSource), + mSourceRect(aSourceRect), + mDestPoint(aDestPoint) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + virtual void OutputSimpleEventInfo( + std::stringstream& aStringStream) const override; + + std::string GetName() const override { + return "CreateSimilarDrawTargetForFilter"; + } + + ReferencePtr mRefPtr; + IntSize mMaxSize; + SurfaceFormat mFormat; + ReferencePtr mFilter; + ReferencePtr mSource; + Rect mSourceRect; + Point mDestPoint; + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedCreateDrawTargetForFilter(S& aStream); +}; + +class RecordedFillRect : public RecordedEventDerived<RecordedFillRect> { + public: + RecordedFillRect(const Rect& aRect, const Pattern& aPattern, + const DrawOptions& aOptions) + : RecordedEventDerived(FILLRECT), + mRect(aRect), + mPattern(), + mOptions(aOptions) { + StorePattern(mPattern, aPattern); + } + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "FillRect"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedFillRect(S& aStream); + + Rect mRect; + PatternStorage mPattern; + DrawOptions mOptions; +}; + +class RecordedStrokeRect : public RecordedEventDerived<RecordedStrokeRect> { + public: + RecordedStrokeRect(const Rect& aRect, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) + : RecordedEventDerived(STROKERECT), + mRect(aRect), + mPattern(), + mStrokeOptions(aStrokeOptions), + mOptions(aOptions) { + StorePattern(mPattern, aPattern); + } + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "StrokeRect"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedStrokeRect(S& aStream); + + Rect mRect; + PatternStorage mPattern; + StrokeOptions mStrokeOptions; + DrawOptions mOptions; +}; + +class RecordedStrokeLine : public RecordedEventDerived<RecordedStrokeLine> { + public: + RecordedStrokeLine(const Point& aBegin, const Point& aEnd, + const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) + : RecordedEventDerived(STROKELINE), + mBegin(aBegin), + mEnd(aEnd), + mPattern(), + mStrokeOptions(aStrokeOptions), + mOptions(aOptions) { + StorePattern(mPattern, aPattern); + } + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "StrokeLine"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedStrokeLine(S& aStream); + + Point mBegin; + Point mEnd; + PatternStorage mPattern; + StrokeOptions mStrokeOptions; + DrawOptions mOptions; +}; + +class RecordedStrokeCircle : public RecordedEventDerived<RecordedStrokeCircle> { + public: + RecordedStrokeCircle(Circle aCircle, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) + : RecordedEventDerived(STROKECIRCLE), + mCircle(aCircle), + mPattern(), + mStrokeOptions(aStrokeOptions), + mOptions(aOptions) { + StorePattern(mPattern, aPattern); + } + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "StrokeCircle"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedStrokeCircle(S& aStream); + + Circle mCircle; + PatternStorage mPattern; + StrokeOptions mStrokeOptions; + DrawOptions mOptions; +}; + +class RecordedFill : public RecordedEventDerived<RecordedFill> { + public: + RecordedFill(ReferencePtr aPath, const Pattern& aPattern, + const DrawOptions& aOptions) + : RecordedEventDerived(FILL), + mPath(aPath), + mPattern(), + mOptions(aOptions) { + StorePattern(mPattern, aPattern); + } + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "Fill"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedFill(S& aStream); + + ReferencePtr mPath; + PatternStorage mPattern; + DrawOptions mOptions; +}; + +class RecordedFillCircle : public RecordedEventDerived<RecordedFillCircle> { + public: + RecordedFillCircle(Circle aCircle, const Pattern& aPattern, + const DrawOptions& aOptions) + : RecordedEventDerived(FILLCIRCLE), + mCircle(aCircle), + mPattern(), + mOptions(aOptions) { + StorePattern(mPattern, aPattern); + } + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "FillCircle"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedFillCircle(S& aStream); + + Circle mCircle; + PatternStorage mPattern; + DrawOptions mOptions; +}; + +template <class Derived> +class RecordedDrawGlyphs : public RecordedEventDerived<Derived> { + public: + RecordedDrawGlyphs(RecordedEvent::EventType aType, ReferencePtr aScaledFont, + const Pattern& aPattern, const DrawOptions& aOptions, + const Glyph* aGlyphs, uint32_t aNumGlyphs) + : RecordedEventDerived<Derived>(aType), + mScaledFont(aScaledFont), + mPattern(), + mOptions(aOptions) { + this->StorePattern(mPattern, aPattern); + mNumGlyphs = aNumGlyphs; + mGlyphs = new Glyph[aNumGlyphs]; + memcpy(mGlyphs, aGlyphs, sizeof(Glyph) * aNumGlyphs); + } + virtual ~RecordedDrawGlyphs(); + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + protected: + friend class RecordedEvent; + + template <class S> + RecordedDrawGlyphs(RecordedEvent::EventType aType, S& aStream); + + virtual void DrawGlyphs(DrawTarget* aDT, ScaledFont* aScaledFont, + const GlyphBuffer& aBuffer, + const Pattern& aPattern) const = 0; + + ReferencePtr mScaledFont; + PatternStorage mPattern; + DrawOptions mOptions; + Glyph* mGlyphs = nullptr; + uint32_t mNumGlyphs = 0; +}; + +class RecordedFillGlyphs : public RecordedDrawGlyphs<RecordedFillGlyphs> { + public: + RecordedFillGlyphs(ReferencePtr aScaledFont, const Pattern& aPattern, + const DrawOptions& aOptions, const Glyph* aGlyphs, + uint32_t aNumGlyphs) + : RecordedDrawGlyphs(FILLGLYPHS, aScaledFont, aPattern, aOptions, aGlyphs, + aNumGlyphs) {} + + std::string GetName() const override { return "FillGlyphs"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedFillGlyphs(S& aStream) + : RecordedDrawGlyphs(FILLGLYPHS, aStream) {} + + void DrawGlyphs(DrawTarget* aDT, ScaledFont* aScaledFont, + const GlyphBuffer& aBuffer, + const Pattern& aPattern) const override { + aDT->FillGlyphs(aScaledFont, aBuffer, aPattern, mOptions); + } +}; + +class RecordedStrokeGlyphs : public RecordedDrawGlyphs<RecordedStrokeGlyphs> { + public: + RecordedStrokeGlyphs(ReferencePtr aScaledFont, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions, const Glyph* aGlyphs, + uint32_t aNumGlyphs) + : RecordedDrawGlyphs(STROKEGLYPHS, aScaledFont, aPattern, aOptions, + aGlyphs, aNumGlyphs), + mStrokeOptions(aStrokeOptions) {} + + std::string GetName() const override { return "StrokeGlyphs"; } + + template <class S> + void Record(S& aStream) const { + RecordedDrawGlyphs::Record(aStream); + RecordStrokeOptions(aStream, mStrokeOptions); + } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedStrokeGlyphs(S& aStream) + : RecordedDrawGlyphs(STROKEGLYPHS, aStream) { + ReadStrokeOptions(aStream, mStrokeOptions); + } + + void DrawGlyphs(DrawTarget* aDT, ScaledFont* aScaledFont, + const GlyphBuffer& aBuffer, + const Pattern& aPattern) const override { + aDT->StrokeGlyphs(aScaledFont, aBuffer, aPattern, mStrokeOptions, mOptions); + } + + StrokeOptions mStrokeOptions; +}; + +class RecordedMask : public RecordedEventDerived<RecordedMask> { + public: + RecordedMask(const Pattern& aSource, const Pattern& aMask, + const DrawOptions& aOptions) + : RecordedEventDerived(MASK), mSource(), mMask(), mOptions(aOptions) { + StorePattern(mSource, aSource); + StorePattern(mMask, aMask); + } + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "Mask"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedMask(S& aStream); + + PatternStorage mSource; + PatternStorage mMask; + DrawOptions mOptions; +}; + +class RecordedStroke : public RecordedEventDerived<RecordedStroke> { + public: + RecordedStroke(ReferencePtr aPath, const Pattern& aPattern, + const StrokeOptions& aStrokeOptions, + const DrawOptions& aOptions) + : RecordedEventDerived(STROKE), + mPath(aPath), + mPattern(), + mStrokeOptions(aStrokeOptions), + mOptions(aOptions) { + StorePattern(mPattern, aPattern); + } + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + virtual void OutputSimpleEventInfo( + std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "Stroke"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedStroke(S& aStream); + + ReferencePtr mPath; + PatternStorage mPattern; + StrokeOptions mStrokeOptions; + DrawOptions mOptions; +}; + +class RecordedClearRect : public RecordedEventDerived<RecordedClearRect> { + public: + explicit RecordedClearRect(const Rect& aRect) + : RecordedEventDerived(CLEARRECT), mRect(aRect) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "ClearRect"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedClearRect(S& aStream); + + Rect mRect; +}; + +class RecordedCopySurface : public RecordedEventDerived<RecordedCopySurface> { + public: + RecordedCopySurface(ReferencePtr aSourceSurface, const IntRect& aSourceRect, + const IntPoint& aDest) + : RecordedEventDerived(COPYSURFACE), + mSourceSurface(aSourceSurface), + mSourceRect(aSourceRect), + mDest(aDest) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "CopySurface"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedCopySurface(S& aStream); + + ReferencePtr mSourceSurface; + IntRect mSourceRect; + IntPoint mDest; +}; + +class RecordedPushClip : public RecordedEventDerived<RecordedPushClip> { + public: + explicit RecordedPushClip(ReferencePtr aPath) + : RecordedEventDerived(PUSHCLIP), mPath(aPath) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "PushClip"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedPushClip(S& aStream); + + ReferencePtr mPath; +}; + +class RecordedPushClipRect : public RecordedEventDerived<RecordedPushClipRect> { + public: + explicit RecordedPushClipRect(const Rect& aRect) + : RecordedEventDerived(PUSHCLIPRECT), mRect(aRect) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "PushClipRect"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedPushClipRect(S& aStream); + + Rect mRect; +}; + +class RecordedPopClip : public RecordedEventDerived<RecordedPopClip> { + public: + MOZ_IMPLICIT RecordedPopClip() : RecordedEventDerived(POPCLIP) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "PopClip"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedPopClip(S& aStream); +}; + +class RecordedPushLayer : public RecordedEventDerived<RecordedPushLayer> { + public: + RecordedPushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask, + const Matrix& aMaskTransform, const IntRect& aBounds, + bool aCopyBackground) + : RecordedEventDerived(PUSHLAYER), + mOpaque(aOpaque), + mOpacity(aOpacity), + mMask(aMask), + mMaskTransform(aMaskTransform), + mBounds(aBounds), + mCopyBackground(aCopyBackground) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "PushLayer"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedPushLayer(S& aStream); + + bool mOpaque; + Float mOpacity; + ReferencePtr mMask; + Matrix mMaskTransform; + IntRect mBounds; + bool mCopyBackground; +}; + +class RecordedPushLayerWithBlend + : public RecordedEventDerived<RecordedPushLayerWithBlend> { + public: + RecordedPushLayerWithBlend(bool aOpaque, Float aOpacity, SourceSurface* aMask, + const Matrix& aMaskTransform, + const IntRect& aBounds, bool aCopyBackground, + CompositionOp aCompositionOp) + : RecordedEventDerived(PUSHLAYERWITHBLEND), + mOpaque(aOpaque), + mOpacity(aOpacity), + mMask(aMask), + mMaskTransform(aMaskTransform), + mBounds(aBounds), + mCopyBackground(aCopyBackground), + mCompositionOp(aCompositionOp) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + virtual void OutputSimpleEventInfo( + std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "PushLayerWithBlend"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedPushLayerWithBlend(S& aStream); + + bool mOpaque; + Float mOpacity; + ReferencePtr mMask; + Matrix mMaskTransform; + IntRect mBounds; + bool mCopyBackground; + CompositionOp mCompositionOp; +}; + +class RecordedPopLayer : public RecordedEventDerived<RecordedPopLayer> { + public: + RecordedPopLayer() : RecordedEventDerived(POPLAYER) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "PopLayer"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedPopLayer(S& aStream); +}; + +class RecordedSetPermitSubpixelAA + : public RecordedEventDerived<RecordedSetPermitSubpixelAA> { + public: + explicit RecordedSetPermitSubpixelAA(bool aPermitSubpixelAA) + : RecordedEventDerived(SETPERMITSUBPIXELAA), + mPermitSubpixelAA(aPermitSubpixelAA) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "SetPermitSubpixelAA"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedSetPermitSubpixelAA(S& aStream); + + bool mPermitSubpixelAA = false; +}; + +class RecordedSetTransform : public RecordedEventDerived<RecordedSetTransform> { + public: + explicit RecordedSetTransform(const Matrix& aTransform) + : RecordedEventDerived(SETTRANSFORM), mTransform(aTransform) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "SetTransform"; } + + Matrix mTransform; + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedSetTransform(S& aStream); +}; + +class RecordedDrawSurface : public RecordedEventDerived<RecordedDrawSurface> { + public: + RecordedDrawSurface(ReferencePtr aRefSource, const Rect& aDest, + const Rect& aSource, const DrawSurfaceOptions& aDSOptions, + const DrawOptions& aOptions) + : RecordedEventDerived(DRAWSURFACE), + mRefSource(aRefSource), + mDest(aDest), + mSource(aSource), + mDSOptions(aDSOptions), + mOptions(aOptions) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "DrawSurface"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedDrawSurface(S& aStream); + + ReferencePtr mRefSource; + Rect mDest; + Rect mSource; + DrawSurfaceOptions mDSOptions; + DrawOptions mOptions; +}; + +class RecordedDrawDependentSurface + : public RecordedEventDerived<RecordedDrawDependentSurface> { + public: + RecordedDrawDependentSurface(uint64_t aId, const Rect& aDest) + : RecordedEventDerived(DRAWDEPENDENTSURFACE), mId(aId), mDest(aDest) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "DrawDependentSurface"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedDrawDependentSurface(S& aStream); + + uint64_t mId; + Rect mDest; +}; + +class RecordedDrawSurfaceWithShadow + : public RecordedEventDerived<RecordedDrawSurfaceWithShadow> { + public: + RecordedDrawSurfaceWithShadow(ReferencePtr aRefSource, const Point& aDest, + const ShadowOptions& aShadow, CompositionOp aOp) + : RecordedEventDerived(DRAWSURFACEWITHSHADOW), + mRefSource(aRefSource), + mDest(aDest), + mShadow(aShadow), + mOp(aOp) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "DrawSurfaceWithShadow"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedDrawSurfaceWithShadow(S& aStream); + + ReferencePtr mRefSource; + Point mDest; + ShadowOptions mShadow; + CompositionOp mOp; +}; + +class RecordedDrawShadow : public RecordedEventDerived<RecordedDrawShadow> { + public: + RecordedDrawShadow(ReferencePtr aPath, const Pattern& aPattern, + const ShadowOptions& aShadow, const DrawOptions& aOptions, + const StrokeOptions* aStrokeOptions) + : RecordedEventDerived(DRAWSHADOW), + mPath(aPath), + mPattern(), + mShadow(aShadow), + mOptions(aOptions), + mHasStrokeOptions(!!aStrokeOptions), + mStrokeOptions(aStrokeOptions ? *aStrokeOptions : StrokeOptions()) { + StorePattern(mPattern, aPattern); + } + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "DrawShadow"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedDrawShadow(S& aStream); + + ReferencePtr mPath; + PatternStorage mPattern; + ShadowOptions mShadow; + DrawOptions mOptions; + bool mHasStrokeOptions; + StrokeOptions mStrokeOptions; +}; + +class RecordedDrawFilter : public RecordedEventDerived<RecordedDrawFilter> { + public: + RecordedDrawFilter(ReferencePtr aNode, const Rect& aSourceRect, + const Point& aDestPoint, const DrawOptions& aOptions) + : RecordedEventDerived(DRAWFILTER), + mNode(aNode), + mSourceRect(aSourceRect), + mDestPoint(aDestPoint), + mOptions(aOptions) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "DrawFilter"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedDrawFilter(S& aStream); + + ReferencePtr mNode; + Rect mSourceRect; + Point mDestPoint; + DrawOptions mOptions; +}; + +class RecordedPathCreation : public RecordedEventDerived<RecordedPathCreation> { + public: + MOZ_IMPLICIT RecordedPathCreation(PathRecording* aPath); + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "Path Creation"; } + + private: + friend class RecordedEvent; + + ReferencePtr mDT; + ReferencePtr mRefPtr; + FillRule mFillRule; + RefPtr<PathRecording> mPath; + UniquePtr<PathOps> mPathOps; + + template <class S> + MOZ_IMPLICIT RecordedPathCreation(S& aStream); +}; + +class RecordedPathDestruction + : public RecordedEventDerived<RecordedPathDestruction> { + public: + MOZ_IMPLICIT RecordedPathDestruction(PathRecording* aPath) + : RecordedEventDerived(PATHDESTRUCTION), mRefPtr(aPath) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "Path Destruction"; } + + private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + + template <class S> + MOZ_IMPLICIT RecordedPathDestruction(S& aStream); +}; + +class RecordedSourceSurfaceCreation + : public RecordedEventDerived<RecordedSourceSurfaceCreation> { + public: + RecordedSourceSurfaceCreation(ReferencePtr aRefPtr, uint8_t* aData, + int32_t aStride, const IntSize& aSize, + SurfaceFormat aFormat) + : RecordedEventDerived(SOURCESURFACECREATION), + mRefPtr(aRefPtr), + mData(aData), + mStride(aStride), + mSize(aSize), + mFormat(aFormat), + mDataOwned(false) {} + + ~RecordedSourceSurfaceCreation(); + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "SourceSurface Creation"; } + + private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + uint8_t* mData = nullptr; + int32_t mStride; + IntSize mSize; + SurfaceFormat mFormat; + mutable bool mDataOwned; + + template <class S> + MOZ_IMPLICIT RecordedSourceSurfaceCreation(S& aStream); +}; + +class RecordedSourceSurfaceDestruction + : public RecordedEventDerived<RecordedSourceSurfaceDestruction> { + public: + MOZ_IMPLICIT RecordedSourceSurfaceDestruction(ReferencePtr aRefPtr) + : RecordedEventDerived(SOURCESURFACEDESTRUCTION), mRefPtr(aRefPtr) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "SourceSurface Destruction"; } + + private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + + template <class S> + MOZ_IMPLICIT RecordedSourceSurfaceDestruction(S& aStream); +}; + +class RecordedOptimizeSourceSurface + : public RecordedEventDerived<RecordedOptimizeSourceSurface> { + public: + RecordedOptimizeSourceSurface(ReferencePtr aSurface, + ReferencePtr aOptimizedSurface) + : RecordedEventDerived(OPTIMIZESOURCESURFACE), + mSurface(aSurface), + mOptimizedSurface(aOptimizedSurface) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "OptimizeSourceSurface"; } + + private: + friend class RecordedEvent; + + ReferencePtr mSurface; + ReferencePtr mOptimizedSurface; + + template <class S> + MOZ_IMPLICIT RecordedOptimizeSourceSurface(S& aStream); +}; + +class RecordedExternalSurfaceCreation + : public RecordedEventDerived<RecordedExternalSurfaceCreation> { + public: + RecordedExternalSurfaceCreation(ReferencePtr aRefPtr, const uint64_t aKey) + : RecordedEventDerived(EXTERNALSURFACECREATION), + mRefPtr(aRefPtr), + mKey(aKey) {} + + ~RecordedExternalSurfaceCreation() = default; + + virtual bool PlayEvent(Translator* aTranslator) const; + + template <class S> + void Record(S& aStream) const; + virtual void OutputSimpleEventInfo(std::stringstream& aStringStream) const; + + virtual std::string GetName() const { + return "SourceSurfaceSharedData Creation"; + } + + private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + uint64_t mKey; + + template <class S> + MOZ_IMPLICIT RecordedExternalSurfaceCreation(S& aStream); +}; + +class RecordedFilterNodeCreation + : public RecordedEventDerived<RecordedFilterNodeCreation> { + public: + RecordedFilterNodeCreation(ReferencePtr aRefPtr, FilterType aType) + : RecordedEventDerived(FILTERNODECREATION), + mRefPtr(aRefPtr), + mType(aType) {} + + ~RecordedFilterNodeCreation(); + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "FilterNode Creation"; } + + private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + FilterType mType; + + template <class S> + MOZ_IMPLICIT RecordedFilterNodeCreation(S& aStream); +}; + +class RecordedFilterNodeDestruction + : public RecordedEventDerived<RecordedFilterNodeDestruction> { + public: + MOZ_IMPLICIT RecordedFilterNodeDestruction(ReferencePtr aRefPtr) + : RecordedEventDerived(FILTERNODEDESTRUCTION), mRefPtr(aRefPtr) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "FilterNode Destruction"; } + + private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + + template <class S> + MOZ_IMPLICIT RecordedFilterNodeDestruction(S& aStream); +}; + +class RecordedGradientStopsCreation + : public RecordedEventDerived<RecordedGradientStopsCreation> { + public: + RecordedGradientStopsCreation(ReferencePtr aRefPtr, GradientStop* aStops, + uint32_t aNumStops, ExtendMode aExtendMode) + : RecordedEventDerived(GRADIENTSTOPSCREATION), + mRefPtr(aRefPtr), + mStops(aStops), + mNumStops(aNumStops), + mExtendMode(aExtendMode), + mDataOwned(false) {} + + ~RecordedGradientStopsCreation(); + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "GradientStops Creation"; } + + private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + GradientStop* mStops = nullptr; + uint32_t mNumStops = 0; + ExtendMode mExtendMode; + bool mDataOwned; + + template <class S> + MOZ_IMPLICIT RecordedGradientStopsCreation(S& aStream); +}; + +class RecordedGradientStopsDestruction + : public RecordedEventDerived<RecordedGradientStopsDestruction> { + public: + MOZ_IMPLICIT RecordedGradientStopsDestruction(ReferencePtr aRefPtr) + : RecordedEventDerived(GRADIENTSTOPSDESTRUCTION), mRefPtr(aRefPtr) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "GradientStops Destruction"; } + + private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + + template <class S> + MOZ_IMPLICIT RecordedGradientStopsDestruction(S& aStream); +}; + +class RecordedFlush : public RecordedEventDerived<RecordedFlush> { + public: + explicit RecordedFlush() : RecordedEventDerived(FLUSH) {} + + bool PlayEvent(Translator* aTranslator) const final; + + template <class S> + void Record(S& aStream) const; + virtual void OutputSimpleEventInfo( + std::stringstream& aStringStream) const override; + + virtual std::string GetName() const override { return "Flush"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedFlush(S& aStream); +}; + +class RecordedDetachAllSnapshots + : public RecordedEventDerived<RecordedDetachAllSnapshots> { + public: + explicit RecordedDetachAllSnapshots() + : RecordedEventDerived(DETACHALLSNAPSHOTS) {} + + bool PlayEvent(Translator* aTranslator) const final; + + template <class S> + void Record(S& aStream) const; + virtual void OutputSimpleEventInfo( + std::stringstream& aStringStream) const override; + + virtual std::string GetName() const override { return "DetachAllSnapshots"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedDetachAllSnapshots(S& aStream); +}; + +class RecordedSnapshot : public RecordedEventDerived<RecordedSnapshot> { + public: + explicit RecordedSnapshot(ReferencePtr aRefPtr) + : RecordedEventDerived(SNAPSHOT), mRefPtr(aRefPtr) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "Snapshot"; } + + private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + + template <class S> + MOZ_IMPLICIT RecordedSnapshot(S& aStream); +}; + +class RecordedIntoLuminanceSource + : public RecordedEventDerived<RecordedIntoLuminanceSource> { + public: + RecordedIntoLuminanceSource(ReferencePtr aRefPtr, + LuminanceType aLuminanceType, float aOpacity) + : RecordedEventDerived(INTOLUMINANCE), + mRefPtr(aRefPtr), + mLuminanceType(aLuminanceType), + mOpacity(aOpacity) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "IntoLuminanceSource"; } + + private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + LuminanceType mLuminanceType; + float mOpacity; + + template <class S> + MOZ_IMPLICIT RecordedIntoLuminanceSource(S& aStream); +}; + +class RecordedExtractSubrect + : public RecordedEventDerived<RecordedExtractSubrect> { + public: + RecordedExtractSubrect(ReferencePtr aRefPtr, ReferencePtr aSourceSurface, + const IntRect& aSubrect) + : RecordedEventDerived(EXTRACTSUBRECT), + mRefPtr(aRefPtr), + mSourceSurface(aSourceSurface), + mSubrect(aSubrect) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "ExtractSubrect"; } + + private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + ReferencePtr mSourceSurface; + IntRect mSubrect; + + template <class S> + MOZ_IMPLICIT RecordedExtractSubrect(S& aStream); +}; + +class RecordedFontData : public RecordedEventDerived<RecordedFontData> { + public: + static void FontDataProc(const uint8_t* aData, uint32_t aSize, + uint32_t aIndex, void* aBaton) { + auto recordedFontData = static_cast<RecordedFontData*>(aBaton); + recordedFontData->SetFontData(aData, aSize, aIndex); + } + + explicit RecordedFontData(UnscaledFont* aUnscaledFont) + : RecordedEventDerived(FONTDATA), + mType(aUnscaledFont->GetType()), + mFontDetails() { + mGetFontFileDataSucceeded = + aUnscaledFont->GetFontFileData(&FontDataProc, this) && mData; + } + + virtual ~RecordedFontData(); + + bool IsValid() const { return mGetFontFileDataSucceeded; } + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "Font Data"; } + + void SetFontData(const uint8_t* aData, uint32_t aSize, uint32_t aIndex); + + bool GetFontDetails(RecordedFontDetails& fontDetails); + + private: + friend class RecordedEvent; + + FontType mType; + uint8_t* mData = nullptr; + RecordedFontDetails mFontDetails; + + bool mGetFontFileDataSucceeded; + + template <class S> + MOZ_IMPLICIT RecordedFontData(S& aStream); +}; + +class RecordedFontDescriptor + : public RecordedEventDerived<RecordedFontDescriptor> { + public: + static void FontDescCb(const uint8_t* aData, uint32_t aSize, uint32_t aIndex, + void* aBaton) { + auto recordedFontDesc = static_cast<RecordedFontDescriptor*>(aBaton); + recordedFontDesc->SetFontDescriptor(aData, aSize, aIndex); + } + + explicit RecordedFontDescriptor(UnscaledFont* aUnscaledFont) + : RecordedEventDerived(FONTDESC), + mType(aUnscaledFont->GetType()), + mIndex(0), + mRefPtr(aUnscaledFont) { + mHasDesc = aUnscaledFont->GetFontDescriptor(FontDescCb, this); + } + + virtual ~RecordedFontDescriptor(); + + bool IsValid() const { return mHasDesc; } + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "Font Desc"; } + + private: + friend class RecordedEvent; + + void SetFontDescriptor(const uint8_t* aData, uint32_t aSize, uint32_t aIndex); + + bool mHasDesc; + + FontType mType; + std::vector<uint8_t> mData; + uint32_t mIndex; + ReferencePtr mRefPtr; + + template <class S> + MOZ_IMPLICIT RecordedFontDescriptor(S& aStream); +}; + +class RecordedUnscaledFontCreation + : public RecordedEventDerived<RecordedUnscaledFontCreation> { + public: + static void FontInstanceDataProc(const uint8_t* aData, uint32_t aSize, + void* aBaton) { + auto recordedUnscaledFontCreation = + static_cast<RecordedUnscaledFontCreation*>(aBaton); + recordedUnscaledFontCreation->SetFontInstanceData(aData, aSize); + } + + RecordedUnscaledFontCreation(UnscaledFont* aUnscaledFont, + RecordedFontDetails aFontDetails) + : RecordedEventDerived(UNSCALEDFONTCREATION), + mRefPtr(aUnscaledFont), + mFontDataKey(aFontDetails.fontDataKey), + mIndex(aFontDetails.index) { + aUnscaledFont->GetFontInstanceData(FontInstanceDataProc, this); + } + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "UnscaledFont Creation"; } + + void SetFontInstanceData(const uint8_t* aData, uint32_t aSize); + + private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + uint64_t mFontDataKey; + uint32_t mIndex; + std::vector<uint8_t> mInstanceData; + + template <class S> + MOZ_IMPLICIT RecordedUnscaledFontCreation(S& aStream); +}; + +class RecordedUnscaledFontDestruction + : public RecordedEventDerived<RecordedUnscaledFontDestruction> { + public: + MOZ_IMPLICIT RecordedUnscaledFontDestruction(ReferencePtr aRefPtr) + : RecordedEventDerived(UNSCALEDFONTDESTRUCTION), mRefPtr(aRefPtr) {} + + bool PlayEvent(Translator* aTranslator) const override; + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "UnscaledFont Destruction"; } + + private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + + template <class S> + MOZ_IMPLICIT RecordedUnscaledFontDestruction(S& aStream); +}; + +class RecordedScaledFontCreation + : public RecordedEventDerived<RecordedScaledFontCreation> { + public: + static void FontInstanceDataProc(const uint8_t* aData, uint32_t aSize, + const FontVariation* aVariations, + uint32_t aNumVariations, void* aBaton) { + auto recordedScaledFontCreation = + static_cast<RecordedScaledFontCreation*>(aBaton); + recordedScaledFontCreation->SetFontInstanceData(aData, aSize, aVariations, + aNumVariations); + } + + RecordedScaledFontCreation(ScaledFont* aScaledFont, + UnscaledFont* aUnscaledFont) + : RecordedEventDerived(SCALEDFONTCREATION), + mRefPtr(aScaledFont), + mUnscaledFont(aUnscaledFont), + mGlyphSize(aScaledFont->GetSize()) { + aScaledFont->GetFontInstanceData(FontInstanceDataProc, this); + } + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "ScaledFont Creation"; } + + void SetFontInstanceData(const uint8_t* aData, uint32_t aSize, + const FontVariation* aVariations, + uint32_t aNumVariations); + + private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + ReferencePtr mUnscaledFont; + Float mGlyphSize; + std::vector<uint8_t> mInstanceData; + std::vector<FontVariation> mVariations; + + template <class S> + MOZ_IMPLICIT RecordedScaledFontCreation(S& aStream); +}; + +class RecordedScaledFontDestruction + : public RecordedEventDerived<RecordedScaledFontDestruction> { + public: + MOZ_IMPLICIT RecordedScaledFontDestruction(ReferencePtr aRefPtr) + : RecordedEventDerived(SCALEDFONTDESTRUCTION), mRefPtr(aRefPtr) {} + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "ScaledFont Destruction"; } + + private: + friend class RecordedEvent; + + ReferencePtr mRefPtr; + + template <class S> + MOZ_IMPLICIT RecordedScaledFontDestruction(S& aStream); +}; + +class RecordedMaskSurface : public RecordedEventDerived<RecordedMaskSurface> { + public: + RecordedMaskSurface(const Pattern& aPattern, ReferencePtr aRefMask, + const Point& aOffset, const DrawOptions& aOptions) + : RecordedEventDerived(MASKSURFACE), + mPattern(), + mRefMask(aRefMask), + mOffset(aOffset), + mOptions(aOptions) { + StorePattern(mPattern, aPattern); + } + + bool PlayEvent(Translator* aTranslator) const override; + + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "MaskSurface"; } + + private: + friend class RecordedEvent; + + template <class S> + MOZ_IMPLICIT RecordedMaskSurface(S& aStream); + + PatternStorage mPattern; + ReferencePtr mRefMask; + Point mOffset; + DrawOptions mOptions; +}; + +class RecordedFilterNodeSetAttribute + : public RecordedEventDerived<RecordedFilterNodeSetAttribute> { + public: + enum ArgType { + ARGTYPE_UINT32, + ARGTYPE_BOOL, + ARGTYPE_FLOAT, + ARGTYPE_SIZE, + ARGTYPE_INTSIZE, + ARGTYPE_INTPOINT, + ARGTYPE_RECT, + ARGTYPE_INTRECT, + ARGTYPE_POINT, + ARGTYPE_MATRIX, + ARGTYPE_MATRIX5X4, + ARGTYPE_POINT3D, + ARGTYPE_COLOR, + ARGTYPE_FLOAT_ARRAY + }; + + template <typename T> + RecordedFilterNodeSetAttribute(FilterNode* aNode, uint32_t aIndex, + T aArgument, ArgType aArgType) + : RecordedEventDerived(FILTERNODESETATTRIBUTE), + mNode(aNode), + mIndex(aIndex), + mArgType(aArgType) { + mPayload.resize(sizeof(T)); + memcpy(&mPayload.front(), &aArgument, sizeof(T)); + } + + RecordedFilterNodeSetAttribute(FilterNode* aNode, uint32_t aIndex, + const Float* aFloat, uint32_t aSize) + : RecordedEventDerived(FILTERNODESETATTRIBUTE), + mNode(aNode), + mIndex(aIndex), + mArgType(ARGTYPE_FLOAT_ARRAY) { + mPayload.resize(sizeof(Float) * aSize); + memcpy(&mPayload.front(), aFloat, sizeof(Float) * aSize); + } + + bool PlayEvent(Translator* aTranslator) const override; + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "SetAttribute"; } + + private: + friend class RecordedEvent; + + ReferencePtr mNode; + + uint32_t mIndex; + ArgType mArgType; + std::vector<uint8_t> mPayload; + + template <class S> + MOZ_IMPLICIT RecordedFilterNodeSetAttribute(S& aStream); +}; + +class RecordedFilterNodeSetInput + : public RecordedEventDerived<RecordedFilterNodeSetInput> { + public: + RecordedFilterNodeSetInput(FilterNode* aNode, uint32_t aIndex, + FilterNode* aInputNode) + : RecordedEventDerived(FILTERNODESETINPUT), + mNode(aNode), + mIndex(aIndex), + mInputFilter(aInputNode), + mInputSurface(nullptr) {} + + RecordedFilterNodeSetInput(FilterNode* aNode, uint32_t aIndex, + SourceSurface* aInputSurface) + : RecordedEventDerived(FILTERNODESETINPUT), + mNode(aNode), + mIndex(aIndex), + mInputFilter(nullptr), + mInputSurface(aInputSurface) {} + + bool PlayEvent(Translator* aTranslator) const override; + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "SetInput"; } + + private: + friend class RecordedEvent; + + ReferencePtr mNode; + uint32_t mIndex; + ReferencePtr mInputFilter; + ReferencePtr mInputSurface; + + template <class S> + MOZ_IMPLICIT RecordedFilterNodeSetInput(S& aStream); +}; + +class RecordedLink : public RecordedEventDerived<RecordedLink> { + public: + RecordedLink(const char* aDestination, const Rect& aRect) + : RecordedEventDerived(LINK), mDestination(aDestination), mRect(aRect) {} + + bool PlayEvent(Translator* aTranslator) const override; + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "Link"; } + + private: + friend class RecordedEvent; + + std::string mDestination; + Rect mRect; + + template <class S> + MOZ_IMPLICIT RecordedLink(S& aStream); +}; + +class RecordedDestination : public RecordedEventDerived<RecordedDestination> { + public: + RecordedDestination(const char* aDestination, const Point& aPoint) + : RecordedEventDerived(DESTINATION), + mDestination(aDestination), + mPoint(aPoint) {} + + bool PlayEvent(Translator* aTranslator) const override; + template <class S> + void Record(S& aStream) const; + void OutputSimpleEventInfo(std::stringstream& aStringStream) const override; + + std::string GetName() const override { return "Destination"; } + + private: + friend class RecordedEvent; + + std::string mDestination; + Point mPoint; + + template <class S> + MOZ_IMPLICIT RecordedDestination(S& aStream); +}; + +static std::string NameFromBackend(BackendType aType) { + switch (aType) { + case BackendType::NONE: + return "None"; + case BackendType::DIRECT2D: + return "Direct2D"; + default: + return "Unknown"; + } +} + +template <class S> +void RecordedEvent::RecordPatternData(S& aStream, + const PatternStorage& aPattern) const { + WriteElement(aStream, aPattern.mType); + + switch (aPattern.mType) { + case PatternType::COLOR: { + WriteElement(aStream, *reinterpret_cast<const ColorPatternStorage*>( + &aPattern.mStorage)); + return; + } + case PatternType::LINEAR_GRADIENT: { + WriteElement(aStream, + *reinterpret_cast<const LinearGradientPatternStorage*>( + &aPattern.mStorage)); + return; + } + case PatternType::RADIAL_GRADIENT: { + WriteElement(aStream, + *reinterpret_cast<const RadialGradientPatternStorage*>( + &aPattern.mStorage)); + return; + } + case PatternType::CONIC_GRADIENT: { + WriteElement(aStream, + *reinterpret_cast<const ConicGradientPatternStorage*>( + &aPattern.mStorage)); + return; + } + case PatternType::SURFACE: { + WriteElement(aStream, *reinterpret_cast<const SurfacePatternStorage*>( + &aPattern.mStorage)); + return; + } + default: + return; + } +} + +template <class S> +void RecordedEvent::ReadPatternData(S& aStream, + PatternStorage& aPattern) const { + ReadElementConstrained(aStream, aPattern.mType, PatternType::COLOR, + kHighestPatternType); + + switch (aPattern.mType) { + case PatternType::COLOR: { + ReadElement(aStream, + *reinterpret_cast<ColorPatternStorage*>(&aPattern.mStorage)); + return; + } + case PatternType::LINEAR_GRADIENT: { + ReadElement(aStream, *reinterpret_cast<LinearGradientPatternStorage*>( + &aPattern.mStorage)); + return; + } + case PatternType::RADIAL_GRADIENT: { + ReadElement(aStream, *reinterpret_cast<RadialGradientPatternStorage*>( + &aPattern.mStorage)); + return; + } + case PatternType::CONIC_GRADIENT: { + ReadElement(aStream, *reinterpret_cast<ConicGradientPatternStorage*>( + &aPattern.mStorage)); + return; + } + case PatternType::SURFACE: { + SurfacePatternStorage* sps = + reinterpret_cast<SurfacePatternStorage*>(&aPattern.mStorage); + ReadElement(aStream, *sps); + if (sps->mExtend < ExtendMode::CLAMP || + sps->mExtend > ExtendMode::REFLECT) { + aStream.SetIsBad(); + return; + } + + if (sps->mSamplingFilter < SamplingFilter::GOOD || + sps->mSamplingFilter >= SamplingFilter::SENTINEL) { + aStream.SetIsBad(); + } + return; + } + default: + return; + } +} + +inline void RecordedEvent::StorePattern(PatternStorage& aDestination, + const Pattern& aSource) const { + aDestination.mType = aSource.GetType(); + + switch (aSource.GetType()) { + case PatternType::COLOR: { + reinterpret_cast<ColorPatternStorage*>(&aDestination.mStorage)->mColor = + static_cast<const ColorPattern*>(&aSource)->mColor; + return; + } + case PatternType::LINEAR_GRADIENT: { + LinearGradientPatternStorage* store = + reinterpret_cast<LinearGradientPatternStorage*>( + &aDestination.mStorage); + const LinearGradientPattern* pat = + static_cast<const LinearGradientPattern*>(&aSource); + store->mBegin = pat->mBegin; + store->mEnd = pat->mEnd; + store->mMatrix = pat->mMatrix; + store->mStops = pat->mStops.get(); + return; + } + case PatternType::RADIAL_GRADIENT: { + RadialGradientPatternStorage* store = + reinterpret_cast<RadialGradientPatternStorage*>( + &aDestination.mStorage); + const RadialGradientPattern* pat = + static_cast<const RadialGradientPattern*>(&aSource); + store->mCenter1 = pat->mCenter1; + store->mCenter2 = pat->mCenter2; + store->mRadius1 = pat->mRadius1; + store->mRadius2 = pat->mRadius2; + store->mMatrix = pat->mMatrix; + store->mStops = pat->mStops.get(); + return; + } + case PatternType::CONIC_GRADIENT: { + ConicGradientPatternStorage* store = + reinterpret_cast<ConicGradientPatternStorage*>( + &aDestination.mStorage); + const ConicGradientPattern* pat = + static_cast<const ConicGradientPattern*>(&aSource); + store->mCenter = pat->mCenter; + store->mAngle = pat->mAngle; + store->mStartOffset = pat->mStartOffset; + store->mEndOffset = pat->mEndOffset; + store->mMatrix = pat->mMatrix; + store->mStops = pat->mStops.get(); + return; + } + case PatternType::SURFACE: { + SurfacePatternStorage* store = + reinterpret_cast<SurfacePatternStorage*>(&aDestination.mStorage); + const SurfacePattern* pat = static_cast<const SurfacePattern*>(&aSource); + store->mExtend = pat->mExtendMode; + store->mSamplingFilter = pat->mSamplingFilter; + store->mMatrix = pat->mMatrix; + store->mSurface = pat->mSurface; + store->mSamplingRect = pat->mSamplingRect; + return; + } + } +} + +template <class S> +void RecordedEvent::RecordStrokeOptions( + S& aStream, const StrokeOptions& aStrokeOptions) const { + JoinStyle joinStyle = aStrokeOptions.mLineJoin; + CapStyle capStyle = aStrokeOptions.mLineCap; + + WriteElement(aStream, uint64_t(aStrokeOptions.mDashLength)); + WriteElement(aStream, aStrokeOptions.mLineWidth); + WriteElement(aStream, aStrokeOptions.mMiterLimit); + WriteElement(aStream, joinStyle); + WriteElement(aStream, capStyle); + + if (!aStrokeOptions.mDashPattern) { + return; + } + + WriteElement(aStream, aStrokeOptions.mDashOffset); + aStream.write((char*)aStrokeOptions.mDashPattern, + sizeof(Float) * aStrokeOptions.mDashLength); +} + +template <class S> +void RecordedEvent::ReadStrokeOptions(S& aStream, + StrokeOptions& aStrokeOptions) { + uint64_t dashLength; + JoinStyle joinStyle; + CapStyle capStyle; + + ReadElement(aStream, dashLength); + ReadElement(aStream, aStrokeOptions.mLineWidth); + ReadElement(aStream, aStrokeOptions.mMiterLimit); + ReadElementConstrained(aStream, joinStyle, JoinStyle::BEVEL, + JoinStyle::MITER_OR_BEVEL); + ReadElementConstrained(aStream, capStyle, CapStyle::BUTT, CapStyle::SQUARE); + // On 32 bit we truncate the value of dashLength. + // See also bug 811850 for history. + aStrokeOptions.mDashLength = size_t(dashLength); + aStrokeOptions.mLineJoin = joinStyle; + aStrokeOptions.mLineCap = capStyle; + + if (!aStrokeOptions.mDashLength || !aStream.good()) { + return; + } + + ReadElement(aStream, aStrokeOptions.mDashOffset); + + mDashPatternStorage.resize(aStrokeOptions.mDashLength); + aStrokeOptions.mDashPattern = &mDashPatternStorage.front(); + aStream.read((char*)aStrokeOptions.mDashPattern, + sizeof(Float) * aStrokeOptions.mDashLength); +} + +template <class S> +static void ReadDrawOptions(S& aStream, DrawOptions& aDrawOptions) { + ReadElement(aStream, aDrawOptions); + if (aDrawOptions.mAntialiasMode < AntialiasMode::NONE || + aDrawOptions.mAntialiasMode > AntialiasMode::DEFAULT) { + aStream.SetIsBad(); + return; + } + + if (aDrawOptions.mCompositionOp < CompositionOp::OP_CLEAR || + aDrawOptions.mCompositionOp > CompositionOp::OP_COUNT) { + aStream.SetIsBad(); + } +} + +template <class S> +static void ReadDrawSurfaceOptions(S& aStream, + DrawSurfaceOptions& aDrawSurfaceOptions) { + ReadElement(aStream, aDrawSurfaceOptions); + if (aDrawSurfaceOptions.mSamplingFilter < SamplingFilter::GOOD || + aDrawSurfaceOptions.mSamplingFilter >= SamplingFilter::SENTINEL) { + aStream.SetIsBad(); + return; + } + + if (aDrawSurfaceOptions.mSamplingBounds < SamplingBounds::UNBOUNDED || + aDrawSurfaceOptions.mSamplingBounds > SamplingBounds::BOUNDED) { + aStream.SetIsBad(); + } +} + +inline void RecordedEvent::OutputSimplePatternInfo( + const PatternStorage& aStorage, std::stringstream& aOutput) const { + switch (aStorage.mType) { + case PatternType::COLOR: { + const DeviceColor color = + reinterpret_cast<const ColorPatternStorage*>(&aStorage.mStorage) + ->mColor; + aOutput << "DeviceColor: (" << color.r << ", " << color.g << ", " + << color.b << ", " << color.a << ")"; + return; + } + case PatternType::LINEAR_GRADIENT: { + const LinearGradientPatternStorage* store = + reinterpret_cast<const LinearGradientPatternStorage*>( + &aStorage.mStorage); + + aOutput << "LinearGradient (" << store->mBegin.x << ", " + << store->mBegin.y << ") - (" << store->mEnd.x << ", " + << store->mEnd.y << ") Stops: " << store->mStops; + return; + } + case PatternType::RADIAL_GRADIENT: { + const RadialGradientPatternStorage* store = + reinterpret_cast<const RadialGradientPatternStorage*>( + &aStorage.mStorage); + aOutput << "RadialGradient (Center 1: (" << store->mCenter1.x << ", " + << store->mCenter2.y << ") Radius 2: " << store->mRadius2; + return; + } + case PatternType::CONIC_GRADIENT: { + const ConicGradientPatternStorage* store = + reinterpret_cast<const ConicGradientPatternStorage*>( + &aStorage.mStorage); + aOutput << "ConicGradient (Center: (" << store->mCenter.x << ", " + << store->mCenter.y << ") Angle: " << store->mAngle + << " Range:" << store->mStartOffset << " - " << store->mEndOffset; + return; + } + case PatternType::SURFACE: { + const SurfacePatternStorage* store = + reinterpret_cast<const SurfacePatternStorage*>(&aStorage.mStorage); + aOutput << "Surface (0x" << store->mSurface << ")"; + return; + } + } +} + +inline bool RecordedDrawTargetCreation::PlayEvent( + Translator* aTranslator) const { + RefPtr<DrawTarget> newDT = + aTranslator->CreateDrawTarget(mRefPtr, mRect.Size(), mFormat); + + // If we couldn't create a DrawTarget this will probably cause us to crash + // with nullptr later in the playback, so return false to abort. + if (!newDT) { + return false; + } + + if (mHasExistingData) { + Rect dataRect(0, 0, mExistingData->GetSize().width, + mExistingData->GetSize().height); + newDT->DrawSurface(mExistingData, dataRect, dataRect); + } + + return true; +} + +template <class S> +void RecordedDrawTargetCreation::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mBackendType); + WriteElement(aStream, mRect); + WriteElement(aStream, mFormat); + WriteElement(aStream, mHasExistingData); + + if (mHasExistingData) { + MOZ_ASSERT(mExistingData); + MOZ_ASSERT(mExistingData->GetSize() == mRect.Size()); + RefPtr<DataSourceSurface> dataSurf = mExistingData->GetDataSurface(); + + DataSourceSurface::ScopedMap map(dataSurf, DataSourceSurface::READ); + for (int y = 0; y < mRect.height; y++) { + aStream.write((const char*)map.GetData() + y * map.GetStride(), + BytesPerPixel(mFormat) * mRect.width); + } + } +} + +template <class S> +RecordedDrawTargetCreation::RecordedDrawTargetCreation(S& aStream) + : RecordedEventDerived(DRAWTARGETCREATION), mExistingData(nullptr) { + ReadElement(aStream, mRefPtr); + ReadElementConstrained(aStream, mBackendType, BackendType::NONE, + BackendType::WEBRENDER_TEXT); + ReadElement(aStream, mRect); + ReadElementConstrained(aStream, mFormat, SurfaceFormat::A8R8G8B8_UINT32, + SurfaceFormat::UNKNOWN); + ReadElement(aStream, mHasExistingData); + + if (mHasExistingData) { + RefPtr<DataSourceSurface> dataSurf = + Factory::CreateDataSourceSurface(mRect.Size(), mFormat); + if (!dataSurf) { + gfxWarning() + << "RecordedDrawTargetCreation had to reset mHasExistingData"; + mHasExistingData = false; + return; + } + + DataSourceSurface::ScopedMap map(dataSurf, DataSourceSurface::READ); + for (int y = 0; y < mRect.height; y++) { + aStream.read((char*)map.GetData() + y * map.GetStride(), + BytesPerPixel(mFormat) * mRect.width); + } + mExistingData = dataSurf; + } +} + +inline void RecordedDrawTargetCreation::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr << "] DrawTarget Creation (Type: " + << NameFromBackend(mBackendType) << ", Size: " << mRect.width + << "x" << mRect.height << ")"; +} + +inline bool RecordedDrawTargetDestruction::PlayEvent( + Translator* aTranslator) const { + aTranslator->RemoveDrawTarget(mRefPtr); + return true; +} + +template <class S> +void RecordedDrawTargetDestruction::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); +} + +template <class S> +RecordedDrawTargetDestruction::RecordedDrawTargetDestruction(S& aStream) + : RecordedEventDerived(DRAWTARGETDESTRUCTION) { + ReadElement(aStream, mRefPtr); +} + +inline void RecordedDrawTargetDestruction::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr << "] DrawTarget Destruction"; +} + +inline bool RecordedSetCurrentDrawTarget::PlayEvent( + Translator* aTranslator) const { + return aTranslator->SetCurrentDrawTarget(mRefPtr); +} + +template <class S> +void RecordedSetCurrentDrawTarget::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); +} + +template <class S> +RecordedSetCurrentDrawTarget::RecordedSetCurrentDrawTarget(S& aStream) + : RecordedEventDerived(SETCURRENTDRAWTARGET) { + ReadElement(aStream, mRefPtr); +} + +inline void RecordedSetCurrentDrawTarget::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr << "] SetCurrentDrawTarget"; +} + +inline bool RecordedCreateSimilarDrawTarget::PlayEvent( + Translator* aTranslator) const { + DrawTarget* drawTarget = aTranslator->GetCurrentDrawTarget(); + if (!drawTarget) { + return false; + } + + RefPtr<DrawTarget> newDT = + drawTarget->CreateSimilarDrawTarget(mSize, mFormat); + + // If we couldn't create a DrawTarget this will probably cause us to crash + // with nullptr later in the playback, so return false to abort. + if (!newDT) { + return false; + } + + aTranslator->AddDrawTarget(mRefPtr, newDT); + return true; +} + +template <class S> +void RecordedCreateSimilarDrawTarget::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mSize); + WriteElement(aStream, mFormat); +} + +template <class S> +RecordedCreateSimilarDrawTarget::RecordedCreateSimilarDrawTarget(S& aStream) + : RecordedEventDerived(CREATESIMILARDRAWTARGET) { + ReadElement(aStream, mRefPtr); + ReadElement(aStream, mSize); + ReadElementConstrained(aStream, mFormat, SurfaceFormat::A8R8G8B8_UINT32, + SurfaceFormat::UNKNOWN); +} + +inline void RecordedCreateSimilarDrawTarget::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr + << "] CreateSimilarDrawTarget (Size: " << mSize.width << "x" + << mSize.height << ")"; +} + +inline bool RecordedCreateDrawTargetForFilter::PlayEvent( + Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + IntRect baseRect = dt->GetRect(); + + auto maxRect = IntRect(IntPoint(0, 0), mMaxSize); + + auto clone = dt->GetTransform(); + bool invertible = clone.Invert(); + // mSourceRect is in filter space. The filter outputs from mSourceRect need + // to be drawn at mDestPoint in user space. + Rect userSpaceSource = Rect(mDestPoint, mSourceRect.Size()); + if (invertible) { + // Try to reduce the source rect so that it's not much bigger + // than the draw target. The result is not minimal. Examples + // are left as an exercise for the reader. + auto destRect = IntRectToRect(baseRect); + Rect userSpaceBounds = clone.TransformBounds(destRect); + userSpaceSource = userSpaceSource.Intersect(userSpaceBounds); + } + + // Compute how much we moved the top-left of the source rect by, and use that + // to compute the new dest point, and move our intersected source rect back + // into the (new) filter space. + Point shift = userSpaceSource.TopLeft() - mDestPoint; + Rect filterSpaceSource = + Rect(mSourceRect.TopLeft() + shift, userSpaceSource.Size()); + + baseRect = RoundedOut(filterSpaceSource); + FilterNode* filter = aTranslator->LookupFilterNode(mFilter); + if (!filter) { + return false; + } + + IntRect transformedRect = filter->MapRectToSource( + baseRect, maxRect, aTranslator->LookupFilterNode(mSource)); + + // Intersect with maxRect to make sure we didn't end up with something bigger + transformedRect = transformedRect.Intersect(maxRect); + + // If we end up with an empty rect make it 1x1 so that things don't break. + if (transformedRect.IsEmpty()) { + transformedRect = IntRect(0, 0, 1, 1); + } + + RefPtr<DrawTarget> newDT = + dt->CreateSimilarDrawTarget(transformedRect.Size(), mFormat); + if (!newDT) { + return false; + } + newDT = + gfx::Factory::CreateOffsetDrawTarget(newDT, transformedRect.TopLeft()); + + // If we couldn't create a DrawTarget this will probably cause us to crash + // with nullptr later in the playback, so return false to abort. + if (!newDT) { + return false; + } + + aTranslator->AddDrawTarget(mRefPtr, newDT); + return true; +} + +inline bool RecordedCreateClippedDrawTarget::PlayEvent( + Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + RefPtr<DrawTarget> newDT = dt->CreateClippedDrawTarget(mBounds, mFormat); + + // If we couldn't create a DrawTarget this will probably cause us to crash + // with nullptr later in the playback, so return false to abort. + if (!newDT) { + return false; + } + + aTranslator->AddDrawTarget(mRefPtr, newDT); + return true; +} + +template <class S> +void RecordedCreateClippedDrawTarget::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mBounds); + WriteElement(aStream, mFormat); +} + +template <class S> +RecordedCreateClippedDrawTarget::RecordedCreateClippedDrawTarget(S& aStream) + : RecordedEventDerived(CREATECLIPPEDDRAWTARGET) { + ReadElement(aStream, mRefPtr); + ReadElement(aStream, mBounds); + ReadElementConstrained(aStream, mFormat, SurfaceFormat::A8R8G8B8_UINT32, + SurfaceFormat::UNKNOWN); +} + +inline void RecordedCreateClippedDrawTarget::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr << "] CreateClippedDrawTarget ()"; +} + +template <class S> +void RecordedCreateDrawTargetForFilter::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mMaxSize); + WriteElement(aStream, mFormat); + WriteElement(aStream, mFilter); + WriteElement(aStream, mSource); + WriteElement(aStream, mSourceRect); + WriteElement(aStream, mDestPoint); +} + +template <class S> +RecordedCreateDrawTargetForFilter::RecordedCreateDrawTargetForFilter(S& aStream) + : RecordedEventDerived(CREATEDRAWTARGETFORFILTER) { + ReadElement(aStream, mRefPtr); + ReadElement(aStream, mMaxSize); + ReadElementConstrained(aStream, mFormat, SurfaceFormat::A8R8G8B8_UINT32, + SurfaceFormat::UNKNOWN); + ReadElement(aStream, mFilter); + ReadElement(aStream, mSource); + ReadElement(aStream, mSourceRect); + ReadElement(aStream, mDestPoint); +} + +inline void RecordedCreateDrawTargetForFilter::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr << "] CreateDrawTargetForFilter ()"; +} + +struct GenericPattern { + GenericPattern(const PatternStorage& aStorage, Translator* aTranslator) + : mPattern(nullptr), mTranslator(aTranslator) { + mStorage = const_cast<PatternStorage*>(&aStorage); + } + + ~GenericPattern() { + if (mPattern) { + mPattern->~Pattern(); + } + } + + operator Pattern*() { + switch (mStorage->mType) { + case PatternType::COLOR: + return new (mColPat) ColorPattern( + reinterpret_cast<ColorPatternStorage*>(&mStorage->mStorage) + ->mColor); + case PatternType::SURFACE: { + SurfacePatternStorage* storage = + reinterpret_cast<SurfacePatternStorage*>(&mStorage->mStorage); + mPattern = new (mSurfPat) + SurfacePattern(mTranslator->LookupSourceSurface(storage->mSurface), + storage->mExtend, storage->mMatrix, + storage->mSamplingFilter, storage->mSamplingRect); + return mPattern; + } + case PatternType::LINEAR_GRADIENT: { + LinearGradientPatternStorage* storage = + reinterpret_cast<LinearGradientPatternStorage*>( + &mStorage->mStorage); + mPattern = new (mLinGradPat) LinearGradientPattern( + storage->mBegin, storage->mEnd, + storage->mStops ? mTranslator->LookupGradientStops(storage->mStops) + : nullptr, + storage->mMatrix); + return mPattern; + } + case PatternType::RADIAL_GRADIENT: { + RadialGradientPatternStorage* storage = + reinterpret_cast<RadialGradientPatternStorage*>( + &mStorage->mStorage); + mPattern = new (mRadGradPat) RadialGradientPattern( + storage->mCenter1, storage->mCenter2, storage->mRadius1, + storage->mRadius2, + storage->mStops ? mTranslator->LookupGradientStops(storage->mStops) + : nullptr, + storage->mMatrix); + return mPattern; + } + case PatternType::CONIC_GRADIENT: { + ConicGradientPatternStorage* storage = + reinterpret_cast<ConicGradientPatternStorage*>(&mStorage->mStorage); + mPattern = new (mConGradPat) ConicGradientPattern( + storage->mCenter, storage->mAngle, storage->mStartOffset, + storage->mEndOffset, + storage->mStops ? mTranslator->LookupGradientStops(storage->mStops) + : nullptr, + storage->mMatrix); + return mPattern; + } + default: + return new (mColPat) ColorPattern(DeviceColor()); + } + + return mPattern; + } + + union { + char mColPat[sizeof(ColorPattern)]; + char mLinGradPat[sizeof(LinearGradientPattern)]; + char mRadGradPat[sizeof(RadialGradientPattern)]; + char mConGradPat[sizeof(ConicGradientPattern)]; + char mSurfPat[sizeof(SurfacePattern)]; + }; + + PatternStorage* mStorage; + Pattern* mPattern; + Translator* mTranslator; +}; + +inline bool RecordedFillRect::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + dt->FillRect(mRect, *GenericPattern(mPattern, aTranslator), mOptions); + return true; +} + +template <class S> +void RecordedFillRect::Record(S& aStream) const { + WriteElement(aStream, mRect); + WriteElement(aStream, mOptions); + RecordPatternData(aStream, mPattern); +} + +template <class S> +RecordedFillRect::RecordedFillRect(S& aStream) + : RecordedEventDerived(FILLRECT) { + ReadElement(aStream, mRect); + ReadDrawOptions(aStream, mOptions); + ReadPatternData(aStream, mPattern); +} + +inline void RecordedFillRect::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "FillRect (" << mRect.X() << ", " << mRect.Y() << " - " + << mRect.Width() << " x " << mRect.Height() << ") "; + OutputSimplePatternInfo(mPattern, aStringStream); +} + +inline bool RecordedStrokeRect::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + dt->StrokeRect(mRect, *GenericPattern(mPattern, aTranslator), mStrokeOptions, + mOptions); + return true; +} + +template <class S> +void RecordedStrokeRect::Record(S& aStream) const { + WriteElement(aStream, mRect); + WriteElement(aStream, mOptions); + RecordPatternData(aStream, mPattern); + RecordStrokeOptions(aStream, mStrokeOptions); +} + +template <class S> +RecordedStrokeRect::RecordedStrokeRect(S& aStream) + : RecordedEventDerived(STROKERECT) { + ReadElement(aStream, mRect); + ReadDrawOptions(aStream, mOptions); + ReadPatternData(aStream, mPattern); + ReadStrokeOptions(aStream, mStrokeOptions); +} + +inline void RecordedStrokeRect::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "StrokeRect (" << mRect.X() << ", " << mRect.Y() << " - " + << mRect.Width() << " x " << mRect.Height() + << ") LineWidth: " << mStrokeOptions.mLineWidth << "px "; + OutputSimplePatternInfo(mPattern, aStringStream); +} + +inline bool RecordedStrokeLine::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + dt->StrokeLine(mBegin, mEnd, *GenericPattern(mPattern, aTranslator), + mStrokeOptions, mOptions); + return true; +} + +template <class S> +void RecordedStrokeLine::Record(S& aStream) const { + WriteElement(aStream, mBegin); + WriteElement(aStream, mEnd); + WriteElement(aStream, mOptions); + RecordPatternData(aStream, mPattern); + RecordStrokeOptions(aStream, mStrokeOptions); +} + +template <class S> +RecordedStrokeLine::RecordedStrokeLine(S& aStream) + : RecordedEventDerived(STROKELINE) { + ReadElement(aStream, mBegin); + ReadElement(aStream, mEnd); + ReadDrawOptions(aStream, mOptions); + ReadPatternData(aStream, mPattern); + ReadStrokeOptions(aStream, mStrokeOptions); +} + +inline void RecordedStrokeLine::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "StrokeLine (" << mBegin.x << ", " << mBegin.y << " - " + << mEnd.x << ", " << mEnd.y + << ") LineWidth: " << mStrokeOptions.mLineWidth << "px "; + OutputSimplePatternInfo(mPattern, aStringStream); +} + +inline bool RecordedStrokeCircle::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + dt->StrokeCircle(mCircle.origin, mCircle.radius, + *GenericPattern(mPattern, aTranslator), mStrokeOptions, + mOptions); + return true; +} + +template <class S> +void RecordedStrokeCircle::Record(S& aStream) const { + WriteElement(aStream, mCircle); + WriteElement(aStream, mOptions); + RecordPatternData(aStream, mPattern); + RecordStrokeOptions(aStream, mStrokeOptions); +} + +template <class S> +RecordedStrokeCircle::RecordedStrokeCircle(S& aStream) + : RecordedEventDerived(STROKECIRCLE) { + ReadElement(aStream, mCircle); + ReadDrawOptions(aStream, mOptions); + ReadPatternData(aStream, mPattern); + ReadStrokeOptions(aStream, mStrokeOptions); +} + +inline void RecordedStrokeCircle::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "StrokeCircle (" << mCircle.origin.x << ", " + << mCircle.origin.y << " - " << mCircle.radius + << ") LineWidth: " << mStrokeOptions.mLineWidth << "px "; + OutputSimplePatternInfo(mPattern, aStringStream); +} + +inline bool RecordedFill::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + dt->Fill(aTranslator->LookupPath(mPath), + *GenericPattern(mPattern, aTranslator), mOptions); + return true; +} + +template <class S> +RecordedFill::RecordedFill(S& aStream) : RecordedEventDerived(FILL) { + ReadElement(aStream, mPath); + ReadDrawOptions(aStream, mOptions); + ReadPatternData(aStream, mPattern); +} + +template <class S> +void RecordedFill::Record(S& aStream) const { + WriteElement(aStream, mPath); + WriteElement(aStream, mOptions); + RecordPatternData(aStream, mPattern); +} + +inline void RecordedFill::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "Fill (" << mPath << ") "; + OutputSimplePatternInfo(mPattern, aStringStream); +} + +inline bool RecordedFillCircle::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + dt->FillCircle(mCircle.origin, mCircle.radius, + *GenericPattern(mPattern, aTranslator), mOptions); + return true; +} + +template <class S> +void RecordedFillCircle::Record(S& aStream) const { + WriteElement(aStream, mCircle); + WriteElement(aStream, mOptions); + RecordPatternData(aStream, mPattern); +} + +template <class S> +RecordedFillCircle::RecordedFillCircle(S& aStream) + : RecordedEventDerived(FILLCIRCLE) { + ReadElement(aStream, mCircle); + ReadDrawOptions(aStream, mOptions); + ReadPatternData(aStream, mPattern); +} + +inline void RecordedFillCircle::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "FillCircle (" << mCircle.origin.x << ", " + << mCircle.origin.y << " - " << mCircle.radius << ")"; + OutputSimplePatternInfo(mPattern, aStringStream); +} + +template <class T> +inline RecordedDrawGlyphs<T>::~RecordedDrawGlyphs() { + delete[] mGlyphs; +} + +template <class T> +inline bool RecordedDrawGlyphs<T>::PlayEvent(Translator* aTranslator) const { + if (mNumGlyphs > 0 && !mGlyphs) { + // Glyph allocation failed + return false; + } + + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + ScaledFont* scaledFont = aTranslator->LookupScaledFont(mScaledFont); + if (!scaledFont) { + return false; + } + + GlyphBuffer buffer; + buffer.mGlyphs = mGlyphs; + buffer.mNumGlyphs = mNumGlyphs; + DrawGlyphs(dt, scaledFont, buffer, *GenericPattern(mPattern, aTranslator)); + return true; +} + +template <class T> +template <class S> +RecordedDrawGlyphs<T>::RecordedDrawGlyphs(RecordedEvent::EventType aType, + S& aStream) + : RecordedEventDerived<T>(aType) { + ReadElement(aStream, mScaledFont); + ReadDrawOptions(aStream, mOptions); + this->ReadPatternData(aStream, mPattern); + ReadElement(aStream, mNumGlyphs); + if (!aStream.good() || mNumGlyphs <= 0) { + return; + } + + mGlyphs = new (fallible) Glyph[mNumGlyphs]; + if (!mGlyphs) { + gfxCriticalNote << "RecordedDrawGlyphs failed to allocate glyphs of size " + << mNumGlyphs; + aStream.SetIsBad(); + } else { + aStream.read((char*)mGlyphs, sizeof(Glyph) * mNumGlyphs); + } +} + +template <class T> +template <class S> +void RecordedDrawGlyphs<T>::Record(S& aStream) const { + WriteElement(aStream, mScaledFont); + WriteElement(aStream, mOptions); + this->RecordPatternData(aStream, mPattern); + WriteElement(aStream, mNumGlyphs); + aStream.write((char*)mGlyphs, sizeof(Glyph) * mNumGlyphs); +} + +template <class T> +inline void RecordedDrawGlyphs<T>::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << this->GetName() << " (" << mScaledFont << ") "; + this->OutputSimplePatternInfo(mPattern, aStringStream); +} + +inline bool RecordedMask::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + dt->Mask(*GenericPattern(mSource, aTranslator), + *GenericPattern(mMask, aTranslator), mOptions); + return true; +} + +template <class S> +RecordedMask::RecordedMask(S& aStream) : RecordedEventDerived(MASK) { + ReadDrawOptions(aStream, mOptions); + ReadPatternData(aStream, mSource); + ReadPatternData(aStream, mMask); +} + +template <class S> +void RecordedMask::Record(S& aStream) const { + WriteElement(aStream, mOptions); + RecordPatternData(aStream, mSource); + RecordPatternData(aStream, mMask); +} + +inline void RecordedMask::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "Mask (Source: "; + OutputSimplePatternInfo(mSource, aStringStream); + aStringStream << " Mask: "; + OutputSimplePatternInfo(mMask, aStringStream); +} + +inline bool RecordedStroke::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + Path* path = aTranslator->LookupPath(mPath); + if (!path) { + return false; + } + + dt->Stroke(path, *GenericPattern(mPattern, aTranslator), mStrokeOptions, + mOptions); + return true; +} + +template <class S> +void RecordedStroke::Record(S& aStream) const { + WriteElement(aStream, mPath); + WriteElement(aStream, mOptions); + RecordPatternData(aStream, mPattern); + RecordStrokeOptions(aStream, mStrokeOptions); +} + +template <class S> +RecordedStroke::RecordedStroke(S& aStream) : RecordedEventDerived(STROKE) { + ReadElement(aStream, mPath); + ReadDrawOptions(aStream, mOptions); + ReadPatternData(aStream, mPattern); + ReadStrokeOptions(aStream, mStrokeOptions); +} + +inline void RecordedStroke::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "Stroke (" << mPath + << ") LineWidth: " << mStrokeOptions.mLineWidth << "px "; + OutputSimplePatternInfo(mPattern, aStringStream); +} + +inline bool RecordedClearRect::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + dt->ClearRect(mRect); + return true; +} + +template <class S> +void RecordedClearRect::Record(S& aStream) const { + WriteElement(aStream, mRect); +} + +template <class S> +RecordedClearRect::RecordedClearRect(S& aStream) + : RecordedEventDerived(CLEARRECT) { + ReadElement(aStream, mRect); +} + +inline void RecordedClearRect::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "ClearRect (" << mRect.X() << ", " << mRect.Y() << " - " + << mRect.Width() << " x " << mRect.Height() << ") "; +} + +inline bool RecordedCopySurface::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + SourceSurface* surface = aTranslator->LookupSourceSurface(mSourceSurface); + if (!surface) { + return false; + } + + dt->CopySurface(surface, mSourceRect, mDest); + return true; +} + +template <class S> +void RecordedCopySurface::Record(S& aStream) const { + WriteElement(aStream, mSourceSurface); + WriteElement(aStream, mSourceRect); + WriteElement(aStream, mDest); +} + +template <class S> +RecordedCopySurface::RecordedCopySurface(S& aStream) + : RecordedEventDerived(COPYSURFACE) { + ReadElement(aStream, mSourceSurface); + ReadElement(aStream, mSourceRect); + ReadElement(aStream, mDest); +} + +inline void RecordedCopySurface::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "CopySurface (" << mSourceSurface << ")"; +} + +inline bool RecordedPushClip::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + Path* path = aTranslator->LookupPath(mPath); + if (!path) { + return false; + } + + dt->PushClip(path); + return true; +} + +template <class S> +void RecordedPushClip::Record(S& aStream) const { + WriteElement(aStream, mPath); +} + +template <class S> +RecordedPushClip::RecordedPushClip(S& aStream) + : RecordedEventDerived(PUSHCLIP) { + ReadElement(aStream, mPath); +} + +inline void RecordedPushClip::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "PushClip (" << mPath << ") "; +} + +inline bool RecordedPushClipRect::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + dt->PushClipRect(mRect); + return true; +} + +template <class S> +void RecordedPushClipRect::Record(S& aStream) const { + WriteElement(aStream, mRect); +} + +template <class S> +RecordedPushClipRect::RecordedPushClipRect(S& aStream) + : RecordedEventDerived(PUSHCLIPRECT) { + ReadElement(aStream, mRect); +} + +inline void RecordedPushClipRect::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "PushClipRect (" << mRect.X() << ", " << mRect.Y() << " - " + << mRect.Width() << " x " << mRect.Height() << ") "; +} + +inline bool RecordedPopClip::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + dt->PopClip(); + return true; +} + +template <class S> +void RecordedPopClip::Record(S& aStream) const {} + +template <class S> +RecordedPopClip::RecordedPopClip(S& aStream) : RecordedEventDerived(POPCLIP) {} + +inline void RecordedPopClip::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "PopClip"; +} + +inline bool RecordedPushLayer::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + SourceSurface* mask = + mMask ? aTranslator->LookupSourceSurface(mMask) : nullptr; + dt->PushLayer(mOpaque, mOpacity, mask, mMaskTransform, mBounds, + mCopyBackground); + return true; +} + +template <class S> +void RecordedPushLayer::Record(S& aStream) const { + WriteElement(aStream, mOpaque); + WriteElement(aStream, mOpacity); + WriteElement(aStream, mMask); + WriteElement(aStream, mMaskTransform); + WriteElement(aStream, mBounds); + WriteElement(aStream, mCopyBackground); +} + +template <class S> +RecordedPushLayer::RecordedPushLayer(S& aStream) + : RecordedEventDerived(PUSHLAYER) { + ReadElement(aStream, mOpaque); + ReadElement(aStream, mOpacity); + ReadElement(aStream, mMask); + ReadElement(aStream, mMaskTransform); + ReadElement(aStream, mBounds); + ReadElement(aStream, mCopyBackground); +} + +inline void RecordedPushLayer::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "PushPLayer (Opaque=" << mOpaque << ", Opacity=" << mOpacity + << ", Mask Ref=" << mMask << ") "; +} + +inline bool RecordedPushLayerWithBlend::PlayEvent( + Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + SourceSurface* mask = + mMask ? aTranslator->LookupSourceSurface(mMask) : nullptr; + dt->PushLayerWithBlend(mOpaque, mOpacity, mask, mMaskTransform, mBounds, + mCopyBackground, mCompositionOp); + return true; +} + +template <class S> +void RecordedPushLayerWithBlend::Record(S& aStream) const { + WriteElement(aStream, mOpaque); + WriteElement(aStream, mOpacity); + WriteElement(aStream, mMask); + WriteElement(aStream, mMaskTransform); + WriteElement(aStream, mBounds); + WriteElement(aStream, mCopyBackground); + WriteElement(aStream, mCompositionOp); +} + +template <class S> +RecordedPushLayerWithBlend::RecordedPushLayerWithBlend(S& aStream) + : RecordedEventDerived(PUSHLAYERWITHBLEND) { + ReadElement(aStream, mOpaque); + ReadElement(aStream, mOpacity); + ReadElement(aStream, mMask); + ReadElement(aStream, mMaskTransform); + ReadElement(aStream, mBounds); + ReadElement(aStream, mCopyBackground); + ReadElementConstrained(aStream, mCompositionOp, CompositionOp::OP_OVER, + CompositionOp::OP_COUNT); +} + +inline void RecordedPushLayerWithBlend::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "PushLayerWithBlend (Opaque=" << mOpaque + << ", Opacity=" << mOpacity << ", Mask Ref=" << mMask << ") "; +} + +inline bool RecordedPopLayer::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + dt->PopLayer(); + return true; +} + +template <class S> +void RecordedPopLayer::Record(S& aStream) const {} + +template <class S> +RecordedPopLayer::RecordedPopLayer(S& aStream) + : RecordedEventDerived(POPLAYER) {} + +inline void RecordedPopLayer::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "PopLayer"; +} + +inline bool RecordedSetPermitSubpixelAA::PlayEvent( + Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + dt->SetPermitSubpixelAA(mPermitSubpixelAA); + return true; +} + +template <class S> +void RecordedSetPermitSubpixelAA::Record(S& aStream) const { + WriteElement(aStream, mPermitSubpixelAA); +} + +template <class S> +RecordedSetPermitSubpixelAA::RecordedSetPermitSubpixelAA(S& aStream) + : RecordedEventDerived(SETPERMITSUBPIXELAA) { + ReadElement(aStream, mPermitSubpixelAA); +} + +inline void RecordedSetPermitSubpixelAA::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "SetPermitSubpixelAA (" << mPermitSubpixelAA << ")"; +} + +inline bool RecordedSetTransform::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + // If we're drawing to the reference DT, then we need to manually apply + // its initial transform, otherwise we'll just clobber it with only the + // the transform that was visible to the code doing the recording. + if (dt == aTranslator->GetReferenceDrawTarget()) { + dt->SetTransform(mTransform * + aTranslator->GetReferenceDrawTargetTransform()); + } else { + dt->SetTransform(mTransform); + } + + return true; +} + +template <class S> +void RecordedSetTransform::Record(S& aStream) const { + WriteElement(aStream, mTransform); +} + +template <class S> +RecordedSetTransform::RecordedSetTransform(S& aStream) + : RecordedEventDerived(SETTRANSFORM) { + ReadElement(aStream, mTransform); +} + +inline void RecordedSetTransform::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "SetTransform [ " << mTransform._11 << " " << mTransform._12 + << " ; " << mTransform._21 << " " << mTransform._22 << " ; " + << mTransform._31 << " " << mTransform._32 << " ]"; +} + +inline bool RecordedDrawSurface::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + SourceSurface* surface = aTranslator->LookupSourceSurface(mRefSource); + if (!surface) { + return false; + } + + dt->DrawSurface(surface, mDest, mSource, mDSOptions, mOptions); + return true; +} + +template <class S> +void RecordedDrawSurface::Record(S& aStream) const { + WriteElement(aStream, mRefSource); + WriteElement(aStream, mDest); + WriteElement(aStream, mSource); + WriteElement(aStream, mDSOptions); + WriteElement(aStream, mOptions); +} + +template <class S> +RecordedDrawSurface::RecordedDrawSurface(S& aStream) + : RecordedEventDerived(DRAWSURFACE) { + ReadElement(aStream, mRefSource); + ReadElement(aStream, mDest); + ReadElement(aStream, mSource); + ReadDrawSurfaceOptions(aStream, mDSOptions); + ReadDrawOptions(aStream, mOptions); +} + +inline void RecordedDrawSurface::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "DrawSurface (" << mRefSource << ")"; +} + +inline bool RecordedDrawDependentSurface::PlayEvent( + Translator* aTranslator) const { + aTranslator->DrawDependentSurface(mId, mDest); + return true; +} + +template <class S> +void RecordedDrawDependentSurface::Record(S& aStream) const { + WriteElement(aStream, mId); + WriteElement(aStream, mDest); +} + +template <class S> +RecordedDrawDependentSurface::RecordedDrawDependentSurface(S& aStream) + : RecordedEventDerived(DRAWDEPENDENTSURFACE) { + ReadElement(aStream, mId); + ReadElement(aStream, mDest); +} + +inline void RecordedDrawDependentSurface::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "DrawDependentSurface (" << mId << ")"; +} + +inline bool RecordedDrawFilter::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + FilterNode* filter = aTranslator->LookupFilterNode(mNode); + if (!filter) { + return false; + } + + dt->DrawFilter(filter, mSourceRect, mDestPoint, mOptions); + return true; +} + +template <class S> +void RecordedDrawFilter::Record(S& aStream) const { + WriteElement(aStream, mNode); + WriteElement(aStream, mSourceRect); + WriteElement(aStream, mDestPoint); + WriteElement(aStream, mOptions); +} + +template <class S> +RecordedDrawFilter::RecordedDrawFilter(S& aStream) + : RecordedEventDerived(DRAWFILTER) { + ReadElement(aStream, mNode); + ReadElement(aStream, mSourceRect); + ReadElement(aStream, mDestPoint); + ReadDrawOptions(aStream, mOptions); +} + +inline void RecordedDrawFilter::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "DrawFilter (" << mNode << ")"; +} + +inline bool RecordedDrawSurfaceWithShadow::PlayEvent( + Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + SourceSurface* surface = aTranslator->LookupSourceSurface(mRefSource); + if (!surface) { + return false; + } + + dt->DrawSurfaceWithShadow(surface, mDest, mShadow, mOp); + return true; +} + +template <class S> +void RecordedDrawSurfaceWithShadow::Record(S& aStream) const { + WriteElement(aStream, mRefSource); + WriteElement(aStream, mDest); + WriteElement(aStream, mShadow); + WriteElement(aStream, mOp); +} + +template <class S> +RecordedDrawSurfaceWithShadow::RecordedDrawSurfaceWithShadow(S& aStream) + : RecordedEventDerived(DRAWSURFACEWITHSHADOW) { + ReadElement(aStream, mRefSource); + ReadElement(aStream, mDest); + ReadElement(aStream, mShadow); + ReadElementConstrained(aStream, mOp, CompositionOp::OP_OVER, + CompositionOp::OP_COUNT); +} + +inline void RecordedDrawSurfaceWithShadow::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "DrawSurfaceWithShadow (" << mRefSource << ") DeviceColor: (" + << mShadow.mColor.r << ", " << mShadow.mColor.g << ", " + << mShadow.mColor.b << ", " << mShadow.mColor.a << ")"; +} + +inline bool RecordedDrawShadow::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + Path* path = aTranslator->LookupPath(mPath); + if (!path) { + return false; + } + + dt->DrawShadow(path, *GenericPattern(mPattern, aTranslator), mShadow, + mOptions, mHasStrokeOptions ? &mStrokeOptions : nullptr); + return true; +} + +template <class S> +void RecordedDrawShadow::Record(S& aStream) const { + WriteElement(aStream, mPath); + RecordPatternData(aStream, mPattern); + WriteElement(aStream, mShadow); + WriteElement(aStream, mOptions); + WriteElement(aStream, mHasStrokeOptions); + if (mHasStrokeOptions) { + RecordStrokeOptions(aStream, mStrokeOptions); + } +} + +template <class S> +RecordedDrawShadow::RecordedDrawShadow(S& aStream) + : RecordedEventDerived(DRAWSHADOW) { + ReadElement(aStream, mPath); + ReadPatternData(aStream, mPattern); + ReadElement(aStream, mShadow); + ReadDrawOptions(aStream, mOptions); + ReadElement(aStream, mHasStrokeOptions); + if (mHasStrokeOptions) { + ReadStrokeOptions(aStream, mStrokeOptions); + } +} + +inline void RecordedDrawShadow::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "DrawShadow (" << mPath << ") DeviceColor: (" + << mShadow.mColor.r << ", " << mShadow.mColor.g << ", " + << mShadow.mColor.b << ", " << mShadow.mColor.a << ")"; +} + +inline RecordedPathCreation::RecordedPathCreation(PathRecording* aPath) + : RecordedEventDerived(PATHCREATION), + mRefPtr(aPath), + mFillRule(aPath->mFillRule), + mPath(aPath) {} + +inline bool RecordedPathCreation::PlayEvent(Translator* aTranslator) const { + DrawTarget* drawTarget = aTranslator->GetCurrentDrawTarget(); + if (!drawTarget) { + return false; + } + + RefPtr<PathBuilder> builder = drawTarget->CreatePathBuilder(mFillRule); + if (!mPathOps->CheckedStreamToSink(*builder)) { + return false; + } + + RefPtr<Path> path = builder->Finish(); + aTranslator->AddPath(mRefPtr, path); + return true; +} + +template <class S> +void RecordedPathCreation::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mFillRule); + mPath->mPathOps.Record(aStream); +} + +template <class S> +RecordedPathCreation::RecordedPathCreation(S& aStream) + : RecordedEventDerived(PATHCREATION) { + ReadElement(aStream, mRefPtr); + ReadElementConstrained(aStream, mFillRule, FillRule::FILL_WINDING, + FillRule::FILL_EVEN_ODD); + mPathOps = MakeUnique<PathOps>(aStream); +} + +inline void RecordedPathCreation::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + size_t numberOfOps = + mPath ? mPath->mPathOps.NumberOfOps() : mPathOps->NumberOfOps(); + aStringStream << "[" << mRefPtr << "] Path created (OpCount: " << numberOfOps + << ")"; +} +inline bool RecordedPathDestruction::PlayEvent(Translator* aTranslator) const { + aTranslator->RemovePath(mRefPtr); + return true; +} + +template <class S> +void RecordedPathDestruction::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); +} + +template <class S> +RecordedPathDestruction::RecordedPathDestruction(S& aStream) + : RecordedEventDerived(PATHDESTRUCTION) { + ReadElement(aStream, mRefPtr); +} + +inline void RecordedPathDestruction::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr << "] Path Destroyed"; +} + +inline RecordedSourceSurfaceCreation::~RecordedSourceSurfaceCreation() { + if (mDataOwned) { + delete[] mData; + } +} + +inline bool RecordedSourceSurfaceCreation::PlayEvent( + Translator* aTranslator) const { + if (!mData) { + return false; + } + + RefPtr<SourceSurface> src = Factory::CreateWrappingDataSourceSurface( + mData, mSize.width * BytesPerPixel(mFormat), mSize, mFormat, + [](void* aClosure) { delete[] static_cast<uint8_t*>(aClosure); }, mData); + if (src) { + mDataOwned = false; + } + + aTranslator->AddSourceSurface(mRefPtr, src); + return true; +} + +template <class S> +void RecordedSourceSurfaceCreation::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mSize); + WriteElement(aStream, mFormat); + MOZ_ASSERT(mData); + size_t dataFormatWidth = BytesPerPixel(mFormat) * mSize.width; + const char* endSrc = (const char*)(mData + (mSize.height * mStride)); + for (const char* src = (const char*)mData; src < endSrc; src += mStride) { + aStream.write(src, dataFormatWidth); + } +} + +template <class S> +RecordedSourceSurfaceCreation::RecordedSourceSurfaceCreation(S& aStream) + : RecordedEventDerived(SOURCESURFACECREATION), mDataOwned(true) { + ReadElement(aStream, mRefPtr); + ReadElement(aStream, mSize); + ReadElementConstrained(aStream, mFormat, SurfaceFormat::A8R8G8B8_UINT32, + SurfaceFormat::UNKNOWN); + + if (!Factory::AllowedSurfaceSize(mSize)) { + gfxCriticalNote << "RecordedSourceSurfaceCreation read invalid size " + << mSize; + aStream.SetIsBad(); + } + + if (!aStream.good()) { + return; + } + + size_t size = 0; + if (mSize.width >= 0 && mSize.height >= 0) { + size = size_t(mSize.width) * size_t(mSize.height) * BytesPerPixel(mFormat); + mData = new (fallible) uint8_t[size]; + } + if (!mData) { + gfxCriticalNote + << "RecordedSourceSurfaceCreation failed to allocate data of size " + << size; + aStream.SetIsBad(); + } else { + aStream.read((char*)mData, size); + } +} + +inline void RecordedSourceSurfaceCreation::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr + << "] SourceSurface created (Size: " << mSize.width << "x" + << mSize.height << ")"; +} + +inline bool RecordedSourceSurfaceDestruction::PlayEvent( + Translator* aTranslator) const { + aTranslator->RemoveSourceSurface(mRefPtr); + return true; +} + +template <class S> +void RecordedSourceSurfaceDestruction::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); +} + +template <class S> +RecordedSourceSurfaceDestruction::RecordedSourceSurfaceDestruction(S& aStream) + : RecordedEventDerived(SOURCESURFACEDESTRUCTION) { + ReadElement(aStream, mRefPtr); +} + +inline void RecordedSourceSurfaceDestruction::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr << "] SourceSurface Destroyed"; +} + +inline bool RecordedOptimizeSourceSurface::PlayEvent( + Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + SourceSurface* surface = aTranslator->LookupSourceSurface(mSurface); + if (!surface) { + return false; + } + + RefPtr<SourceSurface> optimizedSurface = dt->OptimizeSourceSurface(surface); + aTranslator->AddSourceSurface(mOptimizedSurface, optimizedSurface); + return true; +} + +template <class S> +void RecordedOptimizeSourceSurface::Record(S& aStream) const { + WriteElement(aStream, mSurface); + WriteElement(aStream, mOptimizedSurface); +} + +template <class S> +RecordedOptimizeSourceSurface::RecordedOptimizeSourceSurface(S& aStream) + : RecordedEventDerived(OPTIMIZESOURCESURFACE) { + ReadElement(aStream, mSurface); + ReadElement(aStream, mOptimizedSurface); +} + +inline void RecordedOptimizeSourceSurface::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mSurface << "] Surface Optimized"; +} + +inline bool RecordedExternalSurfaceCreation::PlayEvent( + Translator* aTranslator) const { + RefPtr<SourceSurface> surface = aTranslator->LookupExternalSurface(mKey); + if (!surface) { + return false; + } + + aTranslator->AddSourceSurface(mRefPtr, surface); + return true; +} + +template <class S> +void RecordedExternalSurfaceCreation::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mKey); +} + +template <class S> +RecordedExternalSurfaceCreation::RecordedExternalSurfaceCreation(S& aStream) + : RecordedEventDerived(EXTERNALSURFACECREATION) { + ReadElement(aStream, mRefPtr); + ReadElement(aStream, mKey); +} + +inline void RecordedExternalSurfaceCreation::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr + << "] SourceSurfaceSharedData created (Key: " << mKey << ")"; +} + +inline RecordedFilterNodeCreation::~RecordedFilterNodeCreation() = default; + +inline bool RecordedFilterNodeCreation::PlayEvent( + Translator* aTranslator) const { + DrawTarget* drawTarget = aTranslator->GetCurrentDrawTarget(); + if (!drawTarget) { + return false; + } + + RefPtr<FilterNode> node = drawTarget->CreateFilter(mType); + aTranslator->AddFilterNode(mRefPtr, node); + return true; +} + +template <class S> +void RecordedFilterNodeCreation::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mType); +} + +template <class S> +RecordedFilterNodeCreation::RecordedFilterNodeCreation(S& aStream) + : RecordedEventDerived(FILTERNODECREATION) { + ReadElement(aStream, mRefPtr); + ReadElementConstrained(aStream, mType, FilterType::BLEND, + FilterType::OPACITY); +} + +inline void RecordedFilterNodeCreation::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "CreateFilter [" << mRefPtr + << "] FilterNode created (Type: " << int(mType) << ")"; +} + +inline bool RecordedFilterNodeDestruction::PlayEvent( + Translator* aTranslator) const { + aTranslator->RemoveFilterNode(mRefPtr); + return true; +} + +template <class S> +void RecordedFilterNodeDestruction::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); +} + +template <class S> +RecordedFilterNodeDestruction::RecordedFilterNodeDestruction(S& aStream) + : RecordedEventDerived(FILTERNODEDESTRUCTION) { + ReadElement(aStream, mRefPtr); +} + +inline void RecordedFilterNodeDestruction::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr << "] FilterNode Destroyed"; +} + +inline RecordedGradientStopsCreation::~RecordedGradientStopsCreation() { + if (mDataOwned) { + delete[] mStops; + } +} + +inline bool RecordedGradientStopsCreation::PlayEvent( + Translator* aTranslator) const { + if (mNumStops > 0 && !mStops) { + // Stops allocation failed + return false; + } + + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + RefPtr<GradientStops> src = + aTranslator->GetOrCreateGradientStops(dt, mStops, mNumStops, mExtendMode); + aTranslator->AddGradientStops(mRefPtr, src); + return true; +} + +template <class S> +void RecordedGradientStopsCreation::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mExtendMode); + WriteElement(aStream, mNumStops); + aStream.write((const char*)mStops, mNumStops * sizeof(GradientStop)); +} + +template <class S> +RecordedGradientStopsCreation::RecordedGradientStopsCreation(S& aStream) + : RecordedEventDerived(GRADIENTSTOPSCREATION), mDataOwned(true) { + ReadElement(aStream, mRefPtr); + ReadElementConstrained(aStream, mExtendMode, ExtendMode::CLAMP, + ExtendMode::REFLECT); + ReadElement(aStream, mNumStops); + if (!aStream.good() || mNumStops <= 0) { + return; + } + + mStops = new (fallible) GradientStop[mNumStops]; + if (!mStops) { + gfxCriticalNote + << "RecordedGradientStopsCreation failed to allocate stops of size " + << mNumStops; + aStream.SetIsBad(); + } else { + aStream.read((char*)mStops, mNumStops * sizeof(GradientStop)); + } +} + +inline void RecordedGradientStopsCreation::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr + << "] GradientStops created (Stops: " << mNumStops << ")"; +} + +inline bool RecordedGradientStopsDestruction::PlayEvent( + Translator* aTranslator) const { + aTranslator->RemoveGradientStops(mRefPtr); + return true; +} + +template <class S> +void RecordedGradientStopsDestruction::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); +} + +template <class S> +RecordedGradientStopsDestruction::RecordedGradientStopsDestruction(S& aStream) + : RecordedEventDerived(GRADIENTSTOPSDESTRUCTION) { + ReadElement(aStream, mRefPtr); +} + +inline void RecordedGradientStopsDestruction::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr << "] GradientStops Destroyed"; +} + +inline bool RecordedIntoLuminanceSource::PlayEvent( + Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + RefPtr<SourceSurface> src = dt->IntoLuminanceSource(mLuminanceType, mOpacity); + aTranslator->AddSourceSurface(mRefPtr, src); + return true; +} + +template <class S> +void RecordedIntoLuminanceSource::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mLuminanceType); + WriteElement(aStream, mOpacity); +} + +template <class S> +RecordedIntoLuminanceSource::RecordedIntoLuminanceSource(S& aStream) + : RecordedEventDerived(INTOLUMINANCE) { + ReadElement(aStream, mRefPtr); + ReadElementConstrained(aStream, mLuminanceType, LuminanceType::LUMINANCE, + LuminanceType::LINEARRGB); + ReadElement(aStream, mOpacity); +} + +inline void RecordedIntoLuminanceSource::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr << "] Into Luminance Source"; +} + +inline bool RecordedExtractSubrect::PlayEvent(Translator* aTranslator) const { + SourceSurface* sourceSurf = aTranslator->LookupSourceSurface(mSourceSurface); + if (!sourceSurf) { + return false; + } + + RefPtr<SourceSurface> subSurf = sourceSurf->ExtractSubrect(mSubrect); + if (!subSurf) { + RefPtr<DrawTarget> dt = + aTranslator->GetReferenceDrawTarget()->CreateSimilarDrawTarget( + mSubrect.Size(), sourceSurf->GetFormat()); + if (dt) { + dt->CopySurface(sourceSurf, mSubrect, IntPoint()); + subSurf = dt->Snapshot(); + } + } + if (!subSurf) { + return false; + } + + aTranslator->AddSourceSurface(mRefPtr, subSurf); + return true; +} + +template <class S> +void RecordedExtractSubrect::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mSourceSurface); + WriteElement(aStream, mSubrect); +} + +template <class S> +RecordedExtractSubrect::RecordedExtractSubrect(S& aStream) + : RecordedEventDerived(EXTRACTSUBRECT) { + ReadElement(aStream, mRefPtr); + ReadElement(aStream, mSourceSurface); + ReadElement(aStream, mSubrect); +} + +inline void RecordedExtractSubrect::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr << "] Exract Subrect"; +} + +inline bool RecordedFlush::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + dt->Flush(); + return true; +} + +template <class S> +void RecordedFlush::Record(S& aStream) const {} + +template <class S> +RecordedFlush::RecordedFlush(S& aStream) : RecordedEventDerived(FLUSH) {} + +inline void RecordedFlush::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "Flush"; +} + +inline bool RecordedDetachAllSnapshots::PlayEvent( + Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + dt->DetachAllSnapshots(); + return true; +} + +template <class S> +void RecordedDetachAllSnapshots::Record(S& aStream) const {} + +template <class S> +RecordedDetachAllSnapshots::RecordedDetachAllSnapshots(S& aStream) + : RecordedEventDerived(DETACHALLSNAPSHOTS) {} + +inline void RecordedDetachAllSnapshots::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "DetachAllSnapshots"; +} + +inline bool RecordedSnapshot::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + RefPtr<SourceSurface> src = dt->Snapshot(); + aTranslator->AddSourceSurface(mRefPtr, src); + return true; +} + +template <class S> +void RecordedSnapshot::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); +} + +template <class S> +RecordedSnapshot::RecordedSnapshot(S& aStream) + : RecordedEventDerived(SNAPSHOT) { + ReadElement(aStream, mRefPtr); +} + +inline void RecordedSnapshot::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr << "] Snapshot Created"; +} + +inline RecordedFontData::~RecordedFontData() { delete[] mData; } + +inline bool RecordedFontData::PlayEvent(Translator* aTranslator) const { + if (!mData) { + return false; + } + + RefPtr<NativeFontResource> fontResource = Factory::CreateNativeFontResource( + mData, mFontDetails.size, mType, aTranslator->GetFontContext()); + if (!fontResource) { + return false; + } + + aTranslator->AddNativeFontResource(mFontDetails.fontDataKey, fontResource); + return true; +} + +template <class S> +void RecordedFontData::Record(S& aStream) const { + MOZ_ASSERT(mGetFontFileDataSucceeded); + + WriteElement(aStream, mType); + WriteElement(aStream, mFontDetails.fontDataKey); + if (!mData) { + WriteElement(aStream, 0); + } else { + WriteElement(aStream, mFontDetails.size); + aStream.write((const char*)mData, mFontDetails.size); + } +} + +inline void RecordedFontData::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "Font Data of size " << mFontDetails.size; +} + +inline void RecordedFontData::SetFontData(const uint8_t* aData, uint32_t aSize, + uint32_t aIndex) { + mData = new (fallible) uint8_t[aSize]; + if (!mData) { + gfxCriticalNote + << "RecordedFontData failed to allocate data for recording of size " + << aSize; + } else { + memcpy(mData, aData, aSize); + } + mFontDetails.fontDataKey = SFNTData::GetUniqueKey(aData, aSize, 0, nullptr); + mFontDetails.size = aSize; + mFontDetails.index = aIndex; +} + +inline bool RecordedFontData::GetFontDetails(RecordedFontDetails& fontDetails) { + if (!mGetFontFileDataSucceeded) { + return false; + } + + fontDetails.fontDataKey = mFontDetails.fontDataKey; + fontDetails.size = mFontDetails.size; + fontDetails.index = mFontDetails.index; + return true; +} + +template <class S> +RecordedFontData::RecordedFontData(S& aStream) + : RecordedEventDerived(FONTDATA), mType(FontType::UNKNOWN) { + ReadElementConstrained(aStream, mType, FontType::DWRITE, FontType::UNKNOWN); + ReadElement(aStream, mFontDetails.fontDataKey); + ReadElement(aStream, mFontDetails.size); + if (!mFontDetails.size || !aStream.good()) { + return; + } + + mData = new (fallible) uint8_t[mFontDetails.size]; + if (!mData) { + gfxCriticalNote + << "RecordedFontData failed to allocate data for playback of size " + << mFontDetails.size; + aStream.SetIsBad(); + } else { + aStream.read((char*)mData, mFontDetails.size); + } +} + +inline RecordedFontDescriptor::~RecordedFontDescriptor() = default; + +inline bool RecordedFontDescriptor::PlayEvent(Translator* aTranslator) const { + RefPtr<UnscaledFont> font = Factory::CreateUnscaledFontFromFontDescriptor( + mType, mData.data(), mData.size(), mIndex); + if (!font) { + gfxDevCrash(LogReason::InvalidFont) + << "Failed creating UnscaledFont of type " << int(mType) + << " from font descriptor"; + return false; + } + + aTranslator->AddUnscaledFont(mRefPtr, font); + return true; +} + +template <class S> +void RecordedFontDescriptor::Record(S& aStream) const { + MOZ_ASSERT(mHasDesc); + WriteElement(aStream, mType); + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mIndex); + WriteElement(aStream, (size_t)mData.size()); + if (mData.size()) { + aStream.write((char*)mData.data(), mData.size()); + } +} + +inline void RecordedFontDescriptor::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr << "] Font Descriptor"; +} + +inline void RecordedFontDescriptor::SetFontDescriptor(const uint8_t* aData, + uint32_t aSize, + uint32_t aIndex) { + mData.assign(aData, aData + aSize); + mIndex = aIndex; +} + +template <class S> +RecordedFontDescriptor::RecordedFontDescriptor(S& aStream) + : RecordedEventDerived(FONTDESC) { + ReadElementConstrained(aStream, mType, FontType::DWRITE, FontType::UNKNOWN); + ReadElement(aStream, mRefPtr); + ReadElement(aStream, mIndex); + + size_t size; + ReadElement(aStream, size); + if (!aStream.good()) { + return; + } + if (size) { + mData.resize(size); + aStream.read((char*)mData.data(), size); + } +} + +inline bool RecordedUnscaledFontCreation::PlayEvent( + Translator* aTranslator) const { + NativeFontResource* fontResource = + aTranslator->LookupNativeFontResource(mFontDataKey); + if (!fontResource) { + gfxDevCrash(LogReason::NativeFontResourceNotFound) + << "NativeFontResource lookup failed for key |" << hexa(mFontDataKey) + << "|."; + return false; + } + + RefPtr<UnscaledFont> unscaledFont = fontResource->CreateUnscaledFont( + mIndex, mInstanceData.data(), mInstanceData.size()); + aTranslator->AddUnscaledFont(mRefPtr, unscaledFont); + return true; +} + +template <class S> +void RecordedUnscaledFontCreation::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mFontDataKey); + WriteElement(aStream, mIndex); + WriteElement(aStream, (size_t)mInstanceData.size()); + if (mInstanceData.size()) { + aStream.write((char*)mInstanceData.data(), mInstanceData.size()); + } +} + +inline void RecordedUnscaledFontCreation::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr << "] UnscaledFont Created"; +} + +inline void RecordedUnscaledFontCreation::SetFontInstanceData( + const uint8_t* aData, uint32_t aSize) { + if (aSize) { + mInstanceData.assign(aData, aData + aSize); + } +} + +template <class S> +RecordedUnscaledFontCreation::RecordedUnscaledFontCreation(S& aStream) + : RecordedEventDerived(UNSCALEDFONTCREATION) { + ReadElement(aStream, mRefPtr); + ReadElement(aStream, mFontDataKey); + ReadElement(aStream, mIndex); + + size_t size; + ReadElement(aStream, size); + if (!aStream.good()) { + return; + } + if (size) { + mInstanceData.resize(size); + aStream.read((char*)mInstanceData.data(), size); + } +} + +inline bool RecordedUnscaledFontDestruction::PlayEvent( + Translator* aTranslator) const { + aTranslator->RemoveUnscaledFont(mRefPtr); + return true; +} + +template <class S> +void RecordedUnscaledFontDestruction::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); +} + +template <class S> +RecordedUnscaledFontDestruction::RecordedUnscaledFontDestruction(S& aStream) + : RecordedEventDerived(UNSCALEDFONTDESTRUCTION) { + ReadElement(aStream, mRefPtr); +} + +inline void RecordedUnscaledFontDestruction::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr << "] UnscaledFont Destroyed"; +} + +inline bool RecordedScaledFontCreation::PlayEvent( + Translator* aTranslator) const { + UnscaledFont* unscaledFont = aTranslator->LookupUnscaledFont(mUnscaledFont); + if (!unscaledFont) { + gfxDevCrash(LogReason::UnscaledFontNotFound) + << "UnscaledFont lookup failed for key |" << hexa(mUnscaledFont) + << "|."; + return false; + } + + RefPtr<ScaledFont> scaledFont = unscaledFont->CreateScaledFont( + mGlyphSize, mInstanceData.data(), mInstanceData.size(), + mVariations.data(), mVariations.size()); + + aTranslator->AddScaledFont(mRefPtr, scaledFont); + return true; +} + +template <class S> +void RecordedScaledFontCreation::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); + WriteElement(aStream, mUnscaledFont); + WriteElement(aStream, mGlyphSize); + WriteElement(aStream, (size_t)mInstanceData.size()); + if (mInstanceData.size()) { + aStream.write((char*)mInstanceData.data(), mInstanceData.size()); + } + WriteElement(aStream, (size_t)mVariations.size()); + if (mVariations.size()) { + aStream.write((char*)mVariations.data(), + sizeof(FontVariation) * mVariations.size()); + } +} + +inline void RecordedScaledFontCreation::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr << "] ScaledFont Created"; +} + +inline void RecordedScaledFontCreation::SetFontInstanceData( + const uint8_t* aData, uint32_t aSize, const FontVariation* aVariations, + uint32_t aNumVariations) { + if (aSize) { + mInstanceData.assign(aData, aData + aSize); + } + if (aNumVariations) { + mVariations.assign(aVariations, aVariations + aNumVariations); + } +} + +template <class S> +RecordedScaledFontCreation::RecordedScaledFontCreation(S& aStream) + : RecordedEventDerived(SCALEDFONTCREATION) { + ReadElement(aStream, mRefPtr); + ReadElement(aStream, mUnscaledFont); + ReadElement(aStream, mGlyphSize); + + size_t size; + ReadElement(aStream, size); + if (!aStream.good()) { + return; + } + if (size) { + mInstanceData.resize(size); + aStream.read((char*)mInstanceData.data(), size); + } + + size_t numVariations; + ReadElement(aStream, numVariations); + if (!aStream.good()) { + return; + } + if (numVariations) { + mVariations.resize(numVariations); + aStream.read((char*)mVariations.data(), + sizeof(FontVariation) * numVariations); + } +} + +inline bool RecordedScaledFontDestruction::PlayEvent( + Translator* aTranslator) const { + aTranslator->RemoveScaledFont(mRefPtr); + return true; +} + +template <class S> +void RecordedScaledFontDestruction::Record(S& aStream) const { + WriteElement(aStream, mRefPtr); +} + +template <class S> +RecordedScaledFontDestruction::RecordedScaledFontDestruction(S& aStream) + : RecordedEventDerived(SCALEDFONTDESTRUCTION) { + ReadElement(aStream, mRefPtr); +} + +inline void RecordedScaledFontDestruction::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mRefPtr << "] ScaledFont Destroyed"; +} + +inline bool RecordedMaskSurface::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + + SourceSurface* surface = aTranslator->LookupSourceSurface(mRefMask); + if (!surface) { + return false; + } + + dt->MaskSurface(*GenericPattern(mPattern, aTranslator), surface, mOffset, + mOptions); + return true; +} + +template <class S> +void RecordedMaskSurface::Record(S& aStream) const { + RecordPatternData(aStream, mPattern); + WriteElement(aStream, mRefMask); + WriteElement(aStream, mOffset); + WriteElement(aStream, mOptions); +} + +template <class S> +RecordedMaskSurface::RecordedMaskSurface(S& aStream) + : RecordedEventDerived(MASKSURFACE) { + ReadPatternData(aStream, mPattern); + ReadElement(aStream, mRefMask); + ReadElement(aStream, mOffset); + ReadDrawOptions(aStream, mOptions); +} + +inline void RecordedMaskSurface::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "MaskSurface (" << mRefMask << ") Offset: (" << mOffset.x + << "x" << mOffset.y << ") Pattern: "; + OutputSimplePatternInfo(mPattern, aStringStream); +} + +template <typename T> +void ReplaySetAttribute(FilterNode* aNode, uint32_t aIndex, T aValue) { + aNode->SetAttribute(aIndex, aValue); +} + +inline bool RecordedFilterNodeSetAttribute::PlayEvent( + Translator* aTranslator) const { + FilterNode* node = aTranslator->LookupFilterNode(mNode); + if (!node) { + return false; + } + +#define REPLAY_SET_ATTRIBUTE(type, argtype) \ + case ARGTYPE_##argtype: \ + ReplaySetAttribute(node, mIndex, *(type*)&mPayload.front()); \ + break + + switch (mArgType) { + REPLAY_SET_ATTRIBUTE(bool, BOOL); + REPLAY_SET_ATTRIBUTE(uint32_t, UINT32); + REPLAY_SET_ATTRIBUTE(Float, FLOAT); + REPLAY_SET_ATTRIBUTE(Size, SIZE); + REPLAY_SET_ATTRIBUTE(IntSize, INTSIZE); + REPLAY_SET_ATTRIBUTE(IntPoint, INTPOINT); + REPLAY_SET_ATTRIBUTE(Rect, RECT); + REPLAY_SET_ATTRIBUTE(IntRect, INTRECT); + REPLAY_SET_ATTRIBUTE(Point, POINT); + REPLAY_SET_ATTRIBUTE(Matrix, MATRIX); + REPLAY_SET_ATTRIBUTE(Matrix5x4, MATRIX5X4); + REPLAY_SET_ATTRIBUTE(Point3D, POINT3D); + REPLAY_SET_ATTRIBUTE(DeviceColor, COLOR); + case ARGTYPE_FLOAT_ARRAY: + node->SetAttribute(mIndex, + reinterpret_cast<const Float*>(&mPayload.front()), + mPayload.size() / sizeof(Float)); + break; + } + + return true; +} + +template <class S> +void RecordedFilterNodeSetAttribute::Record(S& aStream) const { + WriteElement(aStream, mNode); + WriteElement(aStream, mIndex); + WriteElement(aStream, mArgType); + WriteElement(aStream, uint64_t(mPayload.size())); + aStream.write((const char*)&mPayload.front(), mPayload.size()); +} + +template <class S> +RecordedFilterNodeSetAttribute::RecordedFilterNodeSetAttribute(S& aStream) + : RecordedEventDerived(FILTERNODESETATTRIBUTE) { + ReadElement(aStream, mNode); + ReadElement(aStream, mIndex); + ReadElementConstrained(aStream, mArgType, ArgType::ARGTYPE_UINT32, + ArgType::ARGTYPE_FLOAT_ARRAY); + uint64_t size; + ReadElement(aStream, size); + if (!aStream.good()) { + return; + } + + mPayload.resize(size_t(size)); + aStream.read((char*)&mPayload.front(), size); +} + +inline void RecordedFilterNodeSetAttribute::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mNode << "] SetAttribute (" << mIndex << ")"; +} + +inline bool RecordedFilterNodeSetInput::PlayEvent( + Translator* aTranslator) const { + FilterNode* node = aTranslator->LookupFilterNode(mNode); + if (!node) { + return false; + } + + if (mInputFilter) { + node->SetInput(mIndex, aTranslator->LookupFilterNode(mInputFilter)); + } else { + node->SetInput(mIndex, aTranslator->LookupSourceSurface(mInputSurface)); + } + + return true; +} + +template <class S> +void RecordedFilterNodeSetInput::Record(S& aStream) const { + WriteElement(aStream, mNode); + WriteElement(aStream, mIndex); + WriteElement(aStream, mInputFilter); + WriteElement(aStream, mInputSurface); +} + +template <class S> +RecordedFilterNodeSetInput::RecordedFilterNodeSetInput(S& aStream) + : RecordedEventDerived(FILTERNODESETINPUT) { + ReadElement(aStream, mNode); + ReadElement(aStream, mIndex); + ReadElement(aStream, mInputFilter); + ReadElement(aStream, mInputSurface); +} + +inline void RecordedFilterNodeSetInput::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "[" << mNode << "] SetAttribute (" << mIndex << ", "; + + if (mInputFilter) { + aStringStream << "Filter: " << mInputFilter; + } else { + aStringStream << "Surface: " << mInputSurface; + } + + aStringStream << ")"; +} + +inline bool RecordedLink::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + dt->Link(mDestination.c_str(), mRect); + return true; +} + +template <class S> +void RecordedLink::Record(S& aStream) const { + WriteElement(aStream, mRect); + uint32_t len = mDestination.length(); + WriteElement(aStream, len); + if (len) { + aStream.write(mDestination.data(), len); + } +} + +template <class S> +RecordedLink::RecordedLink(S& aStream) : RecordedEventDerived(LINK) { + ReadElement(aStream, mRect); + uint32_t len; + ReadElement(aStream, len); + mDestination.resize(size_t(len)); + if (len && aStream.good()) { + aStream.read(&mDestination.front(), len); + } +} + +inline void RecordedLink::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "Link [" << mDestination << " @ " << mRect << "]"; +} + +inline bool RecordedDestination::PlayEvent(Translator* aTranslator) const { + DrawTarget* dt = aTranslator->GetCurrentDrawTarget(); + if (!dt) { + return false; + } + dt->Destination(mDestination.c_str(), mPoint); + return true; +} + +template <class S> +void RecordedDestination::Record(S& aStream) const { + WriteElement(aStream, mPoint); + uint32_t len = mDestination.length(); + WriteElement(aStream, len); + if (len) { + aStream.write(mDestination.data(), len); + } +} + +template <class S> +RecordedDestination::RecordedDestination(S& aStream) + : RecordedEventDerived(DESTINATION) { + ReadElement(aStream, mPoint); + uint32_t len; + ReadElement(aStream, len); + mDestination.resize(size_t(len)); + if (len && aStream.good()) { + aStream.read(&mDestination.front(), len); + } +} + +inline void RecordedDestination::OutputSimpleEventInfo( + std::stringstream& aStringStream) const { + aStringStream << "Destination [" << mDestination << " @ " << mPoint << "]"; +} + +#define FOR_EACH_EVENT(f) \ + f(DRAWTARGETCREATION, RecordedDrawTargetCreation); \ + f(DRAWTARGETDESTRUCTION, RecordedDrawTargetDestruction); \ + f(SETCURRENTDRAWTARGET, RecordedSetCurrentDrawTarget); \ + f(FILLRECT, RecordedFillRect); \ + f(STROKERECT, RecordedStrokeRect); \ + f(STROKELINE, RecordedStrokeLine); \ + f(STROKECIRCLE, RecordedStrokeCircle); \ + f(CLEARRECT, RecordedClearRect); \ + f(COPYSURFACE, RecordedCopySurface); \ + f(SETPERMITSUBPIXELAA, RecordedSetPermitSubpixelAA); \ + f(SETTRANSFORM, RecordedSetTransform); \ + f(PUSHCLIPRECT, RecordedPushClipRect); \ + f(PUSHCLIP, RecordedPushClip); \ + f(POPCLIP, RecordedPopClip); \ + f(FILL, RecordedFill); \ + f(FILLCIRCLE, RecordedFillCircle); \ + f(FILLGLYPHS, RecordedFillGlyphs); \ + f(STROKEGLYPHS, RecordedStrokeGlyphs); \ + f(MASK, RecordedMask); \ + f(STROKE, RecordedStroke); \ + f(DRAWSURFACE, RecordedDrawSurface); \ + f(DRAWDEPENDENTSURFACE, RecordedDrawDependentSurface); \ + f(DRAWSURFACEWITHSHADOW, RecordedDrawSurfaceWithShadow); \ + f(DRAWSHADOW, RecordedDrawShadow); \ + f(DRAWFILTER, RecordedDrawFilter); \ + f(PATHCREATION, RecordedPathCreation); \ + f(PATHDESTRUCTION, RecordedPathDestruction); \ + f(SOURCESURFACECREATION, RecordedSourceSurfaceCreation); \ + f(SOURCESURFACEDESTRUCTION, RecordedSourceSurfaceDestruction); \ + f(FILTERNODECREATION, RecordedFilterNodeCreation); \ + f(FILTERNODEDESTRUCTION, RecordedFilterNodeDestruction); \ + f(GRADIENTSTOPSCREATION, RecordedGradientStopsCreation); \ + f(GRADIENTSTOPSDESTRUCTION, RecordedGradientStopsDestruction); \ + f(SNAPSHOT, RecordedSnapshot); \ + f(SCALEDFONTCREATION, RecordedScaledFontCreation); \ + f(SCALEDFONTDESTRUCTION, RecordedScaledFontDestruction); \ + f(MASKSURFACE, RecordedMaskSurface); \ + f(FILTERNODESETATTRIBUTE, RecordedFilterNodeSetAttribute); \ + f(FILTERNODESETINPUT, RecordedFilterNodeSetInput); \ + f(CREATESIMILARDRAWTARGET, RecordedCreateSimilarDrawTarget); \ + f(CREATECLIPPEDDRAWTARGET, RecordedCreateClippedDrawTarget); \ + f(CREATEDRAWTARGETFORFILTER, RecordedCreateDrawTargetForFilter); \ + f(FONTDATA, RecordedFontData); \ + f(FONTDESC, RecordedFontDescriptor); \ + f(PUSHLAYER, RecordedPushLayer); \ + f(PUSHLAYERWITHBLEND, RecordedPushLayerWithBlend); \ + f(POPLAYER, RecordedPopLayer); \ + f(UNSCALEDFONTCREATION, RecordedUnscaledFontCreation); \ + f(UNSCALEDFONTDESTRUCTION, RecordedUnscaledFontDestruction); \ + f(INTOLUMINANCE, RecordedIntoLuminanceSource); \ + f(EXTRACTSUBRECT, RecordedExtractSubrect); \ + f(EXTERNALSURFACECREATION, RecordedExternalSurfaceCreation); \ + f(FLUSH, RecordedFlush); \ + f(DETACHALLSNAPSHOTS, RecordedDetachAllSnapshots); \ + f(OPTIMIZESOURCESURFACE, RecordedOptimizeSourceSurface); \ + f(LINK, RecordedLink); \ + f(DESTINATION, RecordedDestination); + +#define DO_WITH_EVENT_TYPE(_typeenum, _class) \ + case _typeenum: { \ + auto e = _class(aStream); \ + return aAction(&e); \ + } + +template <class S> +bool RecordedEvent::DoWithEvent( + S& aStream, EventType aType, + const std::function<bool(RecordedEvent*)>& aAction) { + switch (aType) { + FOR_EACH_EVENT(DO_WITH_EVENT_TYPE) + default: + return false; + } +} + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/2d/RecordingTypes.h b/gfx/2d/RecordingTypes.h new file mode 100644 index 0000000000..94325b1295 --- /dev/null +++ b/gfx/2d/RecordingTypes.h @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_RECORDINGTYPES_H_ +#define MOZILLA_GFX_RECORDINGTYPES_H_ + +#include <ostream> +#include <vector> + +#include "Logging.h" + +namespace mozilla { +namespace gfx { + +template <class S, class T> +struct ElementStreamFormat { + static void Write(S& aStream, const T& aElement) { + aStream.write(reinterpret_cast<const char*>(&aElement), sizeof(T)); + } + static void Read(S& aStream, T& aElement) { + aStream.read(reinterpret_cast<char*>(&aElement), sizeof(T)); + } +}; +template <class S> +struct ElementStreamFormat<S, bool> { + static void Write(S& aStream, const bool& aElement) { + char boolChar = aElement ? '\x01' : '\x00'; + aStream.write(&boolChar, sizeof(boolChar)); + } + static void Read(S& aStream, bool& aElement) { + char boolChar; + aStream.read(&boolChar, sizeof(boolChar)); + switch (boolChar) { + case '\x00': + aElement = false; + break; + case '\x01': + aElement = true; + break; + default: + aStream.SetIsBad(); + break; + } + } +}; + +template <class S, class T> +void WriteElement(S& aStream, const T& aElement) { + ElementStreamFormat<S, T>::Write(aStream, aElement); +} +template <class S, class T> +void WriteVector(S& aStream, const std::vector<T>& aVector) { + size_t size = aVector.size(); + WriteElement(aStream, size); + if (size) { + aStream.write(reinterpret_cast<const char*>(aVector.data()), + sizeof(T) * size); + } +} + +// ReadElement is disabled for enum types. Use ReadElementConstrained instead. +template <class S, class T, + typename = typename std::enable_if<!std::is_enum<T>::value>::type> +void ReadElement(S& aStream, T& aElement) { + ElementStreamFormat<S, T>::Read(aStream, aElement); +} +template <class S, class T> +void ReadElementConstrained(S& aStream, T& aElement, const T& aMinValue, + const T& aMaxValue) { + ElementStreamFormat<S, T>::Read(aStream, aElement); + if (aElement < aMinValue || aElement > aMaxValue) { + aStream.SetIsBad(); + } +} +template <class S, class T> +void ReadVector(S& aStream, std::vector<T>& aVector) { + size_t size; + ReadElement(aStream, size); + if (size && aStream.good()) { + aVector.resize(size); + aStream.read(reinterpret_cast<char*>(aVector.data()), sizeof(T) * size); + } else { + aVector.clear(); + } +} + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_RECORDINGTYPES_H_ */ diff --git a/gfx/2d/Rect.h b/gfx/2d/Rect.h new file mode 100644 index 0000000000..e3558f3237 --- /dev/null +++ b/gfx/2d/Rect.h @@ -0,0 +1,498 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_RECT_H_ +#define MOZILLA_GFX_RECT_H_ + +#include "BaseRect.h" +#include "BaseMargin.h" +#include "NumericTools.h" +#include "Point.h" +#include "Tools.h" +#include "mozilla/Maybe.h" + +#include <cmath> +#include <cstdint> + +namespace mozilla { + +template <typename> +struct IsPixel; + +namespace gfx { + +template <class Units, class F> +struct RectTyped; + +template <class Units> +struct MOZ_EMPTY_BASES IntMarginTyped + : public BaseMargin<int32_t, IntMarginTyped<Units>, IntCoordTyped<Units> >, + public Units { + static_assert(IsPixel<Units>::value, + "'Units' must be a coordinate system tag"); + + typedef IntCoordTyped<Units> Coord; + typedef BaseMargin<int32_t, IntMarginTyped<Units>, Coord> Super; + + IntMarginTyped() : Super() { + static_assert(sizeof(IntMarginTyped) == sizeof(int32_t) * 4, + "Would be unfortunate otherwise!"); + } + IntMarginTyped(Coord aTop, Coord aRight, Coord aBottom, Coord 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.value, aMargin.right.value, + aMargin.bottom.value, aMargin.left.value); + } + + IntMarginTyped<UnknownUnits> ToUnknownMargin() const { + return IntMarginTyped<UnknownUnits>(this->top, this->right, this->bottom, + this->left); + } +}; +typedef IntMarginTyped<UnknownUnits> IntMargin; + +template <class Units, class F = Float> +struct MarginTyped + : public BaseMargin<F, MarginTyped<Units, F>, CoordTyped<Units, F> >, + public Units { + static_assert(IsPixel<Units>::value, + "'Units' must be a coordinate system tag"); + + typedef CoordTyped<Units, F> Coord; + typedef BaseMargin<F, MarginTyped<Units, F>, Coord> Super; + + MarginTyped() : Super() {} + MarginTyped(Coord aTop, Coord aRight, Coord aBottom, Coord aLeft) + : Super(aTop, aRight, aBottom, aLeft) {} + explicit MarginTyped(const IntMarginTyped<Units>& aMargin) + : Super(F(aMargin.top), F(aMargin.right), F(aMargin.bottom), + F(aMargin.left)) {} + + bool WithinEpsilonOf(const MarginTyped& aOther, F aEpsilon) const { + return fabs(this->left - aOther.left) < aEpsilon && + fabs(this->top - aOther.top) < aEpsilon && + fabs(this->right - aOther.right) < aEpsilon && + fabs(this->bottom - aOther.bottom) < aEpsilon; + } + + IntMarginTyped<Units> Rounded() const { + return IntMarginTyped<Units>(int32_t(std::floor(this->top + 0.5f)), + int32_t(std::floor(this->right + 0.5f)), + int32_t(std::floor(this->bottom + 0.5f)), + int32_t(std::floor(this->left + 0.5f))); + } +}; +typedef MarginTyped<UnknownUnits> Margin; +typedef MarginTyped<UnknownUnits, double> MarginDouble; + +template <class Units> +IntMarginTyped<Units> RoundedToInt(const MarginTyped<Units>& aMargin) { + return aMargin.Rounded(); +} + +template <class Units> +struct MOZ_EMPTY_BASES IntRectTyped + : public BaseRect<int32_t, IntRectTyped<Units>, IntPointTyped<Units>, + IntSizeTyped<Units>, IntMarginTyped<Units> >, + public Units { + static_assert(IsPixel<Units>::value, + "'Units' must be a coordinate system tag"); + + typedef BaseRect<int32_t, IntRectTyped<Units>, IntPointTyped<Units>, + IntSizeTyped<Units>, IntMarginTyped<Units> > + Super; + typedef IntRectTyped<Units> Self; + typedef IntParam<int32_t> ToInt; + + IntRectTyped() : Super() { + static_assert(sizeof(IntRectTyped) == sizeof(int32_t) * 4, + "Would be unfortunate otherwise!"); + } + IntRectTyped(const IntPointTyped<Units>& aPos, + const IntSizeTyped<Units>& aSize) + : Super(aPos, aSize) {} + + IntRectTyped(ToInt aX, ToInt aY, ToInt aWidth, ToInt aHeight) + : Super(aX.value, aY.value, aWidth.value, aHeight.value) {} + + static IntRectTyped<Units> RoundIn(float aX, float aY, float aW, float aH) { + return IntRectTyped<Units>::RoundIn( + RectTyped<Units, float>(aX, aY, aW, aH)); + } + + static IntRectTyped<Units> RoundOut(float aX, float aY, float aW, float aH) { + return IntRectTyped<Units>::RoundOut( + RectTyped<Units, float>(aX, aY, aW, aH)); + } + + static IntRectTyped<Units> Round(float aX, float aY, float aW, float aH) { + return IntRectTyped<Units>::Round(RectTyped<Units, float>(aX, aY, aW, aH)); + } + + static IntRectTyped<Units> Truncate(float aX, float aY, float aW, float aH) { + return IntRectTyped<Units>(IntPointTyped<Units>::Truncate(aX, aY), + IntSizeTyped<Units>::Truncate(aW, aH)); + } + + static IntRectTyped<Units> RoundIn(const RectTyped<Units, float>& aRect) { + auto tmp(aRect); + tmp.RoundIn(); + return IntRectTyped(int32_t(tmp.X()), int32_t(tmp.Y()), + int32_t(tmp.Width()), int32_t(tmp.Height())); + } + + static IntRectTyped<Units> RoundOut(const RectTyped<Units, float>& aRect) { + auto tmp(aRect); + tmp.RoundOut(); + return IntRectTyped(int32_t(tmp.X()), int32_t(tmp.Y()), + int32_t(tmp.Width()), int32_t(tmp.Height())); + } + + static IntRectTyped<Units> Round(const RectTyped<Units, float>& aRect) { + auto tmp(aRect); + tmp.Round(); + return IntRectTyped(int32_t(tmp.X()), int32_t(tmp.Y()), + int32_t(tmp.Width()), int32_t(tmp.Height())); + } + + static IntRectTyped<Units> Truncate(const RectTyped<Units, float>& aRect) { + return IntRectTyped::Truncate(aRect.X(), aRect.Y(), aRect.Width(), + aRect.Height()); + } + + // Rounding isn't meaningful on an integer rectangle. + void Round() {} + void RoundIn() {} + void RoundOut() {} + + // XXX When all of the code is ported, the following functions to convert + // to and from unknown types should be removed. + + static IntRectTyped<Units> FromUnknownRect( + const IntRectTyped<UnknownUnits>& rect) { + return IntRectTyped<Units>(rect.X(), rect.Y(), rect.Width(), rect.Height()); + } + + IntRectTyped<UnknownUnits> ToUnknownRect() const { + return IntRectTyped<UnknownUnits>(this->X(), this->Y(), this->Width(), + this->Height()); + } + + bool Overflows() const { + CheckedInt<int32_t> xMost = this->X(); + xMost += this->Width(); + CheckedInt<int32_t> yMost = this->Y(); + yMost += this->Height(); + return !xMost.isValid() || !yMost.isValid(); + } + + // Same as Union(), but in the cases where aRect is non-empty, the union is + // done while guarding against overflow. If an overflow is detected, Nothing + // is returned. + [[nodiscard]] Maybe<Self> SafeUnion(const Self& aRect) const { + if (this->IsEmpty()) { + return aRect.Overflows() ? Nothing() : Some(aRect); + } else if (aRect.IsEmpty()) { + return Some(*static_cast<const Self*>(this)); + } else { + return this->SafeUnionEdges(aRect); + } + } + + // Same as UnionEdges, but guards against overflow. If an overflow is + // detected, Nothing is returned. + [[nodiscard]] Maybe<Self> SafeUnionEdges(const Self& aRect) const { + if (this->Overflows() || aRect.Overflows()) { + return Nothing(); + } + // If neither |this| nor |aRect| overflow, then their XMost/YMost values + // should be safe to use. + CheckedInt<int32_t> newX = std::min(this->x, aRect.x); + CheckedInt<int32_t> newY = std::min(this->y, aRect.y); + CheckedInt<int32_t> newXMost = std::max(this->XMost(), aRect.XMost()); + CheckedInt<int32_t> newYMost = std::max(this->YMost(), aRect.YMost()); + CheckedInt<int32_t> newW = newXMost - newX; + CheckedInt<int32_t> newH = newYMost - newY; + if (!newW.isValid() || !newH.isValid()) { + return Nothing(); + } + return Some(Self(newX.value(), newY.value(), newW.value(), newH.value())); + } + + // This is here only to keep IPDL-generated code happy. DO NOT USE. + bool operator==(const IntRectTyped<Units>& aRect) const { + return IntRectTyped<Units>::IsEqualEdges(aRect); + } + + void InflateToMultiple(const IntSizeTyped<Units>& aTileSize) { + if (this->IsEmpty()) { + return; + } + + int32_t yMost = this->YMost(); + int32_t xMost = this->XMost(); + + this->x = mozilla::RoundDownToMultiple(this->x, aTileSize.width); + this->y = mozilla::RoundDownToMultiple(this->y, aTileSize.height); + xMost = mozilla::RoundUpToMultiple(xMost, aTileSize.width); + yMost = mozilla::RoundUpToMultiple(yMost, aTileSize.height); + + this->SetWidth(xMost - this->x); + this->SetHeight(yMost - this->y); + } +}; +typedef IntRectTyped<UnknownUnits> IntRect; + +template <class Units, class F = Float> +struct MOZ_EMPTY_BASES RectTyped + : public BaseRect<F, RectTyped<Units, F>, PointTyped<Units, F>, + SizeTyped<Units, F>, MarginTyped<Units, F> >, + public Units { + static_assert(IsPixel<Units>::value, + "'Units' must be a coordinate system tag"); + + typedef BaseRect<F, RectTyped<Units, F>, PointTyped<Units, F>, + SizeTyped<Units, F>, MarginTyped<Units, F> > + Super; + + RectTyped() : Super() { + static_assert(sizeof(RectTyped) == sizeof(F) * 4, + "Would be unfortunate otherwise!"); + } + RectTyped(const PointTyped<Units, F>& aPos, const SizeTyped<Units, F>& aSize) + : Super(aPos, aSize) {} + RectTyped(F _x, F _y, F _width, F _height) : Super(_x, _y, _width, _height) {} + explicit RectTyped(const IntRectTyped<Units>& rect) + : Super(F(rect.X()), F(rect.Y()), F(rect.Width()), F(rect.Height())) {} + + void NudgeToIntegers() { + NudgeToInteger(&(this->x)); + NudgeToInteger(&(this->y)); + NudgeToInteger(&(this->width)); + NudgeToInteger(&(this->height)); + } + + bool ToIntRect(IntRectTyped<Units>* aOut) const { + *aOut = + IntRectTyped<Units>(int32_t(this->X()), int32_t(this->Y()), + int32_t(this->Width()), int32_t(this->Height())); + return RectTyped<Units, F>(F(aOut->X()), F(aOut->Y()), F(aOut->Width()), + F(aOut->Height())) + .IsEqualEdges(*this); + } + + // XXX When all of the code is ported, the following functions to convert to + // and from unknown types should be removed. + + static RectTyped<Units, F> FromUnknownRect( + const RectTyped<UnknownUnits, F>& rect) { + return RectTyped<Units, F>(rect.X(), rect.Y(), rect.Width(), rect.Height()); + } + + RectTyped<UnknownUnits, F> ToUnknownRect() const { + return RectTyped<UnknownUnits, F>(this->X(), this->Y(), this->Width(), + this->Height()); + } + + // This is here only to keep IPDL-generated code happy. DO NOT USE. + bool operator==(const RectTyped<Units, F>& aRect) const { + return RectTyped<Units, F>::IsEqualEdges(aRect); + } + + bool WithinEpsilonOf(const RectTyped& aOther, F aEpsilon) const { + return fabs(this->x - aOther.x) < aEpsilon && + fabs(this->y - aOther.y) < aEpsilon && + fabs(this->width - aOther.width) < aEpsilon && + fabs(this->height - aOther.height) < aEpsilon; + } +}; +typedef RectTyped<UnknownUnits> Rect; +typedef RectTyped<UnknownUnits, double> RectDouble; + +template <class Units> +IntRectTyped<Units> RoundedToInt(const RectTyped<Units>& aRect) { + RectTyped<Units> copy(aRect); + copy.Round(); + return IntRectTyped<Units>(int32_t(copy.X()), int32_t(copy.Y()), + int32_t(copy.Width()), int32_t(copy.Height())); +} + +template <class Units> +bool RectIsInt32Safe(const RectTyped<Units>& aRect) { + float min = (float)std::numeric_limits<std::int32_t>::min(); + float max = (float)std::numeric_limits<std::int32_t>::max(); + return aRect.x > min && aRect.y > min && aRect.width < max && + aRect.height < max && aRect.XMost() < max && aRect.YMost() < max; +} + +template <class Units> +IntRectTyped<Units> RoundedIn(const RectTyped<Units>& aRect) { + return IntRectTyped<Units>::RoundIn(aRect); +} + +template <class Units> +IntRectTyped<Units> RoundedOut(const RectTyped<Units>& aRect) { + return IntRectTyped<Units>::RoundOut(aRect); +} + +template <class Units> +IntRectTyped<Units> TruncatedToInt(const RectTyped<Units>& aRect) { + return IntRectTyped<Units>::Truncate(aRect); +} + +template <class Units> +RectTyped<Units> IntRectToRect(const IntRectTyped<Units>& aRect) { + return RectTyped<Units>(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); +} + +// Convenience functions for intersecting and unioning two rectangles wrapped in +// Maybes. +template <typename Rect> +Maybe<Rect> IntersectMaybeRects(const Maybe<Rect>& a, const Maybe<Rect>& b) { + if (!a) { + return b; + } else if (!b) { + return a; + } else { + return Some(a->Intersect(*b)); + } +} +template <typename Rect> +Maybe<Rect> UnionMaybeRects(const Maybe<Rect>& a, const Maybe<Rect>& b) { + if (!a) { + return b; + } else if (!b) { + return a; + } else { + return Some(a->Union(*b)); + } +} + +struct RectCornerRadii final { + Size radii[eCornerCount]; + + RectCornerRadii() = default; + + explicit RectCornerRadii(Float radius) { + for (const auto i : mozilla::AllPhysicalCorners()) { + radii[i].SizeTo(radius, radius); + } + } + + RectCornerRadii(Float radiusX, Float radiusY) { + for (const auto i : mozilla::AllPhysicalCorners()) { + radii[i].SizeTo(radiusX, radiusY); + } + } + + RectCornerRadii(Float tl, Float tr, Float br, Float bl) { + radii[eCornerTopLeft].SizeTo(tl, tl); + radii[eCornerTopRight].SizeTo(tr, tr); + radii[eCornerBottomRight].SizeTo(br, br); + radii[eCornerBottomLeft].SizeTo(bl, bl); + } + + RectCornerRadii(const Size& tl, const Size& tr, const Size& br, + const Size& bl) { + radii[eCornerTopLeft] = tl; + radii[eCornerTopRight] = tr; + radii[eCornerBottomRight] = br; + radii[eCornerBottomLeft] = bl; + } + + const Size& operator[](size_t aCorner) const { return radii[aCorner]; } + + Size& operator[](size_t aCorner) { return radii[aCorner]; } + + bool operator==(const RectCornerRadii& aOther) const { + return TopLeft() == aOther.TopLeft() && TopRight() == aOther.TopRight() && + BottomRight() == aOther.BottomRight() && + BottomLeft() == aOther.BottomLeft(); + } + + bool AreRadiiSame() const { + return TopLeft() == TopRight() && TopLeft() == BottomRight() && + TopLeft() == BottomLeft(); + } + + void Scale(Float aXScale, Float aYScale) { + for (const auto i : mozilla::AllPhysicalCorners()) { + radii[i].Scale(aXScale, aYScale); + } + } + + const Size TopLeft() const { return radii[eCornerTopLeft]; } + Size& TopLeft() { return radii[eCornerTopLeft]; } + + const Size TopRight() const { return radii[eCornerTopRight]; } + Size& TopRight() { return radii[eCornerTopRight]; } + + const Size BottomRight() const { return radii[eCornerBottomRight]; } + Size& BottomRight() { return radii[eCornerBottomRight]; } + + const Size BottomLeft() const { return radii[eCornerBottomLeft]; } + Size& BottomLeft() { return radii[eCornerBottomLeft]; } + + bool IsEmpty() const { + return TopLeft().IsEmpty() && TopRight().IsEmpty() && + BottomRight().IsEmpty() && BottomLeft().IsEmpty(); + } +}; + +/* A rounded rectangle abstraction. + * + * This can represent a rectangle with a different pair of radii on each corner. + * + * Note: CoreGraphics and Direct2D only support rounded rectangle with the same + * radii on all corners. However, supporting CSS's border-radius requires the + * extra flexibility. */ +struct RoundedRect { + typedef mozilla::gfx::RectCornerRadii RectCornerRadii; + + RoundedRect(const Rect& aRect, const RectCornerRadii& aCorners) + : rect(aRect), corners(aCorners) {} + + void Deflate(Float aTopWidth, Float aBottomWidth, Float aLeftWidth, + Float aRightWidth) { + // deflate the internal rect + rect.SetRect(rect.X() + aLeftWidth, rect.Y() + aTopWidth, + std::max(0.f, rect.Width() - aLeftWidth - aRightWidth), + std::max(0.f, rect.Height() - aTopWidth - aBottomWidth)); + + corners.radii[mozilla::eCornerTopLeft].width = std::max( + 0.f, corners.radii[mozilla::eCornerTopLeft].width - aLeftWidth); + corners.radii[mozilla::eCornerTopLeft].height = std::max( + 0.f, corners.radii[mozilla::eCornerTopLeft].height - aTopWidth); + + corners.radii[mozilla::eCornerTopRight].width = std::max( + 0.f, corners.radii[mozilla::eCornerTopRight].width - aRightWidth); + corners.radii[mozilla::eCornerTopRight].height = std::max( + 0.f, corners.radii[mozilla::eCornerTopRight].height - aTopWidth); + + corners.radii[mozilla::eCornerBottomLeft].width = std::max( + 0.f, corners.radii[mozilla::eCornerBottomLeft].width - aLeftWidth); + corners.radii[mozilla::eCornerBottomLeft].height = std::max( + 0.f, corners.radii[mozilla::eCornerBottomLeft].height - aBottomWidth); + + corners.radii[mozilla::eCornerBottomRight].width = std::max( + 0.f, corners.radii[mozilla::eCornerBottomRight].width - aRightWidth); + corners.radii[mozilla::eCornerBottomRight].height = std::max( + 0.f, corners.radii[mozilla::eCornerBottomRight].height - aBottomWidth); + } + Rect rect; + RectCornerRadii corners; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_RECT_H_ */ diff --git a/gfx/2d/RectAbsolute.h b/gfx/2d/RectAbsolute.h new file mode 100644 index 0000000000..09da87c80f --- /dev/null +++ b/gfx/2d/RectAbsolute.h @@ -0,0 +1,305 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_RECT_ABSOLUTE_H_ +#define MOZILLA_GFX_RECT_ABSOLUTE_H_ + +#include <algorithm> +#include <cstdint> + +#include "mozilla/Attributes.h" +#include "Point.h" +#include "Rect.h" +#include "Types.h" + +namespace mozilla { + +template <typename> +struct IsPixel; + +namespace gfx { + +/** + * A RectAbsolute is similar to a Rect (see BaseRect.h), but represented as + * (x1, y1, x2, y2) instead of (x, y, width, height). + * + * Unless otherwise indicated, methods on this class correspond + * to methods on BaseRect. + * + * The API is currently very bare-bones; it may be extended as needed. + * + * Do not use this class directly. Subclass it, pass that subclass as the + * Sub parameter, and only use that subclass. + */ +template <class T, class Sub, class Point, class Rect> +struct BaseRectAbsolute { + protected: + T left, top, right, bottom; + + public: + BaseRectAbsolute() : left(0), top(0), right(0), bottom(0) {} + BaseRectAbsolute(T aLeft, T aTop, T aRight, T aBottom) + : left(aLeft), top(aTop), right(aRight), bottom(aBottom) {} + + MOZ_ALWAYS_INLINE T X() const { return left; } + MOZ_ALWAYS_INLINE T Y() const { return top; } + MOZ_ALWAYS_INLINE T Width() const { return right - left; } + MOZ_ALWAYS_INLINE T Height() const { return bottom - top; } + MOZ_ALWAYS_INLINE T XMost() const { return right; } + MOZ_ALWAYS_INLINE T YMost() const { return bottom; } + MOZ_ALWAYS_INLINE const T& Left() const { return left; } + MOZ_ALWAYS_INLINE const T& Right() const { return right; } + MOZ_ALWAYS_INLINE const T& Top() const { return top; } + MOZ_ALWAYS_INLINE const T& Bottom() const { return bottom; } + MOZ_ALWAYS_INLINE T& Left() { return left; } + MOZ_ALWAYS_INLINE T& Right() { return right; } + MOZ_ALWAYS_INLINE T& Top() { return top; } + MOZ_ALWAYS_INLINE T& Bottom() { return bottom; } + T Area() const { return Width() * Height(); } + + void Inflate(T aD) { Inflate(aD, aD); } + void Inflate(T aDx, T aDy) { + left -= aDx; + top -= aDy; + right += aDx; + bottom += aDy; + } + + MOZ_ALWAYS_INLINE void SetBox(T aLeft, T aTop, T aRight, T aBottom) { + left = aLeft; + top = aTop; + right = aRight; + bottom = aBottom; + } + void SetLeftEdge(T aLeft) { left = aLeft; } + void SetRightEdge(T aRight) { right = aRight; } + void SetTopEdge(T aTop) { top = aTop; } + void SetBottomEdge(T aBottom) { bottom = aBottom; } + + static Sub FromRect(const Rect& aRect) { + if (aRect.Overflows()) { + return Sub(); + } + return Sub(aRect.x, aRect.y, aRect.XMost(), aRect.YMost()); + } + + [[nodiscard]] Sub Intersect(const Sub& aOther) const { + Sub result; + result.left = std::max<T>(left, aOther.left); + result.top = std::max<T>(top, aOther.top); + result.right = std::min<T>(right, aOther.right); + result.bottom = std::min<T>(bottom, aOther.bottom); + if (result.right < result.left || result.bottom < result.top) { + result.SizeTo(0, 0); + } + return result; + } + + bool IsEmpty() const { return right <= left || bottom <= top; } + + bool IsEqualEdges(const Sub& aOther) const { + return left == aOther.left && top == aOther.top && right == aOther.right && + bottom == aOther.bottom; + } + + bool IsEqualInterior(const Sub& aRect) const { + return IsEqualEdges(aRect) || (IsEmpty() && aRect.IsEmpty()); + } + + MOZ_ALWAYS_INLINE void MoveBy(T aDx, T aDy) { + left += aDx; + right += aDx; + top += aDy; + bottom += aDy; + } + MOZ_ALWAYS_INLINE void MoveBy(const Point& aPoint) { + left += aPoint.x; + right += aPoint.x; + top += aPoint.y; + bottom += aPoint.y; + } + MOZ_ALWAYS_INLINE void SizeTo(T aWidth, T aHeight) { + right = left + aWidth; + bottom = top + aHeight; + } + + bool Contains(const Sub& aRect) const { + return aRect.IsEmpty() || (left <= aRect.left && aRect.right <= right && + top <= aRect.top && aRect.bottom <= bottom); + } + bool Contains(T aX, T aY) const { + return (left <= aX && aX < right && top <= aY && aY < bottom); + } + + bool Intersects(const Sub& aRect) const { + return !IsEmpty() && !aRect.IsEmpty() && left < aRect.right && + aRect.left < right && top < aRect.bottom && aRect.top < bottom; + } + + void SetEmpty() { left = right = top = bottom = 0; } + + // Returns the smallest rectangle that contains both the area of both + // this and aRect. Thus, empty input rectangles are ignored. + // Note: if both rectangles are empty, returns aRect. + // WARNING! This is not safe against overflow, prefer using SafeUnion instead + // when dealing with int-based rects. + [[nodiscard]] Sub Union(const Sub& aRect) const { + if (IsEmpty()) { + return aRect; + } else if (aRect.IsEmpty()) { + return *static_cast<const Sub*>(this); + } else { + return UnionEdges(aRect); + } + } + // Returns the smallest rectangle that contains both the points (including + // edges) of both aRect1 and aRect2. + // Thus, empty input rectangles are allowed to affect the result. + // WARNING! This is not safe against overflow, prefer using SafeUnionEdges + // instead when dealing with int-based rects. + [[nodiscard]] Sub UnionEdges(const Sub& aRect) const { + Sub result; + result.left = std::min(left, aRect.left); + result.top = std::min(top, aRect.top); + result.right = std::max(XMost(), aRect.XMost()); + result.bottom = std::max(YMost(), aRect.YMost()); + return result; + } + + // Scale 'this' by aScale without doing any rounding. + void Scale(T aScale) { Scale(aScale, aScale); } + // Scale 'this' by aXScale and aYScale, without doing any rounding. + void Scale(T aXScale, T aYScale) { + right = XMost() * aXScale; + bottom = YMost() * aYScale; + left = left * aXScale; + top = top * aYScale; + } + // Scale 'this' by aScale, converting coordinates to integers so that the + // result is the smallest integer-coordinate rectangle containing the + // unrounded result. Note: this can turn an empty rectangle into a non-empty + // rectangle + void ScaleRoundOut(double aScale) { ScaleRoundOut(aScale, aScale); } + // Scale 'this' by aXScale and aYScale, converting coordinates to integers so + // that the result is the smallest integer-coordinate rectangle containing the + // unrounded result. + // Note: this can turn an empty rectangle into a non-empty rectangle + void ScaleRoundOut(double aXScale, double aYScale) { + right = static_cast<T>(ceil(double(XMost()) * aXScale)); + bottom = static_cast<T>(ceil(double(YMost()) * aYScale)); + left = static_cast<T>(floor(double(left) * aXScale)); + top = static_cast<T>(floor(double(top) * aYScale)); + } + // Scale 'this' by aScale, converting coordinates to integers so that the + // result is the largest integer-coordinate rectangle contained by the + // unrounded result. + void ScaleRoundIn(double aScale) { ScaleRoundIn(aScale, aScale); } + // Scale 'this' by aXScale and aYScale, converting coordinates to integers so + // that the result is the largest integer-coordinate rectangle contained by + // the unrounded result. + void ScaleRoundIn(double aXScale, double aYScale) { + right = static_cast<T>(floor(double(XMost()) * aXScale)); + bottom = static_cast<T>(floor(double(YMost()) * aYScale)); + left = static_cast<T>(ceil(double(left) * aXScale)); + top = static_cast<T>(ceil(double(top) * aYScale)); + } + // Scale 'this' by 1/aScale, converting coordinates to integers so that the + // result is the smallest integer-coordinate rectangle containing the + // unrounded result. Note: this can turn an empty rectangle into a non-empty + // rectangle + void ScaleInverseRoundOut(double aScale) { + ScaleInverseRoundOut(aScale, aScale); + } + // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers + // so that the result is the smallest integer-coordinate rectangle containing + // the unrounded result. Note: this can turn an empty rectangle into a + // non-empty rectangle + void ScaleInverseRoundOut(double aXScale, double aYScale) { + right = static_cast<T>(ceil(double(XMost()) / aXScale)); + bottom = static_cast<T>(ceil(double(YMost()) / aYScale)); + left = static_cast<T>(floor(double(left) / aXScale)); + top = static_cast<T>(floor(double(top) / aYScale)); + } + // Scale 'this' by 1/aScale, converting coordinates to integers so that the + // result is the largest integer-coordinate rectangle contained by the + // unrounded result. + void ScaleInverseRoundIn(double aScale) { + ScaleInverseRoundIn(aScale, aScale); + } + // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers + // so that the result is the largest integer-coordinate rectangle contained by + // the unrounded result. + void ScaleInverseRoundIn(double aXScale, double aYScale) { + right = static_cast<T>(floor(double(XMost()) / aXScale)); + bottom = static_cast<T>(floor(double(YMost()) / aYScale)); + left = static_cast<T>(ceil(double(left) / aXScale)); + top = static_cast<T>(ceil(double(top) / aYScale)); + } + + /** + * Translate this rectangle to be inside aRect. If it doesn't fit inside + * aRect then the dimensions that don't fit will be shrunk so that they + * do fit. The resulting rect is returned. + */ + [[nodiscard]] Sub MoveInsideAndClamp(const Sub& aRect) const { + T newLeft = std::max(aRect.left, left); + T newTop = std::max(aRect.top, top); + T width = std::min(aRect.Width(), Width()); + T height = std::min(aRect.Height(), Height()); + Sub rect(newLeft, newTop, newLeft + width, newTop + height); + newLeft = std::min(rect.right, aRect.right) - width; + newTop = std::min(rect.bottom, aRect.bottom) - height; + rect.MoveBy(newLeft - rect.left, newTop - rect.top); + return rect; + } + + friend std::ostream& operator<<( + std::ostream& stream, + const BaseRectAbsolute<T, Sub, Point, Rect>& aRect) { + return stream << "(l=" << aRect.left << ", t=" << aRect.top + << ", r=" << aRect.right << ", b=" << aRect.bottom << ')'; + } +}; + +template <class Units> +struct IntRectAbsoluteTyped + : public BaseRectAbsolute<int32_t, IntRectAbsoluteTyped<Units>, + IntPointTyped<Units>, IntRectTyped<Units>>, + public Units { + static_assert(IsPixel<Units>::value, + "'units' must be a coordinate system tag"); + typedef BaseRectAbsolute<int32_t, IntRectAbsoluteTyped<Units>, + IntPointTyped<Units>, IntRectTyped<Units>> + Super; + typedef IntParam<int32_t> ToInt; + + IntRectAbsoluteTyped() : Super() {} + IntRectAbsoluteTyped(ToInt aLeft, ToInt aTop, ToInt aRight, ToInt aBottom) + : Super(aLeft.value, aTop.value, aRight.value, aBottom.value) {} +}; + +template <class Units> +struct RectAbsoluteTyped + : public BaseRectAbsolute<Float, RectAbsoluteTyped<Units>, + PointTyped<Units>, RectTyped<Units>>, + public Units { + static_assert(IsPixel<Units>::value, + "'units' must be a coordinate system tag"); + typedef BaseRectAbsolute<Float, RectAbsoluteTyped<Units>, PointTyped<Units>, + RectTyped<Units>> + Super; + + RectAbsoluteTyped() : Super() {} + RectAbsoluteTyped(Float aLeft, Float aTop, Float aRight, Float aBottom) + : Super(aLeft, aTop, aRight, aBottom) {} +}; + +typedef IntRectAbsoluteTyped<UnknownUnits> IntRectAbsolute; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_RECT_ABSOLUTE_H_ */ diff --git a/gfx/2d/SFNTData.cpp b/gfx/2d/SFNTData.cpp new file mode 100644 index 0000000000..42cf95c33c --- /dev/null +++ b/gfx/2d/SFNTData.cpp @@ -0,0 +1,206 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SFNTData.h" + +#include <algorithm> +#include <numeric> + +#include "BigEndianInts.h" +#include "Logging.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/Span.h" + +namespace mozilla { +namespace gfx { + +#define TRUETYPE_TAG(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d)) + +#pragma pack(push, 1) + +struct TTCHeader { + BigEndianUint32 ttcTag; // Always 'ttcf' + BigEndianUint32 version; // Fixed, 0x00010000 + BigEndianUint32 numFonts; +}; + +struct OffsetTable { + BigEndianUint32 sfntVersion; // Fixed, 0x00010000 for version 1.0. + BigEndianUint16 numTables; + BigEndianUint16 searchRange; // (Maximum power of 2 <= numTables) x 16. + BigEndianUint16 entrySelector; // Log2(maximum power of 2 <= numTables). + BigEndianUint16 rangeShift; // NumTables x 16-searchRange. +}; + +struct TableDirEntry { + BigEndianUint32 tag; // 4 -byte identifier. + BigEndianUint32 checkSum; // CheckSum for this table. + BigEndianUint32 offset; // Offset from beginning of TrueType font file. + BigEndianUint32 length; // Length of this table. + + friend bool operator<(const TableDirEntry& lhs, const uint32_t aTag) { + return lhs.tag < aTag; + } +}; + +#pragma pack(pop) + +class SFNTData::Font { + public: + Font(const OffsetTable* aOffsetTable, const uint8_t* aFontData, + uint32_t aDataLength) + : mFontData(aFontData), + mFirstDirEntry( + reinterpret_cast<const TableDirEntry*>(aOffsetTable + 1)), + mEndOfDirEntries(mFirstDirEntry + aOffsetTable->numTables), + mDataLength(aDataLength) {} + + Span<const uint8_t> GetHeadTableBytes() const { + const TableDirEntry* dirEntry = + GetDirEntry(TRUETYPE_TAG('h', 'e', 'a', 'd')); + if (!dirEntry) { + gfxWarning() << "Head table entry not found."; + return {}; + } + + return {mFontData + dirEntry->offset, dirEntry->length}; + } + + Span<const uint8_t> GetCmapTableBytes() const { + const TableDirEntry* dirEntry = + GetDirEntry(TRUETYPE_TAG('c', 'm', 'a', 'p')); + if (!dirEntry) { + gfxWarning() << "Cmap table entry not found."; + return {}; + } + + return {mFontData + dirEntry->offset, dirEntry->length}; + } + + private: + const TableDirEntry* GetDirEntry(const uint32_t aTag) const { + const TableDirEntry* foundDirEntry = + std::lower_bound(mFirstDirEntry, mEndOfDirEntries, aTag); + + if (foundDirEntry == mEndOfDirEntries || foundDirEntry->tag != aTag) { + gfxWarning() << "Font data does not contain tag."; + return nullptr; + } + + if (mDataLength < (foundDirEntry->offset + foundDirEntry->length)) { + gfxWarning() << "Font data too short to contain table."; + return nullptr; + } + + return foundDirEntry; + } + + const uint8_t* mFontData; + const TableDirEntry* mFirstDirEntry; + const TableDirEntry* mEndOfDirEntries; + uint32_t mDataLength; +}; + +/* static */ +UniquePtr<SFNTData> SFNTData::Create(const uint8_t* aFontData, + uint32_t aDataLength) { + MOZ_ASSERT(aFontData); + + // Check to see if this is a font collection. + if (aDataLength < sizeof(TTCHeader)) { + gfxWarning() << "Font data too short."; + return nullptr; + } + + const TTCHeader* ttcHeader = reinterpret_cast<const TTCHeader*>(aFontData); + if (ttcHeader->ttcTag == TRUETYPE_TAG('t', 't', 'c', 'f')) { + uint32_t numFonts = ttcHeader->numFonts; + if (aDataLength < + sizeof(TTCHeader) + (numFonts * sizeof(BigEndianUint32))) { + gfxWarning() << "Font data too short to contain full TTC Header."; + return nullptr; + } + + UniquePtr<SFNTData> sfntData(new SFNTData); + const BigEndianUint32* offset = + reinterpret_cast<const BigEndianUint32*>(aFontData + sizeof(TTCHeader)); + const BigEndianUint32* endOfOffsets = offset + numFonts; + while (offset != endOfOffsets) { + if (!sfntData->AddFont(aFontData, aDataLength, *offset)) { + return nullptr; + } + ++offset; + } + + return sfntData; + } + + UniquePtr<SFNTData> sfntData(new SFNTData); + if (!sfntData->AddFont(aFontData, aDataLength, 0)) { + return nullptr; + } + + return sfntData; +} + +/* static */ +uint64_t SFNTData::GetUniqueKey(const uint8_t* aFontData, uint32_t aDataLength, + uint32_t aVarDataSize, const void* aVarData) { + uint64_t hash = 0; + UniquePtr<SFNTData> sfntData = SFNTData::Create(aFontData, aDataLength); + if (sfntData) { + hash = sfntData->HashHeadAndCmapTables(); + } else { + gfxWarning() << "Failed to create SFNTData from data, hashing whole font."; + hash = HashBytes(aFontData, aDataLength); + } + + if (aVarDataSize) { + hash = AddToHash(hash, HashBytes(aVarData, aVarDataSize)); + } + + return hash << 32 | aDataLength; +} + +SFNTData::~SFNTData() { + for (size_t i = 0; i < mFonts.length(); ++i) { + delete mFonts[i]; + } +} + +bool SFNTData::AddFont(const uint8_t* aFontData, uint32_t aDataLength, + uint32_t aOffset) { + uint32_t remainingLength = aDataLength - aOffset; + if (remainingLength < sizeof(OffsetTable)) { + gfxWarning() << "Font data too short to contain OffsetTable " << aOffset; + return false; + } + + const OffsetTable* offsetTable = + reinterpret_cast<const OffsetTable*>(aFontData + aOffset); + if (remainingLength < + sizeof(OffsetTable) + (offsetTable->numTables * sizeof(TableDirEntry))) { + gfxWarning() << "Font data too short to contain tables."; + return false; + } + + return mFonts.append(new Font(offsetTable, aFontData, aDataLength)); +} + +uint32_t SFNTData::HashHeadAndCmapTables() { + uint32_t tablesHash = std::accumulate( + mFonts.begin(), mFonts.end(), 0U, [](uint32_t hash, Font* font) { + Span<const uint8_t> headBytes = font->GetHeadTableBytes(); + hash = AddToHash(hash, HashBytes(headBytes.data(), headBytes.size())); + Span<const uint8_t> cmapBytes = font->GetCmapTableBytes(); + return AddToHash(hash, HashBytes(cmapBytes.data(), cmapBytes.size())); + }); + + return tablesHash; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/SFNTData.h b/gfx/2d/SFNTData.h new file mode 100644 index 0000000000..334438efc0 --- /dev/null +++ b/gfx/2d/SFNTData.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_SFNTData_h +#define mozilla_gfx_SFNTData_h + +#include "mozilla/UniquePtr.h" +#include "mozilla/Vector.h" + +namespace mozilla { +namespace gfx { + +class SFNTData final { + public: + /** + * Creates an SFNTData if the header is a format that we understand and + * aDataLength is sufficient for the length information in the header data. + * Note that the data is NOT copied, so must exist the SFNTData's lifetime. + * + * @param aFontData the SFNT data. + * @param aDataLength length + * @return UniquePtr to a SFNTData or nullptr if the header is invalid. + */ + static UniquePtr<SFNTData> Create(const uint8_t* aFontData, + uint32_t aDataLength); + + /** + * Creates a unique key for the given font data and variation settings. + * + * @param aFontData the SFNT data + * @param aDataLength length + * @return unique key to be used for caching + */ + static uint64_t GetUniqueKey(const uint8_t* aFontData, uint32_t aDataLength, + uint32_t aVarDataSize, const void* aVarData); + + ~SFNTData(); + + private: + SFNTData() = default; + + bool AddFont(const uint8_t* aFontData, uint32_t aDataLength, + uint32_t aOffset); + + uint32_t HashHeadAndCmapTables(); + + // Internal representation of single font in font file. + class Font; + + Vector<Font*> mFonts; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // mozilla_gfx_SFNTData_h diff --git a/gfx/2d/SIMD.h b/gfx/2d/SIMD.h new file mode 100644 index 0000000000..80aca407b4 --- /dev/null +++ b/gfx/2d/SIMD.h @@ -0,0 +1,1039 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_SIMD_H_ +#define _MOZILLA_GFX_SIMD_H_ + +/** + * Consumers of this file need to #define SIMD_COMPILE_SSE2 before including it + * if they want access to the SSE2 functions. + */ + +#ifdef SIMD_COMPILE_SSE2 +# include <xmmintrin.h> +#endif + +namespace mozilla { +namespace gfx { + +namespace simd { + +template <typename u8x16_t> +u8x16_t Load8(const uint8_t* aSource); + +template <typename u8x16_t> +u8x16_t From8(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f, + uint8_t g, uint8_t h, uint8_t i, uint8_t j, uint8_t k, uint8_t l, + uint8_t m, uint8_t n, uint8_t o, uint8_t p); + +template <typename u8x16_t> +u8x16_t FromZero8(); + +template <typename i16x8_t> +i16x8_t FromI16(int16_t a, int16_t b, int16_t c, int16_t d, int16_t e, + int16_t f, int16_t g, int16_t h); + +template <typename u16x8_t> +u16x8_t FromU16(uint16_t a, uint16_t b, uint16_t c, uint16_t d, uint16_t e, + uint16_t f, uint16_t g, uint16_t h); + +template <typename i16x8_t> +i16x8_t FromI16(int16_t a); + +template <typename u16x8_t> +u16x8_t FromU16(uint16_t a); + +template <typename i32x4_t> +i32x4_t From32(int32_t a, int32_t b, int32_t c, int32_t d); + +template <typename i32x4_t> +i32x4_t From32(int32_t a); + +template <typename f32x4_t> +f32x4_t FromF32(float a, float b, float c, float d); + +template <typename f32x4_t> +f32x4_t FromF32(float a); + +// All SIMD backends overload these functions for their SIMD types: + +#if 0 + +// Store 16 bytes to a 16-byte aligned address +void Store8(uint8_t* aTarget, u8x16_t aM); + +// Fixed shifts +template<int32_t aNumberOfBits> i16x8_t ShiftRight16(i16x8_t aM); +template<int32_t aNumberOfBits> i32x4_t ShiftRight32(i32x4_t aM); + +i16x8_t Add16(i16x8_t aM1, i16x8_t aM2); +i32x4_t Add32(i32x4_t aM1, i32x4_t aM2); +i16x8_t Sub16(i16x8_t aM1, i16x8_t aM2); +i32x4_t Sub32(i32x4_t aM1, i32x4_t aM2); +u8x16_t Min8(u8x16_t aM1, iu8x16_t aM2); +u8x16_t Max8(u8x16_t aM1, iu8x16_t aM2); +i32x4_t Min32(i32x4_t aM1, i32x4_t aM2); +i32x4_t Max32(i32x4_t aM1, i32x4_t aM2); + +// Truncating i16 -> i16 multiplication +i16x8_t Mul16(i16x8_t aM1, i16x8_t aM2); + +// Long multiplication i16 -> i32 +// aFactorsA1B1 = (a1[4] b1[4]) +// aFactorsA2B2 = (a2[4] b2[4]) +// aProductA = a1 * a2, aProductB = b1 * b2 +void Mul16x4x2x2To32x4x2(i16x8_t aFactorsA1B1, i16x8_t aFactorsA2B2, + i32x4_t& aProductA, i32x4_t& aProductB); + +// Long multiplication + pairwise addition i16 -> i32 +// See the scalar implementation for specifics. +i32x4_t MulAdd16x8x2To32x4(i16x8_t aFactorsA, i16x8_t aFactorsB); +i32x4_t MulAdd16x8x2To32x4(u16x8_t aFactorsA, u16x8_t aFactorsB); + +// Set all four 32-bit components to the value of the component at aIndex. +template<int8_t aIndex> +i32x4_t Splat32(i32x4_t aM); + +// Interpret the input as four 32-bit values, apply Splat32<aIndex> on them, +// re-interpret the result as sixteen 8-bit values. +template<int8_t aIndex> +u8x16_t Splat32On8(u8x16_t aM); + +template<int8_t i0, int8_t i1, int8_t i2, int8_t i3> i32x4 Shuffle32(i32x4 aM); +template<int8_t i0, int8_t i1, int8_t i2, int8_t i3> i16x8 ShuffleLo16(i16x8 aM); +template<int8_t i0, int8_t i1, int8_t i2, int8_t i3> i16x8 ShuffleHi16(i16x8 aM); + +u8x16_t InterleaveLo8(u8x16_t m1, u8x16_t m2); +u8x16_t InterleaveHi8(u8x16_t m1, u8x16_t m2); +i16x8_t InterleaveLo16(i16x8_t m1, i16x8_t m2); +i16x8_t InterleaveHi16(i16x8_t m1, i16x8_t m2); +i32x4_t InterleaveLo32(i32x4_t m1, i32x4_t m2); + +i16x8_t UnpackLo8x8ToI16x8(u8x16_t m); +i16x8_t UnpackHi8x8ToI16x8(u8x16_t m); +u16x8_t UnpackLo8x8ToU16x8(u8x16_t m); +u16x8_t UnpackHi8x8ToU16x8(u8x16_t m); + +i16x8_t PackAndSaturate32To16(i32x4_t m1, i32x4_t m2); +u8x16_t PackAndSaturate16To8(i16x8_t m1, i16x8_t m2); +u8x16_t PackAndSaturate32To8(i32x4_t m1, i32x4_t m2, i32x4_t m3, const i32x4_t& m4); + +i32x4 FastDivideBy255(i32x4 m); +i16x8 FastDivideBy255_16(i16x8 m); + +#endif + +// Scalar + +struct Scalaru8x16_t { + uint8_t u8[16]; +}; + +union Scalari16x8_t { + int16_t i16[8]; + uint16_t u16[8]; +}; + +typedef Scalari16x8_t Scalaru16x8_t; + +struct Scalari32x4_t { + int32_t i32[4]; +}; + +struct Scalarf32x4_t { + float f32[4]; +}; + +template <> +inline Scalaru8x16_t Load8<Scalaru8x16_t>(const uint8_t* aSource) { + return *(Scalaru8x16_t*)aSource; +} + +inline void Store8(uint8_t* aTarget, Scalaru8x16_t aM) { + *(Scalaru8x16_t*)aTarget = aM; +} + +template <> +inline Scalaru8x16_t From8<Scalaru8x16_t>(uint8_t a, uint8_t b, uint8_t c, + uint8_t d, uint8_t e, uint8_t f, + uint8_t g, uint8_t h, uint8_t i, + uint8_t j, uint8_t k, uint8_t l, + uint8_t m, uint8_t n, uint8_t o, + uint8_t p) { + Scalaru8x16_t _m; + _m.u8[0] = a; + _m.u8[1] = b; + _m.u8[2] = c; + _m.u8[3] = d; + _m.u8[4] = e; + _m.u8[5] = f; + _m.u8[6] = g; + _m.u8[7] = h; + _m.u8[8 + 0] = i; + _m.u8[8 + 1] = j; + _m.u8[8 + 2] = k; + _m.u8[8 + 3] = l; + _m.u8[8 + 4] = m; + _m.u8[8 + 5] = n; + _m.u8[8 + 6] = o; + _m.u8[8 + 7] = p; + return _m; +} + +template <> +inline Scalaru8x16_t FromZero8<Scalaru8x16_t>() { + return From8<Scalaru8x16_t>(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); +} + +template <> +inline Scalari16x8_t FromI16<Scalari16x8_t>(int16_t a, int16_t b, int16_t c, + int16_t d, int16_t e, int16_t f, + int16_t g, int16_t h) { + Scalari16x8_t m; + m.i16[0] = a; + m.i16[1] = b; + m.i16[2] = c; + m.i16[3] = d; + m.i16[4] = e; + m.i16[5] = f; + m.i16[6] = g; + m.i16[7] = h; + return m; +} + +template <> +inline Scalaru16x8_t FromU16<Scalaru16x8_t>(uint16_t a, uint16_t b, uint16_t c, + uint16_t d, uint16_t e, uint16_t f, + uint16_t g, uint16_t h) { + Scalaru16x8_t m; + m.u16[0] = a; + m.u16[1] = b; + m.u16[2] = c; + m.u16[3] = d; + m.u16[4] = e; + m.u16[5] = f; + m.u16[6] = g; + m.u16[7] = h; + return m; +} + +template <> +inline Scalari16x8_t FromI16<Scalari16x8_t>(int16_t a) { + return FromI16<Scalari16x8_t>(a, a, a, a, a, a, a, a); +} + +template <> +inline Scalaru16x8_t FromU16<Scalaru16x8_t>(uint16_t a) { + return FromU16<Scalaru16x8_t>(a, a, a, a, a, a, a, a); +} + +template <> +inline Scalari32x4_t From32<Scalari32x4_t>(int32_t a, int32_t b, int32_t c, + int32_t d) { + Scalari32x4_t m; + m.i32[0] = a; + m.i32[1] = b; + m.i32[2] = c; + m.i32[3] = d; + return m; +} + +template <> +inline Scalarf32x4_t FromF32<Scalarf32x4_t>(float a, float b, float c, + float d) { + Scalarf32x4_t m; + m.f32[0] = a; + m.f32[1] = b; + m.f32[2] = c; + m.f32[3] = d; + return m; +} + +template <> +inline Scalarf32x4_t FromF32<Scalarf32x4_t>(float a) { + return FromF32<Scalarf32x4_t>(a, a, a, a); +} + +template <> +inline Scalari32x4_t From32<Scalari32x4_t>(int32_t a) { + return From32<Scalari32x4_t>(a, a, a, a); +} + +template <int32_t aNumberOfBits> +inline Scalari16x8_t ShiftRight16(Scalari16x8_t aM) { + return FromI16<Scalari16x8_t>(uint16_t(aM.i16[0]) >> aNumberOfBits, + uint16_t(aM.i16[1]) >> aNumberOfBits, + uint16_t(aM.i16[2]) >> aNumberOfBits, + uint16_t(aM.i16[3]) >> aNumberOfBits, + uint16_t(aM.i16[4]) >> aNumberOfBits, + uint16_t(aM.i16[5]) >> aNumberOfBits, + uint16_t(aM.i16[6]) >> aNumberOfBits, + uint16_t(aM.i16[7]) >> aNumberOfBits); +} + +template <int32_t aNumberOfBits> +inline Scalari32x4_t ShiftRight32(Scalari32x4_t aM) { + return From32<Scalari32x4_t>( + aM.i32[0] >> aNumberOfBits, aM.i32[1] >> aNumberOfBits, + aM.i32[2] >> aNumberOfBits, aM.i32[3] >> aNumberOfBits); +} + +inline Scalaru16x8_t Add16(Scalaru16x8_t aM1, Scalaru16x8_t aM2) { + return FromU16<Scalaru16x8_t>( + aM1.u16[0] + aM2.u16[0], aM1.u16[1] + aM2.u16[1], aM1.u16[2] + aM2.u16[2], + aM1.u16[3] + aM2.u16[3], aM1.u16[4] + aM2.u16[4], aM1.u16[5] + aM2.u16[5], + aM1.u16[6] + aM2.u16[6], aM1.u16[7] + aM2.u16[7]); +} + +inline Scalari32x4_t Add32(Scalari32x4_t aM1, Scalari32x4_t aM2) { + return From32<Scalari32x4_t>(aM1.i32[0] + aM2.i32[0], aM1.i32[1] + aM2.i32[1], + aM1.i32[2] + aM2.i32[2], + aM1.i32[3] + aM2.i32[3]); +} + +inline Scalaru16x8_t Sub16(Scalaru16x8_t aM1, Scalaru16x8_t aM2) { + return FromU16<Scalaru16x8_t>( + aM1.u16[0] - aM2.u16[0], aM1.u16[1] - aM2.u16[1], aM1.u16[2] - aM2.u16[2], + aM1.u16[3] - aM2.u16[3], aM1.u16[4] - aM2.u16[4], aM1.u16[5] - aM2.u16[5], + aM1.u16[6] - aM2.u16[6], aM1.u16[7] - aM2.u16[7]); +} + +inline Scalari32x4_t Sub32(Scalari32x4_t aM1, Scalari32x4_t aM2) { + return From32<Scalari32x4_t>(aM1.i32[0] - aM2.i32[0], aM1.i32[1] - aM2.i32[1], + aM1.i32[2] - aM2.i32[2], + aM1.i32[3] - aM2.i32[3]); +} + +inline int32_t umin(int32_t a, int32_t b) { return a - ((a - b) & -(a > b)); } + +inline int32_t umax(int32_t a, int32_t b) { return a - ((a - b) & -(a < b)); } + +inline Scalaru8x16_t Min8(Scalaru8x16_t aM1, Scalaru8x16_t aM2) { + return From8<Scalaru8x16_t>( + umin(aM1.u8[0], aM2.u8[0]), umin(aM1.u8[1], aM2.u8[1]), + umin(aM1.u8[2], aM2.u8[2]), umin(aM1.u8[3], aM2.u8[3]), + umin(aM1.u8[4], aM2.u8[4]), umin(aM1.u8[5], aM2.u8[5]), + umin(aM1.u8[6], aM2.u8[6]), umin(aM1.u8[7], aM2.u8[7]), + umin(aM1.u8[8 + 0], aM2.u8[8 + 0]), umin(aM1.u8[8 + 1], aM2.u8[8 + 1]), + umin(aM1.u8[8 + 2], aM2.u8[8 + 2]), umin(aM1.u8[8 + 3], aM2.u8[8 + 3]), + umin(aM1.u8[8 + 4], aM2.u8[8 + 4]), umin(aM1.u8[8 + 5], aM2.u8[8 + 5]), + umin(aM1.u8[8 + 6], aM2.u8[8 + 6]), umin(aM1.u8[8 + 7], aM2.u8[8 + 7])); +} + +inline Scalaru8x16_t Max8(Scalaru8x16_t aM1, Scalaru8x16_t aM2) { + return From8<Scalaru8x16_t>( + umax(aM1.u8[0], aM2.u8[0]), umax(aM1.u8[1], aM2.u8[1]), + umax(aM1.u8[2], aM2.u8[2]), umax(aM1.u8[3], aM2.u8[3]), + umax(aM1.u8[4], aM2.u8[4]), umax(aM1.u8[5], aM2.u8[5]), + umax(aM1.u8[6], aM2.u8[6]), umax(aM1.u8[7], aM2.u8[7]), + umax(aM1.u8[8 + 0], aM2.u8[8 + 0]), umax(aM1.u8[8 + 1], aM2.u8[8 + 1]), + umax(aM1.u8[8 + 2], aM2.u8[8 + 2]), umax(aM1.u8[8 + 3], aM2.u8[8 + 3]), + umax(aM1.u8[8 + 4], aM2.u8[8 + 4]), umax(aM1.u8[8 + 5], aM2.u8[8 + 5]), + umax(aM1.u8[8 + 6], aM2.u8[8 + 6]), umax(aM1.u8[8 + 7], aM2.u8[8 + 7])); +} + +inline Scalari32x4_t Min32(Scalari32x4_t aM1, Scalari32x4_t aM2) { + return From32<Scalari32x4_t>( + umin(aM1.i32[0], aM2.i32[0]), umin(aM1.i32[1], aM2.i32[1]), + umin(aM1.i32[2], aM2.i32[2]), umin(aM1.i32[3], aM2.i32[3])); +} + +inline Scalari32x4_t Max32(Scalari32x4_t aM1, Scalari32x4_t aM2) { + return From32<Scalari32x4_t>( + umax(aM1.i32[0], aM2.i32[0]), umax(aM1.i32[1], aM2.i32[1]), + umax(aM1.i32[2], aM2.i32[2]), umax(aM1.i32[3], aM2.i32[3])); +} + +inline Scalaru16x8_t Mul16(Scalaru16x8_t aM1, Scalaru16x8_t aM2) { + return FromU16<Scalaru16x8_t>( + uint16_t(int32_t(aM1.u16[0]) * int32_t(aM2.u16[0])), + uint16_t(int32_t(aM1.u16[1]) * int32_t(aM2.u16[1])), + uint16_t(int32_t(aM1.u16[2]) * int32_t(aM2.u16[2])), + uint16_t(int32_t(aM1.u16[3]) * int32_t(aM2.u16[3])), + uint16_t(int32_t(aM1.u16[4]) * int32_t(aM2.u16[4])), + uint16_t(int32_t(aM1.u16[5]) * int32_t(aM2.u16[5])), + uint16_t(int32_t(aM1.u16[6]) * int32_t(aM2.u16[6])), + uint16_t(int32_t(aM1.u16[7]) * int32_t(aM2.u16[7]))); +} + +inline void Mul16x4x2x2To32x4x2(Scalari16x8_t aFactorsA1B1, + Scalari16x8_t aFactorsA2B2, + Scalari32x4_t& aProductA, + Scalari32x4_t& aProductB) { + aProductA = From32<Scalari32x4_t>(aFactorsA1B1.i16[0] * aFactorsA2B2.i16[0], + aFactorsA1B1.i16[1] * aFactorsA2B2.i16[1], + aFactorsA1B1.i16[2] * aFactorsA2B2.i16[2], + aFactorsA1B1.i16[3] * aFactorsA2B2.i16[3]); + aProductB = From32<Scalari32x4_t>(aFactorsA1B1.i16[4] * aFactorsA2B2.i16[4], + aFactorsA1B1.i16[5] * aFactorsA2B2.i16[5], + aFactorsA1B1.i16[6] * aFactorsA2B2.i16[6], + aFactorsA1B1.i16[7] * aFactorsA2B2.i16[7]); +} + +inline Scalari32x4_t MulAdd16x8x2To32x4(Scalari16x8_t aFactorsA, + Scalari16x8_t aFactorsB) { + return From32<Scalari32x4_t>( + aFactorsA.i16[0] * aFactorsB.i16[0] + aFactorsA.i16[1] * aFactorsB.i16[1], + aFactorsA.i16[2] * aFactorsB.i16[2] + aFactorsA.i16[3] * aFactorsB.i16[3], + aFactorsA.i16[4] * aFactorsB.i16[4] + aFactorsA.i16[5] * aFactorsB.i16[5], + aFactorsA.i16[6] * aFactorsB.i16[6] + + aFactorsA.i16[7] * aFactorsB.i16[7]); +} + +template <int8_t aIndex> +inline void AssertIndex() { + static_assert(aIndex == 0 || aIndex == 1 || aIndex == 2 || aIndex == 3, + "Invalid splat index"); +} + +template <int8_t aIndex> +inline Scalari32x4_t Splat32(Scalari32x4_t aM) { + AssertIndex<aIndex>(); + return From32<Scalari32x4_t>(aM.i32[aIndex], aM.i32[aIndex], aM.i32[aIndex], + aM.i32[aIndex]); +} + +template <int8_t i> +inline Scalaru8x16_t Splat32On8(Scalaru8x16_t aM) { + AssertIndex<i>(); + return From8<Scalaru8x16_t>( + aM.u8[i * 4], aM.u8[i * 4 + 1], aM.u8[i * 4 + 2], aM.u8[i * 4 + 3], + aM.u8[i * 4], aM.u8[i * 4 + 1], aM.u8[i * 4 + 2], aM.u8[i * 4 + 3], + aM.u8[i * 4], aM.u8[i * 4 + 1], aM.u8[i * 4 + 2], aM.u8[i * 4 + 3], + aM.u8[i * 4], aM.u8[i * 4 + 1], aM.u8[i * 4 + 2], aM.u8[i * 4 + 3]); +} + +template <int8_t i0, int8_t i1, int8_t i2, int8_t i3> +inline Scalari32x4_t Shuffle32(Scalari32x4_t aM) { + AssertIndex<i0>(); + AssertIndex<i1>(); + AssertIndex<i2>(); + AssertIndex<i3>(); + Scalari32x4_t m = aM; + m.i32[0] = aM.i32[i3]; + m.i32[1] = aM.i32[i2]; + m.i32[2] = aM.i32[i1]; + m.i32[3] = aM.i32[i0]; + return m; +} + +template <int8_t i0, int8_t i1, int8_t i2, int8_t i3> +inline Scalari16x8_t ShuffleLo16(Scalari16x8_t aM) { + AssertIndex<i0>(); + AssertIndex<i1>(); + AssertIndex<i2>(); + AssertIndex<i3>(); + Scalari16x8_t m = aM; + m.i16[0] = aM.i16[i3]; + m.i16[1] = aM.i16[i2]; + m.i16[2] = aM.i16[i1]; + m.i16[3] = aM.i16[i0]; + return m; +} + +template <int8_t i0, int8_t i1, int8_t i2, int8_t i3> +inline Scalari16x8_t ShuffleHi16(Scalari16x8_t aM) { + AssertIndex<i0>(); + AssertIndex<i1>(); + AssertIndex<i2>(); + AssertIndex<i3>(); + Scalari16x8_t m = aM; + m.i16[4 + 0] = aM.i16[4 + i3]; + m.i16[4 + 1] = aM.i16[4 + i2]; + m.i16[4 + 2] = aM.i16[4 + i1]; + m.i16[4 + 3] = aM.i16[4 + i0]; + return m; +} + +template <int8_t aIndexLo, int8_t aIndexHi> +inline Scalaru16x8_t Splat16(Scalaru16x8_t aM) { + AssertIndex<aIndexLo>(); + AssertIndex<aIndexHi>(); + Scalaru16x8_t m; + int16_t chosenValueLo = aM.u16[aIndexLo]; + m.u16[0] = chosenValueLo; + m.u16[1] = chosenValueLo; + m.u16[2] = chosenValueLo; + m.u16[3] = chosenValueLo; + int16_t chosenValueHi = aM.u16[4 + aIndexHi]; + m.u16[4] = chosenValueHi; + m.u16[5] = chosenValueHi; + m.u16[6] = chosenValueHi; + m.u16[7] = chosenValueHi; + return m; +} + +inline Scalaru8x16_t InterleaveLo8(Scalaru8x16_t m1, Scalaru8x16_t m2) { + return From8<Scalaru8x16_t>(m1.u8[0], m2.u8[0], m1.u8[1], m2.u8[1], m1.u8[2], + m2.u8[2], m1.u8[3], m2.u8[3], m1.u8[4], m2.u8[4], + m1.u8[5], m2.u8[5], m1.u8[6], m2.u8[6], m1.u8[7], + m2.u8[7]); +} + +inline Scalaru8x16_t InterleaveHi8(Scalaru8x16_t m1, Scalaru8x16_t m2) { + return From8<Scalaru8x16_t>( + m1.u8[8 + 0], m2.u8[8 + 0], m1.u8[8 + 1], m2.u8[8 + 1], m1.u8[8 + 2], + m2.u8[8 + 2], m1.u8[8 + 3], m2.u8[8 + 3], m1.u8[8 + 4], m2.u8[8 + 4], + m1.u8[8 + 5], m2.u8[8 + 5], m1.u8[8 + 6], m2.u8[8 + 6], m1.u8[8 + 7], + m2.u8[8 + 7]); +} + +inline Scalaru16x8_t InterleaveLo16(Scalaru16x8_t m1, Scalaru16x8_t m2) { + return FromU16<Scalaru16x8_t>(m1.u16[0], m2.u16[0], m1.u16[1], m2.u16[1], + m1.u16[2], m2.u16[2], m1.u16[3], m2.u16[3]); +} + +inline Scalaru16x8_t InterleaveHi16(Scalaru16x8_t m1, Scalaru16x8_t m2) { + return FromU16<Scalaru16x8_t>(m1.u16[4], m2.u16[4], m1.u16[5], m2.u16[5], + m1.u16[6], m2.u16[6], m1.u16[7], m2.u16[7]); +} + +inline Scalari32x4_t InterleaveLo32(Scalari32x4_t m1, Scalari32x4_t m2) { + return From32<Scalari32x4_t>(m1.i32[0], m2.i32[0], m1.i32[1], m2.i32[1]); +} + +inline Scalari16x8_t UnpackLo8x8ToI16x8(Scalaru8x16_t aM) { + Scalari16x8_t m; + m.i16[0] = aM.u8[0]; + m.i16[1] = aM.u8[1]; + m.i16[2] = aM.u8[2]; + m.i16[3] = aM.u8[3]; + m.i16[4] = aM.u8[4]; + m.i16[5] = aM.u8[5]; + m.i16[6] = aM.u8[6]; + m.i16[7] = aM.u8[7]; + return m; +} + +inline Scalari16x8_t UnpackHi8x8ToI16x8(Scalaru8x16_t aM) { + Scalari16x8_t m; + m.i16[0] = aM.u8[8 + 0]; + m.i16[1] = aM.u8[8 + 1]; + m.i16[2] = aM.u8[8 + 2]; + m.i16[3] = aM.u8[8 + 3]; + m.i16[4] = aM.u8[8 + 4]; + m.i16[5] = aM.u8[8 + 5]; + m.i16[6] = aM.u8[8 + 6]; + m.i16[7] = aM.u8[8 + 7]; + return m; +} + +inline Scalaru16x8_t UnpackLo8x8ToU16x8(Scalaru8x16_t aM) { + return FromU16<Scalaru16x8_t>(uint16_t(aM.u8[0]), uint16_t(aM.u8[1]), + uint16_t(aM.u8[2]), uint16_t(aM.u8[3]), + uint16_t(aM.u8[4]), uint16_t(aM.u8[5]), + uint16_t(aM.u8[6]), uint16_t(aM.u8[7])); +} + +inline Scalaru16x8_t UnpackHi8x8ToU16x8(Scalaru8x16_t aM) { + return FromU16<Scalaru16x8_t>(aM.u8[8 + 0], aM.u8[8 + 1], aM.u8[8 + 2], + aM.u8[8 + 3], aM.u8[8 + 4], aM.u8[8 + 5], + aM.u8[8 + 6], aM.u8[8 + 7]); +} + +template <uint8_t aNumBytes> +inline Scalaru8x16_t Rotate8(Scalaru8x16_t a1234, Scalaru8x16_t a5678) { + Scalaru8x16_t m; + for (uint8_t i = 0; i < 16; i++) { + uint8_t sourceByte = i + aNumBytes; + m.u8[i] = + sourceByte < 16 ? a1234.u8[sourceByte] : a5678.u8[sourceByte - 16]; + } + return m; +} + +template <typename T> +inline int16_t SaturateTo16(T a) { + return int16_t(a >= INT16_MIN ? (a <= INT16_MAX ? a : INT16_MAX) : INT16_MIN); +} + +inline Scalari16x8_t PackAndSaturate32To16(Scalari32x4_t m1, Scalari32x4_t m2) { + Scalari16x8_t m; + m.i16[0] = SaturateTo16(m1.i32[0]); + m.i16[1] = SaturateTo16(m1.i32[1]); + m.i16[2] = SaturateTo16(m1.i32[2]); + m.i16[3] = SaturateTo16(m1.i32[3]); + m.i16[4] = SaturateTo16(m2.i32[0]); + m.i16[5] = SaturateTo16(m2.i32[1]); + m.i16[6] = SaturateTo16(m2.i32[2]); + m.i16[7] = SaturateTo16(m2.i32[3]); + return m; +} + +template <typename T> +inline uint16_t SaturateToU16(T a) { + return uint16_t(umin(a & -(a >= 0), INT16_MAX)); +} + +inline Scalaru16x8_t PackAndSaturate32ToU16(Scalari32x4_t m1, + Scalari32x4_t m2) { + Scalaru16x8_t m; + m.u16[0] = SaturateToU16(m1.i32[0]); + m.u16[1] = SaturateToU16(m1.i32[1]); + m.u16[2] = SaturateToU16(m1.i32[2]); + m.u16[3] = SaturateToU16(m1.i32[3]); + m.u16[4] = SaturateToU16(m2.i32[0]); + m.u16[5] = SaturateToU16(m2.i32[1]); + m.u16[6] = SaturateToU16(m2.i32[2]); + m.u16[7] = SaturateToU16(m2.i32[3]); + return m; +} + +template <typename T> +inline uint8_t SaturateTo8(T a) { + return uint8_t(umin(a & -(a >= 0), 255)); +} + +inline Scalaru8x16_t PackAndSaturate32To8(Scalari32x4_t m1, Scalari32x4_t m2, + Scalari32x4_t m3, + const Scalari32x4_t& m4) { + Scalaru8x16_t m; + m.u8[0] = SaturateTo8(m1.i32[0]); + m.u8[1] = SaturateTo8(m1.i32[1]); + m.u8[2] = SaturateTo8(m1.i32[2]); + m.u8[3] = SaturateTo8(m1.i32[3]); + m.u8[4] = SaturateTo8(m2.i32[0]); + m.u8[5] = SaturateTo8(m2.i32[1]); + m.u8[6] = SaturateTo8(m2.i32[2]); + m.u8[7] = SaturateTo8(m2.i32[3]); + m.u8[8] = SaturateTo8(m3.i32[0]); + m.u8[9] = SaturateTo8(m3.i32[1]); + m.u8[10] = SaturateTo8(m3.i32[2]); + m.u8[11] = SaturateTo8(m3.i32[3]); + m.u8[12] = SaturateTo8(m4.i32[0]); + m.u8[13] = SaturateTo8(m4.i32[1]); + m.u8[14] = SaturateTo8(m4.i32[2]); + m.u8[15] = SaturateTo8(m4.i32[3]); + return m; +} + +inline Scalaru8x16_t PackAndSaturate16To8(Scalari16x8_t m1, Scalari16x8_t m2) { + Scalaru8x16_t m; + m.u8[0] = SaturateTo8(m1.i16[0]); + m.u8[1] = SaturateTo8(m1.i16[1]); + m.u8[2] = SaturateTo8(m1.i16[2]); + m.u8[3] = SaturateTo8(m1.i16[3]); + m.u8[4] = SaturateTo8(m1.i16[4]); + m.u8[5] = SaturateTo8(m1.i16[5]); + m.u8[6] = SaturateTo8(m1.i16[6]); + m.u8[7] = SaturateTo8(m1.i16[7]); + m.u8[8] = SaturateTo8(m2.i16[0]); + m.u8[9] = SaturateTo8(m2.i16[1]); + m.u8[10] = SaturateTo8(m2.i16[2]); + m.u8[11] = SaturateTo8(m2.i16[3]); + m.u8[12] = SaturateTo8(m2.i16[4]); + m.u8[13] = SaturateTo8(m2.i16[5]); + m.u8[14] = SaturateTo8(m2.i16[6]); + m.u8[15] = SaturateTo8(m2.i16[7]); + return m; +} + +// Fast approximate division by 255. It has the property that +// for all 0 <= n <= 255*255, FAST_DIVIDE_BY_255(n) == n/255. +// But it only uses two adds and two shifts instead of an +// integer division (which is expensive on many processors). +// +// equivalent to v/255 +template <class B, class A> +inline B FastDivideBy255(A v) { + return ((v << 8) + v + 255) >> 16; +} + +inline Scalaru16x8_t FastDivideBy255_16(Scalaru16x8_t m) { + return FromU16<Scalaru16x8_t>(FastDivideBy255<uint16_t>(int32_t(m.u16[0])), + FastDivideBy255<uint16_t>(int32_t(m.u16[1])), + FastDivideBy255<uint16_t>(int32_t(m.u16[2])), + FastDivideBy255<uint16_t>(int32_t(m.u16[3])), + FastDivideBy255<uint16_t>(int32_t(m.u16[4])), + FastDivideBy255<uint16_t>(int32_t(m.u16[5])), + FastDivideBy255<uint16_t>(int32_t(m.u16[6])), + FastDivideBy255<uint16_t>(int32_t(m.u16[7]))); +} + +inline Scalari32x4_t FastDivideBy255(Scalari32x4_t m) { + return From32<Scalari32x4_t>( + FastDivideBy255<int32_t>(m.i32[0]), FastDivideBy255<int32_t>(m.i32[1]), + FastDivideBy255<int32_t>(m.i32[2]), FastDivideBy255<int32_t>(m.i32[3])); +} + +inline Scalaru8x16_t Pick(Scalaru8x16_t mask, Scalaru8x16_t a, + Scalaru8x16_t b) { + return From8<Scalaru8x16_t>( + (a.u8[0] & (~mask.u8[0])) | (b.u8[0] & mask.u8[0]), + (a.u8[1] & (~mask.u8[1])) | (b.u8[1] & mask.u8[1]), + (a.u8[2] & (~mask.u8[2])) | (b.u8[2] & mask.u8[2]), + (a.u8[3] & (~mask.u8[3])) | (b.u8[3] & mask.u8[3]), + (a.u8[4] & (~mask.u8[4])) | (b.u8[4] & mask.u8[4]), + (a.u8[5] & (~mask.u8[5])) | (b.u8[5] & mask.u8[5]), + (a.u8[6] & (~mask.u8[6])) | (b.u8[6] & mask.u8[6]), + (a.u8[7] & (~mask.u8[7])) | (b.u8[7] & mask.u8[7]), + (a.u8[8 + 0] & (~mask.u8[8 + 0])) | (b.u8[8 + 0] & mask.u8[8 + 0]), + (a.u8[8 + 1] & (~mask.u8[8 + 1])) | (b.u8[8 + 1] & mask.u8[8 + 1]), + (a.u8[8 + 2] & (~mask.u8[8 + 2])) | (b.u8[8 + 2] & mask.u8[8 + 2]), + (a.u8[8 + 3] & (~mask.u8[8 + 3])) | (b.u8[8 + 3] & mask.u8[8 + 3]), + (a.u8[8 + 4] & (~mask.u8[8 + 4])) | (b.u8[8 + 4] & mask.u8[8 + 4]), + (a.u8[8 + 5] & (~mask.u8[8 + 5])) | (b.u8[8 + 5] & mask.u8[8 + 5]), + (a.u8[8 + 6] & (~mask.u8[8 + 6])) | (b.u8[8 + 6] & mask.u8[8 + 6]), + (a.u8[8 + 7] & (~mask.u8[8 + 7])) | (b.u8[8 + 7] & mask.u8[8 + 7])); +} + +inline Scalari32x4_t Pick(Scalari32x4_t mask, Scalari32x4_t a, + Scalari32x4_t b) { + return From32<Scalari32x4_t>( + (a.i32[0] & (~mask.i32[0])) | (b.i32[0] & mask.i32[0]), + (a.i32[1] & (~mask.i32[1])) | (b.i32[1] & mask.i32[1]), + (a.i32[2] & (~mask.i32[2])) | (b.i32[2] & mask.i32[2]), + (a.i32[3] & (~mask.i32[3])) | (b.i32[3] & mask.i32[3])); +} + +inline Scalarf32x4_t MixF32(Scalarf32x4_t a, Scalarf32x4_t b, float t) { + return FromF32<Scalarf32x4_t>(a.f32[0] + (b.f32[0] - a.f32[0]) * t, + a.f32[1] + (b.f32[1] - a.f32[1]) * t, + a.f32[2] + (b.f32[2] - a.f32[2]) * t, + a.f32[3] + (b.f32[3] - a.f32[3]) * t); +} + +inline Scalarf32x4_t WSumF32(Scalarf32x4_t a, Scalarf32x4_t b, float wa, + float wb) { + return FromF32<Scalarf32x4_t>( + a.f32[0] * wa + b.f32[0] * wb, a.f32[1] * wa + b.f32[1] * wb, + a.f32[2] * wa + b.f32[2] * wb, a.f32[3] * wa + b.f32[3] * wb); +} + +inline Scalarf32x4_t AbsF32(Scalarf32x4_t a) { + return FromF32<Scalarf32x4_t>(fabs(a.f32[0]), fabs(a.f32[1]), fabs(a.f32[2]), + fabs(a.f32[3])); +} + +inline Scalarf32x4_t AddF32(Scalarf32x4_t a, Scalarf32x4_t b) { + return FromF32<Scalarf32x4_t>(a.f32[0] + b.f32[0], a.f32[1] + b.f32[1], + a.f32[2] + b.f32[2], a.f32[3] + b.f32[3]); +} + +inline Scalarf32x4_t MulF32(Scalarf32x4_t a, Scalarf32x4_t b) { + return FromF32<Scalarf32x4_t>(a.f32[0] * b.f32[0], a.f32[1] * b.f32[1], + a.f32[2] * b.f32[2], a.f32[3] * b.f32[3]); +} + +inline Scalarf32x4_t DivF32(Scalarf32x4_t a, Scalarf32x4_t b) { + return FromF32<Scalarf32x4_t>(a.f32[0] / b.f32[0], a.f32[1] / b.f32[1], + a.f32[2] / b.f32[2], a.f32[3] / b.f32[3]); +} + +template <uint8_t aIndex> +inline Scalarf32x4_t SplatF32(Scalarf32x4_t m) { + AssertIndex<aIndex>(); + return FromF32<Scalarf32x4_t>(m.f32[aIndex], m.f32[aIndex], m.f32[aIndex], + m.f32[aIndex]); +} + +inline Scalari32x4_t F32ToI32(Scalarf32x4_t m) { + return From32<Scalari32x4_t>( + int32_t(floor(m.f32[0] + 0.5f)), int32_t(floor(m.f32[1] + 0.5f)), + int32_t(floor(m.f32[2] + 0.5f)), int32_t(floor(m.f32[3] + 0.5f))); +} + +#ifdef SIMD_COMPILE_SSE2 + +// SSE2 + +template <> +inline __m128i Load8<__m128i>(const uint8_t* aSource) { + return _mm_load_si128((const __m128i*)aSource); +} + +inline void Store8(uint8_t* aTarget, __m128i aM) { + _mm_store_si128((__m128i*)aTarget, aM); +} + +template <> +inline __m128i FromZero8<__m128i>() { + return _mm_setzero_si128(); +} + +template <> +inline __m128i From8<__m128i>(uint8_t a, uint8_t b, uint8_t c, uint8_t d, + uint8_t e, uint8_t f, uint8_t g, uint8_t h, + uint8_t i, uint8_t j, uint8_t k, uint8_t l, + uint8_t m, uint8_t n, uint8_t o, uint8_t p) { + return _mm_setr_epi16((b << 8) + a, (d << 8) + c, (e << 8) + f, (h << 8) + g, + (j << 8) + i, (l << 8) + k, (m << 8) + n, (p << 8) + o); +} + +template <> +inline __m128i FromI16<__m128i>(int16_t a, int16_t b, int16_t c, int16_t d, + int16_t e, int16_t f, int16_t g, int16_t h) { + return _mm_setr_epi16(a, b, c, d, e, f, g, h); +} + +template <> +inline __m128i FromU16<__m128i>(uint16_t a, uint16_t b, uint16_t c, uint16_t d, + uint16_t e, uint16_t f, uint16_t g, + uint16_t h) { + return _mm_setr_epi16(a, b, c, d, e, f, g, h); +} + +template <> +inline __m128i FromI16<__m128i>(int16_t a) { + return _mm_set1_epi16(a); +} + +template <> +inline __m128i FromU16<__m128i>(uint16_t a) { + return _mm_set1_epi16((int16_t)a); +} + +template <> +inline __m128i From32<__m128i>(int32_t a, int32_t b, int32_t c, int32_t d) { + return _mm_setr_epi32(a, b, c, d); +} + +template <> +inline __m128i From32<__m128i>(int32_t a) { + return _mm_set1_epi32(a); +} + +template <> +inline __m128 FromF32<__m128>(float a, float b, float c, float d) { + return _mm_setr_ps(a, b, c, d); +} + +template <> +inline __m128 FromF32<__m128>(float a) { + return _mm_set1_ps(a); +} + +template <int32_t aNumberOfBits> +inline __m128i ShiftRight16(__m128i aM) { + return _mm_srli_epi16(aM, aNumberOfBits); +} + +template <int32_t aNumberOfBits> +inline __m128i ShiftRight32(__m128i aM) { + return _mm_srai_epi32(aM, aNumberOfBits); +} + +inline __m128i Add16(__m128i aM1, __m128i aM2) { + return _mm_add_epi16(aM1, aM2); +} + +inline __m128i Add32(__m128i aM1, __m128i aM2) { + return _mm_add_epi32(aM1, aM2); +} + +inline __m128i Sub16(__m128i aM1, __m128i aM2) { + return _mm_sub_epi16(aM1, aM2); +} + +inline __m128i Sub32(__m128i aM1, __m128i aM2) { + return _mm_sub_epi32(aM1, aM2); +} + +inline __m128i Min8(__m128i aM1, __m128i aM2) { return _mm_min_epu8(aM1, aM2); } + +inline __m128i Max8(__m128i aM1, __m128i aM2) { return _mm_max_epu8(aM1, aM2); } + +inline __m128i Min32(__m128i aM1, __m128i aM2) { + __m128i m1_minus_m2 = _mm_sub_epi32(aM1, aM2); + __m128i m1_greater_than_m2 = _mm_cmpgt_epi32(aM1, aM2); + return _mm_sub_epi32(aM1, _mm_and_si128(m1_minus_m2, m1_greater_than_m2)); +} + +inline __m128i Max32(__m128i aM1, __m128i aM2) { + __m128i m1_minus_m2 = _mm_sub_epi32(aM1, aM2); + __m128i m2_greater_than_m1 = _mm_cmpgt_epi32(aM2, aM1); + return _mm_sub_epi32(aM1, _mm_and_si128(m1_minus_m2, m2_greater_than_m1)); +} + +inline __m128i Mul16(__m128i aM1, __m128i aM2) { + return _mm_mullo_epi16(aM1, aM2); +} + +inline __m128i MulU16(__m128i aM1, __m128i aM2) { + return _mm_mullo_epi16(aM1, aM2); +} + +inline void Mul16x4x2x2To32x4x2(__m128i aFactorsA1B1, __m128i aFactorsA2B2, + __m128i& aProductA, __m128i& aProductB) { + __m128i prodAB_lo = _mm_mullo_epi16(aFactorsA1B1, aFactorsA2B2); + __m128i prodAB_hi = _mm_mulhi_epi16(aFactorsA1B1, aFactorsA2B2); + aProductA = _mm_unpacklo_epi16(prodAB_lo, prodAB_hi); + aProductB = _mm_unpackhi_epi16(prodAB_lo, prodAB_hi); +} + +inline __m128i MulAdd16x8x2To32x4(__m128i aFactorsA, __m128i aFactorsB) { + return _mm_madd_epi16(aFactorsA, aFactorsB); +} + +template <int8_t i0, int8_t i1, int8_t i2, int8_t i3> +inline __m128i Shuffle32(__m128i aM) { + AssertIndex<i0>(); + AssertIndex<i1>(); + AssertIndex<i2>(); + AssertIndex<i3>(); + return _mm_shuffle_epi32(aM, _MM_SHUFFLE(i0, i1, i2, i3)); +} + +template <int8_t i0, int8_t i1, int8_t i2, int8_t i3> +inline __m128i ShuffleLo16(__m128i aM) { + AssertIndex<i0>(); + AssertIndex<i1>(); + AssertIndex<i2>(); + AssertIndex<i3>(); + return _mm_shufflelo_epi16(aM, _MM_SHUFFLE(i0, i1, i2, i3)); +} + +template <int8_t i0, int8_t i1, int8_t i2, int8_t i3> +inline __m128i ShuffleHi16(__m128i aM) { + AssertIndex<i0>(); + AssertIndex<i1>(); + AssertIndex<i2>(); + AssertIndex<i3>(); + return _mm_shufflehi_epi16(aM, _MM_SHUFFLE(i0, i1, i2, i3)); +} + +template <int8_t aIndex> +inline __m128i Splat32(__m128i aM) { + return Shuffle32<aIndex, aIndex, aIndex, aIndex>(aM); +} + +template <int8_t aIndex> +inline __m128i Splat32On8(__m128i aM) { + return Shuffle32<aIndex, aIndex, aIndex, aIndex>(aM); +} + +template <int8_t aIndexLo, int8_t aIndexHi> +inline __m128i Splat16(__m128i aM) { + AssertIndex<aIndexLo>(); + AssertIndex<aIndexHi>(); + return ShuffleHi16<aIndexHi, aIndexHi, aIndexHi, aIndexHi>( + ShuffleLo16<aIndexLo, aIndexLo, aIndexLo, aIndexLo>(aM)); +} + +inline __m128i UnpackLo8x8ToI16x8(__m128i m) { + __m128i zero = _mm_set1_epi8(0); + return _mm_unpacklo_epi8(m, zero); +} + +inline __m128i UnpackHi8x8ToI16x8(__m128i m) { + __m128i zero = _mm_set1_epi8(0); + return _mm_unpackhi_epi8(m, zero); +} + +inline __m128i UnpackLo8x8ToU16x8(__m128i m) { + __m128i zero = _mm_set1_epi8(0); + return _mm_unpacklo_epi8(m, zero); +} + +inline __m128i UnpackHi8x8ToU16x8(__m128i m) { + __m128i zero = _mm_set1_epi8(0); + return _mm_unpackhi_epi8(m, zero); +} + +inline __m128i InterleaveLo8(__m128i m1, __m128i m2) { + return _mm_unpacklo_epi8(m1, m2); +} + +inline __m128i InterleaveHi8(__m128i m1, __m128i m2) { + return _mm_unpackhi_epi8(m1, m2); +} + +inline __m128i InterleaveLo16(__m128i m1, __m128i m2) { + return _mm_unpacklo_epi16(m1, m2); +} + +inline __m128i InterleaveHi16(__m128i m1, __m128i m2) { + return _mm_unpackhi_epi16(m1, m2); +} + +inline __m128i InterleaveLo32(__m128i m1, __m128i m2) { + return _mm_unpacklo_epi32(m1, m2); +} + +template <uint8_t aNumBytes> +inline __m128i Rotate8(__m128i a1234, __m128i a5678) { + return _mm_or_si128(_mm_srli_si128(a1234, aNumBytes), + _mm_slli_si128(a5678, 16 - aNumBytes)); +} + +inline __m128i PackAndSaturate32To16(__m128i m1, __m128i m2) { + return _mm_packs_epi32(m1, m2); +} + +inline __m128i PackAndSaturate32ToU16(__m128i m1, __m128i m2) { + return _mm_packs_epi32(m1, m2); +} + +inline __m128i PackAndSaturate32To8(__m128i m1, __m128i m2, __m128i m3, + const __m128i& m4) { + // Pack into 8 16bit signed integers (saturating). + __m128i m12 = _mm_packs_epi32(m1, m2); + __m128i m34 = _mm_packs_epi32(m3, m4); + + // Pack into 16 8bit unsigned integers (saturating). + return _mm_packus_epi16(m12, m34); +} + +inline __m128i PackAndSaturate16To8(__m128i m1, __m128i m2) { + // Pack into 16 8bit unsigned integers (saturating). + return _mm_packus_epi16(m1, m2); +} + +inline __m128i FastDivideBy255(__m128i m) { + // v = m << 8 + __m128i v = _mm_slli_epi32(m, 8); + // v = v + (m + (255,255,255,255)) + v = _mm_add_epi32(v, _mm_add_epi32(m, _mm_set1_epi32(255))); + // v = v >> 16 + return _mm_srai_epi32(v, 16); +} + +inline __m128i FastDivideBy255_16(__m128i m) { + __m128i zero = _mm_set1_epi16(0); + __m128i lo = _mm_unpacklo_epi16(m, zero); + __m128i hi = _mm_unpackhi_epi16(m, zero); + return _mm_packs_epi32(FastDivideBy255(lo), FastDivideBy255(hi)); +} + +inline __m128i Pick(__m128i mask, __m128i a, __m128i b) { + return _mm_or_si128(_mm_andnot_si128(mask, a), _mm_and_si128(mask, b)); +} + +inline __m128 MixF32(__m128 a, __m128 b, float t) { + return _mm_add_ps(a, _mm_mul_ps(_mm_sub_ps(b, a), _mm_set1_ps(t))); +} + +inline __m128 WSumF32(__m128 a, __m128 b, float wa, float wb) { + return _mm_add_ps(_mm_mul_ps(a, _mm_set1_ps(wa)), + _mm_mul_ps(b, _mm_set1_ps(wb))); +} + +inline __m128 AbsF32(__m128 a) { + return _mm_max_ps(_mm_sub_ps(_mm_setzero_ps(), a), a); +} + +inline __m128 AddF32(__m128 a, __m128 b) { return _mm_add_ps(a, b); } + +inline __m128 MulF32(__m128 a, __m128 b) { return _mm_mul_ps(a, b); } + +inline __m128 DivF32(__m128 a, __m128 b) { return _mm_div_ps(a, b); } + +template <uint8_t aIndex> +inline __m128 SplatF32(__m128 m) { + AssertIndex<aIndex>(); + return _mm_shuffle_ps(m, m, _MM_SHUFFLE(aIndex, aIndex, aIndex, aIndex)); +} + +inline __m128i F32ToI32(__m128 m) { return _mm_cvtps_epi32(m); } + +#endif // SIMD_COMPILE_SSE2 + +} // namespace simd + +} // namespace gfx +} // namespace mozilla + +#endif // _MOZILLA_GFX_SIMD_H_ diff --git a/gfx/2d/SSEHelpers.h b/gfx/2d/SSEHelpers.h new file mode 100644 index 0000000000..1b32024009 --- /dev/null +++ b/gfx/2d/SSEHelpers.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <xmmintrin.h> +#include <emmintrin.h> + +/* Before Nehalem _mm_loadu_si128 could be very slow, this trick is a little + * faster. Once enough people are on architectures where _mm_loadu_si128 is + * fast we can migrate to it. + */ +MOZ_ALWAYS_INLINE __m128i loadUnaligned128(const __m128i* aSource) { + // Yes! We use uninitialized memory here, we'll overwrite it though! + __m128 res = _mm_loadl_pi(_mm_set1_ps(0), (const __m64*)aSource); + return _mm_castps_si128(_mm_loadh_pi(res, ((const __m64*)(aSource)) + 1)); +} diff --git a/gfx/2d/SVGTurbulenceRenderer-inl.h b/gfx/2d/SVGTurbulenceRenderer-inl.h new file mode 100644 index 0000000000..27448befe1 --- /dev/null +++ b/gfx/2d/SVGTurbulenceRenderer-inl.h @@ -0,0 +1,362 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "2D.h" +#include "Filters.h" +#include "SIMD.h" + +namespace mozilla { +namespace gfx { + +template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, + typename u8x16_t> +class SVGTurbulenceRenderer { + public: + SVGTurbulenceRenderer(const Size& aBaseFrequency, int32_t aSeed, + int aNumOctaves, const Rect& aTileRect); + + already_AddRefed<DataSourceSurface> Render(const IntSize& aSize, + const Point& aOffset) const; + + private: + /* The turbulence calculation code is an adapted version of what + appears in the SVG 1.1 specification: + http://www.w3.org/TR/SVG11/filters.html#feTurbulence + */ + + struct StitchInfo { + int32_t width; // How much to subtract to wrap for stitching. + int32_t height; + int32_t wrapX; // Minimum value to wrap. + int32_t wrapY; + }; + + const static int sBSize = 0x100; + const static int sBM = 0xff; + void InitFromSeed(int32_t aSeed); + void AdjustBaseFrequencyForStitch(const Rect& aTileRect); + IntPoint AdjustForStitch(IntPoint aLatticePoint, + const StitchInfo& aStitchInfo) const; + StitchInfo CreateStitchInfo(const Rect& aTileRect) const; + f32x4_t Noise2(Point aVec, const StitchInfo& aStitchInfo) const; + i32x4_t Turbulence(const Point& aPoint) const; + Point EquivalentNonNegativeOffset(const Point& aOffset) const; + + Size mBaseFrequency; + int32_t mNumOctaves; + StitchInfo mStitchInfo; + bool mStitchable; + TurbulenceType mType; + uint8_t mLatticeSelector[sBSize]; + f32x4_t mGradient[sBSize][2]; +}; + +namespace { + +struct RandomNumberSource { + explicit RandomNumberSource(int32_t aSeed) : mLast(SetupSeed(aSeed)) {} + int32_t Next() { + mLast = Random(mLast); + return mLast; + } + + private: + static const int32_t RAND_M = 2147483647; /* 2**31 - 1 */ + static const int32_t RAND_A = 16807; /* 7**5; primitive root of m */ + static const int32_t RAND_Q = 127773; /* m / a */ + static const int32_t RAND_R = 2836; /* m % a */ + + /* Produces results in the range [1, 2**31 - 2]. + Algorithm is: r = (a * r) mod m + where a = 16807 and m = 2**31 - 1 = 2147483647 + See [Park & Miller], CACM vol. 31 no. 10 p. 1195, Oct. 1988 + To test: the algorithm should produce the result 1043618065 + as the 10,000th generated number if the original seed is 1. + */ + + static int32_t SetupSeed(int32_t aSeed) { + if (aSeed <= 0) aSeed = -(aSeed % (RAND_M - 1)) + 1; + if (aSeed > RAND_M - 1) aSeed = RAND_M - 1; + return aSeed; + } + + static int32_t Random(int32_t aSeed) { + int32_t result = RAND_A * (aSeed % RAND_Q) - RAND_R * (aSeed / RAND_Q); + if (result <= 0) result += RAND_M; + return result; + } + + int32_t mLast; +}; + +} // unnamed namespace + +template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, + typename u8x16_t> +SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>:: + SVGTurbulenceRenderer(const Size& aBaseFrequency, int32_t aSeed, + int aNumOctaves, const Rect& aTileRect) + : mBaseFrequency(aBaseFrequency), + mNumOctaves(aNumOctaves), + mStitchInfo(), + mStitchable(false), + mType(TURBULENCE_TYPE_TURBULENCE) { + InitFromSeed(aSeed); + if (Stitch) { + AdjustBaseFrequencyForStitch(aTileRect); + mStitchInfo = CreateStitchInfo(aTileRect); + } +} + +template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, + typename u8x16_t> +void SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, + u8x16_t>::InitFromSeed(int32_t aSeed) { + RandomNumberSource rand(aSeed); + + float gradient[4][sBSize][2]; + for (int32_t k = 0; k < 4; k++) { + for (int32_t i = 0; i < sBSize; i++) { + float a, b; + do { + a = float((rand.Next() % (sBSize + sBSize)) - sBSize) / sBSize; + b = float((rand.Next() % (sBSize + sBSize)) - sBSize) / sBSize; + } while (a == 0 && b == 0); + float s = sqrt(a * a + b * b); + gradient[k][i][0] = a / s; + gradient[k][i][1] = b / s; + } + } + + for (int32_t i = 0; i < sBSize; i++) { + mLatticeSelector[i] = i; + } + for (int32_t i1 = sBSize - 1; i1 > 0; i1--) { + int32_t i2 = rand.Next() % sBSize; + std::swap(mLatticeSelector[i1], mLatticeSelector[i2]); + } + + for (int32_t i = 0; i < sBSize; i++) { + // Contrary to the code in the spec, we build the first lattice selector + // lookup into mGradient so that we don't need to do it again for every + // pixel. + // We also change the order of the gradient indexing so that we can process + // all four color channels at the same time. + uint8_t j = mLatticeSelector[i]; + mGradient[i][0] = + simd::FromF32<f32x4_t>(gradient[2][j][0], gradient[1][j][0], + gradient[0][j][0], gradient[3][j][0]); + mGradient[i][1] = + simd::FromF32<f32x4_t>(gradient[2][j][1], gradient[1][j][1], + gradient[0][j][1], gradient[3][j][1]); + } +} + +// Adjust aFreq such that aLength * AdjustForLength(aFreq, aLength) is integer +// and as close to aLength * aFreq as possible. +static inline float AdjustForLength(float aFreq, float aLength) { + float lowFreq = floor(aLength * aFreq) / aLength; + float hiFreq = ceil(aLength * aFreq) / aLength; + if (aFreq / lowFreq < hiFreq / aFreq) { + return lowFreq; + } + return hiFreq; +} + +template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, + typename u8x16_t> +void SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>:: + AdjustBaseFrequencyForStitch(const Rect& aTileRect) { + mBaseFrequency = + Size(AdjustForLength(mBaseFrequency.width, aTileRect.Width()), + AdjustForLength(mBaseFrequency.height, aTileRect.Height())); +} + +template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, + typename u8x16_t> +typename SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, + u8x16_t>::StitchInfo +SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, + u8x16_t>::CreateStitchInfo(const Rect& aTileRect) const { + StitchInfo stitch; + stitch.width = + int32_t(floorf(aTileRect.Width() * mBaseFrequency.width + 0.5f)); + stitch.height = + int32_t(floorf(aTileRect.Height() * mBaseFrequency.height + 0.5f)); + stitch.wrapX = int32_t(aTileRect.X() * mBaseFrequency.width) + stitch.width; + stitch.wrapY = int32_t(aTileRect.Y() * mBaseFrequency.height) + stitch.height; + return stitch; +} + +static MOZ_ALWAYS_INLINE Float SCurve(Float t) { return t * t * (3 - 2 * t); } + +static MOZ_ALWAYS_INLINE Point SCurve(Point t) { + return Point(SCurve(t.x), SCurve(t.y)); +} + +template <typename f32x4_t> +static MOZ_ALWAYS_INLINE f32x4_t BiMix(const f32x4_t& aa, const f32x4_t& ab, + const f32x4_t& ba, const f32x4_t& bb, + Point s) { + return simd::MixF32(simd::MixF32(aa, ab, s.x), simd::MixF32(ba, bb, s.x), + s.y); +} + +template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, + typename u8x16_t> +IntPoint +SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>::AdjustForStitch( + IntPoint aLatticePoint, const StitchInfo& aStitchInfo) const { + if (Stitch) { + if (aLatticePoint.x >= aStitchInfo.wrapX) { + aLatticePoint.x -= aStitchInfo.width; + } + if (aLatticePoint.y >= aStitchInfo.wrapY) { + aLatticePoint.y -= aStitchInfo.height; + } + } + return aLatticePoint; +} + +template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, + typename u8x16_t> +f32x4_t SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>::Noise2( + Point aVec, const StitchInfo& aStitchInfo) const { + // aVec is guaranteed to be non-negative, so casting to int32_t always + // rounds towards negative infinity. + IntPoint topLeftLatticePoint(int32_t(aVec.x), int32_t(aVec.y)); + Point r = aVec - topLeftLatticePoint; // fractional offset + + IntPoint b0 = AdjustForStitch(topLeftLatticePoint, aStitchInfo); + IntPoint b1 = AdjustForStitch(b0 + IntPoint(1, 1), aStitchInfo); + + uint8_t i = mLatticeSelector[b0.x & sBM]; + uint8_t j = mLatticeSelector[b1.x & sBM]; + + const f32x4_t* qua = mGradient[(i + b0.y) & sBM]; + const f32x4_t* qub = mGradient[(i + b1.y) & sBM]; + const f32x4_t* qva = mGradient[(j + b0.y) & sBM]; + const f32x4_t* qvb = mGradient[(j + b1.y) & sBM]; + + return BiMix(simd::WSumF32(qua[0], qua[1], r.x, r.y), + simd::WSumF32(qva[0], qva[1], r.x - 1.f, r.y), + simd::WSumF32(qub[0], qub[1], r.x, r.y - 1.f), + simd::WSumF32(qvb[0], qvb[1], r.x - 1.f, r.y - 1.f), SCurve(r)); +} + +template <typename f32x4_t, typename i32x4_t, typename u8x16_t> +static inline i32x4_t ColorToBGRA(f32x4_t aUnscaledUnpreFloat) { + // Color is an unpremultiplied float vector where 1.0f means white. We will + // convert it into an integer vector where 255 means white. + f32x4_t alpha = simd::SplatF32<3>(aUnscaledUnpreFloat); + f32x4_t scaledUnpreFloat = + simd::MulF32(aUnscaledUnpreFloat, simd::FromF32<f32x4_t>(255)); + i32x4_t scaledUnpreInt = simd::F32ToI32(scaledUnpreFloat); + + // Multiply all channels with alpha. + i32x4_t scaledPreInt = simd::F32ToI32(simd::MulF32(scaledUnpreFloat, alpha)); + + // Use the premultiplied color channels and the unpremultiplied alpha channel. + i32x4_t alphaMask = simd::From32<i32x4_t>(0, 0, 0, -1); + return simd::Pick(alphaMask, scaledPreInt, scaledUnpreInt); +} + +template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, + typename u8x16_t> +i32x4_t SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, + u8x16_t>::Turbulence(const Point& aPoint) const { + StitchInfo stitchInfo = mStitchInfo; + f32x4_t sum = simd::FromF32<f32x4_t>(0); + Point vec(aPoint.x * mBaseFrequency.width, aPoint.y * mBaseFrequency.height); + f32x4_t ratio = simd::FromF32<f32x4_t>(1); + + for (int octave = 0; octave < mNumOctaves; octave++) { + f32x4_t thisOctave = Noise2(vec, stitchInfo); + if (Type == TURBULENCE_TYPE_TURBULENCE) { + thisOctave = simd::AbsF32(thisOctave); + } + sum = simd::AddF32(sum, simd::DivF32(thisOctave, ratio)); + vec = vec * 2; + ratio = simd::MulF32(ratio, simd::FromF32<f32x4_t>(2)); + + if (Stitch) { + stitchInfo.width *= 2; + stitchInfo.wrapX *= 2; + stitchInfo.height *= 2; + stitchInfo.wrapY *= 2; + } + } + + if (Type == TURBULENCE_TYPE_FRACTAL_NOISE) { + sum = simd::DivF32(simd::AddF32(sum, simd::FromF32<f32x4_t>(1)), + simd::FromF32<f32x4_t>(2)); + } + return ColorToBGRA<f32x4_t, i32x4_t, u8x16_t>(sum); +} + +static inline Float MakeNonNegative(Float aValue, Float aIncrementSize) { + if (aIncrementSize == 0) { + return 0; + } + if (aValue >= 0) { + return aValue; + } + return aValue + ceilf(-aValue / aIncrementSize) * aIncrementSize; +} + +static inline Float FiniteDivide(Float aValue, Float aDivisor) { + if (aDivisor == 0) { + return 0; + } + return aValue / aDivisor; +} + +template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, + typename u8x16_t> +Point SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>:: + EquivalentNonNegativeOffset(const Point& aOffset) const { + Size basePeriod = Stitch ? Size(mStitchInfo.width, mStitchInfo.height) + : Size(sBSize, sBSize); + Size repeatingSize(FiniteDivide(basePeriod.width, mBaseFrequency.width), + FiniteDivide(basePeriod.height, mBaseFrequency.height)); + return Point(MakeNonNegative(aOffset.x, repeatingSize.width), + MakeNonNegative(aOffset.y, repeatingSize.height)); +} + +template <TurbulenceType Type, bool Stitch, typename f32x4_t, typename i32x4_t, + typename u8x16_t> +already_AddRefed<DataSourceSurface> +SVGTurbulenceRenderer<Type, Stitch, f32x4_t, i32x4_t, u8x16_t>::Render( + const IntSize& aSize, const Point& aOffset) const { + RefPtr<DataSourceSurface> target = + Factory::CreateDataSourceSurface(aSize, SurfaceFormat::B8G8R8A8); + if (!target) { + return nullptr; + } + + DataSourceSurface::ScopedMap map(target, DataSourceSurface::READ_WRITE); + uint8_t* targetData = map.GetData(); + uint32_t stride = map.GetStride(); + + Point startOffset = EquivalentNonNegativeOffset(aOffset); + + for (int32_t y = 0; y < aSize.height; y++) { + for (int32_t x = 0; x < aSize.width; x += 4) { + int32_t targIndex = y * stride + x * 4; + i32x4_t a = Turbulence(startOffset + Point(x, y)); + i32x4_t b = Turbulence(startOffset + Point(x + 1, y)); + i32x4_t c = Turbulence(startOffset + Point(x + 2, y)); + i32x4_t d = Turbulence(startOffset + Point(x + 3, y)); + u8x16_t result1234 = simd::PackAndSaturate32To8(a, b, c, d); + simd::Store8(&targetData[targIndex], result1234); + } + } + + return target.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/Scale.h b/gfx/2d/Scale.h new file mode 100644 index 0000000000..673bb88233 --- /dev/null +++ b/gfx/2d/Scale.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SCALE_H_ +#define MOZILLA_GFX_SCALE_H_ + +#include "Types.h" + +namespace mozilla { +namespace gfx { + +/** + * Scale an image using a high-quality filter. + * + * Synchronously scales an image and writes the output to the destination in + * 32-bit format. The destination must be pre-allocated by the caller. + * + * Returns true if scaling was successful, and false otherwise. Currently, this + * function is implemented using Skia. If Skia is not enabled when building, + * calling this function will always return false. + * + * IMPLEMTATION NOTES: + * This API is not currently easily hardware acceleratable. A better API might + * take a SourceSurface and return a SourceSurface; the Direct2D backend, for + * example, could simply set a status bit on a copy of the image, and use + * Direct2D's high-quality scaler at draw time. + */ +GFX2D_API bool Scale(uint8_t* srcData, int32_t srcWidth, int32_t srcHeight, + int32_t srcStride, uint8_t* dstData, int32_t dstWidth, + int32_t dstHeight, int32_t dstStride, + SurfaceFormat format); + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_BLUR_H_ */ diff --git a/gfx/2d/ScaleFactor.h b/gfx/2d/ScaleFactor.h new file mode 100644 index 0000000000..4d7346d200 --- /dev/null +++ b/gfx/2d/ScaleFactor.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SCALEFACTOR_H_ +#define MOZILLA_GFX_SCALEFACTOR_H_ + +#include <ostream> + +#include "mozilla/Attributes.h" + +#include "gfxPoint.h" + +namespace mozilla { +namespace gfx { + +/* + * This class represents a scaling factor between two different pixel unit + * systems. This is effectively a type-safe float, intended to be used in + * combination with the known-type instances of gfx::Point, gfx::Rect, etc. + * + * This class is meant to be used in cases where a single scale applies to + * both the x and y axes. For cases where two diferent scales apply, use + * ScaleFactors2D. + */ +template <class Src, class Dst> +struct ScaleFactor { + float scale; + + constexpr ScaleFactor() : scale(1.0) {} + constexpr ScaleFactor(const ScaleFactor<Src, Dst>& aCopy) + : scale(aCopy.scale) {} + explicit constexpr ScaleFactor(float aScale) : scale(aScale) {} + + ScaleFactor<Dst, Src> Inverse() { return ScaleFactor<Dst, Src>(1 / scale); } + + ScaleFactor<Src, Dst>& operator=(const ScaleFactor<Src, Dst>&) = default; + + bool operator==(const ScaleFactor<Src, Dst>& aOther) const { + return scale == aOther.scale; + } + + bool operator!=(const ScaleFactor<Src, Dst>& aOther) const { + return !(*this == aOther); + } + + bool operator<(const ScaleFactor<Src, Dst>& aOther) const { + return scale < aOther.scale; + } + + bool operator<=(const ScaleFactor<Src, Dst>& aOther) const { + return scale <= aOther.scale; + } + + bool operator>(const ScaleFactor<Src, Dst>& aOther) const { + return scale > aOther.scale; + } + + bool operator>=(const ScaleFactor<Src, Dst>& aOther) const { + return scale >= aOther.scale; + } + + template <class Other> + ScaleFactor<Other, Dst> operator/( + const ScaleFactor<Src, Other>& aOther) const { + return ScaleFactor<Other, Dst>(scale / aOther.scale); + } + + template <class Other> + ScaleFactor<Src, Other> operator/( + const ScaleFactor<Other, Dst>& aOther) const { + return ScaleFactor<Src, Other>(scale / aOther.scale); + } + + template <class Other> + ScaleFactor<Src, Other> operator*( + const ScaleFactor<Dst, Other>& aOther) const { + return ScaleFactor<Src, Other>(scale * aOther.scale); + } + + template <class Other> + ScaleFactor<Other, Dst> operator*( + const ScaleFactor<Other, Src>& aOther) const { + return ScaleFactor<Other, Dst>(scale * aOther.scale); + } + + friend std::ostream& operator<<(std::ostream& aStream, + const ScaleFactor<Src, Dst>& aSF) { + return aStream << aSF.scale; + } +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SCALEFACTOR_H_ */ diff --git a/gfx/2d/ScaleFactors2D.h b/gfx/2d/ScaleFactors2D.h new file mode 100644 index 0000000000..c557e3cec6 --- /dev/null +++ b/gfx/2d/ScaleFactors2D.h @@ -0,0 +1,198 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SCALEFACTORS2D_H_ +#define MOZILLA_GFX_SCALEFACTORS2D_H_ + +#include <ostream> + +#include "mozilla/Attributes.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/gfx/ScaleFactor.h" +#include "mozilla/gfx/Point.h" + +#include "gfxPoint.h" + +namespace mozilla { +namespace gfx { + +/* + * This class is like ScaleFactor, but allows different scales on the x and + * y axes. + */ +template <class Src, class Dst, class T> +struct BaseScaleFactors2D { + T xScale; + T yScale; + + constexpr BaseScaleFactors2D() : xScale(1.0), yScale(1.0) {} + constexpr BaseScaleFactors2D(const BaseScaleFactors2D& aCopy) + : xScale(aCopy.xScale), yScale(aCopy.yScale) {} + constexpr BaseScaleFactors2D(T aXScale, T aYScale) + : xScale(aXScale), yScale(aYScale) {} + // Layout code often uses gfxSize to represent a pair of x/y scales. + explicit constexpr BaseScaleFactors2D(const gfxSize& aSize) + : xScale(aSize.width), yScale(aSize.height) {} + + // "Upgrade" from a ScaleFactor. + // This is deliberately 'explicit' so that the treatment of a single scale + // number as both the x- and y-scale in a context where they are allowed to + // be different, is more visible. + explicit constexpr BaseScaleFactors2D(const ScaleFactor<Src, Dst>& aScale) + : xScale(aScale.scale), yScale(aScale.scale) {} + + bool AreScalesSame() const { + return FuzzyEqualsMultiplicative(xScale, yScale); + } + + // Convert the underlying floating point type storing the scale factors + // to that of NewT. + template <typename NewT> + BaseScaleFactors2D<Src, Dst, NewT> ConvertTo() const { + return BaseScaleFactors2D<Src, Dst, NewT>(NewT(xScale), NewT(yScale)); + } + + // Convert to a ScaleFactor. Asserts that the scales are, in fact, equal. + ScaleFactor<Src, Dst> ToScaleFactor() const { + // Avoid implicit narrowing from double to float. An explicit conversion + // may be done with `scales.ConvertTo<float>().ToScaleFactor()` if desired. + static_assert(std::is_same_v<T, float>); + MOZ_ASSERT(AreScalesSame()); + return ScaleFactor<Src, Dst>(xScale); + } + + // Convert to a SizeTyped. Eventually, we should replace all uses of SizeTyped + // to represent scales with ScaleFactors2D, and remove this function. + SizeTyped<UnknownUnits, T> ToSize() const { + return SizeTyped<UnknownUnits, T>(xScale, yScale); + } + + BaseScaleFactors2D& operator=(const BaseScaleFactors2D&) = default; + + bool operator==(const BaseScaleFactors2D& aOther) const { + return xScale == aOther.xScale && yScale == aOther.yScale; + } + + bool operator!=(const BaseScaleFactors2D& aOther) const { + return !(*this == aOther); + } + + friend std::ostream& operator<<(std::ostream& aStream, + const BaseScaleFactors2D& aScale) { + if (aScale.AreScalesSame()) { + return aStream << aScale.xScale; + } else { + return aStream << '(' << aScale.xScale << ',' << aScale.yScale << ')'; + } + } + + template <class Other> + BaseScaleFactors2D<Other, Dst, T> operator/( + const BaseScaleFactors2D<Src, Other, T>& aOther) const { + return BaseScaleFactors2D<Other, Dst, T>(xScale / aOther.xScale, + yScale / aOther.yScale); + } + + template <class Other> + BaseScaleFactors2D<Src, Other, T> operator/( + const BaseScaleFactors2D<Other, Dst, T>& aOther) const { + return BaseScaleFactors2D<Src, Other, T>(xScale / aOther.xScale, + yScale / aOther.yScale); + } + + template <class Other> + BaseScaleFactors2D<Src, Other, T> operator*( + const BaseScaleFactors2D<Dst, Other, T>& aOther) const { + return BaseScaleFactors2D<Src, Other, T>(xScale * aOther.xScale, + yScale * aOther.yScale); + } + + template <class Other> + BaseScaleFactors2D<Other, Dst, T> operator*( + const BaseScaleFactors2D<Other, Src, T>& aOther) const { + return BaseScaleFactors2D<Other, Dst, T>(xScale * aOther.xScale, + yScale * aOther.yScale); + } + + BaseScaleFactors2D<Src, Src, T> operator*( + const BaseScaleFactors2D<Dst, Src, T>& aOther) const { + return BaseScaleFactors2D<Src, Src, T>(xScale * aOther.xScale, + yScale * aOther.yScale); + } + + template <class Other> + BaseScaleFactors2D<Src, Other, T> operator*( + const ScaleFactor<Dst, Other>& aOther) const { + return *this * BaseScaleFactors2D<Dst, Other, T>(aOther); + } + + template <class Other> + BaseScaleFactors2D<Other, Dst, T> operator*( + const ScaleFactor<Other, Src>& aOther) const { + return *this * BaseScaleFactors2D<Other, Src, T>(aOther); + } + + BaseScaleFactors2D<Src, Src, T> operator*( + const ScaleFactor<Dst, Src>& aOther) const { + return *this * BaseScaleFactors2D<Dst, Src, T>(aOther); + } + + template <class Other> + BaseScaleFactors2D<Src, Other, T> operator/( + const ScaleFactor<Other, Dst>& aOther) const { + return *this / BaseScaleFactors2D<Other, Dst, T>(aOther); + } + + template <class Other> + BaseScaleFactors2D<Other, Dst, T> operator/( + const ScaleFactor<Src, Other>& aOther) const { + return *this / BaseScaleFactors2D<Src, Other, T>(aOther); + } + + template <class Other> + friend BaseScaleFactors2D<Other, Dst, T> operator*( + const ScaleFactor<Other, Src>& aA, const BaseScaleFactors2D& aB) { + return BaseScaleFactors2D<Other, Src, T>(aA) * aB; + } + + template <class Other> + friend BaseScaleFactors2D<Other, Src, T> operator/( + const ScaleFactor<Other, Dst>& aA, const BaseScaleFactors2D& aB) { + return BaseScaleFactors2D<Other, Src, T>(aA) / aB; + } + + static BaseScaleFactors2D<Src, Dst, T> FromUnknownScale( + const BaseScaleFactors2D<UnknownUnits, UnknownUnits, T>& scale) { + return BaseScaleFactors2D<Src, Dst, T>(scale.xScale, scale.yScale); + } + + BaseScaleFactors2D<UnknownUnits, UnknownUnits, T> ToUnknownScale() const { + return BaseScaleFactors2D<UnknownUnits, UnknownUnits, T>(xScale, yScale); + } + + friend BaseScaleFactors2D Min(const BaseScaleFactors2D& aA, + const BaseScaleFactors2D& aB) { + return BaseScaleFactors2D(std::min(aA.xScale, aB.xScale), + std::min(aA.yScale, aB.yScale)); + } + + friend BaseScaleFactors2D Max(const BaseScaleFactors2D& aA, + const BaseScaleFactors2D& aB) { + return BaseScaleFactors2D(std::max(aA.xScale, aB.xScale), + std::max(aA.yScale, aB.yScale)); + } +}; + +template <class Src, class Dst> +using ScaleFactors2D = BaseScaleFactors2D<Src, Dst, float>; + +template <class Src, class Dst> +using ScaleFactors2DDouble = BaseScaleFactors2D<Src, Dst, double>; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SCALEFACTORS2D_H_ */ diff --git a/gfx/2d/ScaledFontBase.cpp b/gfx/2d/ScaledFontBase.cpp new file mode 100644 index 0000000000..cd52df3ccd --- /dev/null +++ b/gfx/2d/ScaledFontBase.cpp @@ -0,0 +1,231 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScaledFontBase.h" + +#include "PathSkia.h" +#include "skia/include/core/SkFont.h" + +#ifdef USE_CAIRO +# include "PathCairo.h" +# include "DrawTargetCairo.h" +# include "HelpersCairo.h" +#endif + +#include <vector> +#include <cmath> + +namespace mozilla { +namespace gfx { + +Atomic<uint32_t> UnscaledFont::sDeletionCounter(0); + +UnscaledFont::~UnscaledFont() { sDeletionCounter++; } + +Atomic<uint32_t> ScaledFont::sDeletionCounter(0); + +ScaledFont::~ScaledFont() { sDeletionCounter++; } + +ScaledFontBase::~ScaledFontBase() { + SkSafeUnref<SkTypeface>(mTypeface); + cairo_scaled_font_destroy(mScaledFont); +} + +ScaledFontBase::ScaledFontBase(const RefPtr<UnscaledFont>& aUnscaledFont, + Float aSize) + : ScaledFont(aUnscaledFont), + mTypeface(nullptr), + mScaledFont(nullptr), + mSize(aSize) {} + +SkTypeface* ScaledFontBase::GetSkTypeface() { + if (!mTypeface) { + SkTypeface* typeface = CreateSkTypeface(); + if (!mTypeface.compareExchange(nullptr, typeface)) { + SkSafeUnref(typeface); + } + } + return mTypeface; +} + +cairo_scaled_font_t* ScaledFontBase::GetCairoScaledFont() { + if (mScaledFont) { + return mScaledFont; + } + + cairo_font_options_t* fontOptions = cairo_font_options_create(); + cairo_font_face_t* fontFace = CreateCairoFontFace(fontOptions); + if (!fontFace) { + cairo_font_options_destroy(fontOptions); + return nullptr; + } + + cairo_matrix_t sizeMatrix; + cairo_matrix_t identityMatrix; + + cairo_matrix_init_scale(&sizeMatrix, mSize, mSize); + cairo_matrix_init_identity(&identityMatrix); + + cairo_scaled_font_t* scaledFont = cairo_scaled_font_create( + fontFace, &sizeMatrix, &identityMatrix, fontOptions); + + cairo_font_options_destroy(fontOptions); + cairo_font_face_destroy(fontFace); + + if (cairo_scaled_font_status(scaledFont) != CAIRO_STATUS_SUCCESS) { + cairo_scaled_font_destroy(scaledFont); + return nullptr; + } + + PrepareCairoScaledFont(scaledFont); + mScaledFont = scaledFont; + return mScaledFont; +} + +SkPath ScaledFontBase::GetSkiaPathForGlyphs(const GlyphBuffer& aBuffer) { + SkTypeface* typeFace = GetSkTypeface(); + MOZ_ASSERT(typeFace); + + SkFont font(sk_ref_sp(typeFace), SkFloatToScalar(mSize)); + + std::vector<uint16_t> indices; + indices.resize(aBuffer.mNumGlyphs); + for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) { + indices[i] = aBuffer.mGlyphs[i].mIndex; + } + + struct Context { + const Glyph* mGlyph; + SkPath mPath; + } ctx = {aBuffer.mGlyphs}; + + font.getPaths( + indices.data(), indices.size(), + [](const SkPath* glyphPath, const SkMatrix& scaleMatrix, void* ctxPtr) { + Context& ctx = *reinterpret_cast<Context*>(ctxPtr); + if (glyphPath) { + SkMatrix transMatrix(scaleMatrix); + transMatrix.postTranslate(SkFloatToScalar(ctx.mGlyph->mPosition.x), + SkFloatToScalar(ctx.mGlyph->mPosition.y)); + ctx.mPath.addPath(*glyphPath, transMatrix); + } + ++ctx.mGlyph; + }, + &ctx); + + return ctx.mPath; +} + +already_AddRefed<Path> ScaledFontBase::GetPathForGlyphs( + const GlyphBuffer& aBuffer, const DrawTarget* aTarget) { + if (aTarget->GetBackendType() == BackendType::SKIA) { + SkPath path = GetSkiaPathForGlyphs(aBuffer); + return MakeAndAddRef<PathSkia>(path, FillRule::FILL_WINDING); + } +#ifdef USE_CAIRO + if (aTarget->GetBackendType() == BackendType::CAIRO) { + auto* cairoScaledFont = GetCairoScaledFont(); + if (!cairoScaledFont) { + MOZ_ASSERT_UNREACHABLE("Invalid scaled font"); + return nullptr; + } + + 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, cairoScaledFont); + + // Convert our GlyphBuffer into an array of Cairo glyphs. + std::vector<cairo_glyph_t> glyphs(aBuffer.mNumGlyphs); + for (uint32_t i = 0; i < aBuffer.mNumGlyphs; ++i) { + glyphs[i].index = aBuffer.mGlyphs[i].mIndex; + glyphs[i].x = aBuffer.mGlyphs[i].mPosition.x; + glyphs[i].y = aBuffer.mGlyphs[i].mPosition.y; + } + + cairo_new_path(ctx); + + cairo_glyph_path(ctx, &glyphs[0], aBuffer.mNumGlyphs); + + RefPtr<PathCairo> newPath = new PathCairo(ctx); + if (isNewContext) { + cairo_destroy(ctx); + } + + return newPath.forget(); + } +#endif + RefPtr<PathBuilder> builder = aTarget->CreatePathBuilder(); + SkPath skPath = GetSkiaPathForGlyphs(aBuffer); + RefPtr<Path> path = MakeAndAddRef<PathSkia>(skPath, FillRule::FILL_WINDING); + path->StreamToSink(builder); + return builder->Finish(); +} + +void ScaledFontBase::CopyGlyphsToBuilder(const GlyphBuffer& aBuffer, + PathBuilder* aBuilder, + const Matrix* aTransformHint) { + BackendType backendType = aBuilder->GetBackendType(); + if (backendType == BackendType::SKIA) { + PathBuilderSkia* builder = static_cast<PathBuilderSkia*>(aBuilder); + builder->AppendPath(GetSkiaPathForGlyphs(aBuffer)); + return; + } +#ifdef USE_CAIRO + if (backendType == BackendType::CAIRO) { + auto* cairoScaledFont = GetCairoScaledFont(); + if (!cairoScaledFont) { + MOZ_ASSERT_UNREACHABLE("Invalid scaled font"); + return; + } + + 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, cairoScaledFont); + cairo_glyph_path(ctx, &glyphs[0], aBuffer.mNumGlyphs); + + RefPtr<PathCairo> cairoPath = new PathCairo(ctx); + cairo_destroy(ctx); + + cairoPath->AppendPathToBuilder(builder); + return; + } +#endif + if (backendType == BackendType::RECORDING) { + SkPath skPath = GetSkiaPathForGlyphs(aBuffer); + RefPtr<Path> path = MakeAndAddRef<PathSkia>(skPath, FillRule::FILL_WINDING); + path->StreamToSink(aBuilder); + return; + } + MOZ_ASSERT(false, "Path not being copied"); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/ScaledFontBase.h b/gfx/2d/ScaledFontBase.h new file mode 100644 index 0000000000..0eb875955e --- /dev/null +++ b/gfx/2d/ScaledFontBase.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SCALEDFONTBASE_H_ +#define MOZILLA_GFX_SCALEDFONTBASE_H_ + +#include "2D.h" + +#include "skia/include/core/SkFont.h" +#include "skia/include/core/SkPath.h" +#include "skia/include/core/SkTypeface.h" +// Skia uses cairo_scaled_font_t as the internal font type in ScaledFont +#include "cairo.h" + +namespace mozilla { +namespace gfx { + +class ScaledFontBase : public ScaledFont { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontBase, override) + + ScaledFontBase(const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize); + virtual ~ScaledFontBase(); + + virtual already_AddRefed<Path> GetPathForGlyphs( + const GlyphBuffer& aBuffer, const DrawTarget* aTarget) override; + + virtual void CopyGlyphsToBuilder(const GlyphBuffer& aBuffer, + PathBuilder* aBuilder, + const Matrix* aTransformHint) override; + + virtual Float GetSize() const override { return mSize; } + + SkTypeface* GetSkTypeface(); + virtual void SetupSkFontDrawOptions(SkFont& aFont) {} + + virtual cairo_scaled_font_t* GetCairoScaledFont() override; + + protected: + friend class DrawTargetSkia; + Atomic<SkTypeface*> mTypeface; + virtual SkTypeface* CreateSkTypeface() { return nullptr; } + SkPath GetSkiaPathForGlyphs(const GlyphBuffer& aBuffer); + virtual cairo_font_face_t* CreateCairoFontFace( + cairo_font_options_t* aFontOptions) { + return nullptr; + } + virtual void PrepareCairoScaledFont(cairo_scaled_font_t* aFont) {} + cairo_scaled_font_t* mScaledFont; + Float mSize; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SCALEDFONTBASE_H_ */ diff --git a/gfx/2d/ScaledFontDWrite.cpp b/gfx/2d/ScaledFontDWrite.cpp new file mode 100644 index 0000000000..f6a0d97504 --- /dev/null +++ b/gfx/2d/ScaledFontDWrite.cpp @@ -0,0 +1,756 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScaledFontDWrite.h" +#include "gfxDWriteCommon.h" +#include "UnscaledFontDWrite.h" +#include "PathD2D.h" +#include "gfxFont.h" +#include "Logging.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "HelpersD2D.h" +#include "StackArray.h" + +#include "dwrite_3.h" + +#include "PathSkia.h" +#include "skia/include/core/SkPaint.h" +#include "skia/include/core/SkPath.h" +#include "skia/include/ports/SkTypeface_win.h" + +#include <vector> + +#include "cairo-win32.h" + +#include "HelpersWinFonts.h" + +namespace mozilla { +namespace gfx { + +#define GASP_TAG 0x70736167 +#define GASP_DOGRAY 0x2 + +static inline unsigned short readShort(const char* aBuf) { + return (*aBuf << 8) | *(aBuf + 1); +} + +static bool DoGrayscale(IDWriteFontFace* aDWFace, Float ppem) { + void* tableContext; + char* tableData; + UINT32 tableSize; + BOOL exists; + aDWFace->TryGetFontTable(GASP_TAG, (const void**)&tableData, &tableSize, + &tableContext, &exists); + + if (exists) { + if (tableSize < 4) { + aDWFace->ReleaseFontTable(tableContext); + return true; + } + struct gaspRange { + unsigned short maxPPEM; // Stored big-endian + unsigned short behavior; // Stored big-endian + }; + unsigned short numRanges = readShort(tableData + 2); + if (tableSize < (UINT)4 + numRanges * 4) { + aDWFace->ReleaseFontTable(tableContext); + return true; + } + gaspRange* ranges = (gaspRange*)(tableData + 4); + for (int i = 0; i < numRanges; i++) { + if (readShort((char*)&ranges[i].maxPPEM) >= ppem) { + if (!(readShort((char*)&ranges[i].behavior) & GASP_DOGRAY)) { + aDWFace->ReleaseFontTable(tableContext); + return false; + } + break; + } + } + aDWFace->ReleaseFontTable(tableContext); + } + return true; +} + +ScaledFontDWrite::ScaledFontDWrite(IDWriteFontFace* aFontFace, + const RefPtr<UnscaledFont>& aUnscaledFont, + Float aSize, bool aUseEmbeddedBitmap, + bool aUseMultistrikeBold, bool aGDIForced, + const gfxFontStyle* aStyle) + : ScaledFontBase(aUnscaledFont, aSize), + mFontFace(aFontFace), + mUseEmbeddedBitmap(aUseEmbeddedBitmap), + mUseMultistrikeBold(aUseMultistrikeBold), + mGDIForced(aGDIForced) { + if (aStyle) { + mStyle = SkFontStyle(aStyle->weight.ToIntRounded(), + DWriteFontStretchFromStretch(aStyle->stretch), + // FIXME(jwatt): also use kOblique_Slant + aStyle->style == FontSlantStyle::NORMAL + ? SkFontStyle::kUpright_Slant + : SkFontStyle::kItalic_Slant); + } +} + +already_AddRefed<Path> ScaledFontDWrite::GetPathForGlyphs( + const GlyphBuffer& aBuffer, const DrawTarget* aTarget) { + RefPtr<PathBuilder> pathBuilder = aTarget->CreatePathBuilder(); + + if (pathBuilder->GetBackendType() != BackendType::DIRECT2D && + pathBuilder->GetBackendType() != BackendType::DIRECT2D1_1) { + return ScaledFontBase::GetPathForGlyphs(aBuffer, aTarget); + } + + PathBuilderD2D* pathBuilderD2D = + static_cast<PathBuilderD2D*>(pathBuilder.get()); + + CopyGlyphsToSink(aBuffer, pathBuilderD2D->GetSink()); + + return pathBuilder->Finish(); +} + +SkTypeface* ScaledFontDWrite::CreateSkTypeface() { + RefPtr<IDWriteFactory> factory = Factory::GetDWriteFactory(); + if (!factory) { + return nullptr; + } + + auto& settings = DWriteSettings(); + Float gamma = settings.Gamma(); + // Skia doesn't support a gamma value outside of 0-4, so default to 2.2 + if (gamma < 0.0f || gamma > 4.0f) { + gamma = 2.2f; + } + + Float contrast = settings.EnhancedContrast(); + // Skia doesn't support a contrast value outside of 0-1, so default to 1.0 + if (contrast < 0.0f || contrast > 1.0f) { + contrast = 1.0f; + } + + Float clearTypeLevel = settings.ClearTypeLevel(); + if (clearTypeLevel < 0.0f || clearTypeLevel > 1.0f) { + clearTypeLevel = 1.0f; + } + + return SkCreateTypefaceFromDWriteFont(factory, mFontFace, mStyle, + (int)settings.RenderingMode(), gamma, + contrast, clearTypeLevel); +} + +void ScaledFontDWrite::SetupSkFontDrawOptions(SkFont& aFont) { + if (ForceGDIMode()) { + aFont.setEmbeddedBitmaps(true); + aFont.setSubpixel(false); + } else { + aFont.setEmbeddedBitmaps(UseEmbeddedBitmaps()); + aFont.setSubpixel(true); + } +} + +bool ScaledFontDWrite::MayUseBitmaps() { + return ForceGDIMode() || UseEmbeddedBitmaps(); +} + +void ScaledFontDWrite::CopyGlyphsToBuilder(const GlyphBuffer& aBuffer, + PathBuilder* aBuilder, + const Matrix* aTransformHint) { + BackendType backendType = aBuilder->GetBackendType(); + if (backendType != BackendType::DIRECT2D && + backendType != BackendType::DIRECT2D1_1) { + ScaledFontBase::CopyGlyphsToBuilder(aBuffer, aBuilder, aTransformHint); + return; + } + + PathBuilderD2D* pathBuilderD2D = static_cast<PathBuilderD2D*>(aBuilder); + + if (pathBuilderD2D->IsFigureActive()) { + gfxCriticalNote + << "Attempting to copy glyphs to PathBuilderD2D with active figure."; + } + + CopyGlyphsToSink(aBuffer, pathBuilderD2D->GetSink()); +} + +void ScaledFontDWrite::CopyGlyphsToSink(const GlyphBuffer& aBuffer, + ID2D1SimplifiedGeometrySink* aSink) { + std::vector<UINT16> indices; + std::vector<FLOAT> advances; + std::vector<DWRITE_GLYPH_OFFSET> offsets; + indices.resize(aBuffer.mNumGlyphs); + advances.resize(aBuffer.mNumGlyphs); + offsets.resize(aBuffer.mNumGlyphs); + + memset(&advances.front(), 0, sizeof(FLOAT) * aBuffer.mNumGlyphs); + for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) { + indices[i] = aBuffer.mGlyphs[i].mIndex; + offsets[i].advanceOffset = aBuffer.mGlyphs[i].mPosition.x; + offsets[i].ascenderOffset = -aBuffer.mGlyphs[i].mPosition.y; + } + + HRESULT hr = mFontFace->GetGlyphRunOutline( + mSize, &indices.front(), &advances.front(), &offsets.front(), + aBuffer.mNumGlyphs, FALSE, FALSE, aSink); + if (FAILED(hr)) { + gfxCriticalNote << "Failed to copy glyphs to geometry sink. Code: " + << hexa(hr); + } +} + +bool UnscaledFontDWrite::GetFontFileData(FontFileDataOutput aDataCallback, + void* aBaton) { + UINT32 fileCount = 0; + HRESULT hr = mFontFace->GetFiles(&fileCount, nullptr); + + if (FAILED(hr) || fileCount > 1) { + MOZ_ASSERT(false); + return false; + } + + if (!aDataCallback) { + return true; + } + + RefPtr<IDWriteFontFile> file; + hr = mFontFace->GetFiles(&fileCount, getter_AddRefs(file)); + if (FAILED(hr)) { + return false; + } + + const void* referenceKey; + UINT32 refKeySize; + // XXX - This can currently crash for webfonts, as when we get the reference + // key out of the file, that can be an invalid reference key for the loader + // we use it with. The fix to this is not obvious but it will probably + // have to happen inside thebes. + hr = file->GetReferenceKey(&referenceKey, &refKeySize); + if (FAILED(hr)) { + return false; + } + + RefPtr<IDWriteFontFileLoader> loader; + hr = file->GetLoader(getter_AddRefs(loader)); + if (FAILED(hr)) { + return false; + } + + RefPtr<IDWriteFontFileStream> stream; + hr = loader->CreateStreamFromKey(referenceKey, refKeySize, + getter_AddRefs(stream)); + if (FAILED(hr)) { + return false; + } + + UINT64 fileSize64; + hr = stream->GetFileSize(&fileSize64); + if (FAILED(hr) || fileSize64 > UINT32_MAX) { + MOZ_ASSERT(false); + return false; + } + + // Try to catch any device memory exceptions that may occur while attempting + // to read the file fragment. + void* context = nullptr; + hr = E_FAIL; + MOZ_SEH_TRY { + uint32_t fileSize = static_cast<uint32_t>(fileSize64); + const void* fragmentStart = nullptr; + hr = stream->ReadFileFragment(&fragmentStart, 0, fileSize, &context); + if (SUCCEEDED(hr)) { + aDataCallback((uint8_t*)fragmentStart, fileSize, mFontFace->GetIndex(), + aBaton); + } + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalNote << "Exception occurred reading DWrite font file data"; + } + if (FAILED(hr)) { + return false; + } + stream->ReleaseFileFragment(context); + return true; +} + +static bool GetFontFileName(RefPtr<IDWriteFontFace> aFontFace, + std::vector<WCHAR>& aFileName) { + UINT32 numFiles; + HRESULT hr = aFontFace->GetFiles(&numFiles, nullptr); + if (FAILED(hr)) { + gfxDebug() << "Failed getting file count for WR font"; + return false; + } else if (numFiles != 1) { + gfxDebug() << "Invalid file count " << numFiles << " for WR font"; + return false; + } + + RefPtr<IDWriteFontFile> file; + hr = aFontFace->GetFiles(&numFiles, getter_AddRefs(file)); + if (FAILED(hr)) { + gfxDebug() << "Failed getting file for WR font"; + return false; + } + + const void* key; + UINT32 keySize; + hr = file->GetReferenceKey(&key, &keySize); + if (FAILED(hr)) { + gfxDebug() << "Failed getting file ref key for WR font"; + return false; + } + RefPtr<IDWriteFontFileLoader> loader; + hr = file->GetLoader(getter_AddRefs(loader)); + if (FAILED(hr)) { + gfxDebug() << "Failed getting file loader for WR font"; + return false; + } + RefPtr<IDWriteLocalFontFileLoader> localLoader; + loader->QueryInterface(__uuidof(IDWriteLocalFontFileLoader), + (void**)getter_AddRefs(localLoader)); + if (!localLoader) { + gfxDebug() << "Failed querying loader interface for WR font"; + return false; + } + UINT32 pathLen; + hr = localLoader->GetFilePathLengthFromKey(key, keySize, &pathLen); + if (FAILED(hr)) { + gfxDebug() << "Failed getting path length for WR font"; + return false; + } + aFileName.resize(pathLen + 1); + hr = localLoader->GetFilePathFromKey(key, keySize, aFileName.data(), + pathLen + 1); + if (FAILED(hr) || aFileName.back() != 0) { + gfxDebug() << "Failed getting path for WR font"; + return false; + } + DWORD attribs = GetFileAttributesW(aFileName.data()); + if (attribs == INVALID_FILE_ATTRIBUTES) { + gfxDebug() << "Invalid file \"" << aFileName.data() << "\" for WR font"; + return false; + } + // We leave the null terminator at the end of the returned file name. + return true; +} + +bool UnscaledFontDWrite::GetFontDescriptor(FontDescriptorOutput aCb, + void* aBaton) { + if (!mFont) { + return false; + } + + std::vector<WCHAR> fileName; + if (!GetFontFileName(mFontFace, fileName)) { + return false; + } + uint32_t index = mFontFace->GetIndex(); + + aCb(reinterpret_cast<const uint8_t*>(fileName.data()), + fileName.size() * sizeof(WCHAR), index, aBaton); + return true; +} + +ScaledFontDWrite::InstanceData::InstanceData( + const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions) { + if (aOptions) { + if (aOptions->flags & wr::FontInstanceFlags::EMBEDDED_BITMAPS) { + mUseEmbeddedBitmap = true; + } + if (aOptions->flags & wr::FontInstanceFlags::SYNTHETIC_BOLD) { + mUseBoldSimulation = true; + } + if (aOptions->flags & wr::FontInstanceFlags::MULTISTRIKE_BOLD) { + mUseMultistrikeBold = true; + } + if (aOptions->flags & wr::FontInstanceFlags::FORCE_GDI) { + mGDIForced = true; + } + } +} + +bool ScaledFontDWrite::HasVariationSettings() { + RefPtr<IDWriteFontFace5> ff5; + mFontFace->QueryInterface(__uuidof(IDWriteFontFace5), + (void**)getter_AddRefs(ff5)); + if (!ff5 || !ff5->HasVariations()) { + return false; + } + + uint32_t count = ff5->GetFontAxisValueCount(); + if (!count) { + return false; + } + + RefPtr<IDWriteFontResource> res; + if (FAILED(ff5->GetFontResource(getter_AddRefs(res)))) { + return false; + } + + std::vector<DWRITE_FONT_AXIS_VALUE> defaults(count); + if (FAILED(res->GetDefaultFontAxisValues(defaults.data(), count))) { + return false; + } + + std::vector<DWRITE_FONT_AXIS_VALUE> values(count); + if (FAILED(ff5->GetFontAxisValues(values.data(), count))) { + return false; + } + + for (uint32_t i = 0; i < count; i++) { + DWRITE_FONT_AXIS_ATTRIBUTES attr = res->GetFontAxisAttributes(i); + if (attr & DWRITE_FONT_AXIS_ATTRIBUTES_VARIABLE) { + if (values[i].value != defaults[i].value) { + return true; + } + } + } + + return false; +} + +// Helper for ScaledFontDWrite::GetFontInstanceData: if the font has variation +// axes, get their current values into the aOutput vector. +static void GetVariationsFromFontFace(IDWriteFontFace* aFace, + std::vector<FontVariation>* aOutput) { + RefPtr<IDWriteFontFace5> ff5; + aFace->QueryInterface(__uuidof(IDWriteFontFace5), + (void**)getter_AddRefs(ff5)); + if (!ff5 || !ff5->HasVariations()) { + return; + } + + uint32_t count = ff5->GetFontAxisValueCount(); + if (!count) { + return; + } + + RefPtr<IDWriteFontResource> res; + if (FAILED(ff5->GetFontResource(getter_AddRefs(res)))) { + return; + } + + std::vector<DWRITE_FONT_AXIS_VALUE> values(count); + if (FAILED(ff5->GetFontAxisValues(values.data(), count))) { + return; + } + + aOutput->reserve(count); + for (uint32_t i = 0; i < count; i++) { + DWRITE_FONT_AXIS_ATTRIBUTES attr = res->GetFontAxisAttributes(i); + if (attr & DWRITE_FONT_AXIS_ATTRIBUTES_VARIABLE) { + float v = values[i].value; + uint32_t t = TRUETYPE_TAG( + uint8_t(values[i].axisTag), uint8_t(values[i].axisTag >> 8), + uint8_t(values[i].axisTag >> 16), uint8_t(values[i].axisTag >> 24)); + aOutput->push_back(FontVariation{uint32_t(t), float(v)}); + } + } +} + +bool ScaledFontDWrite::GetFontInstanceData(FontInstanceDataOutput aCb, + void* aBaton) { + InstanceData instance(this); + + // If the font has variations, get the list of axis values. + std::vector<FontVariation> variations; + GetVariationsFromFontFace(mFontFace, &variations); + + aCb(reinterpret_cast<uint8_t*>(&instance), sizeof(instance), + variations.data(), variations.size(), aBaton); + + return true; +} + +bool ScaledFontDWrite::GetWRFontInstanceOptions( + Maybe<wr::FontInstanceOptions>* aOutOptions, + Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions, + std::vector<FontVariation>* aOutVariations) { + wr::FontInstanceOptions options; + options.render_mode = wr::ToFontRenderMode(GetDefaultAAMode()); + options.flags = wr::FontInstanceFlags{0}; + if (HasBoldSimulation()) { + options.flags |= wr::FontInstanceFlags::SYNTHETIC_BOLD; + } + if (UseMultistrikeBold()) { + options.flags |= wr::FontInstanceFlags::MULTISTRIKE_BOLD; + } + if (UseEmbeddedBitmaps()) { + options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS; + } + if (ForceGDIMode()) { + options.flags |= wr::FontInstanceFlags::FORCE_GDI; + } else { + options.flags |= wr::FontInstanceFlags::SUBPIXEL_POSITION; + } + auto& settings = DWriteSettings(); + switch (settings.RenderingMode()) { + case DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC: + options.flags |= wr::FontInstanceFlags::FORCE_SYMMETRIC; + break; + case DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL: + options.flags |= wr::FontInstanceFlags::NO_SYMMETRIC; + break; + default: + break; + } + if (Factory::GetBGRSubpixelOrder()) { + options.flags |= wr::FontInstanceFlags::SUBPIXEL_BGR; + } + options.synthetic_italics = + wr::DegreesToSyntheticItalics(GetSyntheticObliqueAngle()); + + wr::FontInstancePlatformOptions platformOptions; + platformOptions.gamma = uint16_t(std::round(settings.Gamma() * 100.0f)); + platformOptions.contrast = + uint8_t(std::round(std::min(settings.EnhancedContrast(), 1.0f) * 100.0f)); + platformOptions.cleartype_level = + uint8_t(std::round(std::min(settings.ClearTypeLevel(), 1.0f) * 100.0f)); + + *aOutOptions = Some(options); + *aOutPlatformOptions = Some(platformOptions); + + GetVariationsFromFontFace(mFontFace, aOutVariations); + + return true; +} + +DWriteSettings& ScaledFontDWrite::DWriteSettings() const { + return DWriteSettings::Get(mGDIForced); +} + +// Helper for UnscaledFontDWrite::CreateScaledFont: create a clone of the +// given IDWriteFontFace, with specified variation-axis values applied. +// Returns nullptr in case of failure. +static already_AddRefed<IDWriteFontFace5> CreateFaceWithVariations( + IDWriteFontFace* aFace, DWRITE_FONT_SIMULATIONS aSimulations, + const FontVariation* aVariations = nullptr, uint32_t aNumVariations = 0) { + auto makeDWriteAxisTag = [](uint32_t aTag) { + return DWRITE_MAKE_FONT_AXIS_TAG((aTag >> 24) & 0xff, (aTag >> 16) & 0xff, + (aTag >> 8) & 0xff, aTag & 0xff); + }; + + MOZ_SEH_TRY { + RefPtr<IDWriteFontFace5> ff5; + aFace->QueryInterface(__uuidof(IDWriteFontFace5), + (void**)getter_AddRefs(ff5)); + if (!ff5) { + return nullptr; + } + + RefPtr<IDWriteFontResource> res; + if (FAILED(ff5->GetFontResource(getter_AddRefs(res)))) { + return nullptr; + } + + std::vector<DWRITE_FONT_AXIS_VALUE> fontAxisValues; + if (aNumVariations) { + fontAxisValues.reserve(aNumVariations); + for (uint32_t i = 0; i < aNumVariations; i++) { + DWRITE_FONT_AXIS_VALUE axisValue = { + makeDWriteAxisTag(aVariations[i].mTag), aVariations[i].mValue}; + fontAxisValues.push_back(axisValue); + } + } else { + uint32_t count = ff5->GetFontAxisValueCount(); + if (count) { + fontAxisValues.resize(count); + if (FAILED(ff5->GetFontAxisValues(fontAxisValues.data(), count))) { + fontAxisValues.clear(); + } + } + } + + RefPtr<IDWriteFontFace5> newFace; + if (FAILED(res->CreateFontFace(aSimulations, fontAxisValues.data(), + fontAxisValues.size(), + getter_AddRefs(newFace)))) { + return nullptr; + } + return newFace.forget(); + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalNote << "Exception occurred initializing variation face"; + return nullptr; + } +} + +bool UnscaledFontDWrite::InitBold() { + if (mFontFaceBold) { + return true; + } + + DWRITE_FONT_SIMULATIONS sims = mFontFace->GetSimulations(); + if (sims & DWRITE_FONT_SIMULATIONS_BOLD) { + mFontFaceBold = mFontFace; + return true; + } + sims |= DWRITE_FONT_SIMULATIONS_BOLD; + + RefPtr<IDWriteFontFace5> ff5 = CreateFaceWithVariations(mFontFace, sims); + if (ff5) { + mFontFaceBold = ff5; + } else { + MOZ_SEH_TRY { + UINT32 numFiles = 0; + if (FAILED(mFontFace->GetFiles(&numFiles, nullptr))) { + return false; + } + StackArray<IDWriteFontFile*, 1> files(numFiles); + if (FAILED(mFontFace->GetFiles(&numFiles, files.data()))) { + return false; + } + HRESULT hr = Factory::GetDWriteFactory()->CreateFontFace( + mFontFace->GetType(), numFiles, files.data(), mFontFace->GetIndex(), + sims, getter_AddRefs(mFontFaceBold)); + for (UINT32 i = 0; i < numFiles; ++i) { + files[i]->Release(); + } + if (FAILED(hr) || !mFontFaceBold) { + return false; + } + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalNote << "Exception occurred initializing bold face"; + return false; + } + } + return true; +} + +already_AddRefed<ScaledFont> UnscaledFontDWrite::CreateScaledFont( + Float aGlyphSize, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength, const FontVariation* aVariations, + uint32_t aNumVariations) { + if (aInstanceDataLength < sizeof(ScaledFontDWrite::InstanceData)) { + gfxWarning() << "DWrite scaled font instance data is truncated."; + return nullptr; + } + const ScaledFontDWrite::InstanceData& instanceData = + *reinterpret_cast<const ScaledFontDWrite::InstanceData*>(aInstanceData); + + IDWriteFontFace* face = mFontFace; + if (instanceData.mUseBoldSimulation) { + if (!InitBold()) { + gfxWarning() << "Failed creating bold IDWriteFontFace."; + return nullptr; + } + face = mFontFaceBold; + } + DWRITE_FONT_SIMULATIONS sims = face->GetSimulations(); + + // If variations are required, we create a separate IDWriteFontFace5 with + // the requested settings applied. + RefPtr<IDWriteFontFace5> ff5; + if (aNumVariations) { + ff5 = + CreateFaceWithVariations(mFontFace, sims, aVariations, aNumVariations); + if (ff5) { + face = ff5; + } else { + gfxWarning() << "Failed to create IDWriteFontFace5 with variations."; + } + } + + return MakeAndAddRef<ScaledFontDWrite>( + face, this, aGlyphSize, instanceData.mUseEmbeddedBitmap, + instanceData.mUseMultistrikeBold, instanceData.mGDIForced, nullptr); +} + +already_AddRefed<ScaledFont> UnscaledFontDWrite::CreateScaledFontFromWRFont( + Float aGlyphSize, const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions, + const FontVariation* aVariations, uint32_t aNumVariations) { + ScaledFontDWrite::InstanceData instanceData(aOptions, aPlatformOptions); + return CreateScaledFont(aGlyphSize, reinterpret_cast<uint8_t*>(&instanceData), + sizeof(instanceData), aVariations, aNumVariations); +} + +AntialiasMode ScaledFontDWrite::GetDefaultAAMode() { + AntialiasMode defaultMode = GetSystemDefaultAAMode(); + + switch (defaultMode) { + case AntialiasMode::SUBPIXEL: + case AntialiasMode::DEFAULT: + if (DWriteSettings().ClearTypeLevel() == 0.0f) { + defaultMode = AntialiasMode::GRAY; + } + break; + case AntialiasMode::GRAY: + if (!DoGrayscale(mFontFace, mSize)) { + defaultMode = AntialiasMode::NONE; + } + break; + case AntialiasMode::NONE: + break; + } + + return defaultMode; +} + +cairo_font_face_t* ScaledFontDWrite::CreateCairoFontFace( + cairo_font_options_t* aFontOptions) { + if (!mFontFace) { + return nullptr; + } + + return cairo_dwrite_font_face_create_for_dwrite_fontface(nullptr, mFontFace); +} + +void ScaledFontDWrite::PrepareCairoScaledFont(cairo_scaled_font_t* aFont) { + if (mGDIForced) { + cairo_dwrite_scaled_font_set_force_GDI_classic(aFont, true); + } +} + +already_AddRefed<UnscaledFont> UnscaledFontDWrite::CreateFromFontDescriptor( + const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex) { + // Note that despite the type of aData here, it actually points to a 16-bit + // Windows font file path (hence the cast to WCHAR* below). + if (aDataLength == 0) { + gfxWarning() << "DWrite font descriptor is truncated."; + return nullptr; + } + + RefPtr<IDWriteFactory> factory = Factory::GetDWriteFactory(); + if (!factory) { + return nullptr; + } + + MOZ_SEH_TRY { + RefPtr<IDWriteFontFile> fontFile; + HRESULT hr = factory->CreateFontFileReference((const WCHAR*)aData, nullptr, + getter_AddRefs(fontFile)); + if (FAILED(hr)) { + return nullptr; + } + BOOL isSupported; + DWRITE_FONT_FILE_TYPE fileType; + DWRITE_FONT_FACE_TYPE faceType; + UINT32 numFaces; + hr = fontFile->Analyze(&isSupported, &fileType, &faceType, &numFaces); + if (FAILED(hr) || !isSupported || aIndex >= numFaces) { + return nullptr; + } + IDWriteFontFile* fontFiles[1] = {fontFile.get()}; + RefPtr<IDWriteFontFace> fontFace; + hr = factory->CreateFontFace(faceType, 1, fontFiles, aIndex, + DWRITE_FONT_SIMULATIONS_NONE, + getter_AddRefs(fontFace)); + if (FAILED(hr)) { + return nullptr; + } + RefPtr unscaledFont = new UnscaledFontDWrite(fontFace, nullptr); + return unscaledFont.forget(); + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + gfxCriticalNote << "Exception occurred creating unscaledFont for " + << NS_ConvertUTF16toUTF8((const char16_t*)aData).get(); + return nullptr; + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/ScaledFontDWrite.h b/gfx/2d/ScaledFontDWrite.h new file mode 100644 index 0000000000..da1568b8ca --- /dev/null +++ b/gfx/2d/ScaledFontDWrite.h @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SCALEDFONTDWRITE_H_ +#define MOZILLA_GFX_SCALEDFONTDWRITE_H_ + +#include <dwrite.h> +#include "DWriteSettings.h" +#include "ScaledFontBase.h" + +struct ID2D1GeometrySink; +struct gfxFontStyle; + +namespace mozilla { +namespace gfx { + +class NativeFontResourceDWrite; +class UnscaledFontDWrite; + +class ScaledFontDWrite final : public ScaledFontBase { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontDWrite, override) + ScaledFontDWrite(IDWriteFontFace* aFontFace, + const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize, + bool aUseEmbeddedBitmap, bool aUseMultistrikeBold, + bool aGDIForced, const gfxFontStyle* aStyle); + + FontType GetType() const override { return FontType::DWRITE; } + + already_AddRefed<Path> GetPathForGlyphs(const GlyphBuffer& aBuffer, + const DrawTarget* aTarget) override; + void CopyGlyphsToBuilder(const GlyphBuffer& aBuffer, PathBuilder* aBuilder, + const Matrix* aTransformHint) override; + + void CopyGlyphsToSink(const GlyphBuffer& aBuffer, + ID2D1SimplifiedGeometrySink* aSink); + + bool CanSerialize() override { return true; } + + bool MayUseBitmaps() override; + + bool GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) override; + + bool GetWRFontInstanceOptions( + Maybe<wr::FontInstanceOptions>* aOutOptions, + Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions, + std::vector<FontVariation>* aOutVariations) override; + + DWriteSettings& DWriteSettings() const; + + AntialiasMode GetDefaultAAMode() override; + + bool UseEmbeddedBitmaps() const { return mUseEmbeddedBitmap; } + bool UseMultistrikeBold() const { return mUseMultistrikeBold; } + bool ForceGDIMode() const { return mGDIForced; } + + bool UseSubpixelPosition() const override { return !ForceGDIMode(); } + + bool HasBoldSimulation() const { + return (mFontFace->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD) != 0; + } + + bool HasVariationSettings() override; + + SkTypeface* CreateSkTypeface() override; + void SetupSkFontDrawOptions(SkFont& aFont) override; + SkFontStyle mStyle; + + RefPtr<IDWriteFontFace> mFontFace; + bool mUseEmbeddedBitmap; + bool mUseMultistrikeBold = false; + bool mGDIForced = false; + + cairo_font_face_t* CreateCairoFontFace( + cairo_font_options_t* aFontOptions) override; + void PrepareCairoScaledFont(cairo_scaled_font_t* aFont) override; + + private: + friend class NativeFontResourceDWrite; + friend class UnscaledFontDWrite; + + struct InstanceData { + explicit InstanceData(ScaledFontDWrite* aScaledFont) + : mUseEmbeddedBitmap(aScaledFont->mUseEmbeddedBitmap), + mUseBoldSimulation(aScaledFont->HasBoldSimulation()), + mUseMultistrikeBold(aScaledFont->UseMultistrikeBold()), + mGDIForced(aScaledFont->mGDIForced) {} + + InstanceData(const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions); + + bool mUseEmbeddedBitmap = false; + bool mUseBoldSimulation = false; + bool mUseMultistrikeBold = false; + bool mGDIForced = false; + }; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SCALEDFONTDWRITE_H_ */ diff --git a/gfx/2d/ScaledFontFontconfig.cpp b/gfx/2d/ScaledFontFontconfig.cpp new file mode 100644 index 0000000000..01e4b30092 --- /dev/null +++ b/gfx/2d/ScaledFontFontconfig.cpp @@ -0,0 +1,549 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScaledFontFontconfig.h" +#include "UnscaledFontFreeType.h" +#include "Logging.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/webrender/WebRenderTypes.h" + +#include "skia/include/ports/SkTypeface_cairo.h" +#include "HelpersSkia.h" + +#include <fontconfig/fcfreetype.h> + +#include FT_LCD_FILTER_H +#include FT_MULTIPLE_MASTERS_H + +namespace mozilla::gfx { + +ScaledFontFontconfig::ScaledFontFontconfig( + RefPtr<SharedFTFace>&& aFace, FcPattern* aPattern, + const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize) + : ScaledFontBase(aUnscaledFont, aSize), + mFace(std::move(aFace)), + mInstanceData(aPattern) {} + +ScaledFontFontconfig::ScaledFontFontconfig( + RefPtr<SharedFTFace>&& aFace, const InstanceData& aInstanceData, + const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize) + : ScaledFontBase(aUnscaledFont, aSize), + mFace(std::move(aFace)), + mInstanceData(aInstanceData) {} + +bool ScaledFontFontconfig::UseSubpixelPosition() const { + return !MOZ_UNLIKELY( + StaticPrefs:: + gfx_text_subpixel_position_force_disabled_AtStartup()) && + mInstanceData.mAntialias != AntialiasMode::NONE && + FT_IS_SCALABLE(mFace->GetFace()) && + (mInstanceData.mHinting == FontHinting::NONE || + mInstanceData.mHinting == FontHinting::LIGHT || + MOZ_UNLIKELY( + StaticPrefs:: + gfx_text_subpixel_position_force_enabled_AtStartup())); +} + +SkTypeface* ScaledFontFontconfig::CreateSkTypeface() { + SkPixelGeometry geo = mInstanceData.mFlags & InstanceData::SUBPIXEL_BGR + ? (mInstanceData.mFlags & InstanceData::LCD_VERTICAL + ? kBGR_V_SkPixelGeometry + : kBGR_H_SkPixelGeometry) + : (mInstanceData.mFlags & InstanceData::LCD_VERTICAL + ? kRGB_V_SkPixelGeometry + : kRGB_H_SkPixelGeometry); + return SkCreateTypefaceFromCairoFTFont(mFace->GetFace(), mFace.get(), geo, + mInstanceData.mLcdFilter); +} + +void ScaledFontFontconfig::SetupSkFontDrawOptions(SkFont& aFont) { + aFont.setSubpixel(UseSubpixelPosition()); + + if (mInstanceData.mFlags & InstanceData::AUTOHINT) { + aFont.setForceAutoHinting(true); + } + if (mInstanceData.mFlags & InstanceData::EMBEDDED_BITMAP) { + aFont.setEmbeddedBitmaps(true); + } + if (mInstanceData.mFlags & InstanceData::EMBOLDEN) { + aFont.setEmbolden(true); + } + + aFont.setHinting(GfxHintingToSkiaHinting(mInstanceData.mHinting)); +} + +bool ScaledFontFontconfig::MayUseBitmaps() { + return mInstanceData.mFlags & InstanceData::EMBEDDED_BITMAP && + !FT_IS_SCALABLE(mFace->GetFace()); +} + +cairo_font_face_t* ScaledFontFontconfig::CreateCairoFontFace( + cairo_font_options_t* aFontOptions) { + int loadFlags; + unsigned int synthFlags; + mInstanceData.SetupFontOptions(aFontOptions, &loadFlags, &synthFlags); + + return cairo_ft_font_face_create_for_ft_face(mFace->GetFace(), loadFlags, + synthFlags, mFace.get()); +} + +AntialiasMode ScaledFontFontconfig::GetDefaultAAMode() { + return mInstanceData.mAntialias; +} + +bool FcPatternAllowsBitmaps(FcPattern* aPattern, bool aAntialias, + bool aHinting) { + if (!aAntialias) { + // Always allow bitmaps when antialiasing is disabled + return true; + } + FcBool bitmap; + if (FcPatternGetBool(aPattern, FC_EMBEDDED_BITMAP, 0, &bitmap) != + FcResultMatch || + !bitmap) { + // If bitmaps were explicitly disabled, then disallow them + return false; + } + if (aHinting) { + // If hinting is used and bitmaps were enabled, then allow them + return true; + } + // When hinting is disabled, then avoid loading bitmaps from outline + // fonts. However, emoji fonts may have no outlines while containing + // bitmaps intended to be scaled, so still allow those. + FcBool outline; + if (FcPatternGetBool(aPattern, FC_OUTLINE, 0, &outline) == FcResultMatch && + outline) { + return false; + } + FcBool scalable; + if (FcPatternGetBool(aPattern, FC_SCALABLE, 0, &scalable) != FcResultMatch || + !scalable) { + return false; + } + return true; +} + +ScaledFontFontconfig::InstanceData::InstanceData(FcPattern* aPattern) + : mFlags(0), + mAntialias(AntialiasMode::NONE), + mHinting(FontHinting::NONE), + mLcdFilter(FT_LCD_FILTER_LEGACY) { + // Record relevant Fontconfig properties into instance data. + FcBool autohint; + if (FcPatternGetBool(aPattern, FC_AUTOHINT, 0, &autohint) == FcResultMatch && + autohint) { + mFlags |= AUTOHINT; + } + FcBool embolden; + if (FcPatternGetBool(aPattern, FC_EMBOLDEN, 0, &embolden) == FcResultMatch && + embolden) { + mFlags |= EMBOLDEN; + } + + // For printer fonts, Cairo hint metrics and hinting will be disabled. + // For other fonts, allow hint metrics and hinting. + FcBool printing; + if (FcPatternGetBool(aPattern, "gfx.printing", 0, &printing) != + FcResultMatch || + !printing) { + mFlags |= HINT_METRICS; + + FcBool hinting; + if (FcPatternGetBool(aPattern, FC_HINTING, 0, &hinting) != FcResultMatch || + hinting) { + int hintstyle; + if (FcPatternGetInteger(aPattern, FC_HINT_STYLE, 0, &hintstyle) != + FcResultMatch) { + hintstyle = FC_HINT_FULL; + } + switch (hintstyle) { + case FC_HINT_SLIGHT: + mHinting = FontHinting::LIGHT; + break; + case FC_HINT_MEDIUM: + mHinting = FontHinting::NORMAL; + break; + case FC_HINT_FULL: + mHinting = FontHinting::FULL; + break; + case FC_HINT_NONE: + default: + break; + } + } + } + + FcBool antialias; + if (FcPatternGetBool(aPattern, FC_ANTIALIAS, 0, &antialias) == + FcResultMatch && + !antialias) { + // If AA is explicitly disabled, leave bitmaps enabled. + mFlags |= EMBEDDED_BITMAP; + } else { + mAntialias = AntialiasMode::GRAY; + + // Otherwise, if AA is enabled, disable embedded bitmaps unless explicitly + // enabled. + if (FcPatternAllowsBitmaps(aPattern, true, mHinting != FontHinting::NONE)) { + mFlags |= EMBEDDED_BITMAP; + } + + // Only record subpixel order and lcd filtering if antialiasing is enabled. + int rgba; + if (mFlags & HINT_METRICS && + FcPatternGetInteger(aPattern, FC_RGBA, 0, &rgba) == FcResultMatch) { + switch (rgba) { + case FC_RGBA_RGB: + case FC_RGBA_BGR: + case FC_RGBA_VRGB: + case FC_RGBA_VBGR: + mAntialias = AntialiasMode::SUBPIXEL; + if (rgba == FC_RGBA_VRGB || rgba == FC_RGBA_VBGR) { + mFlags |= LCD_VERTICAL; + } + if (rgba == FC_RGBA_BGR || rgba == FC_RGBA_VBGR) { + mFlags |= SUBPIXEL_BGR; + } + break; + case FC_RGBA_NONE: + case FC_RGBA_UNKNOWN: + default: + break; + } + } + + int filter; + if (mAntialias == AntialiasMode::SUBPIXEL && + FcPatternGetInteger(aPattern, FC_LCD_FILTER, 0, &filter) == + FcResultMatch) { + switch (filter) { + case FC_LCD_NONE: + mLcdFilter = FT_LCD_FILTER_NONE; + break; + case FC_LCD_DEFAULT: + mLcdFilter = FT_LCD_FILTER_DEFAULT; + break; + case FC_LCD_LIGHT: + mLcdFilter = FT_LCD_FILTER_LIGHT; + break; + case FC_LCD_LEGACY: + default: + break; + } + } + } +} + +ScaledFontFontconfig::InstanceData::InstanceData( + const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions) + : mFlags(HINT_METRICS), + mAntialias(AntialiasMode::NONE), + mHinting(FontHinting::FULL), + mLcdFilter(FT_LCD_FILTER_LEGACY) { + if (aOptions) { + if (aOptions->flags & wr::FontInstanceFlags::FORCE_AUTOHINT) { + mFlags |= AUTOHINT; + } + if (aOptions->flags & wr::FontInstanceFlags::EMBEDDED_BITMAPS) { + mFlags |= EMBEDDED_BITMAP; + } + if (aOptions->flags & wr::FontInstanceFlags::SYNTHETIC_BOLD) { + mFlags |= EMBOLDEN; + } + if (aOptions->render_mode == wr::FontRenderMode::Subpixel) { + mAntialias = AntialiasMode::SUBPIXEL; + if (aOptions->flags & wr::FontInstanceFlags::SUBPIXEL_BGR) { + mFlags |= SUBPIXEL_BGR; + } + if (aOptions->flags & wr::FontInstanceFlags::LCD_VERTICAL) { + mFlags |= LCD_VERTICAL; + } + } else if (aOptions->render_mode != wr::FontRenderMode::Mono) { + mAntialias = AntialiasMode::GRAY; + } + } + if (aPlatformOptions) { + switch (aPlatformOptions->hinting) { + case wr::FontHinting::None: + mHinting = FontHinting::NONE; + break; + case wr::FontHinting::Light: + mHinting = FontHinting::LIGHT; + break; + case wr::FontHinting::Normal: + mHinting = FontHinting::NORMAL; + break; + default: + break; + } + switch (aPlatformOptions->lcd_filter) { + case wr::FontLCDFilter::None: + mLcdFilter = FT_LCD_FILTER_NONE; + break; + case wr::FontLCDFilter::Default: + mLcdFilter = FT_LCD_FILTER_DEFAULT; + break; + case wr::FontLCDFilter::Light: + mLcdFilter = FT_LCD_FILTER_LIGHT; + break; + default: + break; + } + } +} + +void ScaledFontFontconfig::InstanceData::SetupFontOptions( + cairo_font_options_t* aFontOptions, int* aOutLoadFlags, + unsigned int* aOutSynthFlags) const { + // For regular (non-printer) fonts, enable hint metrics as well as hinting + // and (possibly subpixel) antialiasing. + cairo_font_options_set_hint_metrics( + aFontOptions, + mFlags & HINT_METRICS ? CAIRO_HINT_METRICS_ON : CAIRO_HINT_METRICS_OFF); + + cairo_hint_style_t hinting; + switch (mHinting) { + case FontHinting::NONE: + hinting = CAIRO_HINT_STYLE_NONE; + break; + case FontHinting::LIGHT: + hinting = CAIRO_HINT_STYLE_SLIGHT; + break; + case FontHinting::NORMAL: + hinting = CAIRO_HINT_STYLE_MEDIUM; + break; + case FontHinting::FULL: + hinting = CAIRO_HINT_STYLE_FULL; + break; + } + cairo_font_options_set_hint_style(aFontOptions, hinting); + + switch (mAntialias) { + case AntialiasMode::NONE: + cairo_font_options_set_antialias(aFontOptions, CAIRO_ANTIALIAS_NONE); + break; + case AntialiasMode::GRAY: + default: + cairo_font_options_set_antialias(aFontOptions, CAIRO_ANTIALIAS_GRAY); + break; + case AntialiasMode::SUBPIXEL: { + cairo_font_options_set_antialias(aFontOptions, CAIRO_ANTIALIAS_SUBPIXEL); + cairo_font_options_set_subpixel_order( + aFontOptions, + mFlags & SUBPIXEL_BGR + ? (mFlags & LCD_VERTICAL ? CAIRO_SUBPIXEL_ORDER_VBGR + : CAIRO_SUBPIXEL_ORDER_BGR) + : (mFlags & LCD_VERTICAL ? CAIRO_SUBPIXEL_ORDER_VRGB + : CAIRO_SUBPIXEL_ORDER_RGB)); + cairo_lcd_filter_t lcdFilter = CAIRO_LCD_FILTER_DEFAULT; + switch (mLcdFilter) { + case FT_LCD_FILTER_NONE: + lcdFilter = CAIRO_LCD_FILTER_NONE; + break; + case FT_LCD_FILTER_DEFAULT: + lcdFilter = CAIRO_LCD_FILTER_FIR5; + break; + case FT_LCD_FILTER_LIGHT: + lcdFilter = CAIRO_LCD_FILTER_FIR3; + break; + case FT_LCD_FILTER_LEGACY: + lcdFilter = CAIRO_LCD_FILTER_INTRA_PIXEL; + break; + } + cairo_font_options_set_lcd_filter(aFontOptions, lcdFilter); + break; + } + } + + // Try to build a sane initial set of Cairo font options based on the + // Fontconfig pattern. + int loadFlags = FT_LOAD_DEFAULT; + unsigned int synthFlags = 0; + + if (!(mFlags & EMBEDDED_BITMAP)) { + loadFlags |= FT_LOAD_NO_BITMAP; + } + if (mFlags & AUTOHINT) { + loadFlags |= FT_LOAD_FORCE_AUTOHINT; + } + if (mFlags & EMBOLDEN) { + synthFlags |= CAIRO_FT_SYNTHESIZE_BOLD; + } + + *aOutLoadFlags = loadFlags; + *aOutSynthFlags = synthFlags; +} + +bool ScaledFontFontconfig::GetFontInstanceData(FontInstanceDataOutput aCb, + void* aBaton) { + std::vector<FontVariation> variations; + if (HasVariationSettings()) { + UnscaledFontFreeType::GetVariationSettingsFromFace(&variations, + mFace->GetFace()); + } + + aCb(reinterpret_cast<uint8_t*>(&mInstanceData), sizeof(mInstanceData), + variations.data(), variations.size(), aBaton); + return true; +} + +bool ScaledFontFontconfig::GetWRFontInstanceOptions( + Maybe<wr::FontInstanceOptions>* aOutOptions, + Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions, + std::vector<FontVariation>* aOutVariations) { + wr::FontInstanceOptions options; + options.render_mode = wr::FontRenderMode::Alpha; + options.flags = wr::FontInstanceFlags{0}; + if (UseSubpixelPosition()) { + options.flags |= wr::FontInstanceFlags::SUBPIXEL_POSITION; + } + options.synthetic_italics = + wr::DegreesToSyntheticItalics(GetSyntheticObliqueAngle()); + + wr::FontInstancePlatformOptions platformOptions; + platformOptions.lcd_filter = wr::FontLCDFilter::Legacy; + platformOptions.hinting = wr::FontHinting::Normal; + + if (mInstanceData.mFlags & InstanceData::AUTOHINT) { + options.flags |= wr::FontInstanceFlags::FORCE_AUTOHINT; + } + if (mInstanceData.mFlags & InstanceData::EMBOLDEN) { + options.flags |= wr::FontInstanceFlags::SYNTHETIC_BOLD; + } + if (mInstanceData.mFlags & InstanceData::EMBEDDED_BITMAP) { + options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS; + } + if (mInstanceData.mAntialias != AntialiasMode::NONE) { + if (mInstanceData.mAntialias == AntialiasMode::SUBPIXEL) { + options.render_mode = wr::FontRenderMode::Subpixel; + platformOptions.hinting = wr::FontHinting::LCD; + if (mInstanceData.mFlags & InstanceData::LCD_VERTICAL) { + options.flags |= wr::FontInstanceFlags::LCD_VERTICAL; + } + if (mInstanceData.mFlags & InstanceData::SUBPIXEL_BGR) { + options.flags |= wr::FontInstanceFlags::SUBPIXEL_BGR; + } + } + + switch (mInstanceData.mLcdFilter) { + case FT_LCD_FILTER_NONE: + platformOptions.lcd_filter = wr::FontLCDFilter::None; + break; + case FT_LCD_FILTER_DEFAULT: + platformOptions.lcd_filter = wr::FontLCDFilter::Default; + break; + case FT_LCD_FILTER_LIGHT: + platformOptions.lcd_filter = wr::FontLCDFilter::Light; + break; + case FT_LCD_FILTER_LEGACY: + default: + break; + } + + switch (mInstanceData.mHinting) { + case FontHinting::NONE: + platformOptions.hinting = wr::FontHinting::None; + break; + case FontHinting::LIGHT: + platformOptions.hinting = wr::FontHinting::Light; + break; + case FontHinting::NORMAL: + platformOptions.hinting = wr::FontHinting::Normal; + break; + case FontHinting::FULL: + break; + } + } else { + options.render_mode = wr::FontRenderMode::Mono; + + switch (mInstanceData.mHinting) { + case FontHinting::NONE: + platformOptions.hinting = wr::FontHinting::None; + break; + default: + platformOptions.hinting = wr::FontHinting::Mono; + break; + } + } + + *aOutOptions = Some(options); + *aOutPlatformOptions = Some(platformOptions); + + if (HasVariationSettings()) { + UnscaledFontFreeType::GetVariationSettingsFromFace(aOutVariations, + mFace->GetFace()); + } + + return true; +} + +already_AddRefed<ScaledFont> UnscaledFontFontconfig::CreateScaledFont( + Float aSize, const uint8_t* aInstanceData, uint32_t aInstanceDataLength, + const FontVariation* aVariations, uint32_t aNumVariations) { + if (aInstanceDataLength < sizeof(ScaledFontFontconfig::InstanceData)) { + gfxWarning() << "Fontconfig scaled font instance data is truncated."; + return nullptr; + } + const ScaledFontFontconfig::InstanceData& instanceData = + *reinterpret_cast<const ScaledFontFontconfig::InstanceData*>( + aInstanceData); + + RefPtr<SharedFTFace> face(InitFace()); + if (!face) { + gfxWarning() << "Attempted to deserialize Fontconfig scaled font without " + "FreeType face"; + return nullptr; + } + + if (aNumVariations > 0 && face->GetData()) { + if (RefPtr<SharedFTFace> varFace = face->GetData()->CloneFace()) { + face = varFace; + } + } + + // Only apply variations if we have an explicitly cloned face. + if (aNumVariations > 0 && face != GetFace()) { + ApplyVariationsToFace(aVariations, aNumVariations, face->GetFace()); + } + + RefPtr<ScaledFontFontconfig> scaledFont = + new ScaledFontFontconfig(std::move(face), instanceData, this, aSize); + + return scaledFont.forget(); +} + +already_AddRefed<ScaledFont> UnscaledFontFontconfig::CreateScaledFontFromWRFont( + Float aGlyphSize, const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions, + const FontVariation* aVariations, uint32_t aNumVariations) { + ScaledFontFontconfig::InstanceData instanceData(aOptions, aPlatformOptions); + return CreateScaledFont(aGlyphSize, reinterpret_cast<uint8_t*>(&instanceData), + sizeof(instanceData), aVariations, aNumVariations); +} + +bool ScaledFontFontconfig::HasVariationSettings() { + // Check if the FT face has been cloned. + return mFace && + mFace->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS && + mFace != static_cast<UnscaledFontFontconfig*>(mUnscaledFont.get()) + ->GetFace(); +} + +already_AddRefed<UnscaledFont> UnscaledFontFontconfig::CreateFromFontDescriptor( + const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex) { + if (aDataLength == 0) { + gfxWarning() << "Fontconfig font descriptor is truncated."; + return nullptr; + } + const char* path = reinterpret_cast<const char*>(aData); + RefPtr<UnscaledFont> unscaledFont = + new UnscaledFontFontconfig(std::string(path, aDataLength), aIndex); + return unscaledFont.forget(); +} + +} // namespace mozilla::gfx diff --git a/gfx/2d/ScaledFontFontconfig.h b/gfx/2d/ScaledFontFontconfig.h new file mode 100644 index 0000000000..e6dfd48097 --- /dev/null +++ b/gfx/2d/ScaledFontFontconfig.h @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SCALEDFONTFONTCONFIG_H_ +#define MOZILLA_GFX_SCALEDFONTFONTCONFIG_H_ + +#include "ScaledFontBase.h" + +#include <cairo-ft.h> + +namespace mozilla { +namespace gfx { + +class NativeFontResourceFontconfig; +class UnscaledFontFontconfig; + +class ScaledFontFontconfig : public ScaledFontBase { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontFontconfig, override) + ScaledFontFontconfig(RefPtr<SharedFTFace>&& aFace, FcPattern* aPattern, + const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize); + + FontType GetType() const override { return FontType::FONTCONFIG; } + + SkTypeface* CreateSkTypeface() override; + void SetupSkFontDrawOptions(SkFont& aFont) override; + + AntialiasMode GetDefaultAAMode() override; + + bool UseSubpixelPosition() const override; + + bool CanSerialize() override { return true; } + + bool MayUseBitmaps() override; + + bool GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) override; + + bool GetWRFontInstanceOptions( + Maybe<wr::FontInstanceOptions>* aOutOptions, + Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions, + std::vector<FontVariation>* aOutVariations) override; + + bool HasVariationSettings() override; + + protected: + cairo_font_face_t* CreateCairoFontFace( + cairo_font_options_t* aFontOptions) override; + + private: + friend class NativeFontResourceFontconfig; + friend class UnscaledFontFontconfig; + + struct InstanceData { + enum { + AUTOHINT = 1 << 0, + EMBEDDED_BITMAP = 1 << 1, + EMBOLDEN = 1 << 2, + HINT_METRICS = 1 << 3, + LCD_VERTICAL = 1 << 4, + SUBPIXEL_BGR = 1 << 5, + }; + + explicit InstanceData(FcPattern* aPattern); + InstanceData(const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions); + + void SetupFontOptions(cairo_font_options_t* aFontOptions, + int* aOutLoadFlags, + unsigned int* aOutSynthFlags) const; + + uint8_t mFlags; + AntialiasMode mAntialias; + FontHinting mHinting; + uint8_t mLcdFilter; + }; + + ScaledFontFontconfig(RefPtr<SharedFTFace>&& aFace, + const InstanceData& aInstanceData, + const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize); + + RefPtr<SharedFTFace> mFace; + InstanceData mInstanceData; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SCALEDFONTFONTCONFIG_H_ */ diff --git a/gfx/2d/ScaledFontFreeType.cpp b/gfx/2d/ScaledFontFreeType.cpp new file mode 100644 index 0000000000..6575a97d5d --- /dev/null +++ b/gfx/2d/ScaledFontFreeType.cpp @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScaledFontFreeType.h" +#include "UnscaledFontFreeType.h" +#include "NativeFontResourceFreeType.h" +#include "Logging.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/webrender/WebRenderTypes.h" + +#include "skia/include/ports/SkTypeface_cairo.h" + +#include FT_MULTIPLE_MASTERS_H + +namespace mozilla { +namespace gfx { + +ScaledFontFreeType::ScaledFontFreeType( + RefPtr<SharedFTFace>&& aFace, const RefPtr<UnscaledFont>& aUnscaledFont, + Float aSize, bool aApplySyntheticBold) + : ScaledFontBase(aUnscaledFont, aSize), + mFace(std::move(aFace)), + mApplySyntheticBold(aApplySyntheticBold) {} + +bool ScaledFontFreeType::UseSubpixelPosition() const { + return !MOZ_UNLIKELY( + StaticPrefs:: + gfx_text_subpixel_position_force_disabled_AtStartup()) && + FT_IS_SCALABLE(mFace->GetFace()); +} + +SkTypeface* ScaledFontFreeType::CreateSkTypeface() { + return SkCreateTypefaceFromCairoFTFont(mFace->GetFace(), mFace.get()); +} + +void ScaledFontFreeType::SetupSkFontDrawOptions(SkFont& aFont) { + aFont.setSubpixel(UseSubpixelPosition()); + + if (mApplySyntheticBold) { + aFont.setEmbolden(true); + } + + aFont.setEmbeddedBitmaps(true); +} + +bool ScaledFontFreeType::MayUseBitmaps() { + return !FT_IS_SCALABLE(mFace->GetFace()); +} + +cairo_font_face_t* ScaledFontFreeType::CreateCairoFontFace( + cairo_font_options_t* aFontOptions) { + cairo_font_options_set_hint_metrics(aFontOptions, CAIRO_HINT_METRICS_OFF); + + int loadFlags = FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING; + if (mFace->GetFace()->face_flags & FT_FACE_FLAG_TRICKY) { + loadFlags &= ~FT_LOAD_NO_AUTOHINT; + } + + unsigned int synthFlags = 0; + if (mApplySyntheticBold) { + synthFlags |= CAIRO_FT_SYNTHESIZE_BOLD; + } + + return cairo_ft_font_face_create_for_ft_face(mFace->GetFace(), loadFlags, + synthFlags, mFace.get()); +} + +bool ScaledFontFreeType::GetFontInstanceData(FontInstanceDataOutput aCb, + void* aBaton) { + std::vector<FontVariation> variations; + if (HasVariationSettings()) { + UnscaledFontFreeType::GetVariationSettingsFromFace(&variations, + mFace->GetFace()); + } + + InstanceData instance(this); + aCb(reinterpret_cast<uint8_t*>(&instance), sizeof(instance), + variations.data(), variations.size(), aBaton); + return true; +} + +bool ScaledFontFreeType::GetWRFontInstanceOptions( + Maybe<wr::FontInstanceOptions>* aOutOptions, + Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions, + std::vector<FontVariation>* aOutVariations) { + wr::FontInstanceOptions options; + options.render_mode = wr::FontRenderMode::Alpha; + options.flags = wr::FontInstanceFlags{0}; + if (UseSubpixelPosition()) { + options.flags |= wr::FontInstanceFlags::SUBPIXEL_POSITION; + } + options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS; + options.synthetic_italics = + wr::DegreesToSyntheticItalics(GetSyntheticObliqueAngle()); + + if (mApplySyntheticBold) { + options.flags |= wr::FontInstanceFlags::SYNTHETIC_BOLD; + } + + wr::FontInstancePlatformOptions platformOptions; + platformOptions.lcd_filter = wr::FontLCDFilter::None; + platformOptions.hinting = wr::FontHinting::None; + + *aOutOptions = Some(options); + *aOutPlatformOptions = Some(platformOptions); + + if (HasVariationSettings()) { + UnscaledFontFreeType::GetVariationSettingsFromFace(aOutVariations, + mFace->GetFace()); + } + + return true; +} + +ScaledFontFreeType::InstanceData::InstanceData( + const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions) + : mApplySyntheticBold(false) { + if (aOptions) { + if (aOptions->flags & wr::FontInstanceFlags::SYNTHETIC_BOLD) { + mApplySyntheticBold = true; + } + } +} + +bool ScaledFontFreeType::HasVariationSettings() { + // Check if the FT face has been cloned. + return mFace && + mFace->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS && + mFace != + static_cast<UnscaledFontFreeType*>(mUnscaledFont.get())->GetFace(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/ScaledFontFreeType.h b/gfx/2d/ScaledFontFreeType.h new file mode 100644 index 0000000000..5a76e8e6c0 --- /dev/null +++ b/gfx/2d/ScaledFontFreeType.h @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SCALEDFONTCAIRO_H_ +#define MOZILLA_GFX_SCALEDFONTCAIRO_H_ + +#include "ScaledFontBase.h" + +#include <cairo-ft.h> + +namespace mozilla { +namespace gfx { + +class UnscaledFontFreeType; + +class ScaledFontFreeType : public ScaledFontBase { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontFreeType, override) + + ScaledFontFreeType(RefPtr<SharedFTFace>&& aFace, + const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize, + bool aApplySyntheticBold = false); + + FontType GetType() const override { return FontType::FREETYPE; } + + SkTypeface* CreateSkTypeface() override; + void SetupSkFontDrawOptions(SkFont& aFont) override; + + AntialiasMode GetDefaultAAMode() override { return AntialiasMode::GRAY; } + + bool UseSubpixelPosition() const override; + + bool CanSerialize() override { return true; } + + bool MayUseBitmaps() override; + + bool GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) override; + + bool GetWRFontInstanceOptions( + Maybe<wr::FontInstanceOptions>* aOutOptions, + Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions, + std::vector<FontVariation>* aOutVariations) override; + + bool HasVariationSettings() override; + + protected: + cairo_font_face_t* CreateCairoFontFace( + cairo_font_options_t* aFontOptions) override; + + private: + friend UnscaledFontFreeType; + + RefPtr<SharedFTFace> mFace; + + bool mApplySyntheticBold; + + struct InstanceData { + explicit InstanceData(ScaledFontFreeType* aScaledFont) + : mApplySyntheticBold(aScaledFont->mApplySyntheticBold) {} + + InstanceData(const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions); + + bool mApplySyntheticBold; + }; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SCALEDFONTCAIRO_H_ */ diff --git a/gfx/2d/ScaledFontMac.cpp b/gfx/2d/ScaledFontMac.cpp new file mode 100644 index 0000000000..9528b3527e --- /dev/null +++ b/gfx/2d/ScaledFontMac.cpp @@ -0,0 +1,826 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScaledFontMac.h" +#include "UnscaledFontMac.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "nsCocoaFeatures.h" +#include "PathSkia.h" +#include "skia/include/core/SkPaint.h" +#include "skia/include/core/SkPath.h" +#include "skia/include/ports/SkTypeface_mac.h" +#include <vector> +#include <dlfcn.h> +#ifdef MOZ_WIDGET_UIKIT +# include <CoreFoundation/CoreFoundation.h> +#endif +#include "mozilla/gfx/Logging.h" + +#ifdef MOZ_WIDGET_COCOA +// prototype for private API +extern "C" { +CGPathRef CGFontGetGlyphPath(CGFontRef fontRef, + CGAffineTransform* textTransform, int unknown, + CGGlyph glyph); +}; +#endif + +#include "cairo-quartz.h" + +namespace mozilla { +namespace gfx { + +// Simple helper class to automatically release a CFObject when it goes out +// of scope. +template <class T> +class AutoRelease final { + public: + explicit AutoRelease(T aObject) : mObject(aObject) {} + + ~AutoRelease() { + if (mObject) { + CFRelease(mObject); + } + } + + AutoRelease<T>& operator=(const T& aObject) { + if (aObject != mObject) { + if (mObject) { + CFRelease(mObject); + } + mObject = aObject; + } + return *this; + } + + operator T() { return mObject; } + + T forget() { + T obj = mObject; + mObject = nullptr; + return obj; + } + + private: + T mObject; +}; + +// Helper to create a CTFont from a CGFont, copying any variations that were +// set on the CGFont, and applying attributes from (optional) aFontDesc. +CTFontRef CreateCTFontFromCGFontWithVariations(CGFontRef aCGFont, CGFloat aSize, + bool aInstalledFont, + CTFontDescriptorRef aFontDesc) { + // New implementation (see bug 1856035) for macOS 13+. + if (nsCocoaFeatures::OnVenturaOrLater()) { + // Create CTFont, applying any descriptor that was passed (used by + // gfxCoreTextShaper to set features). + AutoRelease<CTFontRef> ctFont( + CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, aFontDesc)); + AutoRelease<CFDictionaryRef> vars(CGFontCopyVariations(aCGFont)); + if (vars) { + // Create an attribute dictionary containing the variations. + AutoRelease<CFDictionaryRef> attrs(CFDictionaryCreate( + nullptr, (const void**)&kCTFontVariationAttribute, + (const void**)&vars, 1, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + // Get the original descriptor from the CTFont, then add the variations + // attribute to it. + AutoRelease<CTFontDescriptorRef> desc(CTFontCopyFontDescriptor(ctFont)); + desc = CTFontDescriptorCreateCopyWithAttributes(desc, attrs); + // Return a copy of the font that has the variations added. + return CTFontCreateCopyWithAttributes(ctFont, 0.0, nullptr, desc); + } + // No variations to set, just return the default CTFont. + return ctFont.forget(); + } + + // Older implementation used up to macOS 12. + CTFontRef ctFont; + if (aInstalledFont) { + AutoRelease<CFDictionaryRef> vars(CGFontCopyVariations(aCGFont)); + if (vars) { + AutoRelease<CFDictionaryRef> varAttr(CFDictionaryCreate( + nullptr, (const void**)&kCTFontVariationAttribute, + (const void**)&vars, 1, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + AutoRelease<CTFontDescriptorRef> varDesc( + aFontDesc + ? ::CTFontDescriptorCreateCopyWithAttributes(aFontDesc, varAttr) + : ::CTFontDescriptorCreateWithAttributes(varAttr)); + + ctFont = CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, varDesc); + } else { + ctFont = CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, nullptr); + } + } else { + ctFont = CTFontCreateWithGraphicsFont(aCGFont, aSize, nullptr, nullptr); + } + return ctFont; +} + +ScaledFontMac::ScaledFontMac(CGFontRef aFont, + const RefPtr<UnscaledFont>& aUnscaledFont, + Float aSize, bool aOwnsFont, + bool aUseFontSmoothing, bool aApplySyntheticBold, + bool aHasColorGlyphs) + : ScaledFontBase(aUnscaledFont, aSize), + mFont(aFont), + mUseFontSmoothing(aUseFontSmoothing), + mApplySyntheticBold(aApplySyntheticBold), + mHasColorGlyphs(aHasColorGlyphs) { + if (!aOwnsFont) { + // XXX: should we be taking a reference + CGFontRetain(aFont); + } + + auto unscaledMac = static_cast<UnscaledFontMac*>(aUnscaledFont.get()); + bool dataFont = unscaledMac->IsDataFont(); + mCTFont = CreateCTFontFromCGFontWithVariations(aFont, aSize, !dataFont); +} + +ScaledFontMac::ScaledFontMac(CTFontRef aFont, + const RefPtr<UnscaledFont>& aUnscaledFont, + bool aUseFontSmoothing, bool aApplySyntheticBold, + bool aHasColorGlyphs) + : ScaledFontBase(aUnscaledFont, CTFontGetSize(aFont)), + mCTFont(aFont), + mUseFontSmoothing(aUseFontSmoothing), + mApplySyntheticBold(aApplySyntheticBold), + mHasColorGlyphs(aHasColorGlyphs) { + mFont = CTFontCopyGraphicsFont(aFont, nullptr); + + CFRetain(mCTFont); +} + +ScaledFontMac::~ScaledFontMac() { + CFRelease(mCTFont); + CGFontRelease(mFont); +} + +SkTypeface* ScaledFontMac::CreateSkTypeface() { + return SkMakeTypefaceFromCTFont(mCTFont).release(); +} + +void ScaledFontMac::SetupSkFontDrawOptions(SkFont& aFont) { + aFont.setSubpixel(true); + + // Normally, Skia enables LCD FontSmoothing which creates thicker fonts + // and also enables subpixel AA. CoreGraphics without font smoothing + // explicitly creates thinner fonts and grayscale AA. + // CoreGraphics doesn't support a configuration that produces thicker + // fonts with grayscale AA as LCD Font Smoothing enables or disables + // both. However, Skia supports it by enabling font smoothing (producing + // subpixel AA) and converts it to grayscale AA. Since Skia doesn't + // support subpixel AA on transparent backgrounds, we still want font + // smoothing for the thicker fonts, even if it is grayscale AA. + // + // With explicit Grayscale AA (from -moz-osx-font-smoothing:grayscale), + // we want to have grayscale AA with no smoothing at all. This means + // disabling the LCD font smoothing behaviour. + // To accomplish this we have to explicitly disable hinting, + // and disable LCDRenderText. + if (aFont.getEdging() == SkFont::Edging::kAntiAlias && !mUseFontSmoothing) { + aFont.setHinting(SkFontHinting::kNone); + } +} + +// private API here are the public options on OS X +// CTFontCreatePathForGlyph +// ATSUGlyphGetCubicPaths +// we've used this in cairo sucessfully for some time. +// Note: cairo dlsyms it. We could do that but maybe it's +// safe just to use? + +already_AddRefed<Path> ScaledFontMac::GetPathForGlyphs( + const GlyphBuffer& aBuffer, const DrawTarget* aTarget) { + return ScaledFontBase::GetPathForGlyphs(aBuffer, aTarget); +} + +static uint32_t CalcTableChecksum(const uint32_t* tableStart, uint32_t length, + bool skipChecksumAdjust = false) { + uint32_t sum = 0L; + const uint32_t* table = tableStart; + const uint32_t* end = table + length / sizeof(uint32_t); + while (table < end) { + if (skipChecksumAdjust && (table - tableStart) == 2) { + table++; + } else { + sum += CFSwapInt32BigToHost(*table++); + } + } + + // The length is not 4-byte aligned, but we still must process the remaining + // bytes. + if (length & 3) { + // Pad with zero before adding to the checksum. + uint32_t last = 0; + memcpy(&last, end, length & 3); + sum += CFSwapInt32BigToHost(last); + } + + return sum; +} + +struct TableRecord { + uint32_t tag; + uint32_t checkSum; + uint32_t offset; + uint32_t length; + CFDataRef data; +}; + +static int maxPow2LessThanEqual(int a) { + int x = 1; + int shift = 0; + while ((x << (shift + 1)) <= a) { + shift++; + } + return shift; +} + +struct writeBuf final { + explicit writeBuf(int size) { + this->data = new unsigned char[size]; + this->offset = 0; + } + ~writeBuf() { delete[] this->data; } + + template <class T> + void writeElement(T a) { + *reinterpret_cast<T*>(&this->data[this->offset]) = a; + this->offset += sizeof(T); + } + + void writeMem(const void* data, unsigned long length) { + memcpy(&this->data[this->offset], data, length); + this->offset += length; + } + + void align() { + while (this->offset & 3) { + this->data[this->offset] = 0; + this->offset++; + } + } + + unsigned char* data; + int offset; +}; + +bool UnscaledFontMac::GetFontFileData(FontFileDataOutput aDataCallback, + void* aBaton) { + // We'll reconstruct a TTF font from the tables we can get from the CGFont + CFArrayRef tags = CGFontCopyTableTags(mFont); + CFIndex count = CFArrayGetCount(tags); + + TableRecord* records = new TableRecord[count]; + uint32_t offset = 0; + offset += sizeof(uint32_t) * 3; + offset += sizeof(uint32_t) * 4 * count; + bool CFF = false; + for (CFIndex i = 0; i < count; i++) { + uint32_t tag = (uint32_t)(uintptr_t)CFArrayGetValueAtIndex(tags, i); + if (tag == 0x43464620) { // 'CFF ' + CFF = true; + } + CFDataRef data = CGFontCopyTableForTag(mFont, tag); + // Bug 1602391 suggests CGFontCopyTableForTag can fail, even though we just + // got the tag from the font via CGFontCopyTableTags above. If we can catch + // this (e.g. in fuzz-testing) it'd be good to understand when it happens, + // but in any case we'll handle it safely below by treating the table as + // zero-length. + MOZ_ASSERT(data, "failed to get font table data"); + records[i].tag = tag; + records[i].offset = offset; + records[i].data = data; + if (data) { + records[i].length = CFDataGetLength(data); + bool skipChecksumAdjust = (tag == 0x68656164); // 'head' + records[i].checkSum = CalcTableChecksum( + reinterpret_cast<const uint32_t*>(CFDataGetBytePtr(data)), + records[i].length, skipChecksumAdjust); + offset += records[i].length; + // 32 bit align the tables + offset = (offset + 3) & ~3; + } else { + records[i].length = 0; + records[i].checkSum = 0; + } + } + CFRelease(tags); + + struct writeBuf buf(offset); + // write header/offset table + if (CFF) { + buf.writeElement(CFSwapInt32HostToBig(0x4f54544f)); + } else { + buf.writeElement(CFSwapInt32HostToBig(0x00010000)); + } + buf.writeElement(CFSwapInt16HostToBig(count)); + int maxPow2Count = maxPow2LessThanEqual(count); + buf.writeElement(CFSwapInt16HostToBig((1 << maxPow2Count) * 16)); + buf.writeElement(CFSwapInt16HostToBig(maxPow2Count)); + buf.writeElement(CFSwapInt16HostToBig((count - (1 << maxPow2Count)) * 16)); + + // write table record entries + for (CFIndex i = 0; i < count; i++) { + buf.writeElement(CFSwapInt32HostToBig(records[i].tag)); + buf.writeElement(CFSwapInt32HostToBig(records[i].checkSum)); + buf.writeElement(CFSwapInt32HostToBig(records[i].offset)); + buf.writeElement(CFSwapInt32HostToBig(records[i].length)); + } + + // write tables + int checkSumAdjustmentOffset = 0; + for (CFIndex i = 0; i < count; i++) { + if (records[i].tag == 0x68656164) { + checkSumAdjustmentOffset = buf.offset + 2 * 4; + } + if (records[i].data) { + buf.writeMem(CFDataGetBytePtr(records[i].data), records[i].length); + buf.align(); + CFRelease(records[i].data); + } + } + delete[] records; + + // clear the checksumAdjust field before checksumming the whole font + memset(&buf.data[checkSumAdjustmentOffset], 0, sizeof(uint32_t)); + uint32_t fontChecksum = CFSwapInt32HostToBig( + 0xb1b0afba - + CalcTableChecksum(reinterpret_cast<const uint32_t*>(buf.data), offset)); + // set checkSumAdjust to the computed checksum + memcpy(&buf.data[checkSumAdjustmentOffset], &fontChecksum, + sizeof(fontChecksum)); + + // we always use an index of 0 + aDataCallback(buf.data, buf.offset, 0, aBaton); + + return true; +} + +bool UnscaledFontMac::GetFontDescriptor(FontDescriptorOutput aCb, + void* aBaton) { + if (mIsDataFont) { + return false; + } + + AutoRelease<CFStringRef> psname(CGFontCopyPostScriptName(mFont)); + if (!psname) { + return false; + } + + char buf[1024]; + const char* cstr = CFStringGetCStringPtr(psname, kCFStringEncodingUTF8); + if (!cstr) { + if (!CFStringGetCString(psname, buf, sizeof(buf), kCFStringEncodingUTF8)) { + return false; + } + cstr = buf; + } + + nsAutoCString descriptor(cstr); + uint32_t psNameLen = descriptor.Length(); + + AutoRelease<CTFontRef> ctFont( + CTFontCreateWithGraphicsFont(mFont, 0, nullptr, nullptr)); + AutoRelease<CFURLRef> fontUrl( + (CFURLRef)CTFontCopyAttribute(ctFont, kCTFontURLAttribute)); + if (fontUrl) { + CFStringRef urlStr(CFURLCopyFileSystemPath(fontUrl, kCFURLPOSIXPathStyle)); + cstr = CFStringGetCStringPtr(urlStr, kCFStringEncodingUTF8); + if (!cstr) { + if (!CFStringGetCString(urlStr, buf, sizeof(buf), + kCFStringEncodingUTF8)) { + return false; + } + cstr = buf; + } + descriptor.Append(cstr); + } + + aCb(reinterpret_cast<const uint8_t*>(descriptor.get()), descriptor.Length(), + psNameLen, aBaton); + return true; +} + +static void CollectVariationsFromDictionary(const void* aKey, + const void* aValue, + void* aContext) { + auto keyPtr = static_cast<const CFTypeRef>(aKey); + auto valuePtr = static_cast<const CFTypeRef>(aValue); + auto outVariations = static_cast<std::vector<FontVariation>*>(aContext); + if (CFGetTypeID(keyPtr) == CFNumberGetTypeID() && + CFGetTypeID(valuePtr) == CFNumberGetTypeID()) { + uint64_t t; + double v; + if (CFNumberGetValue(static_cast<CFNumberRef>(keyPtr), kCFNumberSInt64Type, + &t) && + CFNumberGetValue(static_cast<CFNumberRef>(valuePtr), + kCFNumberDoubleType, &v)) { + outVariations->push_back(FontVariation{uint32_t(t), float(v)}); + } + } +} + +static bool GetVariationsForCTFont(CTFontRef aCTFont, + std::vector<FontVariation>* aOutVariations) { + if (!aCTFont) { + return true; + } + AutoRelease<CFDictionaryRef> dict(CTFontCopyVariation(aCTFont)); + CFIndex count = dict ? CFDictionaryGetCount(dict) : 0; + if (count > 0) { + aOutVariations->reserve(count); + CFDictionaryApplyFunction(dict, CollectVariationsFromDictionary, + aOutVariations); + } + return true; +} + +bool ScaledFontMac::GetFontInstanceData(FontInstanceDataOutput aCb, + void* aBaton) { + // Collect any variation settings that were incorporated into the CTFont. + std::vector<FontVariation> variations; + if (!GetVariationsForCTFont(mCTFont, &variations)) { + return false; + } + + InstanceData instance(this); + aCb(reinterpret_cast<uint8_t*>(&instance), sizeof(instance), + variations.data(), variations.size(), aBaton); + return true; +} + +bool ScaledFontMac::GetWRFontInstanceOptions( + Maybe<wr::FontInstanceOptions>* aOutOptions, + Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions, + std::vector<FontVariation>* aOutVariations) { + GetVariationsForCTFont(mCTFont, aOutVariations); + + wr::FontInstanceOptions options; + options.render_mode = wr::FontRenderMode::Subpixel; + options.flags = wr::FontInstanceFlags::SUBPIXEL_POSITION; + if (mUseFontSmoothing) { + options.flags |= wr::FontInstanceFlags::FONT_SMOOTHING; + } + if (mApplySyntheticBold) { + options.flags |= wr::FontInstanceFlags::SYNTHETIC_BOLD; + } + if (mHasColorGlyphs) { + options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS; + } + options.synthetic_italics = + wr::DegreesToSyntheticItalics(GetSyntheticObliqueAngle()); + *aOutOptions = Some(options); + return true; +} + +ScaledFontMac::InstanceData::InstanceData( + const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions) + : mUseFontSmoothing(true), + mApplySyntheticBold(false), + mHasColorGlyphs(false) { + if (aOptions) { + if (!(aOptions->flags & wr::FontInstanceFlags::FONT_SMOOTHING)) { + mUseFontSmoothing = false; + } + if (aOptions->flags & wr::FontInstanceFlags::SYNTHETIC_BOLD) { + mApplySyntheticBold = true; + } + if (aOptions->flags & wr::FontInstanceFlags::EMBEDDED_BITMAPS) { + mHasColorGlyphs = true; + } + } +} + +static CFDictionaryRef CreateVariationDictionaryOrNull( + CGFontRef aCGFont, CFArrayRef& aCGAxesCache, CFArrayRef& aCTAxesCache, + uint32_t aVariationCount, const FontVariation* aVariations) { + if (!aCGAxesCache) { + aCGAxesCache = CGFontCopyVariationAxes(aCGFont); + if (!aCGAxesCache) { + return nullptr; + } + } + if (!aCTAxesCache) { + AutoRelease<CTFontRef> ctFont( + CTFontCreateWithGraphicsFont(aCGFont, 0, nullptr, nullptr)); + aCTAxesCache = CTFontCopyVariationAxes(ctFont); + if (!aCTAxesCache) { + return nullptr; + } + } + + CFIndex axisCount = CFArrayGetCount(aCTAxesCache); + if (CFArrayGetCount(aCGAxesCache) != axisCount) { + return nullptr; + } + + AutoRelease<CFMutableDictionaryRef> dict(CFDictionaryCreateMutable( + kCFAllocatorDefault, axisCount, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + // Number of variation settings passed in the aVariations parameter. + // This will typically be a very low value, so we just linear-search them. + bool allDefaultValues = true; + + for (CFIndex i = 0; i < axisCount; ++i) { + // We sanity-check the axis info found in the CTFont, and bail out + // (returning null) if it doesn't have the expected types. + CFTypeRef axisInfo = CFArrayGetValueAtIndex(aCTAxesCache, i); + if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) { + return nullptr; + } + CFDictionaryRef axis = static_cast<CFDictionaryRef>(axisInfo); + + CFTypeRef axisTag = + CFDictionaryGetValue(axis, kCTFontVariationAxisIdentifierKey); + if (!axisTag || CFGetTypeID(axisTag) != CFNumberGetTypeID()) { + return nullptr; + } + int64_t tagLong; + if (!CFNumberGetValue(static_cast<CFNumberRef>(axisTag), + kCFNumberSInt64Type, &tagLong)) { + return nullptr; + } + + axisInfo = CFArrayGetValueAtIndex(aCGAxesCache, i); + if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) { + return nullptr; + } + CFTypeRef axisName = CFDictionaryGetValue( + static_cast<CFDictionaryRef>(axisInfo), kCGFontVariationAxisName); + if (!axisName || CFGetTypeID(axisName) != CFStringGetTypeID()) { + return nullptr; + } + + // Clamp axis values to the supported range. + CFTypeRef min = + CFDictionaryGetValue(axis, kCTFontVariationAxisMinimumValueKey); + CFTypeRef max = + CFDictionaryGetValue(axis, kCTFontVariationAxisMaximumValueKey); + CFTypeRef def = + CFDictionaryGetValue(axis, kCTFontVariationAxisDefaultValueKey); + if (!min || CFGetTypeID(min) != CFNumberGetTypeID() || !max || + CFGetTypeID(max) != CFNumberGetTypeID() || !def || + CFGetTypeID(def) != CFNumberGetTypeID()) { + return nullptr; + } + double minDouble; + double maxDouble; + double defDouble; + if (!CFNumberGetValue(static_cast<CFNumberRef>(min), kCFNumberDoubleType, + &minDouble) || + !CFNumberGetValue(static_cast<CFNumberRef>(max), kCFNumberDoubleType, + &maxDouble) || + !CFNumberGetValue(static_cast<CFNumberRef>(def), kCFNumberDoubleType, + &defDouble)) { + return nullptr; + } + + double value = defDouble; + for (uint32_t j = 0; j < aVariationCount; ++j) { + if (aVariations[j].mTag == tagLong) { + value = std::min(std::max<double>(aVariations[j].mValue, minDouble), + maxDouble); + if (value != defDouble) { + allDefaultValues = false; + } + break; + } + } + AutoRelease<CFNumberRef> valueNumber( + CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value)); + CFDictionaryAddValue(dict, axisName, valueNumber); + } + + if (allDefaultValues) { + // We didn't actually set any non-default values, so throw away the + // variations dictionary and just use the default rendering. + return nullptr; + } + + return dict.forget(); +} + +static CFDictionaryRef CreateVariationTagDictionaryOrNull( + CTFontRef aCTFont, uint32_t aVariationCount, + const FontVariation* aVariations) { + AutoRelease<CFArrayRef> axes(CTFontCopyVariationAxes(aCTFont)); + CFIndex axisCount = CFArrayGetCount(axes); + + AutoRelease<CFMutableDictionaryRef> dict(CFDictionaryCreateMutable( + kCFAllocatorDefault, axisCount, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + // Number of variation settings passed in the aVariations parameter. + // This will typically be a very low value, so we just linear-search them. + bool allDefaultValues = true; + + for (CFIndex i = 0; i < axisCount; ++i) { + // We sanity-check the axis info found in the CTFont, and bail out + // (returning null) if it doesn't have the expected types. + CFTypeRef axisInfo = CFArrayGetValueAtIndex(axes, i); + if (CFDictionaryGetTypeID() != CFGetTypeID(axisInfo)) { + return nullptr; + } + CFDictionaryRef axis = static_cast<CFDictionaryRef>(axisInfo); + + CFTypeRef axisTag = + CFDictionaryGetValue(axis, kCTFontVariationAxisIdentifierKey); + if (!axisTag || CFGetTypeID(axisTag) != CFNumberGetTypeID()) { + return nullptr; + } + int64_t tagLong; + if (!CFNumberGetValue(static_cast<CFNumberRef>(axisTag), + kCFNumberSInt64Type, &tagLong)) { + return nullptr; + } + + // Clamp axis values to the supported range. + CFTypeRef min = + CFDictionaryGetValue(axis, kCTFontVariationAxisMinimumValueKey); + CFTypeRef max = + CFDictionaryGetValue(axis, kCTFontVariationAxisMaximumValueKey); + CFTypeRef def = + CFDictionaryGetValue(axis, kCTFontVariationAxisDefaultValueKey); + if (!min || CFGetTypeID(min) != CFNumberGetTypeID() || !max || + CFGetTypeID(max) != CFNumberGetTypeID() || !def || + CFGetTypeID(def) != CFNumberGetTypeID()) { + return nullptr; + } + double minDouble; + double maxDouble; + double defDouble; + if (!CFNumberGetValue(static_cast<CFNumberRef>(min), kCFNumberDoubleType, + &minDouble) || + !CFNumberGetValue(static_cast<CFNumberRef>(max), kCFNumberDoubleType, + &maxDouble) || + !CFNumberGetValue(static_cast<CFNumberRef>(def), kCFNumberDoubleType, + &defDouble)) { + return nullptr; + } + + double value = defDouble; + for (uint32_t j = 0; j < aVariationCount; ++j) { + if (aVariations[j].mTag == tagLong) { + value = std::min(std::max<double>(aVariations[j].mValue, minDouble), + maxDouble); + if (value != defDouble) { + allDefaultValues = false; + } + break; + } + } + AutoRelease<CFNumberRef> valueNumber( + CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value)); + CFDictionaryAddValue(dict, axisTag, valueNumber); + } + + if (allDefaultValues) { + // We didn't actually set any non-default values, so throw away the + // variations dictionary and just use the default rendering. + return nullptr; + } + + return dict.forget(); +} + +/* static */ +CGFontRef UnscaledFontMac::CreateCGFontWithVariations( + CGFontRef aFont, CFArrayRef& aCGAxesCache, CFArrayRef& aCTAxesCache, + uint32_t aVariationCount, const FontVariation* aVariations) { + if (!aVariationCount) { + return nullptr; + } + MOZ_ASSERT(aVariations); + + AutoRelease<CFDictionaryRef> varDict(CreateVariationDictionaryOrNull( + aFont, aCGAxesCache, aCTAxesCache, aVariationCount, aVariations)); + if (!varDict) { + return nullptr; + } + + return CGFontCreateCopyWithVariations(aFont, varDict); +} + +already_AddRefed<ScaledFont> UnscaledFontMac::CreateScaledFont( + Float aGlyphSize, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength, const FontVariation* aVariations, + uint32_t aNumVariations) + +{ + if (aInstanceDataLength < sizeof(ScaledFontMac::InstanceData)) { + gfxWarning() << "Mac scaled font instance data is truncated."; + return nullptr; + } + const ScaledFontMac::InstanceData& instanceData = + *reinterpret_cast<const ScaledFontMac::InstanceData*>(aInstanceData); + RefPtr<ScaledFontMac> scaledFont; + if (mFontDesc) { + AutoRelease<CTFontRef> font( + CTFontCreateWithFontDescriptor(mFontDesc, aGlyphSize, nullptr)); + if (aNumVariations > 0) { + AutoRelease<CFDictionaryRef> varDict(CreateVariationTagDictionaryOrNull( + font, aNumVariations, aVariations)); + if (varDict) { + CFDictionaryRef varAttr = CFDictionaryCreate( + nullptr, (const void**)&kCTFontVariationAttribute, + (const void**)&varDict, 1, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + AutoRelease<CTFontDescriptorRef> fontDesc( + CTFontDescriptorCreateCopyWithAttributes(mFontDesc, varAttr)); + if (!fontDesc) { + return nullptr; + } + font = CTFontCreateWithFontDescriptor(fontDesc, aGlyphSize, nullptr); + } + } + scaledFont = new ScaledFontMac(font, this, instanceData.mUseFontSmoothing, + instanceData.mApplySyntheticBold, + instanceData.mHasColorGlyphs); + } else { + CGFontRef fontRef = mFont; + if (aNumVariations > 0) { + CGFontRef varFont = CreateCGFontWithVariations( + mFont, mCGAxesCache, mCTAxesCache, aNumVariations, aVariations); + if (varFont) { + fontRef = varFont; + } + } + + scaledFont = new ScaledFontMac(fontRef, this, aGlyphSize, fontRef != mFont, + instanceData.mUseFontSmoothing, + instanceData.mApplySyntheticBold, + instanceData.mHasColorGlyphs); + } + return scaledFont.forget(); +} + +already_AddRefed<ScaledFont> UnscaledFontMac::CreateScaledFontFromWRFont( + Float aGlyphSize, const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions, + const FontVariation* aVariations, uint32_t aNumVariations) { + ScaledFontMac::InstanceData instanceData(aOptions, aPlatformOptions); + return CreateScaledFont(aGlyphSize, reinterpret_cast<uint8_t*>(&instanceData), + sizeof(instanceData), aVariations, aNumVariations); +} + +cairo_font_face_t* ScaledFontMac::CreateCairoFontFace( + cairo_font_options_t* aFontOptions) { + MOZ_ASSERT(mFont); + return cairo_quartz_font_face_create_for_cgfont(mFont); +} + +already_AddRefed<UnscaledFont> UnscaledFontMac::CreateFromFontDescriptor( + const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex) { + if (aDataLength == 0) { + gfxWarning() << "Mac font descriptor is truncated."; + return nullptr; + } + AutoRelease<CFStringRef> name( + CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)aData, aIndex, + kCFStringEncodingUTF8, false)); + if (!name) { + return nullptr; + } + CGFontRef font = CGFontCreateWithFontName(name); + if (!font) { + return nullptr; + } + + // If the descriptor included a font file path, apply that attribute and + // refresh the font in case it changed. + if (aIndex < aDataLength) { + AutoRelease<CFStringRef> path(CFStringCreateWithBytes( + kCFAllocatorDefault, (const UInt8*)aData + aIndex, aDataLength - aIndex, + kCFStringEncodingUTF8, false)); + AutoRelease<CFURLRef> url(CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, path, kCFURLPOSIXPathStyle, false)); + AutoRelease<CFDictionaryRef> attrs(CFDictionaryCreate( + nullptr, (const void**)&kCTFontURLAttribute, (const void**)&url, 1, + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); + AutoRelease<CTFontRef> ctFont( + CTFontCreateWithGraphicsFont(font, 0.0, nullptr, nullptr)); + AutoRelease<CTFontDescriptorRef> desc(CTFontCopyFontDescriptor(ctFont)); + AutoRelease<CTFontDescriptorRef> newDesc( + CTFontDescriptorCreateCopyWithAttributes(desc, attrs)); + AutoRelease<CTFontRef> newFont( + CTFontCreateWithFontDescriptor(newDesc, 0.0, nullptr)); + CFRelease(font); + font = CTFontCopyGraphicsFont(newFont, nullptr); + } + + RefPtr<UnscaledFont> unscaledFont = new UnscaledFontMac(font); + CFRelease(font); + return unscaledFont.forget(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/ScaledFontMac.h b/gfx/2d/ScaledFontMac.h new file mode 100644 index 0000000000..6946b539a2 --- /dev/null +++ b/gfx/2d/ScaledFontMac.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SCALEDFONTMAC_H_ +#define MOZILLA_GFX_SCALEDFONTMAC_H_ + +#ifdef MOZ_WIDGET_COCOA +# include <ApplicationServices/ApplicationServices.h> +#else +# include <CoreGraphics/CoreGraphics.h> +# include <CoreText/CoreText.h> +#endif + +#include "2D.h" + +#include "ScaledFontBase.h" + +namespace mozilla { +namespace gfx { + +// Utility to create a CTFont from a CGFont, copying any variations that were +// set on the original CGFont, and applying additional attributes from aDesc +// (which may be NULL). +// Exposed here because it is also used by gfxMacFont and gfxCoreTextShaper. +CTFontRef CreateCTFontFromCGFontWithVariations( + CGFontRef aCGFont, CGFloat aSize, bool aInstalledFont, + CTFontDescriptorRef aFontDesc = nullptr); + +class UnscaledFontMac; + +class ScaledFontMac : public ScaledFontBase { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontMac, override) + ScaledFontMac(CGFontRef aFont, const RefPtr<UnscaledFont>& aUnscaledFont, + Float aSize, bool aOwnsFont = false, + bool aUseFontSmoothing = true, bool aApplySyntheticBold = false, + bool aHasColorGlyphs = false); + ScaledFontMac(CTFontRef aFont, const RefPtr<UnscaledFont>& aUnscaledFont, + bool aUseFontSmoothing = true, bool aApplySyntheticBold = false, + bool aHasColorGlyphs = false); + ~ScaledFontMac(); + + FontType GetType() const override { return FontType::MAC; } + SkTypeface* CreateSkTypeface() override; + void SetupSkFontDrawOptions(SkFont& aFont) override; + already_AddRefed<Path> GetPathForGlyphs(const GlyphBuffer& aBuffer, + const DrawTarget* aTarget) override; + + bool GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) override; + + bool GetWRFontInstanceOptions( + Maybe<wr::FontInstanceOptions>* aOutOptions, + Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions, + std::vector<FontVariation>* aOutVariations) override; + + bool CanSerialize() override { return true; } + + bool MayUseBitmaps() override { return mHasColorGlyphs; } + + bool UseSubpixelPosition() const override { return true; } + + cairo_font_face_t* CreateCairoFontFace( + cairo_font_options_t* aFontOptions) override; + + private: + friend class DrawTargetSkia; + friend class UnscaledFontMac; + + CGFontRef mFont; + CTFontRef + mCTFont; // only created if CTFontDrawGlyphs is available, otherwise null + + bool mUseFontSmoothing; + bool mApplySyntheticBold; + bool mHasColorGlyphs; + + struct InstanceData { + explicit InstanceData(ScaledFontMac* aScaledFont) + : mUseFontSmoothing(aScaledFont->mUseFontSmoothing), + mApplySyntheticBold(aScaledFont->mApplySyntheticBold), + mHasColorGlyphs(aScaledFont->mHasColorGlyphs) {} + + InstanceData(const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions); + + bool mUseFontSmoothing; + bool mApplySyntheticBold; + bool mHasColorGlyphs; + }; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SCALEDFONTMAC_H_ */ diff --git a/gfx/2d/ScaledFontWin.cpp b/gfx/2d/ScaledFontWin.cpp new file mode 100644 index 0000000000..5a90e5ad01 --- /dev/null +++ b/gfx/2d/ScaledFontWin.cpp @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScaledFontWin.h" +#include "UnscaledFontGDI.h" + +#include "AutoHelpersWin.h" +#include "Logging.h" +#include "nsString.h" + +#include "skia/include/ports/SkTypeface_win.h" + +#include "cairo-win32.h" + +#include "HelpersWinFonts.h" + +namespace mozilla { +namespace gfx { + +ScaledFontWin::ScaledFontWin(const LOGFONT* aFont, + const RefPtr<UnscaledFont>& aUnscaledFont, + Float aSize) + : ScaledFontBase(aUnscaledFont, aSize), mLogFont(*aFont) {} + +bool UnscaledFontGDI::GetFontFileData(FontFileDataOutput aDataCallback, + void* aBaton) { + AutoDC dc; + AutoSelectFont font(dc.GetDC(), &mLogFont); + + // Check for a font collection first. + uint32_t table = 0x66637474; // 'ttcf' + uint32_t tableSize = ::GetFontData(dc.GetDC(), table, 0, nullptr, 0); + if (tableSize == GDI_ERROR) { + // Try as if just a single font. + table = 0; + tableSize = ::GetFontData(dc.GetDC(), table, 0, nullptr, 0); + if (tableSize == GDI_ERROR) { + return false; + } + } + + UniquePtr<uint8_t[]> fontData(new uint8_t[tableSize]); + + uint32_t sizeGot = + ::GetFontData(dc.GetDC(), table, 0, fontData.get(), tableSize); + if (sizeGot != tableSize) { + return false; + } + + aDataCallback(fontData.get(), tableSize, 0, aBaton); + return true; +} + +bool ScaledFontWin::GetFontInstanceData(FontInstanceDataOutput aCb, + void* aBaton) { + aCb(reinterpret_cast<uint8_t*>(&mLogFont), sizeof(mLogFont), nullptr, 0, + aBaton); + return true; +} + +bool UnscaledFontGDI::GetFontInstanceData(FontInstanceDataOutput aCb, + void* aBaton) { + aCb(reinterpret_cast<uint8_t*>(&mLogFont), sizeof(mLogFont), aBaton); + return true; +} + +bool UnscaledFontGDI::GetFontDescriptor(FontDescriptorOutput aCb, + void* aBaton) { + // Because all the callers of this function are preparing a recorded + // event to be played back in another process, it's not helpful to ever + // return a font descriptor, since it isn't meaningful in another + // process. Those callers will always need to send full font data, and + // returning false here will ensure that that happens. + return false; +} + +already_AddRefed<UnscaledFont> UnscaledFontGDI::CreateFromFontDescriptor( + const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex) { + if (aDataLength < sizeof(LOGFONT)) { + gfxWarning() << "GDI font descriptor is truncated."; + return nullptr; + } + + const LOGFONT* logFont = reinterpret_cast<const LOGFONT*>(aData); + RefPtr<UnscaledFont> unscaledFont = new UnscaledFontGDI(*logFont); + return unscaledFont.forget(); +} + +already_AddRefed<ScaledFont> UnscaledFontGDI::CreateScaledFont( + Float aGlyphSize, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength, const FontVariation* aVariations, + uint32_t aNumVariations) { + if (aInstanceDataLength < sizeof(LOGFONT)) { + gfxWarning() << "GDI unscaled font instance data is truncated."; + return nullptr; + } + return MakeAndAddRef<ScaledFontWin>( + reinterpret_cast<const LOGFONT*>(aInstanceData), this, aGlyphSize); +} + +AntialiasMode ScaledFontWin::GetDefaultAAMode() { + return GetSystemDefaultAAMode(); +} + +SkTypeface* ScaledFontWin::CreateSkTypeface() { + return SkCreateTypefaceFromLOGFONT(mLogFont); +} + +cairo_font_face_t* ScaledFontWin::CreateCairoFontFace( + cairo_font_options_t* aFontOptions) { + if (mLogFont.lfFaceName[0] == 0) { + return nullptr; + } + return cairo_win32_font_face_create_for_logfontw(&mLogFont); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/ScaledFontWin.h b/gfx/2d/ScaledFontWin.h new file mode 100644 index 0000000000..45ca23a3bb --- /dev/null +++ b/gfx/2d/ScaledFontWin.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SCALEDFONTWIN_H_ +#define MOZILLA_GFX_SCALEDFONTWIN_H_ + +#include "ScaledFontBase.h" +#include <windows.h> + +namespace mozilla { +namespace gfx { + +class ScaledFontWin : public ScaledFontBase { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontWin, override) + ScaledFontWin(const LOGFONT* aFont, const RefPtr<UnscaledFont>& aUnscaledFont, + Float aSize); + + FontType GetType() const override { return FontType::GDI; } + + bool GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) override; + + AntialiasMode GetDefaultAAMode() override; + + SkTypeface* CreateSkTypeface() override; + + bool MayUseBitmaps() override { return true; } + + bool UseSubpixelPosition() const override { return false; } + + protected: + cairo_font_face_t* CreateCairoFontFace( + cairo_font_options_t* aFontOptions) override; + + private: + friend class DrawTargetSkia; + LOGFONT mLogFont; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SCALEDFONTWIN_H_ */ diff --git a/gfx/2d/ShadersD2D.fx b/gfx/2d/ShadersD2D.fx new file mode 100644 index 0000000000..b87ee18a91 --- /dev/null +++ b/gfx/2d/ShadersD2D.fx @@ -0,0 +1,824 @@ +// We store vertex coordinates and the quad shape in a constant buffer, this is +// easy to update and allows us to use a single call to set the x, y, w, h of +// the quad. +// The QuadDesc and TexCoords both work as follows: +// The x component is the quad left point, the y component is the top point +// the z component is the width, and the w component is the height. The quad +// are specified in viewport coordinates, i.e. { -1.0f, 1.0f, 2.0f, -2.0f } +// would cover the entire viewport (which runs from <-1.0f, 1.0f> left to right +// and <-1.0f, 1.0f> -bottom- to top. The TexCoords desc is specified in texture +// space <0, 1.0f> left to right and top to bottom. The input vertices of the +// shader stage always form a rectangle from {0, 0} - {1, 1} +cbuffer cb0 +{ + float4 QuadDesc; + float4 TexCoords; + float4 MaskTexCoords; + float4 TextColor; +} + +cbuffer cb1 +{ + float4 BlurOffsetsH[3]; + float4 BlurOffsetsV[3]; + float4 BlurWeights[3]; + float4 ShadowColor; +} + +cbuffer cb2 +{ + float3x3 DeviceSpaceToUserSpace; + float2 dimensions; + // Precalculate as much as we can! + float3 diff; + float2 center1; + float A; + float radius1; + float sq_radius1; +} + +cbuffer cb3 +{ + float3x3 DeviceSpaceToUserSpace_cb3; + float2 dimensions_cb3; + float2 center; + float angle; + float start_offset; + float end_offset; +} + +struct VS_OUTPUT +{ + float4 Position : SV_Position; + float2 TexCoord : TEXCOORD0; + float2 MaskTexCoord : TEXCOORD1; +}; + +struct VS_RADIAL_OUTPUT +{ + float4 Position : SV_Position; + float2 MaskTexCoord : TEXCOORD0; + float2 PixelCoord : TEXCOORD1; +}; + +struct VS_CONIC_OUTPUT +{ + float4 Position : SV_Position; + float2 MaskTexCoord : TEXCOORD0; + float2 PixelCoord : TEXCOORD1; +}; + +struct PS_TEXT_OUTPUT +{ + float4 color; + float4 alpha; +}; + +Texture2D tex; +Texture2D bcktex; +Texture2D mask; +uint blendop; + +sampler sSampler = sampler_state { + Filter = MIN_MAG_MIP_LINEAR; + Texture = tex; + AddressU = Clamp; + AddressV = Clamp; +}; + +sampler sBckSampler = sampler_state { + Filter = MIN_MAG_MIP_LINEAR; + Texture = bcktex; + AddressU = Clamp; + AddressV = Clamp; +}; + +sampler sWrapSampler = sampler_state { + Filter = MIN_MAG_MIP_LINEAR; + Texture = tex; + AddressU = Wrap; + AddressV = Wrap; +}; + +sampler sMirrorSampler = sampler_state { + Filter = MIN_MAG_MIP_LINEAR; + Texture = tex; + AddressU = Mirror; + AddressV = Mirror; +}; + +sampler sMaskSampler = sampler_state { + Filter = MIN_MAG_MIP_LINEAR; + Texture = mask; + AddressU = Clamp; + AddressV = Clamp; +}; + +sampler sShadowSampler = sampler_state { + Filter = MIN_MAG_MIP_LINEAR; + Texture = tex; + AddressU = Border; + AddressV = Border; + BorderColor = float4(0, 0, 0, 0); +}; + +RasterizerState TextureRast +{ + ScissorEnable = True; + CullMode = None; +}; + +BlendState ShadowBlendH +{ + BlendEnable[0] = False; + RenderTargetWriteMask[0] = 0xF; +}; + +BlendState ShadowBlendV +{ + BlendEnable[0] = True; + SrcBlend = One; + DestBlend = Inv_Src_Alpha; + BlendOp = Add; + SrcBlendAlpha = One; + DestBlendAlpha = Inv_Src_Alpha; + BlendOpAlpha = Add; + RenderTargetWriteMask[0] = 0xF; +}; + +BlendState bTextBlend +{ + AlphaToCoverageEnable = FALSE; + BlendEnable[0] = TRUE; + SrcBlend = Src1_Color; + DestBlend = Inv_Src1_Color; + BlendOp = Add; + SrcBlendAlpha = Src1_Alpha; + DestBlendAlpha = Inv_Src1_Alpha; + BlendOpAlpha = Add; + RenderTargetWriteMask[0] = 0x0F; // All +}; + +VS_OUTPUT SampleTextureVS(float3 pos : POSITION) +{ + VS_OUTPUT Output; + Output.Position.w = 1.0f; + Output.Position.x = pos.x * QuadDesc.z + QuadDesc.x; + Output.Position.y = pos.y * QuadDesc.w + QuadDesc.y; + Output.Position.z = 0; + Output.TexCoord.x = pos.x * TexCoords.z + TexCoords.x; + Output.TexCoord.y = pos.y * TexCoords.w + TexCoords.y; + Output.MaskTexCoord.x = pos.x * MaskTexCoords.z + MaskTexCoords.x; + Output.MaskTexCoord.y = pos.y * MaskTexCoords.w + MaskTexCoords.y; + return Output; +} + +VS_RADIAL_OUTPUT SampleRadialVS(float3 pos : POSITION) +{ + VS_RADIAL_OUTPUT Output; + Output.Position.w = 1.0f; + Output.Position.x = pos.x * QuadDesc.z + QuadDesc.x; + Output.Position.y = pos.y * QuadDesc.w + QuadDesc.y; + Output.Position.z = 0; + Output.MaskTexCoord.x = pos.x * MaskTexCoords.z + MaskTexCoords.x; + Output.MaskTexCoord.y = pos.y * MaskTexCoords.w + MaskTexCoords.y; + + // For the radial gradient pixel shader we need to pass in the pixel's + // coordinates in user space for the color to be correctly determined. + + Output.PixelCoord.x = ((Output.Position.x + 1.0f) / 2.0f) * dimensions.x; + Output.PixelCoord.y = ((1.0f - Output.Position.y) / 2.0f) * dimensions.y; + Output.PixelCoord.xy = mul(float3(Output.PixelCoord.x, Output.PixelCoord.y, 1.0f), DeviceSpaceToUserSpace).xy; + return Output; +} + +VS_CONIC_OUTPUT SampleConicVS(float3 pos : POSITION) +{ + VS_CONIC_OUTPUT Output; + Output.Position.w = 1.0f; + Output.Position.x = pos.x * QuadDesc.z + QuadDesc.x; + Output.Position.y = pos.y * QuadDesc.w + QuadDesc.y; + Output.Position.z = 0; + Output.MaskTexCoord.x = pos.x * MaskTexCoords.z + MaskTexCoords.x; + Output.MaskTexCoord.y = pos.y * MaskTexCoords.w + MaskTexCoords.y; + + // For the conic gradient pixel shader we need to pass in the pixel's + // coordinates in user space for the color to be correctly determined. + + Output.PixelCoord.x = ((Output.Position.x + 1.0f) / 2.0f) * dimensions_cb3.x; + Output.PixelCoord.y = ((1.0f - Output.Position.y) / 2.0f) * dimensions_cb3.y; + Output.PixelCoord.xy = mul(float3(Output.PixelCoord.x, Output.PixelCoord.y, 1.0f), DeviceSpaceToUserSpace_cb3).xy; + return Output; +} + +float Screen(float a, float b) +{ + return 1 - ((1 - a)*(1 - b)); +} + +static float RedLuminance = 0.3f; +static float GreenLuminance = 0.59f; +static float BlueLuminance = 0.11f; + +float Lum(float3 C) +{ + return RedLuminance * C.r + GreenLuminance * C.g + BlueLuminance * C.b; +} + +float3 ClipColor(float3 C) +{ + float L = Lum(C); + float n = min(min(C.r, C.g), C.b); + float x = max(max(C.r, C.g), C.b); + + if(n < 0) + C = L + (((C - L) * L) / (L - n)); + + if(x > 1) + C = L + ((C - L) * (1 - L) / (x - L)); + + return C; +} + +float3 SetLum(float3 C, float l) +{ + float d = l - Lum(C); + C = C + d; + return ClipColor(C); +} + +float Sat(float3 C) +{ + return max(C.r, max(C.g, C.b)) - min(C.r, min(C.g, C.b)); +} + +void SetSatComponents(inout float minComp, inout float midComp, inout float maxComp, in float satVal) +{ + midComp -= minComp; + maxComp -= minComp; + minComp = 0.0; + if (maxComp > 0.0) + { + midComp *= satVal/maxComp; + maxComp = satVal; + } +} + +float3 SetSat(float3 color, in float satVal) +{ + if (color.x <= color.y) { + if (color.y <= color.z) { + // x <= y <= z + SetSatComponents(color.x, color.y, color.z, satVal); + } + else { + if (color.x <= color.z) { + // x <= z <= y + SetSatComponents(color.x, color.z, color.y, satVal); + } + else { + // z <= x <= y + SetSatComponents(color.z, color.x, color.y, satVal); + } + } + } + else { + if (color.x <= color.z) { + // y <= x <= z + SetSatComponents(color.y, color.x, color.z, satVal); + } + else { + if (color.y <= color.z) { + // y <= z <= x + SetSatComponents(color.y, color.z, color.x, satVal); + } + else { + // z <= y <= x + SetSatComponents(color.z, color.y, color.x, satVal); + } + } + } + + return color; +} + +float4 SampleBlendTextureSeparablePS_1( VS_OUTPUT In) : SV_Target +{ + float4 output = tex.Sample(sSampler, In.TexCoord); + float4 background = bcktex.Sample(sBckSampler, In.TexCoord); + if((output.a == 0) || (background.a == 0)) + return output; + + output.rgb /= output.a; + background.rgb /= background.a; + + float4 retval = output; + + if(blendop == 1) { // multiply + retval.rgb = output.rgb * background.rgb; + } else if(blendop == 2) { + retval.rgb = output.rgb + background.rgb - output.rgb * background.rgb; + } else if(blendop == 3) { + if(background.r <= 0.5) + retval.r = 2*background.r * output.r; + else + retval.r = Screen(output.r, 2 * background.r - 1); + if(background.g <= 0.5) + retval.g = 2 * background.g * output.g; + else + retval.g = Screen(output.g, 2 * background.g - 1); + if(background.b <= 0.5) + retval.b = 2 * background.b * output.b; + else + retval.b = Screen(output.b, 2 * background.b - 1); + } else if(blendop == 4) { + retval.rgb = min(output.rgb, background.rgb); + } else if(blendop == 5) { + retval.rgb = max(output.rgb, background.rgb); + } else { + if(background.r == 0) + retval.r = 0; + else + if(output.r == 1) + retval.r = 1; + else + retval.r = min(1, background.r / (1 - output.r)); + if(background.g == 0) + retval.g = 0; + else + if(output.g == 1) + retval.g = 1; + else + retval.g = min(1, background.g / (1 - output.g)); + if(background.b == 0) + retval.b = 0; + else + if(output.b == 1) + retval.b = 1; + else + retval.b = min(1, background.b / (1 - output.b)); + } + + output.rgb = ((1 - background.a) * output.rgb + background.a * retval.rgb) * output.a; + return output; +} + +float4 SampleBlendTextureSeparablePS_2( VS_OUTPUT In) : SV_Target +{ + float4 output = tex.Sample(sSampler, In.TexCoord); + float4 background = bcktex.Sample(sBckSampler, In.TexCoord); + if((output.a == 0) || (background.a == 0)) + return output; + + output.rgb /= output.a; + background.rgb /= background.a; + + float4 retval = output; + + if(blendop == 7) { + if(background.r == 1) + retval.r = 1; + else + if(output.r == 0) + retval.r = 0; + else + retval.r = 1 - min(1, (1 - background.r) / output.r); + if(background.g == 1) + retval.g = 1; + else + if(output.g == 0) + retval.g = 0; + else + retval.g = 1 - min(1, (1 - background.g) / output.g); + if(background.b == 1) + retval.b = 1; + else + if(output.b == 0) + retval.b = 0; + else + retval.b = 1 - min(1, (1 - background.b) / output.b); + } else if(blendop == 8) { + if(output.r <= 0.5) + retval.r = 2 * output.r * background.r; + else + retval.r = Screen(background.r, 2 * output.r -1); + if(output.g <= 0.5) + retval.g = 2 * output.g * background.g; + else + retval.g = Screen(background.g, 2 * output.g -1); + if(output.b <= 0.5) + retval.b = 2 * output.b * background.b; + else + retval.b = Screen(background.b, 2 * output.b -1); + } else if(blendop == 9){ + float D; + if(background.r <= 0.25) + D = ((16 * background.r - 12) * background.r + 4) * background.r; + else + D = sqrt(background.r); + if(output.r <= 0.5) + retval.r = background.r - (1 - 2 * output.r) * background.r * (1 - background.r); + else + retval.r = background.r + (2 * output.r - 1) * (D - background.r); + + if(background.g <= 0.25) + D = ((16 * background.g - 12) * background.g + 4) * background.g; + else + D = sqrt(background.g); + if(output.g <= 0.5) + retval.g = background.g - (1 - 2 * output.g) * background.g * (1 - background.g); + else + retval.g = background.g + (2 * output.g - 1) * (D - background.g); + + if(background.b <= 0.25) + D = ((16 * background.b - 12) * background.b + 4) * background.b; + else + D = sqrt(background.b); + + if(output.b <= 0.5) + retval.b = background.b - (1 - 2 * output.b) * background.b * (1 - background.b); + else + retval.b = background.b + (2 * output.b - 1) * (D - background.b); + } else if(blendop == 10) { + retval.rgb = abs(output.rgb - background.rgb); + } else { + retval.rgb = output.rgb + background.rgb - 2 * output.rgb * background.rgb; + } + + output.rgb = ((1 - background.a) * output.rgb + background.a * retval.rgb) * output.a; + return output; +} + +float4 SampleBlendTextureNonSeparablePS( VS_OUTPUT In) : SV_Target +{ + float4 output = tex.Sample(sSampler, In.TexCoord); + float4 background = bcktex.Sample(sBckSampler, In.TexCoord); + if((output.a == 0) || (background.a == 0)) + return output; + + output.rgb /= output.a; + background.rgb /= background.a; + + float4 retval = output; + + if(blendop == 12) { + retval.rgb = SetLum(SetSat(output.rgb, Sat(background.rgb)), Lum(background.rgb)); + } else if(blendop == 13) { + retval.rgb = SetLum(SetSat(background.rgb, Sat(output.rgb)), Lum(background.rgb)); + } else if(blendop == 14) { + retval.rgb = SetLum(output.rgb, Lum(background.rgb)); + } else { + retval.rgb = SetLum(background.rgb, Lum(output.rgb)); + } + + output.rgb = ((1 - background.a) * output.rgb + background.a * retval.rgb) * output.a; + return output; +} + + +float4 SampleTexturePS( VS_OUTPUT In) : SV_Target +{ + return tex.Sample(sSampler, In.TexCoord); +} + +float4 SampleMaskTexturePS( VS_OUTPUT In) : SV_Target +{ + return tex.Sample(sSampler, In.TexCoord) * mask.Sample(sMaskSampler, In.MaskTexCoord).a; +} + +float4 SampleRadialGradientPS(VS_RADIAL_OUTPUT In, uniform sampler aSampler) : SV_Target +{ + // Radial gradient painting is defined as the set of circles whose centers + // are described by C(t) = (C2 - C1) * t + C1; with radii + // R(t) = (R2 - R1) * t + R1; for R(t) > 0. This shader solves the + // quadratic equation that arises when calculating t for pixel (x, y). + // + // A more extensive derrivation can be found in the pixman radial gradient + // code. + + float2 p = In.PixelCoord; + float3 dp = float3(p - center1, radius1); + + // dpx * dcx + dpy * dcy + r * dr + float B = dot(dp, diff); + + float C = pow(dp.x, 2) + pow(dp.y, 2) - sq_radius1; + + float det = pow(B, 2) - A * C; + + if (det < 0) { + return float4(0, 0, 0, 0); + } + + float sqrt_det = sqrt(abs(det)); + + float2 t = (B + float2(sqrt_det, -sqrt_det)) / A; + + float2 isValid = step(float2(-radius1, -radius1), t * diff.z); + + if (max(isValid.x, isValid.y) <= 0) { + return float4(0, 0, 0, 0); + } + + float upper_t = lerp(t.y, t.x, isValid.x); + + float4 output = tex.Sample(aSampler, float2(upper_t, 0.5)); + // Premultiply + output.rgb *= output.a; + // Multiply the output color by the input mask for the operation. + output *= mask.Sample(sMaskSampler, In.MaskTexCoord).a; + return output; +}; + +float4 SampleRadialGradientA0PS( VS_RADIAL_OUTPUT In, uniform sampler aSampler ) : SV_Target +{ + // This simpler shader is used for the degenerate case where A is 0, + // i.e. we're actually solving a linear equation. + + float2 p = In.PixelCoord; + float3 dp = float3(p - center1, radius1); + + // dpx * dcx + dpy * dcy + r * dr + float B = dot(dp, diff); + + float C = pow(dp.x, 2) + pow(dp.y, 2) - pow(radius1, 2); + + float t = 0.5 * C / B; + + if (-radius1 >= t * diff.z) { + return float4(0, 0, 0, 0); + } + + float4 output = tex.Sample(aSampler, float2(t, 0.5)); + // Premultiply + output.rgb *= output.a; + // Multiply the output color by the input mask for the operation. + output *= mask.Sample(sMaskSampler, In.MaskTexCoord).a; + return output; +}; + +float4 SampleConicGradientPS(VS_CONIC_OUTPUT In, uniform sampler aSampler) : SV_Target +{ + float2 current_dir = In.PixelCoord - center; + float current_angle = atan2(current_dir.y, current_dir.x) + (3.141592 / 2.0 - angle); + float offset = fmod(current_angle / (2.0 * 3.141592), 1.0) - start_offset; + offset = offset / (end_offset - start_offset); + + float upper_t = lerp(0, 1, offset); + + float4 output = tex.Sample(aSampler, float2(upper_t, 0.5)); + // Premultiply + output.rgb *= output.a; + // Multiply the output color by the input mask for the operation. + output *= mask.Sample(sMaskSampler, In.MaskTexCoord).a; + return output; +}; + +float4 SampleShadowHPS( VS_OUTPUT In) : SV_Target +{ + float outputStrength = 0; + + outputStrength += BlurWeights[0].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[0].x, In.TexCoord.y)).a; + outputStrength += BlurWeights[0].y * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[0].y, In.TexCoord.y)).a; + outputStrength += BlurWeights[0].z * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[0].z, In.TexCoord.y)).a; + outputStrength += BlurWeights[0].w * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[0].w, In.TexCoord.y)).a; + outputStrength += BlurWeights[1].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[1].x, In.TexCoord.y)).a; + outputStrength += BlurWeights[1].y * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[1].y, In.TexCoord.y)).a; + outputStrength += BlurWeights[1].z * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[1].z, In.TexCoord.y)).a; + outputStrength += BlurWeights[1].w * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[1].w, In.TexCoord.y)).a; + outputStrength += BlurWeights[2].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x + BlurOffsetsH[2].x, In.TexCoord.y)).a; + + return ShadowColor * outputStrength; +}; + +float4 SampleShadowVPS( VS_OUTPUT In) : SV_Target +{ + float4 outputColor = float4(0, 0, 0, 0); + + outputColor += BlurWeights[0].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[0].x)); + outputColor += BlurWeights[0].y * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[0].y)); + outputColor += BlurWeights[0].z * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[0].z)); + outputColor += BlurWeights[0].w * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[0].w)); + outputColor += BlurWeights[1].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[1].x)); + outputColor += BlurWeights[1].y * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[1].y)); + outputColor += BlurWeights[1].z * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[1].z)); + outputColor += BlurWeights[1].w * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[1].w)); + outputColor += BlurWeights[2].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[2].x)); + + return outputColor; +}; + +float4 SampleMaskShadowVPS( VS_OUTPUT In) : SV_Target +{ + float4 outputColor = float4(0, 0, 0, 0); + + outputColor += BlurWeights[0].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[0].x)); + outputColor += BlurWeights[0].y * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[0].y)); + outputColor += BlurWeights[0].z * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[0].z)); + outputColor += BlurWeights[0].w * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[0].w)); + outputColor += BlurWeights[1].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[1].x)); + outputColor += BlurWeights[1].y * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[1].y)); + outputColor += BlurWeights[1].z * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[1].z)); + outputColor += BlurWeights[1].w * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[1].w)); + outputColor += BlurWeights[2].x * tex.Sample(sShadowSampler, float2(In.TexCoord.x, In.TexCoord.y + BlurOffsetsV[2].x)); + + return outputColor * mask.Sample(sMaskSampler, In.MaskTexCoord).a; +}; + +PS_TEXT_OUTPUT SampleTextTexturePS( VS_OUTPUT In) : SV_Target +{ + PS_TEXT_OUTPUT output; + output.color = float4(TextColor.r, TextColor.g, TextColor.b, 1.0); + output.alpha.rgba = tex.Sample(sSampler, In.TexCoord).bgrg * TextColor.a; + return output; +}; + +PS_TEXT_OUTPUT SampleTextTexturePSMasked( VS_OUTPUT In) : SV_Target +{ + PS_TEXT_OUTPUT output; + + float maskValue = mask.Sample(sMaskSampler, In.MaskTexCoord).a; + + output.color = float4(TextColor.r, TextColor.g, TextColor.b, 1.0); + output.alpha.rgba = tex.Sample(sSampler, In.TexCoord).bgrg * TextColor.a * maskValue; + + return output; +}; + +technique10 SampleTexture +{ + pass P0 + { + SetRasterizerState(TextureRast); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleTexturePS())); + } +} + +technique10 SampleTextureForSeparableBlending_1 +{ + pass P0 + { + SetRasterizerState(TextureRast); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleBlendTextureSeparablePS_1())); + } +} + +technique10 SampleTextureForSeparableBlending_2 +{ + pass P0 + { + SetRasterizerState(TextureRast); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleBlendTextureSeparablePS_2())); + } +} + +technique10 SampleTextureForNonSeparableBlending +{ + pass P0 + { + SetRasterizerState(TextureRast); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleBlendTextureNonSeparablePS())); + } +} + +technique10 SampleRadialGradient +{ + pass APos + { + SetRasterizerState(TextureRast); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleRadialVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleRadialGradientPS( sSampler ))); + } + pass A0 + { + SetRasterizerState(TextureRast); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleRadialVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleRadialGradientA0PS( sSampler ))); + } + pass APosWrap + { + SetRasterizerState(TextureRast); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleRadialVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleRadialGradientPS( sWrapSampler ))); + } + pass A0Wrap + { + SetRasterizerState(TextureRast); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleRadialVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleRadialGradientA0PS( sWrapSampler ))); + } + pass APosMirror + { + SetRasterizerState(TextureRast); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleRadialVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleRadialGradientPS( sMirrorSampler ))); + } + pass A0Mirror + { + SetRasterizerState(TextureRast); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleRadialVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleRadialGradientA0PS( sMirrorSampler ))); + } +} + +technique10 SampleConicGradient +{ + pass APos + { + SetRasterizerState(TextureRast); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleConicVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleConicGradientPS( sSampler ))); + } + pass APosWrap + { + SetRasterizerState(TextureRast); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleConicVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleConicGradientPS( sWrapSampler ))); + } + pass APosMirror + { + SetRasterizerState(TextureRast); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleConicVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleConicGradientPS( sMirrorSampler ))); + } +} + +technique10 SampleMaskedTexture +{ + pass P0 + { + SetRasterizerState(TextureRast); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleMaskTexturePS())); + } +} + +technique10 SampleTextureWithShadow +{ + // Horizontal pass + pass P0 + { + SetRasterizerState(TextureRast); + SetBlendState(ShadowBlendH, float4(1.0f, 1.0f, 1.0f, 1.0f), 0xffffffff); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleShadowHPS())); + } + // Vertical pass + pass P1 + { + SetRasterizerState(TextureRast); + SetBlendState(ShadowBlendV, float4(1.0f, 1.0f, 1.0f, 1.0f), 0xffffffff); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleShadowVPS())); + } + // Vertical pass - used when using a mask + pass P2 + { + SetRasterizerState(TextureRast); + SetBlendState(ShadowBlendV, float4(1.0f, 1.0f, 1.0f, 1.0f), 0xffffffff); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleMaskShadowVPS())); + } +} + +technique10 SampleTextTexture +{ + pass Unmasked + { + SetRasterizerState(TextureRast); + SetBlendState(bTextBlend, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF ); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleTextTexturePS())); + } + pass Masked + { + SetRasterizerState(TextureRast); + SetBlendState(bTextBlend, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF ); + SetVertexShader(CompileShader(vs_4_0_level_9_3, SampleTextureVS())); + SetGeometryShader(NULL); + SetPixelShader(CompileShader(ps_4_0_level_9_3, SampleTextTexturePSMasked())); + } +} + diff --git a/gfx/2d/ShadersD2D.h b/gfx/2d/ShadersD2D.h new file mode 100644 index 0000000000..ceca3df511 --- /dev/null +++ b/gfx/2d/ShadersD2D.h @@ -0,0 +1,10855 @@ +#if 0 +// +// FX Version: fx_4_0 +// Child effect (requires effect pool): false +// +// 5 local buffer(s) +// +cbuffer $Globals +{ + uint blendop; // Offset: 0, size: 4 +} + +cbuffer cb0 +{ + float4 QuadDesc; // Offset: 0, size: 16 + float4 TexCoords; // Offset: 16, size: 16 + float4 MaskTexCoords; // Offset: 32, size: 16 + float4 TextColor; // Offset: 48, size: 16 +} + +cbuffer cb1 +{ + float4 BlurOffsetsH[3]; // Offset: 0, size: 48 + float4 BlurOffsetsV[3]; // Offset: 48, size: 48 + float4 BlurWeights[3]; // Offset: 96, size: 48 + float4 ShadowColor; // Offset: 144, size: 16 +} + +cbuffer cb2 +{ + float3x3 DeviceSpaceToUserSpace; // Offset: 0, size: 44 + float2 dimensions; // Offset: 48, size: 8 + float3 diff; // Offset: 64, size: 12 + float2 center1; // Offset: 80, size: 8 + float A; // Offset: 88, size: 4 + float radius1; // Offset: 92, size: 4 + float sq_radius1; // Offset: 96, size: 4 +} + +cbuffer cb3 +{ + float3x3 DeviceSpaceToUserSpace_cb3;// Offset: 0, size: 44 + float2 dimensions_cb3; // Offset: 48, size: 8 + float2 center; // Offset: 56, size: 8 + float angle; // Offset: 64, size: 4 + float start_offset; // Offset: 68, size: 4 + float end_offset; // Offset: 72, size: 4 +} + +// +// 13 local object(s) +// +Texture2D tex; +Texture2D bcktex; +Texture2D mask; +SamplerState sSampler +{ + Filter = uint(MIN_MAG_MIP_LINEAR /* 21 */); + Texture = tex; + AddressU = uint(CLAMP /* 3 */); + AddressV = uint(CLAMP /* 3 */); +}; +SamplerState sBckSampler +{ + Filter = uint(MIN_MAG_MIP_LINEAR /* 21 */); + Texture = bcktex; + AddressU = uint(CLAMP /* 3 */); + AddressV = uint(CLAMP /* 3 */); +}; +SamplerState sWrapSampler +{ + Filter = uint(MIN_MAG_MIP_LINEAR /* 21 */); + Texture = tex; + AddressU = uint(WRAP /* 1 */); + AddressV = uint(WRAP /* 1 */); +}; +SamplerState sMirrorSampler +{ + Filter = uint(MIN_MAG_MIP_LINEAR /* 21 */); + Texture = tex; + AddressU = uint(MIRROR /* 2 */); + AddressV = uint(MIRROR /* 2 */); +}; +SamplerState sMaskSampler +{ + Filter = uint(MIN_MAG_MIP_LINEAR /* 21 */); + Texture = mask; + AddressU = uint(CLAMP /* 3 */); + AddressV = uint(CLAMP /* 3 */); +}; +SamplerState sShadowSampler +{ + Filter = uint(MIN_MAG_MIP_LINEAR /* 21 */); + Texture = tex; + AddressU = uint(BORDER /* 4 */); + AddressV = uint(BORDER /* 4 */); + BorderColor = float4(0, 0, 0, 0); +}; +RasterizerState TextureRast +{ + ScissorEnable = bool(TRUE /* 1 */); + CullMode = uint(NONE /* 1 */); +}; +BlendState ShadowBlendH +{ + BlendEnable[0] = bool(FALSE /* 0 */); + RenderTargetWriteMask[0] = byte(0x0f); +}; +BlendState ShadowBlendV +{ + BlendEnable[0] = bool(TRUE /* 1 */); + SrcBlend[0] = uint(ONE /* 2 */); + DestBlend[0] = uint(INV_SRC_ALPHA /* 6 */); + BlendOp[0] = uint(ADD /* 1 */); + SrcBlendAlpha[0] = uint(ONE /* 2 */); + DestBlendAlpha[0] = uint(INV_SRC_ALPHA /* 6 */); + BlendOpAlpha[0] = uint(ADD /* 1 */); + RenderTargetWriteMask[0] = byte(0x0f); +}; +BlendState bTextBlend +{ + AlphaToCoverageEnable = bool(FALSE /* 0 */); + BlendEnable[0] = bool(TRUE /* 1 */); + SrcBlend[0] = uint(SRC1_COLOR /* 16 */); + DestBlend[0] = uint(INV_SRC1_COLOR /* 17 */); + BlendOp[0] = uint(ADD /* 1 */); + SrcBlendAlpha[0] = uint(SRC1_ALPHA /* 18 */); + DestBlendAlpha[0] = uint(INV_SRC1_ALPHA /* 19 */); + BlendOpAlpha[0] = uint(ADD /* 1 */); + RenderTargetWriteMask[0] = byte(0x0f); +}; + +// +// 9 technique(s) +// +technique10 SampleTexture +{ + pass P0 + { + RasterizerState = TextureRast; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 3 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c4, 0, 1, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx + mad r0.xy, v0, c1.zwzw, c1 + add oPos.xy, r0, c0 + mov oPos.zw, c4.xyxy + + // approximately 5 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.zw, l(0,0,0,1.000000) + mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx + mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy + ret + // Approximately 5 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sSampler sampler NA NA 0 1 + // tex texture float4 2d 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // + // + // Level9 shader bytecode: + // + ps_2_x + dcl t0 + dcl_2d s0 + texld r0, t0, s0 + mov oC0, r0 + + // approximately 2 instruction slots used (1 texture, 1 arithmetic) + ps_4_0 + dcl_sampler s0, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_input_ps linear v1.xy + dcl_output o0.xyzw + sample o0.xyzw, v1.xyxx, t0.xyzw, s0 + ret + // Approximately 2 instruction slots used + + }; + } + +} + +technique10 SampleTextureForSeparableBlending_1 +{ + pass P0 + { + RasterizerState = TextureRast; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 3 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c4, 0, 1, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx + mad r0.xy, v0, c1.zwzw, c1 + add oPos.xy, r0, c0 + mov oPos.zw, c4.xyxy + + // approximately 5 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.zw, l(0,0,0,1.000000) + mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx + mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy + ret + // Approximately 5 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer $Globals + // { + // + // uint blendop; // Offset: 0 Size: 4 + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sSampler sampler NA NA 0 1 + // sBckSampler sampler NA NA 1 1 + // tex texture float4 2d 0 1 + // bcktex texture float4 2d 1 1 + // $Globals cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c0 cb0 0 1 (UINT, FLT, FLT, FLT) + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // s1 s1 t1 + // + // + // Level9 shader bytecode: + // + ps_2_x + def c1, -1, -2, -3, -4 + def c2, 1, 0, 0.5, -2 + def c3, -5, 0, 0, 0 + dcl t0 + dcl_2d s0 + dcl_2d s1 + mov r0.w, c0.x + add r0.x, r0.w, c3.x + mul r0.x, r0.x, r0.x + texld r1, t0, s1 + texld r2, t0, s0 + rcp r0.y, r2.w + mad r3.xyz, r2, r0.y, -c2.x + mul r3.xyz, r3, r3 + mad r4.xyz, r2, -r0.y, c2.x + rcp r3.w, r4.x + rcp r4.w, r1.w + mul r5.xyz, r1, r4.w + mad r1.xyz, r1, -r4.w, c2.z + mul r3.w, r3.w, r5.x + min r4.w, r3.w, c2.x + cmp r4.w, -r3.x, c2.x, r4.w + mul r6.xyz, r5, r5 + cmp r7.x, -r6.x, c2.y, r4.w + rcp r4.w, r4.y + mul r4.w, r4.w, r5.y + min r5.w, r4.w, c2.x + cmp r4.w, -r3.y, c2.x, r5.w + cmp r7.y, -r6.y, c2.y, r4.w + rcp r4.w, r4.z + mul r4.w, r4.w, r5.z + min r5.w, r4.w, c2.x + cmp r4.w, -r3.z, c2.x, r5.w + cmp r7.z, -r6.z, c2.y, r4.w + mul r3.xyz, r0.y, r2 + mad r6.xyz, r2, r0.y, r5 + mad r6.xyz, r3, -r5, r6 + max r8.xyz, r3, r5 + cmp r0.xyz, -r0.x, r8, r7 + add r7, r0.w, c1 + mul r7, r7, r7 + min r8.xyz, r5, r3 + cmp r0.xyz, -r7.w, r8, r0 + mad r8.xyz, r5, -c2.w, -c2.x + add r8.xyz, -r8, c2.x + mad r4.xyz, r4, -r8, c2.x + add r8.xyz, r5, r5 + mul r5.xyz, r5, r3 + mul r8.xyz, r3, r8 + cmp r1.xyz, r1, r8, r4 + cmp r0.xyz, -r7.z, r1, r0 + cmp r0.xyz, -r7.y, r6, r0 + cmp r0.xyz, -r7.x, r5, r0 + lrp r4.xyz, r1.w, r0, r3 + mul r4.w, r1.w, r1.w + cmp r4.w, -r4.w, c2.x, c2.y + mul r0.xyz, r2.w, r4 + mul r0.w, r2.w, r2.w + cmp r0.w, -r0.w, c2.x, c2.y + add r0.w, r4.w, r0.w + cmp r2.xyz, -r0.w, r0, r2 + mov oC0, r2 + + // approximately 56 instruction slots used (2 texture, 54 arithmetic) + ps_4_0 + dcl_constantbuffer cb0[1], immediateIndexed + dcl_sampler s0, mode_default + dcl_sampler s1, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_resource_texture2d (float,float,float,float) t1 + dcl_input_ps linear v1.xy + dcl_output o0.xyzw + dcl_temps 7 + sample r0.xyzw, v1.xyxx, t0.xyzw, s0 + sample r1.xyzw, v1.xyxx, t1.xyzw, s1 + eq r2.x, r0.w, l(0.000000) + eq r2.y, r1.w, l(0.000000) + or r2.x, r2.y, r2.x + if_nz r2.x + mov o0.xyzw, r0.xyzw + ret + endif + div r0.xyz, r0.xyzx, r0.wwww + div r1.xyz, r1.xyzx, r1.wwww + ieq r2.x, cb0[0].x, l(1) + if_nz r2.x + mul r2.xyz, r0.xyzx, r1.xyzx + else + ieq r2.w, cb0[0].x, l(2) + if_nz r2.w + add r3.xyz, r0.xyzx, r1.xyzx + mad r2.xyz, -r0.xyzx, r1.xyzx, r3.xyzx + else + ieq r2.w, cb0[0].x, l(3) + if_nz r2.w + ge r3.xyz, l(0.500000, 0.500000, 0.500000, 0.000000), r1.xyzx + add r4.xyz, r1.xyzx, r1.xyzx + mul r4.xyz, r0.xyzx, r4.xyzx + mad r5.xyz, r1.xyzx, l(2.000000, 2.000000, 2.000000, 0.000000), l(-1.000000, -1.000000, -1.000000, 0.000000) + add r6.xyz, -r0.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) + add r5.xyz, -r5.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) + mad r5.xyz, -r6.xyzx, r5.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) + movc r2.xyz, r3.xyzx, r4.xyzx, r5.xyzx + else + ieq r2.w, cb0[0].x, l(4) + if_nz r2.w + min r2.xyz, r0.xyzx, r1.xyzx + else + ieq r2.w, cb0[0].x, l(5) + if_nz r2.w + max r2.xyz, r0.xyzx, r1.xyzx + else + eq r3.xyz, r1.xyzx, l(0.000000, 0.000000, 0.000000, 0.000000) + eq r4.xyz, r0.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) + add r5.xyz, -r0.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) + div r1.xyz, r1.xyzx, r5.xyzx + min r1.xyz, r1.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) + movc r1.xyz, r4.xyzx, l(1.000000,1.000000,1.000000,0), r1.xyzx + movc r2.xyz, r3.xyzx, l(0,0,0,0), r1.xyzx + endif + endif + endif + endif + endif + add r1.x, -r1.w, l(1.000000) + mul r1.yzw, r1.wwww, r2.xxyz + mad r0.xyz, r1.xxxx, r0.xyzx, r1.yzwy + mul o0.xyz, r0.wwww, r0.xyzx + mov o0.w, r0.w + ret + // Approximately 57 instruction slots used + + }; + } + +} + +technique10 SampleTextureForSeparableBlending_2 +{ + pass P0 + { + RasterizerState = TextureRast; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 3 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c4, 0, 1, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx + mad r0.xy, v0, c1.zwzw, c1 + add oPos.xy, r0, c0 + mov oPos.zw, c4.xyxy + + // approximately 5 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.zw, l(0,0,0,1.000000) + mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx + mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy + ret + // Approximately 5 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer $Globals + // { + // + // uint blendop; // Offset: 0 Size: 4 + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sSampler sampler NA NA 0 1 + // sBckSampler sampler NA NA 1 1 + // tex texture float4 2d 0 1 + // bcktex texture float4 2d 1 1 + // $Globals cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c0 cb0 0 1 (UINT, FLT, FLT, FLT) + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // s1 s1 t1 + // + // + // Level9 shader bytecode: + // + ps_2_x + def c1, -7, -8, -9, -10 + def c2, 1, 0, -1, 0.25 + def c3, 0.5, 2, -1, 4 + def c4, 16, -12, 2, 1 + dcl t0 + dcl_2d s0 + dcl_2d s1 + mov r0.w, c0.x + add r0, r0.w, c1 + mul r0, r0, r0 + texld r1, t0, s0 + texld r2, t0, s1 + rcp r3.w, r2.w + mad r3.xy, r2.yzzw, -r3.w, c2.w + mul r4.xyz, r2, r3.w + mad r5.xyz, r4, c4.x, c4.y + mad r5.xyz, r5, r4, c3.w + mul r5.xyz, r4, r5 + rsq r4.w, r4.y + rcp r4.w, r4.w + cmp r4.w, r3.x, r5.y, r4.w + mad r4.w, r2.y, -r3.w, r4.w + rcp r3.x, r1.w + mul r6.xyz, r1, r3.x + mad r7.xyz, r6, c3.y, c3.z + mad r4.w, r7.y, r4.w, r4.y + mad r8.xyz, r1, -r3.x, c3.x + mad r9, r2.xyzx, -r3.w, c2.xxxw + mad r10.xyz, r6, -c4.z, c4.w + mul r10.xyz, r4, r10 + mad r10.xyz, r10, -r9, r4 + cmp r11.y, r8.y, r10.y, r4.w + rsq r4.w, r4.z + rcp r4.w, r4.w + cmp r4.w, r3.y, r5.z, r4.w + mad r4.w, r2.z, -r3.w, r4.w + mad r4.w, r7.z, r4.w, r4.z + cmp r11.z, r8.z, r10.z, r4.w + rsq r4.w, r4.x + rcp r4.w, r4.w + cmp r4.w, r9.w, r5.x, r4.w + mad r4.w, r2.x, -r3.w, r4.w + mad r2.xyz, r2, r3.w, c2.z + mul r2.xyz, r2, r2 + mad r4.w, r7.x, r4.w, r4.x + add r3.yzw, -r7.xxyz, c2.x + mad r3.yzw, r9.xxyz, -r3, c2.x + cmp r11.x, r8.x, r10.x, r4.w + mad r5.xyz, r1, r3.x, -r4 + mad r7.xyz, r1, r3.x, r4 + abs r5.xyz, r5 + mul r10.xyz, r4, r6 + mad r7.xyz, r10, -c3.y, r7 + cmp r5.xyz, -r0.w, r5, r7 + cmp r5.xyz, -r0.z, r11, r5 + add r7.xyz, r6, r6 + mul r4.xyz, r4, r7 + cmp r3.xyz, r8, r4, r3.yzww + cmp r0.yzw, -r0.y, r3.xxyz, r5.xxyz + rcp r6.w, r6.x + mad r6.w, r9.x, -r6.w, c2.x + max r3.x, r6.w, c2.y + mul r3.yzw, r6.xxyz, r6.xxyz + cmp r6.w, -r3.y, c2.y, r3.x + cmp r4.x, -r2.x, c2.x, r6.w + rcp r4.w, r6.y + mad r4.w, r9.y, -r4.w, c2.x + max r6.w, r4.w, c2.y + cmp r4.w, -r3.z, c2.y, r6.w + cmp r4.y, -r2.y, c2.x, r4.w + rcp r4.w, r6.z + mad r4.w, r9.z, -r4.w, c2.x + max r6.w, r4.w, c2.y + cmp r4.w, -r3.w, c2.y, r6.w + cmp r4.z, -r2.z, c2.x, r4.w + cmp r0.xyz, -r0.x, r4, r0.yzww + lrp r3.xyz, r2.w, r0, r6 + mul r3.w, r2.w, r2.w + cmp r3.w, -r3.w, c2.x, c2.y + mul r0.xyz, r1.w, r3 + mul r0.w, r1.w, r1.w + cmp r0.w, -r0.w, c2.x, c2.y + add r0.w, r3.w, r0.w + cmp r1.xyz, -r0.w, r0, r1 + mov oC0, r1 + + // approximately 78 instruction slots used (2 texture, 76 arithmetic) + ps_4_0 + dcl_constantbuffer cb0[1], immediateIndexed + dcl_sampler s0, mode_default + dcl_sampler s1, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_resource_texture2d (float,float,float,float) t1 + dcl_input_ps linear v1.xy + dcl_output o0.xyzw + dcl_temps 7 + sample r0.xyzw, v1.xyxx, t0.xyzw, s0 + sample r1.xyzw, v1.xyxx, t1.xyzw, s1 + eq r2.x, r0.w, l(0.000000) + eq r2.y, r1.w, l(0.000000) + or r2.x, r2.y, r2.x + if_nz r2.x + mov o0.xyzw, r0.xyzw + ret + endif + div r0.xyz, r0.xyzx, r0.wwww + div r1.xyz, r1.xyzx, r1.wwww + ieq r2.x, cb0[0].x, l(7) + if_nz r2.x + eq r2.xyz, r1.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) + eq r3.xyz, r0.xyzx, l(0.000000, 0.000000, 0.000000, 0.000000) + add r4.xyz, -r1.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) + div r4.xyz, r4.xyzx, r0.xyzx + min r4.xyz, r4.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) + add r4.xyz, -r4.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) + movc r3.xyz, r3.xyzx, l(0,0,0,0), r4.xyzx + movc r2.xyz, r2.xyzx, l(1.000000,1.000000,1.000000,0), r3.xyzx + else + ieq r2.w, cb0[0].x, l(8) + if_nz r2.w + ge r3.xyz, l(0.500000, 0.500000, 0.500000, 0.000000), r0.xyzx + add r4.xyz, r0.xyzx, r0.xyzx + mul r4.xyz, r1.xyzx, r4.xyzx + mad r5.xyz, r0.xyzx, l(2.000000, 2.000000, 2.000000, 0.000000), l(-1.000000, -1.000000, -1.000000, 0.000000) + add r6.xyz, -r1.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) + add r5.xyz, -r5.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) + mad r5.xyz, -r6.xyzx, r5.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) + movc r2.xyz, r3.xyzx, r4.xyzx, r5.xyzx + else + ieq r2.w, cb0[0].x, l(9) + if_nz r2.w + ge r3.xyz, l(0.250000, 0.250000, 0.250000, 0.000000), r1.xyzx + mad r4.xyz, r1.xyzx, l(16.000000, 16.000000, 16.000000, 0.000000), l(-12.000000, -12.000000, -12.000000, 0.000000) + mad r4.xyz, r4.xyzx, r1.xyzx, l(4.000000, 4.000000, 4.000000, 0.000000) + mul r4.xyz, r1.xyzx, r4.xyzx + sqrt r5.xyz, r1.xyzx + movc r3.xyz, r3.xyzx, r4.xyzx, r5.xyzx + ge r4.xyz, l(0.500000, 0.500000, 0.500000, 0.000000), r0.xyzx + mad r5.xyz, -r0.xyzx, l(2.000000, 2.000000, 2.000000, 0.000000), l(1.000000, 1.000000, 1.000000, 0.000000) + mul r5.xyz, r1.xyzx, r5.xyzx + add r6.xyz, -r1.xyzx, l(1.000000, 1.000000, 1.000000, 0.000000) + mad r5.xyz, -r5.xyzx, r6.xyzx, r1.xyzx + mad r6.xyz, r0.xyzx, l(2.000000, 2.000000, 2.000000, 0.000000), l(-1.000000, -1.000000, -1.000000, 0.000000) + add r3.xyz, -r1.xyzx, r3.xyzx + mad r3.xyz, r6.xyzx, r3.xyzx, r1.xyzx + movc r2.xyz, r4.xyzx, r5.xyzx, r3.xyzx + else + ieq r2.w, cb0[0].x, l(10) + add r3.xyz, r0.xyzx, -r1.xyzx + add r4.xyz, r0.xyzx, r1.xyzx + mul r1.xyz, r0.xyzx, r1.xyzx + mad r1.xyz, -r1.xyzx, l(2.000000, 2.000000, 2.000000, 0.000000), r4.xyzx + movc r2.xyz, r2.wwww, |r3.xyzx|, r1.xyzx + endif + endif + endif + add r1.x, -r1.w, l(1.000000) + mul r1.yzw, r1.wwww, r2.xxyz + mad r0.xyz, r1.xxxx, r0.xyzx, r1.yzwy + mul o0.xyz, r0.wwww, r0.xyzx + mov o0.w, r0.w + ret + // Approximately 66 instruction slots used + + }; + } + +} + +technique10 SampleTextureForNonSeparableBlending +{ + pass P0 + { + RasterizerState = TextureRast; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 3 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c4, 0, 1, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx + mad r0.xy, v0, c1.zwzw, c1 + add oPos.xy, r0, c0 + mov oPos.zw, c4.xyxy + + // approximately 5 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.zw, l(0,0,0,1.000000) + mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx + mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy + ret + // Approximately 5 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer $Globals + // { + // + // uint blendop; // Offset: 0 Size: 4 + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sSampler sampler NA NA 0 1 + // sBckSampler sampler NA NA 1 1 + // tex texture float4 2d 0 1 + // bcktex texture float4 2d 1 1 + // $Globals cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c0 cb0 0 1 (UINT, FLT, FLT, FLT) + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // s1 s1 t1 + // + // + // Level9 shader bytecode: + // + ps_2_x + def c1, -12, -13, -14, 0 + def c2, 1, 0, 0, 0 + def c3, 0.300000012, 0.589999974, 0.109999999, 0 + dcl t0 + dcl_2d s0 + dcl_2d s1 + mov r0.y, c2.y + mov r1.y, c2.y + mov r2.z, c2.y + texld r3, t0, s1 + texld r4, t0, s0 + rcp r0.w, r4.w + mul r5.xyz, r0.w, r4 + mad r6.xy, r4.yxzw, r0.w, -r5.zyzw + cmp r7.xy, r6.x, r5.yzzw, r5.zyzw + max r1.w, r5.x, r7.x + min r2.w, r7.y, r5.x + add r7.w, r1.w, -r2.w + rcp r1.w, r3.w + mul r8.xyz, r1.w, r3 + mad r9.xy, r3.x, r1.w, -r8.zyzw + rcp r2.w, r9.y + mul r2.w, r2.w, r7.w + mad r10, r3.zyyz, r1.w, -r8.xxzy + mul r7.y, r2.w, r10.w + mov r9.zw, r10 + cmp r1.xz, -r9.y, r9.yyww, r7.wyyw + rcp r2.w, r9.x + mul r2.w, r2.w, r7.w + mul r7.x, r2.w, r9.z + cmp r2.xy, -r9.x, r9.xzzw, r7.wxzw + cmp r1.xyz, r9.w, r1, r2 + rcp r5.w, r9.w + mul r5.w, r5.w, r7.w + mul r7.z, r5.w, r9.y + cmp r0.xz, -r10.w, r9.yyww, r7.zyww + cmp r0.xyz, r10.x, r0, r1 + mov r1.x, c2.y + mov r2.x, c2.y + mov r11.z, c2.y + rcp r2.w, r9.z + mul r2.w, r2.w, r7.w + mul r7.x, r2.w, r9.x + cmp r11.xy, -r10.z, r9.xzzw, r7.xwzw + rcp r2.w, r10.y + mul r2.w, r2.w, r7.w + mul r7.y, r2.w, r10.x + cmp r2.yz, -r10.y, r10.xyxw, r7.xwyw + cmp r2.xyz, r10.x, r2, r11 + rcp r2.w, r10.x + mul r2.w, r2.w, r7.w + mul r7.z, r2.w, r10.y + cmp r1.yz, -r10.x, r10.xyxw, r7.xzww + cmp r1.xyz, r9.w, r1, r2 + cmp r0.xyz, r10.y, r1, r0 + cmp r1.xy, r9.z, r8.yzzw, r8.zyzw + dp3 r5.w, r0, c3 + dp3 r1.z, r8, c3 + add r5.w, -r5.w, r1.z + add r0.xyz, r0, r5.w + add r5.w, -r0.y, r0.x + cmp r2.xy, r5.w, r0.yxzw, r0 + min r5.w, r0.z, r2.x + max r7.x, r2.y, r0.z + dp3 r2.x, r0, c3 + add r2.y, -r5.w, r2.x + rcp r2.y, r2.y + add r7.yzw, r0.xxyz, -r2.x + mul r7.yzw, r2.x, r7 + mad r2.yzw, r7, r2.y, r2.x + cmp r0.xyz, r5.w, r0, r2.yzww + add r2.yzw, -r2.x, r0.xxyz + add r5.w, -r2.x, c2.x + mul r2.yzw, r2, r5.w + add r5.w, -r2.x, r7.x + add r7.x, -r7.x, c2.x + rcp r5.w, r5.w + mad r2.xyz, r2.yzww, r5.w, r2.x + cmp r0.xyz, r7.x, r0, r2 + dp3 r5.w, r5, c3 + add r2.x, r1.z, -r5.w + add r5.w, -r1.z, r5.w + mad r2.yzw, r3.xxyz, r1.w, r5.w + mad r3.xyz, r4, r0.w, r2.x + mad r7, r4.zyzx, r0.w, -r5.xxyz + add r0.w, -r3.y, r3.x + cmp r8.yz, r0.w, r3.xyxw, r3.xxyw + min r0.w, r3.z, r8.y + max r1.w, r8.z, r3.z + dp3 r5.w, r3, c3 + add r2.x, -r0.w, r5.w + rcp r2.x, r2.x + add r8.yzw, r3.xxyz, -r5.w + mul r8.yzw, r5.w, r8 + mad r8.yzw, r8, r2.x, r5.w + cmp r3.xyz, r0.w, r3, r8.yzww + add r8.yzw, -r5.w, r3.xxyz + add r0.w, -r5.w, c2.x + mul r8.yzw, r0.w, r8 + add r0.w, r1.w, -r5.w + add r1.w, -r1.w, c2.x + rcp r0.w, r0.w + mad r8.yzw, r8, r0.w, r5.w + cmp r3.xyz, r1.w, r3, r8.yzww + add r0.w, -r2.z, r2.y + cmp r8.yz, r0.w, r2.xzyw, r2 + min r0.w, r2.w, r8.y + max r1.w, r8.z, r2.w + dp3 r5.w, r2.yzww, c3 + add r2.x, -r0.w, r5.w + rcp r2.x, r2.x + add r8.yzw, r2, -r5.w + mul r8.yzw, r5.w, r8 + mad r8.yzw, r8, r2.x, r5.w + cmp r2.xyz, r0.w, r2.yzww, r8.yzww + add r8.yzw, -r5.w, r2.xxyz + add r0.w, -r5.w, c2.x + mul r8.yzw, r0.w, r8 + add r0.w, r1.w, -r5.w + add r1.w, -r1.w, c2.x + rcp r0.w, r0.w + mad r8.yzw, r8, r0.w, r5.w + cmp r2.xyz, r1.w, r2, r8.yzww + mov r0.w, c0.x + add r8.yzw, r0.w, c1.xxyz + mul r8.yzw, r8, r8 + cmp r2.xyz, -r8.w, r3, r2 + cmp r0.xyz, -r8.z, r0, r2 + mov r2.y, c2.y + mov r3.y, c2.y + mov r9.z, c2.y + max r0.w, r8.x, r1.x + min r2.w, r1.y, r8.x + add r10.w, r0.w, -r2.w + rcp r0.w, r7.w + mul r0.w, r0.w, r10.w + mul r10.x, r0.w, r6.x + mov r6.zw, r7.xywz + cmp r9.xy, -r7.w, r6.zxzw, r10.wxzw + rcp r0.w, r6.y + mul r0.w, r0.w, r10.w + mul r10.y, r0.w, r7.z + cmp r3.xz, -r6.y, r6.yyww, r10.wyyw + cmp r1.xyw, r7.z, r3.xyzz, r9.xyzz + rcp r0.w, r7.z + mul r0.w, r0.w, r10.w + mul r10.z, r0.w, r6.y + cmp r2.xz, -r7.z, r6.yyww, r10.zyww + cmp r1.xyw, r7.x, r2.xyzz, r1 + mov r2.x, c2.y + mov r3.z, c2.y + rcp r0.w, r6.x + mul r0.w, r0.w, r10.w + mul r10.x, r0.w, r7.w + cmp r3.xy, -r6.x, r6.zxzw, r10.xwzw + rcp r0.w, r7.y + mul r0.w, r0.w, r10.w + mul r10.y, r0.w, r7.x + cmp r2.yz, -r7.y, r7.xyxw, r10.xwyw + cmp r2.xyz, r7.x, r2, r3 + mov r3.x, c2.y + rcp r0.w, r7.x + mul r0.w, r0.w, r10.w + mul r10.z, r0.w, r7.y + cmp r3.yz, -r7.x, r7.xyxw, r10.xzww + cmp r2.xyz, r7.z, r3, r2 + cmp r1.xyw, r7.y, r2.xyzz, r1 + dp3 r0.w, r1.xyww, c3 + add r0.w, -r0.w, r1.z + add r1.xyz, r0.w, r1.xyww + add r0.w, -r1.y, r1.x + cmp r2.xy, r0.w, r1.yxzw, r1 + min r0.w, r1.z, r2.x + max r5.w, r2.y, r1.z + dp3 r1.w, r1, c3 + add r2.xyz, -r1.w, r1 + mul r2.xyz, r1.w, r2 + add r2.w, -r0.w, r1.w + rcp r2.w, r2.w + mad r2.xyz, r2, r2.w, r1.w + cmp r1.xyz, r0.w, r1, r2 + add r2.xyz, -r1.w, r1 + add r0.w, -r1.w, c2.x + mul r2.xyz, r0.w, r2 + add r0.w, -r1.w, r5.w + add r2.w, -r5.w, c2.x + rcp r0.w, r0.w + mad r2.xyz, r2, r0.w, r1.w + cmp r1.xyz, r2.w, r1, r2 + cmp r0.xyz, -r8.y, r1, r0 + lrp r1.xyz, r3.w, r0, r5 + mul r1.w, r3.w, r3.w + cmp r1.w, -r1.w, c2.x, c2.y + mul r0.xyz, r4.w, r1 + mul r0.w, r4.w, r4.w + cmp r0.w, -r0.w, c2.x, c2.y + add r0.w, r1.w, r0.w + cmp r4.xyz, -r0.w, r0, r4 + mov oC0, r4 + + // approximately 193 instruction slots used (2 texture, 191 arithmetic) + ps_4_0 + dcl_constantbuffer cb0[1], immediateIndexed + dcl_sampler s0, mode_default + dcl_sampler s1, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_resource_texture2d (float,float,float,float) t1 + dcl_input_ps linear v1.xy + dcl_output o0.xyzw + dcl_temps 9 + sample r0.xyzw, v1.xyxx, t0.xyzw, s0 + sample r1.xyzw, v1.xyxx, t1.xyzw, s1 + eq r2.x, r0.w, l(0.000000) + eq r2.y, r1.w, l(0.000000) + or r2.x, r2.y, r2.x + if_nz r2.x + mov o0.xyzw, r0.xyzw + ret + endif + div r0.xyz, r0.xyzx, r0.wwww + div r1.xyz, r1.xyzx, r1.wwww + ieq r2.x, cb0[0].x, l(12) + if_nz r2.x + max r2.x, r1.z, r1.y + max r2.x, r1.x, r2.x + min r2.y, r1.z, r1.y + min r2.y, r1.x, r2.y + add r2.w, -r2.y, r2.x + ge r3.x, r0.y, r0.x + if_nz r3.x + add r3.xyzw, -r0.xxzz, r0.yzxy + lt r4.xyz, l(0.000000, 0.000000, 0.000000, 0.000000), r3.yxwy + div r5.xyz, r2.wwww, r3.yxwy + mul r2.xyz, r3.xyzx, r5.xyzx + movc r5.yz, r4.xxxx, r2.xxwx, r3.xxyx + ge r4.xw, r0.zzzz, r0.yyyx + movc r6.yz, r4.yyyy, r2.wwyw, r3.xxyx + movc r3.xy, r4.zzzz, r2.zwzz, r3.zwzz + mov r6.x, l(0) + mov r3.z, l(0) + movc r3.xyz, r4.wwww, r6.xyzx, r3.xyzx + mov r5.x, l(0) + movc r3.xyz, r4.xxxx, r5.xyzx, r3.xyzx + else + add r4.xyzw, -r0.yyzz, r0.xzyx + lt r5.xyz, l(0.000000, 0.000000, 0.000000, 0.000000), r4.yxwy + div r6.xyz, r2.wwww, r4.yxwy + mul r2.xyz, r4.xyzx, r6.xyzx + movc r6.xz, r5.xxxx, r2.xxwx, r4.xxyx + ge r5.xw, r0.zzzz, r0.xxxy + movc r7.xz, r5.yyyy, r2.wwyw, r4.xxyx + movc r2.xy, r5.zzzz, r2.wzww, r4.wzww + mov r7.y, l(0) + mov r2.z, l(0) + movc r2.xyz, r5.wwww, r7.xyzx, r2.xyzx + mov r6.y, l(0) + movc r3.xyz, r5.xxxx, r6.xyzx, r2.xyzx + endif + dp3 r2.x, r1.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000) + dp3 r2.y, r3.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000) + add r2.x, -r2.y, r2.x + add r2.xyz, r2.xxxx, r3.xyzx + dp3 r2.w, r2.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000) + min r3.x, r2.y, r2.x + min r3.x, r2.z, r3.x + max r3.y, r2.y, r2.x + max r3.y, r2.z, r3.y + lt r3.z, r3.x, l(0.000000) + add r4.xyz, -r2.wwww, r2.xyzx + mul r4.xyz, r2.wwww, r4.xyzx + add r3.x, r2.w, -r3.x + div r4.xyz, r4.xyzx, r3.xxxx + add r4.xyz, r2.wwww, r4.xyzx + movc r2.xyz, r3.zzzz, r4.xyzx, r2.xyzx + lt r3.x, l(1.000000), r3.y + add r4.xyz, -r2.wwww, r2.xyzx + add r3.z, -r2.w, l(1.000000) + mul r4.xyz, r3.zzzz, r4.xyzx + add r3.y, -r2.w, r3.y + div r3.yzw, r4.xxyz, r3.yyyy + add r3.yzw, r2.wwww, r3.yyzw + movc r2.xyz, r3.xxxx, r3.yzwy, r2.xyzx + else + ieq r2.w, cb0[0].x, l(13) + if_nz r2.w + max r2.w, r0.z, r0.y + max r2.w, r0.x, r2.w + min r3.x, r0.z, r0.y + min r3.x, r0.x, r3.x + add r3.w, r2.w, -r3.x + ge r2.w, r1.y, r1.x + if_nz r2.w + add r4.xyzw, -r1.xxzz, r1.yzxy + lt r5.xyz, l(0.000000, 0.000000, 0.000000, 0.000000), r4.yxwy + div r6.xyz, r3.wwww, r4.yxwy + mul r3.xyz, r4.xyzx, r6.xyzx + movc r6.yz, r5.xxxx, r3.xxwx, r4.xxyx + ge r5.xw, r1.zzzz, r1.yyyx + movc r7.yz, r5.yyyy, r3.wwyw, r4.xxyx + movc r4.xy, r5.zzzz, r3.zwzz, r4.zwzz + mov r7.x, l(0) + mov r4.z, l(0) + movc r4.xyz, r5.wwww, r7.xyzx, r4.xyzx + mov r6.x, l(0) + movc r4.xyz, r5.xxxx, r6.xyzx, r4.xyzx + else + add r5.xyzw, -r1.yyzz, r1.xzyx + lt r6.xyz, l(0.000000, 0.000000, 0.000000, 0.000000), r5.yxwy + div r7.xyz, r3.wwww, r5.yxwy + mul r3.xyz, r5.xyzx, r7.xyzx + movc r7.xz, r6.xxxx, r3.xxwx, r5.xxyx + ge r6.xw, r1.zzzz, r1.xxxy + movc r8.xz, r6.yyyy, r3.wwyw, r5.xxyx + movc r3.xy, r6.zzzz, r3.wzww, r5.wzww + mov r8.y, l(0) + mov r3.z, l(0) + movc r3.xyz, r6.wwww, r8.xyzx, r3.xyzx + mov r7.y, l(0) + movc r4.xyz, r6.xxxx, r7.xyzx, r3.xyzx + endif + dp3 r2.w, r1.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000) + dp3 r3.x, r4.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000) + add r2.w, r2.w, -r3.x + add r3.xyz, r2.wwww, r4.xyzx + dp3 r2.w, r3.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000) + min r3.w, r3.y, r3.x + min r3.w, r3.z, r3.w + max r4.x, r3.y, r3.x + max r4.x, r3.z, r4.x + lt r4.y, r3.w, l(0.000000) + add r5.xyz, -r2.wwww, r3.xyzx + mul r5.xyz, r2.wwww, r5.xyzx + add r3.w, r2.w, -r3.w + div r5.xyz, r5.xyzx, r3.wwww + add r5.xyz, r2.wwww, r5.xyzx + movc r3.xyz, r4.yyyy, r5.xyzx, r3.xyzx + lt r3.w, l(1.000000), r4.x + add r4.yzw, -r2.wwww, r3.xxyz + add r5.x, -r2.w, l(1.000000) + mul r4.yzw, r4.yyzw, r5.xxxx + add r4.x, -r2.w, r4.x + div r4.xyz, r4.yzwy, r4.xxxx + add r4.xyz, r2.wwww, r4.xyzx + movc r2.xyz, r3.wwww, r4.xyzx, r3.xyzx + else + ieq r2.w, cb0[0].x, l(14) + if_nz r2.w + dp3 r2.w, r1.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000) + dp3 r3.x, r0.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000) + add r2.w, r2.w, -r3.x + add r3.xyz, r0.xyzx, r2.wwww + dp3 r2.w, r3.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000) + min r3.w, r3.y, r3.x + min r3.w, r3.z, r3.w + max r4.x, r3.y, r3.x + max r4.x, r3.z, r4.x + lt r4.y, r3.w, l(0.000000) + add r5.xyz, -r2.wwww, r3.xyzx + mul r5.xyz, r2.wwww, r5.xyzx + add r3.w, r2.w, -r3.w + div r5.xyz, r5.xyzx, r3.wwww + add r5.xyz, r2.wwww, r5.xyzx + movc r3.xyz, r4.yyyy, r5.xyzx, r3.xyzx + lt r3.w, l(1.000000), r4.x + add r4.yzw, -r2.wwww, r3.xxyz + add r5.x, -r2.w, l(1.000000) + mul r4.yzw, r4.yyzw, r5.xxxx + add r4.x, -r2.w, r4.x + div r4.xyz, r4.yzwy, r4.xxxx + add r4.xyz, r2.wwww, r4.xyzx + movc r2.xyz, r3.wwww, r4.xyzx, r3.xyzx + else + dp3 r2.w, r0.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000) + dp3 r3.x, r1.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000) + add r2.w, r2.w, -r3.x + add r1.xyz, r1.xyzx, r2.wwww + dp3 r2.w, r1.xyzx, l(0.300000, 0.590000, 0.110000, 0.000000) + min r3.x, r1.y, r1.x + min r3.x, r1.z, r3.x + max r3.y, r1.y, r1.x + max r3.y, r1.z, r3.y + lt r3.z, r3.x, l(0.000000) + add r4.xyz, r1.xyzx, -r2.wwww + mul r4.xyz, r2.wwww, r4.xyzx + add r3.x, r2.w, -r3.x + div r4.xyz, r4.xyzx, r3.xxxx + add r4.xyz, r2.wwww, r4.xyzx + movc r1.xyz, r3.zzzz, r4.xyzx, r1.xyzx + lt r3.x, l(1.000000), r3.y + add r4.xyz, -r2.wwww, r1.xyzx + add r3.z, -r2.w, l(1.000000) + mul r4.xyz, r3.zzzz, r4.xyzx + add r3.y, -r2.w, r3.y + div r3.yzw, r4.xxyz, r3.yyyy + add r3.yzw, r2.wwww, r3.yyzw + movc r2.xyz, r3.xxxx, r3.yzwy, r1.xyzx + endif + endif + endif + add r1.x, -r1.w, l(1.000000) + mul r1.yzw, r1.wwww, r2.xxyz + mad r0.xyz, r1.xxxx, r0.xyzx, r1.yzwy + mul o0.xyz, r0.wwww, r0.xyzx + mov o0.w, r0.w + ret + // Approximately 195 instruction slots used + + }; + } + +} + +technique10 SampleRadialGradient +{ + pass APos + { + RasterizerState = TextureRast; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 [unused] + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // cbuffer cb2 + // { + // + // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 + // float2 dimensions; // Offset: 48 Size: 8 + // float3 diff; // Offset: 64 Size: 12 [unused] + // float2 center1; // Offset: 80 Size: 8 [unused] + // float A; // Offset: 88 Size: 4 [unused] + // float radius1; // Offset: 92 Size: 4 [unused] + // float sq_radius1; // Offset: 96 Size: 4 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // cb2 cbuffer NA NA 1 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 1 ( FLT, FLT, FLT, FLT) + // c2 cb0 2 1 ( FLT, FLT, FLT, FLT) + // c3 cb1 0 2 ( FLT, FLT, FLT, FLT) + // c5 cb1 3 1 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c6, 1, 0.5, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad r0.xy, v0, c1.zwzw, c1 + add r0.z, r0.x, c6.x + mul r0.z, r0.z, c5.x + mul r1.x, r0.z, c6.y + add r0.z, -r0.y, c6.x + add oPos.xy, r0, c0 + mul r0.x, r0.z, c5.y + mul r1.y, r0.x, c6.y + mov r1.z, c6.x + dp3 oT0.w, r1, c3 + dp3 oT0.z, r1, c4 + mov oPos.zw, c6.xyzx + + // approximately 13 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_constantbuffer cb1[4], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + dcl_temps 2 + mov o0.zw, l(0,0,0,1.000000) + mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.xy, r0.xyxx + add r0.x, r0.x, l(1.000000) + add r0.y, -r0.y, l(1.000000) + mul r0.xy, r0.xyxx, cb1[3].xyxx + mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000) + mov r1.z, l(1.000000) + dp3 o1.z, r1.xyzx, cb1[0].xyzx + dp3 o1.w, r1.xyzx, cb1[1].xyzx + mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx + ret + // Approximately 12 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb2 + // { + // + // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 [unused] + // float2 dimensions; // Offset: 48 Size: 8 [unused] + // float3 diff; // Offset: 64 Size: 12 + // float2 center1; // Offset: 80 Size: 8 + // float A; // Offset: 88 Size: 4 + // float radius1; // Offset: 92 Size: 4 + // float sq_radius1; // Offset: 96 Size: 4 + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sSampler sampler NA NA 0 1 + // sMaskSampler sampler NA NA 1 1 + // tex texture float4 2d 0 1 + // mask texture float4 2d 1 1 + // cb2 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c0 cb0 4 3 ( FLT, FLT, FLT, FLT) + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // s1 s1 t1 + // + // + // Level9 shader bytecode: + // + ps_2_x + def c3, 0.5, 0, 0, 0 + def c4, 1, -1, 0, -0 + dcl t0 + dcl_2d s0 + dcl_2d s1 + add r0.xy, t0.wzzw, -c1 + dp2add r0.w, r0, r0, -c2.x + mul r0.w, r0.w, c1.z + mov r0.z, c1.w + dp3 r0.x, r0, c0 + mad r0.y, r0.x, r0.x, -r0.w + abs r0.z, r0.y + rsq r0.z, r0.z + rcp r1.x, r0.z + mov r1.yz, -r1.x + add r0.xzw, r0.x, r1.xyyz + rcp r1.x, c1.z + mul r0.xzw, r0, r1.x + mov r1.w, c1.w + mad r1.xyz, r0.xzww, c0.z, r1.w + cmp r2.x, r1.x, r0.x, r0.w + cmp r0.xzw, r1.xyyz, c4.xyxy, c4.zyzw + mov r2.y, c3.x + texld r1, t0, s1 + texld r2, r2, s0 + mul r2.xyz, r2.w, r2 + mul r1, r1.w, r2 + add r0.w, r0.w, r0.x + cmp r0.x, r0.w, r0.x, r0.z + cmp r1, -r0.x, c4.z, r1 + cmp r0, r0.y, r1, c4.z + mov oC0, r0 + + // approximately 28 instruction slots used (2 texture, 26 arithmetic) + ps_4_0 + dcl_constantbuffer cb0[7], immediateIndexed + dcl_sampler s0, mode_default + dcl_sampler s1, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_resource_texture2d (float,float,float,float) t1 + dcl_input_ps linear v1.xy + dcl_input_ps linear v1.zw + dcl_output o0.xyzw + dcl_temps 3 + add r0.xy, v1.zwzz, -cb0[5].xyxx + mov r0.z, cb0[5].w + dp3 r0.z, r0.xyzx, cb0[4].xyzx + dp2 r0.x, r0.xyxx, r0.xyxx + add r0.x, r0.x, -cb0[6].x + mul r0.x, r0.x, cb0[5].z + mad r0.x, r0.z, r0.z, -r0.x + lt r0.y, r0.x, l(0.000000) + sqrt r1.x, |r0.x| + mov r1.y, -r1.x + add r0.xz, r0.zzzz, r1.xxyx + div r0.xz, r0.xxzx, cb0[5].zzzz + mul r1.xy, r0.xzxx, cb0[4].zzzz + ge r1.xy, r1.xyxx, -cb0[5].wwww + and r1.xy, r1.xyxx, l(0x3f800000, 0x3f800000, 0, 0) + add r0.x, -r0.z, r0.x + mad r2.x, r1.x, r0.x, r0.z + mov r2.y, l(0.500000) + sample r2.xyzw, r2.xyxx, t0.xyzw, s0 + if_nz r0.y + mov o0.xyzw, l(0,0,0,0) + ret + endif + max r0.x, r1.y, r1.x + ge r0.x, l(0.000000), r0.x + if_nz r0.x + mov o0.xyzw, l(0,0,0,0) + ret + endif + mul r2.xyz, r2.wwww, r2.xyzx + sample r0.xyzw, v1.xyxx, t1.xyzw, s1 + mul o0.xyzw, r0.wwww, r2.xyzw + ret + // Approximately 33 instruction slots used + + }; + } + + pass A0 + { + RasterizerState = TextureRast; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 [unused] + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // cbuffer cb2 + // { + // + // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 + // float2 dimensions; // Offset: 48 Size: 8 + // float3 diff; // Offset: 64 Size: 12 [unused] + // float2 center1; // Offset: 80 Size: 8 [unused] + // float A; // Offset: 88 Size: 4 [unused] + // float radius1; // Offset: 92 Size: 4 [unused] + // float sq_radius1; // Offset: 96 Size: 4 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // cb2 cbuffer NA NA 1 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 1 ( FLT, FLT, FLT, FLT) + // c2 cb0 2 1 ( FLT, FLT, FLT, FLT) + // c3 cb1 0 2 ( FLT, FLT, FLT, FLT) + // c5 cb1 3 1 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c6, 1, 0.5, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad r0.xy, v0, c1.zwzw, c1 + add r0.z, r0.x, c6.x + mul r0.z, r0.z, c5.x + mul r1.x, r0.z, c6.y + add r0.z, -r0.y, c6.x + add oPos.xy, r0, c0 + mul r0.x, r0.z, c5.y + mul r1.y, r0.x, c6.y + mov r1.z, c6.x + dp3 oT0.w, r1, c3 + dp3 oT0.z, r1, c4 + mov oPos.zw, c6.xyzx + + // approximately 13 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_constantbuffer cb1[4], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + dcl_temps 2 + mov o0.zw, l(0,0,0,1.000000) + mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.xy, r0.xyxx + add r0.x, r0.x, l(1.000000) + add r0.y, -r0.y, l(1.000000) + mul r0.xy, r0.xyxx, cb1[3].xyxx + mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000) + mov r1.z, l(1.000000) + dp3 o1.z, r1.xyzx, cb1[0].xyzx + dp3 o1.w, r1.xyzx, cb1[1].xyzx + mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx + ret + // Approximately 12 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb2 + // { + // + // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 [unused] + // float2 dimensions; // Offset: 48 Size: 8 [unused] + // float3 diff; // Offset: 64 Size: 12 + // float2 center1; // Offset: 80 Size: 8 + // float A; // Offset: 88 Size: 4 [unused] + // float radius1; // Offset: 92 Size: 4 + // float sq_radius1; // Offset: 96 Size: 4 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sSampler sampler NA NA 0 1 + // sMaskSampler sampler NA NA 1 1 + // tex texture float4 2d 0 1 + // mask texture float4 2d 1 1 + // cb2 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c0 cb0 4 2 ( FLT, FLT, FLT, FLT) + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // s1 s1 t1 + // + // + // Level9 shader bytecode: + // + ps_2_x + def c2, 0.5, 0, 0, 0 + dcl t0 + dcl_2d s0 + dcl_2d s1 + mul r0.w, c1.w, c1.w + add r0.xy, t0.wzzw, -c1 + dp2add r0.w, r0, r0, -r0.w + mul r0.w, r0.w, c2.x + mov r0.z, c1.w + dp3 r0.x, r0, c0 + rcp r0.x, r0.x + mul r0.x, r0.x, r0.w + mov r0.y, c2.x + texld r1, t0, s1 + texld r2, r0, s0 + mov r0.w, c1.w + mad r0.x, r0.x, -c0.z, -r0.w + mul r2.xyz, r2.w, r2 + mul r1, r1.w, r2 + cmp r0, r0.x, c2.y, r1 + mov oC0, r0 + + // approximately 18 instruction slots used (2 texture, 16 arithmetic) + ps_4_0 + dcl_constantbuffer cb0[6], immediateIndexed + dcl_sampler s0, mode_default + dcl_sampler s1, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_resource_texture2d (float,float,float,float) t1 + dcl_input_ps linear v1.xy + dcl_input_ps linear v1.zw + dcl_output o0.xyzw + dcl_temps 2 + add r0.xy, v1.zwzz, -cb0[5].xyxx + mov r0.z, cb0[5].w + dp3 r0.z, r0.xyzx, cb0[4].xyzx + dp2 r0.x, r0.xyxx, r0.xyxx + mad r0.x, -cb0[5].w, cb0[5].w, r0.x + mul r0.x, r0.x, l(0.500000) + div r0.x, r0.x, r0.z + mul r0.z, r0.x, cb0[4].z + ge r0.z, -cb0[5].w, r0.z + mov r0.y, l(0.500000) + sample r1.xyzw, r0.xyxx, t0.xyzw, s0 + if_nz r0.z + mov o0.xyzw, l(0,0,0,0) + ret + endif + mul r1.xyz, r1.wwww, r1.xyzx + sample r0.xyzw, v1.xyxx, t1.xyzw, s1 + mul o0.xyzw, r0.wwww, r1.xyzw + ret + // Approximately 19 instruction slots used + + }; + } + + pass APosWrap + { + RasterizerState = TextureRast; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 [unused] + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // cbuffer cb2 + // { + // + // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 + // float2 dimensions; // Offset: 48 Size: 8 + // float3 diff; // Offset: 64 Size: 12 [unused] + // float2 center1; // Offset: 80 Size: 8 [unused] + // float A; // Offset: 88 Size: 4 [unused] + // float radius1; // Offset: 92 Size: 4 [unused] + // float sq_radius1; // Offset: 96 Size: 4 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // cb2 cbuffer NA NA 1 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 1 ( FLT, FLT, FLT, FLT) + // c2 cb0 2 1 ( FLT, FLT, FLT, FLT) + // c3 cb1 0 2 ( FLT, FLT, FLT, FLT) + // c5 cb1 3 1 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c6, 1, 0.5, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad r0.xy, v0, c1.zwzw, c1 + add r0.z, r0.x, c6.x + mul r0.z, r0.z, c5.x + mul r1.x, r0.z, c6.y + add r0.z, -r0.y, c6.x + add oPos.xy, r0, c0 + mul r0.x, r0.z, c5.y + mul r1.y, r0.x, c6.y + mov r1.z, c6.x + dp3 oT0.w, r1, c3 + dp3 oT0.z, r1, c4 + mov oPos.zw, c6.xyzx + + // approximately 13 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_constantbuffer cb1[4], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + dcl_temps 2 + mov o0.zw, l(0,0,0,1.000000) + mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.xy, r0.xyxx + add r0.x, r0.x, l(1.000000) + add r0.y, -r0.y, l(1.000000) + mul r0.xy, r0.xyxx, cb1[3].xyxx + mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000) + mov r1.z, l(1.000000) + dp3 o1.z, r1.xyzx, cb1[0].xyzx + dp3 o1.w, r1.xyzx, cb1[1].xyzx + mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx + ret + // Approximately 12 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb2 + // { + // + // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 [unused] + // float2 dimensions; // Offset: 48 Size: 8 [unused] + // float3 diff; // Offset: 64 Size: 12 + // float2 center1; // Offset: 80 Size: 8 + // float A; // Offset: 88 Size: 4 + // float radius1; // Offset: 92 Size: 4 + // float sq_radius1; // Offset: 96 Size: 4 + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sWrapSampler sampler NA NA 0 1 + // sMaskSampler sampler NA NA 1 1 + // tex texture float4 2d 0 1 + // mask texture float4 2d 1 1 + // cb2 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c0 cb0 4 3 ( FLT, FLT, FLT, FLT) + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // s1 s1 t1 + // + // + // Level9 shader bytecode: + // + ps_2_x + def c3, 0.5, 0, 0, 0 + def c4, 1, -1, 0, -0 + dcl t0 + dcl_2d s0 + dcl_2d s1 + add r0.xy, t0.wzzw, -c1 + dp2add r0.w, r0, r0, -c2.x + mul r0.w, r0.w, c1.z + mov r0.z, c1.w + dp3 r0.x, r0, c0 + mad r0.y, r0.x, r0.x, -r0.w + abs r0.z, r0.y + rsq r0.z, r0.z + rcp r1.x, r0.z + mov r1.yz, -r1.x + add r0.xzw, r0.x, r1.xyyz + rcp r1.x, c1.z + mul r0.xzw, r0, r1.x + mov r1.w, c1.w + mad r1.xyz, r0.xzww, c0.z, r1.w + cmp r2.x, r1.x, r0.x, r0.w + cmp r0.xzw, r1.xyyz, c4.xyxy, c4.zyzw + mov r2.y, c3.x + texld r1, t0, s1 + texld r2, r2, s0 + mul r2.xyz, r2.w, r2 + mul r1, r1.w, r2 + add r0.w, r0.w, r0.x + cmp r0.x, r0.w, r0.x, r0.z + cmp r1, -r0.x, c4.z, r1 + cmp r0, r0.y, r1, c4.z + mov oC0, r0 + + // approximately 28 instruction slots used (2 texture, 26 arithmetic) + ps_4_0 + dcl_constantbuffer cb0[7], immediateIndexed + dcl_sampler s0, mode_default + dcl_sampler s1, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_resource_texture2d (float,float,float,float) t1 + dcl_input_ps linear v1.xy + dcl_input_ps linear v1.zw + dcl_output o0.xyzw + dcl_temps 3 + add r0.xy, v1.zwzz, -cb0[5].xyxx + mov r0.z, cb0[5].w + dp3 r0.z, r0.xyzx, cb0[4].xyzx + dp2 r0.x, r0.xyxx, r0.xyxx + add r0.x, r0.x, -cb0[6].x + mul r0.x, r0.x, cb0[5].z + mad r0.x, r0.z, r0.z, -r0.x + lt r0.y, r0.x, l(0.000000) + sqrt r1.x, |r0.x| + mov r1.y, -r1.x + add r0.xz, r0.zzzz, r1.xxyx + div r0.xz, r0.xxzx, cb0[5].zzzz + mul r1.xy, r0.xzxx, cb0[4].zzzz + ge r1.xy, r1.xyxx, -cb0[5].wwww + and r1.xy, r1.xyxx, l(0x3f800000, 0x3f800000, 0, 0) + add r0.x, -r0.z, r0.x + mad r2.x, r1.x, r0.x, r0.z + mov r2.y, l(0.500000) + sample r2.xyzw, r2.xyxx, t0.xyzw, s0 + if_nz r0.y + mov o0.xyzw, l(0,0,0,0) + ret + endif + max r0.x, r1.y, r1.x + ge r0.x, l(0.000000), r0.x + if_nz r0.x + mov o0.xyzw, l(0,0,0,0) + ret + endif + mul r2.xyz, r2.wwww, r2.xyzx + sample r0.xyzw, v1.xyxx, t1.xyzw, s1 + mul o0.xyzw, r0.wwww, r2.xyzw + ret + // Approximately 33 instruction slots used + + }; + } + + pass A0Wrap + { + RasterizerState = TextureRast; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 [unused] + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // cbuffer cb2 + // { + // + // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 + // float2 dimensions; // Offset: 48 Size: 8 + // float3 diff; // Offset: 64 Size: 12 [unused] + // float2 center1; // Offset: 80 Size: 8 [unused] + // float A; // Offset: 88 Size: 4 [unused] + // float radius1; // Offset: 92 Size: 4 [unused] + // float sq_radius1; // Offset: 96 Size: 4 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // cb2 cbuffer NA NA 1 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 1 ( FLT, FLT, FLT, FLT) + // c2 cb0 2 1 ( FLT, FLT, FLT, FLT) + // c3 cb1 0 2 ( FLT, FLT, FLT, FLT) + // c5 cb1 3 1 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c6, 1, 0.5, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad r0.xy, v0, c1.zwzw, c1 + add r0.z, r0.x, c6.x + mul r0.z, r0.z, c5.x + mul r1.x, r0.z, c6.y + add r0.z, -r0.y, c6.x + add oPos.xy, r0, c0 + mul r0.x, r0.z, c5.y + mul r1.y, r0.x, c6.y + mov r1.z, c6.x + dp3 oT0.w, r1, c3 + dp3 oT0.z, r1, c4 + mov oPos.zw, c6.xyzx + + // approximately 13 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_constantbuffer cb1[4], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + dcl_temps 2 + mov o0.zw, l(0,0,0,1.000000) + mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.xy, r0.xyxx + add r0.x, r0.x, l(1.000000) + add r0.y, -r0.y, l(1.000000) + mul r0.xy, r0.xyxx, cb1[3].xyxx + mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000) + mov r1.z, l(1.000000) + dp3 o1.z, r1.xyzx, cb1[0].xyzx + dp3 o1.w, r1.xyzx, cb1[1].xyzx + mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx + ret + // Approximately 12 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb2 + // { + // + // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 [unused] + // float2 dimensions; // Offset: 48 Size: 8 [unused] + // float3 diff; // Offset: 64 Size: 12 + // float2 center1; // Offset: 80 Size: 8 + // float A; // Offset: 88 Size: 4 [unused] + // float radius1; // Offset: 92 Size: 4 + // float sq_radius1; // Offset: 96 Size: 4 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sWrapSampler sampler NA NA 0 1 + // sMaskSampler sampler NA NA 1 1 + // tex texture float4 2d 0 1 + // mask texture float4 2d 1 1 + // cb2 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c0 cb0 4 2 ( FLT, FLT, FLT, FLT) + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // s1 s1 t1 + // + // + // Level9 shader bytecode: + // + ps_2_x + def c2, 0.5, 0, 0, 0 + dcl t0 + dcl_2d s0 + dcl_2d s1 + mul r0.w, c1.w, c1.w + add r0.xy, t0.wzzw, -c1 + dp2add r0.w, r0, r0, -r0.w + mul r0.w, r0.w, c2.x + mov r0.z, c1.w + dp3 r0.x, r0, c0 + rcp r0.x, r0.x + mul r0.x, r0.x, r0.w + mov r0.y, c2.x + texld r1, t0, s1 + texld r2, r0, s0 + mov r0.w, c1.w + mad r0.x, r0.x, -c0.z, -r0.w + mul r2.xyz, r2.w, r2 + mul r1, r1.w, r2 + cmp r0, r0.x, c2.y, r1 + mov oC0, r0 + + // approximately 18 instruction slots used (2 texture, 16 arithmetic) + ps_4_0 + dcl_constantbuffer cb0[6], immediateIndexed + dcl_sampler s0, mode_default + dcl_sampler s1, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_resource_texture2d (float,float,float,float) t1 + dcl_input_ps linear v1.xy + dcl_input_ps linear v1.zw + dcl_output o0.xyzw + dcl_temps 2 + add r0.xy, v1.zwzz, -cb0[5].xyxx + mov r0.z, cb0[5].w + dp3 r0.z, r0.xyzx, cb0[4].xyzx + dp2 r0.x, r0.xyxx, r0.xyxx + mad r0.x, -cb0[5].w, cb0[5].w, r0.x + mul r0.x, r0.x, l(0.500000) + div r0.x, r0.x, r0.z + mul r0.z, r0.x, cb0[4].z + ge r0.z, -cb0[5].w, r0.z + mov r0.y, l(0.500000) + sample r1.xyzw, r0.xyxx, t0.xyzw, s0 + if_nz r0.z + mov o0.xyzw, l(0,0,0,0) + ret + endif + mul r1.xyz, r1.wwww, r1.xyzx + sample r0.xyzw, v1.xyxx, t1.xyzw, s1 + mul o0.xyzw, r0.wwww, r1.xyzw + ret + // Approximately 19 instruction slots used + + }; + } + + pass APosMirror + { + RasterizerState = TextureRast; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 [unused] + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // cbuffer cb2 + // { + // + // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 + // float2 dimensions; // Offset: 48 Size: 8 + // float3 diff; // Offset: 64 Size: 12 [unused] + // float2 center1; // Offset: 80 Size: 8 [unused] + // float A; // Offset: 88 Size: 4 [unused] + // float radius1; // Offset: 92 Size: 4 [unused] + // float sq_radius1; // Offset: 96 Size: 4 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // cb2 cbuffer NA NA 1 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 1 ( FLT, FLT, FLT, FLT) + // c2 cb0 2 1 ( FLT, FLT, FLT, FLT) + // c3 cb1 0 2 ( FLT, FLT, FLT, FLT) + // c5 cb1 3 1 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c6, 1, 0.5, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad r0.xy, v0, c1.zwzw, c1 + add r0.z, r0.x, c6.x + mul r0.z, r0.z, c5.x + mul r1.x, r0.z, c6.y + add r0.z, -r0.y, c6.x + add oPos.xy, r0, c0 + mul r0.x, r0.z, c5.y + mul r1.y, r0.x, c6.y + mov r1.z, c6.x + dp3 oT0.w, r1, c3 + dp3 oT0.z, r1, c4 + mov oPos.zw, c6.xyzx + + // approximately 13 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_constantbuffer cb1[4], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + dcl_temps 2 + mov o0.zw, l(0,0,0,1.000000) + mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.xy, r0.xyxx + add r0.x, r0.x, l(1.000000) + add r0.y, -r0.y, l(1.000000) + mul r0.xy, r0.xyxx, cb1[3].xyxx + mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000) + mov r1.z, l(1.000000) + dp3 o1.z, r1.xyzx, cb1[0].xyzx + dp3 o1.w, r1.xyzx, cb1[1].xyzx + mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx + ret + // Approximately 12 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb2 + // { + // + // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 [unused] + // float2 dimensions; // Offset: 48 Size: 8 [unused] + // float3 diff; // Offset: 64 Size: 12 + // float2 center1; // Offset: 80 Size: 8 + // float A; // Offset: 88 Size: 4 + // float radius1; // Offset: 92 Size: 4 + // float sq_radius1; // Offset: 96 Size: 4 + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sMirrorSampler sampler NA NA 0 1 + // sMaskSampler sampler NA NA 1 1 + // tex texture float4 2d 0 1 + // mask texture float4 2d 1 1 + // cb2 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c0 cb0 4 3 ( FLT, FLT, FLT, FLT) + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // s1 s1 t1 + // + // + // Level9 shader bytecode: + // + ps_2_x + def c3, 0.5, 0, 0, 0 + def c4, 1, -1, 0, -0 + dcl t0 + dcl_2d s0 + dcl_2d s1 + add r0.xy, t0.wzzw, -c1 + dp2add r0.w, r0, r0, -c2.x + mul r0.w, r0.w, c1.z + mov r0.z, c1.w + dp3 r0.x, r0, c0 + mad r0.y, r0.x, r0.x, -r0.w + abs r0.z, r0.y + rsq r0.z, r0.z + rcp r1.x, r0.z + mov r1.yz, -r1.x + add r0.xzw, r0.x, r1.xyyz + rcp r1.x, c1.z + mul r0.xzw, r0, r1.x + mov r1.w, c1.w + mad r1.xyz, r0.xzww, c0.z, r1.w + cmp r2.x, r1.x, r0.x, r0.w + cmp r0.xzw, r1.xyyz, c4.xyxy, c4.zyzw + mov r2.y, c3.x + texld r1, t0, s1 + texld r2, r2, s0 + mul r2.xyz, r2.w, r2 + mul r1, r1.w, r2 + add r0.w, r0.w, r0.x + cmp r0.x, r0.w, r0.x, r0.z + cmp r1, -r0.x, c4.z, r1 + cmp r0, r0.y, r1, c4.z + mov oC0, r0 + + // approximately 28 instruction slots used (2 texture, 26 arithmetic) + ps_4_0 + dcl_constantbuffer cb0[7], immediateIndexed + dcl_sampler s0, mode_default + dcl_sampler s1, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_resource_texture2d (float,float,float,float) t1 + dcl_input_ps linear v1.xy + dcl_input_ps linear v1.zw + dcl_output o0.xyzw + dcl_temps 3 + add r0.xy, v1.zwzz, -cb0[5].xyxx + mov r0.z, cb0[5].w + dp3 r0.z, r0.xyzx, cb0[4].xyzx + dp2 r0.x, r0.xyxx, r0.xyxx + add r0.x, r0.x, -cb0[6].x + mul r0.x, r0.x, cb0[5].z + mad r0.x, r0.z, r0.z, -r0.x + lt r0.y, r0.x, l(0.000000) + sqrt r1.x, |r0.x| + mov r1.y, -r1.x + add r0.xz, r0.zzzz, r1.xxyx + div r0.xz, r0.xxzx, cb0[5].zzzz + mul r1.xy, r0.xzxx, cb0[4].zzzz + ge r1.xy, r1.xyxx, -cb0[5].wwww + and r1.xy, r1.xyxx, l(0x3f800000, 0x3f800000, 0, 0) + add r0.x, -r0.z, r0.x + mad r2.x, r1.x, r0.x, r0.z + mov r2.y, l(0.500000) + sample r2.xyzw, r2.xyxx, t0.xyzw, s0 + if_nz r0.y + mov o0.xyzw, l(0,0,0,0) + ret + endif + max r0.x, r1.y, r1.x + ge r0.x, l(0.000000), r0.x + if_nz r0.x + mov o0.xyzw, l(0,0,0,0) + ret + endif + mul r2.xyz, r2.wwww, r2.xyzx + sample r0.xyzw, v1.xyxx, t1.xyzw, s1 + mul o0.xyzw, r0.wwww, r2.xyzw + ret + // Approximately 33 instruction slots used + + }; + } + + pass A0Mirror + { + RasterizerState = TextureRast; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 [unused] + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // cbuffer cb2 + // { + // + // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 + // float2 dimensions; // Offset: 48 Size: 8 + // float3 diff; // Offset: 64 Size: 12 [unused] + // float2 center1; // Offset: 80 Size: 8 [unused] + // float A; // Offset: 88 Size: 4 [unused] + // float radius1; // Offset: 92 Size: 4 [unused] + // float sq_radius1; // Offset: 96 Size: 4 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // cb2 cbuffer NA NA 1 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 1 ( FLT, FLT, FLT, FLT) + // c2 cb0 2 1 ( FLT, FLT, FLT, FLT) + // c3 cb1 0 2 ( FLT, FLT, FLT, FLT) + // c5 cb1 3 1 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c6, 1, 0.5, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad r0.xy, v0, c1.zwzw, c1 + add r0.z, r0.x, c6.x + mul r0.z, r0.z, c5.x + mul r1.x, r0.z, c6.y + add r0.z, -r0.y, c6.x + add oPos.xy, r0, c0 + mul r0.x, r0.z, c5.y + mul r1.y, r0.x, c6.y + mov r1.z, c6.x + dp3 oT0.w, r1, c3 + dp3 oT0.z, r1, c4 + mov oPos.zw, c6.xyzx + + // approximately 13 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_constantbuffer cb1[4], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + dcl_temps 2 + mov o0.zw, l(0,0,0,1.000000) + mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.xy, r0.xyxx + add r0.x, r0.x, l(1.000000) + add r0.y, -r0.y, l(1.000000) + mul r0.xy, r0.xyxx, cb1[3].xyxx + mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000) + mov r1.z, l(1.000000) + dp3 o1.z, r1.xyzx, cb1[0].xyzx + dp3 o1.w, r1.xyzx, cb1[1].xyzx + mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx + ret + // Approximately 12 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb2 + // { + // + // float3x3 DeviceSpaceToUserSpace; // Offset: 0 Size: 44 [unused] + // float2 dimensions; // Offset: 48 Size: 8 [unused] + // float3 diff; // Offset: 64 Size: 12 + // float2 center1; // Offset: 80 Size: 8 + // float A; // Offset: 88 Size: 4 [unused] + // float radius1; // Offset: 92 Size: 4 + // float sq_radius1; // Offset: 96 Size: 4 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sMirrorSampler sampler NA NA 0 1 + // sMaskSampler sampler NA NA 1 1 + // tex texture float4 2d 0 1 + // mask texture float4 2d 1 1 + // cb2 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c0 cb0 4 2 ( FLT, FLT, FLT, FLT) + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // s1 s1 t1 + // + // + // Level9 shader bytecode: + // + ps_2_x + def c2, 0.5, 0, 0, 0 + dcl t0 + dcl_2d s0 + dcl_2d s1 + mul r0.w, c1.w, c1.w + add r0.xy, t0.wzzw, -c1 + dp2add r0.w, r0, r0, -r0.w + mul r0.w, r0.w, c2.x + mov r0.z, c1.w + dp3 r0.x, r0, c0 + rcp r0.x, r0.x + mul r0.x, r0.x, r0.w + mov r0.y, c2.x + texld r1, t0, s1 + texld r2, r0, s0 + mov r0.w, c1.w + mad r0.x, r0.x, -c0.z, -r0.w + mul r2.xyz, r2.w, r2 + mul r1, r1.w, r2 + cmp r0, r0.x, c2.y, r1 + mov oC0, r0 + + // approximately 18 instruction slots used (2 texture, 16 arithmetic) + ps_4_0 + dcl_constantbuffer cb0[6], immediateIndexed + dcl_sampler s0, mode_default + dcl_sampler s1, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_resource_texture2d (float,float,float,float) t1 + dcl_input_ps linear v1.xy + dcl_input_ps linear v1.zw + dcl_output o0.xyzw + dcl_temps 2 + add r0.xy, v1.zwzz, -cb0[5].xyxx + mov r0.z, cb0[5].w + dp3 r0.z, r0.xyzx, cb0[4].xyzx + dp2 r0.x, r0.xyxx, r0.xyxx + mad r0.x, -cb0[5].w, cb0[5].w, r0.x + mul r0.x, r0.x, l(0.500000) + div r0.x, r0.x, r0.z + mul r0.z, r0.x, cb0[4].z + ge r0.z, -cb0[5].w, r0.z + mov r0.y, l(0.500000) + sample r1.xyzw, r0.xyxx, t0.xyzw, s0 + if_nz r0.z + mov o0.xyzw, l(0,0,0,0) + ret + endif + mul r1.xyz, r1.wwww, r1.xyzx + sample r0.xyzw, v1.xyxx, t1.xyzw, s1 + mul o0.xyzw, r0.wwww, r1.xyzw + ret + // Approximately 19 instruction slots used + + }; + } + +} + +technique10 SampleConicGradient +{ + pass APos + { + RasterizerState = TextureRast; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 [unused] + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // cbuffer cb3 + // { + // + // float3x3 DeviceSpaceToUserSpace_cb3;// Offset: 0 Size: 44 + // float2 dimensions_cb3; // Offset: 48 Size: 8 + // float2 center; // Offset: 56 Size: 8 [unused] + // float angle; // Offset: 64 Size: 4 [unused] + // float start_offset; // Offset: 68 Size: 4 [unused] + // float end_offset; // Offset: 72 Size: 4 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // cb3 cbuffer NA NA 1 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 1 ( FLT, FLT, FLT, FLT) + // c2 cb0 2 1 ( FLT, FLT, FLT, FLT) + // c3 cb1 0 2 ( FLT, FLT, FLT, FLT) + // c5 cb1 3 1 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c6, 1, 0.5, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad r0.xy, v0, c1.zwzw, c1 + add r0.z, r0.x, c6.x + mul r0.z, r0.z, c5.x + mul r1.x, r0.z, c6.y + add r0.z, -r0.y, c6.x + add oPos.xy, r0, c0 + mul r0.x, r0.z, c5.y + mul r1.y, r0.x, c6.y + mov r1.z, c6.x + dp3 oT0.w, r1, c3 + dp3 oT0.z, r1, c4 + mov oPos.zw, c6.xyzx + + // approximately 13 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_constantbuffer cb1[4], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + dcl_temps 2 + mov o0.zw, l(0,0,0,1.000000) + mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.xy, r0.xyxx + add r0.x, r0.x, l(1.000000) + add r0.y, -r0.y, l(1.000000) + mul r0.xy, r0.xyxx, cb1[3].xyxx + mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000) + mov r1.z, l(1.000000) + dp3 o1.z, r1.xyzx, cb1[0].xyzx + dp3 o1.w, r1.xyzx, cb1[1].xyzx + mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx + ret + // Approximately 12 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb3 + // { + // + // float3x3 DeviceSpaceToUserSpace_cb3;// Offset: 0 Size: 44 [unused] + // float2 dimensions_cb3; // Offset: 48 Size: 8 [unused] + // float2 center; // Offset: 56 Size: 8 + // float angle; // Offset: 64 Size: 4 + // float start_offset; // Offset: 68 Size: 4 + // float end_offset; // Offset: 72 Size: 4 + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sSampler sampler NA NA 0 1 + // sMaskSampler sampler NA NA 1 1 + // tex texture float4 2d 0 1 + // mask texture float4 2d 1 1 + // cb3 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c0 cb0 3 2 ( FLT, FLT, FLT, FLT) + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // s1 s1 t1 + // + // + // Level9 shader bytecode: + // + ps_2_x + def c2, 0.0208350997, -0.0851330012, 0.180141002, -0.330299497 + def c3, 0.999866009, 0, 1, 3.14159274 + def c4, -2, 1.57079637, 1.57079601, 0.159154981 + def c5, 0.5, 0, 0, 0 + dcl t0 + dcl_2d s0 + dcl_2d s1 + add r0.xy, t0.wzzw, -c0.zwzw + abs r0.zw, r0.xyxy + add r1.xy, -r0.zwzw, r0.wzzw + cmp r0.zw, r1.x, r0, r0.xywz + cmp r1.x, r1.y, c3.y, c3.z + rcp r0.w, r0.w + mul r0.z, r0.w, r0.z + mul r0.w, r0.z, r0.z + mad r1.y, r0.w, c2.x, c2.y + mad r1.y, r0.w, r1.y, c2.z + mad r1.y, r0.w, r1.y, c2.w + mad r0.w, r0.w, r1.y, c3.x + mul r0.z, r0.w, r0.z + mad r0.w, r0.z, c4.x, c4.y + mad r0.z, r0.w, r1.x, r0.z + cmp r0.w, r0.x, -c3.y, -c3.w + add r0.z, r0.w, r0.z + add r0.w, r0.z, r0.z + add r1.x, -r0.x, r0.y + cmp r0.xy, r1.x, r0, r0.yxzw + cmp r0.y, r0.y, c3.z, c3.y + cmp r0.x, r0.x, c3.y, r0.y + mad r0.x, r0.x, -r0.w, r0.z + add r0.x, r0.x, -c1.x + add r0.x, r0.x, c4.z + mul r0.y, r0.x, c4.w + abs r0.y, r0.y + frc r0.y, r0.y + cmp r0.x, r0.x, r0.y, -r0.y + add r0.x, r0.x, -c1.y + add r0.y, -c1.y, c1.z + rcp r0.y, r0.y + mul r0.x, r0.y, r0.x + mov r0.y, c5.x + texld r1, t0, s1 + texld r0, r0, s0 + mul r0.xyz, r0.w, r0 + mul r0, r1.w, r0 + mov oC0, r0 + + // approximately 39 instruction slots used (2 texture, 37 arithmetic) + ps_4_0 + dcl_constantbuffer cb0[5], immediateIndexed + dcl_sampler s0, mode_default + dcl_sampler s1, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_resource_texture2d (float,float,float,float) t1 + dcl_input_ps linear v1.xy + dcl_input_ps linear v1.zw + dcl_output o0.xyzw + dcl_temps 2 + add r0.xy, v1.wzww, -cb0[3].wzww + max r0.z, |r0.y|, |r0.x| + div r0.z, l(1.000000, 1.000000, 1.000000, 1.000000), r0.z + min r0.w, |r0.y|, |r0.x| + mul r0.z, r0.z, r0.w + mul r0.w, r0.z, r0.z + mad r1.x, r0.w, l(0.020835), l(-0.085133) + mad r1.x, r0.w, r1.x, l(0.180141) + mad r1.x, r0.w, r1.x, l(-0.330299) + mad r0.w, r0.w, r1.x, l(0.999866) + mul r1.x, r0.w, r0.z + mad r1.x, r1.x, l(-2.000000), l(1.570796) + lt r1.y, |r0.y|, |r0.x| + and r1.x, r1.y, r1.x + mad r0.z, r0.z, r0.w, r1.x + lt r0.w, r0.y, -r0.y + and r0.w, r0.w, l(0xc0490fdb) + add r0.z, r0.w, r0.z + min r0.w, r0.y, r0.x + max r0.x, r0.y, r0.x + ge r0.x, r0.x, -r0.x + lt r0.y, r0.w, -r0.w + and r0.x, r0.x, r0.y + movc r0.x, r0.x, -r0.z, r0.z + add r0.x, r0.x, -cb0[4].x + add r0.x, r0.x, l(1.570796) + mul r0.x, r0.x, l(0.159155) + ge r0.y, r0.x, -r0.x + frc r0.x, |r0.x| + movc r0.x, r0.y, r0.x, -r0.x + add r0.x, r0.x, -cb0[4].y + add r0.y, -cb0[4].y, cb0[4].z + div r0.x, r0.x, r0.y + mov r0.y, l(0.500000) + sample r0.xyzw, r0.xyxx, t0.xyzw, s0 + mul r0.xyz, r0.wwww, r0.xyzx + sample r1.xyzw, v1.xyxx, t1.xyzw, s1 + mul o0.xyzw, r0.xyzw, r1.wwww + ret + // Approximately 39 instruction slots used + + }; + } + + pass APosWrap + { + RasterizerState = TextureRast; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 [unused] + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // cbuffer cb3 + // { + // + // float3x3 DeviceSpaceToUserSpace_cb3;// Offset: 0 Size: 44 + // float2 dimensions_cb3; // Offset: 48 Size: 8 + // float2 center; // Offset: 56 Size: 8 [unused] + // float angle; // Offset: 64 Size: 4 [unused] + // float start_offset; // Offset: 68 Size: 4 [unused] + // float end_offset; // Offset: 72 Size: 4 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // cb3 cbuffer NA NA 1 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 1 ( FLT, FLT, FLT, FLT) + // c2 cb0 2 1 ( FLT, FLT, FLT, FLT) + // c3 cb1 0 2 ( FLT, FLT, FLT, FLT) + // c5 cb1 3 1 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c6, 1, 0.5, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad r0.xy, v0, c1.zwzw, c1 + add r0.z, r0.x, c6.x + mul r0.z, r0.z, c5.x + mul r1.x, r0.z, c6.y + add r0.z, -r0.y, c6.x + add oPos.xy, r0, c0 + mul r0.x, r0.z, c5.y + mul r1.y, r0.x, c6.y + mov r1.z, c6.x + dp3 oT0.w, r1, c3 + dp3 oT0.z, r1, c4 + mov oPos.zw, c6.xyzx + + // approximately 13 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_constantbuffer cb1[4], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + dcl_temps 2 + mov o0.zw, l(0,0,0,1.000000) + mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.xy, r0.xyxx + add r0.x, r0.x, l(1.000000) + add r0.y, -r0.y, l(1.000000) + mul r0.xy, r0.xyxx, cb1[3].xyxx + mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000) + mov r1.z, l(1.000000) + dp3 o1.z, r1.xyzx, cb1[0].xyzx + dp3 o1.w, r1.xyzx, cb1[1].xyzx + mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx + ret + // Approximately 12 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb3 + // { + // + // float3x3 DeviceSpaceToUserSpace_cb3;// Offset: 0 Size: 44 [unused] + // float2 dimensions_cb3; // Offset: 48 Size: 8 [unused] + // float2 center; // Offset: 56 Size: 8 + // float angle; // Offset: 64 Size: 4 + // float start_offset; // Offset: 68 Size: 4 + // float end_offset; // Offset: 72 Size: 4 + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sWrapSampler sampler NA NA 0 1 + // sMaskSampler sampler NA NA 1 1 + // tex texture float4 2d 0 1 + // mask texture float4 2d 1 1 + // cb3 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c0 cb0 3 2 ( FLT, FLT, FLT, FLT) + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // s1 s1 t1 + // + // + // Level9 shader bytecode: + // + ps_2_x + def c2, 0.0208350997, -0.0851330012, 0.180141002, -0.330299497 + def c3, 0.999866009, 0, 1, 3.14159274 + def c4, -2, 1.57079637, 1.57079601, 0.159154981 + def c5, 0.5, 0, 0, 0 + dcl t0 + dcl_2d s0 + dcl_2d s1 + add r0.xy, t0.wzzw, -c0.zwzw + abs r0.zw, r0.xyxy + add r1.xy, -r0.zwzw, r0.wzzw + cmp r0.zw, r1.x, r0, r0.xywz + cmp r1.x, r1.y, c3.y, c3.z + rcp r0.w, r0.w + mul r0.z, r0.w, r0.z + mul r0.w, r0.z, r0.z + mad r1.y, r0.w, c2.x, c2.y + mad r1.y, r0.w, r1.y, c2.z + mad r1.y, r0.w, r1.y, c2.w + mad r0.w, r0.w, r1.y, c3.x + mul r0.z, r0.w, r0.z + mad r0.w, r0.z, c4.x, c4.y + mad r0.z, r0.w, r1.x, r0.z + cmp r0.w, r0.x, -c3.y, -c3.w + add r0.z, r0.w, r0.z + add r0.w, r0.z, r0.z + add r1.x, -r0.x, r0.y + cmp r0.xy, r1.x, r0, r0.yxzw + cmp r0.y, r0.y, c3.z, c3.y + cmp r0.x, r0.x, c3.y, r0.y + mad r0.x, r0.x, -r0.w, r0.z + add r0.x, r0.x, -c1.x + add r0.x, r0.x, c4.z + mul r0.y, r0.x, c4.w + abs r0.y, r0.y + frc r0.y, r0.y + cmp r0.x, r0.x, r0.y, -r0.y + add r0.x, r0.x, -c1.y + add r0.y, -c1.y, c1.z + rcp r0.y, r0.y + mul r0.x, r0.y, r0.x + mov r0.y, c5.x + texld r1, t0, s1 + texld r0, r0, s0 + mul r0.xyz, r0.w, r0 + mul r0, r1.w, r0 + mov oC0, r0 + + // approximately 39 instruction slots used (2 texture, 37 arithmetic) + ps_4_0 + dcl_constantbuffer cb0[5], immediateIndexed + dcl_sampler s0, mode_default + dcl_sampler s1, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_resource_texture2d (float,float,float,float) t1 + dcl_input_ps linear v1.xy + dcl_input_ps linear v1.zw + dcl_output o0.xyzw + dcl_temps 2 + add r0.xy, v1.wzww, -cb0[3].wzww + max r0.z, |r0.y|, |r0.x| + div r0.z, l(1.000000, 1.000000, 1.000000, 1.000000), r0.z + min r0.w, |r0.y|, |r0.x| + mul r0.z, r0.z, r0.w + mul r0.w, r0.z, r0.z + mad r1.x, r0.w, l(0.020835), l(-0.085133) + mad r1.x, r0.w, r1.x, l(0.180141) + mad r1.x, r0.w, r1.x, l(-0.330299) + mad r0.w, r0.w, r1.x, l(0.999866) + mul r1.x, r0.w, r0.z + mad r1.x, r1.x, l(-2.000000), l(1.570796) + lt r1.y, |r0.y|, |r0.x| + and r1.x, r1.y, r1.x + mad r0.z, r0.z, r0.w, r1.x + lt r0.w, r0.y, -r0.y + and r0.w, r0.w, l(0xc0490fdb) + add r0.z, r0.w, r0.z + min r0.w, r0.y, r0.x + max r0.x, r0.y, r0.x + ge r0.x, r0.x, -r0.x + lt r0.y, r0.w, -r0.w + and r0.x, r0.x, r0.y + movc r0.x, r0.x, -r0.z, r0.z + add r0.x, r0.x, -cb0[4].x + add r0.x, r0.x, l(1.570796) + mul r0.x, r0.x, l(0.159155) + ge r0.y, r0.x, -r0.x + frc r0.x, |r0.x| + movc r0.x, r0.y, r0.x, -r0.x + add r0.x, r0.x, -cb0[4].y + add r0.y, -cb0[4].y, cb0[4].z + div r0.x, r0.x, r0.y + mov r0.y, l(0.500000) + sample r0.xyzw, r0.xyxx, t0.xyzw, s0 + mul r0.xyz, r0.wwww, r0.xyzx + sample r1.xyzw, v1.xyxx, t1.xyzw, s1 + mul o0.xyzw, r0.xyzw, r1.wwww + ret + // Approximately 39 instruction slots used + + }; + } + + pass APosMirror + { + RasterizerState = TextureRast; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 [unused] + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // cbuffer cb3 + // { + // + // float3x3 DeviceSpaceToUserSpace_cb3;// Offset: 0 Size: 44 + // float2 dimensions_cb3; // Offset: 48 Size: 8 + // float2 center; // Offset: 56 Size: 8 [unused] + // float angle; // Offset: 64 Size: 4 [unused] + // float start_offset; // Offset: 68 Size: 4 [unused] + // float end_offset; // Offset: 72 Size: 4 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // cb3 cbuffer NA NA 1 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 1 ( FLT, FLT, FLT, FLT) + // c2 cb0 2 1 ( FLT, FLT, FLT, FLT) + // c3 cb1 0 2 ( FLT, FLT, FLT, FLT) + // c5 cb1 3 1 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c6, 1, 0.5, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad r0.xy, v0, c1.zwzw, c1 + add r0.z, r0.x, c6.x + mul r0.z, r0.z, c5.x + mul r1.x, r0.z, c6.y + add r0.z, -r0.y, c6.x + add oPos.xy, r0, c0 + mul r0.x, r0.z, c5.y + mul r1.y, r0.x, c6.y + mov r1.z, c6.x + dp3 oT0.w, r1, c3 + dp3 oT0.z, r1, c4 + mov oPos.zw, c6.xyzx + + // approximately 13 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_constantbuffer cb1[4], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + dcl_temps 2 + mov o0.zw, l(0,0,0,1.000000) + mad r0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.xy, r0.xyxx + add r0.x, r0.x, l(1.000000) + add r0.y, -r0.y, l(1.000000) + mul r0.xy, r0.xyxx, cb1[3].xyxx + mul r1.xy, r0.xyxx, l(0.500000, 0.500000, 0.000000, 0.000000) + mov r1.z, l(1.000000) + dp3 o1.z, r1.xyzx, cb1[0].xyzx + dp3 o1.w, r1.xyzx, cb1[1].xyzx + mad o1.xy, v0.xyxx, cb0[2].zwzz, cb0[2].xyxx + ret + // Approximately 12 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb3 + // { + // + // float3x3 DeviceSpaceToUserSpace_cb3;// Offset: 0 Size: 44 [unused] + // float2 dimensions_cb3; // Offset: 48 Size: 8 [unused] + // float2 center; // Offset: 56 Size: 8 + // float angle; // Offset: 64 Size: 4 + // float start_offset; // Offset: 68 Size: 4 + // float end_offset; // Offset: 72 Size: 4 + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sMirrorSampler sampler NA NA 0 1 + // sMaskSampler sampler NA NA 1 1 + // tex texture float4 2d 0 1 + // mask texture float4 2d 1 1 + // cb3 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c0 cb0 3 2 ( FLT, FLT, FLT, FLT) + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // s1 s1 t1 + // + // + // Level9 shader bytecode: + // + ps_2_x + def c2, 0.0208350997, -0.0851330012, 0.180141002, -0.330299497 + def c3, 0.999866009, 0, 1, 3.14159274 + def c4, -2, 1.57079637, 1.57079601, 0.159154981 + def c5, 0.5, 0, 0, 0 + dcl t0 + dcl_2d s0 + dcl_2d s1 + add r0.xy, t0.wzzw, -c0.zwzw + abs r0.zw, r0.xyxy + add r1.xy, -r0.zwzw, r0.wzzw + cmp r0.zw, r1.x, r0, r0.xywz + cmp r1.x, r1.y, c3.y, c3.z + rcp r0.w, r0.w + mul r0.z, r0.w, r0.z + mul r0.w, r0.z, r0.z + mad r1.y, r0.w, c2.x, c2.y + mad r1.y, r0.w, r1.y, c2.z + mad r1.y, r0.w, r1.y, c2.w + mad r0.w, r0.w, r1.y, c3.x + mul r0.z, r0.w, r0.z + mad r0.w, r0.z, c4.x, c4.y + mad r0.z, r0.w, r1.x, r0.z + cmp r0.w, r0.x, -c3.y, -c3.w + add r0.z, r0.w, r0.z + add r0.w, r0.z, r0.z + add r1.x, -r0.x, r0.y + cmp r0.xy, r1.x, r0, r0.yxzw + cmp r0.y, r0.y, c3.z, c3.y + cmp r0.x, r0.x, c3.y, r0.y + mad r0.x, r0.x, -r0.w, r0.z + add r0.x, r0.x, -c1.x + add r0.x, r0.x, c4.z + mul r0.y, r0.x, c4.w + abs r0.y, r0.y + frc r0.y, r0.y + cmp r0.x, r0.x, r0.y, -r0.y + add r0.x, r0.x, -c1.y + add r0.y, -c1.y, c1.z + rcp r0.y, r0.y + mul r0.x, r0.y, r0.x + mov r0.y, c5.x + texld r1, t0, s1 + texld r0, r0, s0 + mul r0.xyz, r0.w, r0 + mul r0, r1.w, r0 + mov oC0, r0 + + // approximately 39 instruction slots used (2 texture, 37 arithmetic) + ps_4_0 + dcl_constantbuffer cb0[5], immediateIndexed + dcl_sampler s0, mode_default + dcl_sampler s1, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_resource_texture2d (float,float,float,float) t1 + dcl_input_ps linear v1.xy + dcl_input_ps linear v1.zw + dcl_output o0.xyzw + dcl_temps 2 + add r0.xy, v1.wzww, -cb0[3].wzww + max r0.z, |r0.y|, |r0.x| + div r0.z, l(1.000000, 1.000000, 1.000000, 1.000000), r0.z + min r0.w, |r0.y|, |r0.x| + mul r0.z, r0.z, r0.w + mul r0.w, r0.z, r0.z + mad r1.x, r0.w, l(0.020835), l(-0.085133) + mad r1.x, r0.w, r1.x, l(0.180141) + mad r1.x, r0.w, r1.x, l(-0.330299) + mad r0.w, r0.w, r1.x, l(0.999866) + mul r1.x, r0.w, r0.z + mad r1.x, r1.x, l(-2.000000), l(1.570796) + lt r1.y, |r0.y|, |r0.x| + and r1.x, r1.y, r1.x + mad r0.z, r0.z, r0.w, r1.x + lt r0.w, r0.y, -r0.y + and r0.w, r0.w, l(0xc0490fdb) + add r0.z, r0.w, r0.z + min r0.w, r0.y, r0.x + max r0.x, r0.y, r0.x + ge r0.x, r0.x, -r0.x + lt r0.y, r0.w, -r0.w + and r0.x, r0.x, r0.y + movc r0.x, r0.x, -r0.z, r0.z + add r0.x, r0.x, -cb0[4].x + add r0.x, r0.x, l(1.570796) + mul r0.x, r0.x, l(0.159155) + ge r0.y, r0.x, -r0.x + frc r0.x, |r0.x| + movc r0.x, r0.y, r0.x, -r0.x + add r0.x, r0.x, -cb0[4].y + add r0.y, -cb0[4].y, cb0[4].z + div r0.x, r0.x, r0.y + mov r0.y, l(0.500000) + sample r0.xyzw, r0.xyxx, t0.xyzw, s0 + mul r0.xyz, r0.wwww, r0.xyzx + sample r1.xyzw, v1.xyxx, t1.xyzw, s1 + mul o0.xyzw, r0.xyzw, r1.wwww + ret + // Approximately 39 instruction slots used + + }; + } + +} + +technique10 SampleMaskedTexture +{ + pass P0 + { + RasterizerState = TextureRast; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 3 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c4, 0, 1, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx + mad r0.xy, v0, c1.zwzw, c1 + add oPos.xy, r0, c0 + mov oPos.zw, c4.xyxy + + // approximately 5 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.zw, l(0,0,0,1.000000) + mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx + mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy + ret + // Approximately 5 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sSampler sampler NA NA 0 1 + // sMaskSampler sampler NA NA 1 1 + // tex texture float4 2d 0 1 + // mask texture float4 2d 1 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // s1 s1 t1 + // + // + // Level9 shader bytecode: + // + ps_2_x + dcl t0 + dcl_2d s0 + dcl_2d s1 + mov r0.xy, t0.wzzw + texld r1, t0, s0 + texld r0, r0, s1 + mul r0, r0.w, r1 + mov oC0, r0 + + // approximately 5 instruction slots used (2 texture, 3 arithmetic) + ps_4_0 + dcl_sampler s0, mode_default + dcl_sampler s1, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_resource_texture2d (float,float,float,float) t1 + dcl_input_ps linear v1.xy + dcl_input_ps linear v1.zw + dcl_output o0.xyzw + dcl_temps 2 + sample r0.xyzw, v1.xyxx, t0.xyzw, s0 + sample r1.xyzw, v1.zwzz, t1.xyzw, s1 + mul o0.xyzw, r0.xyzw, r1.wwww + ret + // Approximately 4 instruction slots used + + }; + } + +} + +technique10 SampleTextureWithShadow +{ + pass P0 + { + RasterizerState = TextureRast; + AB_BlendFactor = float4(1, 1, 1, 1); + AB_SampleMask = uint(0xffffffff); + BlendState = ShadowBlendH; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 3 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c4, 0, 1, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx + mad r0.xy, v0, c1.zwzw, c1 + add oPos.xy, r0, c0 + mov oPos.zw, c4.xyxy + + // approximately 5 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.zw, l(0,0,0,1.000000) + mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx + mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy + ret + // Approximately 5 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb1 + // { + // + // float4 BlurOffsetsH[3]; // Offset: 0 Size: 48 + // float4 BlurOffsetsV[3]; // Offset: 48 Size: 48 [unused] + // float4 BlurWeights[3]; // Offset: 96 Size: 48 + // float4 ShadowColor; // Offset: 144 Size: 16 + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sShadowSampler sampler NA NA 0 1 + // tex texture float4 2d 0 1 + // cb1 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c0 cb0 0 3 ( FLT, FLT, FLT, FLT) + // c3 cb0 6 4 ( FLT, FLT, FLT, FLT) + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // + // + // Level9 shader bytecode: + // + ps_2_x + dcl t0 + dcl_2d s0 + add r0.x, t0.x, c0.y + mov r0.y, t0.y + add r1.x, t0.x, c0.x + mov r1.y, t0.y + texld r0, r0, s0 + texld r1, r1, s0 + mul r0.x, r0.w, c3.y + mad r0.x, c3.x, r1.w, r0.x + add r1.x, t0.x, c0.z + mov r1.y, t0.y + add r2.x, t0.x, c0.w + mov r2.y, t0.y + texld r1, r1, s0 + texld r2, r2, s0 + mad r0.x, c3.z, r1.w, r0.x + mad r0.x, c3.w, r2.w, r0.x + add r1.x, t0.x, c1.x + mov r1.y, t0.y + add r2.x, t0.x, c1.y + mov r2.y, t0.y + texld r1, r1, s0 + texld r2, r2, s0 + mad r0.x, c4.x, r1.w, r0.x + mad r0.x, c4.y, r2.w, r0.x + add r1.x, t0.x, c1.z + mov r1.y, t0.y + add r2.x, t0.x, c1.w + mov r2.y, t0.y + texld r1, r1, s0 + texld r2, r2, s0 + mad r0.x, c4.z, r1.w, r0.x + mad r0.x, c4.w, r2.w, r0.x + add r1.x, t0.x, c2.x + mov r1.y, t0.y + texld r1, r1, s0 + mad r0.x, c5.x, r1.w, r0.x + mul r0, r0.x, c6 + mov oC0, r0 + + // approximately 38 instruction slots used (9 texture, 29 arithmetic) + ps_4_0 + dcl_constantbuffer cb0[10], immediateIndexed + dcl_sampler s0, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_input_ps linear v1.xy + dcl_output o0.xyzw + dcl_temps 4 + add r0.xyzw, v1.xxxx, cb0[0].zxwy + mov r1.xz, r0.yywy + mov r1.yw, v1.yyyy + sample r2.xyzw, r1.zwzz, t0.xyzw, s0 + sample r1.xyzw, r1.xyxx, t0.xyzw, s0 + mul r1.x, r2.w, cb0[6].y + mad r1.x, cb0[6].x, r1.w, r1.x + mov r0.yw, v1.yyyy + sample r2.xyzw, r0.xyxx, t0.xyzw, s0 + sample r0.xyzw, r0.zwzz, t0.xyzw, s0 + mad r0.x, cb0[6].z, r2.w, r1.x + mad r0.x, cb0[6].w, r0.w, r0.x + add r1.xyzw, v1.xxxx, cb0[1].zxwy + mov r2.xz, r1.yywy + mov r2.yw, v1.yyyy + sample r3.xyzw, r2.xyxx, t0.xyzw, s0 + sample r2.xyzw, r2.zwzz, t0.xyzw, s0 + mad r0.x, cb0[7].x, r3.w, r0.x + mad r0.x, cb0[7].y, r2.w, r0.x + mov r1.yw, v1.yyyy + sample r2.xyzw, r1.xyxx, t0.xyzw, s0 + sample r1.xyzw, r1.zwzz, t0.xyzw, s0 + mad r0.x, cb0[7].z, r2.w, r0.x + mad r0.x, cb0[7].w, r1.w, r0.x + add r1.x, v1.x, cb0[2].x + mov r1.y, v1.y + sample r1.xyzw, r1.xyxx, t0.xyzw, s0 + mad r0.x, cb0[8].x, r1.w, r0.x + mul o0.xyzw, r0.xxxx, cb0[9].xyzw + ret + // Approximately 30 instruction slots used + + }; + } + + pass P1 + { + RasterizerState = TextureRast; + AB_BlendFactor = float4(1, 1, 1, 1); + AB_SampleMask = uint(0xffffffff); + BlendState = ShadowBlendV; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 3 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c4, 0, 1, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx + mad r0.xy, v0, c1.zwzw, c1 + add oPos.xy, r0, c0 + mov oPos.zw, c4.xyxy + + // approximately 5 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.zw, l(0,0,0,1.000000) + mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx + mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy + ret + // Approximately 5 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb1 + // { + // + // float4 BlurOffsetsH[3]; // Offset: 0 Size: 48 [unused] + // float4 BlurOffsetsV[3]; // Offset: 48 Size: 48 + // float4 BlurWeights[3]; // Offset: 96 Size: 48 + // float4 ShadowColor; // Offset: 144 Size: 16 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sShadowSampler sampler NA NA 0 1 + // tex texture float4 2d 0 1 + // cb1 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c0 cb0 3 6 ( FLT, FLT, FLT, FLT) + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // + // + // Level9 shader bytecode: + // + ps_2_x + dcl t0 + dcl_2d s0 + add r0.y, t0.y, c0.y + mov r0.x, t0.x + add r1.y, t0.y, c0.x + mov r1.x, t0.x + texld r0, r0, s0 + texld r1, r1, s0 + mul r0, r0, c3.y + mad r0, c3.x, r1, r0 + add r1.y, t0.y, c0.z + mov r1.x, t0.x + add r2.y, t0.y, c0.w + mov r2.x, t0.x + texld r1, r1, s0 + texld r2, r2, s0 + mad r0, c3.z, r1, r0 + mad r0, c3.w, r2, r0 + add r1.y, t0.y, c1.x + mov r1.x, t0.x + add r2.y, t0.y, c1.y + mov r2.x, t0.x + texld r1, r1, s0 + texld r2, r2, s0 + mad r0, c4.x, r1, r0 + mad r0, c4.y, r2, r0 + add r1.y, t0.y, c1.z + mov r1.x, t0.x + add r2.y, t0.y, c1.w + mov r2.x, t0.x + texld r1, r1, s0 + texld r2, r2, s0 + mad r0, c4.z, r1, r0 + mad r0, c4.w, r2, r0 + add r1.y, t0.y, c2.x + mov r1.x, t0.x + texld r1, r1, s0 + mad r0, c5.x, r1, r0 + mov oC0, r0 + + // approximately 37 instruction slots used (9 texture, 28 arithmetic) + ps_4_0 + dcl_constantbuffer cb0[9], immediateIndexed + dcl_sampler s0, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_input_ps linear v1.xy + dcl_output o0.xyzw + dcl_temps 4 + mov r0.xz, v1.xxxx + add r1.xyzw, v1.yyyy, cb0[3].xzyw + mov r0.yw, r1.xxxz + sample r2.xyzw, r0.zwzz, t0.xyzw, s0 + sample r0.xyzw, r0.xyxx, t0.xyzw, s0 + mul r2.xyzw, r2.xyzw, cb0[6].yyyy + mad r0.xyzw, cb0[6].xxxx, r0.xyzw, r2.xyzw + mov r1.xz, v1.xxxx + sample r2.xyzw, r1.xyxx, t0.xyzw, s0 + sample r1.xyzw, r1.zwzz, t0.xyzw, s0 + mad r0.xyzw, cb0[6].zzzz, r2.xyzw, r0.xyzw + mad r0.xyzw, cb0[6].wwww, r1.xyzw, r0.xyzw + mov r1.xz, v1.xxxx + add r2.xyzw, v1.yyyy, cb0[4].xzyw + mov r1.yw, r2.xxxz + sample r3.xyzw, r1.xyxx, t0.xyzw, s0 + sample r1.xyzw, r1.zwzz, t0.xyzw, s0 + mad r0.xyzw, cb0[7].xxxx, r3.xyzw, r0.xyzw + mad r0.xyzw, cb0[7].yyyy, r1.xyzw, r0.xyzw + mov r2.xz, v1.xxxx + sample r1.xyzw, r2.xyxx, t0.xyzw, s0 + sample r2.xyzw, r2.zwzz, t0.xyzw, s0 + mad r0.xyzw, cb0[7].zzzz, r1.xyzw, r0.xyzw + mad r0.xyzw, cb0[7].wwww, r2.xyzw, r0.xyzw + add r1.y, v1.y, cb0[5].x + mov r1.x, v1.x + sample r1.xyzw, r1.xyxx, t0.xyzw, s0 + mad o0.xyzw, cb0[8].xxxx, r1.xyzw, r0.xyzw + ret + // Approximately 29 instruction slots used + + }; + } + + pass P2 + { + RasterizerState = TextureRast; + AB_BlendFactor = float4(1, 1, 1, 1); + AB_SampleMask = uint(0xffffffff); + BlendState = ShadowBlendV; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 3 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c4, 0, 1, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx + mad r0.xy, v0, c1.zwzw, c1 + add oPos.xy, r0, c0 + mov oPos.zw, c4.xyxy + + // approximately 5 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.zw, l(0,0,0,1.000000) + mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx + mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy + ret + // Approximately 5 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb1 + // { + // + // float4 BlurOffsetsH[3]; // Offset: 0 Size: 48 [unused] + // float4 BlurOffsetsV[3]; // Offset: 48 Size: 48 + // float4 BlurWeights[3]; // Offset: 96 Size: 48 + // float4 ShadowColor; // Offset: 144 Size: 16 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sMaskSampler sampler NA NA 0 1 + // sShadowSampler sampler NA NA 1 1 + // tex texture float4 2d 0 1 + // mask texture float4 2d 1 1 + // cb1 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c0 cb0 3 6 ( FLT, FLT, FLT, FLT) + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t1 + // s1 s1 t0 + // + // + // Level9 shader bytecode: + // + ps_2_x + dcl t0 + dcl_2d s0 + dcl_2d s1 + add r0.y, t0.y, c0.y + mov r0.x, t0.x + add r1.y, t0.y, c0.x + mov r1.x, t0.x + texld r0, r0, s1 + texld r1, r1, s1 + mul r0, r0, c3.y + mad r0, c3.x, r1, r0 + add r1.y, t0.y, c0.z + mov r1.x, t0.x + add r2.y, t0.y, c0.w + mov r2.x, t0.x + texld r1, r1, s1 + texld r2, r2, s1 + mad r0, c3.z, r1, r0 + mad r0, c3.w, r2, r0 + add r1.y, t0.y, c1.x + mov r1.x, t0.x + add r2.y, t0.y, c1.y + mov r2.x, t0.x + texld r1, r1, s1 + texld r2, r2, s1 + mad r0, c4.x, r1, r0 + mad r0, c4.y, r2, r0 + add r1.y, t0.y, c1.z + mov r1.x, t0.x + add r2.y, t0.y, c1.w + mov r2.x, t0.x + texld r1, r1, s1 + texld r2, r2, s1 + mad r0, c4.z, r1, r0 + mad r0, c4.w, r2, r0 + add r1.y, t0.y, c2.x + mov r1.x, t0.x + mov r2.xy, t0.wzzw + texld r1, r1, s1 + texld r2, r2, s0 + mad r0, c5.x, r1, r0 + mul r0, r2.w, r0 + mov oC0, r0 + + // approximately 40 instruction slots used (10 texture, 30 arithmetic) + ps_4_0 + dcl_constantbuffer cb0[9], immediateIndexed + dcl_sampler s0, mode_default + dcl_sampler s1, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_resource_texture2d (float,float,float,float) t1 + dcl_input_ps linear v1.xy + dcl_input_ps linear v1.zw + dcl_output o0.xyzw + dcl_temps 4 + mov r0.xz, v1.xxxx + add r1.xyzw, v1.yyyy, cb0[3].xzyw + mov r0.yw, r1.xxxz + sample r2.xyzw, r0.zwzz, t0.xyzw, s1 + sample r0.xyzw, r0.xyxx, t0.xyzw, s1 + mul r2.xyzw, r2.xyzw, cb0[6].yyyy + mad r0.xyzw, cb0[6].xxxx, r0.xyzw, r2.xyzw + mov r1.xz, v1.xxxx + sample r2.xyzw, r1.xyxx, t0.xyzw, s1 + sample r1.xyzw, r1.zwzz, t0.xyzw, s1 + mad r0.xyzw, cb0[6].zzzz, r2.xyzw, r0.xyzw + mad r0.xyzw, cb0[6].wwww, r1.xyzw, r0.xyzw + mov r1.xz, v1.xxxx + add r2.xyzw, v1.yyyy, cb0[4].xzyw + mov r1.yw, r2.xxxz + sample r3.xyzw, r1.xyxx, t0.xyzw, s1 + sample r1.xyzw, r1.zwzz, t0.xyzw, s1 + mad r0.xyzw, cb0[7].xxxx, r3.xyzw, r0.xyzw + mad r0.xyzw, cb0[7].yyyy, r1.xyzw, r0.xyzw + mov r2.xz, v1.xxxx + sample r1.xyzw, r2.xyxx, t0.xyzw, s1 + sample r2.xyzw, r2.zwzz, t0.xyzw, s1 + mad r0.xyzw, cb0[7].zzzz, r1.xyzw, r0.xyzw + mad r0.xyzw, cb0[7].wwww, r2.xyzw, r0.xyzw + add r1.y, v1.y, cb0[5].x + mov r1.x, v1.x + sample r1.xyzw, r1.xyxx, t0.xyzw, s1 + mad r0.xyzw, cb0[8].xxxx, r1.xyzw, r0.xyzw + sample r1.xyzw, v1.zwzz, t1.xyzw, s0 + mul o0.xyzw, r0.xyzw, r1.wwww + ret + // Approximately 31 instruction slots used + + }; + } + +} + +technique10 SampleTextTexture +{ + pass Unmasked + { + RasterizerState = TextureRast; + AB_BlendFactor = float4(0, 0, 0, 0); + AB_SampleMask = uint(0xffffffff); + BlendState = bTextBlend; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 3 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c4, 0, 1, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx + mad r0.xy, v0, c1.zwzw, c1 + add oPos.xy, r0, c0 + mov oPos.zw, c4.xyxy + + // approximately 5 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.zw, l(0,0,0,1.000000) + mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx + mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy + ret + // Approximately 5 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 [unused] + // float4 TexCoords; // Offset: 16 Size: 16 [unused] + // float4 MaskTexCoords; // Offset: 32 Size: 16 [unused] + // float4 TextColor; // Offset: 48 Size: 16 + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sSampler sampler NA NA 0 1 + // tex texture float4 2d 0 1 + // cb0 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // SV_Target 1 xyzw 1 TARGET float xyzw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c0 cb0 3 1 ( FLT, FLT, FLT, FLT) + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // + // + // Level9 shader bytecode: + // + ps_2_x + def c1, 1, 0, 0, 0 + dcl t0 + dcl_2d s0 + mov r0.xyz, c0 + mad r0, r0.xyzx, c1.xxxy, c1.yyyx + mov oC0, r0 + texld r0, t0, s0 + mul r0, r0.zyxy, c0.w + mov oC1, r0 + + // approximately 6 instruction slots used (1 texture, 5 arithmetic) + ps_4_0 + dcl_constantbuffer cb0[4], immediateIndexed + dcl_sampler s0, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_input_ps linear v1.xy + dcl_output o0.xyzw + dcl_output o1.xyzw + dcl_temps 1 + mov o0.xyz, cb0[3].xyzx + mov o0.w, l(1.000000) + sample r0.xyzw, v1.xyxx, t0.xyzw, s0 + mul o1.xyzw, r0.zyxy, cb0[3].wwww + ret + // Approximately 5 instruction slots used + + }; + } + + pass Masked + { + RasterizerState = TextureRast; + AB_BlendFactor = float4(0, 0, 0, 0); + AB_SampleMask = uint(0xffffffff); + BlendState = bTextBlend; + VertexShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 + // float4 TexCoords; // Offset: 16 Size: 16 + // float4 MaskTexCoords; // Offset: 32 Size: 16 + // float4 TextColor; // Offset: 48 Size: 16 [unused] + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // cb0 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // POSITION 0 xyz 0 NONE float xy + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float xyzw + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c1 cb0 0 3 ( FLT, FLT, FLT, FLT) + // + // + // Runtime generated constant mappings: + // + // Target Reg Constant Description + // ---------- -------------------------------------------------- + // c0 Vertex Shader position offset + // + // + // Level9 shader bytecode: + // + vs_2_x + def c4, 0, 1, 0, 0 + dcl_texcoord v0 + mad oT0.xy, v0, c2.zwzw, c2 + mad oT0.zw, v0.xyyx, c3.xywz, c3.xyyx + mad r0.xy, v0, c1.zwzw, c1 + add oPos.xy, r0, c0 + mov oPos.zw, c4.xyxy + + // approximately 5 instruction slots used + vs_4_0 + dcl_constantbuffer cb0[3], immediateIndexed + dcl_input v0.xy + dcl_output_siv o0.xyzw, position + dcl_output o1.xy + dcl_output o1.zw + mad o0.xy, v0.xyxx, cb0[0].zwzz, cb0[0].xyxx + mov o0.zw, l(0,0,0,1.000000) + mad o1.xy, v0.xyxx, cb0[1].zwzz, cb0[1].xyxx + mad o1.zw, v0.xxxy, cb0[2].zzzw, cb0[2].xxxy + ret + // Approximately 5 instruction slots used + + }; + GeometryShader = NULL; + PixelShader = asm { + // + // Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 + // + // + // Buffer Definitions: + // + // cbuffer cb0 + // { + // + // float4 QuadDesc; // Offset: 0 Size: 16 [unused] + // float4 TexCoords; // Offset: 16 Size: 16 [unused] + // float4 MaskTexCoords; // Offset: 32 Size: 16 [unused] + // float4 TextColor; // Offset: 48 Size: 16 + // + // } + // + // + // Resource Bindings: + // + // Name Type Format Dim Slot Elements + // ------------------------------ ---------- ------- ----------- ---- -------- + // sSampler sampler NA NA 0 1 + // sMaskSampler sampler NA NA 1 1 + // tex texture float4 2d 0 1 + // mask texture float4 2d 1 1 + // cb0 cbuffer NA NA 0 1 + // + // + // + // Input signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Position 0 xyzw 0 POS float + // TEXCOORD 0 xy 1 NONE float xy + // TEXCOORD 1 zw 1 NONE float zw + // + // + // Output signature: + // + // Name Index Mask Register SysValue Format Used + // -------------------- ----- ------ -------- -------- ------- ------ + // SV_Target 0 xyzw 0 TARGET float xyzw + // SV_Target 1 xyzw 1 TARGET float xyzw + // + // + // Constant buffer to DX9 shader constant mappings: + // + // Target Reg Buffer Start Reg # of Regs Data Conversion + // ---------- ------- --------- --------- ---------------------- + // c0 cb0 3 1 ( FLT, FLT, FLT, FLT) + // + // + // Sampler/Resource to DX9 shader sampler mappings: + // + // Target Sampler Source Sampler Source Resource + // -------------- --------------- ---------------- + // s0 s0 t0 + // s1 s1 t1 + // + // + // Level9 shader bytecode: + // + ps_2_x + def c1, 1, 0, 0, 0 + dcl t0 + dcl_2d s0 + dcl_2d s1 + mov r0.xyz, c0 + mad r0, r0.xyzx, c1.xxxy, c1.yyyx + mov oC0, r0 + mov r0.xy, t0.wzzw + texld r1, t0, s0 + texld r0, r0, s1 + mul r1, r1.zyxy, c0.w + mul r0, r0.w, r1 + mov oC1, r0 + + // approximately 9 instruction slots used (2 texture, 7 arithmetic) + ps_4_0 + dcl_constantbuffer cb0[4], immediateIndexed + dcl_sampler s0, mode_default + dcl_sampler s1, mode_default + dcl_resource_texture2d (float,float,float,float) t0 + dcl_resource_texture2d (float,float,float,float) t1 + dcl_input_ps linear v1.xy + dcl_input_ps linear v1.zw + dcl_output o0.xyzw + dcl_output o1.xyzw + dcl_temps 2 + mov o0.xyz, cb0[3].xyzx + mov o0.w, l(1.000000) + sample r0.xyzw, v1.xyxx, t0.xyzw, s0 + mul r0.xyzw, r0.zyxy, cb0[3].wwww + sample r1.xyzw, v1.zwzz, t1.xyzw, s1 + mul o1.xyzw, r0.xyzw, r1.wwww + ret + // Approximately 7 instruction slots used + + }; + } + +} + +#endif + +const BYTE d2deffect[] = { + 68, 88, 66, 67, 90, 71, 243, 245, 168, 88, 153, 105, 108, 146, 135, + 174, 199, 125, 74, 149, 1, 0, 0, 0, 137, 80, 1, 0, 1, 0, + 0, 0, 36, 0, 0, 0, 70, 88, 49, 48, 93, 80, 1, 0, 1, + 16, 255, 254, 5, 0, 0, 0, 22, 0, 0, 0, 13, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, + 0, 37, 66, 1, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 6, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 38, 0, 0, 0, 38, 0, 0, 0, + 0, 0, 0, 0, 36, 71, 108, 111, 98, 97, 108, 115, 0, 117, 105, + 110, 116, 0, 13, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 25, 9, 0, + 0, 98, 108, 101, 110, 100, 111, 112, 0, 99, 98, 48, 0, 102, 108, + 111, 97, 116, 52, 0, 58, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 10, + 33, 0, 0, 81, 117, 97, 100, 68, 101, 115, 99, 0, 84, 101, 120, + 67, 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, + 111, 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, + 0, 99, 98, 49, 0, 58, 0, 0, 0, 1, 0, 0, 0, 3, 0, + 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 48, 0, 0, 0, 10, + 33, 0, 0, 66, 108, 117, 114, 79, 102, 102, 115, 101, 116, 115, 72, + 0, 66, 108, 117, 114, 79, 102, 102, 115, 101, 116, 115, 86, 0, 66, + 108, 117, 114, 87, 101, 105, 103, 104, 116, 115, 0, 83, 104, 97, 100, + 111, 119, 67, 111, 108, 111, 114, 0, 99, 98, 50, 0, 102, 108, 111, + 97, 116, 51, 120, 51, 0, 222, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 44, 0, 0, 0, 48, 0, 0, 0, 36, 0, 0, 0, + 11, 91, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, + 84, 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 0, 102, 108, 111, + 97, 116, 50, 0, 26, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 8, 0, 0, 0, 16, 0, 0, 0, 8, 0, 0, 0, 10, 17, + 0, 0, 100, 105, 109, 101, 110, 115, 105, 111, 110, 115, 0, 102, 108, + 111, 97, 116, 51, 0, 72, 1, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 12, 0, 0, 0, 16, 0, 0, 0, 12, 0, 0, 0, 10, + 25, 0, 0, 100, 105, 102, 102, 0, 99, 101, 110, 116, 101, 114, 49, + 0, 102, 108, 111, 97, 116, 0, 120, 1, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 4, 0, 0, 0, 16, 0, 0, 0, 4, 0, 0, + 0, 9, 9, 0, 0, 65, 0, 114, 97, 100, 105, 117, 115, 49, 0, + 115, 113, 95, 114, 97, 100, 105, 117, 115, 49, 0, 99, 98, 51, 0, + 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84, 111, 85, 115, + 101, 114, 83, 112, 97, 99, 101, 95, 99, 98, 51, 0, 100, 105, 109, + 101, 110, 115, 105, 111, 110, 115, 95, 99, 98, 51, 0, 99, 101, 110, + 116, 101, 114, 0, 97, 110, 103, 108, 101, 0, 115, 116, 97, 114, 116, + 95, 111, 102, 102, 115, 101, 116, 0, 101, 110, 100, 95, 111, 102, 102, + 115, 101, 116, 0, 84, 101, 120, 116, 117, 114, 101, 50, 68, 0, 2, + 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 116, 101, 120, + 0, 98, 99, 107, 116, 101, 120, 0, 109, 97, 115, 107, 0, 83, 97, + 109, 112, 108, 101, 114, 83, 116, 97, 116, 101, 0, 56, 2, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 21, 0, 0, 0, 115, 83, 97, 109, 112, 108, + 101, 114, 0, 1, 0, 0, 0, 2, 0, 0, 0, 21, 0, 0, 0, + 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, + 0, 2, 0, 0, 0, 3, 0, 0, 0, 115, 66, 99, 107, 83, 97, + 109, 112, 108, 101, 114, 0, 1, 0, 0, 0, 2, 0, 0, 0, 21, + 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 115, 87, 114, + 97, 112, 83, 97, 109, 112, 108, 101, 114, 0, 1, 0, 0, 0, 2, + 0, 0, 0, 21, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, + 0, 115, 77, 105, 114, 114, 111, 114, 83, 97, 109, 112, 108, 101, 114, + 0, 1, 0, 0, 0, 2, 0, 0, 0, 21, 0, 0, 0, 1, 0, + 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 2, + 0, 0, 0, 2, 0, 0, 0, 115, 77, 97, 115, 107, 83, 97, 109, + 112, 108, 101, 114, 0, 1, 0, 0, 0, 2, 0, 0, 0, 21, 0, + 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 1, + 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 115, 83, 104, 97, + 100, 111, 119, 83, 97, 109, 112, 108, 101, 114, 0, 1, 0, 0, 0, + 2, 0, 0, 0, 21, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, + 0, 4, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 4, 0, + 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 82, 97, 115, 116, 101, 114, 105, + 122, 101, 114, 83, 116, 97, 116, 101, 0, 170, 3, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 4, 0, 0, 0, 84, 101, 120, 116, 117, 114, 101, 82, + 97, 115, 116, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 66, 108, + 101, 110, 100, 83, 116, 97, 116, 101, 0, 250, 3, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 2, 0, 0, 0, 83, 104, 97, 100, 111, 119, 66, 108, + 101, 110, 100, 72, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 15, 0, 0, 0, 83, + 104, 97, 100, 111, 119, 66, 108, 101, 110, 100, 86, 0, 1, 0, 0, + 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, + 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, + 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, + 0, 2, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 2, 0, + 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 15, + 0, 0, 0, 98, 84, 101, 120, 116, 66, 108, 101, 110, 100, 0, 1, + 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, + 0, 16, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 17, 0, + 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, + 0, 0, 0, 2, 0, 0, 0, 18, 0, 0, 0, 1, 0, 0, 0, + 2, 0, 0, 0, 19, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, + 0, 1, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 15, 0, + 0, 0, 83, 97, 109, 112, 108, 101, 84, 101, 120, 116, 117, 114, 101, + 0, 80, 48, 0, 68, 4, 0, 0, 68, 88, 66, 67, 77, 85, 167, + 240, 56, 56, 155, 78, 125, 96, 49, 253, 103, 100, 22, 62, 1, 0, + 0, 0, 68, 4, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 248, + 0, 0, 0, 244, 1, 0, 0, 112, 2, 0, 0, 160, 3, 0, 0, + 212, 3, 0, 0, 65, 111, 110, 57, 184, 0, 0, 0, 184, 0, 0, + 0, 0, 2, 254, 255, 132, 0, 0, 0, 52, 0, 0, 0, 1, 0, + 36, 0, 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 36, 0, 1, + 0, 48, 0, 0, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0, 5, 4, 0, 15, + 160, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, + 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, 4, + 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, 160, + 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 12, 224, 0, 0, 20, + 144, 3, 0, 180, 160, 3, 0, 20, 160, 4, 0, 0, 4, 0, 0, + 3, 128, 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, + 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, 160, + 1, 0, 0, 2, 0, 0, 12, 192, 4, 0, 68, 160, 255, 255, 0, + 0, 83, 72, 68, 82, 244, 0, 0, 0, 64, 0, 1, 0, 61, 0, + 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 3, + 0, 0, 0, 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0, 0, + 103, 0, 0, 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 101, 0, 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101, 0, + 0, 3, 194, 32, 16, 0, 1, 0, 0, 0, 50, 0, 0, 11, 50, + 32, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, + 230, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 8, 194, 32, + 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, + 50, 32, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, + 0, 230, 138, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 70, 128, + 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 11, 194, + 32, 16, 0, 1, 0, 0, 0, 6, 20, 16, 0, 0, 0, 0, 0, + 166, 142, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 6, 132, 32, + 0, 0, 0, 0, 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, + 65, 84, 116, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 4, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 82, 68, 69, 70, 40, 1, 0, 0, 1, 0, 0, 0, 64, + 0, 0, 0, 1, 0, 0, 0, 28, 0, 0, 0, 0, 4, 254, 255, + 0, 1, 0, 0, 246, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 60, + 0, 0, 0, 4, 0, 0, 0, 88, 0, 0, 0, 64, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 184, 0, 0, 0, 0, 0, 0, + 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, + 0, 0, 212, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 2, + 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, + 32, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, + 0, 0, 0, 0, 0, 236, 0, 0, 0, 48, 0, 0, 0, 16, 0, + 0, 0, 0, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 81, + 117, 97, 100, 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, + 67, 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, + 111, 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, + 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, + 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, + 49, 54, 51, 56, 52, 0, 73, 83, 71, 78, 44, 0, 0, 0, 1, + 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, 0, + 0, 80, 79, 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, 83, + 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 12, + 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, + 0, 0, 0, 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, 80, + 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, + 68, 0, 171, 171, 171, 59, 5, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 212, 2, 0, 0, 68, + 88, 66, 67, 17, 106, 69, 218, 119, 68, 79, 85, 211, 176, 27, 183, + 77, 210, 131, 41, 1, 0, 0, 0, 212, 2, 0, 0, 6, 0, 0, + 0, 56, 0, 0, 0, 164, 0, 0, 0, 16, 1, 0, 0, 140, 1, + 0, 0, 48, 2, 0, 0, 160, 2, 0, 0, 65, 111, 110, 57, 100, + 0, 0, 0, 100, 0, 0, 0, 0, 2, 255, 255, 60, 0, 0, 0, + 40, 0, 0, 0, 0, 0, 40, 0, 0, 0, 40, 0, 0, 0, 40, + 0, 1, 0, 36, 0, 0, 0, 40, 0, 0, 0, 0, 0, 1, 2, + 255, 255, 31, 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, + 0, 0, 2, 0, 0, 0, 144, 0, 8, 15, 160, 66, 0, 0, 3, + 0, 0, 15, 128, 0, 0, 228, 176, 0, 8, 228, 160, 1, 0, 0, + 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0, 83, 72, + 68, 82, 100, 0, 0, 0, 64, 0, 0, 0, 25, 0, 0, 0, 90, + 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 88, 24, 0, 4, + 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 98, 16, 0, + 3, 50, 16, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, + 16, 0, 0, 0, 0, 0, 69, 0, 0, 9, 242, 32, 16, 0, 0, + 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, + 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 62, 0, 0, + 1, 83, 84, 65, 84, 116, 0, 0, 0, 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 82, 68, 69, 70, 156, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 28, 0, 0, 0, 0, + 4, 255, 255, 0, 1, 0, 0, 105, 0, 0, 0, 92, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 101, 0, + 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, + 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, + 115, 83, 97, 109, 112, 108, 101, 114, 0, 116, 101, 120, 0, 77, 105, + 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, + 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, + 56, 52, 0, 171, 73, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, + 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 3, 3, 0, 0, 92, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 0, + 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, + 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 79, 83, 71, 78, + 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, 114, 103, 101, 116, + 0, 171, 171, 151, 9, 0, 0, 0, 0, 0, 0, 83, 97, 109, 112, + 108, 101, 84, 101, 120, 116, 117, 114, 101, 70, 111, 114, 83, 101, 112, + 97, 114, 97, 98, 108, 101, 66, 108, 101, 110, 100, 105, 110, 103, 95, + 49, 0, 68, 4, 0, 0, 68, 88, 66, 67, 77, 85, 167, 240, 56, + 56, 155, 78, 125, 96, 49, 253, 103, 100, 22, 62, 1, 0, 0, 0, + 68, 4, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 248, 0, 0, + 0, 244, 1, 0, 0, 112, 2, 0, 0, 160, 3, 0, 0, 212, 3, + 0, 0, 65, 111, 110, 57, 184, 0, 0, 0, 184, 0, 0, 0, 0, + 2, 254, 255, 132, 0, 0, 0, 52, 0, 0, 0, 1, 0, 36, 0, + 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 36, 0, 1, 0, 48, + 0, 0, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 2, 254, 255, 81, 0, 0, 5, 4, 0, 15, 160, 0, + 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 0, + 31, 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, 4, 0, 0, + 4, 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, 160, 2, 0, + 228, 160, 4, 0, 0, 4, 0, 0, 12, 224, 0, 0, 20, 144, 3, + 0, 180, 160, 3, 0, 20, 160, 4, 0, 0, 4, 0, 0, 3, 128, + 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0, 0, + 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, 160, 1, 0, + 0, 2, 0, 0, 12, 192, 4, 0, 68, 160, 255, 255, 0, 0, 83, + 72, 68, 82, 244, 0, 0, 0, 64, 0, 1, 0, 61, 0, 0, 0, + 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 3, 0, 0, + 0, 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0, 0, 103, 0, + 0, 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0, 0, 0, 101, + 0, 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, + 194, 32, 16, 0, 1, 0, 0, 0, 50, 0, 0, 11, 50, 32, 16, + 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16, 0, + 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50, 32, + 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, + 138, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 70, 128, 32, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 11, 194, 32, 16, + 0, 1, 0, 0, 0, 6, 20, 16, 0, 0, 0, 0, 0, 166, 142, + 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 6, 132, 32, 0, 0, + 0, 0, 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, + 116, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 4, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 82, 68, 69, 70, 40, 1, 0, 0, 1, 0, 0, 0, 64, 0, 0, + 0, 1, 0, 0, 0, 28, 0, 0, 0, 0, 4, 254, 255, 0, 1, + 0, 0, 246, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 60, 0, 0, + 0, 4, 0, 0, 0, 88, 0, 0, 0, 64, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 184, 0, 0, 0, 0, 0, 0, 0, 16, + 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, + 212, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, + 0, 196, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 32, 0, + 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, + 0, 0, 0, 236, 0, 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 81, 117, 97, + 100, 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, + 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, 111, + 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, 111, + 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, 77, + 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, + 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, + 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, + 51, 56, 52, 0, 73, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, + 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, 0, 0, 80, + 79, 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, 83, 71, 78, + 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 12, 0, 0, + 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, + 0, 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, 80, 111, 115, + 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, + 171, 171, 171, 155, 12, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, 72, 13, 0, 0, 68, 88, 66, + 67, 193, 65, 249, 15, 188, 209, 36, 123, 179, 111, 3, 63, 40, 10, + 7, 98, 1, 0, 0, 0, 72, 13, 0, 0, 6, 0, 0, 0, 56, + 0, 0, 0, 172, 4, 0, 0, 188, 10, 0, 0, 56, 11, 0, 0, + 164, 12, 0, 0, 20, 13, 0, 0, 65, 111, 110, 57, 108, 4, 0, + 0, 108, 4, 0, 0, 0, 2, 255, 255, 52, 4, 0, 0, 56, 0, + 0, 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, 0, 56, 0, 2, + 0, 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, 1, 1, 1, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 1, 2, 255, + 255, 81, 0, 0, 5, 1, 0, 15, 160, 0, 0, 128, 191, 0, 0, + 0, 192, 0, 0, 64, 192, 0, 0, 128, 192, 81, 0, 0, 5, 2, + 0, 15, 160, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 63, + 0, 0, 0, 192, 81, 0, 0, 5, 3, 0, 15, 160, 0, 0, 160, + 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, + 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, + 0, 0, 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, + 1, 8, 15, 160, 1, 0, 0, 2, 0, 0, 8, 128, 0, 0, 0, + 160, 2, 0, 0, 3, 0, 0, 1, 128, 0, 0, 255, 128, 3, 0, + 0, 160, 5, 0, 0, 3, 0, 0, 1, 128, 0, 0, 0, 128, 0, + 0, 0, 128, 66, 0, 0, 3, 1, 0, 15, 128, 0, 0, 228, 176, + 1, 8, 228, 160, 66, 0, 0, 3, 2, 0, 15, 128, 0, 0, 228, + 176, 0, 8, 228, 160, 6, 0, 0, 2, 0, 0, 2, 128, 2, 0, + 255, 128, 4, 0, 0, 4, 3, 0, 7, 128, 2, 0, 228, 128, 0, + 0, 85, 128, 2, 0, 0, 161, 5, 0, 0, 3, 3, 0, 7, 128, + 3, 0, 228, 128, 3, 0, 228, 128, 4, 0, 0, 4, 4, 0, 7, + 128, 2, 0, 228, 128, 0, 0, 85, 129, 2, 0, 0, 160, 6, 0, + 0, 2, 3, 0, 8, 128, 4, 0, 0, 128, 6, 0, 0, 2, 4, + 0, 8, 128, 1, 0, 255, 128, 5, 0, 0, 3, 5, 0, 7, 128, + 1, 0, 228, 128, 4, 0, 255, 128, 4, 0, 0, 4, 1, 0, 7, + 128, 1, 0, 228, 128, 4, 0, 255, 129, 2, 0, 170, 160, 5, 0, + 0, 3, 3, 0, 8, 128, 3, 0, 255, 128, 5, 0, 0, 128, 10, + 0, 0, 3, 4, 0, 8, 128, 3, 0, 255, 128, 2, 0, 0, 160, + 88, 0, 0, 4, 4, 0, 8, 128, 3, 0, 0, 129, 2, 0, 0, + 160, 4, 0, 255, 128, 5, 0, 0, 3, 6, 0, 7, 128, 5, 0, + 228, 128, 5, 0, 228, 128, 88, 0, 0, 4, 7, 0, 1, 128, 6, + 0, 0, 129, 2, 0, 85, 160, 4, 0, 255, 128, 6, 0, 0, 2, + 4, 0, 8, 128, 4, 0, 85, 128, 5, 0, 0, 3, 4, 0, 8, + 128, 4, 0, 255, 128, 5, 0, 85, 128, 10, 0, 0, 3, 5, 0, + 8, 128, 4, 0, 255, 128, 2, 0, 0, 160, 88, 0, 0, 4, 4, + 0, 8, 128, 3, 0, 85, 129, 2, 0, 0, 160, 5, 0, 255, 128, + 88, 0, 0, 4, 7, 0, 2, 128, 6, 0, 85, 129, 2, 0, 85, + 160, 4, 0, 255, 128, 6, 0, 0, 2, 4, 0, 8, 128, 4, 0, + 170, 128, 5, 0, 0, 3, 4, 0, 8, 128, 4, 0, 255, 128, 5, + 0, 170, 128, 10, 0, 0, 3, 5, 0, 8, 128, 4, 0, 255, 128, + 2, 0, 0, 160, 88, 0, 0, 4, 4, 0, 8, 128, 3, 0, 170, + 129, 2, 0, 0, 160, 5, 0, 255, 128, 88, 0, 0, 4, 7, 0, + 4, 128, 6, 0, 170, 129, 2, 0, 85, 160, 4, 0, 255, 128, 5, + 0, 0, 3, 3, 0, 7, 128, 0, 0, 85, 128, 2, 0, 228, 128, + 4, 0, 0, 4, 6, 0, 7, 128, 2, 0, 228, 128, 0, 0, 85, + 128, 5, 0, 228, 128, 4, 0, 0, 4, 6, 0, 7, 128, 3, 0, + 228, 128, 5, 0, 228, 129, 6, 0, 228, 128, 11, 0, 0, 3, 8, + 0, 7, 128, 3, 0, 228, 128, 5, 0, 228, 128, 88, 0, 0, 4, + 0, 0, 7, 128, 0, 0, 0, 129, 8, 0, 228, 128, 7, 0, 228, + 128, 2, 0, 0, 3, 7, 0, 15, 128, 0, 0, 255, 128, 1, 0, + 228, 160, 5, 0, 0, 3, 7, 0, 15, 128, 7, 0, 228, 128, 7, + 0, 228, 128, 10, 0, 0, 3, 8, 0, 7, 128, 5, 0, 228, 128, + 3, 0, 228, 128, 88, 0, 0, 4, 0, 0, 7, 128, 7, 0, 255, + 129, 8, 0, 228, 128, 0, 0, 228, 128, 4, 0, 0, 4, 8, 0, + 7, 128, 5, 0, 228, 128, 2, 0, 255, 161, 2, 0, 0, 161, 2, + 0, 0, 3, 8, 0, 7, 128, 8, 0, 228, 129, 2, 0, 0, 160, + 4, 0, 0, 4, 4, 0, 7, 128, 4, 0, 228, 128, 8, 0, 228, + 129, 2, 0, 0, 160, 2, 0, 0, 3, 8, 0, 7, 128, 5, 0, + 228, 128, 5, 0, 228, 128, 5, 0, 0, 3, 5, 0, 7, 128, 5, + 0, 228, 128, 3, 0, 228, 128, 5, 0, 0, 3, 8, 0, 7, 128, + 3, 0, 228, 128, 8, 0, 228, 128, 88, 0, 0, 4, 1, 0, 7, + 128, 1, 0, 228, 128, 8, 0, 228, 128, 4, 0, 228, 128, 88, 0, + 0, 4, 0, 0, 7, 128, 7, 0, 170, 129, 1, 0, 228, 128, 0, + 0, 228, 128, 88, 0, 0, 4, 0, 0, 7, 128, 7, 0, 85, 129, + 6, 0, 228, 128, 0, 0, 228, 128, 88, 0, 0, 4, 0, 0, 7, + 128, 7, 0, 0, 129, 5, 0, 228, 128, 0, 0, 228, 128, 18, 0, + 0, 4, 4, 0, 7, 128, 1, 0, 255, 128, 0, 0, 228, 128, 3, + 0, 228, 128, 5, 0, 0, 3, 4, 0, 8, 128, 1, 0, 255, 128, + 1, 0, 255, 128, 88, 0, 0, 4, 4, 0, 8, 128, 4, 0, 255, + 129, 2, 0, 0, 160, 2, 0, 85, 160, 5, 0, 0, 3, 0, 0, + 7, 128, 2, 0, 255, 128, 4, 0, 228, 128, 5, 0, 0, 3, 0, + 0, 8, 128, 2, 0, 255, 128, 2, 0, 255, 128, 88, 0, 0, 4, + 0, 0, 8, 128, 0, 0, 255, 129, 2, 0, 0, 160, 2, 0, 85, + 160, 2, 0, 0, 3, 0, 0, 8, 128, 4, 0, 255, 128, 0, 0, + 255, 128, 88, 0, 0, 4, 2, 0, 7, 128, 0, 0, 255, 129, 0, + 0, 228, 128, 2, 0, 228, 128, 1, 0, 0, 2, 0, 8, 15, 128, + 2, 0, 228, 128, 255, 255, 0, 0, 83, 72, 68, 82, 8, 6, 0, + 0, 64, 0, 0, 0, 130, 1, 0, 0, 89, 0, 0, 4, 70, 142, + 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 90, 0, 0, 3, 0, + 96, 16, 0, 0, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, + 1, 0, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 0, 0, 0, + 0, 85, 85, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 1, 0, + 0, 0, 85, 85, 0, 0, 98, 16, 0, 3, 50, 16, 16, 0, 1, + 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, 0, 0, 0, 0, 0, + 104, 0, 0, 2, 7, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, + 0, 0, 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 69, + 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, + 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0, 96, 16, + 0, 1, 0, 0, 0, 24, 0, 0, 7, 18, 0, 16, 0, 2, 0, + 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, + 0, 0, 0, 24, 0, 0, 7, 34, 0, 16, 0, 2, 0, 0, 0, + 58, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, + 0, 60, 0, 0, 7, 18, 0, 16, 0, 2, 0, 0, 0, 26, 0, + 16, 0, 2, 0, 0, 0, 10, 0, 16, 0, 2, 0, 0, 0, 31, + 0, 4, 3, 10, 0, 16, 0, 2, 0, 0, 0, 54, 0, 0, 5, + 242, 32, 16, 0, 0, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, + 0, 62, 0, 0, 1, 21, 0, 0, 1, 14, 0, 0, 7, 114, 0, + 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 246, + 15, 16, 0, 0, 0, 0, 0, 14, 0, 0, 7, 114, 0, 16, 0, + 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 246, 15, 16, + 0, 1, 0, 0, 0, 32, 0, 0, 8, 18, 0, 16, 0, 2, 0, + 0, 0, 10, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 64, 0, 0, 1, 0, 0, 0, 31, 0, 4, 3, 10, 0, 16, 0, + 2, 0, 0, 0, 56, 0, 0, 7, 114, 0, 16, 0, 2, 0, 0, + 0, 70, 2, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 1, 0, + 0, 0, 18, 0, 0, 1, 32, 0, 0, 8, 130, 0, 16, 0, 2, + 0, 0, 0, 10, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 2, 0, 0, 0, 31, 0, 4, 3, 58, 0, 16, + 0, 2, 0, 0, 0, 0, 0, 0, 7, 114, 0, 16, 0, 3, 0, + 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 1, + 0, 0, 0, 50, 0, 0, 10, 114, 0, 16, 0, 2, 0, 0, 0, + 70, 2, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 70, 2, 16, + 0, 1, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, 0, 18, 0, + 0, 1, 32, 0, 0, 8, 130, 0, 16, 0, 2, 0, 0, 0, 10, + 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, + 3, 0, 0, 0, 31, 0, 4, 3, 58, 0, 16, 0, 2, 0, 0, + 0, 29, 0, 0, 10, 114, 0, 16, 0, 3, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 0, 63, 0, 0, 0, 63, 0, 0, 0, 63, 0, + 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 0, 0, 0, 7, + 114, 0, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, + 0, 70, 2, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 114, 0, + 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 70, + 2, 16, 0, 4, 0, 0, 0, 50, 0, 0, 15, 114, 0, 16, 0, + 5, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 2, 64, 0, + 0, 0, 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, 64, 0, 0, + 0, 0, 2, 64, 0, 0, 0, 0, 128, 191, 0, 0, 128, 191, 0, + 0, 128, 191, 0, 0, 0, 0, 0, 0, 0, 11, 114, 0, 16, 0, + 6, 0, 0, 0, 70, 2, 16, 128, 65, 0, 0, 0, 0, 0, 0, + 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 0, 0, 0, 0, 0, 11, 114, 0, 16, 0, 5, + 0, 0, 0, 70, 2, 16, 128, 65, 0, 0, 0, 5, 0, 0, 0, + 2, 64, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, + 63, 0, 0, 0, 0, 50, 0, 0, 13, 114, 0, 16, 0, 5, 0, + 0, 0, 70, 2, 16, 128, 65, 0, 0, 0, 6, 0, 0, 0, 70, + 2, 16, 0, 5, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63, + 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 55, 0, 0, + 9, 114, 0, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 3, 0, + 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, 5, + 0, 0, 0, 18, 0, 0, 1, 32, 0, 0, 8, 130, 0, 16, 0, + 2, 0, 0, 0, 10, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 64, 0, 0, 4, 0, 0, 0, 31, 0, 4, 3, 58, 0, + 16, 0, 2, 0, 0, 0, 51, 0, 0, 7, 114, 0, 16, 0, 2, + 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, + 1, 0, 0, 0, 18, 0, 0, 1, 32, 0, 0, 8, 130, 0, 16, + 0, 2, 0, 0, 0, 10, 128, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 64, 0, 0, 5, 0, 0, 0, 31, 0, 4, 3, 58, + 0, 16, 0, 2, 0, 0, 0, 52, 0, 0, 7, 114, 0, 16, 0, + 2, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 70, 2, 16, + 0, 1, 0, 0, 0, 18, 0, 0, 1, 24, 0, 0, 10, 114, 0, + 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 2, + 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 24, 0, 0, 10, 114, 0, 16, 0, 4, 0, 0, + 0, 70, 2, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, + 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 0, + 0, 0, 11, 114, 0, 16, 0, 5, 0, 0, 0, 70, 2, 16, 128, + 65, 0, 0, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, + 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 14, 0, + 0, 7, 114, 0, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, + 0, 0, 0, 70, 2, 16, 0, 5, 0, 0, 0, 51, 0, 0, 10, + 114, 0, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, + 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 0, 0, 55, 0, 0, 12, 114, 0, 16, 0, 1, + 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 2, 64, 0, 0, + 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, + 0, 70, 2, 16, 0, 1, 0, 0, 0, 55, 0, 0, 12, 114, 0, + 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, 0, 2, + 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 21, 0, 0, + 1, 21, 0, 0, 1, 21, 0, 0, 1, 21, 0, 0, 1, 21, 0, + 0, 1, 0, 0, 0, 8, 18, 0, 16, 0, 1, 0, 0, 0, 58, + 0, 16, 128, 65, 0, 0, 0, 1, 0, 0, 0, 1, 64, 0, 0, + 0, 0, 128, 63, 56, 0, 0, 7, 226, 0, 16, 0, 1, 0, 0, + 0, 246, 15, 16, 0, 1, 0, 0, 0, 6, 9, 16, 0, 2, 0, + 0, 0, 50, 0, 0, 9, 114, 0, 16, 0, 0, 0, 0, 0, 6, + 0, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, + 150, 7, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 114, 32, 16, + 0, 0, 0, 0, 0, 246, 15, 16, 0, 0, 0, 0, 0, 70, 2, + 16, 0, 0, 0, 0, 0, 54, 0, 0, 5, 130, 32, 16, 0, 0, + 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 62, 0, 0, 1, + 83, 84, 65, 84, 116, 0, 0, 0, 57, 0, 0, 0, 7, 0, 0, + 0, 0, 0, 0, 0, 2, 0, 0, 0, 25, 0, 0, 0, 5, 0, + 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 82, 68, 69, 70, 100, 1, 0, 0, 1, 0, 0, + 0, 232, 0, 0, 0, 5, 0, 0, 0, 28, 0, 0, 0, 0, 4, + 255, 255, 0, 1, 0, 0, 48, 1, 0, 0, 188, 0, 0, 0, 3, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 197, 0, 0, + 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 209, + 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, + 0, 213, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, + 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 1, 0, 0, 0, 12, + 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 115, 83, 97, 109, 112, 108, 101, 114, 0, 115, + 66, 99, 107, 83, 97, 109, 112, 108, 101, 114, 0, 116, 101, 120, 0, + 98, 99, 107, 116, 101, 120, 0, 36, 71, 108, 111, 98, 97, 108, 115, + 0, 171, 171, 171, 220, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 1, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 32, + 1, 0, 0, 0, 0, 0, 0, 98, 108, 101, 110, 100, 111, 112, 0, + 0, 0, 19, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, + 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, + 49, 54, 51, 56, 52, 0, 171, 171, 73, 83, 71, 78, 104, 0, 0, + 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, + 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, + 0, 0, 12, 0, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, + 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, + 79, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, + 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, + 114, 103, 101, 116, 0, 171, 171, 247, 16, 0, 0, 0, 0, 0, 0, + 83, 97, 109, 112, 108, 101, 84, 101, 120, 116, 117, 114, 101, 70, 111, + 114, 83, 101, 112, 97, 114, 97, 98, 108, 101, 66, 108, 101, 110, 100, + 105, 110, 103, 95, 50, 0, 68, 4, 0, 0, 68, 88, 66, 67, 77, + 85, 167, 240, 56, 56, 155, 78, 125, 96, 49, 253, 103, 100, 22, 62, + 1, 0, 0, 0, 68, 4, 0, 0, 6, 0, 0, 0, 56, 0, 0, + 0, 248, 0, 0, 0, 244, 1, 0, 0, 112, 2, 0, 0, 160, 3, + 0, 0, 212, 3, 0, 0, 65, 111, 110, 57, 184, 0, 0, 0, 184, + 0, 0, 0, 0, 2, 254, 255, 132, 0, 0, 0, 52, 0, 0, 0, + 1, 0, 36, 0, 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 36, + 0, 1, 0, 48, 0, 0, 0, 0, 0, 3, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0, 5, 4, + 0, 15, 160, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, + 0, 0, 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, + 144, 4, 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, + 238, 160, 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 12, 224, 0, + 0, 20, 144, 3, 0, 180, 160, 3, 0, 20, 160, 4, 0, 0, 4, + 0, 0, 3, 128, 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, + 160, 2, 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, + 228, 160, 1, 0, 0, 2, 0, 0, 12, 192, 4, 0, 68, 160, 255, + 255, 0, 0, 83, 72, 68, 82, 244, 0, 0, 0, 64, 0, 1, 0, + 61, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, + 0, 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, + 101, 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, 0, 50, 0, 0, + 11, 50, 32, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, + 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, + 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 8, + 194, 32, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, + 0, 11, 50, 32, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, + 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 70, 128, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, + 11, 194, 32, 16, 0, 1, 0, 0, 0, 6, 20, 16, 0, 0, 0, + 0, 0, 166, 142, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 6, + 132, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 62, 0, 0, 1, + 83, 84, 65, 84, 116, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 4, 0, 0, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 82, 68, 69, 70, 40, 1, 0, 0, 1, 0, 0, + 0, 64, 0, 0, 0, 1, 0, 0, 0, 28, 0, 0, 0, 0, 4, + 254, 255, 0, 1, 0, 0, 246, 0, 0, 0, 60, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, + 0, 60, 0, 0, 0, 4, 0, 0, 0, 88, 0, 0, 0, 64, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 184, 0, 0, 0, 0, + 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, + 0, 0, 0, 0, 212, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, + 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 222, 0, + 0, 0, 32, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, + 0, 0, 0, 0, 0, 0, 0, 236, 0, 0, 0, 48, 0, 0, 0, + 16, 0, 0, 0, 0, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, + 0, 81, 117, 97, 100, 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, + 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, + 101, 120, 67, 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, + 120, 67, 111, 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, + 111, 114, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, + 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, + 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, + 48, 46, 49, 54, 51, 56, 52, 0, 73, 83, 71, 78, 44, 0, 0, + 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, + 3, 0, 0, 80, 79, 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, + 79, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, + 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, + 3, 12, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, + 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, + 79, 82, 68, 0, 171, 171, 171, 111, 30, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 88, 17, 0, + 0, 68, 88, 66, 67, 62, 116, 36, 238, 73, 63, 158, 95, 222, 192, + 91, 113, 112, 55, 55, 145, 1, 0, 0, 0, 88, 17, 0, 0, 6, + 0, 0, 0, 56, 0, 0, 0, 88, 6, 0, 0, 204, 14, 0, 0, + 72, 15, 0, 0, 180, 16, 0, 0, 36, 17, 0, 0, 65, 111, 110, + 57, 24, 6, 0, 0, 24, 6, 0, 0, 0, 2, 255, 255, 224, 5, + 0, 0, 56, 0, 0, 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, + 0, 56, 0, 2, 0, 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, + 0, 1, 2, 255, 255, 81, 0, 0, 5, 1, 0, 15, 160, 0, 0, + 224, 192, 0, 0, 0, 193, 0, 0, 16, 193, 0, 0, 32, 193, 81, + 0, 0, 5, 2, 0, 15, 160, 0, 0, 128, 63, 0, 0, 0, 0, + 0, 0, 128, 191, 0, 0, 128, 62, 81, 0, 0, 5, 3, 0, 15, + 160, 0, 0, 0, 63, 0, 0, 0, 64, 0, 0, 128, 191, 0, 0, + 128, 64, 81, 0, 0, 5, 4, 0, 15, 160, 0, 0, 128, 65, 0, + 0, 64, 193, 0, 0, 0, 64, 0, 0, 128, 63, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, 0, 0, + 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1, 8, + 15, 160, 1, 0, 0, 2, 0, 0, 8, 128, 0, 0, 0, 160, 2, + 0, 0, 3, 0, 0, 15, 128, 0, 0, 255, 128, 1, 0, 228, 160, + 5, 0, 0, 3, 0, 0, 15, 128, 0, 0, 228, 128, 0, 0, 228, + 128, 66, 0, 0, 3, 1, 0, 15, 128, 0, 0, 228, 176, 0, 8, + 228, 160, 66, 0, 0, 3, 2, 0, 15, 128, 0, 0, 228, 176, 1, + 8, 228, 160, 6, 0, 0, 2, 3, 0, 8, 128, 2, 0, 255, 128, + 4, 0, 0, 4, 3, 0, 3, 128, 2, 0, 233, 128, 3, 0, 255, + 129, 2, 0, 255, 160, 5, 0, 0, 3, 4, 0, 7, 128, 2, 0, + 228, 128, 3, 0, 255, 128, 4, 0, 0, 4, 5, 0, 7, 128, 4, + 0, 228, 128, 4, 0, 0, 160, 4, 0, 85, 160, 4, 0, 0, 4, + 5, 0, 7, 128, 5, 0, 228, 128, 4, 0, 228, 128, 3, 0, 255, + 160, 5, 0, 0, 3, 5, 0, 7, 128, 4, 0, 228, 128, 5, 0, + 228, 128, 7, 0, 0, 2, 4, 0, 8, 128, 4, 0, 85, 128, 6, + 0, 0, 2, 4, 0, 8, 128, 4, 0, 255, 128, 88, 0, 0, 4, + 4, 0, 8, 128, 3, 0, 0, 128, 5, 0, 85, 128, 4, 0, 255, + 128, 4, 0, 0, 4, 4, 0, 8, 128, 2, 0, 85, 128, 3, 0, + 255, 129, 4, 0, 255, 128, 6, 0, 0, 2, 3, 0, 1, 128, 1, + 0, 255, 128, 5, 0, 0, 3, 6, 0, 7, 128, 1, 0, 228, 128, + 3, 0, 0, 128, 4, 0, 0, 4, 7, 0, 7, 128, 6, 0, 228, + 128, 3, 0, 85, 160, 3, 0, 170, 160, 4, 0, 0, 4, 4, 0, + 8, 128, 7, 0, 85, 128, 4, 0, 255, 128, 4, 0, 85, 128, 4, + 0, 0, 4, 8, 0, 7, 128, 1, 0, 228, 128, 3, 0, 0, 129, + 3, 0, 0, 160, 4, 0, 0, 4, 9, 0, 15, 128, 2, 0, 36, + 128, 3, 0, 255, 129, 2, 0, 192, 160, 4, 0, 0, 4, 10, 0, + 7, 128, 6, 0, 228, 128, 4, 0, 170, 161, 4, 0, 255, 160, 5, + 0, 0, 3, 10, 0, 7, 128, 4, 0, 228, 128, 10, 0, 228, 128, + 4, 0, 0, 4, 10, 0, 7, 128, 10, 0, 228, 128, 9, 0, 228, + 129, 4, 0, 228, 128, 88, 0, 0, 4, 11, 0, 2, 128, 8, 0, + 85, 128, 10, 0, 85, 128, 4, 0, 255, 128, 7, 0, 0, 2, 4, + 0, 8, 128, 4, 0, 170, 128, 6, 0, 0, 2, 4, 0, 8, 128, + 4, 0, 255, 128, 88, 0, 0, 4, 4, 0, 8, 128, 3, 0, 85, + 128, 5, 0, 170, 128, 4, 0, 255, 128, 4, 0, 0, 4, 4, 0, + 8, 128, 2, 0, 170, 128, 3, 0, 255, 129, 4, 0, 255, 128, 4, + 0, 0, 4, 4, 0, 8, 128, 7, 0, 170, 128, 4, 0, 255, 128, + 4, 0, 170, 128, 88, 0, 0, 4, 11, 0, 4, 128, 8, 0, 170, + 128, 10, 0, 170, 128, 4, 0, 255, 128, 7, 0, 0, 2, 4, 0, + 8, 128, 4, 0, 0, 128, 6, 0, 0, 2, 4, 0, 8, 128, 4, + 0, 255, 128, 88, 0, 0, 4, 4, 0, 8, 128, 9, 0, 255, 128, + 5, 0, 0, 128, 4, 0, 255, 128, 4, 0, 0, 4, 4, 0, 8, + 128, 2, 0, 0, 128, 3, 0, 255, 129, 4, 0, 255, 128, 4, 0, + 0, 4, 2, 0, 7, 128, 2, 0, 228, 128, 3, 0, 255, 128, 2, + 0, 170, 160, 5, 0, 0, 3, 2, 0, 7, 128, 2, 0, 228, 128, + 2, 0, 228, 128, 4, 0, 0, 4, 4, 0, 8, 128, 7, 0, 0, + 128, 4, 0, 255, 128, 4, 0, 0, 128, 2, 0, 0, 3, 3, 0, + 14, 128, 7, 0, 144, 129, 2, 0, 0, 160, 4, 0, 0, 4, 3, + 0, 14, 128, 9, 0, 144, 128, 3, 0, 228, 129, 2, 0, 0, 160, + 88, 0, 0, 4, 11, 0, 1, 128, 8, 0, 0, 128, 10, 0, 0, + 128, 4, 0, 255, 128, 4, 0, 0, 4, 5, 0, 7, 128, 1, 0, + 228, 128, 3, 0, 0, 128, 4, 0, 228, 129, 4, 0, 0, 4, 7, + 0, 7, 128, 1, 0, 228, 128, 3, 0, 0, 128, 4, 0, 228, 128, + 35, 0, 0, 2, 5, 0, 7, 128, 5, 0, 228, 128, 5, 0, 0, + 3, 10, 0, 7, 128, 4, 0, 228, 128, 6, 0, 228, 128, 4, 0, + 0, 4, 7, 0, 7, 128, 10, 0, 228, 128, 3, 0, 85, 161, 7, + 0, 228, 128, 88, 0, 0, 4, 5, 0, 7, 128, 0, 0, 255, 129, + 5, 0, 228, 128, 7, 0, 228, 128, 88, 0, 0, 4, 5, 0, 7, + 128, 0, 0, 170, 129, 11, 0, 228, 128, 5, 0, 228, 128, 2, 0, + 0, 3, 7, 0, 7, 128, 6, 0, 228, 128, 6, 0, 228, 128, 5, + 0, 0, 3, 4, 0, 7, 128, 4, 0, 228, 128, 7, 0, 228, 128, + 88, 0, 0, 4, 3, 0, 7, 128, 8, 0, 228, 128, 4, 0, 228, + 128, 3, 0, 249, 128, 88, 0, 0, 4, 0, 0, 14, 128, 0, 0, + 85, 129, 3, 0, 144, 128, 5, 0, 144, 128, 6, 0, 0, 2, 6, + 0, 8, 128, 6, 0, 0, 128, 4, 0, 0, 4, 6, 0, 8, 128, + 9, 0, 0, 128, 6, 0, 255, 129, 2, 0, 0, 160, 11, 0, 0, + 3, 3, 0, 1, 128, 6, 0, 255, 128, 2, 0, 85, 160, 5, 0, + 0, 3, 3, 0, 14, 128, 6, 0, 144, 128, 6, 0, 144, 128, 88, + 0, 0, 4, 6, 0, 8, 128, 3, 0, 85, 129, 2, 0, 85, 160, + 3, 0, 0, 128, 88, 0, 0, 4, 4, 0, 1, 128, 2, 0, 0, + 129, 2, 0, 0, 160, 6, 0, 255, 128, 6, 0, 0, 2, 4, 0, + 8, 128, 6, 0, 85, 128, 4, 0, 0, 4, 4, 0, 8, 128, 9, + 0, 85, 128, 4, 0, 255, 129, 2, 0, 0, 160, 11, 0, 0, 3, + 6, 0, 8, 128, 4, 0, 255, 128, 2, 0, 85, 160, 88, 0, 0, + 4, 4, 0, 8, 128, 3, 0, 170, 129, 2, 0, 85, 160, 6, 0, + 255, 128, 88, 0, 0, 4, 4, 0, 2, 128, 2, 0, 85, 129, 2, + 0, 0, 160, 4, 0, 255, 128, 6, 0, 0, 2, 4, 0, 8, 128, + 6, 0, 170, 128, 4, 0, 0, 4, 4, 0, 8, 128, 9, 0, 170, + 128, 4, 0, 255, 129, 2, 0, 0, 160, 11, 0, 0, 3, 6, 0, + 8, 128, 4, 0, 255, 128, 2, 0, 85, 160, 88, 0, 0, 4, 4, + 0, 8, 128, 3, 0, 255, 129, 2, 0, 85, 160, 6, 0, 255, 128, + 88, 0, 0, 4, 4, 0, 4, 128, 2, 0, 170, 129, 2, 0, 0, + 160, 4, 0, 255, 128, 88, 0, 0, 4, 0, 0, 7, 128, 0, 0, + 0, 129, 4, 0, 228, 128, 0, 0, 249, 128, 18, 0, 0, 4, 3, + 0, 7, 128, 2, 0, 255, 128, 0, 0, 228, 128, 6, 0, 228, 128, + 5, 0, 0, 3, 3, 0, 8, 128, 2, 0, 255, 128, 2, 0, 255, + 128, 88, 0, 0, 4, 3, 0, 8, 128, 3, 0, 255, 129, 2, 0, + 0, 160, 2, 0, 85, 160, 5, 0, 0, 3, 0, 0, 7, 128, 1, + 0, 255, 128, 3, 0, 228, 128, 5, 0, 0, 3, 0, 0, 8, 128, + 1, 0, 255, 128, 1, 0, 255, 128, 88, 0, 0, 4, 0, 0, 8, + 128, 0, 0, 255, 129, 2, 0, 0, 160, 2, 0, 85, 160, 2, 0, + 0, 3, 0, 0, 8, 128, 3, 0, 255, 128, 0, 0, 255, 128, 88, + 0, 0, 4, 1, 0, 7, 128, 0, 0, 255, 129, 0, 0, 228, 128, + 1, 0, 228, 128, 1, 0, 0, 2, 0, 8, 15, 128, 1, 0, 228, + 128, 255, 255, 0, 0, 83, 72, 68, 82, 108, 8, 0, 0, 64, 0, + 0, 0, 27, 2, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, + 0, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 1, 0, 0, + 0, 88, 24, 0, 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, + 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 1, 0, 0, 0, 85, + 85, 0, 0, 98, 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, + 101, 0, 0, 3, 242, 32, 16, 0, 0, 0, 0, 0, 104, 0, 0, + 2, 7, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 0, 0, + 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 0, + 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 69, 0, 0, 9, + 242, 0, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, + 0, 70, 126, 16, 0, 1, 0, 0, 0, 0, 96, 16, 0, 1, 0, + 0, 0, 24, 0, 0, 7, 18, 0, 16, 0, 2, 0, 0, 0, 58, + 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, + 24, 0, 0, 7, 34, 0, 16, 0, 2, 0, 0, 0, 58, 0, 16, + 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 60, 0, + 0, 7, 18, 0, 16, 0, 2, 0, 0, 0, 26, 0, 16, 0, 2, + 0, 0, 0, 10, 0, 16, 0, 2, 0, 0, 0, 31, 0, 4, 3, + 10, 0, 16, 0, 2, 0, 0, 0, 54, 0, 0, 5, 242, 32, 16, + 0, 0, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 62, 0, + 0, 1, 21, 0, 0, 1, 14, 0, 0, 7, 114, 0, 16, 0, 0, + 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 246, 15, 16, 0, + 0, 0, 0, 0, 14, 0, 0, 7, 114, 0, 16, 0, 1, 0, 0, + 0, 70, 2, 16, 0, 1, 0, 0, 0, 246, 15, 16, 0, 1, 0, + 0, 0, 32, 0, 0, 8, 18, 0, 16, 0, 2, 0, 0, 0, 10, + 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, + 7, 0, 0, 0, 31, 0, 4, 3, 10, 0, 16, 0, 2, 0, 0, + 0, 24, 0, 0, 10, 114, 0, 16, 0, 2, 0, 0, 0, 70, 2, + 16, 0, 1, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, + 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 24, 0, 0, 10, + 114, 0, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, + 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 114, 0, 16, 0, 4, + 0, 0, 0, 70, 2, 16, 128, 65, 0, 0, 0, 1, 0, 0, 0, + 2, 64, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, + 63, 0, 0, 0, 0, 14, 0, 0, 7, 114, 0, 16, 0, 4, 0, + 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, 0, + 0, 0, 0, 51, 0, 0, 10, 114, 0, 16, 0, 4, 0, 0, 0, + 70, 2, 16, 0, 4, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, + 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, + 0, 11, 114, 0, 16, 0, 4, 0, 0, 0, 70, 2, 16, 128, 65, + 0, 0, 0, 4, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63, + 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 55, 0, 0, + 12, 114, 0, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 3, 0, + 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, + 55, 0, 0, 12, 114, 0, 16, 0, 2, 0, 0, 0, 70, 2, 16, + 0, 2, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 70, 2, 16, 0, 3, + 0, 0, 0, 18, 0, 0, 1, 32, 0, 0, 8, 130, 0, 16, 0, + 2, 0, 0, 0, 10, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 64, 0, 0, 8, 0, 0, 0, 31, 0, 4, 3, 58, 0, + 16, 0, 2, 0, 0, 0, 29, 0, 0, 10, 114, 0, 16, 0, 3, + 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 63, 0, 0, 0, 63, + 0, 0, 0, 63, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, + 0, 0, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 70, 2, + 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 56, + 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, + 1, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 50, 0, 0, + 15, 114, 0, 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, 0, 0, + 0, 0, 2, 64, 0, 0, 0, 0, 0, 64, 0, 0, 0, 64, 0, + 0, 0, 64, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 191, + 0, 0, 128, 191, 0, 0, 128, 191, 0, 0, 0, 0, 0, 0, 0, + 11, 114, 0, 16, 0, 6, 0, 0, 0, 70, 2, 16, 128, 65, 0, + 0, 0, 1, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, + 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 11, + 114, 0, 16, 0, 5, 0, 0, 0, 70, 2, 16, 128, 65, 0, 0, + 0, 5, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 50, 0, 0, 13, 114, + 0, 16, 0, 5, 0, 0, 0, 70, 2, 16, 128, 65, 0, 0, 0, + 6, 0, 0, 0, 70, 2, 16, 0, 5, 0, 0, 0, 2, 64, 0, + 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, + 0, 0, 55, 0, 0, 9, 114, 0, 16, 0, 2, 0, 0, 0, 70, + 2, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, + 70, 2, 16, 0, 5, 0, 0, 0, 18, 0, 0, 1, 32, 0, 0, + 8, 130, 0, 16, 0, 2, 0, 0, 0, 10, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 9, 0, 0, 0, 31, + 0, 4, 3, 58, 0, 16, 0, 2, 0, 0, 0, 29, 0, 0, 10, + 114, 0, 16, 0, 3, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, + 62, 0, 0, 128, 62, 0, 0, 128, 62, 0, 0, 0, 0, 70, 2, + 16, 0, 1, 0, 0, 0, 50, 0, 0, 15, 114, 0, 16, 0, 4, + 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 2, 64, 0, 0, + 0, 0, 128, 65, 0, 0, 128, 65, 0, 0, 128, 65, 0, 0, 0, + 0, 2, 64, 0, 0, 0, 0, 64, 193, 0, 0, 64, 193, 0, 0, + 64, 193, 0, 0, 0, 0, 50, 0, 0, 12, 114, 0, 16, 0, 4, + 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, + 1, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 64, 0, 0, 128, + 64, 0, 0, 128, 64, 0, 0, 0, 0, 56, 0, 0, 7, 114, 0, + 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 70, + 2, 16, 0, 4, 0, 0, 0, 75, 0, 0, 5, 114, 0, 16, 0, + 5, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 55, 0, 0, + 9, 114, 0, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 3, 0, + 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, 5, + 0, 0, 0, 29, 0, 0, 10, 114, 0, 16, 0, 4, 0, 0, 0, + 2, 64, 0, 0, 0, 0, 0, 63, 0, 0, 0, 63, 0, 0, 0, + 63, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 50, 0, + 0, 16, 114, 0, 16, 0, 5, 0, 0, 0, 70, 2, 16, 128, 65, + 0, 0, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 64, + 0, 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, 0, 2, 64, 0, + 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, + 0, 0, 56, 0, 0, 7, 114, 0, 16, 0, 5, 0, 0, 0, 70, + 2, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 5, 0, 0, 0, + 0, 0, 0, 11, 114, 0, 16, 0, 6, 0, 0, 0, 70, 2, 16, + 128, 65, 0, 0, 0, 1, 0, 0, 0, 2, 64, 0, 0, 0, 0, + 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 50, + 0, 0, 10, 114, 0, 16, 0, 5, 0, 0, 0, 70, 2, 16, 128, + 65, 0, 0, 0, 5, 0, 0, 0, 70, 2, 16, 0, 6, 0, 0, + 0, 70, 2, 16, 0, 1, 0, 0, 0, 50, 0, 0, 15, 114, 0, + 16, 0, 6, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 2, + 64, 0, 0, 0, 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, 64, + 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 191, 0, 0, 128, + 191, 0, 0, 128, 191, 0, 0, 0, 0, 0, 0, 0, 8, 114, 0, + 16, 0, 3, 0, 0, 0, 70, 2, 16, 128, 65, 0, 0, 0, 1, + 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, 0, 50, 0, 0, 9, + 114, 0, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 6, 0, 0, + 0, 70, 2, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 1, 0, + 0, 0, 55, 0, 0, 9, 114, 0, 16, 0, 2, 0, 0, 0, 70, + 2, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, 5, 0, 0, 0, + 70, 2, 16, 0, 3, 0, 0, 0, 18, 0, 0, 1, 32, 0, 0, + 8, 130, 0, 16, 0, 2, 0, 0, 0, 10, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 10, 0, 0, 0, 0, + 0, 0, 8, 114, 0, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, + 0, 0, 0, 0, 70, 2, 16, 128, 65, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 70, 2, + 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 56, + 0, 0, 7, 114, 0, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, + 0, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 50, 0, 0, + 13, 114, 0, 16, 0, 1, 0, 0, 0, 70, 2, 16, 128, 65, 0, + 0, 0, 1, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 64, 0, + 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, 0, 70, 2, 16, 0, + 4, 0, 0, 0, 55, 0, 0, 10, 114, 0, 16, 0, 2, 0, 0, + 0, 246, 15, 16, 0, 2, 0, 0, 0, 70, 2, 16, 128, 129, 0, + 0, 0, 3, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 21, + 0, 0, 1, 21, 0, 0, 1, 21, 0, 0, 1, 0, 0, 0, 8, + 18, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 128, 65, 0, 0, + 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 56, 0, + 0, 7, 226, 0, 16, 0, 1, 0, 0, 0, 246, 15, 16, 0, 1, + 0, 0, 0, 6, 9, 16, 0, 2, 0, 0, 0, 50, 0, 0, 9, + 114, 0, 16, 0, 0, 0, 0, 0, 6, 0, 16, 0, 1, 0, 0, + 0, 70, 2, 16, 0, 0, 0, 0, 0, 150, 7, 16, 0, 1, 0, + 0, 0, 56, 0, 0, 7, 114, 32, 16, 0, 0, 0, 0, 0, 246, + 15, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, + 54, 0, 0, 5, 130, 32, 16, 0, 0, 0, 0, 0, 58, 0, 16, + 0, 0, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, + 0, 0, 66, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 38, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, + 5, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, + 69, 70, 100, 1, 0, 0, 1, 0, 0, 0, 232, 0, 0, 0, 5, + 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0, + 48, 1, 0, 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 197, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 209, 0, 0, 0, 2, 0, 0, + 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, + 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 213, 0, 0, 0, 2, + 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, + 1, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 220, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 115, + 83, 97, 109, 112, 108, 101, 114, 0, 115, 66, 99, 107, 83, 97, 109, + 112, 108, 101, 114, 0, 116, 101, 120, 0, 98, 99, 107, 116, 101, 120, + 0, 36, 71, 108, 111, 98, 97, 108, 115, 0, 171, 171, 171, 220, 0, + 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 16, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 24, 1, 0, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 2, 0, 0, 0, 32, 1, 0, 0, 0, 0, 0, + 0, 98, 108, 101, 110, 100, 111, 112, 0, 0, 0, 19, 0, 1, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 105, 99, 114, 111, + 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, + 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, + 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, + 171, 171, 73, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, + 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, + 0, 0, 3, 3, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, + 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, + 67, 79, 79, 82, 68, 0, 171, 171, 171, 79, 83, 71, 78, 44, 0, + 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 15, 0, 0, 0, 83, 86, 95, 84, 97, 114, 103, 101, 116, 0, 171, + 171, 203, 34, 0, 0, 0, 0, 0, 0, 83, 97, 109, 112, 108, 101, + 84, 101, 120, 116, 117, 114, 101, 70, 111, 114, 78, 111, 110, 83, 101, + 112, 97, 114, 97, 98, 108, 101, 66, 108, 101, 110, 100, 105, 110, 103, + 0, 68, 4, 0, 0, 68, 88, 66, 67, 77, 85, 167, 240, 56, 56, + 155, 78, 125, 96, 49, 253, 103, 100, 22, 62, 1, 0, 0, 0, 68, + 4, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 248, 0, 0, 0, + 244, 1, 0, 0, 112, 2, 0, 0, 160, 3, 0, 0, 212, 3, 0, + 0, 65, 111, 110, 57, 184, 0, 0, 0, 184, 0, 0, 0, 0, 2, + 254, 255, 132, 0, 0, 0, 52, 0, 0, 0, 1, 0, 36, 0, 0, + 0, 48, 0, 0, 0, 48, 0, 0, 0, 36, 0, 1, 0, 48, 0, + 0, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 2, 254, 255, 81, 0, 0, 5, 4, 0, 15, 160, 0, 0, + 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 0, 31, + 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, 4, 0, 0, 4, + 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, 160, 2, 0, 228, + 160, 4, 0, 0, 4, 0, 0, 12, 224, 0, 0, 20, 144, 3, 0, + 180, 160, 3, 0, 20, 160, 4, 0, 0, 4, 0, 0, 3, 128, 0, + 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0, 0, 3, + 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, 160, 1, 0, 0, + 2, 0, 0, 12, 192, 4, 0, 68, 160, 255, 255, 0, 0, 83, 72, + 68, 82, 244, 0, 0, 0, 64, 0, 1, 0, 61, 0, 0, 0, 89, + 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0, 0, 103, 0, 0, + 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0, 0, 0, 101, 0, + 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 194, + 32, 16, 0, 1, 0, 0, 0, 50, 0, 0, 11, 50, 32, 16, 0, + 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16, 0, 0, + 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50, 32, 16, + 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, + 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 11, 194, 32, 16, 0, + 1, 0, 0, 0, 6, 20, 16, 0, 0, 0, 0, 0, 166, 142, 32, + 0, 0, 0, 0, 0, 2, 0, 0, 0, 6, 132, 32, 0, 0, 0, + 0, 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, + 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, + 68, 69, 70, 40, 1, 0, 0, 1, 0, 0, 0, 64, 0, 0, 0, + 1, 0, 0, 0, 28, 0, 0, 0, 0, 4, 254, 255, 0, 1, 0, + 0, 246, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 60, 0, 0, 0, + 4, 0, 0, 0, 88, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 184, 0, 0, 0, 0, 0, 0, 0, 16, 0, + 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 212, + 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, + 196, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 32, 0, 0, + 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, + 0, 0, 236, 0, 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 0, + 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, + 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, + 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, + 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, 77, 105, + 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, + 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, + 56, 52, 0, 73, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, + 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, 0, 0, 80, 79, + 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, 83, 71, 78, 104, + 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 12, 0, 0, 92, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, 80, 111, 115, 105, + 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, + 171, 171, 84, 52, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 216, 37, 0, 0, 68, 88, 66, 67, + 205, 124, 125, 227, 208, 119, 203, 250, 120, 38, 135, 194, 158, 189, 85, + 176, 1, 0, 0, 0, 216, 37, 0, 0, 6, 0, 0, 0, 56, 0, + 0, 0, 72, 13, 0, 0, 76, 35, 0, 0, 200, 35, 0, 0, 52, + 37, 0, 0, 164, 37, 0, 0, 65, 111, 110, 57, 8, 13, 0, 0, + 8, 13, 0, 0, 0, 2, 255, 255, 208, 12, 0, 0, 56, 0, 0, + 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, 0, 56, 0, 2, 0, + 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 1, 2, 255, 255, + 81, 0, 0, 5, 1, 0, 15, 160, 0, 0, 64, 193, 0, 0, 80, + 193, 0, 0, 96, 193, 0, 0, 0, 0, 81, 0, 0, 5, 2, 0, + 15, 160, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 81, 0, 0, 5, 3, 0, 15, 160, 154, 153, 153, 62, + 61, 10, 23, 63, 174, 71, 225, 61, 0, 0, 0, 0, 31, 0, 0, + 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, 0, + 0, 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1, + 8, 15, 160, 1, 0, 0, 2, 0, 0, 2, 128, 2, 0, 85, 160, + 1, 0, 0, 2, 1, 0, 2, 128, 2, 0, 85, 160, 1, 0, 0, + 2, 2, 0, 4, 128, 2, 0, 85, 160, 66, 0, 0, 3, 3, 0, + 15, 128, 0, 0, 228, 176, 1, 8, 228, 160, 66, 0, 0, 3, 4, + 0, 15, 128, 0, 0, 228, 176, 0, 8, 228, 160, 6, 0, 0, 2, + 0, 0, 8, 128, 4, 0, 255, 128, 5, 0, 0, 3, 5, 0, 7, + 128, 0, 0, 255, 128, 4, 0, 228, 128, 4, 0, 0, 4, 6, 0, + 3, 128, 4, 0, 225, 128, 0, 0, 255, 128, 5, 0, 230, 129, 88, + 0, 0, 4, 7, 0, 3, 128, 6, 0, 0, 128, 5, 0, 233, 128, + 5, 0, 230, 128, 11, 0, 0, 3, 1, 0, 8, 128, 5, 0, 0, + 128, 7, 0, 0, 128, 10, 0, 0, 3, 2, 0, 8, 128, 7, 0, + 85, 128, 5, 0, 0, 128, 2, 0, 0, 3, 7, 0, 8, 128, 1, + 0, 255, 128, 2, 0, 255, 129, 6, 0, 0, 2, 1, 0, 8, 128, + 3, 0, 255, 128, 5, 0, 0, 3, 8, 0, 7, 128, 1, 0, 255, + 128, 3, 0, 228, 128, 4, 0, 0, 4, 9, 0, 3, 128, 3, 0, + 0, 128, 1, 0, 255, 128, 8, 0, 230, 129, 6, 0, 0, 2, 2, + 0, 8, 128, 9, 0, 85, 128, 5, 0, 0, 3, 2, 0, 8, 128, + 2, 0, 255, 128, 7, 0, 255, 128, 4, 0, 0, 4, 10, 0, 15, + 128, 3, 0, 150, 128, 1, 0, 255, 128, 8, 0, 96, 129, 5, 0, + 0, 3, 7, 0, 2, 128, 2, 0, 255, 128, 10, 0, 255, 128, 1, + 0, 0, 2, 9, 0, 12, 128, 10, 0, 228, 128, 88, 0, 0, 4, + 1, 0, 5, 128, 9, 0, 85, 129, 9, 0, 245, 128, 7, 0, 215, + 128, 6, 0, 0, 2, 2, 0, 8, 128, 9, 0, 0, 128, 5, 0, + 0, 3, 2, 0, 8, 128, 2, 0, 255, 128, 7, 0, 255, 128, 5, + 0, 0, 3, 7, 0, 1, 128, 2, 0, 255, 128, 9, 0, 170, 128, + 88, 0, 0, 4, 2, 0, 3, 128, 9, 0, 0, 129, 9, 0, 232, + 128, 7, 0, 227, 128, 88, 0, 0, 4, 1, 0, 7, 128, 9, 0, + 255, 128, 1, 0, 228, 128, 2, 0, 228, 128, 6, 0, 0, 2, 5, + 0, 8, 128, 9, 0, 255, 128, 5, 0, 0, 3, 5, 0, 8, 128, + 5, 0, 255, 128, 7, 0, 255, 128, 5, 0, 0, 3, 7, 0, 4, + 128, 5, 0, 255, 128, 9, 0, 85, 128, 88, 0, 0, 4, 0, 0, + 5, 128, 10, 0, 255, 129, 9, 0, 245, 128, 7, 0, 246, 128, 88, + 0, 0, 4, 0, 0, 7, 128, 10, 0, 0, 128, 0, 0, 228, 128, + 1, 0, 228, 128, 1, 0, 0, 2, 1, 0, 1, 128, 2, 0, 85, + 160, 1, 0, 0, 2, 2, 0, 1, 128, 2, 0, 85, 160, 1, 0, + 0, 2, 11, 0, 4, 128, 2, 0, 85, 160, 6, 0, 0, 2, 2, + 0, 8, 128, 9, 0, 170, 128, 5, 0, 0, 3, 2, 0, 8, 128, + 2, 0, 255, 128, 7, 0, 255, 128, 5, 0, 0, 3, 7, 0, 1, + 128, 2, 0, 255, 128, 9, 0, 0, 128, 88, 0, 0, 4, 11, 0, + 3, 128, 10, 0, 170, 129, 9, 0, 232, 128, 7, 0, 236, 128, 6, + 0, 0, 2, 2, 0, 8, 128, 10, 0, 85, 128, 5, 0, 0, 3, + 2, 0, 8, 128, 2, 0, 255, 128, 7, 0, 255, 128, 5, 0, 0, + 3, 7, 0, 2, 128, 2, 0, 255, 128, 10, 0, 0, 128, 88, 0, + 0, 4, 2, 0, 6, 128, 10, 0, 85, 129, 10, 0, 196, 128, 7, + 0, 220, 128, 88, 0, 0, 4, 2, 0, 7, 128, 10, 0, 0, 128, + 2, 0, 228, 128, 11, 0, 228, 128, 6, 0, 0, 2, 2, 0, 8, + 128, 10, 0, 0, 128, 5, 0, 0, 3, 2, 0, 8, 128, 2, 0, + 255, 128, 7, 0, 255, 128, 5, 0, 0, 3, 7, 0, 4, 128, 2, + 0, 255, 128, 10, 0, 85, 128, 88, 0, 0, 4, 1, 0, 6, 128, + 10, 0, 0, 129, 10, 0, 196, 128, 7, 0, 248, 128, 88, 0, 0, + 4, 1, 0, 7, 128, 9, 0, 255, 128, 1, 0, 228, 128, 2, 0, + 228, 128, 88, 0, 0, 4, 0, 0, 7, 128, 10, 0, 85, 128, 1, + 0, 228, 128, 0, 0, 228, 128, 88, 0, 0, 4, 1, 0, 3, 128, + 9, 0, 170, 128, 8, 0, 233, 128, 8, 0, 230, 128, 8, 0, 0, + 3, 5, 0, 8, 128, 0, 0, 228, 128, 3, 0, 228, 160, 8, 0, + 0, 3, 1, 0, 4, 128, 8, 0, 228, 128, 3, 0, 228, 160, 2, + 0, 0, 3, 5, 0, 8, 128, 5, 0, 255, 129, 1, 0, 170, 128, + 2, 0, 0, 3, 0, 0, 7, 128, 0, 0, 228, 128, 5, 0, 255, + 128, 2, 0, 0, 3, 5, 0, 8, 128, 0, 0, 85, 129, 0, 0, + 0, 128, 88, 0, 0, 4, 2, 0, 3, 128, 5, 0, 255, 128, 0, + 0, 225, 128, 0, 0, 228, 128, 10, 0, 0, 3, 5, 0, 8, 128, + 0, 0, 170, 128, 2, 0, 0, 128, 11, 0, 0, 3, 7, 0, 1, + 128, 2, 0, 85, 128, 0, 0, 170, 128, 8, 0, 0, 3, 2, 0, + 1, 128, 0, 0, 228, 128, 3, 0, 228, 160, 2, 0, 0, 3, 2, + 0, 2, 128, 5, 0, 255, 129, 2, 0, 0, 128, 6, 0, 0, 2, + 2, 0, 2, 128, 2, 0, 85, 128, 2, 0, 0, 3, 7, 0, 14, + 128, 0, 0, 144, 128, 2, 0, 0, 129, 5, 0, 0, 3, 7, 0, + 14, 128, 2, 0, 0, 128, 7, 0, 228, 128, 4, 0, 0, 4, 2, + 0, 14, 128, 7, 0, 228, 128, 2, 0, 85, 128, 2, 0, 0, 128, + 88, 0, 0, 4, 0, 0, 7, 128, 5, 0, 255, 128, 0, 0, 228, + 128, 2, 0, 249, 128, 2, 0, 0, 3, 2, 0, 14, 128, 2, 0, + 0, 129, 0, 0, 144, 128, 2, 0, 0, 3, 5, 0, 8, 128, 2, + 0, 0, 129, 2, 0, 0, 160, 5, 0, 0, 3, 2, 0, 14, 128, + 2, 0, 228, 128, 5, 0, 255, 128, 2, 0, 0, 3, 5, 0, 8, + 128, 2, 0, 0, 129, 7, 0, 0, 128, 2, 0, 0, 3, 7, 0, + 1, 128, 7, 0, 0, 129, 2, 0, 0, 160, 6, 0, 0, 2, 5, + 0, 8, 128, 5, 0, 255, 128, 4, 0, 0, 4, 2, 0, 7, 128, + 2, 0, 249, 128, 5, 0, 255, 128, 2, 0, 0, 128, 88, 0, 0, + 4, 0, 0, 7, 128, 7, 0, 0, 128, 0, 0, 228, 128, 2, 0, + 228, 128, 8, 0, 0, 3, 5, 0, 8, 128, 5, 0, 228, 128, 3, + 0, 228, 160, 2, 0, 0, 3, 2, 0, 1, 128, 1, 0, 170, 128, + 5, 0, 255, 129, 2, 0, 0, 3, 5, 0, 8, 128, 1, 0, 170, + 129, 5, 0, 255, 128, 4, 0, 0, 4, 2, 0, 14, 128, 3, 0, + 144, 128, 1, 0, 255, 128, 5, 0, 255, 128, 4, 0, 0, 4, 3, + 0, 7, 128, 4, 0, 228, 128, 0, 0, 255, 128, 2, 0, 0, 128, + 4, 0, 0, 4, 7, 0, 15, 128, 4, 0, 38, 128, 0, 0, 255, + 128, 5, 0, 144, 129, 2, 0, 0, 3, 0, 0, 8, 128, 3, 0, + 85, 129, 3, 0, 0, 128, 88, 0, 0, 4, 8, 0, 6, 128, 0, + 0, 255, 128, 3, 0, 196, 128, 3, 0, 208, 128, 10, 0, 0, 3, + 0, 0, 8, 128, 3, 0, 170, 128, 8, 0, 85, 128, 11, 0, 0, + 3, 1, 0, 8, 128, 8, 0, 170, 128, 3, 0, 170, 128, 8, 0, + 0, 3, 5, 0, 8, 128, 3, 0, 228, 128, 3, 0, 228, 160, 2, + 0, 0, 3, 2, 0, 1, 128, 0, 0, 255, 129, 5, 0, 255, 128, + 6, 0, 0, 2, 2, 0, 1, 128, 2, 0, 0, 128, 2, 0, 0, + 3, 8, 0, 14, 128, 3, 0, 144, 128, 5, 0, 255, 129, 5, 0, + 0, 3, 8, 0, 14, 128, 5, 0, 255, 128, 8, 0, 228, 128, 4, + 0, 0, 4, 8, 0, 14, 128, 8, 0, 228, 128, 2, 0, 0, 128, + 5, 0, 255, 128, 88, 0, 0, 4, 3, 0, 7, 128, 0, 0, 255, + 128, 3, 0, 228, 128, 8, 0, 249, 128, 2, 0, 0, 3, 8, 0, + 14, 128, 5, 0, 255, 129, 3, 0, 144, 128, 2, 0, 0, 3, 0, + 0, 8, 128, 5, 0, 255, 129, 2, 0, 0, 160, 5, 0, 0, 3, + 8, 0, 14, 128, 0, 0, 255, 128, 8, 0, 228, 128, 2, 0, 0, + 3, 0, 0, 8, 128, 1, 0, 255, 128, 5, 0, 255, 129, 2, 0, + 0, 3, 1, 0, 8, 128, 1, 0, 255, 129, 2, 0, 0, 160, 6, + 0, 0, 2, 0, 0, 8, 128, 0, 0, 255, 128, 4, 0, 0, 4, + 8, 0, 14, 128, 8, 0, 228, 128, 0, 0, 255, 128, 5, 0, 255, + 128, 88, 0, 0, 4, 3, 0, 7, 128, 1, 0, 255, 128, 3, 0, + 228, 128, 8, 0, 249, 128, 2, 0, 0, 3, 0, 0, 8, 128, 2, + 0, 170, 129, 2, 0, 85, 128, 88, 0, 0, 4, 8, 0, 6, 128, + 0, 0, 255, 128, 2, 0, 216, 128, 2, 0, 228, 128, 10, 0, 0, + 3, 0, 0, 8, 128, 2, 0, 255, 128, 8, 0, 85, 128, 11, 0, + 0, 3, 1, 0, 8, 128, 8, 0, 170, 128, 2, 0, 255, 128, 8, + 0, 0, 3, 5, 0, 8, 128, 2, 0, 249, 128, 3, 0, 228, 160, + 2, 0, 0, 3, 2, 0, 1, 128, 0, 0, 255, 129, 5, 0, 255, + 128, 6, 0, 0, 2, 2, 0, 1, 128, 2, 0, 0, 128, 2, 0, + 0, 3, 8, 0, 14, 128, 2, 0, 228, 128, 5, 0, 255, 129, 5, + 0, 0, 3, 8, 0, 14, 128, 5, 0, 255, 128, 8, 0, 228, 128, + 4, 0, 0, 4, 8, 0, 14, 128, 8, 0, 228, 128, 2, 0, 0, + 128, 5, 0, 255, 128, 88, 0, 0, 4, 2, 0, 7, 128, 0, 0, + 255, 128, 2, 0, 249, 128, 8, 0, 249, 128, 2, 0, 0, 3, 8, + 0, 14, 128, 5, 0, 255, 129, 2, 0, 144, 128, 2, 0, 0, 3, + 0, 0, 8, 128, 5, 0, 255, 129, 2, 0, 0, 160, 5, 0, 0, + 3, 8, 0, 14, 128, 0, 0, 255, 128, 8, 0, 228, 128, 2, 0, + 0, 3, 0, 0, 8, 128, 1, 0, 255, 128, 5, 0, 255, 129, 2, + 0, 0, 3, 1, 0, 8, 128, 1, 0, 255, 129, 2, 0, 0, 160, + 6, 0, 0, 2, 0, 0, 8, 128, 0, 0, 255, 128, 4, 0, 0, + 4, 8, 0, 14, 128, 8, 0, 228, 128, 0, 0, 255, 128, 5, 0, + 255, 128, 88, 0, 0, 4, 2, 0, 7, 128, 1, 0, 255, 128, 2, + 0, 228, 128, 8, 0, 249, 128, 1, 0, 0, 2, 0, 0, 8, 128, + 0, 0, 0, 160, 2, 0, 0, 3, 8, 0, 14, 128, 0, 0, 255, + 128, 1, 0, 144, 160, 5, 0, 0, 3, 8, 0, 14, 128, 8, 0, + 228, 128, 8, 0, 228, 128, 88, 0, 0, 4, 2, 0, 7, 128, 8, + 0, 255, 129, 3, 0, 228, 128, 2, 0, 228, 128, 88, 0, 0, 4, + 0, 0, 7, 128, 8, 0, 170, 129, 0, 0, 228, 128, 2, 0, 228, + 128, 1, 0, 0, 2, 2, 0, 2, 128, 2, 0, 85, 160, 1, 0, + 0, 2, 3, 0, 2, 128, 2, 0, 85, 160, 1, 0, 0, 2, 9, + 0, 4, 128, 2, 0, 85, 160, 11, 0, 0, 3, 0, 0, 8, 128, + 8, 0, 0, 128, 1, 0, 0, 128, 10, 0, 0, 3, 2, 0, 8, + 128, 1, 0, 85, 128, 8, 0, 0, 128, 2, 0, 0, 3, 10, 0, + 8, 128, 0, 0, 255, 128, 2, 0, 255, 129, 6, 0, 0, 2, 0, + 0, 8, 128, 7, 0, 255, 128, 5, 0, 0, 3, 0, 0, 8, 128, + 0, 0, 255, 128, 10, 0, 255, 128, 5, 0, 0, 3, 10, 0, 1, + 128, 0, 0, 255, 128, 6, 0, 0, 128, 1, 0, 0, 2, 6, 0, + 12, 128, 7, 0, 180, 128, 88, 0, 0, 4, 9, 0, 3, 128, 7, + 0, 255, 129, 6, 0, 226, 128, 10, 0, 227, 128, 6, 0, 0, 2, + 0, 0, 8, 128, 6, 0, 85, 128, 5, 0, 0, 3, 0, 0, 8, + 128, 0, 0, 255, 128, 10, 0, 255, 128, 5, 0, 0, 3, 10, 0, + 2, 128, 0, 0, 255, 128, 7, 0, 170, 128, 88, 0, 0, 4, 3, + 0, 5, 128, 6, 0, 85, 129, 6, 0, 245, 128, 10, 0, 215, 128, + 88, 0, 0, 4, 1, 0, 11, 128, 7, 0, 170, 128, 3, 0, 164, + 128, 9, 0, 164, 128, 6, 0, 0, 2, 0, 0, 8, 128, 7, 0, + 170, 128, 5, 0, 0, 3, 0, 0, 8, 128, 0, 0, 255, 128, 10, + 0, 255, 128, 5, 0, 0, 3, 10, 0, 4, 128, 0, 0, 255, 128, + 6, 0, 85, 128, 88, 0, 0, 4, 2, 0, 5, 128, 7, 0, 170, + 129, 6, 0, 245, 128, 10, 0, 246, 128, 88, 0, 0, 4, 1, 0, + 11, 128, 7, 0, 0, 128, 2, 0, 164, 128, 1, 0, 228, 128, 1, + 0, 0, 2, 2, 0, 1, 128, 2, 0, 85, 160, 1, 0, 0, 2, + 3, 0, 4, 128, 2, 0, 85, 160, 6, 0, 0, 2, 0, 0, 8, + 128, 6, 0, 0, 128, 5, 0, 0, 3, 0, 0, 8, 128, 0, 0, + 255, 128, 10, 0, 255, 128, 5, 0, 0, 3, 10, 0, 1, 128, 0, + 0, 255, 128, 7, 0, 255, 128, 88, 0, 0, 4, 3, 0, 3, 128, + 6, 0, 0, 129, 6, 0, 226, 128, 10, 0, 236, 128, 6, 0, 0, + 2, 0, 0, 8, 128, 7, 0, 85, 128, 5, 0, 0, 3, 0, 0, + 8, 128, 0, 0, 255, 128, 10, 0, 255, 128, 5, 0, 0, 3, 10, + 0, 2, 128, 0, 0, 255, 128, 7, 0, 0, 128, 88, 0, 0, 4, + 2, 0, 6, 128, 7, 0, 85, 129, 7, 0, 196, 128, 10, 0, 220, + 128, 88, 0, 0, 4, 2, 0, 7, 128, 7, 0, 0, 128, 2, 0, + 228, 128, 3, 0, 228, 128, 1, 0, 0, 2, 3, 0, 1, 128, 2, + 0, 85, 160, 6, 0, 0, 2, 0, 0, 8, 128, 7, 0, 0, 128, + 5, 0, 0, 3, 0, 0, 8, 128, 0, 0, 255, 128, 10, 0, 255, + 128, 5, 0, 0, 3, 10, 0, 4, 128, 0, 0, 255, 128, 7, 0, + 85, 128, 88, 0, 0, 4, 3, 0, 6, 128, 7, 0, 0, 129, 7, + 0, 196, 128, 10, 0, 248, 128, 88, 0, 0, 4, 2, 0, 7, 128, + 7, 0, 170, 128, 3, 0, 228, 128, 2, 0, 228, 128, 88, 0, 0, + 4, 1, 0, 11, 128, 7, 0, 85, 128, 2, 0, 164, 128, 1, 0, + 228, 128, 8, 0, 0, 3, 0, 0, 8, 128, 1, 0, 244, 128, 3, + 0, 228, 160, 2, 0, 0, 3, 0, 0, 8, 128, 0, 0, 255, 129, + 1, 0, 170, 128, 2, 0, 0, 3, 1, 0, 7, 128, 0, 0, 255, + 128, 1, 0, 244, 128, 2, 0, 0, 3, 0, 0, 8, 128, 1, 0, + 85, 129, 1, 0, 0, 128, 88, 0, 0, 4, 2, 0, 3, 128, 0, + 0, 255, 128, 1, 0, 225, 128, 1, 0, 228, 128, 10, 0, 0, 3, + 0, 0, 8, 128, 1, 0, 170, 128, 2, 0, 0, 128, 11, 0, 0, + 3, 5, 0, 8, 128, 2, 0, 85, 128, 1, 0, 170, 128, 8, 0, + 0, 3, 1, 0, 8, 128, 1, 0, 228, 128, 3, 0, 228, 160, 2, + 0, 0, 3, 2, 0, 7, 128, 1, 0, 255, 129, 1, 0, 228, 128, + 5, 0, 0, 3, 2, 0, 7, 128, 1, 0, 255, 128, 2, 0, 228, + 128, 2, 0, 0, 3, 2, 0, 8, 128, 0, 0, 255, 129, 1, 0, + 255, 128, 6, 0, 0, 2, 2, 0, 8, 128, 2, 0, 255, 128, 4, + 0, 0, 4, 2, 0, 7, 128, 2, 0, 228, 128, 2, 0, 255, 128, + 1, 0, 255, 128, 88, 0, 0, 4, 1, 0, 7, 128, 0, 0, 255, + 128, 1, 0, 228, 128, 2, 0, 228, 128, 2, 0, 0, 3, 2, 0, + 7, 128, 1, 0, 255, 129, 1, 0, 228, 128, 2, 0, 0, 3, 0, + 0, 8, 128, 1, 0, 255, 129, 2, 0, 0, 160, 5, 0, 0, 3, + 2, 0, 7, 128, 0, 0, 255, 128, 2, 0, 228, 128, 2, 0, 0, + 3, 0, 0, 8, 128, 1, 0, 255, 129, 5, 0, 255, 128, 2, 0, + 0, 3, 2, 0, 8, 128, 5, 0, 255, 129, 2, 0, 0, 160, 6, + 0, 0, 2, 0, 0, 8, 128, 0, 0, 255, 128, 4, 0, 0, 4, + 2, 0, 7, 128, 2, 0, 228, 128, 0, 0, 255, 128, 1, 0, 255, + 128, 88, 0, 0, 4, 1, 0, 7, 128, 2, 0, 255, 128, 1, 0, + 228, 128, 2, 0, 228, 128, 88, 0, 0, 4, 0, 0, 7, 128, 8, + 0, 85, 129, 1, 0, 228, 128, 0, 0, 228, 128, 18, 0, 0, 4, + 1, 0, 7, 128, 3, 0, 255, 128, 0, 0, 228, 128, 5, 0, 228, + 128, 5, 0, 0, 3, 1, 0, 8, 128, 3, 0, 255, 128, 3, 0, + 255, 128, 88, 0, 0, 4, 1, 0, 8, 128, 1, 0, 255, 129, 2, + 0, 0, 160, 2, 0, 85, 160, 5, 0, 0, 3, 0, 0, 7, 128, + 4, 0, 255, 128, 1, 0, 228, 128, 5, 0, 0, 3, 0, 0, 8, + 128, 4, 0, 255, 128, 4, 0, 255, 128, 88, 0, 0, 4, 0, 0, + 8, 128, 0, 0, 255, 129, 2, 0, 0, 160, 2, 0, 85, 160, 2, + 0, 0, 3, 0, 0, 8, 128, 1, 0, 255, 128, 0, 0, 255, 128, + 88, 0, 0, 4, 4, 0, 7, 128, 0, 0, 255, 129, 0, 0, 228, + 128, 4, 0, 228, 128, 1, 0, 0, 2, 0, 8, 15, 128, 4, 0, + 228, 128, 255, 255, 0, 0, 83, 72, 68, 82, 252, 21, 0, 0, 64, + 0, 0, 0, 127, 5, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, + 0, 0, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 1, 0, + 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, + 85, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 1, 0, 0, 0, + 85, 85, 0, 0, 98, 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, + 0, 101, 0, 0, 3, 242, 32, 16, 0, 0, 0, 0, 0, 104, 0, + 0, 2, 9, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 0, + 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, + 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 69, 0, 0, + 9, 242, 0, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 1, 0, + 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0, 96, 16, 0, 1, + 0, 0, 0, 24, 0, 0, 7, 18, 0, 16, 0, 2, 0, 0, 0, + 58, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, + 0, 24, 0, 0, 7, 34, 0, 16, 0, 2, 0, 0, 0, 58, 0, + 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 60, + 0, 0, 7, 18, 0, 16, 0, 2, 0, 0, 0, 26, 0, 16, 0, + 2, 0, 0, 0, 10, 0, 16, 0, 2, 0, 0, 0, 31, 0, 4, + 3, 10, 0, 16, 0, 2, 0, 0, 0, 54, 0, 0, 5, 242, 32, + 16, 0, 0, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 62, + 0, 0, 1, 21, 0, 0, 1, 14, 0, 0, 7, 114, 0, 16, 0, + 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 246, 15, 16, + 0, 0, 0, 0, 0, 14, 0, 0, 7, 114, 0, 16, 0, 1, 0, + 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 246, 15, 16, 0, 1, + 0, 0, 0, 32, 0, 0, 8, 18, 0, 16, 0, 2, 0, 0, 0, + 10, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, 0, + 0, 12, 0, 0, 0, 31, 0, 4, 3, 10, 0, 16, 0, 2, 0, + 0, 0, 52, 0, 0, 7, 18, 0, 16, 0, 2, 0, 0, 0, 42, + 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, + 52, 0, 0, 7, 18, 0, 16, 0, 2, 0, 0, 0, 10, 0, 16, + 0, 1, 0, 0, 0, 10, 0, 16, 0, 2, 0, 0, 0, 51, 0, + 0, 7, 34, 0, 16, 0, 2, 0, 0, 0, 42, 0, 16, 0, 1, + 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 51, 0, 0, 7, + 34, 0, 16, 0, 2, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, + 0, 26, 0, 16, 0, 2, 0, 0, 0, 0, 0, 0, 8, 130, 0, + 16, 0, 2, 0, 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 2, + 0, 0, 0, 10, 0, 16, 0, 2, 0, 0, 0, 29, 0, 0, 7, + 18, 0, 16, 0, 3, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, + 0, 10, 0, 16, 0, 0, 0, 0, 0, 31, 0, 4, 3, 10, 0, + 16, 0, 3, 0, 0, 0, 0, 0, 0, 8, 242, 0, 16, 0, 3, + 0, 0, 0, 6, 10, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, + 150, 4, 16, 0, 0, 0, 0, 0, 49, 0, 0, 10, 114, 0, 16, + 0, 4, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 7, 16, 0, 3, + 0, 0, 0, 14, 0, 0, 7, 114, 0, 16, 0, 5, 0, 0, 0, + 246, 15, 16, 0, 2, 0, 0, 0, 22, 7, 16, 0, 3, 0, 0, + 0, 56, 0, 0, 7, 114, 0, 16, 0, 2, 0, 0, 0, 70, 2, + 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 5, 0, 0, 0, 55, + 0, 0, 9, 98, 0, 16, 0, 5, 0, 0, 0, 6, 0, 16, 0, + 4, 0, 0, 0, 6, 3, 16, 0, 2, 0, 0, 0, 6, 1, 16, + 0, 3, 0, 0, 0, 29, 0, 0, 7, 146, 0, 16, 0, 4, 0, + 0, 0, 166, 10, 16, 0, 0, 0, 0, 0, 86, 1, 16, 0, 0, + 0, 0, 0, 55, 0, 0, 9, 98, 0, 16, 0, 6, 0, 0, 0, + 86, 5, 16, 0, 4, 0, 0, 0, 246, 13, 16, 0, 2, 0, 0, + 0, 6, 1, 16, 0, 3, 0, 0, 0, 55, 0, 0, 9, 50, 0, + 16, 0, 3, 0, 0, 0, 166, 10, 16, 0, 4, 0, 0, 0, 230, + 10, 16, 0, 2, 0, 0, 0, 230, 10, 16, 0, 3, 0, 0, 0, + 54, 0, 0, 5, 18, 0, 16, 0, 6, 0, 0, 0, 1, 64, 0, + 0, 0, 0, 0, 0, 54, 0, 0, 5, 66, 0, 16, 0, 3, 0, + 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 55, 0, 0, 9, 114, + 0, 16, 0, 3, 0, 0, 0, 246, 15, 16, 0, 4, 0, 0, 0, + 70, 2, 16, 0, 6, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, + 0, 54, 0, 0, 5, 18, 0, 16, 0, 5, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 0, 0, 55, 0, 0, 9, 114, 0, 16, 0, 3, + 0, 0, 0, 6, 0, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, + 5, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, 0, 18, 0, 0, + 1, 0, 0, 0, 8, 242, 0, 16, 0, 4, 0, 0, 0, 86, 10, + 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 134, 1, 16, 0, 0, + 0, 0, 0, 49, 0, 0, 10, 114, 0, 16, 0, 5, 0, 0, 0, + 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 22, 7, 16, 0, 4, 0, 0, 0, 14, 0, + 0, 7, 114, 0, 16, 0, 6, 0, 0, 0, 246, 15, 16, 0, 2, + 0, 0, 0, 22, 7, 16, 0, 4, 0, 0, 0, 56, 0, 0, 7, + 114, 0, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, + 0, 70, 2, 16, 0, 6, 0, 0, 0, 55, 0, 0, 9, 82, 0, + 16, 0, 6, 0, 0, 0, 6, 0, 16, 0, 5, 0, 0, 0, 6, + 3, 16, 0, 2, 0, 0, 0, 6, 1, 16, 0, 4, 0, 0, 0, + 29, 0, 0, 7, 146, 0, 16, 0, 5, 0, 0, 0, 166, 10, 16, + 0, 0, 0, 0, 0, 6, 4, 16, 0, 0, 0, 0, 0, 55, 0, + 0, 9, 82, 0, 16, 0, 7, 0, 0, 0, 86, 5, 16, 0, 5, + 0, 0, 0, 246, 13, 16, 0, 2, 0, 0, 0, 6, 1, 16, 0, + 4, 0, 0, 0, 55, 0, 0, 9, 50, 0, 16, 0, 2, 0, 0, + 0, 166, 10, 16, 0, 5, 0, 0, 0, 182, 15, 16, 0, 2, 0, + 0, 0, 182, 15, 16, 0, 4, 0, 0, 0, 54, 0, 0, 5, 34, + 0, 16, 0, 7, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, + 54, 0, 0, 5, 66, 0, 16, 0, 2, 0, 0, 0, 1, 64, 0, + 0, 0, 0, 0, 0, 55, 0, 0, 9, 114, 0, 16, 0, 2, 0, + 0, 0, 246, 15, 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, 7, + 0, 0, 0, 70, 2, 16, 0, 2, 0, 0, 0, 54, 0, 0, 5, + 34, 0, 16, 0, 6, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, + 0, 55, 0, 0, 9, 114, 0, 16, 0, 3, 0, 0, 0, 6, 0, + 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, 6, 0, 0, 0, 70, + 2, 16, 0, 2, 0, 0, 0, 21, 0, 0, 1, 16, 0, 0, 10, + 18, 0, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, + 0, 2, 64, 0, 0, 154, 153, 153, 62, 61, 10, 23, 63, 174, 71, + 225, 61, 0, 0, 0, 0, 16, 0, 0, 10, 34, 0, 16, 0, 2, + 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, 0, 2, 64, 0, 0, + 154, 153, 153, 62, 61, 10, 23, 63, 174, 71, 225, 61, 0, 0, 0, + 0, 0, 0, 0, 8, 18, 0, 16, 0, 2, 0, 0, 0, 26, 0, + 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 10, 0, 16, 0, 2, + 0, 0, 0, 0, 0, 0, 7, 114, 0, 16, 0, 2, 0, 0, 0, + 6, 0, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, + 0, 16, 0, 0, 10, 130, 0, 16, 0, 2, 0, 0, 0, 70, 2, + 16, 0, 2, 0, 0, 0, 2, 64, 0, 0, 154, 153, 153, 62, 61, + 10, 23, 63, 174, 71, 225, 61, 0, 0, 0, 0, 51, 0, 0, 7, + 18, 0, 16, 0, 3, 0, 0, 0, 26, 0, 16, 0, 2, 0, 0, + 0, 10, 0, 16, 0, 2, 0, 0, 0, 51, 0, 0, 7, 18, 0, + 16, 0, 3, 0, 0, 0, 42, 0, 16, 0, 2, 0, 0, 0, 10, + 0, 16, 0, 3, 0, 0, 0, 52, 0, 0, 7, 34, 0, 16, 0, + 3, 0, 0, 0, 26, 0, 16, 0, 2, 0, 0, 0, 10, 0, 16, + 0, 2, 0, 0, 0, 52, 0, 0, 7, 34, 0, 16, 0, 3, 0, + 0, 0, 42, 0, 16, 0, 2, 0, 0, 0, 26, 0, 16, 0, 3, + 0, 0, 0, 49, 0, 0, 7, 66, 0, 16, 0, 3, 0, 0, 0, + 10, 0, 16, 0, 3, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 8, 114, 0, 16, 0, 4, 0, 0, 0, 246, 15, + 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 70, 2, 16, 0, 2, + 0, 0, 0, 56, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, + 246, 15, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, + 0, 0, 0, 0, 8, 18, 0, 16, 0, 3, 0, 0, 0, 58, 0, + 16, 0, 2, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 3, + 0, 0, 0, 14, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, + 70, 2, 16, 0, 4, 0, 0, 0, 6, 0, 16, 0, 3, 0, 0, + 0, 0, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 246, 15, + 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 55, + 0, 0, 9, 114, 0, 16, 0, 2, 0, 0, 0, 166, 10, 16, 0, + 3, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 70, 2, 16, + 0, 2, 0, 0, 0, 49, 0, 0, 7, 18, 0, 16, 0, 3, 0, + 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 26, 0, 16, 0, 3, + 0, 0, 0, 0, 0, 0, 8, 114, 0, 16, 0, 4, 0, 0, 0, + 246, 15, 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 70, 2, 16, + 0, 2, 0, 0, 0, 0, 0, 0, 8, 66, 0, 16, 0, 3, 0, + 0, 0, 58, 0, 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 1, + 64, 0, 0, 0, 0, 128, 63, 56, 0, 0, 7, 114, 0, 16, 0, + 4, 0, 0, 0, 166, 10, 16, 0, 3, 0, 0, 0, 70, 2, 16, + 0, 4, 0, 0, 0, 0, 0, 0, 8, 34, 0, 16, 0, 3, 0, + 0, 0, 58, 0, 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 26, + 0, 16, 0, 3, 0, 0, 0, 14, 0, 0, 7, 226, 0, 16, 0, + 3, 0, 0, 0, 6, 9, 16, 0, 4, 0, 0, 0, 86, 5, 16, + 0, 3, 0, 0, 0, 0, 0, 0, 7, 226, 0, 16, 0, 3, 0, + 0, 0, 246, 15, 16, 0, 2, 0, 0, 0, 86, 14, 16, 0, 3, + 0, 0, 0, 55, 0, 0, 9, 114, 0, 16, 0, 2, 0, 0, 0, + 6, 0, 16, 0, 3, 0, 0, 0, 150, 7, 16, 0, 3, 0, 0, + 0, 70, 2, 16, 0, 2, 0, 0, 0, 18, 0, 0, 1, 32, 0, + 0, 8, 130, 0, 16, 0, 2, 0, 0, 0, 10, 128, 32, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 13, 0, 0, 0, + 31, 0, 4, 3, 58, 0, 16, 0, 2, 0, 0, 0, 52, 0, 0, + 7, 130, 0, 16, 0, 2, 0, 0, 0, 42, 0, 16, 0, 0, 0, + 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 52, 0, 0, 7, 130, + 0, 16, 0, 2, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, + 58, 0, 16, 0, 2, 0, 0, 0, 51, 0, 0, 7, 18, 0, 16, + 0, 3, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 26, 0, + 16, 0, 0, 0, 0, 0, 51, 0, 0, 7, 18, 0, 16, 0, 3, + 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, + 3, 0, 0, 0, 0, 0, 0, 8, 130, 0, 16, 0, 3, 0, 0, + 0, 58, 0, 16, 0, 2, 0, 0, 0, 10, 0, 16, 128, 65, 0, + 0, 0, 3, 0, 0, 0, 29, 0, 0, 7, 130, 0, 16, 0, 2, + 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, + 1, 0, 0, 0, 31, 0, 4, 3, 58, 0, 16, 0, 2, 0, 0, + 0, 0, 0, 0, 8, 242, 0, 16, 0, 4, 0, 0, 0, 6, 10, + 16, 128, 65, 0, 0, 0, 1, 0, 0, 0, 150, 4, 16, 0, 1, + 0, 0, 0, 49, 0, 0, 10, 114, 0, 16, 0, 5, 0, 0, 0, + 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 22, 7, 16, 0, 4, 0, 0, 0, 14, 0, + 0, 7, 114, 0, 16, 0, 6, 0, 0, 0, 246, 15, 16, 0, 3, + 0, 0, 0, 22, 7, 16, 0, 4, 0, 0, 0, 56, 0, 0, 7, + 114, 0, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, + 0, 70, 2, 16, 0, 6, 0, 0, 0, 55, 0, 0, 9, 98, 0, + 16, 0, 6, 0, 0, 0, 6, 0, 16, 0, 5, 0, 0, 0, 6, + 3, 16, 0, 3, 0, 0, 0, 6, 1, 16, 0, 4, 0, 0, 0, + 29, 0, 0, 7, 146, 0, 16, 0, 5, 0, 0, 0, 166, 10, 16, + 0, 1, 0, 0, 0, 86, 1, 16, 0, 1, 0, 0, 0, 55, 0, + 0, 9, 98, 0, 16, 0, 7, 0, 0, 0, 86, 5, 16, 0, 5, + 0, 0, 0, 246, 13, 16, 0, 3, 0, 0, 0, 6, 1, 16, 0, + 4, 0, 0, 0, 55, 0, 0, 9, 50, 0, 16, 0, 4, 0, 0, + 0, 166, 10, 16, 0, 5, 0, 0, 0, 230, 10, 16, 0, 3, 0, + 0, 0, 230, 10, 16, 0, 4, 0, 0, 0, 54, 0, 0, 5, 18, + 0, 16, 0, 7, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, + 54, 0, 0, 5, 66, 0, 16, 0, 4, 0, 0, 0, 1, 64, 0, + 0, 0, 0, 0, 0, 55, 0, 0, 9, 114, 0, 16, 0, 4, 0, + 0, 0, 246, 15, 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, 7, + 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 54, 0, 0, 5, + 18, 0, 16, 0, 6, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, + 0, 55, 0, 0, 9, 114, 0, 16, 0, 4, 0, 0, 0, 6, 0, + 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, 6, 0, 0, 0, 70, + 2, 16, 0, 4, 0, 0, 0, 18, 0, 0, 1, 0, 0, 0, 8, + 242, 0, 16, 0, 5, 0, 0, 0, 86, 10, 16, 128, 65, 0, 0, + 0, 1, 0, 0, 0, 134, 1, 16, 0, 1, 0, 0, 0, 49, 0, + 0, 10, 114, 0, 16, 0, 6, 0, 0, 0, 2, 64, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 22, 7, 16, 0, 5, 0, 0, 0, 14, 0, 0, 7, 114, 0, 16, + 0, 7, 0, 0, 0, 246, 15, 16, 0, 3, 0, 0, 0, 22, 7, + 16, 0, 5, 0, 0, 0, 56, 0, 0, 7, 114, 0, 16, 0, 3, + 0, 0, 0, 70, 2, 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, + 7, 0, 0, 0, 55, 0, 0, 9, 82, 0, 16, 0, 7, 0, 0, + 0, 6, 0, 16, 0, 6, 0, 0, 0, 6, 3, 16, 0, 3, 0, + 0, 0, 6, 1, 16, 0, 5, 0, 0, 0, 29, 0, 0, 7, 146, + 0, 16, 0, 6, 0, 0, 0, 166, 10, 16, 0, 1, 0, 0, 0, + 6, 4, 16, 0, 1, 0, 0, 0, 55, 0, 0, 9, 82, 0, 16, + 0, 8, 0, 0, 0, 86, 5, 16, 0, 6, 0, 0, 0, 246, 13, + 16, 0, 3, 0, 0, 0, 6, 1, 16, 0, 5, 0, 0, 0, 55, + 0, 0, 9, 50, 0, 16, 0, 3, 0, 0, 0, 166, 10, 16, 0, + 6, 0, 0, 0, 182, 15, 16, 0, 3, 0, 0, 0, 182, 15, 16, + 0, 5, 0, 0, 0, 54, 0, 0, 5, 34, 0, 16, 0, 8, 0, + 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 66, + 0, 16, 0, 3, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, + 55, 0, 0, 9, 114, 0, 16, 0, 3, 0, 0, 0, 246, 15, 16, + 0, 6, 0, 0, 0, 70, 2, 16, 0, 8, 0, 0, 0, 70, 2, + 16, 0, 3, 0, 0, 0, 54, 0, 0, 5, 34, 0, 16, 0, 7, + 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 55, 0, 0, 9, + 114, 0, 16, 0, 4, 0, 0, 0, 6, 0, 16, 0, 6, 0, 0, + 0, 70, 2, 16, 0, 7, 0, 0, 0, 70, 2, 16, 0, 3, 0, + 0, 0, 21, 0, 0, 1, 16, 0, 0, 10, 130, 0, 16, 0, 2, + 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 2, 64, 0, 0, + 154, 153, 153, 62, 61, 10, 23, 63, 174, 71, 225, 61, 0, 0, 0, + 0, 16, 0, 0, 10, 18, 0, 16, 0, 3, 0, 0, 0, 70, 2, + 16, 0, 4, 0, 0, 0, 2, 64, 0, 0, 154, 153, 153, 62, 61, + 10, 23, 63, 174, 71, 225, 61, 0, 0, 0, 0, 0, 0, 0, 8, + 130, 0, 16, 0, 2, 0, 0, 0, 58, 0, 16, 0, 2, 0, 0, + 0, 10, 0, 16, 128, 65, 0, 0, 0, 3, 0, 0, 0, 0, 0, + 0, 7, 114, 0, 16, 0, 3, 0, 0, 0, 246, 15, 16, 0, 2, + 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 16, 0, 0, 10, + 130, 0, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, + 0, 2, 64, 0, 0, 154, 153, 153, 62, 61, 10, 23, 63, 174, 71, + 225, 61, 0, 0, 0, 0, 51, 0, 0, 7, 130, 0, 16, 0, 3, + 0, 0, 0, 26, 0, 16, 0, 3, 0, 0, 0, 10, 0, 16, 0, + 3, 0, 0, 0, 51, 0, 0, 7, 130, 0, 16, 0, 3, 0, 0, + 0, 42, 0, 16, 0, 3, 0, 0, 0, 58, 0, 16, 0, 3, 0, + 0, 0, 52, 0, 0, 7, 18, 0, 16, 0, 4, 0, 0, 0, 26, + 0, 16, 0, 3, 0, 0, 0, 10, 0, 16, 0, 3, 0, 0, 0, + 52, 0, 0, 7, 18, 0, 16, 0, 4, 0, 0, 0, 42, 0, 16, + 0, 3, 0, 0, 0, 10, 0, 16, 0, 4, 0, 0, 0, 49, 0, + 0, 7, 34, 0, 16, 0, 4, 0, 0, 0, 58, 0, 16, 0, 3, + 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, + 114, 0, 16, 0, 5, 0, 0, 0, 246, 15, 16, 128, 65, 0, 0, + 0, 2, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, 0, 56, 0, + 0, 7, 114, 0, 16, 0, 5, 0, 0, 0, 246, 15, 16, 0, 2, + 0, 0, 0, 70, 2, 16, 0, 5, 0, 0, 0, 0, 0, 0, 8, + 130, 0, 16, 0, 3, 0, 0, 0, 58, 0, 16, 0, 2, 0, 0, + 0, 58, 0, 16, 128, 65, 0, 0, 0, 3, 0, 0, 0, 14, 0, + 0, 7, 114, 0, 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, 5, + 0, 0, 0, 246, 15, 16, 0, 3, 0, 0, 0, 0, 0, 0, 7, + 114, 0, 16, 0, 5, 0, 0, 0, 246, 15, 16, 0, 2, 0, 0, + 0, 70, 2, 16, 0, 5, 0, 0, 0, 55, 0, 0, 9, 114, 0, + 16, 0, 3, 0, 0, 0, 86, 5, 16, 0, 4, 0, 0, 0, 70, + 2, 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, 0, + 49, 0, 0, 7, 130, 0, 16, 0, 3, 0, 0, 0, 1, 64, 0, + 0, 0, 0, 128, 63, 10, 0, 16, 0, 4, 0, 0, 0, 0, 0, + 0, 8, 226, 0, 16, 0, 4, 0, 0, 0, 246, 15, 16, 128, 65, + 0, 0, 0, 2, 0, 0, 0, 6, 9, 16, 0, 3, 0, 0, 0, + 0, 0, 0, 8, 18, 0, 16, 0, 5, 0, 0, 0, 58, 0, 16, + 128, 65, 0, 0, 0, 2, 0, 0, 0, 1, 64, 0, 0, 0, 0, + 128, 63, 56, 0, 0, 7, 226, 0, 16, 0, 4, 0, 0, 0, 86, + 14, 16, 0, 4, 0, 0, 0, 6, 0, 16, 0, 5, 0, 0, 0, + 0, 0, 0, 8, 18, 0, 16, 0, 4, 0, 0, 0, 58, 0, 16, + 128, 65, 0, 0, 0, 2, 0, 0, 0, 10, 0, 16, 0, 4, 0, + 0, 0, 14, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 150, + 7, 16, 0, 4, 0, 0, 0, 6, 0, 16, 0, 4, 0, 0, 0, + 0, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 246, 15, 16, + 0, 2, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 55, 0, + 0, 9, 114, 0, 16, 0, 2, 0, 0, 0, 246, 15, 16, 0, 3, + 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, + 3, 0, 0, 0, 18, 0, 0, 1, 32, 0, 0, 8, 130, 0, 16, + 0, 2, 0, 0, 0, 10, 128, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 64, 0, 0, 14, 0, 0, 0, 31, 0, 4, 3, 58, + 0, 16, 0, 2, 0, 0, 0, 16, 0, 0, 10, 130, 0, 16, 0, + 2, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 2, 64, 0, + 0, 154, 153, 153, 62, 61, 10, 23, 63, 174, 71, 225, 61, 0, 0, + 0, 0, 16, 0, 0, 10, 18, 0, 16, 0, 3, 0, 0, 0, 70, + 2, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 154, 153, 153, 62, + 61, 10, 23, 63, 174, 71, 225, 61, 0, 0, 0, 0, 0, 0, 0, + 8, 130, 0, 16, 0, 2, 0, 0, 0, 58, 0, 16, 0, 2, 0, + 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 7, 114, 0, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, + 0, 0, 0, 0, 246, 15, 16, 0, 2, 0, 0, 0, 16, 0, 0, + 10, 130, 0, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 3, 0, + 0, 0, 2, 64, 0, 0, 154, 153, 153, 62, 61, 10, 23, 63, 174, + 71, 225, 61, 0, 0, 0, 0, 51, 0, 0, 7, 130, 0, 16, 0, + 3, 0, 0, 0, 26, 0, 16, 0, 3, 0, 0, 0, 10, 0, 16, + 0, 3, 0, 0, 0, 51, 0, 0, 7, 130, 0, 16, 0, 3, 0, + 0, 0, 42, 0, 16, 0, 3, 0, 0, 0, 58, 0, 16, 0, 3, + 0, 0, 0, 52, 0, 0, 7, 18, 0, 16, 0, 4, 0, 0, 0, + 26, 0, 16, 0, 3, 0, 0, 0, 10, 0, 16, 0, 3, 0, 0, + 0, 52, 0, 0, 7, 18, 0, 16, 0, 4, 0, 0, 0, 42, 0, + 16, 0, 3, 0, 0, 0, 10, 0, 16, 0, 4, 0, 0, 0, 49, + 0, 0, 7, 34, 0, 16, 0, 4, 0, 0, 0, 58, 0, 16, 0, + 3, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 8, 114, 0, 16, 0, 5, 0, 0, 0, 246, 15, 16, 128, 65, 0, + 0, 0, 2, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, 0, 56, + 0, 0, 7, 114, 0, 16, 0, 5, 0, 0, 0, 246, 15, 16, 0, + 2, 0, 0, 0, 70, 2, 16, 0, 5, 0, 0, 0, 0, 0, 0, + 8, 130, 0, 16, 0, 3, 0, 0, 0, 58, 0, 16, 0, 2, 0, + 0, 0, 58, 0, 16, 128, 65, 0, 0, 0, 3, 0, 0, 0, 14, + 0, 0, 7, 114, 0, 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, + 5, 0, 0, 0, 246, 15, 16, 0, 3, 0, 0, 0, 0, 0, 0, + 7, 114, 0, 16, 0, 5, 0, 0, 0, 246, 15, 16, 0, 2, 0, + 0, 0, 70, 2, 16, 0, 5, 0, 0, 0, 55, 0, 0, 9, 114, + 0, 16, 0, 3, 0, 0, 0, 86, 5, 16, 0, 4, 0, 0, 0, + 70, 2, 16, 0, 5, 0, 0, 0, 70, 2, 16, 0, 3, 0, 0, + 0, 49, 0, 0, 7, 130, 0, 16, 0, 3, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 63, 10, 0, 16, 0, 4, 0, 0, 0, 0, + 0, 0, 8, 226, 0, 16, 0, 4, 0, 0, 0, 246, 15, 16, 128, + 65, 0, 0, 0, 2, 0, 0, 0, 6, 9, 16, 0, 3, 0, 0, + 0, 0, 0, 0, 8, 18, 0, 16, 0, 5, 0, 0, 0, 58, 0, + 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 1, 64, 0, 0, 0, + 0, 128, 63, 56, 0, 0, 7, 226, 0, 16, 0, 4, 0, 0, 0, + 86, 14, 16, 0, 4, 0, 0, 0, 6, 0, 16, 0, 5, 0, 0, + 0, 0, 0, 0, 8, 18, 0, 16, 0, 4, 0, 0, 0, 58, 0, + 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 10, 0, 16, 0, 4, + 0, 0, 0, 14, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, + 150, 7, 16, 0, 4, 0, 0, 0, 6, 0, 16, 0, 4, 0, 0, + 0, 0, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 246, 15, + 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 55, + 0, 0, 9, 114, 0, 16, 0, 2, 0, 0, 0, 246, 15, 16, 0, + 3, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 70, 2, 16, + 0, 3, 0, 0, 0, 18, 0, 0, 1, 16, 0, 0, 10, 130, 0, + 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 2, + 64, 0, 0, 154, 153, 153, 62, 61, 10, 23, 63, 174, 71, 225, 61, + 0, 0, 0, 0, 16, 0, 0, 10, 18, 0, 16, 0, 3, 0, 0, + 0, 70, 2, 16, 0, 1, 0, 0, 0, 2, 64, 0, 0, 154, 153, + 153, 62, 61, 10, 23, 63, 174, 71, 225, 61, 0, 0, 0, 0, 0, + 0, 0, 8, 130, 0, 16, 0, 2, 0, 0, 0, 58, 0, 16, 0, + 2, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 3, 0, 0, + 0, 0, 0, 0, 7, 114, 0, 16, 0, 1, 0, 0, 0, 70, 2, + 16, 0, 1, 0, 0, 0, 246, 15, 16, 0, 2, 0, 0, 0, 16, + 0, 0, 10, 130, 0, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, + 1, 0, 0, 0, 2, 64, 0, 0, 154, 153, 153, 62, 61, 10, 23, + 63, 174, 71, 225, 61, 0, 0, 0, 0, 51, 0, 0, 7, 18, 0, + 16, 0, 3, 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 10, + 0, 16, 0, 1, 0, 0, 0, 51, 0, 0, 7, 18, 0, 16, 0, + 3, 0, 0, 0, 42, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, + 0, 3, 0, 0, 0, 52, 0, 0, 7, 34, 0, 16, 0, 3, 0, + 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 1, + 0, 0, 0, 52, 0, 0, 7, 34, 0, 16, 0, 3, 0, 0, 0, + 42, 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 0, 3, 0, 0, + 0, 49, 0, 0, 7, 66, 0, 16, 0, 3, 0, 0, 0, 10, 0, + 16, 0, 3, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 8, 114, 0, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, + 1, 0, 0, 0, 246, 15, 16, 128, 65, 0, 0, 0, 2, 0, 0, + 0, 56, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 246, 15, + 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 0, + 0, 0, 8, 18, 0, 16, 0, 3, 0, 0, 0, 58, 0, 16, 0, + 2, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 3, 0, 0, + 0, 14, 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 70, 2, + 16, 0, 4, 0, 0, 0, 6, 0, 16, 0, 3, 0, 0, 0, 0, + 0, 0, 7, 114, 0, 16, 0, 4, 0, 0, 0, 246, 15, 16, 0, + 2, 0, 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 55, 0, 0, + 9, 114, 0, 16, 0, 1, 0, 0, 0, 166, 10, 16, 0, 3, 0, + 0, 0, 70, 2, 16, 0, 4, 0, 0, 0, 70, 2, 16, 0, 1, + 0, 0, 0, 49, 0, 0, 7, 18, 0, 16, 0, 3, 0, 0, 0, + 1, 64, 0, 0, 0, 0, 128, 63, 26, 0, 16, 0, 3, 0, 0, + 0, 0, 0, 0, 8, 114, 0, 16, 0, 4, 0, 0, 0, 246, 15, + 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 70, 2, 16, 0, 1, + 0, 0, 0, 0, 0, 0, 8, 66, 0, 16, 0, 3, 0, 0, 0, + 58, 0, 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 1, 64, 0, + 0, 0, 0, 128, 63, 56, 0, 0, 7, 114, 0, 16, 0, 4, 0, + 0, 0, 166, 10, 16, 0, 3, 0, 0, 0, 70, 2, 16, 0, 4, + 0, 0, 0, 0, 0, 0, 8, 34, 0, 16, 0, 3, 0, 0, 0, + 58, 0, 16, 128, 65, 0, 0, 0, 2, 0, 0, 0, 26, 0, 16, + 0, 3, 0, 0, 0, 14, 0, 0, 7, 226, 0, 16, 0, 3, 0, + 0, 0, 6, 9, 16, 0, 4, 0, 0, 0, 86, 5, 16, 0, 3, + 0, 0, 0, 0, 0, 0, 7, 226, 0, 16, 0, 3, 0, 0, 0, + 246, 15, 16, 0, 2, 0, 0, 0, 86, 14, 16, 0, 3, 0, 0, + 0, 55, 0, 0, 9, 114, 0, 16, 0, 2, 0, 0, 0, 6, 0, + 16, 0, 3, 0, 0, 0, 150, 7, 16, 0, 3, 0, 0, 0, 70, + 2, 16, 0, 1, 0, 0, 0, 21, 0, 0, 1, 21, 0, 0, 1, + 21, 0, 0, 1, 0, 0, 0, 8, 18, 0, 16, 0, 1, 0, 0, + 0, 58, 0, 16, 128, 65, 0, 0, 0, 1, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 63, 56, 0, 0, 7, 226, 0, 16, 0, 1, + 0, 0, 0, 246, 15, 16, 0, 1, 0, 0, 0, 6, 9, 16, 0, + 2, 0, 0, 0, 50, 0, 0, 9, 114, 0, 16, 0, 0, 0, 0, + 0, 6, 0, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 0, 0, + 0, 0, 150, 7, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 114, + 32, 16, 0, 0, 0, 0, 0, 246, 15, 16, 0, 0, 0, 0, 0, + 70, 2, 16, 0, 0, 0, 0, 0, 54, 0, 0, 5, 130, 32, 16, + 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 62, 0, + 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, 195, 0, 0, 0, 9, + 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 128, 0, 0, 0, + 3, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, + 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, 100, 1, 0, 0, 1, + 0, 0, 0, 232, 0, 0, 0, 5, 0, 0, 0, 28, 0, 0, 0, + 0, 4, 255, 255, 0, 1, 0, 0, 48, 1, 0, 0, 188, 0, 0, + 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 197, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 209, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 12, + 0, 0, 0, 213, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, + 4, 0, 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 1, 0, 0, + 0, 12, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 115, 83, 97, 109, 112, 108, 101, 114, + 0, 115, 66, 99, 107, 83, 97, 109, 112, 108, 101, 114, 0, 116, 101, + 120, 0, 98, 99, 107, 116, 101, 120, 0, 36, 71, 108, 111, 98, 97, + 108, 115, 0, 171, 171, 171, 220, 0, 0, 0, 1, 0, 0, 0, 0, + 1, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 24, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, + 0, 32, 1, 0, 0, 0, 0, 0, 0, 98, 108, 101, 110, 100, 111, + 112, 0, 0, 0, 19, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, + 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, + 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, + 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 73, 83, 71, 78, 104, + 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 12, 0, 0, 0, 83, 86, 95, 80, 111, 115, 105, + 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, + 171, 171, 79, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, + 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, + 84, 97, 114, 103, 101, 116, 0, 171, 171, 176, 56, 0, 0, 0, 0, + 0, 0, 83, 97, 109, 112, 108, 101, 82, 97, 100, 105, 97, 108, 71, + 114, 97, 100, 105, 101, 110, 116, 0, 65, 80, 111, 115, 0, 44, 7, + 0, 0, 68, 88, 66, 67, 172, 27, 205, 113, 176, 254, 27, 44, 22, + 107, 179, 112, 127, 38, 148, 161, 1, 0, 0, 0, 44, 7, 0, 0, + 6, 0, 0, 0, 56, 0, 0, 0, 148, 1, 0, 0, 104, 3, 0, + 0, 228, 3, 0, 0, 136, 6, 0, 0, 188, 6, 0, 0, 65, 111, + 110, 57, 84, 1, 0, 0, 84, 1, 0, 0, 0, 2, 254, 255, 252, + 0, 0, 0, 88, 0, 0, 0, 4, 0, 36, 0, 0, 0, 84, 0, + 0, 0, 84, 0, 0, 0, 36, 0, 1, 0, 84, 0, 0, 0, 0, + 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, + 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 3, 0, 0, + 0, 0, 0, 1, 0, 3, 0, 1, 0, 5, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0, 5, 6, 0, 15, + 160, 0, 0, 128, 63, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, + 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, 4, + 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, 160, + 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 3, 128, 0, 0, 228, + 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0, 0, 3, 0, 0, + 4, 128, 0, 0, 0, 128, 6, 0, 0, 160, 5, 0, 0, 3, 0, + 0, 4, 128, 0, 0, 170, 128, 5, 0, 0, 160, 5, 0, 0, 3, + 1, 0, 1, 128, 0, 0, 170, 128, 6, 0, 85, 160, 2, 0, 0, + 3, 0, 0, 4, 128, 0, 0, 85, 129, 6, 0, 0, 160, 2, 0, + 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, 160, 5, + 0, 0, 3, 0, 0, 1, 128, 0, 0, 170, 128, 5, 0, 85, 160, + 5, 0, 0, 3, 1, 0, 2, 128, 0, 0, 0, 128, 6, 0, 85, + 160, 1, 0, 0, 2, 1, 0, 4, 128, 6, 0, 0, 160, 8, 0, + 0, 3, 0, 0, 8, 224, 1, 0, 228, 128, 3, 0, 228, 160, 8, + 0, 0, 3, 0, 0, 4, 224, 1, 0, 228, 128, 4, 0, 228, 160, + 1, 0, 0, 2, 0, 0, 12, 192, 6, 0, 36, 160, 255, 255, 0, + 0, 83, 72, 68, 82, 204, 1, 0, 0, 64, 0, 1, 0, 115, 0, + 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 3, + 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 1, 0, 0, 0, + 4, 0, 0, 0, 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0, + 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101, + 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, 0, 104, 0, 0, 2, + 2, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16, 0, 0, 0, 0, + 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50, 0, 16, 0, 0, + 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 54, 0, 0, 5, 50, 32, 16, 0, 0, 0, + 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 7, 18, + 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, 128, 63, 0, 0, 0, 8, 34, 0, 16, + 0, 0, 0, 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 0, 0, + 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 56, 0, 0, 8, 50, + 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, + 70, 128, 32, 0, 1, 0, 0, 0, 3, 0, 0, 0, 56, 0, 0, + 10, 50, 0, 16, 0, 1, 0, 0, 0, 70, 0, 16, 0, 0, 0, + 0, 0, 2, 64, 0, 0, 0, 0, 0, 63, 0, 0, 0, 63, 0, + 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 66, 0, 16, 0, + 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 16, 0, 0, + 8, 66, 32, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, + 0, 0, 70, 130, 32, 0, 1, 0, 0, 0, 0, 0, 0, 0, 16, + 0, 0, 8, 130, 32, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, + 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, 0, 1, 0, 0, + 0, 50, 0, 0, 11, 50, 32, 16, 0, 1, 0, 0, 0, 70, 16, + 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, 12, 0, 0, + 0, 2, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 8, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, 156, 2, 0, + 0, 2, 0, 0, 0, 100, 0, 0, 0, 2, 0, 0, 0, 28, 0, + 0, 0, 0, 4, 254, 255, 0, 1, 0, 0, 103, 2, 0, 0, 92, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 99, 98, 48, 0, 99, 98, 50, 0, 92, 0, 0, 0, + 4, 0, 0, 0, 148, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 96, 0, 0, 0, 7, 0, 0, 0, 52, 1, + 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 244, + 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 16, 1, 0, 0, 16, 0, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 26, 1, 0, 0, 32, 0, 0, 0, 16, 0, 0, 0, 2, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0, + 48, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, 101, 115, 99, 0, 171, + 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 77, 97, + 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 84, 101, 120, + 116, 67, 111, 108, 111, 114, 0, 171, 171, 220, 1, 0, 0, 0, 0, + 0, 0, 44, 0, 0, 0, 2, 0, 0, 0, 244, 1, 0, 0, 0, + 0, 0, 0, 4, 2, 0, 0, 48, 0, 0, 0, 8, 0, 0, 0, + 2, 0, 0, 0, 16, 2, 0, 0, 0, 0, 0, 0, 32, 2, 0, + 0, 64, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 40, 2, + 0, 0, 0, 0, 0, 0, 56, 2, 0, 0, 80, 0, 0, 0, 8, + 0, 0, 0, 0, 0, 0, 0, 16, 2, 0, 0, 0, 0, 0, 0, + 64, 2, 0, 0, 88, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, + 0, 68, 2, 0, 0, 0, 0, 0, 0, 84, 2, 0, 0, 92, 0, + 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 68, 2, 0, 0, 0, + 0, 0, 0, 92, 2, 0, 0, 96, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 68, 2, 0, 0, 0, 0, 0, 0, 68, 101, 118, + 105, 99, 101, 83, 112, 97, 99, 101, 84, 111, 85, 115, 101, 114, 83, + 112, 97, 99, 101, 0, 171, 3, 0, 3, 0, 3, 0, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 100, 105, 109, 101, 110, 115, 105, 111, + 110, 115, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 100, 105, 102, 102, 0, 171, 171, 171, 1, 0, + 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, + 101, 110, 116, 101, 114, 49, 0, 65, 0, 171, 171, 0, 0, 3, 0, + 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 97, 100, + 105, 117, 115, 49, 0, 115, 113, 95, 114, 97, 100, 105, 117, 115, 49, + 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, + 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, + 49, 54, 51, 56, 52, 0, 171, 171, 171, 73, 83, 71, 78, 44, 0, + 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 7, 3, 0, 0, 80, 79, 83, 73, 84, 73, 79, 78, 0, 171, 171, + 171, 79, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, + 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, + 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, + 0, 3, 12, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 3, 0, 0, 83, + 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, + 79, 79, 82, 68, 0, 171, 171, 171, 174, 94, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 224, 9, + 0, 0, 68, 88, 66, 67, 76, 106, 34, 250, 169, 50, 124, 43, 130, + 255, 198, 178, 126, 127, 40, 188, 1, 0, 0, 0, 224, 9, 0, 0, + 6, 0, 0, 0, 56, 0, 0, 0, 128, 2, 0, 0, 88, 6, 0, + 0, 212, 6, 0, 0, 60, 9, 0, 0, 172, 9, 0, 0, 65, 111, + 110, 57, 64, 2, 0, 0, 64, 2, 0, 0, 0, 2, 255, 255, 8, + 2, 0, 0, 56, 0, 0, 0, 1, 0, 44, 0, 0, 0, 56, 0, + 0, 0, 56, 0, 2, 0, 36, 0, 0, 0, 56, 0, 0, 0, 0, + 0, 1, 1, 1, 0, 0, 0, 4, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 1, 2, 255, 255, 81, 0, 0, 5, 3, 0, 15, 160, 0, + 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 81, 0, 0, 5, 4, 0, 15, 160, 0, 0, 128, 63, 0, 0, 128, + 191, 0, 0, 0, 0, 0, 0, 0, 128, 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, 0, 0, 144, 0, + 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1, 8, 15, 160, + 2, 0, 0, 3, 0, 0, 3, 128, 0, 0, 235, 176, 1, 0, 228, + 161, 90, 0, 0, 4, 0, 0, 8, 128, 0, 0, 228, 128, 0, 0, + 228, 128, 2, 0, 0, 161, 5, 0, 0, 3, 0, 0, 8, 128, 0, + 0, 255, 128, 1, 0, 170, 160, 1, 0, 0, 2, 0, 0, 4, 128, + 1, 0, 255, 160, 8, 0, 0, 3, 0, 0, 1, 128, 0, 0, 228, + 128, 0, 0, 228, 160, 4, 0, 0, 4, 0, 0, 2, 128, 0, 0, + 0, 128, 0, 0, 0, 128, 0, 0, 255, 129, 35, 0, 0, 2, 0, + 0, 4, 128, 0, 0, 85, 128, 7, 0, 0, 2, 0, 0, 4, 128, + 0, 0, 170, 128, 6, 0, 0, 2, 1, 0, 1, 128, 0, 0, 170, + 128, 1, 0, 0, 2, 1, 0, 6, 128, 1, 0, 0, 129, 2, 0, + 0, 3, 0, 0, 13, 128, 0, 0, 0, 128, 1, 0, 148, 128, 6, + 0, 0, 2, 1, 0, 1, 128, 1, 0, 170, 160, 5, 0, 0, 3, + 0, 0, 13, 128, 0, 0, 228, 128, 1, 0, 0, 128, 1, 0, 0, + 2, 1, 0, 8, 128, 1, 0, 255, 160, 4, 0, 0, 4, 1, 0, + 7, 128, 0, 0, 248, 128, 0, 0, 170, 160, 1, 0, 255, 128, 88, + 0, 0, 4, 2, 0, 1, 128, 1, 0, 0, 128, 0, 0, 0, 128, + 0, 0, 255, 128, 88, 0, 0, 4, 0, 0, 13, 128, 1, 0, 148, + 128, 4, 0, 68, 160, 4, 0, 230, 160, 1, 0, 0, 2, 2, 0, + 2, 128, 3, 0, 0, 160, 66, 0, 0, 3, 1, 0, 15, 128, 0, + 0, 228, 176, 1, 8, 228, 160, 66, 0, 0, 3, 2, 0, 15, 128, + 2, 0, 228, 128, 0, 8, 228, 160, 5, 0, 0, 3, 2, 0, 7, + 128, 2, 0, 255, 128, 2, 0, 228, 128, 5, 0, 0, 3, 1, 0, + 15, 128, 1, 0, 255, 128, 2, 0, 228, 128, 2, 0, 0, 3, 0, + 0, 8, 128, 0, 0, 255, 128, 0, 0, 0, 128, 88, 0, 0, 4, + 0, 0, 1, 128, 0, 0, 255, 128, 0, 0, 0, 128, 0, 0, 170, + 128, 88, 0, 0, 4, 1, 0, 15, 128, 0, 0, 0, 129, 4, 0, + 170, 160, 1, 0, 228, 128, 88, 0, 0, 4, 0, 0, 15, 128, 0, + 0, 85, 128, 1, 0, 228, 128, 4, 0, 170, 160, 1, 0, 0, 2, + 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0, 83, 72, 68, + 82, 208, 3, 0, 0, 64, 0, 0, 0, 244, 0, 0, 0, 89, 0, + 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 7, 0, 0, 0, 90, + 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 90, 0, 0, 3, + 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, 0, 4, 0, 112, 16, + 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, 24, 0, 4, 0, 112, + 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, 98, 16, 0, 3, 50, + 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, 3, 194, 16, 16, 0, + 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, 0, 0, 0, 0, + 0, 104, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 9, 50, 0, + 16, 0, 0, 0, 0, 0, 230, 26, 16, 0, 1, 0, 0, 0, 70, + 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, + 54, 0, 0, 6, 66, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, + 0, 0, 0, 0, 0, 5, 0, 0, 0, 16, 0, 0, 8, 66, 0, + 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 70, + 130, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0, 15, 0, 0, 7, + 18, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, + 0, 70, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 9, 18, 0, + 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, + 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, + 56, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, + 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0, 0, 5, 0, + 0, 0, 50, 0, 0, 10, 18, 0, 16, 0, 0, 0, 0, 0, 42, + 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, + 10, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 49, 0, 0, + 7, 34, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 75, 0, 0, 6, 18, + 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0, 0, + 0, 0, 0, 0, 54, 0, 0, 6, 34, 0, 16, 0, 1, 0, 0, + 0, 10, 0, 16, 128, 65, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 7, 82, 0, 16, 0, 0, 0, 0, 0, 166, 10, 16, 0, 0, + 0, 0, 0, 6, 1, 16, 0, 1, 0, 0, 0, 14, 0, 0, 8, + 82, 0, 16, 0, 0, 0, 0, 0, 6, 2, 16, 0, 0, 0, 0, + 0, 166, 138, 32, 0, 0, 0, 0, 0, 5, 0, 0, 0, 56, 0, + 0, 8, 50, 0, 16, 0, 1, 0, 0, 0, 134, 0, 16, 0, 0, + 0, 0, 0, 166, 138, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0, + 29, 0, 0, 9, 50, 0, 16, 0, 1, 0, 0, 0, 70, 0, 16, + 0, 1, 0, 0, 0, 246, 143, 32, 128, 65, 0, 0, 0, 0, 0, + 0, 0, 5, 0, 0, 0, 1, 0, 0, 10, 50, 0, 16, 0, 1, + 0, 0, 0, 70, 0, 16, 0, 1, 0, 0, 0, 2, 64, 0, 0, + 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 42, 0, + 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, + 0, 0, 0, 50, 0, 0, 9, 18, 0, 16, 0, 2, 0, 0, 0, + 10, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, + 0, 42, 0, 16, 0, 0, 0, 0, 0, 54, 0, 0, 5, 34, 0, + 16, 0, 2, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 63, 69, + 0, 0, 9, 242, 0, 16, 0, 2, 0, 0, 0, 70, 0, 16, 0, + 2, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, + 0, 0, 0, 0, 0, 31, 0, 4, 3, 26, 0, 16, 0, 0, 0, + 0, 0, 54, 0, 0, 8, 242, 32, 16, 0, 0, 0, 0, 0, 2, + 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 62, 0, 0, 1, 21, 0, 0, 1, 52, 0, 0, + 7, 18, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 1, 0, + 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 29, 0, 0, 7, 18, + 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 31, 0, 4, 3, 10, 0, 16, + 0, 0, 0, 0, 0, 54, 0, 0, 8, 242, 32, 16, 0, 0, 0, + 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 21, 0, 0, 1, + 56, 0, 0, 7, 114, 0, 16, 0, 2, 0, 0, 0, 246, 15, 16, + 0, 2, 0, 0, 0, 70, 2, 16, 0, 2, 0, 0, 0, 69, 0, + 0, 9, 242, 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 1, + 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0, 96, 16, 0, + 1, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16, 0, 0, 0, 0, + 0, 246, 15, 16, 0, 0, 0, 0, 0, 70, 14, 16, 0, 2, 0, + 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, 33, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 19, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, + 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, 96, + 2, 0, 0, 1, 0, 0, 0, 224, 0, 0, 0, 5, 0, 0, 0, + 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0, 43, 2, 0, + 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 197, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 210, 0, 0, 0, 2, 0, 0, 0, 5, 0, + 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 1, + 0, 0, 0, 12, 0, 0, 0, 214, 0, 0, 0, 2, 0, 0, 0, + 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 1, 0, 0, + 0, 1, 0, 0, 0, 12, 0, 0, 0, 219, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 115, 83, 97, 109, + 112, 108, 101, 114, 0, 115, 77, 97, 115, 107, 83, 97, 109, 112, 108, + 101, 114, 0, 116, 101, 120, 0, 109, 97, 115, 107, 0, 99, 98, 50, + 0, 171, 219, 0, 0, 0, 7, 0, 0, 0, 248, 0, 0, 0, 112, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 160, 1, 0, 0, + 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 184, 1, 0, + 0, 0, 0, 0, 0, 200, 1, 0, 0, 48, 0, 0, 0, 8, 0, + 0, 0, 0, 0, 0, 0, 212, 1, 0, 0, 0, 0, 0, 0, 228, + 1, 0, 0, 64, 0, 0, 0, 12, 0, 0, 0, 2, 0, 0, 0, + 236, 1, 0, 0, 0, 0, 0, 0, 252, 1, 0, 0, 80, 0, 0, + 0, 8, 0, 0, 0, 2, 0, 0, 0, 212, 1, 0, 0, 0, 0, + 0, 0, 4, 2, 0, 0, 88, 0, 0, 0, 4, 0, 0, 0, 2, + 0, 0, 0, 8, 2, 0, 0, 0, 0, 0, 0, 24, 2, 0, 0, + 92, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 8, 2, 0, + 0, 0, 0, 0, 0, 32, 2, 0, 0, 96, 0, 0, 0, 4, 0, + 0, 0, 2, 0, 0, 0, 8, 2, 0, 0, 0, 0, 0, 0, 68, + 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84, 111, 85, 115, 101, + 114, 83, 112, 97, 99, 101, 0, 171, 3, 0, 3, 0, 3, 0, 3, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 109, 101, 110, 115, + 105, 111, 110, 115, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 100, 105, 102, 102, 0, 171, 171, 171, + 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 99, 101, 110, 116, 101, 114, 49, 0, 65, 0, 171, 171, 0, 0, + 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, + 97, 100, 105, 117, 115, 49, 0, 115, 113, 95, 114, 97, 100, 105, 117, + 115, 49, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, + 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, + 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, + 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 171, 73, 83, 71, 78, + 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 3, 0, 0, + 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, + 0, 1, 0, 0, 0, 12, 12, 0, 0, 83, 86, 95, 80, 111, 115, + 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, + 171, 171, 171, 79, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, + 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 83, 86, + 95, 84, 97, 114, 103, 101, 116, 0, 171, 171, 242, 101, 0, 0, 0, + 0, 0, 0, 65, 48, 0, 44, 7, 0, 0, 68, 88, 66, 67, 172, + 27, 205, 113, 176, 254, 27, 44, 22, 107, 179, 112, 127, 38, 148, 161, + 1, 0, 0, 0, 44, 7, 0, 0, 6, 0, 0, 0, 56, 0, 0, + 0, 148, 1, 0, 0, 104, 3, 0, 0, 228, 3, 0, 0, 136, 6, + 0, 0, 188, 6, 0, 0, 65, 111, 110, 57, 84, 1, 0, 0, 84, + 1, 0, 0, 0, 2, 254, 255, 252, 0, 0, 0, 88, 0, 0, 0, + 4, 0, 36, 0, 0, 0, 84, 0, 0, 0, 84, 0, 0, 0, 36, + 0, 1, 0, 84, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 2, 0, 1, 0, 2, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 2, 0, 3, 0, 0, 0, 0, 0, 1, 0, 3, 0, + 1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 254, + 255, 81, 0, 0, 5, 6, 0, 15, 160, 0, 0, 128, 63, 0, 0, + 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 5, + 0, 0, 128, 0, 0, 15, 144, 4, 0, 0, 4, 0, 0, 3, 224, + 0, 0, 228, 144, 2, 0, 238, 160, 2, 0, 228, 160, 4, 0, 0, + 4, 0, 0, 3, 128, 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, + 228, 160, 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, 0, 128, 6, + 0, 0, 160, 5, 0, 0, 3, 0, 0, 4, 128, 0, 0, 170, 128, + 5, 0, 0, 160, 5, 0, 0, 3, 1, 0, 1, 128, 0, 0, 170, + 128, 6, 0, 85, 160, 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, + 85, 129, 6, 0, 0, 160, 2, 0, 0, 3, 0, 0, 3, 192, 0, + 0, 228, 128, 0, 0, 228, 160, 5, 0, 0, 3, 0, 0, 1, 128, + 0, 0, 170, 128, 5, 0, 85, 160, 5, 0, 0, 3, 1, 0, 2, + 128, 0, 0, 0, 128, 6, 0, 85, 160, 1, 0, 0, 2, 1, 0, + 4, 128, 6, 0, 0, 160, 8, 0, 0, 3, 0, 0, 8, 224, 1, + 0, 228, 128, 3, 0, 228, 160, 8, 0, 0, 3, 0, 0, 4, 224, + 1, 0, 228, 128, 4, 0, 228, 160, 1, 0, 0, 2, 0, 0, 12, + 192, 6, 0, 36, 160, 255, 255, 0, 0, 83, 72, 68, 82, 204, 1, + 0, 0, 64, 0, 1, 0, 115, 0, 0, 0, 89, 0, 0, 4, 70, + 142, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0, 89, 0, 0, 4, + 70, 142, 32, 0, 1, 0, 0, 0, 4, 0, 0, 0, 95, 0, 0, + 3, 50, 16, 16, 0, 0, 0, 0, 0, 103, 0, 0, 4, 242, 32, + 16, 0, 0, 0, 0, 0, 1, 0, 0, 0, 101, 0, 0, 3, 50, + 32, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 194, 32, 16, 0, + 1, 0, 0, 0, 104, 0, 0, 2, 2, 0, 0, 0, 54, 0, 0, + 8, 194, 32, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, + 0, 0, 11, 50, 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, + 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 70, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, + 0, 5, 50, 32, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, + 0, 0, 0, 0, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, + 63, 0, 0, 0, 8, 34, 0, 16, 0, 0, 0, 0, 0, 26, 0, + 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, + 0, 128, 63, 56, 0, 0, 8, 50, 0, 16, 0, 0, 0, 0, 0, + 70, 0, 16, 0, 0, 0, 0, 0, 70, 128, 32, 0, 1, 0, 0, + 0, 3, 0, 0, 0, 56, 0, 0, 10, 50, 0, 16, 0, 1, 0, + 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, + 0, 0, 63, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, + 54, 0, 0, 5, 66, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, + 0, 0, 0, 128, 63, 16, 0, 0, 8, 66, 32, 16, 0, 1, 0, + 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 8, 130, 32, 16, 0, + 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 70, 130, 32, + 0, 1, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 11, 50, 32, + 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, + 138, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 70, 128, 32, 0, + 0, 0, 0, 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, + 84, 116, 0, 0, 0, 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, + 0, 0, 4, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 82, 68, 69, 70, 156, 2, 0, 0, 2, 0, 0, 0, 100, 0, + 0, 0, 2, 0, 0, 0, 28, 0, 0, 0, 0, 4, 254, 255, 0, + 1, 0, 0, 103, 2, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, + 99, 98, 50, 0, 92, 0, 0, 0, 4, 0, 0, 0, 148, 0, 0, + 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, + 0, 0, 7, 0, 0, 0, 52, 1, 0, 0, 112, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 244, 0, 0, 0, 0, 0, 0, 0, + 16, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 16, 1, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 26, 1, 0, 0, 32, + 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 40, 1, 0, 0, 48, 0, 0, 0, 16, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 81, 117, + 97, 100, 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, + 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, + 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, + 171, 171, 220, 1, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 2, + 0, 0, 0, 244, 1, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, + 48, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, 16, 2, 0, + 0, 0, 0, 0, 0, 32, 2, 0, 0, 64, 0, 0, 0, 12, 0, + 0, 0, 0, 0, 0, 0, 40, 2, 0, 0, 0, 0, 0, 0, 56, + 2, 0, 0, 80, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, + 16, 2, 0, 0, 0, 0, 0, 0, 64, 2, 0, 0, 88, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 68, 2, 0, 0, 0, 0, + 0, 0, 84, 2, 0, 0, 92, 0, 0, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 68, 2, 0, 0, 0, 0, 0, 0, 92, 2, 0, 0, + 96, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 68, 2, 0, + 0, 0, 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, + 101, 84, 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 0, 171, 3, + 0, 3, 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 100, 105, 109, 101, 110, 115, 105, 111, 110, 115, 0, 171, 1, 0, 3, + 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, + 102, 102, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 49, 0, + 65, 0, 171, 171, 0, 0, 3, 0, 1, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 114, 97, 100, 105, 117, 115, 49, 0, 115, 113, + 95, 114, 97, 100, 105, 117, 115, 49, 0, 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, + 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 171, + 171, 171, 73, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, + 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, 0, 0, 80, 79, 83, + 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, 83, 71, 78, 104, 0, + 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 12, 0, 0, 92, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, + 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, + 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, + 171, 225, 111, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 192, 7, 0, 0, 68, 88, 66, 67, 73, + 174, 125, 52, 147, 212, 172, 159, 223, 39, 1, 144, 137, 10, 201, 206, + 1, 0, 0, 0, 192, 7, 0, 0, 6, 0, 0, 0, 56, 0, 0, + 0, 196, 1, 0, 0, 56, 4, 0, 0, 180, 4, 0, 0, 28, 7, + 0, 0, 140, 7, 0, 0, 65, 111, 110, 57, 132, 1, 0, 0, 132, + 1, 0, 0, 0, 2, 255, 255, 76, 1, 0, 0, 56, 0, 0, 0, + 1, 0, 44, 0, 0, 0, 56, 0, 0, 0, 56, 0, 2, 0, 36, + 0, 0, 0, 56, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, + 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 2, 255, 255, 81, + 0, 0, 5, 2, 0, 15, 160, 0, 0, 0, 63, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 0, 0, 0, + 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, 0, 0, 144, 0, 8, + 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1, 8, 15, 160, 5, + 0, 0, 3, 0, 0, 8, 128, 1, 0, 255, 160, 1, 0, 255, 160, + 2, 0, 0, 3, 0, 0, 3, 128, 0, 0, 235, 176, 1, 0, 228, + 161, 90, 0, 0, 4, 0, 0, 8, 128, 0, 0, 228, 128, 0, 0, + 228, 128, 0, 0, 255, 129, 5, 0, 0, 3, 0, 0, 8, 128, 0, + 0, 255, 128, 2, 0, 0, 160, 1, 0, 0, 2, 0, 0, 4, 128, + 1, 0, 255, 160, 8, 0, 0, 3, 0, 0, 1, 128, 0, 0, 228, + 128, 0, 0, 228, 160, 6, 0, 0, 2, 0, 0, 1, 128, 0, 0, + 0, 128, 5, 0, 0, 3, 0, 0, 1, 128, 0, 0, 0, 128, 0, + 0, 255, 128, 1, 0, 0, 2, 0, 0, 2, 128, 2, 0, 0, 160, + 66, 0, 0, 3, 1, 0, 15, 128, 0, 0, 228, 176, 1, 8, 228, + 160, 66, 0, 0, 3, 2, 0, 15, 128, 0, 0, 228, 128, 0, 8, + 228, 160, 1, 0, 0, 2, 0, 0, 8, 128, 1, 0, 255, 160, 4, + 0, 0, 4, 0, 0, 1, 128, 0, 0, 0, 128, 0, 0, 170, 161, + 0, 0, 255, 129, 5, 0, 0, 3, 2, 0, 7, 128, 2, 0, 255, + 128, 2, 0, 228, 128, 5, 0, 0, 3, 1, 0, 15, 128, 1, 0, + 255, 128, 2, 0, 228, 128, 88, 0, 0, 4, 0, 0, 15, 128, 0, + 0, 0, 128, 2, 0, 85, 160, 1, 0, 228, 128, 1, 0, 0, 2, + 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0, 83, 72, 68, + 82, 108, 2, 0, 0, 64, 0, 0, 0, 155, 0, 0, 0, 89, 0, + 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 6, 0, 0, 0, 90, + 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 90, 0, 0, 3, + 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, 0, 4, 0, 112, 16, + 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, 24, 0, 4, 0, 112, + 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, 98, 16, 0, 3, 50, + 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, 3, 194, 16, 16, 0, + 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, 0, 0, 0, 0, + 0, 104, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 9, 50, 0, + 16, 0, 0, 0, 0, 0, 230, 26, 16, 0, 1, 0, 0, 0, 70, + 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, + 54, 0, 0, 6, 66, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, + 0, 0, 0, 0, 0, 5, 0, 0, 0, 16, 0, 0, 8, 66, 0, + 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 70, + 130, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0, 15, 0, 0, 7, + 18, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, + 0, 70, 0, 16, 0, 0, 0, 0, 0, 50, 0, 0, 12, 18, 0, + 16, 0, 0, 0, 0, 0, 58, 128, 32, 128, 65, 0, 0, 0, 0, + 0, 0, 0, 5, 0, 0, 0, 58, 128, 32, 0, 0, 0, 0, 0, + 5, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 56, 0, 0, + 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, 0, 0, 0, 63, 14, 0, 0, 7, 18, + 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, + 42, 0, 16, 0, 0, 0, 0, 0, 56, 0, 0, 8, 66, 0, 16, + 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 42, 128, + 32, 0, 0, 0, 0, 0, 4, 0, 0, 0, 29, 0, 0, 9, 66, + 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, 128, 65, 0, 0, 0, + 0, 0, 0, 0, 5, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, + 0, 54, 0, 0, 5, 34, 0, 16, 0, 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 0, 63, 69, 0, 0, 9, 242, 0, 16, 0, 1, + 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 70, 126, 16, 0, + 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 31, 0, 4, + 3, 42, 0, 16, 0, 0, 0, 0, 0, 54, 0, 0, 8, 242, 32, + 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, + 21, 0, 0, 1, 56, 0, 0, 7, 114, 0, 16, 0, 1, 0, 0, + 0, 246, 15, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, + 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 0, 0, 0, 0, 70, + 16, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, + 0, 96, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16, + 0, 0, 0, 0, 0, 246, 15, 16, 0, 0, 0, 0, 0, 70, 14, + 16, 0, 1, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, + 0, 0, 0, 19, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, + 68, 69, 70, 96, 2, 0, 0, 1, 0, 0, 0, 224, 0, 0, 0, + 5, 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, + 0, 43, 2, 0, 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 197, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 210, 0, 0, 0, 2, 0, + 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 214, 0, 0, 0, + 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, + 255, 1, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 219, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 115, 83, 97, 109, 112, 108, 101, 114, 0, 115, 77, 97, 115, 107, 83, + 97, 109, 112, 108, 101, 114, 0, 116, 101, 120, 0, 109, 97, 115, 107, + 0, 99, 98, 50, 0, 171, 219, 0, 0, 0, 7, 0, 0, 0, 248, + 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 160, 1, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, + 0, 184, 1, 0, 0, 0, 0, 0, 0, 200, 1, 0, 0, 48, 0, + 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 212, 1, 0, 0, 0, + 0, 0, 0, 228, 1, 0, 0, 64, 0, 0, 0, 12, 0, 0, 0, + 2, 0, 0, 0, 236, 1, 0, 0, 0, 0, 0, 0, 252, 1, 0, + 0, 80, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, 212, 1, + 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 88, 0, 0, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 8, 2, 0, 0, 0, 0, 0, 0, + 24, 2, 0, 0, 92, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, + 0, 8, 2, 0, 0, 0, 0, 0, 0, 32, 2, 0, 0, 96, 0, + 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 8, 2, 0, 0, 0, + 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84, + 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 0, 171, 3, 0, 3, + 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, + 109, 101, 110, 115, 105, 111, 110, 115, 0, 171, 1, 0, 3, 0, 1, + 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 102, 102, + 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 49, 0, 65, 0, + 171, 171, 0, 0, 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 114, 97, 100, 105, 117, 115, 49, 0, 115, 113, 95, 114, + 97, 100, 105, 117, 115, 49, 0, 77, 105, 99, 114, 111, 115, 111, 102, + 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, + 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, + 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 171, + 73, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, + 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, + 3, 3, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 12, 0, 0, 83, 86, + 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, + 79, 82, 68, 0, 171, 171, 171, 79, 83, 71, 78, 44, 0, 0, 0, + 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, + 0, 0, 83, 86, 95, 84, 97, 114, 103, 101, 116, 0, 171, 171, 37, + 119, 0, 0, 0, 0, 0, 0, 65, 80, 111, 115, 87, 114, 97, 112, + 0, 44, 7, 0, 0, 68, 88, 66, 67, 172, 27, 205, 113, 176, 254, + 27, 44, 22, 107, 179, 112, 127, 38, 148, 161, 1, 0, 0, 0, 44, + 7, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 148, 1, 0, 0, + 104, 3, 0, 0, 228, 3, 0, 0, 136, 6, 0, 0, 188, 6, 0, + 0, 65, 111, 110, 57, 84, 1, 0, 0, 84, 1, 0, 0, 0, 2, + 254, 255, 252, 0, 0, 0, 88, 0, 0, 0, 4, 0, 36, 0, 0, + 0, 84, 0, 0, 0, 84, 0, 0, 0, 36, 0, 1, 0, 84, 0, + 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 1, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, + 3, 0, 0, 0, 0, 0, 1, 0, 3, 0, 1, 0, 5, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0, 5, + 6, 0, 15, 160, 0, 0, 128, 63, 0, 0, 0, 63, 0, 0, 0, + 0, 0, 0, 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0, 0, + 15, 144, 4, 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144, 2, + 0, 238, 160, 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 3, 128, + 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0, 0, + 3, 0, 0, 4, 128, 0, 0, 0, 128, 6, 0, 0, 160, 5, 0, + 0, 3, 0, 0, 4, 128, 0, 0, 170, 128, 5, 0, 0, 160, 5, + 0, 0, 3, 1, 0, 1, 128, 0, 0, 170, 128, 6, 0, 85, 160, + 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, 85, 129, 6, 0, 0, + 160, 2, 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, + 228, 160, 5, 0, 0, 3, 0, 0, 1, 128, 0, 0, 170, 128, 5, + 0, 85, 160, 5, 0, 0, 3, 1, 0, 2, 128, 0, 0, 0, 128, + 6, 0, 85, 160, 1, 0, 0, 2, 1, 0, 4, 128, 6, 0, 0, + 160, 8, 0, 0, 3, 0, 0, 8, 224, 1, 0, 228, 128, 3, 0, + 228, 160, 8, 0, 0, 3, 0, 0, 4, 224, 1, 0, 228, 128, 4, + 0, 228, 160, 1, 0, 0, 2, 0, 0, 12, 192, 6, 0, 36, 160, + 255, 255, 0, 0, 83, 72, 68, 82, 204, 1, 0, 0, 64, 0, 1, + 0, 115, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 1, + 0, 0, 0, 4, 0, 0, 0, 95, 0, 0, 3, 50, 16, 16, 0, + 0, 0, 0, 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, 1, 0, + 0, 0, 101, 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, 0, 104, + 0, 0, 2, 2, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16, 0, + 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50, 0, + 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, + 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 50, 32, 16, + 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 0, 0, + 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, + 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 0, 0, 0, 8, + 34, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 65, 0, 0, + 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 56, 0, + 0, 8, 50, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, + 0, 0, 0, 70, 128, 32, 0, 1, 0, 0, 0, 3, 0, 0, 0, + 56, 0, 0, 10, 50, 0, 16, 0, 1, 0, 0, 0, 70, 0, 16, + 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 63, 0, 0, + 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 66, + 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, + 16, 0, 0, 8, 66, 32, 16, 0, 1, 0, 0, 0, 70, 2, 16, + 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 16, 0, 0, 8, 130, 32, 16, 0, 1, 0, 0, 0, 70, + 2, 16, 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 50, 0, 0, 11, 50, 32, 16, 0, 1, 0, 0, + 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, + 0, 0, 2, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, + 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, + 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, + 156, 2, 0, 0, 2, 0, 0, 0, 100, 0, 0, 0, 2, 0, 0, + 0, 28, 0, 0, 0, 0, 4, 254, 255, 0, 1, 0, 0, 103, 2, + 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 99, 98, 50, 0, 92, + 0, 0, 0, 4, 0, 0, 0, 148, 0, 0, 0, 64, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 7, 0, 0, + 0, 52, 1, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 244, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 2, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 16, 1, 0, 0, + 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 26, 1, 0, 0, 32, 0, 0, 0, 16, 0, + 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 40, + 1, 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, 101, 115, + 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, 100, 115, + 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, + 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, 171, 171, 220, 1, 0, + 0, 0, 0, 0, 0, 44, 0, 0, 0, 2, 0, 0, 0, 244, 1, + 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 48, 0, 0, 0, 8, + 0, 0, 0, 2, 0, 0, 0, 16, 2, 0, 0, 0, 0, 0, 0, + 32, 2, 0, 0, 64, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, + 0, 40, 2, 0, 0, 0, 0, 0, 0, 56, 2, 0, 0, 80, 0, + 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 16, 2, 0, 0, 0, + 0, 0, 0, 64, 2, 0, 0, 88, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 68, 2, 0, 0, 0, 0, 0, 0, 84, 2, 0, + 0, 92, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 68, 2, + 0, 0, 0, 0, 0, 0, 92, 2, 0, 0, 96, 0, 0, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 68, 2, 0, 0, 0, 0, 0, 0, + 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84, 111, 85, 115, + 101, 114, 83, 112, 97, 99, 101, 0, 171, 3, 0, 3, 0, 3, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 109, 101, 110, + 115, 105, 111, 110, 115, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 102, 102, 0, 171, 171, + 171, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 99, 101, 110, 116, 101, 114, 49, 0, 65, 0, 171, 171, 0, + 0, 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 114, 97, 100, 105, 117, 115, 49, 0, 115, 113, 95, 114, 97, 100, 105, + 117, 115, 49, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, + 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, + 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, + 48, 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 171, 73, 83, 71, + 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 7, 3, 0, 0, 80, 79, 83, 73, 84, 73, 79, 78, + 0, 171, 171, 171, 79, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, + 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 3, 12, 0, 0, 92, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 3, + 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, + 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 250, 126, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, + 0, 228, 9, 0, 0, 68, 88, 66, 67, 193, 68, 83, 4, 120, 206, + 206, 65, 213, 56, 189, 186, 120, 85, 235, 59, 1, 0, 0, 0, 228, + 9, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 128, 2, 0, 0, + 88, 6, 0, 0, 212, 6, 0, 0, 64, 9, 0, 0, 176, 9, 0, + 0, 65, 111, 110, 57, 64, 2, 0, 0, 64, 2, 0, 0, 0, 2, + 255, 255, 8, 2, 0, 0, 56, 0, 0, 0, 1, 0, 44, 0, 0, + 0, 56, 0, 0, 0, 56, 0, 2, 0, 36, 0, 0, 0, 56, 0, + 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 4, 0, 3, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 255, 255, 81, 0, 0, 5, 3, 0, + 15, 160, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 81, 0, 0, 5, 4, 0, 15, 160, 0, 0, 128, 63, + 0, 0, 128, 191, 0, 0, 0, 0, 0, 0, 0, 128, 31, 0, 0, + 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, 0, + 0, 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1, + 8, 15, 160, 2, 0, 0, 3, 0, 0, 3, 128, 0, 0, 235, 176, + 1, 0, 228, 161, 90, 0, 0, 4, 0, 0, 8, 128, 0, 0, 228, + 128, 0, 0, 228, 128, 2, 0, 0, 161, 5, 0, 0, 3, 0, 0, + 8, 128, 0, 0, 255, 128, 1, 0, 170, 160, 1, 0, 0, 2, 0, + 0, 4, 128, 1, 0, 255, 160, 8, 0, 0, 3, 0, 0, 1, 128, + 0, 0, 228, 128, 0, 0, 228, 160, 4, 0, 0, 4, 0, 0, 2, + 128, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 255, 129, 35, 0, + 0, 2, 0, 0, 4, 128, 0, 0, 85, 128, 7, 0, 0, 2, 0, + 0, 4, 128, 0, 0, 170, 128, 6, 0, 0, 2, 1, 0, 1, 128, + 0, 0, 170, 128, 1, 0, 0, 2, 1, 0, 6, 128, 1, 0, 0, + 129, 2, 0, 0, 3, 0, 0, 13, 128, 0, 0, 0, 128, 1, 0, + 148, 128, 6, 0, 0, 2, 1, 0, 1, 128, 1, 0, 170, 160, 5, + 0, 0, 3, 0, 0, 13, 128, 0, 0, 228, 128, 1, 0, 0, 128, + 1, 0, 0, 2, 1, 0, 8, 128, 1, 0, 255, 160, 4, 0, 0, + 4, 1, 0, 7, 128, 0, 0, 248, 128, 0, 0, 170, 160, 1, 0, + 255, 128, 88, 0, 0, 4, 2, 0, 1, 128, 1, 0, 0, 128, 0, + 0, 0, 128, 0, 0, 255, 128, 88, 0, 0, 4, 0, 0, 13, 128, + 1, 0, 148, 128, 4, 0, 68, 160, 4, 0, 230, 160, 1, 0, 0, + 2, 2, 0, 2, 128, 3, 0, 0, 160, 66, 0, 0, 3, 1, 0, + 15, 128, 0, 0, 228, 176, 1, 8, 228, 160, 66, 0, 0, 3, 2, + 0, 15, 128, 2, 0, 228, 128, 0, 8, 228, 160, 5, 0, 0, 3, + 2, 0, 7, 128, 2, 0, 255, 128, 2, 0, 228, 128, 5, 0, 0, + 3, 1, 0, 15, 128, 1, 0, 255, 128, 2, 0, 228, 128, 2, 0, + 0, 3, 0, 0, 8, 128, 0, 0, 255, 128, 0, 0, 0, 128, 88, + 0, 0, 4, 0, 0, 1, 128, 0, 0, 255, 128, 0, 0, 0, 128, + 0, 0, 170, 128, 88, 0, 0, 4, 1, 0, 15, 128, 0, 0, 0, + 129, 4, 0, 170, 160, 1, 0, 228, 128, 88, 0, 0, 4, 0, 0, + 15, 128, 0, 0, 85, 128, 1, 0, 228, 128, 4, 0, 170, 160, 1, + 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0, + 83, 72, 68, 82, 208, 3, 0, 0, 64, 0, 0, 0, 244, 0, 0, + 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 7, 0, + 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 90, + 0, 0, 3, 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, 0, 4, + 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, 24, 0, + 4, 0, 112, 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, 98, 16, + 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, 3, 194, + 16, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, 0, + 0, 0, 0, 0, 104, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, + 9, 50, 0, 16, 0, 0, 0, 0, 0, 230, 26, 16, 0, 1, 0, + 0, 0, 70, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 5, + 0, 0, 0, 54, 0, 0, 6, 66, 0, 16, 0, 0, 0, 0, 0, + 58, 128, 32, 0, 0, 0, 0, 0, 5, 0, 0, 0, 16, 0, 0, + 8, 66, 0, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, + 0, 0, 70, 130, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0, 15, + 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, + 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, + 9, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, + 0, 0, 10, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 6, + 0, 0, 0, 56, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0, + 0, 5, 0, 0, 0, 50, 0, 0, 10, 18, 0, 16, 0, 0, 0, + 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, + 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, + 49, 0, 0, 7, 34, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, + 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 75, 0, + 0, 6, 18, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 128, 129, + 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 6, 34, 0, 16, 0, + 1, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 7, 82, 0, 16, 0, 0, 0, 0, 0, 166, 10, + 16, 0, 0, 0, 0, 0, 6, 1, 16, 0, 1, 0, 0, 0, 14, + 0, 0, 8, 82, 0, 16, 0, 0, 0, 0, 0, 6, 2, 16, 0, + 0, 0, 0, 0, 166, 138, 32, 0, 0, 0, 0, 0, 5, 0, 0, + 0, 56, 0, 0, 8, 50, 0, 16, 0, 1, 0, 0, 0, 134, 0, + 16, 0, 0, 0, 0, 0, 166, 138, 32, 0, 0, 0, 0, 0, 4, + 0, 0, 0, 29, 0, 0, 9, 50, 0, 16, 0, 1, 0, 0, 0, + 70, 0, 16, 0, 1, 0, 0, 0, 246, 143, 32, 128, 65, 0, 0, + 0, 0, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 10, 50, 0, + 16, 0, 1, 0, 0, 0, 70, 0, 16, 0, 1, 0, 0, 0, 2, + 64, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, + 0, 42, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, 50, 0, 0, 9, 18, 0, 16, 0, 2, + 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 54, 0, 0, + 5, 34, 0, 16, 0, 2, 0, 0, 0, 1, 64, 0, 0, 0, 0, + 0, 63, 69, 0, 0, 9, 242, 0, 16, 0, 2, 0, 0, 0, 70, + 0, 16, 0, 2, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, 0, 0, 31, 0, 4, 3, 26, 0, 16, + 0, 0, 0, 0, 0, 54, 0, 0, 8, 242, 32, 16, 0, 0, 0, + 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 21, 0, 0, 1, + 52, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, + 0, 1, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 29, 0, + 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, + 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 31, 0, 4, 3, + 10, 0, 16, 0, 0, 0, 0, 0, 54, 0, 0, 8, 242, 32, 16, + 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 21, + 0, 0, 1, 56, 0, 0, 7, 114, 0, 16, 0, 2, 0, 0, 0, + 246, 15, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 2, 0, 0, + 0, 69, 0, 0, 9, 242, 0, 16, 0, 0, 0, 0, 0, 70, 16, + 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0, + 96, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16, 0, + 0, 0, 0, 0, 246, 15, 16, 0, 0, 0, 0, 0, 70, 14, 16, + 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, + 0, 0, 33, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, + 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, + 69, 70, 100, 2, 0, 0, 1, 0, 0, 0, 228, 0, 0, 0, 5, + 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0, + 47, 2, 0, 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 214, 0, 0, 0, 2, 0, 0, + 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, + 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 218, 0, 0, 0, 2, + 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, + 1, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 223, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 115, + 87, 114, 97, 112, 83, 97, 109, 112, 108, 101, 114, 0, 115, 77, 97, + 115, 107, 83, 97, 109, 112, 108, 101, 114, 0, 116, 101, 120, 0, 109, + 97, 115, 107, 0, 99, 98, 50, 0, 171, 223, 0, 0, 0, 7, 0, + 0, 0, 252, 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 164, 1, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, + 0, 0, 0, 0, 188, 1, 0, 0, 0, 0, 0, 0, 204, 1, 0, + 0, 48, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 216, 1, + 0, 0, 0, 0, 0, 0, 232, 1, 0, 0, 64, 0, 0, 0, 12, + 0, 0, 0, 2, 0, 0, 0, 240, 1, 0, 0, 0, 0, 0, 0, + 0, 2, 0, 0, 80, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, + 0, 216, 1, 0, 0, 0, 0, 0, 0, 8, 2, 0, 0, 88, 0, + 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 12, 2, 0, 0, 0, + 0, 0, 0, 28, 2, 0, 0, 92, 0, 0, 0, 4, 0, 0, 0, + 2, 0, 0, 0, 12, 2, 0, 0, 0, 0, 0, 0, 36, 2, 0, + 0, 96, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 12, 2, + 0, 0, 0, 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, + 99, 101, 84, 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 0, 171, + 3, 0, 3, 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 100, 105, 109, 101, 110, 115, 105, 111, 110, 115, 0, 171, 1, 0, + 3, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, + 105, 102, 102, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 49, + 0, 65, 0, 171, 171, 0, 0, 3, 0, 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 114, 97, 100, 105, 117, 115, 49, 0, 115, + 113, 95, 114, 97, 100, 105, 117, 115, 49, 0, 77, 105, 99, 114, 111, + 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, + 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, + 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, + 171, 171, 171, 73, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, + 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, + 0, 0, 0, 3, 3, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 12, 0, + 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, + 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 79, 83, 71, 78, 44, + 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, 114, 103, 101, 116, 0, + 171, 171, 62, 134, 0, 0, 0, 0, 0, 0, 65, 48, 87, 114, 97, + 112, 0, 44, 7, 0, 0, 68, 88, 66, 67, 172, 27, 205, 113, 176, + 254, 27, 44, 22, 107, 179, 112, 127, 38, 148, 161, 1, 0, 0, 0, + 44, 7, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 148, 1, 0, + 0, 104, 3, 0, 0, 228, 3, 0, 0, 136, 6, 0, 0, 188, 6, + 0, 0, 65, 111, 110, 57, 84, 1, 0, 0, 84, 1, 0, 0, 0, + 2, 254, 255, 252, 0, 0, 0, 88, 0, 0, 0, 4, 0, 36, 0, + 0, 0, 84, 0, 0, 0, 84, 0, 0, 0, 36, 0, 1, 0, 84, + 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 1, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, + 0, 3, 0, 0, 0, 0, 0, 1, 0, 3, 0, 1, 0, 5, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0, + 5, 6, 0, 15, 160, 0, 0, 128, 63, 0, 0, 0, 63, 0, 0, + 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0, + 0, 15, 144, 4, 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144, + 2, 0, 238, 160, 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 3, + 128, 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0, + 0, 3, 0, 0, 4, 128, 0, 0, 0, 128, 6, 0, 0, 160, 5, + 0, 0, 3, 0, 0, 4, 128, 0, 0, 170, 128, 5, 0, 0, 160, + 5, 0, 0, 3, 1, 0, 1, 128, 0, 0, 170, 128, 6, 0, 85, + 160, 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, 85, 129, 6, 0, + 0, 160, 2, 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, + 0, 228, 160, 5, 0, 0, 3, 0, 0, 1, 128, 0, 0, 170, 128, + 5, 0, 85, 160, 5, 0, 0, 3, 1, 0, 2, 128, 0, 0, 0, + 128, 6, 0, 85, 160, 1, 0, 0, 2, 1, 0, 4, 128, 6, 0, + 0, 160, 8, 0, 0, 3, 0, 0, 8, 224, 1, 0, 228, 128, 3, + 0, 228, 160, 8, 0, 0, 3, 0, 0, 4, 224, 1, 0, 228, 128, + 4, 0, 228, 160, 1, 0, 0, 2, 0, 0, 12, 192, 6, 0, 36, + 160, 255, 255, 0, 0, 83, 72, 68, 82, 204, 1, 0, 0, 64, 0, + 1, 0, 115, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, + 0, 0, 0, 3, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, + 1, 0, 0, 0, 4, 0, 0, 0, 95, 0, 0, 3, 50, 16, 16, + 0, 0, 0, 0, 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, 1, + 0, 0, 0, 101, 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, 0, + 104, 0, 0, 2, 2, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16, + 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50, + 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, + 230, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 50, 32, + 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 0, + 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 0, 0, 0, + 8, 34, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 65, 0, + 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 56, + 0, 0, 8, 50, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, + 0, 0, 0, 0, 70, 128, 32, 0, 1, 0, 0, 0, 3, 0, 0, + 0, 56, 0, 0, 10, 50, 0, 16, 0, 1, 0, 0, 0, 70, 0, + 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 63, 0, + 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, + 66, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, + 63, 16, 0, 0, 8, 66, 32, 16, 0, 1, 0, 0, 0, 70, 2, + 16, 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 16, 0, 0, 8, 130, 32, 16, 0, 1, 0, 0, 0, + 70, 2, 16, 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, + 0, 1, 0, 0, 0, 50, 0, 0, 11, 50, 32, 16, 0, 1, 0, + 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, + 0, 0, 0, 2, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, + 0, 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 4, 0, + 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, + 70, 156, 2, 0, 0, 2, 0, 0, 0, 100, 0, 0, 0, 2, 0, + 0, 0, 28, 0, 0, 0, 0, 4, 254, 255, 0, 1, 0, 0, 103, + 2, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 99, 98, 50, 0, + 92, 0, 0, 0, 4, 0, 0, 0, 148, 0, 0, 0, 64, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 7, 0, + 0, 0, 52, 1, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 244, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, + 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 16, 1, 0, + 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 26, 1, 0, 0, 32, 0, 0, 0, 16, + 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 40, 1, 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, 101, + 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, 100, + 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, 115, + 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, 171, 171, 220, 1, + 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 2, 0, 0, 0, 244, + 1, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 48, 0, 0, 0, + 8, 0, 0, 0, 2, 0, 0, 0, 16, 2, 0, 0, 0, 0, 0, + 0, 32, 2, 0, 0, 64, 0, 0, 0, 12, 0, 0, 0, 0, 0, + 0, 0, 40, 2, 0, 0, 0, 0, 0, 0, 56, 2, 0, 0, 80, + 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 16, 2, 0, 0, + 0, 0, 0, 0, 64, 2, 0, 0, 88, 0, 0, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 68, 2, 0, 0, 0, 0, 0, 0, 84, 2, + 0, 0, 92, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 68, + 2, 0, 0, 0, 0, 0, 0, 92, 2, 0, 0, 96, 0, 0, 0, + 4, 0, 0, 0, 0, 0, 0, 0, 68, 2, 0, 0, 0, 0, 0, + 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84, 111, 85, + 115, 101, 114, 83, 112, 97, 99, 101, 0, 171, 3, 0, 3, 0, 3, + 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 109, 101, + 110, 115, 105, 111, 110, 115, 0, 171, 1, 0, 3, 0, 1, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 102, 102, 0, 171, + 171, 171, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 99, 101, 110, 116, 101, 114, 49, 0, 65, 0, 171, 171, + 0, 0, 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 114, 97, 100, 105, 117, 115, 49, 0, 115, 113, 95, 114, 97, 100, + 105, 117, 115, 49, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, + 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, + 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, + 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 171, 73, 83, + 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 7, 3, 0, 0, 80, 79, 83, 73, 84, 73, 79, + 78, 0, 171, 171, 171, 79, 83, 71, 78, 104, 0, 0, 0, 3, 0, + 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, + 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, + 0, 1, 0, 0, 0, 3, 12, 0, 0, 92, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, + 3, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, + 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 53, 144, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, + 0, 0, 196, 7, 0, 0, 68, 88, 66, 67, 223, 174, 80, 104, 241, + 52, 44, 173, 100, 134, 52, 219, 15, 210, 214, 245, 1, 0, 0, 0, + 196, 7, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 196, 1, 0, + 0, 56, 4, 0, 0, 180, 4, 0, 0, 32, 7, 0, 0, 144, 7, + 0, 0, 65, 111, 110, 57, 132, 1, 0, 0, 132, 1, 0, 0, 0, + 2, 255, 255, 76, 1, 0, 0, 56, 0, 0, 0, 1, 0, 44, 0, + 0, 0, 56, 0, 0, 0, 56, 0, 2, 0, 36, 0, 0, 0, 56, + 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 4, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 1, 2, 255, 255, 81, 0, 0, 5, 2, + 0, 15, 160, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 31, 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, + 176, 31, 0, 0, 2, 0, 0, 0, 144, 0, 8, 15, 160, 31, 0, + 0, 2, 0, 0, 0, 144, 1, 8, 15, 160, 5, 0, 0, 3, 0, + 0, 8, 128, 1, 0, 255, 160, 1, 0, 255, 160, 2, 0, 0, 3, + 0, 0, 3, 128, 0, 0, 235, 176, 1, 0, 228, 161, 90, 0, 0, + 4, 0, 0, 8, 128, 0, 0, 228, 128, 0, 0, 228, 128, 0, 0, + 255, 129, 5, 0, 0, 3, 0, 0, 8, 128, 0, 0, 255, 128, 2, + 0, 0, 160, 1, 0, 0, 2, 0, 0, 4, 128, 1, 0, 255, 160, + 8, 0, 0, 3, 0, 0, 1, 128, 0, 0, 228, 128, 0, 0, 228, + 160, 6, 0, 0, 2, 0, 0, 1, 128, 0, 0, 0, 128, 5, 0, + 0, 3, 0, 0, 1, 128, 0, 0, 0, 128, 0, 0, 255, 128, 1, + 0, 0, 2, 0, 0, 2, 128, 2, 0, 0, 160, 66, 0, 0, 3, + 1, 0, 15, 128, 0, 0, 228, 176, 1, 8, 228, 160, 66, 0, 0, + 3, 2, 0, 15, 128, 0, 0, 228, 128, 0, 8, 228, 160, 1, 0, + 0, 2, 0, 0, 8, 128, 1, 0, 255, 160, 4, 0, 0, 4, 0, + 0, 1, 128, 0, 0, 0, 128, 0, 0, 170, 161, 0, 0, 255, 129, + 5, 0, 0, 3, 2, 0, 7, 128, 2, 0, 255, 128, 2, 0, 228, + 128, 5, 0, 0, 3, 1, 0, 15, 128, 1, 0, 255, 128, 2, 0, + 228, 128, 88, 0, 0, 4, 0, 0, 15, 128, 0, 0, 0, 128, 2, + 0, 85, 160, 1, 0, 228, 128, 1, 0, 0, 2, 0, 8, 15, 128, + 0, 0, 228, 128, 255, 255, 0, 0, 83, 72, 68, 82, 108, 2, 0, + 0, 64, 0, 0, 0, 155, 0, 0, 0, 89, 0, 0, 4, 70, 142, + 32, 0, 0, 0, 0, 0, 6, 0, 0, 0, 90, 0, 0, 3, 0, + 96, 16, 0, 0, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, + 1, 0, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 0, 0, 0, + 0, 85, 85, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 1, 0, + 0, 0, 85, 85, 0, 0, 98, 16, 0, 3, 50, 16, 16, 0, 1, + 0, 0, 0, 98, 16, 0, 3, 194, 16, 16, 0, 1, 0, 0, 0, + 101, 0, 0, 3, 242, 32, 16, 0, 0, 0, 0, 0, 104, 0, 0, + 2, 2, 0, 0, 0, 0, 0, 0, 9, 50, 0, 16, 0, 0, 0, + 0, 0, 230, 26, 16, 0, 1, 0, 0, 0, 70, 128, 32, 128, 65, + 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 54, 0, 0, 6, + 66, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, 0, 0, 0, 0, + 0, 5, 0, 0, 0, 16, 0, 0, 8, 66, 0, 16, 0, 0, 0, + 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 70, 130, 32, 0, 0, + 0, 0, 0, 4, 0, 0, 0, 15, 0, 0, 7, 18, 0, 16, 0, + 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, + 0, 0, 0, 0, 0, 50, 0, 0, 12, 18, 0, 16, 0, 0, 0, + 0, 0, 58, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 5, + 0, 0, 0, 58, 128, 32, 0, 0, 0, 0, 0, 5, 0, 0, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 56, 0, 0, 7, 18, 0, 16, + 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 0, 63, 14, 0, 0, 7, 18, 0, 16, 0, 0, + 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, + 0, 0, 0, 0, 56, 0, 0, 8, 66, 0, 16, 0, 0, 0, 0, + 0, 10, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, + 0, 0, 4, 0, 0, 0, 29, 0, 0, 9, 66, 0, 16, 0, 0, + 0, 0, 0, 58, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, + 5, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 54, 0, 0, + 5, 34, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, + 0, 63, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 70, + 0, 16, 0, 0, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, 0, 0, 31, 0, 4, 3, 42, 0, 16, + 0, 0, 0, 0, 0, 54, 0, 0, 8, 242, 32, 16, 0, 0, 0, + 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 21, 0, 0, 1, + 56, 0, 0, 7, 114, 0, 16, 0, 1, 0, 0, 0, 246, 15, 16, + 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 69, 0, + 0, 9, 242, 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 1, + 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0, 96, 16, 0, + 1, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16, 0, 0, 0, 0, + 0, 246, 15, 16, 0, 0, 0, 0, 0, 70, 14, 16, 0, 1, 0, + 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, 19, + 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, 100, + 2, 0, 0, 1, 0, 0, 0, 228, 0, 0, 0, 5, 0, 0, 0, + 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0, 47, 2, 0, + 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 201, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 214, 0, 0, 0, 2, 0, 0, 0, 5, 0, + 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 1, + 0, 0, 0, 12, 0, 0, 0, 218, 0, 0, 0, 2, 0, 0, 0, + 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 1, 0, 0, + 0, 1, 0, 0, 0, 12, 0, 0, 0, 223, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 115, 87, 114, 97, + 112, 83, 97, 109, 112, 108, 101, 114, 0, 115, 77, 97, 115, 107, 83, + 97, 109, 112, 108, 101, 114, 0, 116, 101, 120, 0, 109, 97, 115, 107, + 0, 99, 98, 50, 0, 171, 223, 0, 0, 0, 7, 0, 0, 0, 252, + 0, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 164, 1, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, + 0, 188, 1, 0, 0, 0, 0, 0, 0, 204, 1, 0, 0, 48, 0, + 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 216, 1, 0, 0, 0, + 0, 0, 0, 232, 1, 0, 0, 64, 0, 0, 0, 12, 0, 0, 0, + 2, 0, 0, 0, 240, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, + 0, 80, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, 216, 1, + 0, 0, 0, 0, 0, 0, 8, 2, 0, 0, 88, 0, 0, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 12, 2, 0, 0, 0, 0, 0, 0, + 28, 2, 0, 0, 92, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, + 0, 12, 2, 0, 0, 0, 0, 0, 0, 36, 2, 0, 0, 96, 0, + 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 12, 2, 0, 0, 0, + 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84, + 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 0, 171, 3, 0, 3, + 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, + 109, 101, 110, 115, 105, 111, 110, 115, 0, 171, 1, 0, 3, 0, 1, + 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 102, 102, + 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 49, 0, 65, 0, + 171, 171, 0, 0, 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 114, 97, 100, 105, 117, 115, 49, 0, 115, 113, 95, 114, + 97, 100, 105, 117, 115, 49, 0, 77, 105, 99, 114, 111, 115, 111, 102, + 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, + 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, + 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 171, + 73, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, + 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, + 3, 3, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 12, 0, 0, 83, 86, + 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, + 79, 82, 68, 0, 171, 171, 171, 79, 83, 71, 78, 44, 0, 0, 0, + 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, + 0, 0, 83, 86, 95, 84, 97, 114, 103, 101, 116, 0, 171, 171, 121, + 151, 0, 0, 0, 0, 0, 0, 65, 80, 111, 115, 77, 105, 114, 114, + 111, 114, 0, 44, 7, 0, 0, 68, 88, 66, 67, 172, 27, 205, 113, + 176, 254, 27, 44, 22, 107, 179, 112, 127, 38, 148, 161, 1, 0, 0, + 0, 44, 7, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 148, 1, + 0, 0, 104, 3, 0, 0, 228, 3, 0, 0, 136, 6, 0, 0, 188, + 6, 0, 0, 65, 111, 110, 57, 84, 1, 0, 0, 84, 1, 0, 0, + 0, 2, 254, 255, 252, 0, 0, 0, 88, 0, 0, 0, 4, 0, 36, + 0, 0, 0, 84, 0, 0, 0, 84, 0, 0, 0, 36, 0, 1, 0, + 84, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 2, 0, 1, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 2, 0, 3, 0, 0, 0, 0, 0, 1, 0, 3, 0, 1, 0, 5, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, + 0, 5, 6, 0, 15, 160, 0, 0, 128, 63, 0, 0, 0, 63, 0, + 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, + 0, 0, 15, 144, 4, 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, + 144, 2, 0, 238, 160, 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, + 3, 128, 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, + 0, 0, 3, 0, 0, 4, 128, 0, 0, 0, 128, 6, 0, 0, 160, + 5, 0, 0, 3, 0, 0, 4, 128, 0, 0, 170, 128, 5, 0, 0, + 160, 5, 0, 0, 3, 1, 0, 1, 128, 0, 0, 170, 128, 6, 0, + 85, 160, 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, 85, 129, 6, + 0, 0, 160, 2, 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, + 0, 0, 228, 160, 5, 0, 0, 3, 0, 0, 1, 128, 0, 0, 170, + 128, 5, 0, 85, 160, 5, 0, 0, 3, 1, 0, 2, 128, 0, 0, + 0, 128, 6, 0, 85, 160, 1, 0, 0, 2, 1, 0, 4, 128, 6, + 0, 0, 160, 8, 0, 0, 3, 0, 0, 8, 224, 1, 0, 228, 128, + 3, 0, 228, 160, 8, 0, 0, 3, 0, 0, 4, 224, 1, 0, 228, + 128, 4, 0, 228, 160, 1, 0, 0, 2, 0, 0, 12, 192, 6, 0, + 36, 160, 255, 255, 0, 0, 83, 72, 68, 82, 204, 1, 0, 0, 64, + 0, 1, 0, 115, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, + 0, 1, 0, 0, 0, 4, 0, 0, 0, 95, 0, 0, 3, 50, 16, + 16, 0, 0, 0, 0, 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, + 1, 0, 0, 0, 101, 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, + 0, 104, 0, 0, 2, 2, 0, 0, 0, 54, 0, 0, 8, 194, 32, + 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, + 50, 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, + 0, 230, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 50, + 32, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, + 0, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, + 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 0, 0, + 0, 8, 34, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 65, + 0, 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, + 56, 0, 0, 8, 50, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, + 0, 0, 0, 0, 0, 70, 128, 32, 0, 1, 0, 0, 0, 3, 0, + 0, 0, 56, 0, 0, 10, 50, 0, 16, 0, 1, 0, 0, 0, 70, + 0, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 63, + 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, + 5, 66, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, + 128, 63, 16, 0, 0, 8, 66, 32, 16, 0, 1, 0, 0, 0, 70, + 2, 16, 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 16, 0, 0, 8, 130, 32, 16, 0, 1, 0, 0, + 0, 70, 2, 16, 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 50, 0, 0, 11, 50, 32, 16, 0, 1, + 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, + 0, 0, 0, 0, 2, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, + 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, + 0, 0, 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 4, + 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, + 69, 70, 156, 2, 0, 0, 2, 0, 0, 0, 100, 0, 0, 0, 2, + 0, 0, 0, 28, 0, 0, 0, 0, 4, 254, 255, 0, 1, 0, 0, + 103, 2, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 99, 98, 50, + 0, 92, 0, 0, 0, 4, 0, 0, 0, 148, 0, 0, 0, 64, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 7, + 0, 0, 0, 52, 1, 0, 0, 112, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 244, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, + 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 16, 1, + 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 26, 1, 0, 0, 32, 0, 0, 0, + 16, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 40, 1, 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, + 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, + 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, + 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, 171, 171, 220, + 1, 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 2, 0, 0, 0, + 244, 1, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 48, 0, 0, + 0, 8, 0, 0, 0, 2, 0, 0, 0, 16, 2, 0, 0, 0, 0, + 0, 0, 32, 2, 0, 0, 64, 0, 0, 0, 12, 0, 0, 0, 0, + 0, 0, 0, 40, 2, 0, 0, 0, 0, 0, 0, 56, 2, 0, 0, + 80, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 16, 2, 0, + 0, 0, 0, 0, 0, 64, 2, 0, 0, 88, 0, 0, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 68, 2, 0, 0, 0, 0, 0, 0, 84, + 2, 0, 0, 92, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, + 68, 2, 0, 0, 0, 0, 0, 0, 92, 2, 0, 0, 96, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 68, 2, 0, 0, 0, 0, + 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84, 111, + 85, 115, 101, 114, 83, 112, 97, 99, 101, 0, 171, 3, 0, 3, 0, + 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 109, + 101, 110, 115, 105, 111, 110, 115, 0, 171, 1, 0, 3, 0, 1, 0, + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 102, 102, 0, + 171, 171, 171, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 49, 0, 65, 0, 171, + 171, 0, 0, 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 114, 97, 100, 105, 117, 115, 49, 0, 115, 113, 95, 114, 97, + 100, 105, 117, 115, 49, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, + 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, + 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, + 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 171, 73, + 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, 7, 3, 0, 0, 80, 79, 83, 73, 84, 73, + 79, 78, 0, 171, 171, 171, 79, 83, 71, 78, 104, 0, 0, 0, 3, + 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, + 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 0, 0, 3, 12, 0, 0, 92, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, + 12, 3, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, + 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 84, 159, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, + 0, 0, 0, 232, 9, 0, 0, 68, 88, 66, 67, 48, 133, 157, 76, + 135, 209, 82, 153, 49, 138, 172, 57, 31, 63, 161, 231, 1, 0, 0, + 0, 232, 9, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 128, 2, + 0, 0, 88, 6, 0, 0, 212, 6, 0, 0, 68, 9, 0, 0, 180, + 9, 0, 0, 65, 111, 110, 57, 64, 2, 0, 0, 64, 2, 0, 0, + 0, 2, 255, 255, 8, 2, 0, 0, 56, 0, 0, 0, 1, 0, 44, + 0, 0, 0, 56, 0, 0, 0, 56, 0, 2, 0, 36, 0, 0, 0, + 56, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 4, 0, 3, + 0, 0, 0, 0, 0, 0, 0, 1, 2, 255, 255, 81, 0, 0, 5, + 3, 0, 15, 160, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 81, 0, 0, 5, 4, 0, 15, 160, 0, 0, + 128, 63, 0, 0, 128, 191, 0, 0, 0, 0, 0, 0, 0, 128, 31, + 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, + 0, 0, 0, 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, + 144, 1, 8, 15, 160, 2, 0, 0, 3, 0, 0, 3, 128, 0, 0, + 235, 176, 1, 0, 228, 161, 90, 0, 0, 4, 0, 0, 8, 128, 0, + 0, 228, 128, 0, 0, 228, 128, 2, 0, 0, 161, 5, 0, 0, 3, + 0, 0, 8, 128, 0, 0, 255, 128, 1, 0, 170, 160, 1, 0, 0, + 2, 0, 0, 4, 128, 1, 0, 255, 160, 8, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 228, 128, 0, 0, 228, 160, 4, 0, 0, 4, 0, + 0, 2, 128, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 255, 129, + 35, 0, 0, 2, 0, 0, 4, 128, 0, 0, 85, 128, 7, 0, 0, + 2, 0, 0, 4, 128, 0, 0, 170, 128, 6, 0, 0, 2, 1, 0, + 1, 128, 0, 0, 170, 128, 1, 0, 0, 2, 1, 0, 6, 128, 1, + 0, 0, 129, 2, 0, 0, 3, 0, 0, 13, 128, 0, 0, 0, 128, + 1, 0, 148, 128, 6, 0, 0, 2, 1, 0, 1, 128, 1, 0, 170, + 160, 5, 0, 0, 3, 0, 0, 13, 128, 0, 0, 228, 128, 1, 0, + 0, 128, 1, 0, 0, 2, 1, 0, 8, 128, 1, 0, 255, 160, 4, + 0, 0, 4, 1, 0, 7, 128, 0, 0, 248, 128, 0, 0, 170, 160, + 1, 0, 255, 128, 88, 0, 0, 4, 2, 0, 1, 128, 1, 0, 0, + 128, 0, 0, 0, 128, 0, 0, 255, 128, 88, 0, 0, 4, 0, 0, + 13, 128, 1, 0, 148, 128, 4, 0, 68, 160, 4, 0, 230, 160, 1, + 0, 0, 2, 2, 0, 2, 128, 3, 0, 0, 160, 66, 0, 0, 3, + 1, 0, 15, 128, 0, 0, 228, 176, 1, 8, 228, 160, 66, 0, 0, + 3, 2, 0, 15, 128, 2, 0, 228, 128, 0, 8, 228, 160, 5, 0, + 0, 3, 2, 0, 7, 128, 2, 0, 255, 128, 2, 0, 228, 128, 5, + 0, 0, 3, 1, 0, 15, 128, 1, 0, 255, 128, 2, 0, 228, 128, + 2, 0, 0, 3, 0, 0, 8, 128, 0, 0, 255, 128, 0, 0, 0, + 128, 88, 0, 0, 4, 0, 0, 1, 128, 0, 0, 255, 128, 0, 0, + 0, 128, 0, 0, 170, 128, 88, 0, 0, 4, 1, 0, 15, 128, 0, + 0, 0, 129, 4, 0, 170, 160, 1, 0, 228, 128, 88, 0, 0, 4, + 0, 0, 15, 128, 0, 0, 85, 128, 1, 0, 228, 128, 4, 0, 170, + 160, 1, 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, + 0, 0, 83, 72, 68, 82, 208, 3, 0, 0, 64, 0, 0, 0, 244, + 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, + 7, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, + 0, 90, 0, 0, 3, 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, + 0, 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, + 24, 0, 4, 0, 112, 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, + 98, 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, + 3, 194, 16, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, + 16, 0, 0, 0, 0, 0, 104, 0, 0, 2, 3, 0, 0, 0, 0, + 0, 0, 9, 50, 0, 16, 0, 0, 0, 0, 0, 230, 26, 16, 0, + 1, 0, 0, 0, 70, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, + 0, 5, 0, 0, 0, 54, 0, 0, 6, 66, 0, 16, 0, 0, 0, + 0, 0, 58, 128, 32, 0, 0, 0, 0, 0, 5, 0, 0, 0, 16, + 0, 0, 8, 66, 0, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, + 0, 0, 0, 0, 70, 130, 32, 0, 0, 0, 0, 0, 4, 0, 0, + 0, 15, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 70, 0, + 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 0, + 0, 0, 9, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 10, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, + 0, 6, 0, 0, 0, 56, 0, 0, 8, 18, 0, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, + 0, 0, 0, 5, 0, 0, 0, 50, 0, 0, 10, 18, 0, 16, 0, + 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, + 0, 0, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 0, 0, + 0, 0, 49, 0, 0, 7, 34, 0, 16, 0, 0, 0, 0, 0, 10, + 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, + 75, 0, 0, 6, 18, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, + 128, 129, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 6, 34, 0, + 16, 0, 1, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 7, 82, 0, 16, 0, 0, 0, 0, 0, + 166, 10, 16, 0, 0, 0, 0, 0, 6, 1, 16, 0, 1, 0, 0, + 0, 14, 0, 0, 8, 82, 0, 16, 0, 0, 0, 0, 0, 6, 2, + 16, 0, 0, 0, 0, 0, 166, 138, 32, 0, 0, 0, 0, 0, 5, + 0, 0, 0, 56, 0, 0, 8, 50, 0, 16, 0, 1, 0, 0, 0, + 134, 0, 16, 0, 0, 0, 0, 0, 166, 138, 32, 0, 0, 0, 0, + 0, 4, 0, 0, 0, 29, 0, 0, 9, 50, 0, 16, 0, 1, 0, + 0, 0, 70, 0, 16, 0, 1, 0, 0, 0, 246, 143, 32, 128, 65, + 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 10, + 50, 0, 16, 0, 1, 0, 0, 0, 70, 0, 16, 0, 1, 0, 0, + 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 18, 0, 16, 0, 0, + 0, 0, 0, 42, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 50, 0, 0, 9, 18, 0, 16, + 0, 2, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 54, + 0, 0, 5, 34, 0, 16, 0, 2, 0, 0, 0, 1, 64, 0, 0, + 0, 0, 0, 63, 69, 0, 0, 9, 242, 0, 16, 0, 2, 0, 0, + 0, 70, 0, 16, 0, 2, 0, 0, 0, 70, 126, 16, 0, 0, 0, + 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 31, 0, 4, 3, 26, + 0, 16, 0, 0, 0, 0, 0, 54, 0, 0, 8, 242, 32, 16, 0, + 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 21, 0, + 0, 1, 52, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 26, + 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, + 29, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, + 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 31, 0, + 4, 3, 10, 0, 16, 0, 0, 0, 0, 0, 54, 0, 0, 8, 242, + 32, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, + 1, 21, 0, 0, 1, 56, 0, 0, 7, 114, 0, 16, 0, 2, 0, + 0, 0, 246, 15, 16, 0, 2, 0, 0, 0, 70, 2, 16, 0, 2, + 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 0, 0, 0, 0, + 70, 16, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, + 0, 0, 96, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 242, 32, + 16, 0, 0, 0, 0, 0, 246, 15, 16, 0, 0, 0, 0, 0, 70, + 14, 16, 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, + 116, 0, 0, 0, 33, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 82, 68, 69, 70, 104, 2, 0, 0, 1, 0, 0, 0, 232, 0, 0, + 0, 5, 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, + 0, 0, 51, 2, 0, 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 203, 0, 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0, 2, + 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 220, 0, 0, + 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, + 255, 255, 1, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 225, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 115, 77, 105, 114, 114, 111, 114, 83, 97, 109, 112, 108, 101, 114, + 0, 115, 77, 97, 115, 107, 83, 97, 109, 112, 108, 101, 114, 0, 116, + 101, 120, 0, 109, 97, 115, 107, 0, 99, 98, 50, 0, 171, 171, 171, + 225, 0, 0, 0, 7, 0, 0, 0, 0, 1, 0, 0, 112, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 168, 1, 0, 0, 0, 0, + 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 192, 1, 0, 0, 0, + 0, 0, 0, 208, 1, 0, 0, 48, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 220, 1, 0, 0, 0, 0, 0, 0, 236, 1, 0, + 0, 64, 0, 0, 0, 12, 0, 0, 0, 2, 0, 0, 0, 244, 1, + 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 80, 0, 0, 0, 8, + 0, 0, 0, 2, 0, 0, 0, 220, 1, 0, 0, 0, 0, 0, 0, + 12, 2, 0, 0, 88, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, + 0, 16, 2, 0, 0, 0, 0, 0, 0, 32, 2, 0, 0, 92, 0, + 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 16, 2, 0, 0, 0, + 0, 0, 0, 40, 2, 0, 0, 96, 0, 0, 0, 4, 0, 0, 0, + 2, 0, 0, 0, 16, 2, 0, 0, 0, 0, 0, 0, 68, 101, 118, + 105, 99, 101, 83, 112, 97, 99, 101, 84, 111, 85, 115, 101, 114, 83, + 112, 97, 99, 101, 0, 171, 3, 0, 3, 0, 3, 0, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 100, 105, 109, 101, 110, 115, 105, 111, + 110, 115, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 100, 105, 102, 102, 0, 171, 171, 171, 1, 0, + 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, + 101, 110, 116, 101, 114, 49, 0, 65, 0, 171, 171, 0, 0, 3, 0, + 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 97, 100, + 105, 117, 115, 49, 0, 115, 113, 95, 114, 97, 100, 105, 117, 115, 49, + 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, + 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, + 49, 54, 51, 56, 52, 0, 171, 171, 171, 73, 83, 71, 78, 104, 0, + 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, + 0, 0, 0, 12, 12, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, + 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, + 171, 79, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, + 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, + 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, 84, + 97, 114, 103, 101, 116, 0, 171, 171, 152, 166, 0, 0, 0, 0, 0, + 0, 65, 48, 77, 105, 114, 114, 111, 114, 0, 44, 7, 0, 0, 68, + 88, 66, 67, 172, 27, 205, 113, 176, 254, 27, 44, 22, 107, 179, 112, + 127, 38, 148, 161, 1, 0, 0, 0, 44, 7, 0, 0, 6, 0, 0, + 0, 56, 0, 0, 0, 148, 1, 0, 0, 104, 3, 0, 0, 228, 3, + 0, 0, 136, 6, 0, 0, 188, 6, 0, 0, 65, 111, 110, 57, 84, + 1, 0, 0, 84, 1, 0, 0, 0, 2, 254, 255, 252, 0, 0, 0, + 88, 0, 0, 0, 4, 0, 36, 0, 0, 0, 84, 0, 0, 0, 84, + 0, 0, 0, 36, 0, 1, 0, 84, 0, 0, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 2, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 2, 0, 3, 0, 0, 0, 0, 0, + 1, 0, 3, 0, 1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 2, 254, 255, 81, 0, 0, 5, 6, 0, 15, 160, 0, 0, + 128, 63, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 31, + 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, 4, 0, 0, 4, + 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, 160, 2, 0, 228, + 160, 4, 0, 0, 4, 0, 0, 3, 128, 0, 0, 228, 144, 1, 0, + 238, 160, 1, 0, 228, 160, 2, 0, 0, 3, 0, 0, 4, 128, 0, + 0, 0, 128, 6, 0, 0, 160, 5, 0, 0, 3, 0, 0, 4, 128, + 0, 0, 170, 128, 5, 0, 0, 160, 5, 0, 0, 3, 1, 0, 1, + 128, 0, 0, 170, 128, 6, 0, 85, 160, 2, 0, 0, 3, 0, 0, + 4, 128, 0, 0, 85, 129, 6, 0, 0, 160, 2, 0, 0, 3, 0, + 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, 160, 5, 0, 0, 3, + 0, 0, 1, 128, 0, 0, 170, 128, 5, 0, 85, 160, 5, 0, 0, + 3, 1, 0, 2, 128, 0, 0, 0, 128, 6, 0, 85, 160, 1, 0, + 0, 2, 1, 0, 4, 128, 6, 0, 0, 160, 8, 0, 0, 3, 0, + 0, 8, 224, 1, 0, 228, 128, 3, 0, 228, 160, 8, 0, 0, 3, + 0, 0, 4, 224, 1, 0, 228, 128, 4, 0, 228, 160, 1, 0, 0, + 2, 0, 0, 12, 192, 6, 0, 36, 160, 255, 255, 0, 0, 83, 72, + 68, 82, 204, 1, 0, 0, 64, 0, 1, 0, 115, 0, 0, 0, 89, + 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 89, 0, 0, 4, 70, 142, 32, 0, 1, 0, 0, 0, 4, 0, 0, + 0, 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0, 0, 103, 0, + 0, 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0, 0, 0, 101, + 0, 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, + 194, 32, 16, 0, 1, 0, 0, 0, 104, 0, 0, 2, 2, 0, 0, + 0, 54, 0, 0, 8, 194, 32, 16, 0, 0, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 128, 63, 50, 0, 0, 11, 50, 0, 16, 0, 0, 0, 0, 0, + 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 54, 0, 0, 5, 50, 32, 16, 0, 0, 0, 0, 0, 70, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 7, 18, 0, 16, 0, + 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, + 0, 0, 0, 128, 63, 0, 0, 0, 8, 34, 0, 16, 0, 0, 0, + 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, + 64, 0, 0, 0, 0, 128, 63, 56, 0, 0, 8, 50, 0, 16, 0, + 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 70, 128, 32, + 0, 1, 0, 0, 0, 3, 0, 0, 0, 56, 0, 0, 10, 50, 0, + 16, 0, 1, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 2, + 64, 0, 0, 0, 0, 0, 63, 0, 0, 0, 63, 0, 0, 0, 0, + 0, 0, 0, 0, 54, 0, 0, 5, 66, 0, 16, 0, 1, 0, 0, + 0, 1, 64, 0, 0, 0, 0, 128, 63, 16, 0, 0, 8, 66, 32, + 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 70, + 130, 32, 0, 1, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 8, + 130, 32, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, + 0, 70, 130, 32, 0, 1, 0, 0, 0, 1, 0, 0, 0, 50, 0, + 0, 11, 50, 32, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, + 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 70, 128, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 62, 0, 0, + 1, 83, 84, 65, 84, 116, 0, 0, 0, 12, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 8, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 82, 68, 69, 70, 156, 2, 0, 0, 2, 0, + 0, 0, 100, 0, 0, 0, 2, 0, 0, 0, 28, 0, 0, 0, 0, + 4, 254, 255, 0, 1, 0, 0, 103, 2, 0, 0, 92, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 96, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 99, 98, 48, 0, 99, 98, 50, 0, 92, 0, 0, 0, 4, 0, 0, + 0, 148, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 96, 0, 0, 0, 7, 0, 0, 0, 52, 1, 0, 0, 112, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 244, 0, 0, 0, + 0, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 16, 1, 0, 0, 16, 0, 0, 0, 16, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 26, + 1, 0, 0, 32, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0, 48, 0, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 81, 117, 97, 100, 68, 101, 115, 99, 0, 171, 171, 171, 1, + 0, 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, + 101, 120, 67, 111, 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, + 108, 111, 114, 0, 171, 171, 220, 1, 0, 0, 0, 0, 0, 0, 44, + 0, 0, 0, 2, 0, 0, 0, 244, 1, 0, 0, 0, 0, 0, 0, + 4, 2, 0, 0, 48, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, + 0, 16, 2, 0, 0, 0, 0, 0, 0, 32, 2, 0, 0, 64, 0, + 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 40, 2, 0, 0, 0, + 0, 0, 0, 56, 2, 0, 0, 80, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 16, 2, 0, 0, 0, 0, 0, 0, 64, 2, 0, + 0, 88, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 68, 2, + 0, 0, 0, 0, 0, 0, 84, 2, 0, 0, 92, 0, 0, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 68, 2, 0, 0, 0, 0, 0, 0, + 92, 2, 0, 0, 96, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, + 0, 68, 2, 0, 0, 0, 0, 0, 0, 68, 101, 118, 105, 99, 101, + 83, 112, 97, 99, 101, 84, 111, 85, 115, 101, 114, 83, 112, 97, 99, + 101, 0, 171, 3, 0, 3, 0, 3, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 100, 105, 109, 101, 110, 115, 105, 111, 110, 115, 0, + 171, 1, 0, 3, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 100, 105, 102, 102, 0, 171, 171, 171, 1, 0, 3, 0, 1, + 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 101, 110, 116, + 101, 114, 49, 0, 65, 0, 171, 171, 0, 0, 3, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 97, 100, 105, 117, 115, + 49, 0, 115, 113, 95, 114, 97, 100, 105, 117, 115, 49, 0, 77, 105, + 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, + 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, + 56, 52, 0, 171, 171, 171, 73, 83, 71, 78, 44, 0, 0, 0, 1, + 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, 0, + 0, 80, 79, 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, 83, + 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 12, + 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, + 0, 0, 0, 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, 80, + 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, + 68, 0, 171, 171, 171, 149, 176, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 200, 7, 0, 0, 68, + 88, 66, 67, 238, 212, 160, 43, 129, 11, 44, 225, 62, 162, 102, 35, + 9, 220, 80, 177, 1, 0, 0, 0, 200, 7, 0, 0, 6, 0, 0, + 0, 56, 0, 0, 0, 196, 1, 0, 0, 56, 4, 0, 0, 180, 4, + 0, 0, 36, 7, 0, 0, 148, 7, 0, 0, 65, 111, 110, 57, 132, + 1, 0, 0, 132, 1, 0, 0, 0, 2, 255, 255, 76, 1, 0, 0, + 56, 0, 0, 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, 0, 56, + 0, 2, 0, 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, 1, 1, + 1, 0, 0, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, + 2, 255, 255, 81, 0, 0, 5, 2, 0, 15, 160, 0, 0, 0, 63, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, + 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, 0, + 0, 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1, + 8, 15, 160, 5, 0, 0, 3, 0, 0, 8, 128, 1, 0, 255, 160, + 1, 0, 255, 160, 2, 0, 0, 3, 0, 0, 3, 128, 0, 0, 235, + 176, 1, 0, 228, 161, 90, 0, 0, 4, 0, 0, 8, 128, 0, 0, + 228, 128, 0, 0, 228, 128, 0, 0, 255, 129, 5, 0, 0, 3, 0, + 0, 8, 128, 0, 0, 255, 128, 2, 0, 0, 160, 1, 0, 0, 2, + 0, 0, 4, 128, 1, 0, 255, 160, 8, 0, 0, 3, 0, 0, 1, + 128, 0, 0, 228, 128, 0, 0, 228, 160, 6, 0, 0, 2, 0, 0, + 1, 128, 0, 0, 0, 128, 5, 0, 0, 3, 0, 0, 1, 128, 0, + 0, 0, 128, 0, 0, 255, 128, 1, 0, 0, 2, 0, 0, 2, 128, + 2, 0, 0, 160, 66, 0, 0, 3, 1, 0, 15, 128, 0, 0, 228, + 176, 1, 8, 228, 160, 66, 0, 0, 3, 2, 0, 15, 128, 0, 0, + 228, 128, 0, 8, 228, 160, 1, 0, 0, 2, 0, 0, 8, 128, 1, + 0, 255, 160, 4, 0, 0, 4, 0, 0, 1, 128, 0, 0, 0, 128, + 0, 0, 170, 161, 0, 0, 255, 129, 5, 0, 0, 3, 2, 0, 7, + 128, 2, 0, 255, 128, 2, 0, 228, 128, 5, 0, 0, 3, 1, 0, + 15, 128, 1, 0, 255, 128, 2, 0, 228, 128, 88, 0, 0, 4, 0, + 0, 15, 128, 0, 0, 0, 128, 2, 0, 85, 160, 1, 0, 228, 128, + 1, 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, + 0, 83, 72, 68, 82, 108, 2, 0, 0, 64, 0, 0, 0, 155, 0, + 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 6, + 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, + 90, 0, 0, 3, 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, 0, + 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, 24, + 0, 4, 0, 112, 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, 98, + 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, 3, + 194, 16, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, + 0, 0, 0, 0, 0, 104, 0, 0, 2, 2, 0, 0, 0, 0, 0, + 0, 9, 50, 0, 16, 0, 0, 0, 0, 0, 230, 26, 16, 0, 1, + 0, 0, 0, 70, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, + 5, 0, 0, 0, 54, 0, 0, 6, 66, 0, 16, 0, 0, 0, 0, + 0, 58, 128, 32, 0, 0, 0, 0, 0, 5, 0, 0, 0, 16, 0, + 0, 8, 66, 0, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, + 0, 0, 0, 70, 130, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0, + 15, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, + 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 50, 0, + 0, 12, 18, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, 128, 65, + 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 58, 128, 32, 0, + 0, 0, 0, 0, 5, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, + 0, 56, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 63, 14, + 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 56, 0, 0, + 8, 66, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, + 0, 0, 42, 128, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0, 29, + 0, 0, 9, 66, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, 128, + 65, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 42, 0, 16, + 0, 0, 0, 0, 0, 54, 0, 0, 5, 34, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, 0, 0, 0, 63, 69, 0, 0, 9, 242, + 0, 16, 0, 1, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, + 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, + 0, 31, 0, 4, 3, 42, 0, 16, 0, 0, 0, 0, 0, 54, 0, + 0, 8, 242, 32, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 62, 0, 0, 1, 21, 0, 0, 1, 56, 0, 0, 7, 114, 0, 16, + 0, 1, 0, 0, 0, 246, 15, 16, 0, 1, 0, 0, 0, 70, 2, + 16, 0, 1, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 0, + 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, + 1, 0, 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 56, 0, 0, + 7, 242, 32, 16, 0, 0, 0, 0, 0, 246, 15, 16, 0, 0, 0, + 0, 0, 70, 14, 16, 0, 1, 0, 0, 0, 62, 0, 0, 1, 83, + 84, 65, 84, 116, 0, 0, 0, 19, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 82, 68, 69, 70, 104, 2, 0, 0, 1, 0, 0, 0, + 232, 0, 0, 0, 5, 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, + 255, 0, 1, 0, 0, 51, 2, 0, 0, 188, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 203, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 216, 0, + 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, + 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, + 220, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, + 0, 255, 255, 255, 255, 1, 0, 0, 0, 1, 0, 0, 0, 12, 0, + 0, 0, 225, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 115, 77, 105, 114, 114, 111, 114, 83, 97, 109, 112, + 108, 101, 114, 0, 115, 77, 97, 115, 107, 83, 97, 109, 112, 108, 101, + 114, 0, 116, 101, 120, 0, 109, 97, 115, 107, 0, 99, 98, 50, 0, + 171, 171, 171, 225, 0, 0, 0, 7, 0, 0, 0, 0, 1, 0, 0, + 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 168, 1, 0, + 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 192, 1, + 0, 0, 0, 0, 0, 0, 208, 1, 0, 0, 48, 0, 0, 0, 8, + 0, 0, 0, 0, 0, 0, 0, 220, 1, 0, 0, 0, 0, 0, 0, + 236, 1, 0, 0, 64, 0, 0, 0, 12, 0, 0, 0, 2, 0, 0, + 0, 244, 1, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 80, 0, + 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, 220, 1, 0, 0, 0, + 0, 0, 0, 12, 2, 0, 0, 88, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 16, 2, 0, 0, 0, 0, 0, 0, 32, 2, 0, + 0, 92, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 16, 2, + 0, 0, 0, 0, 0, 0, 40, 2, 0, 0, 96, 0, 0, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 16, 2, 0, 0, 0, 0, 0, 0, + 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84, 111, 85, 115, + 101, 114, 83, 112, 97, 99, 101, 0, 171, 3, 0, 3, 0, 3, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 109, 101, 110, + 115, 105, 111, 110, 115, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, 102, 102, 0, 171, 171, + 171, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 99, 101, 110, 116, 101, 114, 49, 0, 65, 0, 171, 171, 0, + 0, 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 114, 97, 100, 105, 117, 115, 49, 0, 115, 113, 95, 114, 97, 100, 105, + 117, 115, 49, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, + 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, + 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, + 48, 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 171, 73, 83, 71, + 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 3, 0, + 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 0, 0, 12, 12, 0, 0, 83, 86, 95, 80, 111, + 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, + 0, 171, 171, 171, 79, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, + 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 83, + 86, 95, 84, 97, 114, 103, 101, 116, 0, 171, 171, 217, 183, 0, 0, + 0, 0, 0, 0, 83, 97, 109, 112, 108, 101, 67, 111, 110, 105, 99, + 71, 114, 97, 100, 105, 101, 110, 116, 0, 12, 7, 0, 0, 68, 88, + 66, 67, 139, 251, 38, 36, 124, 246, 203, 168, 214, 67, 77, 25, 142, + 114, 138, 15, 1, 0, 0, 0, 12, 7, 0, 0, 6, 0, 0, 0, + 56, 0, 0, 0, 148, 1, 0, 0, 104, 3, 0, 0, 228, 3, 0, + 0, 104, 6, 0, 0, 156, 6, 0, 0, 65, 111, 110, 57, 84, 1, + 0, 0, 84, 1, 0, 0, 0, 2, 254, 255, 252, 0, 0, 0, 88, + 0, 0, 0, 4, 0, 36, 0, 0, 0, 84, 0, 0, 0, 84, 0, + 0, 0, 36, 0, 1, 0, 84, 0, 0, 0, 0, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 2, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 2, 0, 3, 0, 0, 0, 0, 0, 1, + 0, 3, 0, 1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 2, 254, 255, 81, 0, 0, 5, 6, 0, 15, 160, 0, 0, 128, + 63, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, + 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, 4, 0, 0, 4, 0, + 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, 160, 2, 0, 228, 160, + 4, 0, 0, 4, 0, 0, 3, 128, 0, 0, 228, 144, 1, 0, 238, + 160, 1, 0, 228, 160, 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, + 0, 128, 6, 0, 0, 160, 5, 0, 0, 3, 0, 0, 4, 128, 0, + 0, 170, 128, 5, 0, 0, 160, 5, 0, 0, 3, 1, 0, 1, 128, + 0, 0, 170, 128, 6, 0, 85, 160, 2, 0, 0, 3, 0, 0, 4, + 128, 0, 0, 85, 129, 6, 0, 0, 160, 2, 0, 0, 3, 0, 0, + 3, 192, 0, 0, 228, 128, 0, 0, 228, 160, 5, 0, 0, 3, 0, + 0, 1, 128, 0, 0, 170, 128, 5, 0, 85, 160, 5, 0, 0, 3, + 1, 0, 2, 128, 0, 0, 0, 128, 6, 0, 85, 160, 1, 0, 0, + 2, 1, 0, 4, 128, 6, 0, 0, 160, 8, 0, 0, 3, 0, 0, + 8, 224, 1, 0, 228, 128, 3, 0, 228, 160, 8, 0, 0, 3, 0, + 0, 4, 224, 1, 0, 228, 128, 4, 0, 228, 160, 1, 0, 0, 2, + 0, 0, 12, 192, 6, 0, 36, 160, 255, 255, 0, 0, 83, 72, 68, + 82, 204, 1, 0, 0, 64, 0, 1, 0, 115, 0, 0, 0, 89, 0, + 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0, 89, + 0, 0, 4, 70, 142, 32, 0, 1, 0, 0, 0, 4, 0, 0, 0, + 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0, 0, 103, 0, 0, + 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0, 0, 0, 101, 0, + 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 194, + 32, 16, 0, 1, 0, 0, 0, 104, 0, 0, 2, 2, 0, 0, 0, + 54, 0, 0, 8, 194, 32, 16, 0, 0, 0, 0, 0, 2, 64, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 128, 63, 50, 0, 0, 11, 50, 0, 16, 0, 0, 0, 0, 0, 70, + 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 54, 0, 0, 5, 50, 32, 16, 0, 0, 0, 0, 0, 70, 0, + 16, 0, 0, 0, 0, 0, 0, 0, 0, 7, 18, 0, 16, 0, 0, + 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, + 0, 0, 128, 63, 0, 0, 0, 8, 34, 0, 16, 0, 0, 0, 0, + 0, 26, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 128, 63, 56, 0, 0, 8, 50, 0, 16, 0, 0, + 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 70, 128, 32, 0, + 1, 0, 0, 0, 3, 0, 0, 0, 56, 0, 0, 10, 50, 0, 16, + 0, 1, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 2, 64, + 0, 0, 0, 0, 0, 63, 0, 0, 0, 63, 0, 0, 0, 0, 0, + 0, 0, 0, 54, 0, 0, 5, 66, 0, 16, 0, 1, 0, 0, 0, + 1, 64, 0, 0, 0, 0, 128, 63, 16, 0, 0, 8, 66, 32, 16, + 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 70, 130, + 32, 0, 1, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 8, 130, + 32, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, + 70, 130, 32, 0, 1, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, + 11, 50, 32, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, 0, + 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 70, + 128, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 62, 0, 0, 1, + 83, 84, 65, 84, 116, 0, 0, 0, 12, 0, 0, 0, 2, 0, 0, + 0, 0, 0, 0, 0, 4, 0, 0, 0, 8, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 82, 68, 69, 70, 124, 2, 0, 0, 2, 0, 0, + 0, 100, 0, 0, 0, 2, 0, 0, 0, 28, 0, 0, 0, 0, 4, + 254, 255, 0, 1, 0, 0, 72, 2, 0, 0, 92, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 99, + 98, 48, 0, 99, 98, 51, 0, 92, 0, 0, 0, 4, 0, 0, 0, + 148, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 96, 0, 0, 0, 6, 0, 0, 0, 52, 1, 0, 0, 80, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 244, 0, 0, 0, 0, + 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 16, 1, 0, 0, 16, 0, 0, 0, 16, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 26, 1, + 0, 0, 32, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0, 48, 0, 0, 0, + 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 81, 117, 97, 100, 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, + 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, + 101, 120, 67, 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, + 120, 67, 111, 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, + 111, 114, 0, 171, 171, 196, 1, 0, 0, 0, 0, 0, 0, 44, 0, + 0, 0, 2, 0, 0, 0, 224, 1, 0, 0, 0, 0, 0, 0, 240, + 1, 0, 0, 48, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, + 0, 2, 0, 0, 0, 0, 0, 0, 16, 2, 0, 0, 56, 0, 0, + 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, + 0, 0, 23, 2, 0, 0, 64, 0, 0, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 32, 2, 0, 0, 0, 0, 0, 0, 48, 2, 0, 0, + 68, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 32, 2, 0, + 0, 0, 0, 0, 0, 61, 2, 0, 0, 72, 0, 0, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 32, 2, 0, 0, 0, 0, 0, 0, 68, + 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84, 111, 85, 115, 101, + 114, 83, 112, 97, 99, 101, 95, 99, 98, 51, 0, 171, 3, 0, 3, + 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 105, + 109, 101, 110, 115, 105, 111, 110, 115, 95, 99, 98, 51, 0, 171, 1, + 0, 3, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 99, 101, 110, 116, 101, 114, 0, 97, 110, 103, 108, 101, 0, 171, 171, + 171, 0, 0, 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 115, 116, 97, 114, 116, 95, 111, 102, 102, 115, 101, 116, 0, + 101, 110, 100, 95, 111, 102, 102, 115, 101, 116, 0, 77, 105, 99, 114, + 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, + 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, + 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, + 0, 171, 171, 73, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, + 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, 0, 0, 80, 79, + 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, 83, 71, 78, 104, + 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 12, 0, 0, 92, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, 80, 111, 115, 105, + 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, + 171, 171, 193, 191, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 76, 12, 0, 0, 68, 88, 66, 67, + 237, 135, 171, 29, 251, 164, 113, 72, 131, 168, 215, 155, 110, 76, 248, + 73, 1, 0, 0, 0, 76, 12, 0, 0, 6, 0, 0, 0, 56, 0, + 0, 0, 144, 3, 0, 0, 228, 8, 0, 0, 96, 9, 0, 0, 168, + 11, 0, 0, 24, 12, 0, 0, 65, 111, 110, 57, 80, 3, 0, 0, + 80, 3, 0, 0, 0, 2, 255, 255, 24, 3, 0, 0, 56, 0, 0, + 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, 0, 56, 0, 2, 0, + 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, + 0, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 2, 255, 255, + 81, 0, 0, 5, 2, 0, 15, 160, 95, 174, 170, 60, 54, 90, 174, + 189, 226, 118, 56, 62, 4, 29, 169, 190, 81, 0, 0, 5, 3, 0, + 15, 160, 56, 247, 127, 63, 0, 0, 0, 0, 0, 0, 128, 63, 219, + 15, 73, 64, 81, 0, 0, 5, 4, 0, 15, 160, 0, 0, 0, 192, + 219, 15, 201, 63, 216, 15, 201, 63, 134, 249, 34, 62, 81, 0, 0, + 5, 5, 0, 15, 160, 0, 0, 0, 63, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 0, 0, 0, 128, 0, + 0, 15, 176, 31, 0, 0, 2, 0, 0, 0, 144, 0, 8, 15, 160, + 31, 0, 0, 2, 0, 0, 0, 144, 1, 8, 15, 160, 2, 0, 0, + 3, 0, 0, 3, 128, 0, 0, 235, 176, 0, 0, 238, 161, 35, 0, + 0, 2, 0, 0, 12, 128, 0, 0, 68, 128, 2, 0, 0, 3, 1, + 0, 3, 128, 0, 0, 238, 129, 0, 0, 235, 128, 88, 0, 0, 4, + 0, 0, 12, 128, 1, 0, 0, 128, 0, 0, 228, 128, 0, 0, 180, + 128, 88, 0, 0, 4, 1, 0, 1, 128, 1, 0, 85, 128, 3, 0, + 85, 160, 3, 0, 170, 160, 6, 0, 0, 2, 0, 0, 8, 128, 0, + 0, 255, 128, 5, 0, 0, 3, 0, 0, 4, 128, 0, 0, 255, 128, + 0, 0, 170, 128, 5, 0, 0, 3, 0, 0, 8, 128, 0, 0, 170, + 128, 0, 0, 170, 128, 4, 0, 0, 4, 1, 0, 2, 128, 0, 0, + 255, 128, 2, 0, 0, 160, 2, 0, 85, 160, 4, 0, 0, 4, 1, + 0, 2, 128, 0, 0, 255, 128, 1, 0, 85, 128, 2, 0, 170, 160, + 4, 0, 0, 4, 1, 0, 2, 128, 0, 0, 255, 128, 1, 0, 85, + 128, 2, 0, 255, 160, 4, 0, 0, 4, 0, 0, 8, 128, 0, 0, + 255, 128, 1, 0, 85, 128, 3, 0, 0, 160, 5, 0, 0, 3, 0, + 0, 4, 128, 0, 0, 255, 128, 0, 0, 170, 128, 4, 0, 0, 4, + 0, 0, 8, 128, 0, 0, 170, 128, 4, 0, 0, 160, 4, 0, 85, + 160, 4, 0, 0, 4, 0, 0, 4, 128, 0, 0, 255, 128, 1, 0, + 0, 128, 0, 0, 170, 128, 88, 0, 0, 4, 0, 0, 8, 128, 0, + 0, 0, 128, 3, 0, 85, 161, 3, 0, 255, 161, 2, 0, 0, 3, + 0, 0, 4, 128, 0, 0, 255, 128, 0, 0, 170, 128, 2, 0, 0, + 3, 0, 0, 8, 128, 0, 0, 170, 128, 0, 0, 170, 128, 2, 0, + 0, 3, 1, 0, 1, 128, 0, 0, 0, 129, 0, 0, 85, 128, 88, + 0, 0, 4, 0, 0, 3, 128, 1, 0, 0, 128, 0, 0, 228, 128, + 0, 0, 225, 128, 88, 0, 0, 4, 0, 0, 2, 128, 0, 0, 85, + 128, 3, 0, 170, 160, 3, 0, 85, 160, 88, 0, 0, 4, 0, 0, + 1, 128, 0, 0, 0, 128, 3, 0, 85, 160, 0, 0, 85, 128, 4, + 0, 0, 4, 0, 0, 1, 128, 0, 0, 0, 128, 0, 0, 255, 129, + 0, 0, 170, 128, 2, 0, 0, 3, 0, 0, 1, 128, 0, 0, 0, + 128, 1, 0, 0, 161, 2, 0, 0, 3, 0, 0, 1, 128, 0, 0, + 0, 128, 4, 0, 170, 160, 5, 0, 0, 3, 0, 0, 2, 128, 0, + 0, 0, 128, 4, 0, 255, 160, 35, 0, 0, 2, 0, 0, 2, 128, + 0, 0, 85, 128, 19, 0, 0, 2, 0, 0, 2, 128, 0, 0, 85, + 128, 88, 0, 0, 4, 0, 0, 1, 128, 0, 0, 0, 128, 0, 0, + 85, 128, 0, 0, 85, 129, 2, 0, 0, 3, 0, 0, 1, 128, 0, + 0, 0, 128, 1, 0, 85, 161, 2, 0, 0, 3, 0, 0, 2, 128, + 1, 0, 85, 161, 1, 0, 170, 160, 6, 0, 0, 2, 0, 0, 2, + 128, 0, 0, 85, 128, 5, 0, 0, 3, 0, 0, 1, 128, 0, 0, + 85, 128, 0, 0, 0, 128, 1, 0, 0, 2, 0, 0, 2, 128, 5, + 0, 0, 160, 66, 0, 0, 3, 1, 0, 15, 128, 0, 0, 228, 176, + 1, 8, 228, 160, 66, 0, 0, 3, 0, 0, 15, 128, 0, 0, 228, + 128, 0, 8, 228, 160, 5, 0, 0, 3, 0, 0, 7, 128, 0, 0, + 255, 128, 0, 0, 228, 128, 5, 0, 0, 3, 0, 0, 15, 128, 1, + 0, 255, 128, 0, 0, 228, 128, 1, 0, 0, 2, 0, 8, 15, 128, + 0, 0, 228, 128, 255, 255, 0, 0, 83, 72, 68, 82, 76, 5, 0, + 0, 64, 0, 0, 0, 83, 1, 0, 0, 89, 0, 0, 4, 70, 142, + 32, 0, 0, 0, 0, 0, 5, 0, 0, 0, 90, 0, 0, 3, 0, + 96, 16, 0, 0, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, + 1, 0, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 0, 0, 0, + 0, 85, 85, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 1, 0, + 0, 0, 85, 85, 0, 0, 98, 16, 0, 3, 50, 16, 16, 0, 1, + 0, 0, 0, 98, 16, 0, 3, 194, 16, 16, 0, 1, 0, 0, 0, + 101, 0, 0, 3, 242, 32, 16, 0, 0, 0, 0, 0, 104, 0, 0, + 2, 2, 0, 0, 0, 0, 0, 0, 9, 50, 0, 16, 0, 0, 0, + 0, 0, 182, 31, 16, 0, 1, 0, 0, 0, 182, 143, 32, 128, 65, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 52, 0, 0, 9, + 66, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 129, 0, 0, + 0, 0, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0, 0, 0, 0, + 0, 0, 14, 0, 0, 10, 66, 0, 16, 0, 0, 0, 0, 0, 2, + 64, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, + 0, 0, 128, 63, 42, 0, 16, 0, 0, 0, 0, 0, 51, 0, 0, + 9, 130, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 129, 0, + 0, 0, 0, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0, 0, 0, + 0, 0, 0, 56, 0, 0, 7, 66, 0, 16, 0, 0, 0, 0, 0, + 42, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, + 0, 56, 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, 42, 0, + 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 50, + 0, 0, 9, 18, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, + 0, 0, 0, 0, 1, 64, 0, 0, 95, 174, 170, 60, 1, 64, 0, + 0, 54, 90, 174, 189, 50, 0, 0, 9, 18, 0, 16, 0, 1, 0, + 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 1, + 0, 0, 0, 1, 64, 0, 0, 226, 118, 56, 62, 50, 0, 0, 9, + 18, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, + 0, 10, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 4, 29, + 169, 190, 50, 0, 0, 9, 130, 0, 16, 0, 0, 0, 0, 0, 58, + 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, + 1, 64, 0, 0, 56, 247, 127, 63, 56, 0, 0, 7, 18, 0, 16, + 0, 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 42, 0, + 16, 0, 0, 0, 0, 0, 50, 0, 0, 9, 18, 0, 16, 0, 1, + 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, + 0, 0, 0, 192, 1, 64, 0, 0, 219, 15, 201, 63, 49, 0, 0, + 9, 34, 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 128, 129, 0, + 0, 0, 0, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 7, 18, 0, 16, 0, 1, 0, 0, 0, + 26, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, + 0, 50, 0, 0, 9, 66, 0, 16, 0, 0, 0, 0, 0, 42, 0, + 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 10, + 0, 16, 0, 1, 0, 0, 0, 49, 0, 0, 8, 130, 0, 16, 0, + 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, + 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 7, 130, 0, + 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 1, + 64, 0, 0, 219, 15, 73, 192, 0, 0, 0, 7, 66, 0, 16, 0, + 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, + 0, 0, 0, 0, 0, 51, 0, 0, 7, 130, 0, 16, 0, 0, 0, + 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, + 0, 0, 0, 52, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, + 26, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, + 0, 29, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 0, + 0, 0, 0, 49, 0, 0, 8, 34, 0, 16, 0, 0, 0, 0, 0, + 58, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 128, 65, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 7, 18, 0, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, + 0, 0, 0, 55, 0, 0, 10, 18, 0, 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 128, 65, 0, 0, + 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 0, 0, + 0, 9, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, + 0, 0, 0, 10, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 0, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, + 0, 10, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 216, 15, + 201, 63, 56, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, + 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 134, 249, 34, 62, + 29, 0, 0, 8, 34, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, + 0, 0, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 0, 0, + 0, 0, 26, 0, 0, 6, 18, 0, 16, 0, 0, 0, 0, 0, 10, + 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 55, 0, 0, 10, + 18, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, + 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 128, 65, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 18, 0, 16, 0, 0, + 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 26, 128, 32, 128, + 65, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, + 10, 34, 0, 16, 0, 0, 0, 0, 0, 26, 128, 32, 128, 65, 0, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 42, 128, 32, 0, 0, + 0, 0, 0, 4, 0, 0, 0, 14, 0, 0, 7, 18, 0, 16, 0, + 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, + 0, 0, 0, 0, 0, 54, 0, 0, 5, 34, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, 0, 0, 0, 63, 69, 0, 0, 9, 242, + 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, + 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, + 0, 56, 0, 0, 7, 114, 0, 16, 0, 0, 0, 0, 0, 246, 15, + 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 69, + 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, + 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0, 96, 16, + 0, 1, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16, 0, 0, 0, + 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 246, 15, 16, 0, 1, + 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, + 39, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, + 0, 30, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, + 64, 2, 0, 0, 1, 0, 0, 0, 224, 0, 0, 0, 5, 0, 0, + 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0, 12, 2, + 0, 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 197, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 210, 0, 0, 0, 2, 0, 0, 0, 5, + 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 1, 0, 0, 0, 12, 0, 0, 0, 214, 0, 0, 0, 2, 0, 0, + 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 1, 0, + 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 219, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 115, 83, 97, + 109, 112, 108, 101, 114, 0, 115, 77, 97, 115, 107, 83, 97, 109, 112, + 108, 101, 114, 0, 116, 101, 120, 0, 109, 97, 115, 107, 0, 99, 98, + 51, 0, 171, 219, 0, 0, 0, 6, 0, 0, 0, 248, 0, 0, 0, + 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 1, 0, + 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 164, 1, + 0, 0, 0, 0, 0, 0, 180, 1, 0, 0, 48, 0, 0, 0, 8, + 0, 0, 0, 0, 0, 0, 0, 196, 1, 0, 0, 0, 0, 0, 0, + 212, 1, 0, 0, 56, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, + 0, 196, 1, 0, 0, 0, 0, 0, 0, 219, 1, 0, 0, 64, 0, + 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 228, 1, 0, 0, 0, + 0, 0, 0, 244, 1, 0, 0, 68, 0, 0, 0, 4, 0, 0, 0, + 2, 0, 0, 0, 228, 1, 0, 0, 0, 0, 0, 0, 1, 2, 0, + 0, 72, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 228, 1, + 0, 0, 0, 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, + 99, 101, 84, 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 95, 99, + 98, 51, 0, 171, 3, 0, 3, 0, 3, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 100, 105, 109, 101, 110, 115, 105, 111, 110, 115, + 95, 99, 98, 51, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 0, 97, + 110, 103, 108, 101, 0, 171, 171, 171, 0, 0, 3, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 116, 97, 114, 116, 95, + 111, 102, 102, 115, 101, 116, 0, 101, 110, 100, 95, 111, 102, 102, 115, + 101, 116, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, + 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, + 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, + 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 73, 83, 71, 78, 104, + 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 12, 12, 0, 0, 83, 86, 95, 80, 111, 115, 105, + 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, + 171, 171, 79, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, + 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, + 84, 97, 114, 103, 101, 116, 0, 171, 171, 229, 198, 0, 0, 0, 0, + 0, 0, 12, 7, 0, 0, 68, 88, 66, 67, 139, 251, 38, 36, 124, + 246, 203, 168, 214, 67, 77, 25, 142, 114, 138, 15, 1, 0, 0, 0, + 12, 7, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 148, 1, 0, + 0, 104, 3, 0, 0, 228, 3, 0, 0, 104, 6, 0, 0, 156, 6, + 0, 0, 65, 111, 110, 57, 84, 1, 0, 0, 84, 1, 0, 0, 0, + 2, 254, 255, 252, 0, 0, 0, 88, 0, 0, 0, 4, 0, 36, 0, + 0, 0, 84, 0, 0, 0, 84, 0, 0, 0, 36, 0, 1, 0, 84, + 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 1, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, + 0, 3, 0, 0, 0, 0, 0, 1, 0, 3, 0, 1, 0, 5, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0, + 5, 6, 0, 15, 160, 0, 0, 128, 63, 0, 0, 0, 63, 0, 0, + 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0, + 0, 15, 144, 4, 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144, + 2, 0, 238, 160, 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 3, + 128, 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0, + 0, 3, 0, 0, 4, 128, 0, 0, 0, 128, 6, 0, 0, 160, 5, + 0, 0, 3, 0, 0, 4, 128, 0, 0, 170, 128, 5, 0, 0, 160, + 5, 0, 0, 3, 1, 0, 1, 128, 0, 0, 170, 128, 6, 0, 85, + 160, 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, 85, 129, 6, 0, + 0, 160, 2, 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, + 0, 228, 160, 5, 0, 0, 3, 0, 0, 1, 128, 0, 0, 170, 128, + 5, 0, 85, 160, 5, 0, 0, 3, 1, 0, 2, 128, 0, 0, 0, + 128, 6, 0, 85, 160, 1, 0, 0, 2, 1, 0, 4, 128, 6, 0, + 0, 160, 8, 0, 0, 3, 0, 0, 8, 224, 1, 0, 228, 128, 3, + 0, 228, 160, 8, 0, 0, 3, 0, 0, 4, 224, 1, 0, 228, 128, + 4, 0, 228, 160, 1, 0, 0, 2, 0, 0, 12, 192, 6, 0, 36, + 160, 255, 255, 0, 0, 83, 72, 68, 82, 204, 1, 0, 0, 64, 0, + 1, 0, 115, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, + 0, 0, 0, 3, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, + 1, 0, 0, 0, 4, 0, 0, 0, 95, 0, 0, 3, 50, 16, 16, + 0, 0, 0, 0, 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, 1, + 0, 0, 0, 101, 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, 0, + 104, 0, 0, 2, 2, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16, + 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50, + 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, + 230, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 50, 32, + 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 0, + 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 0, 0, 0, + 8, 34, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 65, 0, + 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 56, + 0, 0, 8, 50, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, + 0, 0, 0, 0, 70, 128, 32, 0, 1, 0, 0, 0, 3, 0, 0, + 0, 56, 0, 0, 10, 50, 0, 16, 0, 1, 0, 0, 0, 70, 0, + 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 63, 0, + 0, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, + 66, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, + 63, 16, 0, 0, 8, 66, 32, 16, 0, 1, 0, 0, 0, 70, 2, + 16, 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 16, 0, 0, 8, 130, 32, 16, 0, 1, 0, 0, 0, + 70, 2, 16, 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, + 0, 1, 0, 0, 0, 50, 0, 0, 11, 50, 32, 16, 0, 1, 0, + 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, + 0, 0, 0, 2, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, + 0, 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 4, 0, + 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, + 70, 124, 2, 0, 0, 2, 0, 0, 0, 100, 0, 0, 0, 2, 0, + 0, 0, 28, 0, 0, 0, 0, 4, 254, 255, 0, 1, 0, 0, 72, + 2, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 99, 98, 51, 0, + 92, 0, 0, 0, 4, 0, 0, 0, 148, 0, 0, 0, 64, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 6, 0, + 0, 0, 52, 1, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 244, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, + 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 16, 1, 0, + 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 26, 1, 0, 0, 32, 0, 0, 0, 16, + 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 40, 1, 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, 101, + 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, 100, + 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, 115, + 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, 171, 171, 196, 1, + 0, 0, 0, 0, 0, 0, 44, 0, 0, 0, 2, 0, 0, 0, 224, + 1, 0, 0, 0, 0, 0, 0, 240, 1, 0, 0, 48, 0, 0, 0, + 8, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, + 0, 16, 2, 0, 0, 56, 0, 0, 0, 8, 0, 0, 0, 0, 0, + 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 23, 2, 0, 0, 64, + 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 32, 2, 0, 0, + 0, 0, 0, 0, 48, 2, 0, 0, 68, 0, 0, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 32, 2, 0, 0, 0, 0, 0, 0, 61, 2, + 0, 0, 72, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 32, + 2, 0, 0, 0, 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, + 97, 99, 101, 84, 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 95, + 99, 98, 51, 0, 171, 3, 0, 3, 0, 3, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 100, 105, 109, 101, 110, 115, 105, 111, 110, + 115, 95, 99, 98, 51, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 0, + 97, 110, 103, 108, 101, 0, 171, 171, 171, 0, 0, 3, 0, 1, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 115, 116, 97, 114, 116, + 95, 111, 102, 102, 115, 101, 116, 0, 101, 110, 100, 95, 111, 102, 102, + 115, 101, 116, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, + 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, + 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, + 48, 48, 46, 49, 54, 51, 56, 52, 0, 171, 171, 73, 83, 71, 78, + 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 7, 3, 0, 0, 80, 79, 83, 73, 84, 73, 79, 78, 0, + 171, 171, 171, 79, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, + 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, + 0, 0, 0, 3, 12, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 3, 0, + 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, + 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 61, 211, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 80, 12, 0, 0, 68, 88, 66, 67, 247, 165, 11, 199, 50, 224, 108, + 119, 183, 179, 87, 201, 53, 213, 28, 250, 1, 0, 0, 0, 80, 12, + 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 144, 3, 0, 0, 228, + 8, 0, 0, 96, 9, 0, 0, 172, 11, 0, 0, 28, 12, 0, 0, + 65, 111, 110, 57, 80, 3, 0, 0, 80, 3, 0, 0, 0, 2, 255, + 255, 24, 3, 0, 0, 56, 0, 0, 0, 1, 0, 44, 0, 0, 0, + 56, 0, 0, 0, 56, 0, 2, 0, 36, 0, 0, 0, 56, 0, 0, + 0, 0, 0, 1, 1, 1, 0, 0, 0, 3, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 1, 2, 255, 255, 81, 0, 0, 5, 2, 0, 15, + 160, 95, 174, 170, 60, 54, 90, 174, 189, 226, 118, 56, 62, 4, 29, + 169, 190, 81, 0, 0, 5, 3, 0, 15, 160, 56, 247, 127, 63, 0, + 0, 0, 0, 0, 0, 128, 63, 219, 15, 73, 64, 81, 0, 0, 5, + 4, 0, 15, 160, 0, 0, 0, 192, 219, 15, 201, 63, 216, 15, 201, + 63, 134, 249, 34, 62, 81, 0, 0, 5, 5, 0, 15, 160, 0, 0, + 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, + 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, + 0, 0, 0, 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, + 144, 1, 8, 15, 160, 2, 0, 0, 3, 0, 0, 3, 128, 0, 0, + 235, 176, 0, 0, 238, 161, 35, 0, 0, 2, 0, 0, 12, 128, 0, + 0, 68, 128, 2, 0, 0, 3, 1, 0, 3, 128, 0, 0, 238, 129, + 0, 0, 235, 128, 88, 0, 0, 4, 0, 0, 12, 128, 1, 0, 0, + 128, 0, 0, 228, 128, 0, 0, 180, 128, 88, 0, 0, 4, 1, 0, + 1, 128, 1, 0, 85, 128, 3, 0, 85, 160, 3, 0, 170, 160, 6, + 0, 0, 2, 0, 0, 8, 128, 0, 0, 255, 128, 5, 0, 0, 3, + 0, 0, 4, 128, 0, 0, 255, 128, 0, 0, 170, 128, 5, 0, 0, + 3, 0, 0, 8, 128, 0, 0, 170, 128, 0, 0, 170, 128, 4, 0, + 0, 4, 1, 0, 2, 128, 0, 0, 255, 128, 2, 0, 0, 160, 2, + 0, 85, 160, 4, 0, 0, 4, 1, 0, 2, 128, 0, 0, 255, 128, + 1, 0, 85, 128, 2, 0, 170, 160, 4, 0, 0, 4, 1, 0, 2, + 128, 0, 0, 255, 128, 1, 0, 85, 128, 2, 0, 255, 160, 4, 0, + 0, 4, 0, 0, 8, 128, 0, 0, 255, 128, 1, 0, 85, 128, 3, + 0, 0, 160, 5, 0, 0, 3, 0, 0, 4, 128, 0, 0, 255, 128, + 0, 0, 170, 128, 4, 0, 0, 4, 0, 0, 8, 128, 0, 0, 170, + 128, 4, 0, 0, 160, 4, 0, 85, 160, 4, 0, 0, 4, 0, 0, + 4, 128, 0, 0, 255, 128, 1, 0, 0, 128, 0, 0, 170, 128, 88, + 0, 0, 4, 0, 0, 8, 128, 0, 0, 0, 128, 3, 0, 85, 161, + 3, 0, 255, 161, 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, 255, + 128, 0, 0, 170, 128, 2, 0, 0, 3, 0, 0, 8, 128, 0, 0, + 170, 128, 0, 0, 170, 128, 2, 0, 0, 3, 1, 0, 1, 128, 0, + 0, 0, 129, 0, 0, 85, 128, 88, 0, 0, 4, 0, 0, 3, 128, + 1, 0, 0, 128, 0, 0, 228, 128, 0, 0, 225, 128, 88, 0, 0, + 4, 0, 0, 2, 128, 0, 0, 85, 128, 3, 0, 170, 160, 3, 0, + 85, 160, 88, 0, 0, 4, 0, 0, 1, 128, 0, 0, 0, 128, 3, + 0, 85, 160, 0, 0, 85, 128, 4, 0, 0, 4, 0, 0, 1, 128, + 0, 0, 0, 128, 0, 0, 255, 129, 0, 0, 170, 128, 2, 0, 0, + 3, 0, 0, 1, 128, 0, 0, 0, 128, 1, 0, 0, 161, 2, 0, + 0, 3, 0, 0, 1, 128, 0, 0, 0, 128, 4, 0, 170, 160, 5, + 0, 0, 3, 0, 0, 2, 128, 0, 0, 0, 128, 4, 0, 255, 160, + 35, 0, 0, 2, 0, 0, 2, 128, 0, 0, 85, 128, 19, 0, 0, + 2, 0, 0, 2, 128, 0, 0, 85, 128, 88, 0, 0, 4, 0, 0, + 1, 128, 0, 0, 0, 128, 0, 0, 85, 128, 0, 0, 85, 129, 2, + 0, 0, 3, 0, 0, 1, 128, 0, 0, 0, 128, 1, 0, 85, 161, + 2, 0, 0, 3, 0, 0, 2, 128, 1, 0, 85, 161, 1, 0, 170, + 160, 6, 0, 0, 2, 0, 0, 2, 128, 0, 0, 85, 128, 5, 0, + 0, 3, 0, 0, 1, 128, 0, 0, 85, 128, 0, 0, 0, 128, 1, + 0, 0, 2, 0, 0, 2, 128, 5, 0, 0, 160, 66, 0, 0, 3, + 1, 0, 15, 128, 0, 0, 228, 176, 1, 8, 228, 160, 66, 0, 0, + 3, 0, 0, 15, 128, 0, 0, 228, 128, 0, 8, 228, 160, 5, 0, + 0, 3, 0, 0, 7, 128, 0, 0, 255, 128, 0, 0, 228, 128, 5, + 0, 0, 3, 0, 0, 15, 128, 1, 0, 255, 128, 0, 0, 228, 128, + 1, 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, + 0, 83, 72, 68, 82, 76, 5, 0, 0, 64, 0, 0, 0, 83, 1, + 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 5, + 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, + 90, 0, 0, 3, 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, 0, + 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, 24, + 0, 4, 0, 112, 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, 98, + 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, 3, + 194, 16, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, + 0, 0, 0, 0, 0, 104, 0, 0, 2, 2, 0, 0, 0, 0, 0, + 0, 9, 50, 0, 16, 0, 0, 0, 0, 0, 182, 31, 16, 0, 1, + 0, 0, 0, 182, 143, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 52, 0, 0, 9, 66, 0, 16, 0, 0, 0, 0, + 0, 26, 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 10, 0, + 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 10, 66, + 0, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63, + 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 42, 0, 16, + 0, 0, 0, 0, 0, 51, 0, 0, 9, 130, 0, 16, 0, 0, 0, + 0, 0, 26, 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 10, + 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 7, + 66, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, + 0, 58, 0, 16, 0, 0, 0, 0, 0, 56, 0, 0, 7, 130, 0, + 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 42, + 0, 16, 0, 0, 0, 0, 0, 50, 0, 0, 9, 18, 0, 16, 0, + 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, + 0, 95, 174, 170, 60, 1, 64, 0, 0, 54, 90, 174, 189, 50, 0, + 0, 9, 18, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0, + 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, + 226, 118, 56, 62, 50, 0, 0, 9, 18, 0, 16, 0, 1, 0, 0, + 0, 58, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 1, 0, + 0, 0, 1, 64, 0, 0, 4, 29, 169, 190, 50, 0, 0, 9, 130, + 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 56, 247, 127, + 63, 56, 0, 0, 7, 18, 0, 16, 0, 1, 0, 0, 0, 58, 0, + 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 50, + 0, 0, 9, 18, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, + 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 192, 1, 64, 0, + 0, 219, 15, 201, 63, 49, 0, 0, 9, 34, 0, 16, 0, 1, 0, + 0, 0, 26, 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 10, + 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 7, + 18, 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, + 0, 10, 0, 16, 0, 1, 0, 0, 0, 50, 0, 0, 9, 66, 0, + 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 58, + 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, + 49, 0, 0, 8, 130, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, + 0, 0, 0, 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, 58, + 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 219, 15, 73, 192, + 0, 0, 0, 7, 66, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, + 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 51, 0, + 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, + 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 52, 0, 0, 7, + 18, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, + 0, 10, 0, 16, 0, 0, 0, 0, 0, 29, 0, 0, 8, 18, 0, + 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, + 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 49, 0, 0, 8, + 34, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, + 0, 58, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, + 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 55, 0, 0, 10, + 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, + 0, 42, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 42, 0, + 16, 0, 0, 0, 0, 0, 0, 0, 0, 9, 18, 0, 16, 0, 0, + 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, 128, 32, 128, + 65, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, + 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, 216, 15, 201, 63, 56, 0, 0, 7, 18, + 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 134, 249, 34, 62, 29, 0, 0, 8, 34, 0, 16, + 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, 0, + 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 6, 18, + 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0, 0, + 0, 0, 0, 0, 55, 0, 0, 10, 18, 0, 16, 0, 0, 0, 0, + 0, 26, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 9, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 26, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 10, 34, 0, 16, 0, 0, 0, + 0, 0, 26, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 4, + 0, 0, 0, 42, 128, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0, + 14, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, + 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 54, 0, + 0, 5, 34, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, + 0, 0, 63, 69, 0, 0, 9, 242, 0, 16, 0, 0, 0, 0, 0, + 70, 0, 16, 0, 0, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, + 0, 0, 96, 16, 0, 0, 0, 0, 0, 56, 0, 0, 7, 114, 0, + 16, 0, 0, 0, 0, 0, 246, 15, 16, 0, 0, 0, 0, 0, 70, + 2, 16, 0, 0, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, + 1, 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 126, 16, + 0, 1, 0, 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 56, 0, + 0, 7, 242, 32, 16, 0, 0, 0, 0, 0, 70, 14, 16, 0, 0, + 0, 0, 0, 246, 15, 16, 0, 1, 0, 0, 0, 62, 0, 0, 1, + 83, 84, 65, 84, 116, 0, 0, 0, 39, 0, 0, 0, 2, 0, 0, + 0, 0, 0, 0, 0, 3, 0, 0, 0, 30, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 82, 68, 69, 70, 68, 2, 0, 0, 1, 0, 0, + 0, 228, 0, 0, 0, 5, 0, 0, 0, 28, 0, 0, 0, 0, 4, + 255, 255, 0, 1, 0, 0, 16, 2, 0, 0, 188, 0, 0, 0, 3, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 201, 0, 0, + 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 214, + 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, + 0, 218, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, + 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 1, 0, 0, 0, 12, + 0, 0, 0, 223, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 115, 87, 114, 97, 112, 83, 97, 109, 112, 108, + 101, 114, 0, 115, 77, 97, 115, 107, 83, 97, 109, 112, 108, 101, 114, + 0, 116, 101, 120, 0, 109, 97, 115, 107, 0, 99, 98, 51, 0, 171, + 223, 0, 0, 0, 6, 0, 0, 0, 252, 0, 0, 0, 80, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 140, 1, 0, 0, 0, 0, + 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 168, 1, 0, 0, 0, + 0, 0, 0, 184, 1, 0, 0, 48, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 200, 1, 0, 0, 0, 0, 0, 0, 216, 1, 0, + 0, 56, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, 200, 1, + 0, 0, 0, 0, 0, 0, 223, 1, 0, 0, 64, 0, 0, 0, 4, + 0, 0, 0, 2, 0, 0, 0, 232, 1, 0, 0, 0, 0, 0, 0, + 248, 1, 0, 0, 68, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, + 0, 232, 1, 0, 0, 0, 0, 0, 0, 5, 2, 0, 0, 72, 0, + 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 232, 1, 0, 0, 0, + 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, 84, + 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 95, 99, 98, 51, 0, + 171, 3, 0, 3, 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 100, 105, 109, 101, 110, 115, 105, 111, 110, 115, 95, 99, 98, + 51, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 0, 97, 110, 103, 108, + 101, 0, 171, 171, 171, 0, 0, 3, 0, 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 115, 116, 97, 114, 116, 95, 111, 102, 102, + 115, 101, 116, 0, 101, 110, 100, 95, 111, 102, 102, 115, 101, 116, 0, + 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, + 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, + 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, + 54, 51, 56, 52, 0, 171, 171, 73, 83, 71, 78, 104, 0, 0, 0, + 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, + 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, + 0, 0, 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, + 0, 12, 12, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, + 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 79, + 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, 114, + 103, 101, 116, 0, 171, 171, 97, 218, 0, 0, 0, 0, 0, 0, 12, + 7, 0, 0, 68, 88, 66, 67, 139, 251, 38, 36, 124, 246, 203, 168, + 214, 67, 77, 25, 142, 114, 138, 15, 1, 0, 0, 0, 12, 7, 0, + 0, 6, 0, 0, 0, 56, 0, 0, 0, 148, 1, 0, 0, 104, 3, + 0, 0, 228, 3, 0, 0, 104, 6, 0, 0, 156, 6, 0, 0, 65, + 111, 110, 57, 84, 1, 0, 0, 84, 1, 0, 0, 0, 2, 254, 255, + 252, 0, 0, 0, 88, 0, 0, 0, 4, 0, 36, 0, 0, 0, 84, + 0, 0, 0, 84, 0, 0, 0, 36, 0, 1, 0, 84, 0, 0, 0, + 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, + 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 3, 0, + 0, 0, 0, 0, 1, 0, 3, 0, 1, 0, 5, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0, 5, 6, 0, + 15, 160, 0, 0, 128, 63, 0, 0, 0, 63, 0, 0, 0, 0, 0, + 0, 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, + 4, 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, + 160, 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 3, 128, 0, 0, + 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0, 0, 3, 0, + 0, 4, 128, 0, 0, 0, 128, 6, 0, 0, 160, 5, 0, 0, 3, + 0, 0, 4, 128, 0, 0, 170, 128, 5, 0, 0, 160, 5, 0, 0, + 3, 1, 0, 1, 128, 0, 0, 170, 128, 6, 0, 85, 160, 2, 0, + 0, 3, 0, 0, 4, 128, 0, 0, 85, 129, 6, 0, 0, 160, 2, + 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, 160, + 5, 0, 0, 3, 0, 0, 1, 128, 0, 0, 170, 128, 5, 0, 85, + 160, 5, 0, 0, 3, 1, 0, 2, 128, 0, 0, 0, 128, 6, 0, + 85, 160, 1, 0, 0, 2, 1, 0, 4, 128, 6, 0, 0, 160, 8, + 0, 0, 3, 0, 0, 8, 224, 1, 0, 228, 128, 3, 0, 228, 160, + 8, 0, 0, 3, 0, 0, 4, 224, 1, 0, 228, 128, 4, 0, 228, + 160, 1, 0, 0, 2, 0, 0, 12, 192, 6, 0, 36, 160, 255, 255, + 0, 0, 83, 72, 68, 82, 204, 1, 0, 0, 64, 0, 1, 0, 115, + 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 1, 0, 0, + 0, 4, 0, 0, 0, 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, + 0, 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, + 101, 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, 0, 104, 0, 0, + 2, 2, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16, 0, 0, 0, + 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50, 0, 16, 0, + 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 50, 32, 16, 0, 0, + 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 7, + 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, + 0, 1, 64, 0, 0, 0, 0, 128, 63, 0, 0, 0, 8, 34, 0, + 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 0, + 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 56, 0, 0, 8, + 50, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, + 0, 70, 128, 32, 0, 1, 0, 0, 0, 3, 0, 0, 0, 56, 0, + 0, 10, 50, 0, 16, 0, 1, 0, 0, 0, 70, 0, 16, 0, 0, + 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 63, 0, 0, 0, 63, + 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 66, 0, 16, + 0, 1, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 16, 0, + 0, 8, 66, 32, 16, 0, 1, 0, 0, 0, 70, 2, 16, 0, 1, + 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 16, 0, 0, 8, 130, 32, 16, 0, 1, 0, 0, 0, 70, 2, 16, + 0, 1, 0, 0, 0, 70, 130, 32, 0, 1, 0, 0, 0, 1, 0, + 0, 0, 50, 0, 0, 11, 50, 32, 16, 0, 1, 0, 0, 0, 70, + 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 2, 0, 0, + 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, 12, 0, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 8, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, 124, 2, + 0, 0, 2, 0, 0, 0, 100, 0, 0, 0, 2, 0, 0, 0, 28, + 0, 0, 0, 0, 4, 254, 255, 0, 1, 0, 0, 72, 2, 0, 0, + 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 99, 98, 48, 0, 99, 98, 51, 0, 92, 0, 0, + 0, 4, 0, 0, 0, 148, 0, 0, 0, 64, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 6, 0, 0, 0, 52, + 1, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 244, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 16, 1, 0, 0, 16, 0, + 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 26, 1, 0, 0, 32, 0, 0, 0, 16, 0, 0, 0, + 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 40, 1, 0, + 0, 48, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, 101, 115, 99, 0, + 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 77, + 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 84, 101, + 120, 116, 67, 111, 108, 111, 114, 0, 171, 171, 196, 1, 0, 0, 0, + 0, 0, 0, 44, 0, 0, 0, 2, 0, 0, 0, 224, 1, 0, 0, + 0, 0, 0, 0, 240, 1, 0, 0, 48, 0, 0, 0, 8, 0, 0, + 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 16, 2, + 0, 0, 56, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 23, 2, 0, 0, 64, 0, 0, 0, + 4, 0, 0, 0, 0, 0, 0, 0, 32, 2, 0, 0, 0, 0, 0, + 0, 48, 2, 0, 0, 68, 0, 0, 0, 4, 0, 0, 0, 0, 0, + 0, 0, 32, 2, 0, 0, 0, 0, 0, 0, 61, 2, 0, 0, 72, + 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 32, 2, 0, 0, + 0, 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, + 84, 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 95, 99, 98, 51, + 0, 171, 3, 0, 3, 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 100, 105, 109, 101, 110, 115, 105, 111, 110, 115, 95, 99, + 98, 51, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 0, 97, 110, 103, + 108, 101, 0, 171, 171, 171, 0, 0, 3, 0, 1, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 115, 116, 97, 114, 116, 95, 111, 102, + 102, 115, 101, 116, 0, 101, 110, 100, 95, 111, 102, 102, 115, 101, 116, + 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, + 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, + 49, 54, 51, 56, 52, 0, 171, 171, 73, 83, 71, 78, 44, 0, 0, + 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, + 3, 0, 0, 80, 79, 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, + 79, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, + 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, + 3, 12, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, + 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, + 79, 82, 68, 0, 171, 171, 171, 189, 230, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 84, 12, 0, + 0, 68, 88, 66, 67, 176, 243, 97, 211, 20, 121, 26, 93, 252, 11, + 132, 198, 181, 186, 97, 15, 1, 0, 0, 0, 84, 12, 0, 0, 6, + 0, 0, 0, 56, 0, 0, 0, 144, 3, 0, 0, 228, 8, 0, 0, + 96, 9, 0, 0, 176, 11, 0, 0, 32, 12, 0, 0, 65, 111, 110, + 57, 80, 3, 0, 0, 80, 3, 0, 0, 0, 2, 255, 255, 24, 3, + 0, 0, 56, 0, 0, 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, + 0, 56, 0, 2, 0, 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0, 3, 0, 2, 0, 0, 0, 0, 0, 0, + 0, 1, 2, 255, 255, 81, 0, 0, 5, 2, 0, 15, 160, 95, 174, + 170, 60, 54, 90, 174, 189, 226, 118, 56, 62, 4, 29, 169, 190, 81, + 0, 0, 5, 3, 0, 15, 160, 56, 247, 127, 63, 0, 0, 0, 0, + 0, 0, 128, 63, 219, 15, 73, 64, 81, 0, 0, 5, 4, 0, 15, + 160, 0, 0, 0, 192, 219, 15, 201, 63, 216, 15, 201, 63, 134, 249, + 34, 62, 81, 0, 0, 5, 5, 0, 15, 160, 0, 0, 0, 63, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, 0, 0, + 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1, 8, + 15, 160, 2, 0, 0, 3, 0, 0, 3, 128, 0, 0, 235, 176, 0, + 0, 238, 161, 35, 0, 0, 2, 0, 0, 12, 128, 0, 0, 68, 128, + 2, 0, 0, 3, 1, 0, 3, 128, 0, 0, 238, 129, 0, 0, 235, + 128, 88, 0, 0, 4, 0, 0, 12, 128, 1, 0, 0, 128, 0, 0, + 228, 128, 0, 0, 180, 128, 88, 0, 0, 4, 1, 0, 1, 128, 1, + 0, 85, 128, 3, 0, 85, 160, 3, 0, 170, 160, 6, 0, 0, 2, + 0, 0, 8, 128, 0, 0, 255, 128, 5, 0, 0, 3, 0, 0, 4, + 128, 0, 0, 255, 128, 0, 0, 170, 128, 5, 0, 0, 3, 0, 0, + 8, 128, 0, 0, 170, 128, 0, 0, 170, 128, 4, 0, 0, 4, 1, + 0, 2, 128, 0, 0, 255, 128, 2, 0, 0, 160, 2, 0, 85, 160, + 4, 0, 0, 4, 1, 0, 2, 128, 0, 0, 255, 128, 1, 0, 85, + 128, 2, 0, 170, 160, 4, 0, 0, 4, 1, 0, 2, 128, 0, 0, + 255, 128, 1, 0, 85, 128, 2, 0, 255, 160, 4, 0, 0, 4, 0, + 0, 8, 128, 0, 0, 255, 128, 1, 0, 85, 128, 3, 0, 0, 160, + 5, 0, 0, 3, 0, 0, 4, 128, 0, 0, 255, 128, 0, 0, 170, + 128, 4, 0, 0, 4, 0, 0, 8, 128, 0, 0, 170, 128, 4, 0, + 0, 160, 4, 0, 85, 160, 4, 0, 0, 4, 0, 0, 4, 128, 0, + 0, 255, 128, 1, 0, 0, 128, 0, 0, 170, 128, 88, 0, 0, 4, + 0, 0, 8, 128, 0, 0, 0, 128, 3, 0, 85, 161, 3, 0, 255, + 161, 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, 255, 128, 0, 0, + 170, 128, 2, 0, 0, 3, 0, 0, 8, 128, 0, 0, 170, 128, 0, + 0, 170, 128, 2, 0, 0, 3, 1, 0, 1, 128, 0, 0, 0, 129, + 0, 0, 85, 128, 88, 0, 0, 4, 0, 0, 3, 128, 1, 0, 0, + 128, 0, 0, 228, 128, 0, 0, 225, 128, 88, 0, 0, 4, 0, 0, + 2, 128, 0, 0, 85, 128, 3, 0, 170, 160, 3, 0, 85, 160, 88, + 0, 0, 4, 0, 0, 1, 128, 0, 0, 0, 128, 3, 0, 85, 160, + 0, 0, 85, 128, 4, 0, 0, 4, 0, 0, 1, 128, 0, 0, 0, + 128, 0, 0, 255, 129, 0, 0, 170, 128, 2, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 0, 128, 1, 0, 0, 161, 2, 0, 0, 3, 0, + 0, 1, 128, 0, 0, 0, 128, 4, 0, 170, 160, 5, 0, 0, 3, + 0, 0, 2, 128, 0, 0, 0, 128, 4, 0, 255, 160, 35, 0, 0, + 2, 0, 0, 2, 128, 0, 0, 85, 128, 19, 0, 0, 2, 0, 0, + 2, 128, 0, 0, 85, 128, 88, 0, 0, 4, 0, 0, 1, 128, 0, + 0, 0, 128, 0, 0, 85, 128, 0, 0, 85, 129, 2, 0, 0, 3, + 0, 0, 1, 128, 0, 0, 0, 128, 1, 0, 85, 161, 2, 0, 0, + 3, 0, 0, 2, 128, 1, 0, 85, 161, 1, 0, 170, 160, 6, 0, + 0, 2, 0, 0, 2, 128, 0, 0, 85, 128, 5, 0, 0, 3, 0, + 0, 1, 128, 0, 0, 85, 128, 0, 0, 0, 128, 1, 0, 0, 2, + 0, 0, 2, 128, 5, 0, 0, 160, 66, 0, 0, 3, 1, 0, 15, + 128, 0, 0, 228, 176, 1, 8, 228, 160, 66, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 128, 0, 8, 228, 160, 5, 0, 0, 3, 0, + 0, 7, 128, 0, 0, 255, 128, 0, 0, 228, 128, 5, 0, 0, 3, + 0, 0, 15, 128, 1, 0, 255, 128, 0, 0, 228, 128, 1, 0, 0, + 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0, 83, 72, + 68, 82, 76, 5, 0, 0, 64, 0, 0, 0, 83, 1, 0, 0, 89, + 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 5, 0, 0, 0, + 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 90, 0, 0, + 3, 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, 0, 4, 0, 112, + 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, 24, 0, 4, 0, + 112, 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, 98, 16, 0, 3, + 50, 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, 3, 194, 16, 16, + 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, 0, 0, 0, + 0, 0, 104, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 9, 50, + 0, 16, 0, 0, 0, 0, 0, 182, 31, 16, 0, 1, 0, 0, 0, + 182, 143, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, + 0, 52, 0, 0, 9, 66, 0, 16, 0, 0, 0, 0, 0, 26, 0, + 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 10, 0, 16, 128, 129, + 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 10, 66, 0, 16, 0, + 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, 0, 128, + 63, 0, 0, 128, 63, 0, 0, 128, 63, 42, 0, 16, 0, 0, 0, + 0, 0, 51, 0, 0, 9, 130, 0, 16, 0, 0, 0, 0, 0, 26, + 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 10, 0, 16, 128, + 129, 0, 0, 0, 0, 0, 0, 0, 56, 0, 0, 7, 66, 0, 16, + 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 58, 0, + 16, 0, 0, 0, 0, 0, 56, 0, 0, 7, 130, 0, 16, 0, 0, + 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, + 0, 0, 0, 0, 50, 0, 0, 9, 18, 0, 16, 0, 1, 0, 0, + 0, 58, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 95, 174, + 170, 60, 1, 64, 0, 0, 54, 90, 174, 189, 50, 0, 0, 9, 18, + 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, 226, 118, 56, + 62, 50, 0, 0, 9, 18, 0, 16, 0, 1, 0, 0, 0, 58, 0, + 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 1, + 64, 0, 0, 4, 29, 169, 190, 50, 0, 0, 9, 130, 0, 16, 0, + 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, + 0, 1, 0, 0, 0, 1, 64, 0, 0, 56, 247, 127, 63, 56, 0, + 0, 7, 18, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0, + 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 50, 0, 0, 9, + 18, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, + 0, 1, 64, 0, 0, 0, 0, 0, 192, 1, 64, 0, 0, 219, 15, + 201, 63, 49, 0, 0, 9, 34, 0, 16, 0, 1, 0, 0, 0, 26, + 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 10, 0, 16, 128, + 129, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 7, 18, 0, 16, + 0, 1, 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 10, 0, + 16, 0, 1, 0, 0, 0, 50, 0, 0, 9, 66, 0, 16, 0, 0, + 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, + 0, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 49, 0, 0, + 8, 130, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, + 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, + 0, 0, 0, 0, 1, 64, 0, 0, 219, 15, 73, 192, 0, 0, 0, + 7, 66, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, + 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 51, 0, 0, 7, 130, + 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 52, 0, 0, 7, 18, 0, 16, + 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, 29, 0, 0, 8, 18, 0, 16, 0, 0, + 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 128, + 65, 0, 0, 0, 0, 0, 0, 0, 49, 0, 0, 8, 34, 0, 16, + 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 58, 0, + 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 7, 18, + 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, + 26, 0, 16, 0, 0, 0, 0, 0, 55, 0, 0, 10, 18, 0, 16, + 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 42, 0, + 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, + 0, 0, 0, 0, 0, 0, 9, 18, 0, 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 10, 128, 32, 128, 65, 0, 0, + 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 7, 18, 0, + 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 1, + 64, 0, 0, 216, 15, 201, 63, 56, 0, 0, 7, 18, 0, 16, 0, + 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, + 0, 134, 249, 34, 62, 29, 0, 0, 8, 34, 0, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 128, 65, + 0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 6, 18, 0, 16, 0, + 0, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, + 0, 55, 0, 0, 10, 18, 0, 16, 0, 0, 0, 0, 0, 26, 0, + 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, + 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, + 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, + 0, 26, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 4, 0, + 0, 0, 0, 0, 0, 10, 34, 0, 16, 0, 0, 0, 0, 0, 26, + 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, + 42, 128, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0, 14, 0, 0, + 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, + 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 54, 0, 0, 5, 34, + 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 63, + 69, 0, 0, 9, 242, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, + 0, 0, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, + 16, 0, 0, 0, 0, 0, 56, 0, 0, 7, 114, 0, 16, 0, 0, + 0, 0, 0, 246, 15, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, + 0, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, + 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, + 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 242, + 32, 16, 0, 0, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, + 246, 15, 16, 0, 1, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, + 84, 116, 0, 0, 0, 39, 0, 0, 0, 2, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 3, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 82, 68, 69, 70, 72, 2, 0, 0, 1, 0, 0, 0, 232, 0, + 0, 0, 5, 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, + 1, 0, 0, 20, 2, 0, 0, 188, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 203, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0, + 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 220, 0, + 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, + 255, 255, 255, 1, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, + 225, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 115, 77, 105, 114, 114, 111, 114, 83, 97, 109, 112, 108, 101, + 114, 0, 115, 77, 97, 115, 107, 83, 97, 109, 112, 108, 101, 114, 0, + 116, 101, 120, 0, 109, 97, 115, 107, 0, 99, 98, 51, 0, 171, 171, + 171, 225, 0, 0, 0, 6, 0, 0, 0, 0, 1, 0, 0, 80, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 144, 1, 0, 0, 0, + 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 172, 1, 0, 0, + 0, 0, 0, 0, 188, 1, 0, 0, 48, 0, 0, 0, 8, 0, 0, + 0, 0, 0, 0, 0, 204, 1, 0, 0, 0, 0, 0, 0, 220, 1, + 0, 0, 56, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, 204, + 1, 0, 0, 0, 0, 0, 0, 227, 1, 0, 0, 64, 0, 0, 0, + 4, 0, 0, 0, 2, 0, 0, 0, 236, 1, 0, 0, 0, 0, 0, + 0, 252, 1, 0, 0, 68, 0, 0, 0, 4, 0, 0, 0, 2, 0, + 0, 0, 236, 1, 0, 0, 0, 0, 0, 0, 9, 2, 0, 0, 72, + 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 236, 1, 0, 0, + 0, 0, 0, 0, 68, 101, 118, 105, 99, 101, 83, 112, 97, 99, 101, + 84, 111, 85, 115, 101, 114, 83, 112, 97, 99, 101, 95, 99, 98, 51, + 0, 171, 3, 0, 3, 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 100, 105, 109, 101, 110, 115, 105, 111, 110, 115, 95, 99, + 98, 51, 0, 171, 1, 0, 3, 0, 1, 0, 2, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 0, 97, 110, 103, + 108, 101, 0, 171, 171, 171, 0, 0, 3, 0, 1, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 115, 116, 97, 114, 116, 95, 111, 102, + 102, 115, 101, 116, 0, 101, 110, 100, 95, 111, 102, 102, 115, 101, 116, + 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, + 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, + 49, 54, 51, 56, 52, 0, 171, 171, 73, 83, 71, 78, 104, 0, 0, + 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, + 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, + 0, 0, 12, 12, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, + 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, + 79, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, + 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, + 114, 103, 101, 116, 0, 171, 171, 225, 237, 0, 0, 0, 0, 0, 0, + 83, 97, 109, 112, 108, 101, 77, 97, 115, 107, 101, 100, 84, 101, 120, + 116, 117, 114, 101, 0, 68, 4, 0, 0, 68, 88, 66, 67, 77, 85, + 167, 240, 56, 56, 155, 78, 125, 96, 49, 253, 103, 100, 22, 62, 1, + 0, 0, 0, 68, 4, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, + 248, 0, 0, 0, 244, 1, 0, 0, 112, 2, 0, 0, 160, 3, 0, + 0, 212, 3, 0, 0, 65, 111, 110, 57, 184, 0, 0, 0, 184, 0, + 0, 0, 0, 2, 254, 255, 132, 0, 0, 0, 52, 0, 0, 0, 1, + 0, 36, 0, 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 36, 0, + 1, 0, 48, 0, 0, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0, 5, 4, 0, + 15, 160, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, + 0, 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, + 4, 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, + 160, 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 12, 224, 0, 0, + 20, 144, 3, 0, 180, 160, 3, 0, 20, 160, 4, 0, 0, 4, 0, + 0, 3, 128, 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, + 2, 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, + 160, 1, 0, 0, 2, 0, 0, 12, 192, 4, 0, 68, 160, 255, 255, + 0, 0, 83, 72, 68, 82, 244, 0, 0, 0, 64, 0, 1, 0, 61, + 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0, + 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101, + 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, 0, 50, 0, 0, 11, + 50, 32, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, + 0, 230, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 8, 194, + 32, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, + 11, 50, 32, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, 0, + 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 70, + 128, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 11, + 194, 32, 16, 0, 1, 0, 0, 0, 6, 20, 16, 0, 0, 0, 0, + 0, 166, 142, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 6, 132, + 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, + 84, 65, 84, 116, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 4, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 82, 68, 69, 70, 40, 1, 0, 0, 1, 0, 0, 0, + 64, 0, 0, 0, 1, 0, 0, 0, 28, 0, 0, 0, 0, 4, 254, + 255, 0, 1, 0, 0, 246, 0, 0, 0, 60, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, + 60, 0, 0, 0, 4, 0, 0, 0, 88, 0, 0, 0, 64, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 184, 0, 0, 0, 0, 0, + 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, + 0, 0, 0, 212, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, + 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, + 0, 32, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, + 0, 0, 0, 0, 0, 0, 236, 0, 0, 0, 48, 0, 0, 0, 16, + 0, 0, 0, 0, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, + 81, 117, 97, 100, 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, + 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, + 120, 67, 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, + 67, 111, 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, + 114, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, + 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, + 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, + 46, 49, 54, 51, 56, 52, 0, 73, 83, 71, 78, 44, 0, 0, 0, + 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, + 0, 0, 80, 79, 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, + 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, + 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, + 12, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, + 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, + 82, 68, 0, 171, 171, 171, 85, 250, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 212, 3, 0, 0, + 68, 88, 66, 67, 98, 136, 224, 212, 103, 235, 205, 77, 125, 241, 101, + 150, 199, 56, 208, 85, 1, 0, 0, 0, 212, 3, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, 224, 0, 0, 0, 188, 1, 0, 0, 56, + 2, 0, 0, 48, 3, 0, 0, 160, 3, 0, 0, 65, 111, 110, 57, + 160, 0, 0, 0, 160, 0, 0, 0, 0, 2, 255, 255, 116, 0, 0, + 0, 44, 0, 0, 0, 0, 0, 44, 0, 0, 0, 44, 0, 0, 0, + 44, 0, 2, 0, 36, 0, 0, 0, 44, 0, 0, 0, 0, 0, 1, + 1, 1, 0, 1, 2, 255, 255, 31, 0, 0, 2, 0, 0, 0, 128, + 0, 0, 15, 176, 31, 0, 0, 2, 0, 0, 0, 144, 0, 8, 15, + 160, 31, 0, 0, 2, 0, 0, 0, 144, 1, 8, 15, 160, 1, 0, + 0, 2, 0, 0, 3, 128, 0, 0, 235, 176, 66, 0, 0, 3, 1, + 0, 15, 128, 0, 0, 228, 176, 0, 8, 228, 160, 66, 0, 0, 3, + 0, 0, 15, 128, 0, 0, 228, 128, 1, 8, 228, 160, 5, 0, 0, + 3, 0, 0, 15, 128, 0, 0, 255, 128, 1, 0, 228, 128, 1, 0, + 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0, 83, + 72, 68, 82, 212, 0, 0, 0, 64, 0, 0, 0, 53, 0, 0, 0, + 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 90, 0, 0, + 3, 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, 0, 4, 0, 112, + 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, 24, 0, 4, 0, + 112, 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, 98, 16, 0, 3, + 50, 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, 3, 194, 16, 16, + 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, 0, 0, 0, + 0, 0, 104, 0, 0, 2, 2, 0, 0, 0, 69, 0, 0, 9, 242, + 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, + 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, + 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 230, 26, + 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0, + 96, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16, 0, + 0, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 246, 15, 16, + 0, 1, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, + 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, + 69, 70, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, + 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0, + 187, 0, 0, 0, 156, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 165, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 178, 0, 0, 0, 2, 0, 0, + 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, + 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 182, 0, 0, 0, 2, + 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, + 1, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 115, 83, 97, + 109, 112, 108, 101, 114, 0, 115, 77, 97, 115, 107, 83, 97, 109, 112, + 108, 101, 114, 0, 116, 101, 120, 0, 109, 97, 115, 107, 0, 77, 105, + 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, + 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, + 56, 52, 0, 171, 171, 171, 73, 83, 71, 78, 104, 0, 0, 0, 3, + 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, + 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, + 12, 12, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, + 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 79, 83, + 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, 114, 103, + 101, 116, 0, 171, 171, 177, 254, 0, 0, 0, 0, 0, 0, 83, 97, + 109, 112, 108, 101, 84, 101, 120, 116, 117, 114, 101, 87, 105, 116, 104, + 83, 104, 97, 100, 111, 119, 0, 4, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 128, 63, 1, 0, 0, 0, 0, 0, 128, 63, 1, 0, 0, + 0, 0, 0, 128, 63, 1, 0, 0, 0, 0, 0, 128, 63, 1, 0, + 0, 0, 3, 0, 0, 0, 255, 255, 255, 255, 68, 4, 0, 0, 68, + 88, 66, 67, 77, 85, 167, 240, 56, 56, 155, 78, 125, 96, 49, 253, + 103, 100, 22, 62, 1, 0, 0, 0, 68, 4, 0, 0, 6, 0, 0, + 0, 56, 0, 0, 0, 248, 0, 0, 0, 244, 1, 0, 0, 112, 2, + 0, 0, 160, 3, 0, 0, 212, 3, 0, 0, 65, 111, 110, 57, 184, + 0, 0, 0, 184, 0, 0, 0, 0, 2, 254, 255, 132, 0, 0, 0, + 52, 0, 0, 0, 1, 0, 36, 0, 0, 0, 48, 0, 0, 0, 48, + 0, 0, 0, 36, 0, 1, 0, 48, 0, 0, 0, 0, 0, 3, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 254, 255, 81, + 0, 0, 5, 4, 0, 15, 160, 0, 0, 0, 0, 0, 0, 128, 63, + 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 5, 0, 0, + 128, 0, 0, 15, 144, 4, 0, 0, 4, 0, 0, 3, 224, 0, 0, + 228, 144, 2, 0, 238, 160, 2, 0, 228, 160, 4, 0, 0, 4, 0, + 0, 12, 224, 0, 0, 20, 144, 3, 0, 180, 160, 3, 0, 20, 160, + 4, 0, 0, 4, 0, 0, 3, 128, 0, 0, 228, 144, 1, 0, 238, + 160, 1, 0, 228, 160, 2, 0, 0, 3, 0, 0, 3, 192, 0, 0, + 228, 128, 0, 0, 228, 160, 1, 0, 0, 2, 0, 0, 12, 192, 4, + 0, 68, 160, 255, 255, 0, 0, 83, 72, 68, 82, 244, 0, 0, 0, + 64, 0, 1, 0, 61, 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, + 0, 0, 0, 0, 0, 3, 0, 0, 0, 95, 0, 0, 3, 50, 16, + 16, 0, 0, 0, 0, 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, + 1, 0, 0, 0, 101, 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, + 0, 50, 0, 0, 11, 50, 32, 16, 0, 0, 0, 0, 0, 70, 16, + 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 54, 0, 0, 8, 194, 32, 16, 0, 0, 0, 0, 0, 2, 64, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 128, 63, 50, 0, 0, 11, 50, 32, 16, 0, 1, 0, 0, 0, 70, + 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 50, 0, 0, 11, 194, 32, 16, 0, 1, 0, 0, 0, 6, 20, + 16, 0, 0, 0, 0, 0, 166, 142, 32, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 6, 132, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, 5, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, 40, 1, 0, + 0, 1, 0, 0, 0, 64, 0, 0, 0, 1, 0, 0, 0, 28, 0, + 0, 0, 0, 4, 254, 255, 0, 1, 0, 0, 246, 0, 0, 0, 60, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 99, 98, 48, 0, 60, 0, 0, 0, 4, 0, 0, 0, 88, 0, + 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 184, + 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, + 196, 0, 0, 0, 0, 0, 0, 0, 212, 0, 0, 0, 16, 0, 0, + 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, + 0, 0, 222, 0, 0, 0, 32, 0, 0, 0, 16, 0, 0, 0, 2, + 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 236, 0, 0, 0, + 48, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 196, 0, 0, + 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, 101, 115, 99, 0, 171, + 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 77, 97, + 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 84, 101, 120, + 116, 67, 111, 108, 111, 114, 0, 77, 105, 99, 114, 111, 115, 111, 102, + 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, + 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, + 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 73, 83, 71, + 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 7, 3, 0, 0, 80, 79, 83, 73, 84, 73, 79, 78, + 0, 171, 171, 171, 79, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, + 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 3, 12, 0, 0, 92, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 3, + 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, + 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 217, 2, 1, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, + 0, 232, 9, 0, 0, 68, 88, 66, 67, 128, 131, 241, 85, 199, 21, + 192, 89, 55, 255, 82, 94, 121, 175, 16, 184, 1, 0, 0, 0, 232, + 9, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 248, 2, 0, 0, + 8, 7, 0, 0, 132, 7, 0, 0, 68, 9, 0, 0, 180, 9, 0, + 0, 65, 111, 110, 57, 184, 2, 0, 0, 184, 2, 0, 0, 0, 2, + 255, 255, 120, 2, 0, 0, 64, 0, 0, 0, 2, 0, 40, 0, 0, + 0, 64, 0, 0, 0, 64, 0, 1, 0, 36, 0, 0, 0, 64, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 6, 0, 4, 0, 3, 0, 0, 0, 0, 0, 1, 2, + 255, 255, 31, 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, + 0, 0, 2, 0, 0, 0, 144, 0, 8, 15, 160, 2, 0, 0, 3, + 0, 0, 1, 128, 0, 0, 0, 176, 0, 0, 85, 160, 1, 0, 0, + 2, 0, 0, 2, 128, 0, 0, 85, 176, 2, 0, 0, 3, 1, 0, + 1, 128, 0, 0, 0, 176, 0, 0, 0, 160, 1, 0, 0, 2, 1, + 0, 2, 128, 0, 0, 85, 176, 66, 0, 0, 3, 0, 0, 15, 128, + 0, 0, 228, 128, 0, 8, 228, 160, 66, 0, 0, 3, 1, 0, 15, + 128, 1, 0, 228, 128, 0, 8, 228, 160, 5, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 255, 128, 3, 0, 85, 160, 4, 0, 0, 4, 0, + 0, 1, 128, 3, 0, 0, 160, 1, 0, 255, 128, 0, 0, 0, 128, + 2, 0, 0, 3, 1, 0, 1, 128, 0, 0, 0, 176, 0, 0, 170, + 160, 1, 0, 0, 2, 1, 0, 2, 128, 0, 0, 85, 176, 2, 0, + 0, 3, 2, 0, 1, 128, 0, 0, 0, 176, 0, 0, 255, 160, 1, + 0, 0, 2, 2, 0, 2, 128, 0, 0, 85, 176, 66, 0, 0, 3, + 1, 0, 15, 128, 1, 0, 228, 128, 0, 8, 228, 160, 66, 0, 0, + 3, 2, 0, 15, 128, 2, 0, 228, 128, 0, 8, 228, 160, 4, 0, + 0, 4, 0, 0, 1, 128, 3, 0, 170, 160, 1, 0, 255, 128, 0, + 0, 0, 128, 4, 0, 0, 4, 0, 0, 1, 128, 3, 0, 255, 160, + 2, 0, 255, 128, 0, 0, 0, 128, 2, 0, 0, 3, 1, 0, 1, + 128, 0, 0, 0, 176, 1, 0, 0, 160, 1, 0, 0, 2, 1, 0, + 2, 128, 0, 0, 85, 176, 2, 0, 0, 3, 2, 0, 1, 128, 0, + 0, 0, 176, 1, 0, 85, 160, 1, 0, 0, 2, 2, 0, 2, 128, + 0, 0, 85, 176, 66, 0, 0, 3, 1, 0, 15, 128, 1, 0, 228, + 128, 0, 8, 228, 160, 66, 0, 0, 3, 2, 0, 15, 128, 2, 0, + 228, 128, 0, 8, 228, 160, 4, 0, 0, 4, 0, 0, 1, 128, 4, + 0, 0, 160, 1, 0, 255, 128, 0, 0, 0, 128, 4, 0, 0, 4, + 0, 0, 1, 128, 4, 0, 85, 160, 2, 0, 255, 128, 0, 0, 0, + 128, 2, 0, 0, 3, 1, 0, 1, 128, 0, 0, 0, 176, 1, 0, + 170, 160, 1, 0, 0, 2, 1, 0, 2, 128, 0, 0, 85, 176, 2, + 0, 0, 3, 2, 0, 1, 128, 0, 0, 0, 176, 1, 0, 255, 160, + 1, 0, 0, 2, 2, 0, 2, 128, 0, 0, 85, 176, 66, 0, 0, + 3, 1, 0, 15, 128, 1, 0, 228, 128, 0, 8, 228, 160, 66, 0, + 0, 3, 2, 0, 15, 128, 2, 0, 228, 128, 0, 8, 228, 160, 4, + 0, 0, 4, 0, 0, 1, 128, 4, 0, 170, 160, 1, 0, 255, 128, + 0, 0, 0, 128, 4, 0, 0, 4, 0, 0, 1, 128, 4, 0, 255, + 160, 2, 0, 255, 128, 0, 0, 0, 128, 2, 0, 0, 3, 1, 0, + 1, 128, 0, 0, 0, 176, 2, 0, 0, 160, 1, 0, 0, 2, 1, + 0, 2, 128, 0, 0, 85, 176, 66, 0, 0, 3, 1, 0, 15, 128, + 1, 0, 228, 128, 0, 8, 228, 160, 4, 0, 0, 4, 0, 0, 1, + 128, 5, 0, 0, 160, 1, 0, 255, 128, 0, 0, 0, 128, 5, 0, + 0, 3, 0, 0, 15, 128, 0, 0, 0, 128, 6, 0, 228, 160, 1, + 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0, + 83, 72, 68, 82, 8, 4, 0, 0, 64, 0, 0, 0, 2, 1, 0, + 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 10, 0, + 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 88, + 24, 0, 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, + 98, 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 101, 0, 0, + 3, 242, 32, 16, 0, 0, 0, 0, 0, 104, 0, 0, 2, 4, 0, + 0, 0, 0, 0, 0, 8, 242, 0, 16, 0, 0, 0, 0, 0, 6, + 16, 16, 0, 1, 0, 0, 0, 38, 135, 32, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 54, 0, 0, 5, 82, 0, 16, 0, 1, 0, 0, + 0, 86, 7, 16, 0, 0, 0, 0, 0, 54, 0, 0, 5, 162, 0, + 16, 0, 1, 0, 0, 0, 86, 21, 16, 0, 1, 0, 0, 0, 69, + 0, 0, 9, 242, 0, 16, 0, 2, 0, 0, 0, 230, 10, 16, 0, + 1, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, + 0, 0, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, + 0, 0, 70, 0, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 0, + 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 56, 0, 0, 8, + 18, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 2, 0, 0, + 0, 26, 128, 32, 0, 0, 0, 0, 0, 6, 0, 0, 0, 50, 0, + 0, 10, 18, 0, 16, 0, 1, 0, 0, 0, 10, 128, 32, 0, 0, + 0, 0, 0, 6, 0, 0, 0, 58, 0, 16, 0, 1, 0, 0, 0, + 10, 0, 16, 0, 1, 0, 0, 0, 54, 0, 0, 5, 162, 0, 16, + 0, 0, 0, 0, 0, 86, 21, 16, 0, 1, 0, 0, 0, 69, 0, + 0, 9, 242, 0, 16, 0, 2, 0, 0, 0, 70, 0, 16, 0, 0, + 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, + 0, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 0, 0, 0, + 0, 230, 10, 16, 0, 0, 0, 0, 0, 70, 126, 16, 0, 0, 0, + 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 50, 0, 0, 10, 18, + 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0, 0, + 6, 0, 0, 0, 58, 0, 16, 0, 2, 0, 0, 0, 10, 0, 16, + 0, 1, 0, 0, 0, 50, 0, 0, 10, 18, 0, 16, 0, 0, 0, + 0, 0, 58, 128, 32, 0, 0, 0, 0, 0, 6, 0, 0, 0, 58, + 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, + 0, 0, 0, 8, 242, 0, 16, 0, 1, 0, 0, 0, 6, 16, 16, + 0, 1, 0, 0, 0, 38, 135, 32, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 54, 0, 0, 5, 82, 0, 16, 0, 2, 0, 0, 0, 86, + 7, 16, 0, 1, 0, 0, 0, 54, 0, 0, 5, 162, 0, 16, 0, + 2, 0, 0, 0, 86, 21, 16, 0, 1, 0, 0, 0, 69, 0, 0, + 9, 242, 0, 16, 0, 3, 0, 0, 0, 70, 0, 16, 0, 2, 0, + 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, + 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 2, 0, 0, 0, + 230, 10, 16, 0, 2, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, + 0, 0, 96, 16, 0, 0, 0, 0, 0, 50, 0, 0, 10, 18, 0, + 16, 0, 0, 0, 0, 0, 10, 128, 32, 0, 0, 0, 0, 0, 7, + 0, 0, 0, 58, 0, 16, 0, 3, 0, 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 50, 0, 0, 10, 18, 0, 16, 0, 0, 0, 0, + 0, 26, 128, 32, 0, 0, 0, 0, 0, 7, 0, 0, 0, 58, 0, + 16, 0, 2, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 54, + 0, 0, 5, 162, 0, 16, 0, 1, 0, 0, 0, 86, 21, 16, 0, + 1, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 2, 0, 0, + 0, 70, 0, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 0, 0, + 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 69, 0, 0, 9, 242, + 0, 16, 0, 1, 0, 0, 0, 230, 10, 16, 0, 1, 0, 0, 0, + 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, + 0, 50, 0, 0, 10, 18, 0, 16, 0, 0, 0, 0, 0, 42, 128, + 32, 0, 0, 0, 0, 0, 7, 0, 0, 0, 58, 0, 16, 0, 2, + 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 50, 0, 0, 10, + 18, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, 0, 0, 0, 0, + 0, 7, 0, 0, 0, 58, 0, 16, 0, 1, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, 0, 0, 0, 8, 18, 0, 16, 0, 1, + 0, 0, 0, 10, 16, 16, 0, 1, 0, 0, 0, 10, 128, 32, 0, + 0, 0, 0, 0, 2, 0, 0, 0, 54, 0, 0, 5, 34, 0, 16, + 0, 1, 0, 0, 0, 26, 16, 16, 0, 1, 0, 0, 0, 69, 0, + 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 70, 0, 16, 0, 1, + 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, + 0, 0, 0, 0, 50, 0, 0, 10, 18, 0, 16, 0, 0, 0, 0, + 0, 10, 128, 32, 0, 0, 0, 0, 0, 8, 0, 0, 0, 58, 0, + 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 56, + 0, 0, 8, 242, 32, 16, 0, 0, 0, 0, 0, 6, 0, 16, 0, + 0, 0, 0, 0, 70, 142, 32, 0, 0, 0, 0, 0, 9, 0, 0, + 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, 30, 0, + 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 13, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, 184, 1, + 0, 0, 1, 0, 0, 0, 148, 0, 0, 0, 3, 0, 0, 0, 28, + 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0, 132, 1, 0, 0, + 124, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 139, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, + 12, 0, 0, 0, 143, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 115, 83, 104, 97, 100, 111, 119, 83, 97, + 109, 112, 108, 101, 114, 0, 116, 101, 120, 0, 99, 98, 49, 0, 171, + 143, 0, 0, 0, 4, 0, 0, 0, 172, 0, 0, 0, 160, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 1, 0, 0, 0, 0, + 0, 0, 48, 0, 0, 0, 2, 0, 0, 0, 28, 1, 0, 0, 0, + 0, 0, 0, 44, 1, 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, + 0, 0, 0, 0, 60, 1, 0, 0, 0, 0, 0, 0, 76, 1, 0, + 0, 96, 0, 0, 0, 48, 0, 0, 0, 2, 0, 0, 0, 88, 1, + 0, 0, 0, 0, 0, 0, 104, 1, 0, 0, 144, 0, 0, 0, 16, + 0, 0, 0, 2, 0, 0, 0, 116, 1, 0, 0, 0, 0, 0, 0, + 66, 108, 117, 114, 79, 102, 102, 115, 101, 116, 115, 72, 0, 171, 171, + 171, 1, 0, 3, 0, 1, 0, 4, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 66, 108, 117, 114, 79, 102, 102, 115, 101, 116, 115, 86, 0, + 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 66, 108, 117, 114, 87, 101, 105, 103, 104, 116, 115, + 0, 1, 0, 3, 0, 1, 0, 4, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 83, 104, 97, 100, 111, 119, 67, 111, 108, 111, 114, 0, 1, + 0, 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, + 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, + 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, + 54, 51, 56, 52, 0, 171, 171, 73, 83, 71, 78, 104, 0, 0, 0, + 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, + 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, + 0, 0, 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, + 0, 12, 0, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, + 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 79, + 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, 114, + 103, 101, 116, 0, 171, 171, 53, 7, 1, 0, 0, 0, 0, 0, 80, + 49, 0, 4, 0, 0, 0, 1, 0, 0, 0, 0, 0, 128, 63, 1, + 0, 0, 0, 0, 0, 128, 63, 1, 0, 0, 0, 0, 0, 128, 63, + 1, 0, 0, 0, 0, 0, 128, 63, 1, 0, 0, 0, 3, 0, 0, + 0, 255, 255, 255, 255, 68, 4, 0, 0, 68, 88, 66, 67, 77, 85, + 167, 240, 56, 56, 155, 78, 125, 96, 49, 253, 103, 100, 22, 62, 1, + 0, 0, 0, 68, 4, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, + 248, 0, 0, 0, 244, 1, 0, 0, 112, 2, 0, 0, 160, 3, 0, + 0, 212, 3, 0, 0, 65, 111, 110, 57, 184, 0, 0, 0, 184, 0, + 0, 0, 0, 2, 254, 255, 132, 0, 0, 0, 52, 0, 0, 0, 1, + 0, 36, 0, 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 36, 0, + 1, 0, 48, 0, 0, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 254, 255, 81, 0, 0, 5, 4, 0, + 15, 160, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, + 0, 0, 0, 31, 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, + 4, 0, 0, 4, 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, + 160, 2, 0, 228, 160, 4, 0, 0, 4, 0, 0, 12, 224, 0, 0, + 20, 144, 3, 0, 180, 160, 3, 0, 20, 160, 4, 0, 0, 4, 0, + 0, 3, 128, 0, 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, + 2, 0, 0, 3, 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, + 160, 1, 0, 0, 2, 0, 0, 12, 192, 4, 0, 68, 160, 255, 255, + 0, 0, 83, 72, 68, 82, 244, 0, 0, 0, 64, 0, 1, 0, 61, + 0, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0, + 0, 103, 0, 0, 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 101, 0, 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101, + 0, 0, 3, 194, 32, 16, 0, 1, 0, 0, 0, 50, 0, 0, 11, + 50, 32, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, + 0, 230, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 8, 194, + 32, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, + 11, 50, 32, 16, 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, 0, + 0, 0, 230, 138, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 70, + 128, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 11, + 194, 32, 16, 0, 1, 0, 0, 0, 6, 20, 16, 0, 0, 0, 0, + 0, 166, 142, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 6, 132, + 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, + 84, 65, 84, 116, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 4, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 82, 68, 69, 70, 40, 1, 0, 0, 1, 0, 0, 0, + 64, 0, 0, 0, 1, 0, 0, 0, 28, 0, 0, 0, 0, 4, 254, + 255, 0, 1, 0, 0, 246, 0, 0, 0, 60, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, + 60, 0, 0, 0, 4, 0, 0, 0, 88, 0, 0, 0, 64, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 184, 0, 0, 0, 0, 0, + 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, + 0, 0, 0, 212, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, + 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, + 0, 32, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, + 0, 0, 0, 0, 0, 0, 236, 0, 0, 0, 48, 0, 0, 0, 16, + 0, 0, 0, 0, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, + 81, 117, 97, 100, 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, + 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, + 120, 67, 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, + 67, 111, 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, + 114, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, + 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, + 109, 112, 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, + 46, 49, 54, 51, 56, 52, 0, 73, 83, 71, 78, 44, 0, 0, 0, + 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, + 0, 0, 80, 79, 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, + 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, + 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, + 12, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, + 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, + 82, 68, 0, 171, 171, 171, 92, 17, 1, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 172, 9, 0, 0, + 68, 88, 66, 67, 67, 47, 1, 244, 0, 102, 246, 41, 38, 220, 84, + 204, 156, 139, 96, 25, 1, 0, 0, 0, 172, 9, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, 220, 2, 0, 0, 204, 6, 0, 0, 72, + 7, 0, 0, 8, 9, 0, 0, 120, 9, 0, 0, 65, 111, 110, 57, + 156, 2, 0, 0, 156, 2, 0, 0, 0, 2, 255, 255, 104, 2, 0, + 0, 52, 0, 0, 0, 1, 0, 40, 0, 0, 0, 52, 0, 0, 0, + 52, 0, 1, 0, 36, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 6, 0, 0, 0, 0, 0, 0, 0, 1, 2, 255, 255, + 31, 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, + 2, 0, 0, 0, 144, 0, 8, 15, 160, 2, 0, 0, 3, 0, 0, + 2, 128, 0, 0, 85, 176, 0, 0, 85, 160, 1, 0, 0, 2, 0, + 0, 1, 128, 0, 0, 0, 176, 2, 0, 0, 3, 1, 0, 2, 128, + 0, 0, 85, 176, 0, 0, 0, 160, 1, 0, 0, 2, 1, 0, 1, + 128, 0, 0, 0, 176, 66, 0, 0, 3, 0, 0, 15, 128, 0, 0, + 228, 128, 0, 8, 228, 160, 66, 0, 0, 3, 1, 0, 15, 128, 1, + 0, 228, 128, 0, 8, 228, 160, 5, 0, 0, 3, 0, 0, 15, 128, + 0, 0, 228, 128, 3, 0, 85, 160, 4, 0, 0, 4, 0, 0, 15, + 128, 3, 0, 0, 160, 1, 0, 228, 128, 0, 0, 228, 128, 2, 0, + 0, 3, 1, 0, 2, 128, 0, 0, 85, 176, 0, 0, 170, 160, 1, + 0, 0, 2, 1, 0, 1, 128, 0, 0, 0, 176, 2, 0, 0, 3, + 2, 0, 2, 128, 0, 0, 85, 176, 0, 0, 255, 160, 1, 0, 0, + 2, 2, 0, 1, 128, 0, 0, 0, 176, 66, 0, 0, 3, 1, 0, + 15, 128, 1, 0, 228, 128, 0, 8, 228, 160, 66, 0, 0, 3, 2, + 0, 15, 128, 2, 0, 228, 128, 0, 8, 228, 160, 4, 0, 0, 4, + 0, 0, 15, 128, 3, 0, 170, 160, 1, 0, 228, 128, 0, 0, 228, + 128, 4, 0, 0, 4, 0, 0, 15, 128, 3, 0, 255, 160, 2, 0, + 228, 128, 0, 0, 228, 128, 2, 0, 0, 3, 1, 0, 2, 128, 0, + 0, 85, 176, 1, 0, 0, 160, 1, 0, 0, 2, 1, 0, 1, 128, + 0, 0, 0, 176, 2, 0, 0, 3, 2, 0, 2, 128, 0, 0, 85, + 176, 1, 0, 85, 160, 1, 0, 0, 2, 2, 0, 1, 128, 0, 0, + 0, 176, 66, 0, 0, 3, 1, 0, 15, 128, 1, 0, 228, 128, 0, + 8, 228, 160, 66, 0, 0, 3, 2, 0, 15, 128, 2, 0, 228, 128, + 0, 8, 228, 160, 4, 0, 0, 4, 0, 0, 15, 128, 4, 0, 0, + 160, 1, 0, 228, 128, 0, 0, 228, 128, 4, 0, 0, 4, 0, 0, + 15, 128, 4, 0, 85, 160, 2, 0, 228, 128, 0, 0, 228, 128, 2, + 0, 0, 3, 1, 0, 2, 128, 0, 0, 85, 176, 1, 0, 170, 160, + 1, 0, 0, 2, 1, 0, 1, 128, 0, 0, 0, 176, 2, 0, 0, + 3, 2, 0, 2, 128, 0, 0, 85, 176, 1, 0, 255, 160, 1, 0, + 0, 2, 2, 0, 1, 128, 0, 0, 0, 176, 66, 0, 0, 3, 1, + 0, 15, 128, 1, 0, 228, 128, 0, 8, 228, 160, 66, 0, 0, 3, + 2, 0, 15, 128, 2, 0, 228, 128, 0, 8, 228, 160, 4, 0, 0, + 4, 0, 0, 15, 128, 4, 0, 170, 160, 1, 0, 228, 128, 0, 0, + 228, 128, 4, 0, 0, 4, 0, 0, 15, 128, 4, 0, 255, 160, 2, + 0, 228, 128, 0, 0, 228, 128, 2, 0, 0, 3, 1, 0, 2, 128, + 0, 0, 85, 176, 2, 0, 0, 160, 1, 0, 0, 2, 1, 0, 1, + 128, 0, 0, 0, 176, 66, 0, 0, 3, 1, 0, 15, 128, 1, 0, + 228, 128, 0, 8, 228, 160, 4, 0, 0, 4, 0, 0, 15, 128, 5, + 0, 0, 160, 1, 0, 228, 128, 0, 0, 228, 128, 1, 0, 0, 2, + 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0, 83, 72, 68, + 82, 232, 3, 0, 0, 64, 0, 0, 0, 250, 0, 0, 0, 89, 0, + 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 9, 0, 0, 0, 90, + 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 88, 24, 0, 4, + 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 98, 16, 0, + 3, 50, 16, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, + 16, 0, 0, 0, 0, 0, 104, 0, 0, 2, 4, 0, 0, 0, 54, + 0, 0, 5, 82, 0, 16, 0, 0, 0, 0, 0, 6, 16, 16, 0, + 1, 0, 0, 0, 0, 0, 0, 8, 242, 0, 16, 0, 1, 0, 0, + 0, 86, 21, 16, 0, 1, 0, 0, 0, 134, 141, 32, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 54, 0, 0, 5, 162, 0, 16, 0, 0, + 0, 0, 0, 6, 8, 16, 0, 1, 0, 0, 0, 69, 0, 0, 9, + 242, 0, 16, 0, 2, 0, 0, 0, 230, 10, 16, 0, 0, 0, 0, + 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, + 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 0, 0, 0, 0, 70, + 0, 16, 0, 0, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, + 0, 96, 16, 0, 0, 0, 0, 0, 56, 0, 0, 8, 242, 0, 16, + 0, 2, 0, 0, 0, 70, 14, 16, 0, 2, 0, 0, 0, 86, 133, + 32, 0, 0, 0, 0, 0, 6, 0, 0, 0, 50, 0, 0, 10, 242, + 0, 16, 0, 0, 0, 0, 0, 6, 128, 32, 0, 0, 0, 0, 0, + 6, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 70, 14, 16, + 0, 2, 0, 0, 0, 54, 0, 0, 5, 82, 0, 16, 0, 1, 0, + 0, 0, 6, 16, 16, 0, 1, 0, 0, 0, 69, 0, 0, 9, 242, + 0, 16, 0, 2, 0, 0, 0, 70, 0, 16, 0, 1, 0, 0, 0, + 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, + 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 230, 10, + 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, + 96, 16, 0, 0, 0, 0, 0, 50, 0, 0, 10, 242, 0, 16, 0, + 0, 0, 0, 0, 166, 138, 32, 0, 0, 0, 0, 0, 6, 0, 0, + 0, 70, 14, 16, 0, 2, 0, 0, 0, 70, 14, 16, 0, 0, 0, + 0, 0, 50, 0, 0, 10, 242, 0, 16, 0, 0, 0, 0, 0, 246, + 143, 32, 0, 0, 0, 0, 0, 6, 0, 0, 0, 70, 14, 16, 0, + 1, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 54, 0, 0, + 5, 82, 0, 16, 0, 1, 0, 0, 0, 6, 16, 16, 0, 1, 0, + 0, 0, 0, 0, 0, 8, 242, 0, 16, 0, 2, 0, 0, 0, 86, + 21, 16, 0, 1, 0, 0, 0, 134, 141, 32, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 54, 0, 0, 5, 162, 0, 16, 0, 1, 0, 0, + 0, 6, 8, 16, 0, 2, 0, 0, 0, 69, 0, 0, 9, 242, 0, + 16, 0, 3, 0, 0, 0, 70, 0, 16, 0, 1, 0, 0, 0, 70, + 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, + 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 230, 10, 16, + 0, 1, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, + 16, 0, 0, 0, 0, 0, 50, 0, 0, 10, 242, 0, 16, 0, 0, + 0, 0, 0, 6, 128, 32, 0, 0, 0, 0, 0, 7, 0, 0, 0, + 70, 14, 16, 0, 3, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, + 0, 50, 0, 0, 10, 242, 0, 16, 0, 0, 0, 0, 0, 86, 133, + 32, 0, 0, 0, 0, 0, 7, 0, 0, 0, 70, 14, 16, 0, 1, + 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 54, 0, 0, 5, + 82, 0, 16, 0, 2, 0, 0, 0, 6, 16, 16, 0, 1, 0, 0, + 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 70, 0, + 16, 0, 2, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, + 96, 16, 0, 0, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, + 2, 0, 0, 0, 230, 10, 16, 0, 2, 0, 0, 0, 70, 126, 16, + 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 50, 0, + 0, 10, 242, 0, 16, 0, 0, 0, 0, 0, 166, 138, 32, 0, 0, + 0, 0, 0, 7, 0, 0, 0, 70, 14, 16, 0, 1, 0, 0, 0, + 70, 14, 16, 0, 0, 0, 0, 0, 50, 0, 0, 10, 242, 0, 16, + 0, 0, 0, 0, 0, 246, 143, 32, 0, 0, 0, 0, 0, 7, 0, + 0, 0, 70, 14, 16, 0, 2, 0, 0, 0, 70, 14, 16, 0, 0, + 0, 0, 0, 0, 0, 0, 8, 34, 0, 16, 0, 1, 0, 0, 0, + 26, 16, 16, 0, 1, 0, 0, 0, 10, 128, 32, 0, 0, 0, 0, + 0, 5, 0, 0, 0, 54, 0, 0, 5, 18, 0, 16, 0, 1, 0, + 0, 0, 10, 16, 16, 0, 1, 0, 0, 0, 69, 0, 0, 9, 242, + 0, 16, 0, 1, 0, 0, 0, 70, 0, 16, 0, 1, 0, 0, 0, + 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, + 0, 50, 0, 0, 10, 242, 32, 16, 0, 0, 0, 0, 0, 6, 128, + 32, 0, 0, 0, 0, 0, 8, 0, 0, 0, 70, 14, 16, 0, 1, + 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 62, 0, 0, 1, + 83, 84, 65, 84, 116, 0, 0, 0, 29, 0, 0, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 2, 0, 0, 0, 12, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 82, 68, 69, 70, 184, 1, 0, 0, 1, 0, 0, + 0, 148, 0, 0, 0, 3, 0, 0, 0, 28, 0, 0, 0, 0, 4, + 255, 255, 0, 1, 0, 0, 132, 1, 0, 0, 124, 0, 0, 0, 3, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 139, 0, 0, + 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 143, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 115, 83, 104, 97, 100, 111, 119, 83, 97, 109, 112, 108, 101, 114, + 0, 116, 101, 120, 0, 99, 98, 49, 0, 171, 143, 0, 0, 0, 4, + 0, 0, 0, 172, 0, 0, 0, 160, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 12, 1, 0, 0, 0, 0, 0, 0, 48, 0, 0, + 0, 0, 0, 0, 0, 28, 1, 0, 0, 0, 0, 0, 0, 44, 1, + 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 2, 0, 0, 0, 60, + 1, 0, 0, 0, 0, 0, 0, 76, 1, 0, 0, 96, 0, 0, 0, + 48, 0, 0, 0, 2, 0, 0, 0, 88, 1, 0, 0, 0, 0, 0, + 0, 104, 1, 0, 0, 144, 0, 0, 0, 16, 0, 0, 0, 0, 0, + 0, 0, 116, 1, 0, 0, 0, 0, 0, 0, 66, 108, 117, 114, 79, + 102, 102, 115, 101, 116, 115, 72, 0, 171, 171, 171, 1, 0, 3, 0, + 1, 0, 4, 0, 3, 0, 0, 0, 0, 0, 0, 0, 66, 108, 117, + 114, 79, 102, 102, 115, 101, 116, 115, 86, 0, 171, 171, 171, 1, 0, + 3, 0, 1, 0, 4, 0, 3, 0, 0, 0, 0, 0, 0, 0, 66, + 108, 117, 114, 87, 101, 105, 103, 104, 116, 115, 0, 1, 0, 3, 0, + 1, 0, 4, 0, 3, 0, 0, 0, 0, 0, 0, 0, 83, 104, 97, + 100, 111, 119, 67, 111, 108, 111, 114, 0, 1, 0, 3, 0, 1, 0, + 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 105, 99, 114, 111, + 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, + 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, + 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, + 171, 171, 73, 83, 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, + 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, + 0, 0, 3, 3, 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, + 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, + 67, 79, 79, 82, 68, 0, 171, 171, 171, 79, 83, 71, 78, 44, 0, + 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 15, 0, 0, 0, 83, 86, 95, 84, 97, 114, 103, 101, 116, 0, 171, + 171, 184, 21, 1, 0, 0, 0, 0, 0, 80, 50, 0, 4, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 128, 63, 1, 0, 0, 0, 0, 0, + 128, 63, 1, 0, 0, 0, 0, 0, 128, 63, 1, 0, 0, 0, 0, + 0, 128, 63, 1, 0, 0, 0, 3, 0, 0, 0, 255, 255, 255, 255, + 68, 4, 0, 0, 68, 88, 66, 67, 77, 85, 167, 240, 56, 56, 155, + 78, 125, 96, 49, 253, 103, 100, 22, 62, 1, 0, 0, 0, 68, 4, + 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 248, 0, 0, 0, 244, + 1, 0, 0, 112, 2, 0, 0, 160, 3, 0, 0, 212, 3, 0, 0, + 65, 111, 110, 57, 184, 0, 0, 0, 184, 0, 0, 0, 0, 2, 254, + 255, 132, 0, 0, 0, 52, 0, 0, 0, 1, 0, 36, 0, 0, 0, + 48, 0, 0, 0, 48, 0, 0, 0, 36, 0, 1, 0, 48, 0, 0, + 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 2, 254, 255, 81, 0, 0, 5, 4, 0, 15, 160, 0, 0, 0, + 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, + 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, 4, 0, 0, 4, 0, + 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, 160, 2, 0, 228, 160, + 4, 0, 0, 4, 0, 0, 12, 224, 0, 0, 20, 144, 3, 0, 180, + 160, 3, 0, 20, 160, 4, 0, 0, 4, 0, 0, 3, 128, 0, 0, + 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0, 0, 3, 0, + 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, 160, 1, 0, 0, 2, + 0, 0, 12, 192, 4, 0, 68, 160, 255, 255, 0, 0, 83, 72, 68, + 82, 244, 0, 0, 0, 64, 0, 1, 0, 61, 0, 0, 0, 89, 0, + 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0, 95, + 0, 0, 3, 50, 16, 16, 0, 0, 0, 0, 0, 103, 0, 0, 4, + 242, 32, 16, 0, 0, 0, 0, 0, 1, 0, 0, 0, 101, 0, 0, + 3, 50, 32, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 194, 32, + 16, 0, 1, 0, 0, 0, 50, 0, 0, 11, 50, 32, 16, 0, 0, + 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16, 0, 0, 0, + 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50, 32, 16, 0, + 1, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 50, 0, 0, 11, 194, 32, 16, 0, 1, + 0, 0, 0, 6, 20, 16, 0, 0, 0, 0, 0, 166, 142, 32, 0, + 0, 0, 0, 0, 2, 0, 0, 0, 6, 132, 32, 0, 0, 0, 0, + 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, + 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, + 69, 70, 40, 1, 0, 0, 1, 0, 0, 0, 64, 0, 0, 0, 1, + 0, 0, 0, 28, 0, 0, 0, 0, 4, 254, 255, 0, 1, 0, 0, + 246, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 60, 0, 0, 0, 4, + 0, 0, 0, 88, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 184, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, + 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 212, 0, + 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, + 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 32, 0, 0, 0, + 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, + 0, 236, 0, 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 0, 0, + 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, + 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, + 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, + 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, 77, 105, 99, + 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, + 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, + 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, + 52, 0, 73, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, + 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, 0, 0, 80, 79, 83, + 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, 83, 71, 78, 104, 0, + 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 12, 0, 0, 92, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, + 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, + 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, + 171, 163, 31, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 164, 10, 0, 0, 68, 88, 66, 67, 70, + 166, 174, 156, 153, 145, 163, 116, 127, 37, 205, 162, 136, 116, 62, 222, + 1, 0, 0, 0, 164, 10, 0, 0, 6, 0, 0, 0, 56, 0, 0, + 0, 24, 3, 0, 0, 112, 7, 0, 0, 236, 7, 0, 0, 0, 10, + 0, 0, 112, 10, 0, 0, 65, 111, 110, 57, 216, 2, 0, 0, 216, + 2, 0, 0, 0, 2, 255, 255, 160, 2, 0, 0, 56, 0, 0, 0, + 1, 0, 44, 0, 0, 0, 56, 0, 0, 0, 56, 0, 2, 0, 36, + 0, 0, 0, 56, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, + 3, 0, 6, 0, 0, 0, 0, 0, 0, 0, 1, 2, 255, 255, 31, + 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, + 0, 0, 0, 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, + 144, 1, 8, 15, 160, 2, 0, 0, 3, 0, 0, 2, 128, 0, 0, + 85, 176, 0, 0, 85, 160, 1, 0, 0, 2, 0, 0, 1, 128, 0, + 0, 0, 176, 2, 0, 0, 3, 1, 0, 2, 128, 0, 0, 85, 176, + 0, 0, 0, 160, 1, 0, 0, 2, 1, 0, 1, 128, 0, 0, 0, + 176, 66, 0, 0, 3, 0, 0, 15, 128, 0, 0, 228, 128, 1, 8, + 228, 160, 66, 0, 0, 3, 1, 0, 15, 128, 1, 0, 228, 128, 1, + 8, 228, 160, 5, 0, 0, 3, 0, 0, 15, 128, 0, 0, 228, 128, + 3, 0, 85, 160, 4, 0, 0, 4, 0, 0, 15, 128, 3, 0, 0, + 160, 1, 0, 228, 128, 0, 0, 228, 128, 2, 0, 0, 3, 1, 0, + 2, 128, 0, 0, 85, 176, 0, 0, 170, 160, 1, 0, 0, 2, 1, + 0, 1, 128, 0, 0, 0, 176, 2, 0, 0, 3, 2, 0, 2, 128, + 0, 0, 85, 176, 0, 0, 255, 160, 1, 0, 0, 2, 2, 0, 1, + 128, 0, 0, 0, 176, 66, 0, 0, 3, 1, 0, 15, 128, 1, 0, + 228, 128, 1, 8, 228, 160, 66, 0, 0, 3, 2, 0, 15, 128, 2, + 0, 228, 128, 1, 8, 228, 160, 4, 0, 0, 4, 0, 0, 15, 128, + 3, 0, 170, 160, 1, 0, 228, 128, 0, 0, 228, 128, 4, 0, 0, + 4, 0, 0, 15, 128, 3, 0, 255, 160, 2, 0, 228, 128, 0, 0, + 228, 128, 2, 0, 0, 3, 1, 0, 2, 128, 0, 0, 85, 176, 1, + 0, 0, 160, 1, 0, 0, 2, 1, 0, 1, 128, 0, 0, 0, 176, + 2, 0, 0, 3, 2, 0, 2, 128, 0, 0, 85, 176, 1, 0, 85, + 160, 1, 0, 0, 2, 2, 0, 1, 128, 0, 0, 0, 176, 66, 0, + 0, 3, 1, 0, 15, 128, 1, 0, 228, 128, 1, 8, 228, 160, 66, + 0, 0, 3, 2, 0, 15, 128, 2, 0, 228, 128, 1, 8, 228, 160, + 4, 0, 0, 4, 0, 0, 15, 128, 4, 0, 0, 160, 1, 0, 228, + 128, 0, 0, 228, 128, 4, 0, 0, 4, 0, 0, 15, 128, 4, 0, + 85, 160, 2, 0, 228, 128, 0, 0, 228, 128, 2, 0, 0, 3, 1, + 0, 2, 128, 0, 0, 85, 176, 1, 0, 170, 160, 1, 0, 0, 2, + 1, 0, 1, 128, 0, 0, 0, 176, 2, 0, 0, 3, 2, 0, 2, + 128, 0, 0, 85, 176, 1, 0, 255, 160, 1, 0, 0, 2, 2, 0, + 1, 128, 0, 0, 0, 176, 66, 0, 0, 3, 1, 0, 15, 128, 1, + 0, 228, 128, 1, 8, 228, 160, 66, 0, 0, 3, 2, 0, 15, 128, + 2, 0, 228, 128, 1, 8, 228, 160, 4, 0, 0, 4, 0, 0, 15, + 128, 4, 0, 170, 160, 1, 0, 228, 128, 0, 0, 228, 128, 4, 0, + 0, 4, 0, 0, 15, 128, 4, 0, 255, 160, 2, 0, 228, 128, 0, + 0, 228, 128, 2, 0, 0, 3, 1, 0, 2, 128, 0, 0, 85, 176, + 2, 0, 0, 160, 1, 0, 0, 2, 1, 0, 1, 128, 0, 0, 0, + 176, 1, 0, 0, 2, 2, 0, 3, 128, 0, 0, 235, 176, 66, 0, + 0, 3, 1, 0, 15, 128, 1, 0, 228, 128, 1, 8, 228, 160, 66, + 0, 0, 3, 2, 0, 15, 128, 2, 0, 228, 128, 0, 8, 228, 160, + 4, 0, 0, 4, 0, 0, 15, 128, 5, 0, 0, 160, 1, 0, 228, + 128, 0, 0, 228, 128, 5, 0, 0, 3, 0, 0, 15, 128, 2, 0, + 255, 128, 0, 0, 228, 128, 1, 0, 0, 2, 0, 8, 15, 128, 0, + 0, 228, 128, 255, 255, 0, 0, 83, 72, 68, 82, 80, 4, 0, 0, + 64, 0, 0, 0, 20, 1, 0, 0, 89, 0, 0, 4, 70, 142, 32, + 0, 0, 0, 0, 0, 9, 0, 0, 0, 90, 0, 0, 3, 0, 96, + 16, 0, 0, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 1, + 0, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 0, 0, 0, 0, + 85, 85, 0, 0, 88, 24, 0, 4, 0, 112, 16, 0, 1, 0, 0, + 0, 85, 85, 0, 0, 98, 16, 0, 3, 50, 16, 16, 0, 1, 0, + 0, 0, 98, 16, 0, 3, 194, 16, 16, 0, 1, 0, 0, 0, 101, + 0, 0, 3, 242, 32, 16, 0, 0, 0, 0, 0, 104, 0, 0, 2, + 4, 0, 0, 0, 54, 0, 0, 5, 82, 0, 16, 0, 0, 0, 0, + 0, 6, 16, 16, 0, 1, 0, 0, 0, 0, 0, 0, 8, 242, 0, + 16, 0, 1, 0, 0, 0, 86, 21, 16, 0, 1, 0, 0, 0, 134, + 141, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0, 54, 0, 0, 5, + 162, 0, 16, 0, 0, 0, 0, 0, 6, 8, 16, 0, 1, 0, 0, + 0, 69, 0, 0, 9, 242, 0, 16, 0, 2, 0, 0, 0, 230, 10, + 16, 0, 0, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, + 96, 16, 0, 1, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, + 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 70, 126, 16, + 0, 0, 0, 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 56, 0, + 0, 8, 242, 0, 16, 0, 2, 0, 0, 0, 70, 14, 16, 0, 2, + 0, 0, 0, 86, 133, 32, 0, 0, 0, 0, 0, 6, 0, 0, 0, + 50, 0, 0, 10, 242, 0, 16, 0, 0, 0, 0, 0, 6, 128, 32, + 0, 0, 0, 0, 0, 6, 0, 0, 0, 70, 14, 16, 0, 0, 0, + 0, 0, 70, 14, 16, 0, 2, 0, 0, 0, 54, 0, 0, 5, 82, + 0, 16, 0, 1, 0, 0, 0, 6, 16, 16, 0, 1, 0, 0, 0, + 69, 0, 0, 9, 242, 0, 16, 0, 2, 0, 0, 0, 70, 0, 16, + 0, 1, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, + 16, 0, 1, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, + 0, 0, 0, 230, 10, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, + 0, 0, 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 50, 0, 0, + 10, 242, 0, 16, 0, 0, 0, 0, 0, 166, 138, 32, 0, 0, 0, + 0, 0, 6, 0, 0, 0, 70, 14, 16, 0, 2, 0, 0, 0, 70, + 14, 16, 0, 0, 0, 0, 0, 50, 0, 0, 10, 242, 0, 16, 0, + 0, 0, 0, 0, 246, 143, 32, 0, 0, 0, 0, 0, 6, 0, 0, + 0, 70, 14, 16, 0, 1, 0, 0, 0, 70, 14, 16, 0, 0, 0, + 0, 0, 54, 0, 0, 5, 82, 0, 16, 0, 1, 0, 0, 0, 6, + 16, 16, 0, 1, 0, 0, 0, 0, 0, 0, 8, 242, 0, 16, 0, + 2, 0, 0, 0, 86, 21, 16, 0, 1, 0, 0, 0, 134, 141, 32, + 0, 0, 0, 0, 0, 4, 0, 0, 0, 54, 0, 0, 5, 162, 0, + 16, 0, 1, 0, 0, 0, 6, 8, 16, 0, 2, 0, 0, 0, 69, + 0, 0, 9, 242, 0, 16, 0, 3, 0, 0, 0, 70, 0, 16, 0, + 1, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, + 0, 1, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, + 0, 0, 230, 10, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 0, + 0, 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 50, 0, 0, 10, + 242, 0, 16, 0, 0, 0, 0, 0, 6, 128, 32, 0, 0, 0, 0, + 0, 7, 0, 0, 0, 70, 14, 16, 0, 3, 0, 0, 0, 70, 14, + 16, 0, 0, 0, 0, 0, 50, 0, 0, 10, 242, 0, 16, 0, 0, + 0, 0, 0, 86, 133, 32, 0, 0, 0, 0, 0, 7, 0, 0, 0, + 70, 14, 16, 0, 1, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, + 0, 54, 0, 0, 5, 82, 0, 16, 0, 2, 0, 0, 0, 6, 16, + 16, 0, 1, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, + 0, 0, 0, 70, 0, 16, 0, 2, 0, 0, 0, 70, 126, 16, 0, + 0, 0, 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 69, 0, 0, + 9, 242, 0, 16, 0, 2, 0, 0, 0, 230, 10, 16, 0, 2, 0, + 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 1, + 0, 0, 0, 50, 0, 0, 10, 242, 0, 16, 0, 0, 0, 0, 0, + 166, 138, 32, 0, 0, 0, 0, 0, 7, 0, 0, 0, 70, 14, 16, + 0, 1, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 50, 0, + 0, 10, 242, 0, 16, 0, 0, 0, 0, 0, 246, 143, 32, 0, 0, + 0, 0, 0, 7, 0, 0, 0, 70, 14, 16, 0, 2, 0, 0, 0, + 70, 14, 16, 0, 0, 0, 0, 0, 0, 0, 0, 8, 34, 0, 16, + 0, 1, 0, 0, 0, 26, 16, 16, 0, 1, 0, 0, 0, 10, 128, + 32, 0, 0, 0, 0, 0, 5, 0, 0, 0, 54, 0, 0, 5, 18, + 0, 16, 0, 1, 0, 0, 0, 10, 16, 16, 0, 1, 0, 0, 0, + 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 70, 0, 16, + 0, 1, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, + 16, 0, 1, 0, 0, 0, 50, 0, 0, 10, 242, 0, 16, 0, 0, + 0, 0, 0, 6, 128, 32, 0, 0, 0, 0, 0, 8, 0, 0, 0, + 70, 14, 16, 0, 1, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, + 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 230, 26, + 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0, + 96, 16, 0, 0, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16, 0, + 0, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 246, 15, 16, + 0, 1, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, + 0, 0, 31, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 3, + 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, + 69, 70, 12, 2, 0, 0, 1, 0, 0, 0, 232, 0, 0, 0, 5, + 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0, + 216, 1, 0, 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 216, 0, 0, 0, 2, 0, 0, + 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, + 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 220, 0, 0, 0, 2, + 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, + 1, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 225, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 115, + 77, 97, 115, 107, 83, 97, 109, 112, 108, 101, 114, 0, 115, 83, 104, + 97, 100, 111, 119, 83, 97, 109, 112, 108, 101, 114, 0, 116, 101, 120, + 0, 109, 97, 115, 107, 0, 99, 98, 49, 0, 171, 171, 171, 225, 0, + 0, 0, 4, 0, 0, 0, 0, 1, 0, 0, 160, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 96, 1, 0, 0, 0, 0, 0, 0, + 48, 0, 0, 0, 0, 0, 0, 0, 112, 1, 0, 0, 0, 0, 0, + 0, 128, 1, 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 2, 0, + 0, 0, 144, 1, 0, 0, 0, 0, 0, 0, 160, 1, 0, 0, 96, + 0, 0, 0, 48, 0, 0, 0, 2, 0, 0, 0, 172, 1, 0, 0, + 0, 0, 0, 0, 188, 1, 0, 0, 144, 0, 0, 0, 16, 0, 0, + 0, 0, 0, 0, 0, 200, 1, 0, 0, 0, 0, 0, 0, 66, 108, + 117, 114, 79, 102, 102, 115, 101, 116, 115, 72, 0, 171, 171, 171, 1, + 0, 3, 0, 1, 0, 4, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 66, 108, 117, 114, 79, 102, 102, 115, 101, 116, 115, 86, 0, 171, 171, + 171, 1, 0, 3, 0, 1, 0, 4, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 66, 108, 117, 114, 87, 101, 105, 103, 104, 116, 115, 0, 1, + 0, 3, 0, 1, 0, 4, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 83, 104, 97, 100, 111, 119, 67, 111, 108, 111, 114, 0, 1, 0, 3, + 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 105, + 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, + 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, + 56, 52, 0, 171, 171, 73, 83, 71, 78, 104, 0, 0, 0, 3, 0, + 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, + 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, + 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, + 12, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, + 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 79, 83, 71, + 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, 114, 103, 101, + 116, 0, 171, 171, 255, 35, 1, 0, 0, 0, 0, 0, 83, 97, 109, + 112, 108, 101, 84, 101, 120, 116, 84, 101, 120, 116, 117, 114, 101, 0, + 85, 110, 109, 97, 115, 107, 101, 100, 0, 4, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 3, 0, 0, 0, 255, 255, 255, 255, 68, 4, 0, + 0, 68, 88, 66, 67, 77, 85, 167, 240, 56, 56, 155, 78, 125, 96, + 49, 253, 103, 100, 22, 62, 1, 0, 0, 0, 68, 4, 0, 0, 6, + 0, 0, 0, 56, 0, 0, 0, 248, 0, 0, 0, 244, 1, 0, 0, + 112, 2, 0, 0, 160, 3, 0, 0, 212, 3, 0, 0, 65, 111, 110, + 57, 184, 0, 0, 0, 184, 0, 0, 0, 0, 2, 254, 255, 132, 0, + 0, 0, 52, 0, 0, 0, 1, 0, 36, 0, 0, 0, 48, 0, 0, + 0, 48, 0, 0, 0, 36, 0, 1, 0, 48, 0, 0, 0, 0, 0, + 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 254, + 255, 81, 0, 0, 5, 4, 0, 15, 160, 0, 0, 0, 0, 0, 0, + 128, 63, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 5, + 0, 0, 128, 0, 0, 15, 144, 4, 0, 0, 4, 0, 0, 3, 224, + 0, 0, 228, 144, 2, 0, 238, 160, 2, 0, 228, 160, 4, 0, 0, + 4, 0, 0, 12, 224, 0, 0, 20, 144, 3, 0, 180, 160, 3, 0, + 20, 160, 4, 0, 0, 4, 0, 0, 3, 128, 0, 0, 228, 144, 1, + 0, 238, 160, 1, 0, 228, 160, 2, 0, 0, 3, 0, 0, 3, 192, + 0, 0, 228, 128, 0, 0, 228, 160, 1, 0, 0, 2, 0, 0, 12, + 192, 4, 0, 68, 160, 255, 255, 0, 0, 83, 72, 68, 82, 244, 0, + 0, 0, 64, 0, 1, 0, 61, 0, 0, 0, 89, 0, 0, 4, 70, + 142, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0, 95, 0, 0, 3, + 50, 16, 16, 0, 0, 0, 0, 0, 103, 0, 0, 4, 242, 32, 16, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 101, 0, 0, 3, 50, 32, + 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 194, 32, 16, 0, 1, + 0, 0, 0, 50, 0, 0, 11, 50, 32, 16, 0, 0, 0, 0, 0, + 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 54, 0, 0, 8, 194, 32, 16, 0, 0, 0, 0, 0, 2, + 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 128, 63, 50, 0, 0, 11, 50, 32, 16, 0, 1, 0, 0, + 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 50, 0, 0, 11, 194, 32, 16, 0, 1, 0, 0, 0, + 6, 20, 16, 0, 0, 0, 0, 0, 166, 142, 32, 0, 0, 0, 0, + 0, 2, 0, 0, 0, 6, 132, 32, 0, 0, 0, 0, 0, 2, 0, + 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, 5, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, 40, + 1, 0, 0, 1, 0, 0, 0, 64, 0, 0, 0, 1, 0, 0, 0, + 28, 0, 0, 0, 0, 4, 254, 255, 0, 1, 0, 0, 246, 0, 0, + 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 99, 98, 48, 0, 60, 0, 0, 0, 4, 0, 0, 0, + 88, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 184, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 2, 0, + 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 212, 0, 0, 0, 16, + 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, + 0, 0, 0, 0, 222, 0, 0, 0, 32, 0, 0, 0, 16, 0, 0, + 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 236, 0, + 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 196, + 0, 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, 101, 115, 99, + 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, + 77, 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 84, + 101, 120, 116, 67, 111, 108, 111, 114, 0, 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, + 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 73, + 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, 7, 3, 0, 0, 80, 79, 83, 73, 84, 73, + 79, 78, 0, 171, 171, 171, 79, 83, 71, 78, 104, 0, 0, 0, 3, + 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, + 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 0, 0, 3, 12, 0, 0, 92, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, + 12, 3, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, + 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 250, 46, + 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, + 0, 0, 0, 152, 4, 0, 0, 68, 88, 66, 67, 227, 84, 48, 176, + 142, 231, 109, 63, 97, 30, 1, 57, 105, 137, 178, 120, 1, 0, 0, + 0, 152, 4, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 4, 1, + 0, 0, 224, 1, 0, 0, 92, 2, 0, 0, 220, 3, 0, 0, 76, + 4, 0, 0, 65, 111, 110, 57, 196, 0, 0, 0, 196, 0, 0, 0, + 0, 2, 255, 255, 144, 0, 0, 0, 52, 0, 0, 0, 1, 0, 40, + 0, 0, 0, 52, 0, 0, 0, 52, 0, 1, 0, 36, 0, 0, 0, + 52, 0, 0, 0, 0, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 1, 2, 255, 255, 81, 0, 0, 5, 1, 0, 15, 160, + 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 31, 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, + 0, 2, 0, 0, 0, 144, 0, 8, 15, 160, 1, 0, 0, 2, 0, + 0, 7, 128, 0, 0, 228, 160, 4, 0, 0, 4, 0, 0, 15, 128, + 0, 0, 36, 128, 1, 0, 64, 160, 1, 0, 21, 160, 1, 0, 0, + 2, 0, 8, 15, 128, 0, 0, 228, 128, 66, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 176, 0, 8, 228, 160, 5, 0, 0, 3, 0, + 0, 15, 128, 0, 0, 70, 128, 0, 0, 255, 160, 1, 0, 0, 2, + 1, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0, 83, 72, 68, + 82, 212, 0, 0, 0, 64, 0, 0, 0, 53, 0, 0, 0, 89, 0, + 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 4, 0, 0, 0, 90, + 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, 88, 24, 0, 4, + 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 98, 16, 0, + 3, 50, 16, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, + 16, 0, 0, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, 0, 1, + 0, 0, 0, 104, 0, 0, 2, 1, 0, 0, 0, 54, 0, 0, 6, + 114, 32, 16, 0, 0, 0, 0, 0, 70, 130, 32, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 54, 0, 0, 5, 130, 32, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 69, 0, 0, 9, 242, + 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, + 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, + 0, 56, 0, 0, 8, 242, 32, 16, 0, 1, 0, 0, 0, 102, 4, + 16, 0, 0, 0, 0, 0, 246, 143, 32, 0, 0, 0, 0, 0, 3, + 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, 0, 0, + 5, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, 69, 70, + 120, 1, 0, 0, 1, 0, 0, 0, 144, 0, 0, 0, 3, 0, 0, + 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0, 70, 1, + 0, 0, 124, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 133, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, + 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, + 0, 0, 12, 0, 0, 0, 137, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 115, 83, 97, 109, 112, 108, 101, + 114, 0, 116, 101, 120, 0, 99, 98, 48, 0, 171, 171, 171, 137, 0, + 0, 0, 4, 0, 0, 0, 168, 0, 0, 0, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 8, 1, 0, 0, 0, 0, 0, 0, + 16, 0, 0, 0, 0, 0, 0, 0, 20, 1, 0, 0, 0, 0, 0, + 0, 36, 1, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, + 0, 0, 20, 1, 0, 0, 0, 0, 0, 0, 46, 1, 0, 0, 32, + 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 20, 1, 0, 0, + 0, 0, 0, 0, 60, 1, 0, 0, 48, 0, 0, 0, 16, 0, 0, + 0, 2, 0, 0, 0, 20, 1, 0, 0, 0, 0, 0, 0, 81, 117, + 97, 100, 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, + 111, 111, 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, + 111, 114, 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, + 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, + 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, + 105, 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, + 54, 51, 56, 52, 0, 73, 83, 71, 78, 104, 0, 0, 0, 3, 0, + 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, + 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, + 0, 1, 0, 0, 0, 3, 3, 0, 0, 92, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 12, + 0, 0, 0, 83, 86, 95, 80, 111, 115, 105, 116, 105, 111, 110, 0, + 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, 171, 171, 79, 83, 71, + 78, 68, 0, 0, 0, 2, 0, 0, 0, 8, 0, 0, 0, 56, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 15, 0, 0, 0, 56, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 15, 0, 0, + 0, 83, 86, 95, 84, 97, 114, 103, 101, 116, 0, 171, 171, 86, 51, + 1, 0, 0, 0, 0, 0, 77, 97, 115, 107, 101, 100, 0, 4, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 255, 255, 255, + 255, 68, 4, 0, 0, 68, 88, 66, 67, 77, 85, 167, 240, 56, 56, + 155, 78, 125, 96, 49, 253, 103, 100, 22, 62, 1, 0, 0, 0, 68, + 4, 0, 0, 6, 0, 0, 0, 56, 0, 0, 0, 248, 0, 0, 0, + 244, 1, 0, 0, 112, 2, 0, 0, 160, 3, 0, 0, 212, 3, 0, + 0, 65, 111, 110, 57, 184, 0, 0, 0, 184, 0, 0, 0, 0, 2, + 254, 255, 132, 0, 0, 0, 52, 0, 0, 0, 1, 0, 36, 0, 0, + 0, 48, 0, 0, 0, 48, 0, 0, 0, 36, 0, 1, 0, 48, 0, + 0, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 2, 254, 255, 81, 0, 0, 5, 4, 0, 15, 160, 0, 0, + 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 0, 31, + 0, 0, 2, 5, 0, 0, 128, 0, 0, 15, 144, 4, 0, 0, 4, + 0, 0, 3, 224, 0, 0, 228, 144, 2, 0, 238, 160, 2, 0, 228, + 160, 4, 0, 0, 4, 0, 0, 12, 224, 0, 0, 20, 144, 3, 0, + 180, 160, 3, 0, 20, 160, 4, 0, 0, 4, 0, 0, 3, 128, 0, + 0, 228, 144, 1, 0, 238, 160, 1, 0, 228, 160, 2, 0, 0, 3, + 0, 0, 3, 192, 0, 0, 228, 128, 0, 0, 228, 160, 1, 0, 0, + 2, 0, 0, 12, 192, 4, 0, 68, 160, 255, 255, 0, 0, 83, 72, + 68, 82, 244, 0, 0, 0, 64, 0, 1, 0, 61, 0, 0, 0, 89, + 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 95, 0, 0, 3, 50, 16, 16, 0, 0, 0, 0, 0, 103, 0, 0, + 4, 242, 32, 16, 0, 0, 0, 0, 0, 1, 0, 0, 0, 101, 0, + 0, 3, 50, 32, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 194, + 32, 16, 0, 1, 0, 0, 0, 50, 0, 0, 11, 50, 32, 16, 0, + 0, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 128, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 54, 0, 0, 8, 194, 32, 16, 0, 0, + 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 128, 63, 50, 0, 0, 11, 50, 32, 16, + 0, 1, 0, 0, 0, 70, 16, 16, 0, 0, 0, 0, 0, 230, 138, + 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 11, 194, 32, 16, 0, + 1, 0, 0, 0, 6, 20, 16, 0, 0, 0, 0, 0, 166, 142, 32, + 0, 0, 0, 0, 0, 2, 0, 0, 0, 6, 132, 32, 0, 0, 0, + 0, 0, 2, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, + 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, + 68, 69, 70, 40, 1, 0, 0, 1, 0, 0, 0, 64, 0, 0, 0, + 1, 0, 0, 0, 28, 0, 0, 0, 0, 4, 254, 255, 0, 1, 0, + 0, 246, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 99, 98, 48, 0, 60, 0, 0, 0, + 4, 0, 0, 0, 88, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 184, 0, 0, 0, 0, 0, 0, 0, 16, 0, + 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 212, + 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, + 196, 0, 0, 0, 0, 0, 0, 0, 222, 0, 0, 0, 32, 0, 0, + 0, 16, 0, 0, 0, 2, 0, 0, 0, 196, 0, 0, 0, 0, 0, + 0, 0, 236, 0, 0, 0, 48, 0, 0, 0, 16, 0, 0, 0, 0, + 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, + 68, 101, 115, 99, 0, 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, + 114, 100, 115, 0, 77, 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, + 100, 115, 0, 84, 101, 120, 116, 67, 111, 108, 111, 114, 0, 77, 105, + 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, + 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, + 56, 52, 0, 73, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, + 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 0, 0, 0, 0, 7, 3, 0, 0, 80, 79, + 83, 73, 84, 73, 79, 78, 0, 171, 171, 171, 79, 83, 71, 78, 104, + 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 12, 0, 0, 92, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 12, 3, 0, 0, 83, 86, 95, 80, 111, 115, 105, + 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 171, + 171, 171, 49, 56, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 140, 5, 0, 0, 68, 88, 66, 67, + 233, 167, 4, 110, 60, 182, 197, 16, 114, 252, 67, 184, 217, 172, 169, + 241, 1, 0, 0, 0, 140, 5, 0, 0, 6, 0, 0, 0, 56, 0, + 0, 0, 64, 1, 0, 0, 132, 2, 0, 0, 0, 3, 0, 0, 208, + 4, 0, 0, 64, 5, 0, 0, 65, 111, 110, 57, 0, 1, 0, 0, + 0, 1, 0, 0, 0, 2, 255, 255, 200, 0, 0, 0, 56, 0, 0, + 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, 0, 56, 0, 2, 0, + 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, + 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2, 255, 255, + 81, 0, 0, 5, 1, 0, 15, 160, 0, 0, 128, 63, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, 0, 0, 144, 0, + 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1, 8, 15, 160, + 1, 0, 0, 2, 0, 0, 7, 128, 0, 0, 228, 160, 4, 0, 0, + 4, 0, 0, 15, 128, 0, 0, 36, 128, 1, 0, 64, 160, 1, 0, + 21, 160, 1, 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, 1, + 0, 0, 2, 0, 0, 3, 128, 0, 0, 235, 176, 66, 0, 0, 3, + 1, 0, 15, 128, 0, 0, 228, 176, 0, 8, 228, 160, 66, 0, 0, + 3, 0, 0, 15, 128, 0, 0, 228, 128, 1, 8, 228, 160, 5, 0, + 0, 3, 1, 0, 15, 128, 1, 0, 70, 128, 0, 0, 255, 160, 5, + 0, 0, 3, 0, 0, 15, 128, 0, 0, 255, 128, 1, 0, 228, 128, + 1, 0, 0, 2, 1, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, + 0, 83, 72, 68, 82, 60, 1, 0, 0, 64, 0, 0, 0, 79, 0, + 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, 4, + 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, 0, + 90, 0, 0, 3, 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, 0, + 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, 24, + 0, 4, 0, 112, 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, 98, + 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, 3, + 194, 16, 16, 0, 1, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, + 0, 0, 0, 0, 0, 101, 0, 0, 3, 242, 32, 16, 0, 1, 0, + 0, 0, 104, 0, 0, 2, 2, 0, 0, 0, 54, 0, 0, 6, 114, + 32, 16, 0, 0, 0, 0, 0, 70, 130, 32, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 54, 0, 0, 5, 130, 32, 16, 0, 0, 0, 0, + 0, 1, 64, 0, 0, 0, 0, 128, 63, 69, 0, 0, 9, 242, 0, + 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, + 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, + 56, 0, 0, 8, 242, 0, 16, 0, 0, 0, 0, 0, 102, 4, 16, + 0, 0, 0, 0, 0, 246, 143, 32, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 230, + 26, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, + 0, 96, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16, + 0, 1, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 246, 15, + 16, 0, 1, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, + 0, 0, 0, 7, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, + 68, 69, 70, 200, 1, 0, 0, 1, 0, 0, 0, 224, 0, 0, 0, + 5, 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, + 0, 150, 1, 0, 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 197, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 210, 0, 0, 0, 2, 0, + 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 214, 0, 0, 0, + 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, + 255, 1, 0, 0, 0, 1, 0, 0, 0, 12, 0, 0, 0, 219, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 115, 83, 97, 109, 112, 108, 101, 114, 0, 115, 77, 97, 115, 107, 83, + 97, 109, 112, 108, 101, 114, 0, 116, 101, 120, 0, 109, 97, 115, 107, + 0, 99, 98, 48, 0, 171, 219, 0, 0, 0, 4, 0, 0, 0, 248, + 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 88, 1, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, + 0, 100, 1, 0, 0, 0, 0, 0, 0, 116, 1, 0, 0, 16, 0, + 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 100, 1, 0, 0, 0, + 0, 0, 0, 126, 1, 0, 0, 32, 0, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 100, 1, 0, 0, 0, 0, 0, 0, 140, 1, 0, + 0, 48, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 100, 1, + 0, 0, 0, 0, 0, 0, 81, 117, 97, 100, 68, 101, 115, 99, 0, + 171, 171, 171, 1, 0, 3, 0, 1, 0, 4, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 77, + 97, 115, 107, 84, 101, 120, 67, 111, 111, 114, 100, 115, 0, 84, 101, + 120, 116, 67, 111, 108, 111, 114, 0, 77, 105, 99, 114, 111, 115, 111, + 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, + 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, 46, + 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 73, 83, + 71, 78, 104, 0, 0, 0, 3, 0, 0, 0, 8, 0, 0, 0, 80, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 3, 3, + 0, 0, 92, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, + 0, 0, 0, 1, 0, 0, 0, 12, 12, 0, 0, 83, 86, 95, 80, + 111, 115, 105, 116, 105, 111, 110, 0, 84, 69, 88, 67, 79, 79, 82, + 68, 0, 171, 171, 171, 79, 83, 71, 78, 68, 0, 0, 0, 2, 0, + 0, 0, 8, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, + 56, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, + 0, 1, 0, 0, 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, 114, + 103, 101, 116, 0, 171, 171, 141, 60, 1, 0, 0, 0, 0, 0, 4, + 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 46, 0, 0, 0, 18, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 64, 0, 0, 0, 0, + 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 93, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 0, + 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 112, 0, 0, 0, + 65, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 126, 0, 0, 0, 65, 0, + 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 136, 0, 0, 0, 160, 0, 0, 0, + 0, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 168, 0, 0, 0, 140, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 181, + 0, 0, 0, 140, 0, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 194, 0, 0, + 0, 140, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 206, 0, 0, 0, 65, + 0, 0, 0, 0, 0, 0, 0, 144, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 218, 0, 0, 0, 112, 0, 0, + 0, 0, 0, 0, 0, 7, 0, 0, 0, 255, 255, 255, 255, 0, 0, + 0, 0, 3, 1, 0, 0, 231, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 61, 1, 0, 0, 33, 1, 0, 0, 0, 0, 0, 0, 48, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 107, 1, + 0, 0, 79, 1, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 112, 1, 0, 0, + 33, 1, 0, 0, 0, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 154, 1, 0, 0, 126, 1, + 0, 0, 0, 0, 0, 0, 88, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 156, 1, 0, 0, 126, 1, 0, 0, + 0, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 164, 1, 0, 0, 126, 1, 0, 0, 0, 0, + 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 175, 1, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, + 6, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 179, 1, 0, + 0, 231, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 206, 1, 0, 0, 33, + 1, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 221, 1, 0, 0, 33, 1, 0, + 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 228, 1, 0, 0, 126, 1, 0, 0, 0, + 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 234, 1, 0, 0, 126, 1, 0, 0, 0, 0, 0, + 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 247, 1, 0, 0, 126, 1, 0, 0, 0, 0, 0, 0, 72, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 40, 2, 0, 0, 12, 2, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 44, 2, 0, 0, 12, 2, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 51, 2, 0, 0, 12, + 2, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 97, 2, 0, 0, 69, 2, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 4, 0, 0, 0, 45, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 106, 2, 0, 0, 55, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 40, 2, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 118, 2, 0, 0, 47, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 130, 2, 0, 0, 0, 0, 0, 0, 142, 2, + 0, 0, 69, 2, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 4, + 0, 0, 0, 45, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 154, 2, 0, 0, 55, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, + 0, 44, 2, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 166, 2, 0, 0, 47, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 178, 2, 0, 0, 0, 0, 0, 0, 190, 2, 0, 0, + 69, 2, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 4, 0, 0, + 0, 45, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 203, 2, + 0, 0, 55, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 40, + 2, 0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 215, 2, 0, 0, 47, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 227, 2, 0, 0, 0, 0, 0, 0, 239, 2, 0, 0, 69, 2, + 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 4, 0, 0, 0, 45, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 254, 2, 0, 0, + 55, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 40, 2, 0, + 0, 46, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 10, 3, + 0, 0, 47, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 22, + 3, 0, 0, 0, 0, 0, 0, 34, 3, 0, 0, 69, 2, 0, 0, + 0, 0, 0, 0, 255, 255, 255, 255, 4, 0, 0, 0, 45, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 47, 3, 0, 0, 55, 0, + 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 51, 2, 0, 0, 46, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 59, 3, 0, 0, + 47, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 71, 3, 0, + 0, 0, 0, 0, 0, 83, 3, 0, 0, 69, 2, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 5, 0, 0, 0, 45, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 98, 3, 0, 0, 55, 0, 0, 0, + 0, 0, 0, 0, 2, 0, 0, 0, 40, 2, 0, 0, 46, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 110, 3, 0, 0, 47, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 122, 3, 0, 0, 52, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 134, 3, 0, 0, + 0, 0, 0, 0, 214, 3, 0, 0, 186, 3, 0, 0, 0, 0, 0, + 0, 255, 255, 255, 255, 2, 0, 0, 0, 19, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 226, 3, 0, 0, 13, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 238, 3, 0, 0, 0, 0, 0, 0, + 33, 4, 0, 0, 5, 4, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 2, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 46, 4, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 58, 4, 0, 0, 0, 0, 0, 0, 70, 4, 0, 0, + 5, 4, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 8, 0, 0, + 0, 37, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 83, 4, + 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 95, + 4, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 107, 4, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 119, 4, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 131, 4, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 143, 4, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 155, 4, 0, 0, 44, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 167, 4, 0, 0, 0, 0, 0, 0, 179, 4, + 0, 0, 5, 4, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 9, + 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 190, 4, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 202, 4, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 214, 4, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 226, 4, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 238, 4, 0, 0, 41, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 250, 4, 0, 0, 42, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 6, 5, 0, 0, 43, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 18, 5, 0, 0, 44, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 30, 5, 0, 0, 0, 0, 0, + 0, 42, 5, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 56, 5, + 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 2, 0, 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, + 0, 0, 0, 0, 7, 0, 0, 0, 131, 9, 0, 0, 8, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 139, 9, 0, 0, 7, 0, + 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 111, 12, 0, 0, 119, + 12, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 56, 5, 0, 0, + 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 0, 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, + 0, 0, 7, 0, 0, 0, 227, 16, 0, 0, 8, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 235, 16, 0, 0, 7, 0, 0, 0, + 0, 0, 0, 0, 7, 0, 0, 0, 67, 30, 0, 0, 75, 30, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 56, 5, 0, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, + 7, 0, 0, 0, 183, 34, 0, 0, 8, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 191, 34, 0, 0, 7, 0, 0, 0, 0, 0, + 0, 0, 7, 0, 0, 0, 39, 52, 0, 0, 47, 52, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 56, 5, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, + 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, + 0, 0, 156, 56, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 164, 56, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, + 7, 0, 0, 0, 140, 94, 0, 0, 148, 94, 0, 0, 6, 0, 0, + 0, 0, 0, 0, 0, 169, 94, 0, 0, 4, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 214, + 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, + 222, 101, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 230, 101, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 7, 0, + 0, 0, 214, 111, 0, 0, 222, 111, 0, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, + 0, 17, 119, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 25, 119, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 7, + 0, 0, 0, 233, 126, 0, 0, 241, 126, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, + 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, + 0, 0, 42, 134, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 50, 134, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, + 7, 0, 0, 0, 38, 144, 0, 0, 46, 144, 0, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, + 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, + 0, 0, 0, 101, 151, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 109, 151, 0, 0, 7, 0, 0, 0, 0, 0, 0, + 0, 7, 0, 0, 0, 65, 159, 0, 0, 73, 159, 0, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, + 7, 0, 0, 0, 132, 166, 0, 0, 8, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 140, 166, 0, 0, 7, 0, 0, 0, 0, 0, + 0, 0, 7, 0, 0, 0, 132, 176, 0, 0, 140, 176, 0, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, + 0, 7, 0, 0, 0, 197, 183, 0, 0, 8, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 205, 183, 0, 0, 7, 0, 0, 0, 0, + 0, 0, 0, 7, 0, 0, 0, 165, 191, 0, 0, 173, 191, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 169, 94, 0, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, + 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, + 0, 0, 0, 209, 198, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 217, 198, 0, 0, 7, 0, 0, 0, 0, 0, 0, + 0, 7, 0, 0, 0, 53, 211, 0, 0, 241, 126, 0, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, + 7, 0, 0, 0, 77, 218, 0, 0, 8, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 85, 218, 0, 0, 7, 0, 0, 0, 0, 0, + 0, 0, 7, 0, 0, 0, 181, 230, 0, 0, 73, 159, 0, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, + 0, 7, 0, 0, 0, 205, 237, 0, 0, 8, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 213, 237, 0, 0, 7, 0, 0, 0, 0, + 0, 0, 0, 7, 0, 0, 0, 57, 250, 0, 0, 65, 250, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 56, 5, 0, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, + 0, 0, 214, 3, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, + 0, 0, 0, 157, 254, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 165, 254, 0, 0, 7, 0, 0, 0, 0, 0, 0, + 0, 7, 0, 0, 0, 137, 2, 1, 0, 145, 2, 1, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 56, 5, 0, 0, 7, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 214, 3, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 169, 2, 1, 0, 11, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 205, 2, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 33, 4, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, + 7, 0, 0, 0, 33, 7, 1, 0, 8, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 41, 7, 1, 0, 7, 0, 0, 0, 0, 0, + 0, 0, 7, 0, 0, 0, 33, 17, 1, 0, 41, 17, 1, 0, 7, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 214, 3, 0, 0, 10, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 44, 17, 1, 0, 11, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 80, 17, 1, 0, 2, 0, 0, 0, 0, + 0, 0, 0, 2, 0, 0, 0, 70, 4, 0, 0, 6, 0, 0, 0, + 0, 0, 0, 0, 7, 0, 0, 0, 164, 21, 1, 0, 8, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 172, 21, 1, 0, 7, 0, + 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 104, 31, 1, 0, 112, + 31, 1, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 0, 0, 0, 214, 3, 0, 0, 10, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 115, 31, 1, 0, 11, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 151, 31, 1, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 70, 4, 0, 0, + 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 235, 35, 1, + 0, 8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 243, 35, + 1, 0, 7, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 167, + 46, 1, 0, 175, 46, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 193, 46, 1, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 2, 0, 0, 0, 214, 3, 0, 0, 10, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 202, 46, 1, 0, 11, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 238, 46, 1, 0, + 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 179, 4, 0, + 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 66, 51, + 1, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 74, + 51, 1, 0, 7, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, + 242, 55, 1, 0, 250, 55, 1, 0, 7, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 214, 3, + 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, + 56, 1, 0, 11, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 37, 56, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, + 0, 179, 4, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7, 0, + 0, 0, 121, 60, 1, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 129, 60, 1, 0, 7, 0, 0, 0, 0, 0, 0, 0, + 7, 0, 0, 0, 29, 66, 1, 0}; diff --git a/gfx/2d/ShadersD2D1.h b/gfx/2d/ShadersD2D1.h new file mode 100644 index 0000000000..9df2858da5 --- /dev/null +++ b/gfx/2d/ShadersD2D1.h @@ -0,0 +1,1186 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 +// +// +// Buffer Definitions: +// +// cbuffer radialGradientConstants +// { +// +// float3 diff; // Offset: 0 Size: 12 +// float2 center1; // Offset: 16 Size: 8 +// float A; // Offset: 24 Size: 4 +// float radius1; // Offset: 28 Size: 4 +// float sq_radius1; // Offset: 32 Size: 4 +// float repeat_correct; // Offset: 36 Size: 4 +// float allow_odd; // Offset: 40 Size: 4 +// float3x2 transform; // Offset: 48 Size: 28 +// +// } +// +// +// Resource Bindings: +// +// Name Type Format Dim Slot Elements +// ------------------------------ ---------- ------- ----------- ---- -------- +// InputSampler sampler NA NA 0 1 +// GradientSampler sampler NA NA 1 1 +// InputTexture texture float4 2d 0 1 +// GradientTexture texture float4 2d 1 1 +// radialGradientConstants cbuffer NA NA 0 1 +// +// +// +// Input signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_POSITION 0 xyzw 0 POS float +// SCENE_POSITION 0 xyzw 1 NONE float xy +// TEXCOORD 0 xyzw 2 NONE float xy +// +// +// Output signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_Target 0 xyzw 0 TARGET float xyzw +// +// +// Constant buffer to DX9 shader constant mappings: +// +// Target Reg Buffer Start Reg # of Regs Data Conversion +// ---------- ------- --------- --------- ---------------------- +// c0 cb0 0 5 ( FLT, FLT, FLT, FLT) +// +// +// Sampler/Resource to DX9 shader sampler mappings: +// +// Target Sampler Source Sampler Source Resource +// -------------- --------------- ---------------- +// s0 s0 t0 +// s1 s1 t1 +// +// +// Level9 shader bytecode: +// + ps_2_x + def c5, 0.5, 1, 0, 0 + def c6, 1, -1, 0, -0 + dcl t0 + dcl t1 + dcl_2d s0 + dcl_2d s1 + dp2add r0.x, t0, c3, c3.z + dp2add r0.y, t0, c4, c4.z + add r0.xy, r0, -c1 + dp2add r0.w, r0, r0, -c2.x + mul r0.w, r0.w, c1.z + mov r0.z, c1.w + dp3 r0.x, r0, c0 + mad r0.y, r0.x, r0.x, -r0.w + abs r0.z, r0.y + cmp r0.y, r0.y, c5.y, c5.z + rsq r0.z, r0.z + rcp r1.x, r0.z + mov r1.yz, -r1.x + add r0.xzw, r0.x, r1.xyyz + rcp r1.x, c1.z + mul r0.xzw, r0, r1.x + mov r1.w, c1.w + mad r1.xyz, r0.xzww, c0.z, r1.w + cmp r1.w, r1.x, r0.x, r0.w + cmp r0.xzw, r1.xyyz, c6.xyxy, c6.zyzw + frc r1.x, r1.w + add r1.x, -r1.x, r1.w + mul r1.y, r1.x, c5.x + abs r1.y, r1.y + frc r1.y, r1.y + cmp r1.y, r1.x, r1.y, -r1.y + add r1.x, -r1.x, r1.w + add r1.y, r1.y, r1.y + abs r1.y, r1.y + mul r1.y, r1.y, c2.z + frc r1.z, -r1.w + lrp r2.w, r1.y, r1.z, r1.x + lrp r3.x, c2.y, r2.w, r1.w + mov r3.y, c5.x + texld r1, t1, s0 + texld r2, r3, s1 + mul r2.xyz, r2.w, r2 + mul r1, r1, r2 + add r0.w, r0.w, r0.x + cmp r0.x, r0.w, r0.x, r0.z + mul r1, r0.x, r1 + mul r0, r0.y, r1 + mov oC0, r0 + +// approximately 46 instruction slots used (2 texture, 44 arithmetic) +ps_4_0 +dcl_constantbuffer cb0[5], immediateIndexed +dcl_sampler s0, mode_default +dcl_sampler s1, mode_default +dcl_resource_texture2d (float,float,float,float) t0 +dcl_resource_texture2d (float,float,float,float) t1 +dcl_input_ps linear v1.xy +dcl_input_ps linear v2.xy +dcl_output o0.xyzw +dcl_temps 3 +dp2 r0.x, v1.xyxx, cb0[3].xyxx +add r0.x, r0.x, cb0[3].z +dp2 r0.z, v1.xyxx, cb0[4].xyxx +add r0.y, r0.z, cb0[4].z +add r0.xy, r0.xyxx, -cb0[1].xyxx +dp2 r0.w, r0.xyxx, r0.xyxx +add r0.w, r0.w, -cb0[2].x +mul r0.w, r0.w, cb0[1].z +mov r0.z, cb0[1].w +dp3 r0.x, r0.xyzx, cb0[0].xyzx +mad r0.y, r0.x, r0.x, -r0.w +sqrt r1.x, |r0.y| +ge r0.y, r0.y, l(0.000000) +and r0.y, r0.y, l(0x3f800000) +mov r1.y, -r1.x +add r0.xz, r0.xxxx, r1.xxyx +div r0.xz, r0.xxzx, cb0[1].zzzz +add r0.w, -r0.z, r0.x +mul r1.xy, r0.xzxx, cb0[0].zzzz +ge r1.xy, r1.xyxx, -cb0[1].wwww +and r1.xy, r1.xyxx, l(0x3f800000, 0x3f800000, 0, 0) +mad r0.x, r1.x, r0.w, r0.z +max r0.z, r1.y, r1.x +ge r0.z, l(0.000000), r0.z +movc r0.z, r0.z, l(-0.000000), l(1.000000) +round_pi r0.w, r0.x +add r0.w, -r0.x, r0.w +round_ni r1.x, r0.x +mul r1.y, r1.x, l(0.500000) +add r1.x, r0.x, -r1.x +ge r1.z, r1.y, -r1.y +frc r1.y, |r1.y| +movc r1.y, r1.z, r1.y, -r1.y +add r1.y, r1.y, r1.y +mul r1.z, |r1.y|, cb0[2].z +mad r1.y, -|r1.y|, cb0[2].z, l(1.000000) +mul r0.w, r0.w, r1.z +mad r0.w, r1.x, r1.y, r0.w +mul r0.w, r0.w, cb0[2].y +add r1.x, l(1.000000), -cb0[2].y +mad r1.x, r0.x, r1.x, r0.w +mov r1.y, l(0.500000) +sample r1.xyzw, r1.xyxx, t1.xyzw, s1 +mul r1.xyz, r1.wwww, r1.xyzx +sample r2.xyzw, v2.xyxx, t0.xyzw, s0 +mul r1.xyzw, r1.xyzw, r2.xyzw +mul r1.xyzw, r0.zzzz, r1.xyzw +mul o0.xyzw, r0.yyyy, r1.xyzw +ret +// Approximately 49 instruction slots used +#endif + +const BYTE SampleRadialGradientPS[] = { + 68, 88, 66, 67, 221, 203, 207, 240, 164, 242, 31, 220, 34, 19, 29, + 61, 18, 184, 230, 185, 1, 0, 0, 0, 196, 13, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, 136, 3, 0, 0, 232, 9, 0, 0, 100, + 10, 0, 0, 20, 13, 0, 0, 144, 13, 0, 0, 65, 111, 110, 57, + 72, 3, 0, 0, 72, 3, 0, 0, 0, 2, 255, 255, 16, 3, 0, + 0, 56, 0, 0, 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, 0, + 56, 0, 2, 0, 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, 1, + 1, 1, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, + 1, 2, 255, 255, 81, 0, 0, 5, 5, 0, 15, 160, 0, 0, 0, + 63, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 0, 81, 0, + 0, 5, 6, 0, 15, 160, 0, 0, 128, 63, 0, 0, 128, 191, 0, + 0, 0, 0, 0, 0, 0, 128, 31, 0, 0, 2, 0, 0, 0, 128, + 0, 0, 15, 176, 31, 0, 0, 2, 0, 0, 0, 128, 1, 0, 15, + 176, 31, 0, 0, 2, 0, 0, 0, 144, 0, 8, 15, 160, 31, 0, + 0, 2, 0, 0, 0, 144, 1, 8, 15, 160, 90, 0, 0, 4, 0, + 0, 1, 128, 0, 0, 228, 176, 3, 0, 228, 160, 3, 0, 170, 160, + 90, 0, 0, 4, 0, 0, 2, 128, 0, 0, 228, 176, 4, 0, 228, + 160, 4, 0, 170, 160, 2, 0, 0, 3, 0, 0, 3, 128, 0, 0, + 228, 128, 1, 0, 228, 161, 90, 0, 0, 4, 0, 0, 8, 128, 0, + 0, 228, 128, 0, 0, 228, 128, 2, 0, 0, 161, 5, 0, 0, 3, + 0, 0, 8, 128, 0, 0, 255, 128, 1, 0, 170, 160, 1, 0, 0, + 2, 0, 0, 4, 128, 1, 0, 255, 160, 8, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 228, 128, 0, 0, 228, 160, 4, 0, 0, 4, 0, + 0, 2, 128, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 255, 129, + 35, 0, 0, 2, 0, 0, 4, 128, 0, 0, 85, 128, 88, 0, 0, + 4, 0, 0, 2, 128, 0, 0, 85, 128, 5, 0, 85, 160, 5, 0, + 170, 160, 7, 0, 0, 2, 0, 0, 4, 128, 0, 0, 170, 128, 6, + 0, 0, 2, 1, 0, 1, 128, 0, 0, 170, 128, 1, 0, 0, 2, + 1, 0, 6, 128, 1, 0, 0, 129, 2, 0, 0, 3, 0, 0, 13, + 128, 0, 0, 0, 128, 1, 0, 148, 128, 6, 0, 0, 2, 1, 0, + 1, 128, 1, 0, 170, 160, 5, 0, 0, 3, 0, 0, 13, 128, 0, + 0, 228, 128, 1, 0, 0, 128, 1, 0, 0, 2, 1, 0, 8, 128, + 1, 0, 255, 160, 4, 0, 0, 4, 1, 0, 7, 128, 0, 0, 248, + 128, 0, 0, 170, 160, 1, 0, 255, 128, 88, 0, 0, 4, 1, 0, + 8, 128, 1, 0, 0, 128, 0, 0, 0, 128, 0, 0, 255, 128, 88, + 0, 0, 4, 0, 0, 13, 128, 1, 0, 148, 128, 6, 0, 68, 160, + 6, 0, 230, 160, 19, 0, 0, 2, 1, 0, 1, 128, 1, 0, 255, + 128, 2, 0, 0, 3, 1, 0, 1, 128, 1, 0, 0, 129, 1, 0, + 255, 128, 5, 0, 0, 3, 1, 0, 2, 128, 1, 0, 0, 128, 5, + 0, 0, 160, 35, 0, 0, 2, 1, 0, 2, 128, 1, 0, 85, 128, + 19, 0, 0, 2, 1, 0, 2, 128, 1, 0, 85, 128, 88, 0, 0, + 4, 1, 0, 2, 128, 1, 0, 0, 128, 1, 0, 85, 128, 1, 0, + 85, 129, 2, 0, 0, 3, 1, 0, 1, 128, 1, 0, 0, 129, 1, + 0, 255, 128, 2, 0, 0, 3, 1, 0, 2, 128, 1, 0, 85, 128, + 1, 0, 85, 128, 35, 0, 0, 2, 1, 0, 2, 128, 1, 0, 85, + 128, 5, 0, 0, 3, 1, 0, 2, 128, 1, 0, 85, 128, 2, 0, + 170, 160, 19, 0, 0, 2, 1, 0, 4, 128, 1, 0, 255, 129, 18, + 0, 0, 4, 2, 0, 8, 128, 1, 0, 85, 128, 1, 0, 170, 128, + 1, 0, 0, 128, 18, 0, 0, 4, 3, 0, 1, 128, 2, 0, 85, + 160, 2, 0, 255, 128, 1, 0, 255, 128, 1, 0, 0, 2, 3, 0, + 2, 128, 5, 0, 0, 160, 66, 0, 0, 3, 1, 0, 15, 128, 1, + 0, 228, 176, 0, 8, 228, 160, 66, 0, 0, 3, 2, 0, 15, 128, + 3, 0, 228, 128, 1, 8, 228, 160, 5, 0, 0, 3, 2, 0, 7, + 128, 2, 0, 255, 128, 2, 0, 228, 128, 5, 0, 0, 3, 1, 0, + 15, 128, 1, 0, 228, 128, 2, 0, 228, 128, 2, 0, 0, 3, 0, + 0, 8, 128, 0, 0, 255, 128, 0, 0, 0, 128, 88, 0, 0, 4, + 0, 0, 1, 128, 0, 0, 255, 128, 0, 0, 0, 128, 0, 0, 170, + 128, 5, 0, 0, 3, 1, 0, 15, 128, 0, 0, 0, 128, 1, 0, + 228, 128, 5, 0, 0, 3, 0, 0, 15, 128, 0, 0, 85, 128, 1, + 0, 228, 128, 1, 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, + 255, 255, 0, 0, 83, 72, 68, 82, 88, 6, 0, 0, 64, 0, 0, + 0, 150, 1, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, + 0, 0, 5, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0, + 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 1, 0, 0, 0, + 88, 24, 0, 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, + 0, 88, 24, 0, 4, 0, 112, 16, 0, 1, 0, 0, 0, 85, 85, + 0, 0, 98, 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 98, + 16, 0, 3, 50, 16, 16, 0, 2, 0, 0, 0, 101, 0, 0, 3, + 242, 32, 16, 0, 0, 0, 0, 0, 104, 0, 0, 2, 3, 0, 0, + 0, 15, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 70, 16, + 16, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 3, + 0, 0, 0, 0, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 15, 0, 0, 8, 66, 0, 16, 0, 0, 0, + 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0, + 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 8, 34, 0, 16, 0, + 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, + 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 9, 50, 0, + 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 70, + 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 15, 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, + 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 0, 0, + 0, 9, 130, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, + 0, 0, 0, 10, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 56, 0, 0, 8, 130, 0, 16, 0, 0, 0, 0, + 0, 58, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 54, 0, 0, 6, 66, 0, 16, 0, 0, + 0, 0, 0, 58, 128, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 16, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 70, 2, 16, + 0, 0, 0, 0, 0, 70, 130, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 50, 0, 0, 10, 34, 0, 16, 0, 0, 0, 0, 0, 10, + 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, + 58, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 75, 0, 0, + 6, 18, 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 128, 129, 0, + 0, 0, 0, 0, 0, 0, 29, 0, 0, 7, 34, 0, 16, 0, 0, + 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 7, 34, 0, 16, 0, 0, 0, 0, + 0, 26, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, + 128, 63, 54, 0, 0, 6, 34, 0, 16, 0, 1, 0, 0, 0, 10, + 0, 16, 128, 65, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 7, + 82, 0, 16, 0, 0, 0, 0, 0, 6, 0, 16, 0, 0, 0, 0, + 0, 6, 1, 16, 0, 1, 0, 0, 0, 14, 0, 0, 8, 82, 0, + 16, 0, 0, 0, 0, 0, 6, 2, 16, 0, 0, 0, 0, 0, 166, + 138, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 8, + 130, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 128, 65, 0, 0, + 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 56, 0, + 0, 8, 50, 0, 16, 0, 1, 0, 0, 0, 134, 0, 16, 0, 0, + 0, 0, 0, 166, 138, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 29, 0, 0, 9, 50, 0, 16, 0, 1, 0, 0, 0, 70, 0, 16, + 0, 1, 0, 0, 0, 246, 143, 32, 128, 65, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 1, 0, 0, 10, 50, 0, 16, 0, 1, + 0, 0, 0, 70, 0, 16, 0, 1, 0, 0, 0, 2, 64, 0, 0, + 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 0, + 0, 50, 0, 0, 9, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, + 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 42, + 0, 16, 0, 0, 0, 0, 0, 52, 0, 0, 7, 66, 0, 16, 0, + 0, 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, + 0, 1, 0, 0, 0, 29, 0, 0, 7, 66, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, + 0, 0, 0, 55, 0, 0, 9, 66, 0, 16, 0, 0, 0, 0, 0, + 42, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, + 128, 1, 64, 0, 0, 0, 0, 128, 63, 66, 0, 0, 5, 130, 0, + 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 0, + 0, 0, 8, 130, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 128, + 65, 0, 0, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, + 0, 65, 0, 0, 5, 18, 0, 16, 0, 1, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, 56, 0, 0, 7, 34, 0, 16, 0, 1, + 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, + 0, 0, 0, 63, 0, 0, 0, 8, 18, 0, 16, 0, 1, 0, 0, + 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 128, 65, 0, + 0, 0, 1, 0, 0, 0, 29, 0, 0, 8, 66, 0, 16, 0, 1, + 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 128, + 65, 0, 0, 0, 1, 0, 0, 0, 26, 0, 0, 6, 34, 0, 16, + 0, 1, 0, 0, 0, 26, 0, 16, 128, 129, 0, 0, 0, 1, 0, + 0, 0, 55, 0, 0, 10, 34, 0, 16, 0, 1, 0, 0, 0, 42, + 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, + 26, 0, 16, 128, 65, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 7, 34, 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 0, 1, 0, + 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 56, 0, 0, 9, 66, + 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 128, 129, 0, 0, 0, + 1, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0, 0, 2, 0, 0, + 0, 50, 0, 0, 11, 34, 0, 16, 0, 1, 0, 0, 0, 26, 0, + 16, 128, 193, 0, 0, 0, 1, 0, 0, 0, 42, 128, 32, 0, 0, + 0, 0, 0, 2, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, + 56, 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, + 0, 0, 0, 0, 0, 42, 0, 16, 0, 1, 0, 0, 0, 50, 0, + 0, 9, 130, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 1, + 0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, + 0, 0, 0, 0, 56, 0, 0, 8, 130, 0, 16, 0, 0, 0, 0, + 0, 58, 0, 16, 0, 0, 0, 0, 0, 26, 128, 32, 0, 0, 0, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 9, 18, 0, 16, 0, 1, + 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 26, 128, 32, 128, + 65, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 50, 0, 0, + 9, 18, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0, + 0, 0, 0, 54, 0, 0, 5, 34, 0, 16, 0, 1, 0, 0, 0, + 1, 64, 0, 0, 0, 0, 0, 63, 69, 0, 0, 9, 242, 0, 16, + 0, 1, 0, 0, 0, 70, 0, 16, 0, 1, 0, 0, 0, 70, 126, + 16, 0, 1, 0, 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 56, + 0, 0, 7, 114, 0, 16, 0, 1, 0, 0, 0, 246, 15, 16, 0, + 1, 0, 0, 0, 70, 2, 16, 0, 1, 0, 0, 0, 69, 0, 0, + 9, 242, 0, 16, 0, 2, 0, 0, 0, 70, 16, 16, 0, 2, 0, + 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, + 0, 0, 0, 56, 0, 0, 7, 242, 0, 16, 0, 1, 0, 0, 0, + 70, 14, 16, 0, 1, 0, 0, 0, 70, 14, 16, 0, 2, 0, 0, + 0, 56, 0, 0, 7, 242, 0, 16, 0, 1, 0, 0, 0, 166, 10, + 16, 0, 0, 0, 0, 0, 70, 14, 16, 0, 1, 0, 0, 0, 56, + 0, 0, 7, 242, 32, 16, 0, 0, 0, 0, 0, 86, 5, 16, 0, + 0, 0, 0, 0, 70, 14, 16, 0, 1, 0, 0, 0, 62, 0, 0, + 1, 83, 84, 65, 84, 116, 0, 0, 0, 49, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 40, 0, 0, 0, 0, + 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 82, 68, 69, 70, 168, 2, 0, 0, 1, 0, + 0, 0, 16, 1, 0, 0, 5, 0, 0, 0, 28, 0, 0, 0, 0, + 4, 255, 255, 0, 1, 0, 0, 116, 2, 0, 0, 188, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 201, 0, + 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, + 217, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 13, 0, + 0, 0, 230, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, + 0, 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 1, 0, 0, 0, + 13, 0, 0, 0, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 73, 110, 112, 117, 116, 83, 97, 109, 112, + 108, 101, 114, 0, 71, 114, 97, 100, 105, 101, 110, 116, 83, 97, 109, + 112, 108, 101, 114, 0, 73, 110, 112, 117, 116, 84, 101, 120, 116, 117, + 114, 101, 0, 71, 114, 97, 100, 105, 101, 110, 116, 84, 101, 120, 116, + 117, 114, 101, 0, 114, 97, 100, 105, 97, 108, 71, 114, 97, 100, 105, + 101, 110, 116, 67, 111, 110, 115, 116, 97, 110, 116, 115, 0, 171, 171, + 246, 0, 0, 0, 8, 0, 0, 0, 40, 1, 0, 0, 80, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 232, 1, 0, 0, 0, 0, + 0, 0, 12, 0, 0, 0, 2, 0, 0, 0, 240, 1, 0, 0, 0, + 0, 0, 0, 0, 2, 0, 0, 16, 0, 0, 0, 8, 0, 0, 0, + 2, 0, 0, 0, 8, 2, 0, 0, 0, 0, 0, 0, 24, 2, 0, + 0, 24, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 28, 2, + 0, 0, 0, 0, 0, 0, 44, 2, 0, 0, 28, 0, 0, 0, 4, + 0, 0, 0, 2, 0, 0, 0, 28, 2, 0, 0, 0, 0, 0, 0, + 52, 2, 0, 0, 32, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, + 0, 28, 2, 0, 0, 0, 0, 0, 0, 63, 2, 0, 0, 36, 0, + 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 28, 2, 0, 0, 0, + 0, 0, 0, 78, 2, 0, 0, 40, 0, 0, 0, 4, 0, 0, 0, + 2, 0, 0, 0, 28, 2, 0, 0, 0, 0, 0, 0, 88, 2, 0, + 0, 48, 0, 0, 0, 28, 0, 0, 0, 2, 0, 0, 0, 100, 2, + 0, 0, 0, 0, 0, 0, 100, 105, 102, 102, 0, 171, 171, 171, 1, + 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 99, 101, 110, 116, 101, 114, 49, 0, 1, 0, 3, 0, 1, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 171, 171, 0, 0, + 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, + 97, 100, 105, 117, 115, 49, 0, 115, 113, 95, 114, 97, 100, 105, 117, + 115, 49, 0, 114, 101, 112, 101, 97, 116, 95, 99, 111, 114, 114, 101, + 99, 116, 0, 97, 108, 108, 111, 119, 95, 111, 100, 100, 0, 116, 114, + 97, 110, 115, 102, 111, 114, 109, 0, 171, 171, 3, 0, 3, 0, 3, + 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 105, 99, 114, + 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, + 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, + 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, + 0, 171, 171, 73, 83, 71, 78, 116, 0, 0, 0, 3, 0, 0, 0, + 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, + 0, 0, 0, 15, 3, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 15, 3, 0, + 0, 83, 86, 95, 80, 79, 83, 73, 84, 73, 79, 78, 0, 83, 67, + 69, 78, 69, 95, 80, 79, 83, 73, 84, 73, 79, 78, 0, 84, 69, + 88, 67, 79, 79, 82, 68, 0, 79, 83, 71, 78, 44, 0, 0, 0, + 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, + 0, 0, 83, 86, 95, 84, 97, 114, 103, 101, 116, 0, 171, 171}; +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 +// +// +// Buffer Definitions: +// +// cbuffer radialGradientConstants +// { +// +// float3 diff; // Offset: 0 Size: 12 +// float2 center1; // Offset: 16 Size: 8 +// float A; // Offset: 24 Size: 4 [unused] +// float radius1; // Offset: 28 Size: 4 +// float sq_radius1; // Offset: 32 Size: 4 [unused] +// float repeat_correct; // Offset: 36 Size: 4 +// float allow_odd; // Offset: 40 Size: 4 +// float3x2 transform; // Offset: 48 Size: 28 +// +// } +// +// +// Resource Bindings: +// +// Name Type Format Dim Slot Elements +// ------------------------------ ---------- ------- ----------- ---- -------- +// InputSampler sampler NA NA 0 1 +// GradientSampler sampler NA NA 1 1 +// InputTexture texture float4 2d 0 1 +// GradientTexture texture float4 2d 1 1 +// radialGradientConstants cbuffer NA NA 0 1 +// +// +// +// Input signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_POSITION 0 xyzw 0 POS float +// SCENE_POSITION 0 xyzw 1 NONE float xy +// TEXCOORD 0 xyzw 2 NONE float xy +// +// +// Output signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_Target 0 xyzw 0 TARGET float xyzw +// +// +// Constant buffer to DX9 shader constant mappings: +// +// Target Reg Buffer Start Reg # of Regs Data Conversion +// ---------- ------- --------- --------- ---------------------- +// c0 cb0 0 5 ( FLT, FLT, FLT, FLT) +// +// +// Sampler/Resource to DX9 shader sampler mappings: +// +// Target Sampler Source Sampler Source Resource +// -------------- --------------- ---------------- +// s0 s0 t0 +// s1 s1 t1 +// +// +// Level9 shader bytecode: +// + ps_2_x + def c5, 0.5, -0, 1, 0 + dcl t0 + dcl t1 + dcl_2d s0 + dcl_2d s1 + dp2add r0.x, t0, c3, c3.z + dp2add r0.y, t0, c4, c4.z + add r0.xy, r0, -c1 + mul r0.w, c1.w, c1.w + dp2add r0.w, r0, r0, -r0.w + mul r0.w, r0.w, c5.x + mov r0.z, c1.w + dp3 r0.x, r0, c0 + rcp r0.x, r0.x + mul r0.y, r0.x, r0.w + frc r0.z, r0.y + add r0.z, -r0.z, r0.y + mul r1.w, r0.z, c5.x + abs r1.x, r1.w + frc r1.x, r1.x + cmp r1.x, r0.z, r1.x, -r1.x + mad r0.x, r0.w, r0.x, -r0.z + add r0.z, r1.x, r1.x + abs r0.z, r0.z + mul r0.z, r0.z, c2.z + frc r0.w, -r0.y + lrp r1.x, r0.z, r0.w, r0.x + lrp r2.x, c2.y, r1.x, r0.y + mov r0.w, c1.w + mad r0.x, r0.y, -c0.z, -r0.w + cmp r0.x, r0.x, c5.y, c5.z + mov r2.y, c5.x + texld r1, t1, s0 + texld r2, r2, s1 + mul r2.xyz, r2.w, r2 + mul r1, r1, r2 + mul r0, r0.x, r1 + mov oC0, r0 + +// approximately 36 instruction slots used (2 texture, 34 arithmetic) +ps_4_0 +dcl_constantbuffer cb0[5], immediateIndexed +dcl_sampler s0, mode_default +dcl_sampler s1, mode_default +dcl_resource_texture2d (float,float,float,float) t0 +dcl_resource_texture2d (float,float,float,float) t1 +dcl_input_ps linear v1.xy +dcl_input_ps linear v2.xy +dcl_output o0.xyzw +dcl_temps 3 +dp2 r0.x, v1.xyxx, cb0[3].xyxx +add r0.x, r0.x, cb0[3].z +dp2 r0.z, v1.xyxx, cb0[4].xyxx +add r0.y, r0.z, cb0[4].z +add r0.xy, r0.xyxx, -cb0[1].xyxx +dp2 r0.w, r0.xyxx, r0.xyxx +mad r0.w, -cb0[1].w, cb0[1].w, r0.w +mul r0.w, r0.w, l(0.500000) +mov r0.z, cb0[1].w +dp3 r0.x, r0.xyzx, cb0[0].xyzx +div r0.x, r0.w, r0.x +round_pi r0.y, r0.x +round_ni r0.z, r0.x +mul r0.w, r0.z, l(0.500000) +add r0.yz, -r0.xxzx, r0.yyxy +ge r1.x, r0.w, -r0.w +frc r0.w, |r0.w| +movc r0.w, r1.x, r0.w, -r0.w +add r0.w, r0.w, r0.w +mul r1.x, |r0.w|, cb0[2].z +mad r0.w, -|r0.w|, cb0[2].z, l(1.000000) +mul r0.y, r0.y, r1.x +mad r0.y, r0.z, r0.w, r0.y +mul r0.y, r0.y, cb0[2].y +add r0.z, l(1.000000), -cb0[2].y +mad r1.x, r0.x, r0.z, r0.y +mul r0.x, r0.x, cb0[0].z +ge r0.x, -cb0[1].w, r0.x +movc r0.x, r0.x, l(-0.000000), l(1.000000) +mov r1.y, l(0.500000) +sample r1.xyzw, r1.xyxx, t1.xyzw, s1 +mul r1.xyz, r1.wwww, r1.xyzx +sample r2.xyzw, v2.xyxx, t0.xyzw, s0 +mul r1.xyzw, r1.xyzw, r2.xyzw +mul o0.xyzw, r0.xxxx, r1.xyzw +ret +// Approximately 36 instruction slots used +#endif + +const BYTE SampleRadialGradientA0PS[] = { + 68, 88, 66, 67, 251, 98, 227, 203, 98, 180, 0, 199, 88, 100, 39, + 81, 223, 130, 11, 15, 1, 0, 0, 0, 136, 11, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, 212, 2, 0, 0, 172, 7, 0, 0, 40, + 8, 0, 0, 216, 10, 0, 0, 84, 11, 0, 0, 65, 111, 110, 57, + 148, 2, 0, 0, 148, 2, 0, 0, 0, 2, 255, 255, 92, 2, 0, + 0, 56, 0, 0, 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, 0, + 56, 0, 2, 0, 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, 1, + 1, 1, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, + 1, 2, 255, 255, 81, 0, 0, 5, 5, 0, 15, 160, 0, 0, 0, + 63, 0, 0, 0, 128, 0, 0, 128, 63, 0, 0, 0, 0, 31, 0, + 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, 0, 2, 0, + 0, 0, 128, 1, 0, 15, 176, 31, 0, 0, 2, 0, 0, 0, 144, + 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, 1, 8, 15, + 160, 90, 0, 0, 4, 0, 0, 1, 128, 0, 0, 228, 176, 3, 0, + 228, 160, 3, 0, 170, 160, 90, 0, 0, 4, 0, 0, 2, 128, 0, + 0, 228, 176, 4, 0, 228, 160, 4, 0, 170, 160, 2, 0, 0, 3, + 0, 0, 3, 128, 0, 0, 228, 128, 1, 0, 228, 161, 5, 0, 0, + 3, 0, 0, 8, 128, 1, 0, 255, 160, 1, 0, 255, 160, 90, 0, + 0, 4, 0, 0, 8, 128, 0, 0, 228, 128, 0, 0, 228, 128, 0, + 0, 255, 129, 5, 0, 0, 3, 0, 0, 8, 128, 0, 0, 255, 128, + 5, 0, 0, 160, 1, 0, 0, 2, 0, 0, 4, 128, 1, 0, 255, + 160, 8, 0, 0, 3, 0, 0, 1, 128, 0, 0, 228, 128, 0, 0, + 228, 160, 6, 0, 0, 2, 0, 0, 1, 128, 0, 0, 0, 128, 5, + 0, 0, 3, 0, 0, 2, 128, 0, 0, 0, 128, 0, 0, 255, 128, + 19, 0, 0, 2, 0, 0, 4, 128, 0, 0, 85, 128, 2, 0, 0, + 3, 0, 0, 4, 128, 0, 0, 170, 129, 0, 0, 85, 128, 5, 0, + 0, 3, 1, 0, 8, 128, 0, 0, 170, 128, 5, 0, 0, 160, 35, + 0, 0, 2, 1, 0, 1, 128, 1, 0, 255, 128, 19, 0, 0, 2, + 1, 0, 1, 128, 1, 0, 0, 128, 88, 0, 0, 4, 1, 0, 1, + 128, 0, 0, 170, 128, 1, 0, 0, 128, 1, 0, 0, 129, 4, 0, + 0, 4, 0, 0, 1, 128, 0, 0, 255, 128, 0, 0, 0, 128, 0, + 0, 170, 129, 2, 0, 0, 3, 0, 0, 4, 128, 1, 0, 0, 128, + 1, 0, 0, 128, 35, 0, 0, 2, 0, 0, 4, 128, 0, 0, 170, + 128, 5, 0, 0, 3, 0, 0, 4, 128, 0, 0, 170, 128, 2, 0, + 170, 160, 19, 0, 0, 2, 0, 0, 8, 128, 0, 0, 85, 129, 18, + 0, 0, 4, 1, 0, 1, 128, 0, 0, 170, 128, 0, 0, 255, 128, + 0, 0, 0, 128, 18, 0, 0, 4, 2, 0, 1, 128, 2, 0, 85, + 160, 1, 0, 0, 128, 0, 0, 85, 128, 1, 0, 0, 2, 0, 0, + 8, 128, 1, 0, 255, 160, 4, 0, 0, 4, 0, 0, 1, 128, 0, + 0, 85, 128, 0, 0, 170, 161, 0, 0, 255, 129, 88, 0, 0, 4, + 0, 0, 1, 128, 0, 0, 0, 128, 5, 0, 85, 160, 5, 0, 170, + 160, 1, 0, 0, 2, 2, 0, 2, 128, 5, 0, 0, 160, 66, 0, + 0, 3, 1, 0, 15, 128, 1, 0, 228, 176, 0, 8, 228, 160, 66, + 0, 0, 3, 2, 0, 15, 128, 2, 0, 228, 128, 1, 8, 228, 160, + 5, 0, 0, 3, 2, 0, 7, 128, 2, 0, 255, 128, 2, 0, 228, + 128, 5, 0, 0, 3, 1, 0, 15, 128, 1, 0, 228, 128, 2, 0, + 228, 128, 5, 0, 0, 3, 0, 0, 15, 128, 0, 0, 0, 128, 1, + 0, 228, 128, 1, 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, + 255, 255, 0, 0, 83, 72, 68, 82, 208, 4, 0, 0, 64, 0, 0, + 0, 52, 1, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, + 0, 0, 5, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0, + 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 1, 0, 0, 0, + 88, 24, 0, 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, + 0, 88, 24, 0, 4, 0, 112, 16, 0, 1, 0, 0, 0, 85, 85, + 0, 0, 98, 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 98, + 16, 0, 3, 50, 16, 16, 0, 2, 0, 0, 0, 101, 0, 0, 3, + 242, 32, 16, 0, 0, 0, 0, 0, 104, 0, 0, 2, 3, 0, 0, + 0, 15, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 70, 16, + 16, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 3, + 0, 0, 0, 0, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 15, 0, 0, 8, 66, 0, 16, 0, 0, 0, + 0, 0, 70, 16, 16, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0, + 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 8, 34, 0, 16, 0, + 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, + 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 9, 50, 0, + 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 70, + 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 15, 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, + 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 50, 0, + 0, 12, 130, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, 128, 65, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 58, 128, 32, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, + 0, 56, 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, 58, 0, + 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 63, 54, + 0, 0, 6, 66, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 16, 0, 0, 8, 18, 0, 16, + 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, 0, 70, 130, + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 7, 18, + 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 66, 0, 0, 5, 34, 0, 16, + 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 65, 0, + 0, 5, 66, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, + 0, 0, 0, 56, 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, + 42, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, + 63, 0, 0, 0, 8, 98, 0, 16, 0, 0, 0, 0, 0, 6, 2, + 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 86, 4, 16, 0, 0, + 0, 0, 0, 29, 0, 0, 8, 18, 0, 16, 0, 1, 0, 0, 0, + 58, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 128, 65, 0, 0, + 0, 0, 0, 0, 0, 26, 0, 0, 6, 130, 0, 16, 0, 0, 0, + 0, 0, 58, 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 55, + 0, 0, 10, 130, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, + 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, + 128, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 130, 0, + 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 58, + 0, 16, 0, 0, 0, 0, 0, 56, 0, 0, 9, 18, 0, 16, 0, + 1, 0, 0, 0, 58, 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, + 0, 42, 128, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 50, 0, + 0, 11, 130, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 128, 193, + 0, 0, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 56, 0, 0, + 7, 34, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 50, 0, 0, 9, 34, + 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, + 58, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, + 0, 56, 0, 0, 8, 34, 0, 16, 0, 0, 0, 0, 0, 26, 0, + 16, 0, 0, 0, 0, 0, 26, 128, 32, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 9, 66, 0, 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 0, 0, 128, 63, 26, 128, 32, 128, 65, 0, 0, + 0, 0, 0, 0, 0, 2, 0, 0, 0, 50, 0, 0, 9, 18, 0, + 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 42, + 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, + 56, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, + 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 29, 0, 0, 9, 18, 0, 16, 0, 0, 0, 0, 0, 58, + 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 55, 0, 0, 9, 18, 0, 16, + 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 0, 128, 1, 64, 0, 0, 0, 0, 128, 63, 54, + 0, 0, 5, 34, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, + 0, 0, 0, 63, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, + 0, 70, 0, 16, 0, 1, 0, 0, 0, 70, 126, 16, 0, 1, 0, + 0, 0, 0, 96, 16, 0, 1, 0, 0, 0, 56, 0, 0, 7, 114, + 0, 16, 0, 1, 0, 0, 0, 246, 15, 16, 0, 1, 0, 0, 0, + 70, 2, 16, 0, 1, 0, 0, 0, 69, 0, 0, 9, 242, 0, 16, + 0, 2, 0, 0, 0, 70, 16, 16, 0, 2, 0, 0, 0, 70, 126, + 16, 0, 0, 0, 0, 0, 0, 96, 16, 0, 0, 0, 0, 0, 56, + 0, 0, 7, 242, 0, 16, 0, 1, 0, 0, 0, 70, 14, 16, 0, + 1, 0, 0, 0, 70, 14, 16, 0, 2, 0, 0, 0, 56, 0, 0, + 7, 242, 32, 16, 0, 0, 0, 0, 0, 6, 0, 16, 0, 0, 0, + 0, 0, 70, 14, 16, 0, 1, 0, 0, 0, 62, 0, 0, 1, 83, + 84, 65, 84, 116, 0, 0, 0, 36, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, + 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 82, 68, 69, 70, 168, 2, 0, 0, 1, 0, 0, 0, + 16, 1, 0, 0, 5, 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, + 255, 0, 1, 0, 0, 116, 2, 0, 0, 188, 0, 0, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 201, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 217, 0, + 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, + 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 13, 0, 0, 0, + 230, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, + 0, 255, 255, 255, 255, 1, 0, 0, 0, 1, 0, 0, 0, 13, 0, + 0, 0, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 73, 110, 112, 117, 116, 83, 97, 109, 112, 108, 101, + 114, 0, 71, 114, 97, 100, 105, 101, 110, 116, 83, 97, 109, 112, 108, + 101, 114, 0, 73, 110, 112, 117, 116, 84, 101, 120, 116, 117, 114, 101, + 0, 71, 114, 97, 100, 105, 101, 110, 116, 84, 101, 120, 116, 117, 114, + 101, 0, 114, 97, 100, 105, 97, 108, 71, 114, 97, 100, 105, 101, 110, + 116, 67, 111, 110, 115, 116, 97, 110, 116, 115, 0, 171, 171, 246, 0, + 0, 0, 8, 0, 0, 0, 40, 1, 0, 0, 80, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 232, 1, 0, 0, 0, 0, 0, 0, + 12, 0, 0, 0, 2, 0, 0, 0, 240, 1, 0, 0, 0, 0, 0, + 0, 0, 2, 0, 0, 16, 0, 0, 0, 8, 0, 0, 0, 2, 0, + 0, 0, 8, 2, 0, 0, 0, 0, 0, 0, 24, 2, 0, 0, 24, + 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 28, 2, 0, 0, + 0, 0, 0, 0, 44, 2, 0, 0, 28, 0, 0, 0, 4, 0, 0, + 0, 2, 0, 0, 0, 28, 2, 0, 0, 0, 0, 0, 0, 52, 2, + 0, 0, 32, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 28, + 2, 0, 0, 0, 0, 0, 0, 63, 2, 0, 0, 36, 0, 0, 0, + 4, 0, 0, 0, 2, 0, 0, 0, 28, 2, 0, 0, 0, 0, 0, + 0, 78, 2, 0, 0, 40, 0, 0, 0, 4, 0, 0, 0, 2, 0, + 0, 0, 28, 2, 0, 0, 0, 0, 0, 0, 88, 2, 0, 0, 48, + 0, 0, 0, 28, 0, 0, 0, 2, 0, 0, 0, 100, 2, 0, 0, + 0, 0, 0, 0, 100, 105, 102, 102, 0, 171, 171, 171, 1, 0, 3, + 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 101, + 110, 116, 101, 114, 49, 0, 1, 0, 3, 0, 1, 0, 2, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 65, 0, 171, 171, 0, 0, 3, 0, + 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 97, 100, + 105, 117, 115, 49, 0, 115, 113, 95, 114, 97, 100, 105, 117, 115, 49, + 0, 114, 101, 112, 101, 97, 116, 95, 99, 111, 114, 114, 101, 99, 116, + 0, 97, 108, 108, 111, 119, 95, 111, 100, 100, 0, 116, 114, 97, 110, + 115, 102, 111, 114, 109, 0, 171, 171, 3, 0, 3, 0, 3, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 54, + 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, 51, 56, 52, 0, 171, + 171, 73, 83, 71, 78, 116, 0, 0, 0, 3, 0, 0, 0, 8, 0, + 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, + 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 92, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, + 0, 15, 3, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 15, 3, 0, 0, 83, + 86, 95, 80, 79, 83, 73, 84, 73, 79, 78, 0, 83, 67, 69, 78, + 69, 95, 80, 79, 83, 73, 84, 73, 79, 78, 0, 84, 69, 88, 67, + 79, 79, 82, 68, 0, 79, 83, 71, 78, 44, 0, 0, 0, 1, 0, + 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, + 83, 86, 95, 84, 97, 114, 103, 101, 116, 0, 171, 171}; +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 6.3.9600.16384 +// +// +// Buffer Definitions: +// +// cbuffer conicGradientConstants +// { +// +// float2 center; // Offset: 0 Size: 8 +// float angle; // Offset: 8 Size: 4 +// float start_offset; // Offset: 12 Size: 4 +// float end_offset; // Offset: 16 Size: 4 +// float repeat_correct_conic; // Offset: 20 Size: 4 [unused] +// float allow_odd_conic; // Offset: 24 Size: 4 [unused] +// float3x2 transform_conic; // Offset: 32 Size: 28 +// +// } +// +// +// Resource Bindings: +// +// Name Type Format Dim Slot Elements +// ------------------------------ ---------- ------- ----------- ---- -------- +// InputSampler sampler NA NA 0 1 +// GradientSampler sampler NA NA 1 1 +// InputTexture texture float4 2d 0 1 +// GradientTexture texture float4 2d 1 1 +// conicGradientConstants cbuffer NA NA 0 1 +// +// +// +// Input signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_POSITION 0 xyzw 0 POS float +// SCENE_POSITION 0 xyzw 1 NONE float xy +// TEXCOORD 0 xyzw 2 NONE float xy +// +// +// Output signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_Target 0 xyzw 0 TARGET float xyzw +// +// +// Constant buffer to DX9 shader constant mappings: +// +// Target Reg Buffer Start Reg # of Regs Data Conversion +// ---------- ------- --------- --------- ---------------------- +// c0 cb0 0 4 ( FLT, FLT, FLT, FLT) +// +// +// Sampler/Resource to DX9 shader sampler mappings: +// +// Target Sampler Source Sampler Source Resource +// -------------- --------------- ---------------- +// s0 s0 t0 +// s1 s1 t1 +// +// +// Level9 shader bytecode: +// + ps_2_x + def c4, 0.0208350997, -0.0851330012, 0.180141002, -0.330299497 + def c5, 0.999866009, 0, 1, 3.14159274 + def c6, -2, 1.57079637, 0.159154937, 0.5 + dcl t0 + dcl t1 + dcl_2d s0 + dcl_2d s1 + dp2add r0.w, t0, c2, c2.z + add r0.x, -r0.w, c0.x + dp2add r0.z, t0, c3, c3.z + add r0.z, -r0.z, c0.y + abs r0.yw, r0.xxzz + max r1.w, r0.y, r0.w + rcp r1.x, r1.w + min r1.y, r0.w, r0.y + add r0.y, -r0.y, r0.w + cmp r0.y, r0.y, c5.y, c5.z + mul r0.w, r1.x, r1.y + mul r1.x, r0.w, r0.w + mad r1.y, r1.x, c4.x, c4.y + mad r1.y, r1.x, r1.y, c4.z + mad r1.y, r1.x, r1.y, c4.w + mad r1.x, r1.x, r1.y, c5.x + mul r0.w, r0.w, r1.x + mad r1.x, r0.w, c6.x, c6.y + mad r0.y, r1.x, r0.y, r0.w + cmp r0.w, -r0.z, -c5.y, -c5.w + add r0.y, r0.w, r0.y + add r0.w, r0.y, r0.y + max r1.x, r0.x, -r0.z + min r1.y, -r0.z, r0.x + cmp r0.x, r1.x, c5.z, c5.y + cmp r0.x, r1.y, c5.y, r0.x + mad r0.x, r0.x, -r0.w, r0.y + add r0.x, r0.x, -c0.z + mov r0.w, c0.w + add r0.y, -r0.w, c1.x + rcp r0.y, r0.y + mul r0.x, r0.x, r0.y + mov r0.z, c6.z + mad r0.x, r0.x, r0.z, c1.x + add r0.x, r0.x, -c0.w + add r0.x, r0.x, c6.w + abs r0.y, r0.x + frc r0.y, r0.y + cmp r0.x, r0.x, r0.y, -r0.y + mov r0.y, c6.w + texld r1, t1, s0 + texld r0, r0, s1 + mul r0.xyz, r0.w, r0 + mul r0, r1, r0 + mov oC0, r0 + +// approximately 47 instruction slots used (2 texture, 45 arithmetic) +ps_4_0 +dcl_constantbuffer cb0[4], immediateIndexed +dcl_sampler s0, mode_default +dcl_sampler s1, mode_default +dcl_resource_texture2d (float,float,float,float) t0 +dcl_resource_texture2d (float,float,float,float) t1 +dcl_input_ps linear v1.xy +dcl_input_ps linear v2.xy +dcl_output o0.xyzw +dcl_temps 2 +dp2 r0.x, v1.xyxx, cb0[2].xyxx +add r0.x, r0.x, cb0[2].z +dp2 r0.y, v1.xyxx, cb0[3].xyxx +add r0.y, r0.y, cb0[3].z +add r0.xy, -r0.xyxx, cb0[0].xyxx +max r0.z, |r0.y|, |r0.x| +div r0.z, l(1.000000, 1.000000, 1.000000, 1.000000), r0.z +min r0.w, |r0.y|, |r0.x| +mul r0.z, r0.z, r0.w +mul r0.w, r0.z, r0.z +mad r1.x, r0.w, l(0.020835), l(-0.085133) +mad r1.x, r0.w, r1.x, l(0.180141) +mad r1.x, r0.w, r1.x, l(-0.330299) +mad r0.w, r0.w, r1.x, l(0.999866) +mul r1.x, r0.w, r0.z +mad r1.x, r1.x, l(-2.000000), l(1.570796) +lt r1.y, |r0.y|, |r0.x| +and r1.x, r1.y, r1.x +mad r0.z, r0.z, r0.w, r1.x +lt r0.w, -r0.y, r0.y +and r0.w, r0.w, l(0xc0490fdb) +add r0.z, r0.w, r0.z +min r0.w, -r0.y, r0.x +max r0.x, -r0.y, r0.x +ge r0.x, r0.x, -r0.x +lt r0.y, r0.w, -r0.w +and r0.x, r0.x, r0.y +movc r0.x, r0.x, -r0.z, r0.z +add r0.x, r0.x, -cb0[0].z +add r0.y, -cb0[0].w, cb0[1].x +div r0.y, l(1.000000, 1.000000, 1.000000, 1.000000), r0.y +mul r0.x, r0.x, r0.y +mad r0.x, r0.x, l(0.159155), cb0[1].x +add r0.x, r0.x, -cb0[0].w +add r0.x, r0.x, l(0.500000) +ge r0.y, r0.x, -r0.x +frc r0.x, |r0.x| +movc r0.x, r0.y, r0.x, -r0.x +mov r0.y, l(0.500000) +sample r0.xyzw, r0.xyxx, t1.xyzw, s1 +mul r0.xyz, r0.wwww, r0.xyzx +sample r1.xyzw, v2.xyxx, t0.xyzw, s0 +mul o0.xyzw, r0.xyzw, r1.xyzw +ret +// Approximately 44 instruction slots used +#endif + +const BYTE SampleConicGradientPS[] = { + 68, 88, 66, 67, 111, 210, 133, 71, 96, 114, 123, 208, 6, 154, 50, + 242, 194, 61, 177, 240, 1, 0, 0, 0, 184, 13, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, 224, 3, 0, 0, 240, 9, 0, 0, 108, + 10, 0, 0, 8, 13, 0, 0, 132, 13, 0, 0, 65, 111, 110, 57, + 160, 3, 0, 0, 160, 3, 0, 0, 0, 2, 255, 255, 104, 3, 0, + 0, 56, 0, 0, 0, 1, 0, 44, 0, 0, 0, 56, 0, 0, 0, + 56, 0, 2, 0, 36, 0, 0, 0, 56, 0, 0, 0, 0, 0, 1, + 1, 1, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, + 1, 2, 255, 255, 81, 0, 0, 5, 4, 0, 15, 160, 95, 174, 170, + 60, 54, 90, 174, 189, 226, 118, 56, 62, 4, 29, 169, 190, 81, 0, + 0, 5, 5, 0, 15, 160, 56, 247, 127, 63, 0, 0, 0, 0, 0, + 0, 128, 63, 219, 15, 73, 64, 81, 0, 0, 5, 6, 0, 15, 160, + 0, 0, 0, 192, 219, 15, 201, 63, 131, 249, 34, 62, 0, 0, 0, + 63, 31, 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 176, 31, 0, + 0, 2, 0, 0, 0, 128, 1, 0, 15, 176, 31, 0, 0, 2, 0, + 0, 0, 144, 0, 8, 15, 160, 31, 0, 0, 2, 0, 0, 0, 144, + 1, 8, 15, 160, 90, 0, 0, 4, 0, 0, 8, 128, 0, 0, 228, + 176, 2, 0, 228, 160, 2, 0, 170, 160, 2, 0, 0, 3, 0, 0, + 1, 128, 0, 0, 255, 129, 0, 0, 0, 160, 90, 0, 0, 4, 0, + 0, 4, 128, 0, 0, 228, 176, 3, 0, 228, 160, 3, 0, 170, 160, + 2, 0, 0, 3, 0, 0, 4, 128, 0, 0, 170, 129, 0, 0, 85, + 160, 35, 0, 0, 2, 0, 0, 10, 128, 0, 0, 160, 128, 11, 0, + 0, 3, 1, 0, 8, 128, 0, 0, 85, 128, 0, 0, 255, 128, 6, + 0, 0, 2, 1, 0, 1, 128, 1, 0, 255, 128, 10, 0, 0, 3, + 1, 0, 2, 128, 0, 0, 255, 128, 0, 0, 85, 128, 2, 0, 0, + 3, 0, 0, 2, 128, 0, 0, 85, 129, 0, 0, 255, 128, 88, 0, + 0, 4, 0, 0, 2, 128, 0, 0, 85, 128, 5, 0, 85, 160, 5, + 0, 170, 160, 5, 0, 0, 3, 0, 0, 8, 128, 1, 0, 0, 128, + 1, 0, 85, 128, 5, 0, 0, 3, 1, 0, 1, 128, 0, 0, 255, + 128, 0, 0, 255, 128, 4, 0, 0, 4, 1, 0, 2, 128, 1, 0, + 0, 128, 4, 0, 0, 160, 4, 0, 85, 160, 4, 0, 0, 4, 1, + 0, 2, 128, 1, 0, 0, 128, 1, 0, 85, 128, 4, 0, 170, 160, + 4, 0, 0, 4, 1, 0, 2, 128, 1, 0, 0, 128, 1, 0, 85, + 128, 4, 0, 255, 160, 4, 0, 0, 4, 1, 0, 1, 128, 1, 0, + 0, 128, 1, 0, 85, 128, 5, 0, 0, 160, 5, 0, 0, 3, 0, + 0, 8, 128, 0, 0, 255, 128, 1, 0, 0, 128, 4, 0, 0, 4, + 1, 0, 1, 128, 0, 0, 255, 128, 6, 0, 0, 160, 6, 0, 85, + 160, 4, 0, 0, 4, 0, 0, 2, 128, 1, 0, 0, 128, 0, 0, + 85, 128, 0, 0, 255, 128, 88, 0, 0, 4, 0, 0, 8, 128, 0, + 0, 170, 129, 5, 0, 85, 161, 5, 0, 255, 161, 2, 0, 0, 3, + 0, 0, 2, 128, 0, 0, 255, 128, 0, 0, 85, 128, 2, 0, 0, + 3, 0, 0, 8, 128, 0, 0, 85, 128, 0, 0, 85, 128, 11, 0, + 0, 3, 1, 0, 1, 128, 0, 0, 0, 128, 0, 0, 170, 129, 10, + 0, 0, 3, 1, 0, 2, 128, 0, 0, 170, 129, 0, 0, 0, 128, + 88, 0, 0, 4, 0, 0, 1, 128, 1, 0, 0, 128, 5, 0, 170, + 160, 5, 0, 85, 160, 88, 0, 0, 4, 0, 0, 1, 128, 1, 0, + 85, 128, 5, 0, 85, 160, 0, 0, 0, 128, 4, 0, 0, 4, 0, + 0, 1, 128, 0, 0, 0, 128, 0, 0, 255, 129, 0, 0, 85, 128, + 2, 0, 0, 3, 0, 0, 1, 128, 0, 0, 0, 128, 0, 0, 170, + 161, 1, 0, 0, 2, 0, 0, 8, 128, 0, 0, 255, 160, 2, 0, + 0, 3, 0, 0, 2, 128, 0, 0, 255, 129, 1, 0, 0, 160, 6, + 0, 0, 2, 0, 0, 2, 128, 0, 0, 85, 128, 5, 0, 0, 3, + 0, 0, 1, 128, 0, 0, 0, 128, 0, 0, 85, 128, 1, 0, 0, + 2, 0, 0, 4, 128, 6, 0, 170, 160, 4, 0, 0, 4, 0, 0, + 1, 128, 0, 0, 0, 128, 0, 0, 170, 128, 1, 0, 0, 160, 2, + 0, 0, 3, 0, 0, 1, 128, 0, 0, 0, 128, 0, 0, 255, 161, + 2, 0, 0, 3, 0, 0, 1, 128, 0, 0, 0, 128, 6, 0, 255, + 160, 35, 0, 0, 2, 0, 0, 2, 128, 0, 0, 0, 128, 19, 0, + 0, 2, 0, 0, 2, 128, 0, 0, 85, 128, 88, 0, 0, 4, 0, + 0, 1, 128, 0, 0, 0, 128, 0, 0, 85, 128, 0, 0, 85, 129, + 1, 0, 0, 2, 0, 0, 2, 128, 6, 0, 255, 160, 66, 0, 0, + 3, 1, 0, 15, 128, 1, 0, 228, 176, 0, 8, 228, 160, 66, 0, + 0, 3, 0, 0, 15, 128, 0, 0, 228, 128, 1, 8, 228, 160, 5, + 0, 0, 3, 0, 0, 7, 128, 0, 0, 255, 128, 0, 0, 228, 128, + 5, 0, 0, 3, 0, 0, 15, 128, 1, 0, 228, 128, 0, 0, 228, + 128, 1, 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, + 0, 0, 83, 72, 68, 82, 8, 6, 0, 0, 64, 0, 0, 0, 130, + 1, 0, 0, 89, 0, 0, 4, 70, 142, 32, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 90, 0, 0, 3, 0, 96, 16, 0, 0, 0, 0, + 0, 90, 0, 0, 3, 0, 96, 16, 0, 1, 0, 0, 0, 88, 24, + 0, 4, 0, 112, 16, 0, 0, 0, 0, 0, 85, 85, 0, 0, 88, + 24, 0, 4, 0, 112, 16, 0, 1, 0, 0, 0, 85, 85, 0, 0, + 98, 16, 0, 3, 50, 16, 16, 0, 1, 0, 0, 0, 98, 16, 0, + 3, 50, 16, 16, 0, 2, 0, 0, 0, 101, 0, 0, 3, 242, 32, + 16, 0, 0, 0, 0, 0, 104, 0, 0, 2, 2, 0, 0, 0, 15, + 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 70, 16, 16, 0, + 1, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, 0, 2, 0, 0, + 0, 0, 0, 0, 8, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 15, 0, 0, 8, 34, 0, 16, 0, 0, 0, 0, 0, + 70, 16, 16, 0, 1, 0, 0, 0, 70, 128, 32, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 0, 0, 0, 8, 34, 0, 16, 0, 0, 0, + 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 42, 128, 32, 0, 0, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 9, 50, 0, 16, 0, + 0, 0, 0, 0, 70, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, + 0, 70, 128, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, + 0, 9, 66, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 129, + 0, 0, 0, 0, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0, 0, + 0, 0, 0, 0, 14, 0, 0, 10, 66, 0, 16, 0, 0, 0, 0, + 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, + 128, 63, 0, 0, 128, 63, 42, 0, 16, 0, 0, 0, 0, 0, 51, + 0, 0, 9, 130, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, + 129, 0, 0, 0, 0, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0, + 0, 0, 0, 0, 0, 56, 0, 0, 7, 66, 0, 16, 0, 0, 0, + 0, 0, 42, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, + 0, 0, 0, 56, 0, 0, 7, 130, 0, 16, 0, 0, 0, 0, 0, + 42, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, 0, 0, 0, + 0, 50, 0, 0, 9, 18, 0, 16, 0, 1, 0, 0, 0, 58, 0, + 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 95, 174, 170, 60, 1, + 64, 0, 0, 54, 90, 174, 189, 50, 0, 0, 9, 18, 0, 16, 0, + 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, + 0, 1, 0, 0, 0, 1, 64, 0, 0, 226, 118, 56, 62, 50, 0, + 0, 9, 18, 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0, + 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 1, 64, 0, 0, + 4, 29, 169, 190, 50, 0, 0, 9, 130, 0, 16, 0, 0, 0, 0, + 0, 58, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 1, 0, + 0, 0, 1, 64, 0, 0, 56, 247, 127, 63, 56, 0, 0, 7, 18, + 0, 16, 0, 1, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, + 42, 0, 16, 0, 0, 0, 0, 0, 50, 0, 0, 9, 18, 0, 16, + 0, 1, 0, 0, 0, 10, 0, 16, 0, 1, 0, 0, 0, 1, 64, + 0, 0, 0, 0, 0, 192, 1, 64, 0, 0, 219, 15, 201, 63, 49, + 0, 0, 9, 34, 0, 16, 0, 1, 0, 0, 0, 26, 0, 16, 128, + 129, 0, 0, 0, 0, 0, 0, 0, 10, 0, 16, 128, 129, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 7, 18, 0, 16, 0, 1, 0, + 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 10, 0, 16, 0, 1, + 0, 0, 0, 50, 0, 0, 9, 66, 0, 16, 0, 0, 0, 0, 0, + 42, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, + 0, 10, 0, 16, 0, 1, 0, 0, 0, 49, 0, 0, 8, 130, 0, + 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 0, + 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 1, 0, 0, 7, + 130, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, + 0, 1, 64, 0, 0, 219, 15, 73, 192, 0, 0, 0, 7, 66, 0, + 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, 0, 0, 0, 42, + 0, 16, 0, 0, 0, 0, 0, 51, 0, 0, 8, 130, 0, 16, 0, + 0, 0, 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, + 0, 10, 0, 16, 0, 0, 0, 0, 0, 52, 0, 0, 8, 18, 0, + 16, 0, 0, 0, 0, 0, 26, 0, 16, 128, 65, 0, 0, 0, 0, + 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 29, 0, 0, 8, + 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, + 0, 10, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, 49, 0, + 0, 8, 34, 0, 16, 0, 0, 0, 0, 0, 58, 0, 16, 0, 0, + 0, 0, 0, 58, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, + 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, 55, 0, + 0, 10, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, + 0, 0, 0, 42, 0, 16, 128, 65, 0, 0, 0, 0, 0, 0, 0, + 42, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 9, 18, 0, 16, + 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 42, 128, + 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 10, 34, 0, 16, 0, 0, 0, 0, 0, 58, 128, 32, 128, + 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 128, 32, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 14, 0, 0, 10, 34, 0, + 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 128, 63, 0, + 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 26, 0, 16, 0, + 0, 0, 0, 0, 56, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, + 0, 10, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, + 0, 0, 50, 0, 0, 10, 18, 0, 16, 0, 0, 0, 0, 0, 10, + 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 131, 249, 34, 62, + 10, 128, 32, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 9, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, + 0, 0, 58, 128, 32, 128, 65, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, + 63, 29, 0, 0, 8, 34, 0, 16, 0, 0, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, 10, 0, 16, 128, 65, 0, 0, 0, 0, + 0, 0, 0, 26, 0, 0, 6, 18, 0, 16, 0, 0, 0, 0, 0, + 10, 0, 16, 128, 129, 0, 0, 0, 0, 0, 0, 0, 55, 0, 0, + 10, 18, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, 0, 0, + 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 128, 65, + 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 5, 34, 0, 16, 0, + 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 63, 69, 0, 0, + 9, 242, 0, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, + 0, 0, 70, 126, 16, 0, 1, 0, 0, 0, 0, 96, 16, 0, 1, + 0, 0, 0, 56, 0, 0, 7, 114, 0, 16, 0, 0, 0, 0, 0, + 246, 15, 16, 0, 0, 0, 0, 0, 70, 2, 16, 0, 0, 0, 0, + 0, 69, 0, 0, 9, 242, 0, 16, 0, 1, 0, 0, 0, 70, 16, + 16, 0, 2, 0, 0, 0, 70, 126, 16, 0, 0, 0, 0, 0, 0, + 96, 16, 0, 0, 0, 0, 0, 56, 0, 0, 7, 242, 32, 16, 0, + 0, 0, 0, 0, 70, 14, 16, 0, 0, 0, 0, 0, 70, 14, 16, + 0, 1, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 116, 0, + 0, 0, 44, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, + 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 68, + 69, 70, 148, 2, 0, 0, 1, 0, 0, 0, 16, 1, 0, 0, 5, + 0, 0, 0, 28, 0, 0, 0, 0, 4, 255, 255, 0, 1, 0, 0, + 96, 2, 0, 0, 188, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 201, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, 217, 0, 0, 0, 2, 0, 0, + 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, 0, 0, + 0, 0, 1, 0, 0, 0, 13, 0, 0, 0, 230, 0, 0, 0, 2, + 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0, 255, 255, 255, 255, + 1, 0, 0, 0, 1, 0, 0, 0, 13, 0, 0, 0, 246, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 73, + 110, 112, 117, 116, 83, 97, 109, 112, 108, 101, 114, 0, 71, 114, 97, + 100, 105, 101, 110, 116, 83, 97, 109, 112, 108, 101, 114, 0, 73, 110, + 112, 117, 116, 84, 101, 120, 116, 117, 114, 101, 0, 71, 114, 97, 100, + 105, 101, 110, 116, 84, 101, 120, 116, 117, 114, 101, 0, 99, 111, 110, + 105, 99, 71, 114, 97, 100, 105, 101, 110, 116, 67, 111, 110, 115, 116, + 97, 110, 116, 115, 0, 171, 171, 171, 246, 0, 0, 0, 7, 0, 0, + 0, 40, 1, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 208, 1, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 2, + 0, 0, 0, 216, 1, 0, 0, 0, 0, 0, 0, 232, 1, 0, 0, + 8, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 240, 1, 0, + 0, 0, 0, 0, 0, 0, 2, 0, 0, 12, 0, 0, 0, 4, 0, + 0, 0, 2, 0, 0, 0, 240, 1, 0, 0, 0, 0, 0, 0, 13, + 2, 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, + 240, 1, 0, 0, 0, 0, 0, 0, 24, 2, 0, 0, 20, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 240, 1, 0, 0, 0, 0, + 0, 0, 45, 2, 0, 0, 24, 0, 0, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 240, 1, 0, 0, 0, 0, 0, 0, 61, 2, 0, 0, + 32, 0, 0, 0, 28, 0, 0, 0, 2, 0, 0, 0, 80, 2, 0, + 0, 0, 0, 0, 0, 99, 101, 110, 116, 101, 114, 0, 171, 1, 0, + 3, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 97, + 110, 103, 108, 101, 0, 171, 171, 0, 0, 3, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 115, 116, 97, 114, 116, 95, 111, + 102, 102, 115, 101, 116, 0, 101, 110, 100, 95, 111, 102, 102, 115, 101, + 116, 0, 114, 101, 112, 101, 97, 116, 95, 99, 111, 114, 114, 101, 99, + 116, 95, 99, 111, 110, 105, 99, 0, 97, 108, 108, 111, 119, 95, 111, + 100, 100, 95, 99, 111, 110, 105, 99, 0, 116, 114, 97, 110, 115, 102, + 111, 114, 109, 95, 99, 111, 110, 105, 99, 0, 171, 171, 171, 3, 0, + 3, 0, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, + 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, + 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, + 108, 101, 114, 32, 54, 46, 51, 46, 57, 54, 48, 48, 46, 49, 54, + 51, 56, 52, 0, 171, 171, 73, 83, 71, 78, 116, 0, 0, 0, 3, + 0, 0, 0, 8, 0, 0, 0, 80, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, + 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 0, 0, 15, 3, 0, 0, 107, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, + 15, 3, 0, 0, 83, 86, 95, 80, 79, 83, 73, 84, 73, 79, 78, + 0, 83, 67, 69, 78, 69, 95, 80, 79, 83, 73, 84, 73, 79, 78, + 0, 84, 69, 88, 67, 79, 79, 82, 68, 0, 79, 83, 71, 78, 44, + 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, + 0, 15, 0, 0, 0, 83, 86, 95, 84, 97, 114, 103, 101, 116, 0, + 171, 171}; diff --git a/gfx/2d/ShadersD2D1.hlsl b/gfx/2d/ShadersD2D1.hlsl new file mode 100644 index 0000000000..163b6b388f --- /dev/null +++ b/gfx/2d/ShadersD2D1.hlsl @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Texture2D InputTexture : register(t0); +SamplerState InputSampler : register(s0); +Texture2D GradientTexture : register(t1); +SamplerState GradientSampler : register(s1); + +cbuffer radialGradientConstants : register(b0) +{ + // Precalculate as much as we can! + float3 diff : packoffset(c0.x); + float2 center1 : packoffset(c1.x); + float A : packoffset(c1.z); + float radius1 : packoffset(c1.w); + float sq_radius1 : packoffset(c2.x); + + // The next two values are used for a hack to compensate for an apparent + // bug in D2D where the GradientSampler SamplerState doesn't get the + // correct addressing modes. + float repeat_correct : packoffset(c2.y); + float allow_odd : packoffset(c2.z); + + float3x2 transform : packoffset(c3.x); +} + +cbuffer conicGradientConstants : register(b0) +{ + float2 center : packoffset(c0.x); + float angle : packoffset(c0.z); + float start_offset : packoffset(c0.w); + float end_offset : packoffset(c1.x); + + // The next two values are used for a hack to compensate for an apparent + // bug in D2D where the GradientSampler SamplerState doesn't get the + // correct addressing modes. + float repeat_correct_conic : packoffset(c1.y); + float allow_odd_conic : packoffset(c1.z); + + float3x2 transform_conic : packoffset(c2.x); +} + + +static const float M_PI = 3.14159265f; + +float4 SampleConicGradientPS( + float4 clipSpaceOutput : SV_POSITION, + float4 sceneSpaceOutput : SCENE_POSITION, + float4 texelSpaceInput0 : TEXCOORD0 + ) : SV_Target +{ + float2 p = float2(sceneSpaceOutput.x * transform_conic._11 + sceneSpaceOutput.y * transform_conic._21 + transform_conic._31, + sceneSpaceOutput.x * transform_conic._12 + sceneSpaceOutput.y * transform_conic._22 + transform_conic._32); + float2 dir = float2( + -(center.y - p.y), + (center.x - p.x)); + float vstart = start_offset; + float vend = end_offset; + float n = 1/(vend-vstart); + float current_angle = atan2(dir.y, dir.x)-angle; + float lambda = fmod(n*current_angle/M_PI/2+vend-vstart+.5,1); + float offset = lambda; + float4 output = GradientTexture.Sample(GradientSampler, float2(offset, 0.5)); + // Premultiply + output.rgb *= output.a; + // Multiply the output color by the input mask for the operation. + output *= InputTexture.Sample(InputSampler, texelSpaceInput0.xy); + + return output; +}; + +float4 SampleRadialGradientPS( + float4 clipSpaceOutput : SV_POSITION, + float4 sceneSpaceOutput : SCENE_POSITION, + float4 texelSpaceInput0 : TEXCOORD0 + ) : SV_Target +{ + // Radial gradient painting is defined as the set of circles whose centers + // are described by C(t) = (C2 - C1) * t + C1; with radii + // R(t) = (R2 - R1) * t + R1; for R(t) > 0. This shader solves the + // quadratic equation that arises when calculating t for pixel (x, y). + // + // A more extensive derrivation can be found in the pixman radial gradient + // code. + + float2 p = float2(sceneSpaceOutput.x * transform._11 + sceneSpaceOutput.y * transform._21 + transform._31, + sceneSpaceOutput.x * transform._12 + sceneSpaceOutput.y * transform._22 + transform._32); + float3 dp = float3(p - center1, radius1); + + // dpx * dcx + dpy * dcy + r * dr + float B = dot(dp, diff); + + float C = pow(dp.x, 2) + pow(dp.y, 2) - sq_radius1; + + float det = pow(B, 2) - A * C; + + float sqrt_det = sqrt(abs(det)); + + float2 t = (B + float2(sqrt_det, -sqrt_det)) / A; + + float2 isValid = step(float2(-radius1, -radius1), t * diff.z); + + float upper_t = lerp(t.y, t.x, isValid.x); + + // Addressing mode bug work-around.. first let's see if we should consider odd repetitions separately. + float oddeven = abs(fmod(floor(upper_t), 2)) * allow_odd; + + // Now let's calculate even or odd addressing in a branchless manner. + float upper_t_repeated = ((upper_t - floor(upper_t)) * (1.0f - oddeven)) + ((ceil(upper_t) - upper_t) * oddeven); + + float4 output = GradientTexture.Sample(GradientSampler, float2(upper_t * (1.0f - repeat_correct) + upper_t_repeated * repeat_correct, 0.5)); + // Premultiply + output.rgb *= output.a; + // Multiply the output color by the input mask for the operation. + output *= InputTexture.Sample(InputSampler, texelSpaceInput0.xy); + + // In order to compile for PS_4_0_level_9_3 we need to be branchless. + // This is essentially returning nothing, i.e. bailing early if: + // det < 0 || max(isValid.x, isValid.y) <= 0 + return output * abs(step(max(isValid.x, isValid.y), 0) - 1.0f) * step(0, det); +}; + +float4 SampleRadialGradientA0PS( + float4 clipSpaceOutput : SV_POSITION, + float4 sceneSpaceOutput : SCENE_POSITION, + float4 texelSpaceInput0 : TEXCOORD0 + ) : SV_Target +{ + // This simpler shader is used for the degenerate case where A is 0, + // i.e. we're actually solving a linear equation. + + float2 p = float2(sceneSpaceOutput.x * transform._11 + sceneSpaceOutput.y * transform._21 + transform._31, + sceneSpaceOutput.x * transform._12 + sceneSpaceOutput.y * transform._22 + transform._32); + float3 dp = float3(p - center1, radius1); + + // dpx * dcx + dpy * dcy + r * dr + float B = dot(dp, diff); + + float C = pow(dp.x, 2) + pow(dp.y, 2) - pow(radius1, 2); + + float t = 0.5 * C / B; + + // Addressing mode bug work-around.. first let's see if we should consider odd repetitions separately. + float oddeven = abs(fmod(floor(t), 2)) * allow_odd; + + // Now let's calculate even or odd addressing in a branchless manner. + float t_repeated = ((t - floor(t)) * (1.0f - oddeven)) + ((ceil(t) - t) * oddeven); + + float4 output = GradientTexture.Sample(GradientSampler, float2(t * (1.0f - repeat_correct) + t_repeated * repeat_correct, 0.5)); + // Premultiply + output.rgb *= output.a; + // Multiply the output color by the input mask for the operation. + output *= InputTexture.Sample(InputSampler, texelSpaceInput0.xy); + + // In order to compile for PS_4_0_level_9_3 we need to be branchless. + // This is essentially returning nothing, i.e. bailing early if: + // -radius1 >= t * diff.z + return output * abs(step(t * diff.z, -radius1) - 1.0f); +}; + diff --git a/gfx/2d/SkConvolver.cpp b/gfx/2d/SkConvolver.cpp new file mode 100644 index 0000000000..befe8da30b --- /dev/null +++ b/gfx/2d/SkConvolver.cpp @@ -0,0 +1,559 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2011-2016 Google Inc. +// Use of this source code is governed by a BSD-style license that can be +// found in the gfx/skia/LICENSE file. + +#include "SkConvolver.h" +#include "mozilla/Vector.h" + +#ifdef USE_SSE2 +# include "mozilla/SSE.h" +#endif + +#ifdef USE_NEON +# include "mozilla/arm.h" +#endif + +namespace skia { + +// Converts the argument to an 8-bit unsigned value by clamping to the range +// 0-255. +static inline unsigned char ClampTo8(int a) { + if (static_cast<unsigned>(a) < 256) { + return a; // Avoid the extra check in the common case. + } + if (a < 0) { + return 0; + } + return 255; +} + +// Convolves horizontally along a single row. The row data is given in +// |srcData| and continues for the numValues() of the filter. +template <bool hasAlpha> +void ConvolveHorizontally(const unsigned char* srcData, + const SkConvolutionFilter1D& filter, + unsigned char* outRow) { + // Loop over each pixel on this row in the output image. + int numValues = filter.numValues(); + for (int outX = 0; outX < numValues; outX++) { + // Get the filter that determines the current output pixel. + int filterOffset, filterLength; + const SkConvolutionFilter1D::ConvolutionFixed* filterValues = + filter.FilterForValue(outX, &filterOffset, &filterLength); + + // Compute the first pixel in this row that the filter affects. It will + // touch |filterLength| pixels (4 bytes each) after this. + const unsigned char* rowToFilter = &srcData[filterOffset * 4]; + + // Apply the filter to the row to get the destination pixel in |accum|. + int accum[4] = {0}; + for (int filterX = 0; filterX < filterLength; filterX++) { + SkConvolutionFilter1D::ConvolutionFixed curFilter = filterValues[filterX]; + accum[0] += curFilter * rowToFilter[filterX * 4 + 0]; + accum[1] += curFilter * rowToFilter[filterX * 4 + 1]; + accum[2] += curFilter * rowToFilter[filterX * 4 + 2]; + if (hasAlpha) { + accum[3] += curFilter * rowToFilter[filterX * 4 + 3]; + } + } + + // Bring this value back in range. All of the filter scaling factors + // are in fixed point with kShiftBits bits of fractional part. + accum[0] >>= SkConvolutionFilter1D::kShiftBits; + accum[1] >>= SkConvolutionFilter1D::kShiftBits; + accum[2] >>= SkConvolutionFilter1D::kShiftBits; + + if (hasAlpha) { + accum[3] >>= SkConvolutionFilter1D::kShiftBits; + } + + // Store the new pixel. + outRow[outX * 4 + 0] = ClampTo8(accum[0]); + outRow[outX * 4 + 1] = ClampTo8(accum[1]); + outRow[outX * 4 + 2] = ClampTo8(accum[2]); + if (hasAlpha) { + outRow[outX * 4 + 3] = ClampTo8(accum[3]); + } + } +} + +// Does vertical convolution to produce one output row. The filter values and +// length are given in the first two parameters. These are applied to each +// of the rows pointed to in the |sourceDataRows| array, with each row +// being |pixelWidth| wide. +// +// The output must have room for |pixelWidth * 4| bytes. +template <bool hasAlpha> +void ConvolveVertically( + const SkConvolutionFilter1D::ConvolutionFixed* filterValues, + int filterLength, unsigned char* const* sourceDataRows, int pixelWidth, + unsigned char* outRow) { + // We go through each column in the output and do a vertical convolution, + // generating one output pixel each time. + for (int outX = 0; outX < pixelWidth; outX++) { + // Compute the number of bytes over in each row that the current column + // we're convolving starts at. The pixel will cover the next 4 bytes. + int byteOffset = outX * 4; + + // Apply the filter to one column of pixels. + int accum[4] = {0}; + for (int filterY = 0; filterY < filterLength; filterY++) { + SkConvolutionFilter1D::ConvolutionFixed curFilter = filterValues[filterY]; + accum[0] += curFilter * sourceDataRows[filterY][byteOffset + 0]; + accum[1] += curFilter * sourceDataRows[filterY][byteOffset + 1]; + accum[2] += curFilter * sourceDataRows[filterY][byteOffset + 2]; + if (hasAlpha) { + accum[3] += curFilter * sourceDataRows[filterY][byteOffset + 3]; + } + } + + // Bring this value back in range. All of the filter scaling factors + // are in fixed point with kShiftBits bits of precision. + accum[0] >>= SkConvolutionFilter1D::kShiftBits; + accum[1] >>= SkConvolutionFilter1D::kShiftBits; + accum[2] >>= SkConvolutionFilter1D::kShiftBits; + if (hasAlpha) { + accum[3] >>= SkConvolutionFilter1D::kShiftBits; + } + + // Store the new pixel. + outRow[byteOffset + 0] = ClampTo8(accum[0]); + outRow[byteOffset + 1] = ClampTo8(accum[1]); + outRow[byteOffset + 2] = ClampTo8(accum[2]); + + if (hasAlpha) { + unsigned char alpha = ClampTo8(accum[3]); + + // Make sure the alpha channel doesn't come out smaller than any of the + // color channels. We use premultipled alpha channels, so this should + // never happen, but rounding errors will cause this from time to time. + // These "impossible" colors will cause overflows (and hence random pixel + // values) when the resulting bitmap is drawn to the screen. + // + // We only need to do this when generating the final output row (here). + int maxColorChannel = + std::max(outRow[byteOffset + 0], + std::max(outRow[byteOffset + 1], outRow[byteOffset + 2])); + if (alpha < maxColorChannel) { + outRow[byteOffset + 3] = maxColorChannel; + } else { + outRow[byteOffset + 3] = alpha; + } + } else { + // No alpha channel, the image is opaque. + outRow[byteOffset + 3] = 0xff; + } + } +} + +#ifdef USE_SSE2 +void convolve_vertically_avx2(const int16_t* filter, int filterLen, + uint8_t* const* srcRows, int width, uint8_t* out, + bool hasAlpha); +void convolve_horizontally_sse2(const unsigned char* srcData, + const SkConvolutionFilter1D& filter, + unsigned char* outRow, bool hasAlpha); +void convolve_vertically_sse2(const int16_t* filter, int filterLen, + uint8_t* const* srcRows, int width, uint8_t* out, + bool hasAlpha); +#elif defined(USE_NEON) +void convolve_horizontally_neon(const unsigned char* srcData, + const SkConvolutionFilter1D& filter, + unsigned char* outRow, bool hasAlpha); +void convolve_vertically_neon(const int16_t* filter, int filterLen, + uint8_t* const* srcRows, int width, uint8_t* out, + bool hasAlpha); +#endif + +void convolve_horizontally(const unsigned char* srcData, + const SkConvolutionFilter1D& filter, + unsigned char* outRow, bool hasAlpha) { +#ifdef USE_SSE2 + if (mozilla::supports_sse2()) { + convolve_horizontally_sse2(srcData, filter, outRow, hasAlpha); + return; + } +#elif defined(USE_NEON) + if (mozilla::supports_neon()) { + convolve_horizontally_neon(srcData, filter, outRow, hasAlpha); + return; + } +#endif + if (hasAlpha) { + ConvolveHorizontally<true>(srcData, filter, outRow); + } else { + ConvolveHorizontally<false>(srcData, filter, outRow); + } +} + +void convolve_vertically( + const SkConvolutionFilter1D::ConvolutionFixed* filterValues, + int filterLength, unsigned char* const* sourceDataRows, int pixelWidth, + unsigned char* outRow, bool hasAlpha) { +#ifdef USE_SSE2 + if (mozilla::supports_avx2()) { + convolve_vertically_avx2(filterValues, filterLength, sourceDataRows, + pixelWidth, outRow, hasAlpha); + return; + } + if (mozilla::supports_sse2()) { + convolve_vertically_sse2(filterValues, filterLength, sourceDataRows, + pixelWidth, outRow, hasAlpha); + return; + } +#elif defined(USE_NEON) + if (mozilla::supports_neon()) { + convolve_vertically_neon(filterValues, filterLength, sourceDataRows, + pixelWidth, outRow, hasAlpha); + return; + } +#endif + if (hasAlpha) { + ConvolveVertically<true>(filterValues, filterLength, sourceDataRows, + pixelWidth, outRow); + } else { + ConvolveVertically<false>(filterValues, filterLength, sourceDataRows, + pixelWidth, outRow); + } +} + +// Stores a list of rows in a circular buffer. The usage is you write into it +// by calling AdvanceRow. It will keep track of which row in the buffer it +// should use next, and the total number of rows added. +class CircularRowBuffer { + public: + // The number of pixels in each row is given in |sourceRowPixelWidth|. + // The maximum number of rows needed in the buffer is |maxYFilterSize| + // (we only need to store enough rows for the biggest filter). + // + // We use the |firstInputRow| to compute the coordinates of all of the + // following rows returned by Advance(). + CircularRowBuffer(int destRowPixelWidth, int maxYFilterSize, + int firstInputRow) + : fRowByteWidth(destRowPixelWidth * 4), + fNumRows(maxYFilterSize), + fNextRow(0), + fNextRowCoordinate(firstInputRow) { + fBuffer.resize(fRowByteWidth * maxYFilterSize); + fRowAddresses.resize(fNumRows); + } + + // Moves to the next row in the buffer, returning a pointer to the beginning + // of it. + unsigned char* advanceRow() { + unsigned char* row = &fBuffer[fNextRow * fRowByteWidth]; + fNextRowCoordinate++; + + // Set the pointer to the next row to use, wrapping around if necessary. + fNextRow++; + if (fNextRow == fNumRows) { + fNextRow = 0; + } + return row; + } + + // Returns a pointer to an "unrolled" array of rows. These rows will start + // at the y coordinate placed into |*firstRowIndex| and will continue in + // order for the maximum number of rows in this circular buffer. + // + // The |firstRowIndex_| may be negative. This means the circular buffer + // starts before the top of the image (it hasn't been filled yet). + unsigned char* const* GetRowAddresses(int* firstRowIndex) { + // Example for a 4-element circular buffer holding coords 6-9. + // Row 0 Coord 8 + // Row 1 Coord 9 + // Row 2 Coord 6 <- fNextRow = 2, fNextRowCoordinate = 10. + // Row 3 Coord 7 + // + // The "next" row is also the first (lowest) coordinate. This computation + // may yield a negative value, but that's OK, the math will work out + // since the user of this buffer will compute the offset relative + // to the firstRowIndex and the negative rows will never be used. + *firstRowIndex = fNextRowCoordinate - fNumRows; + + int curRow = fNextRow; + for (int i = 0; i < fNumRows; i++) { + fRowAddresses[i] = &fBuffer[curRow * fRowByteWidth]; + + // Advance to the next row, wrapping if necessary. + curRow++; + if (curRow == fNumRows) { + curRow = 0; + } + } + return &fRowAddresses[0]; + } + + private: + // The buffer storing the rows. They are packed, each one fRowByteWidth. + std::vector<unsigned char> fBuffer; + + // Number of bytes per row in the |buffer|. + int fRowByteWidth; + + // The number of rows available in the buffer. + int fNumRows; + + // The next row index we should write into. This wraps around as the + // circular buffer is used. + int fNextRow; + + // The y coordinate of the |fNextRow|. This is incremented each time a + // new row is appended and does not wrap. + int fNextRowCoordinate; + + // Buffer used by GetRowAddresses(). + std::vector<unsigned char*> fRowAddresses; +}; + +SkConvolutionFilter1D::SkConvolutionFilter1D() : fMaxFilter(0) {} + +SkConvolutionFilter1D::~SkConvolutionFilter1D() = default; + +void SkConvolutionFilter1D::AddFilter(int filterOffset, + const ConvolutionFixed* filterValues, + int filterLength) { + // It is common for leading/trailing filter values to be zeros. In such + // cases it is beneficial to only store the central factors. + // For a scaling to 1/4th in each dimension using a Lanczos-2 filter on + // a 1080p image this optimization gives a ~10% speed improvement. + int filterSize = filterLength; + int firstNonZero = 0; + while (firstNonZero < filterLength && filterValues[firstNonZero] == 0) { + firstNonZero++; + } + + if (firstNonZero < filterLength) { + // Here we have at least one non-zero factor. + int lastNonZero = filterLength - 1; + while (lastNonZero >= 0 && filterValues[lastNonZero] == 0) { + lastNonZero--; + } + + filterOffset += firstNonZero; + filterLength = lastNonZero + 1 - firstNonZero; + MOZ_ASSERT(filterLength > 0); + + fFilterValues.insert(fFilterValues.end(), &filterValues[firstNonZero], + &filterValues[lastNonZero + 1]); + } else { + // Here all the factors were zeroes. + filterLength = 0; + } + + FilterInstance instance = { + // We pushed filterLength elements onto fFilterValues + int(fFilterValues.size()) - filterLength, filterOffset, filterLength, + filterSize}; + fFilters.push_back(instance); + + fMaxFilter = std::max(fMaxFilter, filterLength); +} + +bool SkConvolutionFilter1D::ComputeFilterValues( + const SkBitmapFilter& aBitmapFilter, int32_t aSrcSize, int32_t aDstSize) { + // When we're doing a magnification, the scale will be larger than one. This + // means the destination pixels are much smaller than the source pixels, and + // that the range covered by the filter won't necessarily cover any source + // pixel boundaries. Therefore, we use these clamped values (max of 1) for + // some computations. + float scale = float(aDstSize) / float(aSrcSize); + float clampedScale = std::min(1.0f, scale); + // This is how many source pixels from the center we need to count + // to support the filtering function. + float srcSupport = aBitmapFilter.width() / clampedScale; + float invScale = 1.0f / scale; + + mozilla::Vector<float, 64> filterValues; + mozilla::Vector<ConvolutionFixed, 64> fixedFilterValues; + + // Loop over all pixels in the output range. We will generate one set of + // filter values for each one. Those values will tell us how to blend the + // source pixels to compute the destination pixel. + + // This value is computed based on how SkTDArray::resizeStorageToAtLeast works + // in order to ensure that it does not overflow or assert. That functions + // computes + // n+4 + (n+4)/4 + // and we want to to fit in a 32 bit signed int. Equating that to 2^31-1 and + // solving n gives n = (2^31-6)*4/5 = 1717986913.6 + const int32_t maxToPassToReserveAdditional = 1717986913; + + int32_t filterValueCount = int32_t(ceilf(aDstSize * srcSupport * 2)); + if (aDstSize > maxToPassToReserveAdditional || filterValueCount < 0 || + filterValueCount > maxToPassToReserveAdditional) { + return false; + } + reserveAdditional(aDstSize, filterValueCount); + for (int32_t destI = 0; destI < aDstSize; destI++) { + // This is the pixel in the source directly under the pixel in the dest. + // Note that we base computations on the "center" of the pixels. To see + // why, observe that the destination pixel at coordinates (0, 0) in a 5.0x + // downscale should "cover" the pixels around the pixel with *its center* + // at coordinates (2.5, 2.5) in the source, not those around (0, 0). + // Hence we need to scale coordinates (0.5, 0.5), not (0, 0). + float srcPixel = (static_cast<float>(destI) + 0.5f) * invScale; + + // Compute the (inclusive) range of source pixels the filter covers. + float srcBegin = std::max(0.0f, floorf(srcPixel - srcSupport)); + float srcEnd = std::min(aSrcSize - 1.0f, ceilf(srcPixel + srcSupport)); + + // Compute the unnormalized filter value at each location of the source + // it covers. + + // Sum of the filter values for normalizing. + // Distance from the center of the filter, this is the filter coordinate + // in source space. We also need to consider the center of the pixel + // when comparing distance against 'srcPixel'. In the 5x downscale + // example used above the distance from the center of the filter to + // the pixel with coordinates (2, 2) should be 0, because its center + // is at (2.5, 2.5). + int32_t filterCount = int32_t(srcEnd - srcBegin) + 1; + if (filterCount <= 0 || !filterValues.resize(filterCount) || + !fixedFilterValues.resize(filterCount)) { + return false; + } + + float destFilterDist = (srcBegin + 0.5f - srcPixel) * clampedScale; + float filterSum = 0.0f; + for (int32_t index = 0; index < filterCount; index++) { + float filterValue = aBitmapFilter.evaluate(destFilterDist); + filterValues[index] = filterValue; + filterSum += filterValue; + destFilterDist += clampedScale; + } + + // The filter must be normalized so that we don't affect the brightness of + // the image. Convert to normalized fixed point. + ConvolutionFixed fixedSum = 0; + float invFilterSum = 1.0f / filterSum; + for (int32_t fixedI = 0; fixedI < filterCount; fixedI++) { + ConvolutionFixed curFixed = ToFixed(filterValues[fixedI] * invFilterSum); + fixedSum += curFixed; + fixedFilterValues[fixedI] = curFixed; + } + + // The conversion to fixed point will leave some rounding errors, which + // we add back in to avoid affecting the brightness of the image. We + // arbitrarily add this to the center of the filter array (this won't always + // be the center of the filter function since it could get clipped on the + // edges, but it doesn't matter enough to worry about that case). + ConvolutionFixed leftovers = ToFixed(1) - fixedSum; + fixedFilterValues[filterCount / 2] += leftovers; + + AddFilter(int32_t(srcBegin), fixedFilterValues.begin(), filterCount); + } + + return maxFilter() > 0 && numValues() == aDstSize; +} + +// Does a two-dimensional convolution on the given source image. +// +// It is assumed the source pixel offsets referenced in the input filters +// reference only valid pixels, so the source image size is not required. Each +// row of the source image starts |sourceByteRowStride| after the previous +// one (this allows you to have rows with some padding at the end). +// +// The result will be put into the given output buffer. The destination image +// size will be xfilter.numValues() * yfilter.numValues() pixels. It will be +// in rows of exactly xfilter.numValues() * 4 bytes. +// +// |sourceHasAlpha| is a hint that allows us to avoid doing computations on +// the alpha channel if the image is opaque. If you don't know, set this to +// true and it will work properly, but setting this to false will be a few +// percent faster if you know the image is opaque. +// +// The layout in memory is assumed to be 4-bytes per pixel in B-G-R-A order +// (this is ARGB when loaded into 32-bit words on a little-endian machine). +/** + * Returns false if it was unable to perform the convolution/rescale. in which + * case the output buffer is assumed to be undefined. + */ +bool BGRAConvolve2D(const unsigned char* sourceData, int sourceByteRowStride, + bool sourceHasAlpha, const SkConvolutionFilter1D& filterX, + const SkConvolutionFilter1D& filterY, + int outputByteRowStride, unsigned char* output) { + int maxYFilterSize = filterY.maxFilter(); + + // The next row in the input that we will generate a horizontally + // convolved row for. If the filter doesn't start at the beginning of the + // image (this is the case when we are only resizing a subset), then we + // don't want to generate any output rows before that. Compute the starting + // row for convolution as the first pixel for the first vertical filter. + int filterOffset = 0, filterLength = 0; + const SkConvolutionFilter1D::ConvolutionFixed* filterValues = + filterY.FilterForValue(0, &filterOffset, &filterLength); + int nextXRow = filterOffset; + + // We loop over each row in the input doing a horizontal convolution. This + // will result in a horizontally convolved image. We write the results into + // a circular buffer of convolved rows and do vertical convolution as rows + // are available. This prevents us from having to store the entire + // intermediate image and helps cache coherency. + // We will need four extra rows to allow horizontal convolution could be done + // simultaneously. We also pad each row in row buffer to be aligned-up to + // 32 bytes. + // TODO(jiesun): We do not use aligned load from row buffer in vertical + // convolution pass yet. Somehow Windows does not like it. + int rowBufferWidth = (filterX.numValues() + 31) & ~0x1F; + int rowBufferHeight = maxYFilterSize; + + // check for too-big allocation requests : crbug.com/528628 + { + int64_t size = int64_t(rowBufferWidth) * int64_t(rowBufferHeight); + // need some limit, to avoid over-committing success from malloc, but then + // crashing when we try to actually use the memory. + // 100meg seems big enough to allow "normal" zoom factors and image sizes + // through while avoiding the crash seen by the bug (crbug.com/528628) + if (size > 100 * 1024 * 1024) { + // printf_stderr("BGRAConvolve2D: tmp allocation [%lld] too + // big\n", size); + return false; + } + } + + CircularRowBuffer rowBuffer(rowBufferWidth, rowBufferHeight, filterOffset); + + // Loop over every possible output row, processing just enough horizontal + // convolutions to run each subsequent vertical convolution. + MOZ_ASSERT(outputByteRowStride >= filterX.numValues() * 4); + int numOutputRows = filterY.numValues(); + + // We need to check which is the last line to convolve before we advance 4 + // lines in one iteration. + int lastFilterOffset, lastFilterLength; + filterY.FilterForValue(numOutputRows - 1, &lastFilterOffset, + &lastFilterLength); + + for (int outY = 0; outY < numOutputRows; outY++) { + filterValues = filterY.FilterForValue(outY, &filterOffset, &filterLength); + + // Generate output rows until we have enough to run the current filter. + while (nextXRow < filterOffset + filterLength) { + convolve_horizontally( + &sourceData[(uint64_t)nextXRow * sourceByteRowStride], filterX, + rowBuffer.advanceRow(), sourceHasAlpha); + nextXRow++; + } + + // Compute where in the output image this row of final data will go. + unsigned char* curOutputRow = &output[(uint64_t)outY * outputByteRowStride]; + + // Get the list of rows that the circular buffer has, in order. + int firstRowInCircularBuffer; + unsigned char* const* rowsToConvolve = + rowBuffer.GetRowAddresses(&firstRowInCircularBuffer); + + // Now compute the start of the subset of those rows that the filter needs. + unsigned char* const* firstRowForFilter = + &rowsToConvolve[filterOffset - firstRowInCircularBuffer]; + + convolve_vertically(filterValues, filterLength, firstRowForFilter, + filterX.numValues(), curOutputRow, sourceHasAlpha); + } + return true; +} + +} // namespace skia diff --git a/gfx/2d/SkConvolver.h b/gfx/2d/SkConvolver.h new file mode 100644 index 0000000000..5ea8ab9b5d --- /dev/null +++ b/gfx/2d/SkConvolver.h @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2011-2016 Google Inc. +// Use of this source code is governed by a BSD-style license that can be +// found in the gfx/skia/LICENSE file. + +#ifndef MOZILLA_GFX_SKCONVOLVER_H_ +#define MOZILLA_GFX_SKCONVOLVER_H_ + +#include "mozilla/Assertions.h" +#include <cfloat> +#include <cmath> +#include <vector> + +namespace skia { + +class SkBitmapFilter { + public: + explicit SkBitmapFilter(float width) : fWidth(width) {} + virtual ~SkBitmapFilter() = default; + + float width() const { return fWidth; } + virtual float evaluate(float x) const = 0; + + protected: + float fWidth; +}; + +class SkBoxFilter final : public SkBitmapFilter { + public: + explicit SkBoxFilter(float width = 0.5f) : SkBitmapFilter(width) {} + + float evaluate(float x) const override { + return (x >= -fWidth && x < fWidth) ? 1.0f : 0.0f; + } +}; + +class SkLanczosFilter final : public SkBitmapFilter { + public: + explicit SkLanczosFilter(float width = 3.0f) : SkBitmapFilter(width) {} + + float evaluate(float x) const override { + if (x <= -fWidth || x >= fWidth) { + return 0.0f; // Outside of the window. + } + if (x > -FLT_EPSILON && x < FLT_EPSILON) { + return 1.0f; // Special case the discontinuity at the origin. + } + float xpi = x * float(M_PI); + return (sinf(xpi) / xpi) * // sinc(x) + sinf(xpi / fWidth) / (xpi / fWidth); // sinc(x/fWidth) + } +}; + +// Represents a filter in one dimension. Each output pixel has one entry in this +// object for the filter values contributing to it. You build up the filter +// list by calling AddFilter for each output pixel (in order). +// +// We do 2-dimensional convolution by first convolving each row by one +// SkConvolutionFilter1D, then convolving each column by another one. +// +// Entries are stored in ConvolutionFixed point, shifted left by kShiftBits. +class SkConvolutionFilter1D { + public: + using ConvolutionFixed = short; + + // The number of bits that ConvolutionFixed point values are shifted by. + enum { kShiftBits = 14 }; + + SkConvolutionFilter1D(); + ~SkConvolutionFilter1D(); + + // Convert between floating point and our ConvolutionFixed point + // representation. + static ConvolutionFixed ToFixed(float f) { + return static_cast<ConvolutionFixed>(f * (1 << kShiftBits)); + } + + // Returns the maximum pixel span of a filter. + int maxFilter() const { return fMaxFilter; } + + // Returns the number of filters in this filter. This is the dimension of the + // output image. + int numValues() const { return static_cast<int>(fFilters.size()); } + + void reserveAdditional(int filterCount, int filterValueCount) { + fFilters.reserve(fFilters.size() + filterCount); + fFilterValues.reserve(fFilterValues.size() + filterValueCount); + } + + // Appends the given list of scaling values for generating a given output + // pixel. |filterOffset| is the distance from the edge of the image to where + // the scaling factors start. The scaling factors apply to the source pixels + // starting from this position, and going for the next |filterLength| pixels. + // + // You will probably want to make sure your input is normalized (that is, + // all entries in |filterValuesg| sub to one) to prevent affecting the overall + // brighness of the image. + // + // The filterLength must be > 0. + void AddFilter(int filterOffset, const ConvolutionFixed* filterValues, + int filterLength); + + // Retrieves a filter for the given |valueOffset|, a position in the output + // image in the direction we're convolving. The offset and length of the + // filter values are put into the corresponding out arguments (see AddFilter + // above for what these mean), and a pointer to the first scaling factor is + // returned. There will be |filterLength| values in this array. + inline const ConvolutionFixed* FilterForValue(int valueOffset, + int* filterOffset, + int* filterLength) const { + const FilterInstance& filter = fFilters[valueOffset]; + *filterOffset = filter.fOffset; + *filterLength = filter.fTrimmedLength; + if (filter.fTrimmedLength == 0) { + return nullptr; + } + return &fFilterValues[filter.fDataLocation]; + } + + bool ComputeFilterValues(const SkBitmapFilter& aBitmapFilter, + int32_t aSrcSize, int32_t aDstSize); + + private: + struct FilterInstance { + // Offset within filterValues for this instance of the filter. + int fDataLocation; + + // Distance from the left of the filter to the center. IN PIXELS + int fOffset; + + // Number of values in this filter instance. + int fTrimmedLength; + + // Filter length as specified. Note that this may be different from + // 'trimmed_length' if leading/trailing zeros of the original floating + // point form were clipped differently on each tail. + int fLength; + }; + + // Stores the information for each filter added to this class. + std::vector<FilterInstance> fFilters; + + // We store all the filter values in this flat list, indexed by + // |FilterInstance.data_location| to avoid the mallocs required for storing + // each one separately. + std::vector<ConvolutionFixed> fFilterValues; + + // The maximum size of any filter we've added. + int fMaxFilter; +}; + +void convolve_horizontally(const unsigned char* srcData, + const SkConvolutionFilter1D& filter, + unsigned char* outRow, bool hasAlpha); + +void convolve_vertically( + const SkConvolutionFilter1D::ConvolutionFixed* filterValues, + int filterLength, unsigned char* const* sourceDataRows, int pixelWidth, + unsigned char* outRow, bool hasAlpha); + +bool BGRAConvolve2D(const unsigned char* sourceData, int sourceByteRowStride, + bool sourceHasAlpha, const SkConvolutionFilter1D& filterX, + const SkConvolutionFilter1D& filterY, + int outputByteRowStride, unsigned char* output); + +} // namespace skia + +#endif /* MOZILLA_GFX_SKCONVOLVER_H_ */ diff --git a/gfx/2d/SourceSurfaceCairo.cpp b/gfx/2d/SourceSurfaceCairo.cpp new file mode 100644 index 0000000000..3bf380c35f --- /dev/null +++ b/gfx/2d/SourceSurfaceCairo.cpp @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SourceSurfaceCairo.h" +#include "DrawTargetCairo.h" +#include "HelpersCairo.h" +#include "DataSourceSurfaceWrapper.h" + +#include "cairo.h" + +namespace mozilla { +namespace gfx { + +static SurfaceFormat CairoFormatToSurfaceFormat(cairo_format_t format) { + switch (format) { + case CAIRO_FORMAT_ARGB32: + return SurfaceFormat::B8G8R8A8; + case CAIRO_FORMAT_RGB24: + return SurfaceFormat::B8G8R8X8; + case CAIRO_FORMAT_RGB16_565: + return SurfaceFormat::R5G6B5_UINT16; + case CAIRO_FORMAT_A8: + return SurfaceFormat::A8; + default: + return SurfaceFormat::B8G8R8A8; + } +} + +SourceSurfaceCairo::SourceSurfaceCairo( + cairo_surface_t* aSurface, const IntSize& aSize, + const SurfaceFormat& aFormat, DrawTargetCairo* aDrawTarget /* = nullptr */) + : mSize(aSize), + mFormat(aFormat), + mSurface(aSurface), + mDrawTarget(aDrawTarget) { + cairo_surface_reference(mSurface); +} + +SourceSurfaceCairo::~SourceSurfaceCairo() { cairo_surface_destroy(mSurface); } + +IntSize SourceSurfaceCairo::GetSize() const { return mSize; } + +SurfaceFormat SourceSurfaceCairo::GetFormat() const { return mFormat; } + +already_AddRefed<DataSourceSurface> SourceSurfaceCairo::GetDataSurface() { + RefPtr<DataSourceSurface> dataSurf; + + if (cairo_surface_get_type(mSurface) == CAIRO_SURFACE_TYPE_IMAGE) { + dataSurf = new DataSourceSurfaceCairo(mSurface); + } else { + cairo_surface_t* imageSurf = cairo_image_surface_create( + GfxFormatToCairoFormat(mFormat), mSize.width, mSize.height); + + // Fill the new image surface with the contents of our surface. + cairo_t* ctx = cairo_create(imageSurf); + cairo_set_source_surface(ctx, mSurface, 0, 0); + cairo_paint(ctx); + cairo_destroy(ctx); + + dataSurf = new DataSourceSurfaceCairo(imageSurf); + cairo_surface_destroy(imageSurf); + } + + // We also need to make sure that the returned surface has + // surface->GetType() == SurfaceType::DATA. + return MakeAndAddRef<DataSourceSurfaceWrapper>(dataSurf); +} + +cairo_surface_t* SourceSurfaceCairo::GetSurface() const { return mSurface; } + +void SourceSurfaceCairo::DrawTargetWillChange() { + if (mDrawTarget) { + mDrawTarget = nullptr; + + // We're about to lose our version of the surface, so make a copy of it. + cairo_surface_t* surface = cairo_surface_create_similar( + mSurface, GfxFormatToCairoContent(mFormat), mSize.width, mSize.height); + cairo_t* ctx = cairo_create(surface); + cairo_pattern_t* pat = cairo_pattern_create_for_surface(mSurface); + cairo_set_source(ctx, pat); + cairo_paint(ctx); + cairo_destroy(ctx); + cairo_pattern_destroy(pat); + + // Swap in this new surface. + cairo_surface_destroy(mSurface); + mSurface = surface; + } +} + +DataSourceSurfaceCairo::DataSourceSurfaceCairo(cairo_surface_t* imageSurf) + : mImageSurface(imageSurf) { + cairo_surface_reference(mImageSurface); +} + +DataSourceSurfaceCairo::~DataSourceSurfaceCairo() { + cairo_surface_destroy(mImageSurface); +} + +unsigned char* DataSourceSurfaceCairo::GetData() { + return cairo_image_surface_get_data(mImageSurface); +} + +int32_t DataSourceSurfaceCairo::Stride() { + return cairo_image_surface_get_stride(mImageSurface); +} + +IntSize DataSourceSurfaceCairo::GetSize() const { + IntSize size; + size.width = cairo_image_surface_get_width(mImageSurface); + size.height = cairo_image_surface_get_height(mImageSurface); + + return size; +} + +SurfaceFormat DataSourceSurfaceCairo::GetFormat() const { + return CairoFormatToSurfaceFormat( + cairo_image_surface_get_format(mImageSurface)); +} + +cairo_surface_t* DataSourceSurfaceCairo::GetSurface() const { + return mImageSurface; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/SourceSurfaceCairo.h b/gfx/2d/SourceSurfaceCairo.h new file mode 100644 index 0000000000..4fcdfa2de8 --- /dev/null +++ b/gfx/2d/SourceSurfaceCairo.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MOZILLA_GFX_OP_SOURCESURFACE_CAIRO_H +#define _MOZILLA_GFX_OP_SOURCESURFACE_CAIRO_H + +#include "2D.h" + +namespace mozilla { +namespace gfx { + +class DrawTargetCairo; + +class SourceSurfaceCairo : public SourceSurface { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceCairo, override) + + // Create a SourceSurfaceCairo. The surface will not be copied, but simply + // referenced. + // If aDrawTarget is non-nullptr, it is assumed that this is a snapshot source + // surface, and we'll call DrawTargetCairo::RemoveSnapshot(this) on it when + // we're destroyed. + SourceSurfaceCairo(cairo_surface_t* aSurface, const IntSize& aSize, + const SurfaceFormat& aFormat, + DrawTargetCairo* aDrawTarget = nullptr); + virtual ~SourceSurfaceCairo(); + + SurfaceType GetType() const override { return SurfaceType::CAIRO; } + IntSize GetSize() const override; + SurfaceFormat GetFormat() const override; + already_AddRefed<DataSourceSurface> GetDataSurface() override; + + cairo_surface_t* GetSurface() const; + + private: // methods + friend class DrawTargetCairo; + void DrawTargetWillChange(); + + private: // data + IntSize mSize; + SurfaceFormat mFormat; + cairo_surface_t* mSurface; + DrawTargetCairo* mDrawTarget; +}; + +class DataSourceSurfaceCairo : public DataSourceSurface { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceCairo, override) + + explicit DataSourceSurfaceCairo(cairo_surface_t* imageSurf); + virtual ~DataSourceSurfaceCairo(); + unsigned char* GetData() override; + int32_t Stride() override; + + SurfaceType GetType() const override { return SurfaceType::CAIRO_IMAGE; } + IntSize GetSize() const override; + SurfaceFormat GetFormat() const override; + + cairo_surface_t* GetSurface() const; + + private: + cairo_surface_t* mImageSurface; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // _MOZILLA_GFX_OP_SOURCESURFACE_CAIRO_H diff --git a/gfx/2d/SourceSurfaceD2D1.cpp b/gfx/2d/SourceSurfaceD2D1.cpp new file mode 100644 index 0000000000..9aef7ab54e --- /dev/null +++ b/gfx/2d/SourceSurfaceD2D1.cpp @@ -0,0 +1,245 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SourceSurfaceD2D1.h" +#include "DrawTargetD2D1.h" + +namespace mozilla { +namespace gfx { + +SourceSurfaceD2D1::SourceSurfaceD2D1(ID2D1Image* aImage, + ID2D1DeviceContext* aDC, + SurfaceFormat aFormat, + const IntSize& aSize, DrawTargetD2D1* aDT) + : mImage(aImage), + mDC(aDC), + mDevice(Factory::GetD2D1Device()), + mFormat(aFormat), + mSize(aSize), + mDrawTarget(aDT), + mOwnsCopy(false) { + aImage->QueryInterface((ID2D1Bitmap1**)getter_AddRefs(mRealizedBitmap)); + if (aDT) { + mSnapshotLock = aDT->mSnapshotLock; + } +} + +SourceSurfaceD2D1::~SourceSurfaceD2D1() { + if (mOwnsCopy) { + DrawTargetD2D1::mVRAMUsageSS -= + mSize.width * mSize.height * BytesPerPixel(mFormat); + } +} + +bool SourceSurfaceD2D1::IsValid() const { + return mDevice == Factory::GetD2D1Device(); +} + +already_AddRefed<DataSourceSurface> SourceSurfaceD2D1::GetDataSurface() { + Maybe<MutexAutoLock> lock; + if (mSnapshotLock) { + lock.emplace(*mSnapshotLock); + } + + if (!EnsureRealizedBitmap()) { + gfxCriticalError() << "Failed to realize a bitmap, device " + << hexa(mDevice); + return nullptr; + } + + HRESULT hr; + + RefPtr<ID2D1Bitmap1> softwareBitmap; + D2D1_BITMAP_PROPERTIES1 props; + props.dpiX = 96; + props.dpiY = 96; + props.pixelFormat = D2DPixelFormat(mFormat); + props.colorContext = nullptr; + props.bitmapOptions = + D2D1_BITMAP_OPTIONS_CANNOT_DRAW | D2D1_BITMAP_OPTIONS_CPU_READ; + hr = mDC->CreateBitmap(D2DIntSize(mSize), nullptr, 0, props, + (ID2D1Bitmap1**)getter_AddRefs(softwareBitmap)); + + if (FAILED(hr)) { + gfxCriticalError() << "Failed to create software bitmap: " << mSize + << " Code: " << hexa(hr); + return nullptr; + } + + D2D1_POINT_2U point = D2D1::Point2U(0, 0); + D2D1_RECT_U rect = D2D1::RectU(0, 0, mSize.width, mSize.height); + + hr = softwareBitmap->CopyFromBitmap(&point, mRealizedBitmap, &rect); + + if (FAILED(hr)) { + gfxWarning() << "Failed to readback into software bitmap. Code: " + << hexa(hr); + return nullptr; + } + + return MakeAndAddRef<DataSourceSurfaceD2D1>(softwareBitmap, mFormat); +} + +bool SourceSurfaceD2D1::EnsureRealizedBitmap() { + if (mRealizedBitmap) { + return true; + } + + // Why aren't we using mDevice here or anywhere else? + RefPtr<ID2D1Device> device = Factory::GetD2D1Device(); + if (!device) { + return false; + } + + RefPtr<ID2D1DeviceContext> dc; + device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, + getter_AddRefs(dc)); + + D2D1_BITMAP_PROPERTIES1 props; + props.dpiX = 96; + props.dpiY = 96; + props.pixelFormat = D2DPixelFormat(mFormat); + props.colorContext = nullptr; + props.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET; + dc->CreateBitmap(D2DIntSize(mSize), nullptr, 0, props, + (ID2D1Bitmap1**)getter_AddRefs(mRealizedBitmap)); + + dc->SetTarget(mRealizedBitmap); + + dc->BeginDraw(); + dc->DrawImage(mImage); + dc->EndDraw(); + + return true; +} + +void SourceSurfaceD2D1::DrawTargetWillChange() { + MOZ_ASSERT(mSnapshotLock); + mSnapshotLock->AssertCurrentThreadOwns(); + + // At this point in time this should always be true here. + MOZ_ASSERT(mRealizedBitmap); + + RefPtr<ID2D1Bitmap1> oldBitmap = mRealizedBitmap; + + D2D1_BITMAP_PROPERTIES1 props; + props.dpiX = 96; + props.dpiY = 96; + props.pixelFormat = D2DPixelFormat(mFormat); + props.colorContext = nullptr; + props.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET; + HRESULT hr = + mDC->CreateBitmap(D2DIntSize(mSize), nullptr, 0, props, + (ID2D1Bitmap1**)getter_AddRefs(mRealizedBitmap)); + + if (FAILED(hr)) { + gfxCriticalError() + << "Failed to create bitmap to make DrawTarget copy. Size: " << mSize + << " Code: " << hexa(hr); + MarkIndependent(); + return; + } + + D2D1_POINT_2U point = D2D1::Point2U(0, 0); + D2D1_RECT_U rect = D2D1::RectU(0, 0, mSize.width, mSize.height); + mRealizedBitmap->CopyFromBitmap(&point, oldBitmap, &rect); + mImage = mRealizedBitmap; + + DrawTargetD2D1::mVRAMUsageSS += + mSize.width * mSize.height * BytesPerPixel(mFormat); + mOwnsCopy = true; + + // Ensure the object stays alive for the duration of MarkIndependent. + RefPtr<SourceSurfaceD2D1> deathGrip = this; + // We now no longer depend on the source surface content remaining the same. + MarkIndependent(); +} + +void SourceSurfaceD2D1::MarkIndependent() { + if (mDrawTarget) { + MOZ_ASSERT(mDrawTarget->mSnapshot == this); + mDrawTarget->mSnapshot = nullptr; + mDrawTarget = nullptr; + } +} + +DataSourceSurfaceD2D1::DataSourceSurfaceD2D1(ID2D1Bitmap1* aMappableBitmap, + SurfaceFormat aFormat) + : mBitmap(aMappableBitmap), + mFormat(aFormat), + mIsMapped(false), + mImplicitMapped(false) {} + +DataSourceSurfaceD2D1::~DataSourceSurfaceD2D1() { + if (mImplicitMapped) { + mBitmap->Unmap(); + } +} + +IntSize DataSourceSurfaceD2D1::GetSize() const { + D2D1_SIZE_F size = mBitmap->GetSize(); + + return IntSize(int32_t(size.width), int32_t(size.height)); +} + +uint8_t* DataSourceSurfaceD2D1::GetData() { + EnsureMapped(); + + return mMap.bits; +} + +bool DataSourceSurfaceD2D1::Map(MapType aMapType, + MappedSurface* aMappedSurface) { + // DataSourceSurfaces used with the new Map API should not be used with + // GetData!! + MOZ_ASSERT(!mImplicitMapped); + MOZ_ASSERT(!mIsMapped); + + if (aMapType != MapType::READ) { + gfxWarning() << "Attempt to map D2D1 DrawTarget for writing."; + return false; + } + + D2D1_MAPPED_RECT map; + if (FAILED(mBitmap->Map(D2D1_MAP_OPTIONS_READ, &map))) { + gfxCriticalError() << "Failed to map bitmap (M)."; + return false; + } + aMappedSurface->mData = map.bits; + aMappedSurface->mStride = map.pitch; + + mIsMapped = !!aMappedSurface->mData; + return mIsMapped; +} + +void DataSourceSurfaceD2D1::Unmap() { + MOZ_ASSERT(mIsMapped); + + mIsMapped = false; + mBitmap->Unmap(); +} + +int32_t DataSourceSurfaceD2D1::Stride() { + EnsureMapped(); + + return mMap.pitch; +} + +void DataSourceSurfaceD2D1::EnsureMapped() { + // Do not use GetData() after having used Map! + MOZ_ASSERT(!mIsMapped); + if (mImplicitMapped) { + return; + } + if (FAILED(mBitmap->Map(D2D1_MAP_OPTIONS_READ, &mMap))) { + gfxCriticalError() << "Failed to map bitmap (EM)."; + return; + } + mImplicitMapped = true; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/SourceSurfaceD2D1.h b/gfx/2d/SourceSurfaceD2D1.h new file mode 100644 index 0000000000..cfc6dd6b86 --- /dev/null +++ b/gfx/2d/SourceSurfaceD2D1.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SOURCESURFACED2D1_H_ +#define MOZILLA_GFX_SOURCESURFACED2D1_H_ + +#include "2D.h" +#include "HelpersD2D.h" +#include <vector> +#include <d3d11.h> +#include <d2d1_1.h> + +namespace mozilla { +namespace gfx { + +class DrawTargetD2D1; + +class SourceSurfaceD2D1 : public SourceSurface { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceD2D1, override) + + SourceSurfaceD2D1(ID2D1Image* aImage, ID2D1DeviceContext* aDC, + SurfaceFormat aFormat, const IntSize& aSize, + DrawTargetD2D1* aDT = nullptr); + ~SourceSurfaceD2D1(); + + SurfaceType GetType() const override { return SurfaceType::D2D1_1_IMAGE; } + IntSize GetSize() const override { return mSize; } + SurfaceFormat GetFormat() const override { return mFormat; } + bool IsValid() const override; + already_AddRefed<DataSourceSurface> GetDataSurface() override; + + ID2D1Image* GetImage() { return mImage; } + + void EnsureIndependent() { + if (!mDrawTarget) return; + DrawTargetWillChange(); + } + + private: + friend class DrawTargetD2D1; + + bool EnsureRealizedBitmap(); + + // This function is called by the draw target this texture belongs to when + // it is about to be changed. The texture will be required to make a copy + // of itself when this happens. + void DrawTargetWillChange(); + + // This will mark the surface as no longer depending on its drawtarget, + // this may happen on destruction or copying. + void MarkIndependent(); + + RefPtr<ID2D1Image> mImage; + // This may be null if we were created for a non-bitmap image and have not + // had a reason yet to realize ourselves. + RefPtr<ID2D1Bitmap1> mRealizedBitmap; + RefPtr<ID2D1DeviceContext> mDC; + // Keep this around to verify whether out image is still valid in the future. + RefPtr<ID2D1Device> mDevice; + + const SurfaceFormat mFormat; + const IntSize mSize; + DrawTargetD2D1* mDrawTarget; + std::shared_ptr<Mutex> mSnapshotLock; + bool mOwnsCopy; +}; + +class DataSourceSurfaceD2D1 : public DataSourceSurface { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceD2D1, override) + + DataSourceSurfaceD2D1(ID2D1Bitmap1* aMappableBitmap, SurfaceFormat aFormat); + ~DataSourceSurfaceD2D1(); + + SurfaceType GetType() const override { return SurfaceType::DATA; } + IntSize GetSize() const override; + SurfaceFormat GetFormat() const override { return mFormat; } + bool IsValid() const override { return !!mBitmap; } + uint8_t* GetData() override; + int32_t Stride() override; + bool Map(MapType, MappedSurface* aMappedSurface) override; + void Unmap() override; + + private: + friend class SourceSurfaceD2DTarget; + void EnsureMapped(); + + mutable RefPtr<ID2D1Bitmap1> mBitmap; + SurfaceFormat mFormat; + D2D1_MAPPED_RECT mMap; + bool mIsMapped; + bool mImplicitMapped; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SOURCESURFACED2D2TARGET_H_ */ diff --git a/gfx/2d/SourceSurfaceRawData.cpp b/gfx/2d/SourceSurfaceRawData.cpp new file mode 100644 index 0000000000..d5590e329c --- /dev/null +++ b/gfx/2d/SourceSurfaceRawData.cpp @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SourceSurfaceRawData.h" + +#include "DataSurfaceHelpers.h" +#include "Logging.h" +#include "mozilla/Types.h" // for decltype + +namespace mozilla { +namespace gfx { + +void SourceSurfaceRawData::InitWrappingData( + uint8_t* aData, const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat, Factory::SourceSurfaceDeallocator aDeallocator, + void* aClosure) { + mRawData = aData; + mSize = aSize; + mStride = aStride; + mFormat = aFormat; + mDeallocator = aDeallocator; + mClosure = aClosure; +} + +void SourceSurfaceRawData::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + SizeOfInfo& aInfo) const { + aInfo.AddType(SurfaceType::DATA); + if (mDeallocator) { + aInfo.mUnknownBytes = mStride * mSize.height; + } +} + +bool SourceSurfaceAlignedRawData::Init(const IntSize& aSize, + SurfaceFormat aFormat, bool aClearMem, + uint8_t aClearValue, int32_t aStride) { + mFormat = aFormat; + mStride = aStride ? aStride + : GetAlignedStride<16>(aSize.width, BytesPerPixel(aFormat)); + + size_t bufLen = BufferSizeFromStrideAndHeight(mStride, aSize.height); + if (bufLen > 0) { + bool zeroMem = aClearMem && !aClearValue; + static_assert(sizeof(decltype(mArray[0])) == 1, + "mArray.Realloc() takes an object count, so its objects must " + "be 1-byte sized if we use bufLen"); + + // AlignedArray uses cmalloc to zero mem for a fast path. + mArray.Realloc(/* actually an object count */ bufLen, zeroMem); + mSize = aSize; + + if (mArray && aClearMem && aClearValue) { + memset(mArray, aClearValue, mStride * aSize.height); + } + } else { + mArray.Dealloc(); + mSize.SizeTo(0, 0); + } + + return mArray != nullptr; +} + +void SourceSurfaceAlignedRawData::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf, SizeOfInfo& aInfo) const { + aInfo.AddType(SurfaceType::DATA_ALIGNED); + aInfo.mHeapBytes = mArray.HeapSizeOfExcludingThis(aMallocSizeOf); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/SourceSurfaceRawData.h b/gfx/2d/SourceSurfaceRawData.h new file mode 100644 index 0000000000..904f0fa1c8 --- /dev/null +++ b/gfx/2d/SourceSurfaceRawData.h @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SOURCESURFACERAWDATA_H_ +#define MOZILLA_GFX_SOURCESURFACERAWDATA_H_ + +#include "2D.h" +#include "Tools.h" +#include "mozilla/Atomics.h" + +namespace mozilla { +namespace gfx { + +class SourceSurfaceMappedData final : public DataSourceSurface { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceMappedData, final) + + SourceSurfaceMappedData(ScopedMap&& aMap, const IntSize& aSize, + SurfaceFormat aFormat) + : mMap(std::move(aMap)), mSize(aSize), mFormat(aFormat) {} + + ~SourceSurfaceMappedData() final = default; + + uint8_t* GetData() final { return mMap.GetData(); } + int32_t Stride() final { return mMap.GetStride(); } + + SurfaceType GetType() const final { return SurfaceType::DATA_MAPPED; } + IntSize GetSize() const final { return mSize; } + SurfaceFormat GetFormat() const final { return mFormat; } + + void SizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + SizeOfInfo& aInfo) const override { + aInfo.AddType(SurfaceType::DATA_MAPPED); + mMap.GetSurface()->SizeOfExcludingThis(aMallocSizeOf, aInfo); + } + + const DataSourceSurface* GetScopedSurface() const { + return mMap.GetSurface(); + } + + private: + ScopedMap mMap; + IntSize mSize; + SurfaceFormat mFormat; +}; + +class SourceSurfaceRawData : public DataSourceSurface { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceRawData, override) + + SourceSurfaceRawData() + : mRawData(0), + mStride(0), + mFormat(SurfaceFormat::UNKNOWN), + mDeallocator(nullptr), + mClosure(nullptr) {} + + virtual ~SourceSurfaceRawData() { + if (mDeallocator) { + mDeallocator(mClosure); + } + } + + virtual uint8_t* GetData() override { return mRawData; } + virtual int32_t Stride() override { return mStride; } + + virtual SurfaceType GetType() const override { return SurfaceType::DATA; } + virtual IntSize GetSize() const override { return mSize; } + virtual SurfaceFormat GetFormat() const override { return mFormat; } + + void SizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + SizeOfInfo& aInfo) const override; + + private: + friend class Factory; + + // If we have a custom deallocator, the |aData| will be released using the + // custom deallocator and |aClosure| in dtor. The assumption is that the + // caller will check for valid size and stride before making this call. + void InitWrappingData(unsigned char* aData, const IntSize& aSize, + int32_t aStride, SurfaceFormat aFormat, + Factory::SourceSurfaceDeallocator aDeallocator, + void* aClosure); + + uint8_t* mRawData; + int32_t mStride; + SurfaceFormat mFormat; + IntSize mSize; + + Factory::SourceSurfaceDeallocator mDeallocator; + void* mClosure; +}; + +class SourceSurfaceAlignedRawData : public DataSourceSurface { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceAlignedRawData, + override) + SourceSurfaceAlignedRawData() : mStride(0), mFormat(SurfaceFormat::UNKNOWN) {} + ~SourceSurfaceAlignedRawData() override = default; + + bool Init(const IntSize& aSize, SurfaceFormat aFormat, bool aClearMem, + uint8_t aClearValue, int32_t aStride = 0); + + uint8_t* GetData() override { return mArray; } + int32_t Stride() override { return mStride; } + + SurfaceType GetType() const override { return SurfaceType::DATA_ALIGNED; } + IntSize GetSize() const override { return mSize; } + SurfaceFormat GetFormat() const override { return mFormat; } + + void SizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + SizeOfInfo& aInfo) const override; + + private: + friend class Factory; + + AlignedArray<uint8_t> mArray; + int32_t mStride; + SurfaceFormat mFormat; + IntSize mSize; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SOURCESURFACERAWDATA_H_ */ diff --git a/gfx/2d/SourceSurfaceSkia.cpp b/gfx/2d/SourceSurfaceSkia.cpp new file mode 100644 index 0000000000..21f15f62e6 --- /dev/null +++ b/gfx/2d/SourceSurfaceSkia.cpp @@ -0,0 +1,228 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Logging.h" +#include "SourceSurfaceSkia.h" +#include "HelpersSkia.h" +#include "DrawTargetSkia.h" +#include "skia/include/core/SkData.h" +#include "skia/include/core/SkImage.h" +#include "skia/include/core/SkSurface.h" +#include "skia/include/private/base/SkMalloc.h" +#include "mozilla/CheckedInt.h" + +namespace mozilla::gfx { + +SourceSurfaceSkia::SourceSurfaceSkia() + : mFormat(SurfaceFormat::UNKNOWN), + mStride(0), + mDrawTarget(nullptr), + mChangeMutex("SourceSurfaceSkia::mChangeMutex"), + mIsMapped(false) {} + +SourceSurfaceSkia::~SourceSurfaceSkia() { + // if mIsMapped is true then mChangeMutex will be locked + // which will cause problems during destruction. + MOZ_RELEASE_ASSERT(!mIsMapped); +} + +IntSize SourceSurfaceSkia::GetSize() const { return mSize; } + +SurfaceFormat SourceSurfaceSkia::GetFormat() const { return mFormat; } + +// This is only ever called by the DT destructor, which can only ever happen +// from one place at a time. Therefore it doesn't need to hold the ChangeMutex +// as mSurface is never read to directly and is just there to keep the object +// alive, which itself is refcounted in a thread-safe manner. +void SourceSurfaceSkia::GiveSurface(SkSurface* aSurface) { + mSurface.reset(aSurface); + mDrawTarget = nullptr; +} + +sk_sp<SkImage> SourceSurfaceSkia::GetImage(Maybe<MutexAutoLock>* aLock) { + // If we were provided a lock object, we can let the caller access + // a shared SkImage and we know it won't go away while the lock is held. + // Otherwise we need to call DrawTargetWillChange to ensure we have our + // own SkImage. + if (aLock) { + MOZ_ASSERT(aLock->isNothing()); + aLock->emplace(mChangeMutex); + + // Now that we are locked, we can check mDrawTarget. If it's null, then + // we're not shared and we can unlock eagerly. + if (!mDrawTarget) { + aLock->reset(); + } + } else { + DrawTargetWillChange(); + } + sk_sp<SkImage> image = mImage; + return image; +} + +static sk_sp<SkData> MakeSkData(void* aData, int32_t aHeight, size_t aStride) { + CheckedInt<size_t> size = aStride; + size *= aHeight; + if (size.isValid()) { + void* mem = sk_malloc_flags(size.value(), 0); + if (mem) { + if (aData) { + memcpy(mem, aData, size.value()); + } + return SkData::MakeFromMalloc(mem, size.value()); + } + } + return nullptr; +} + +static sk_sp<SkImage> ReadSkImage(const sk_sp<SkImage>& aImage, + const SkImageInfo& aInfo, size_t aStride, + int aX = 0, int aY = 0) { + if (sk_sp<SkData> data = MakeSkData(nullptr, aInfo.height(), aStride)) { + if (aImage->readPixels(aInfo, data->writable_data(), aStride, aX, aY, + SkImage::kDisallow_CachingHint)) { + return SkImage::MakeRasterData(aInfo, data, aStride); + } + } + return nullptr; +} + +bool SourceSurfaceSkia::InitFromData(unsigned char* aData, const IntSize& aSize, + int32_t aStride, SurfaceFormat aFormat) { + sk_sp<SkData> data = MakeSkData(aData, aSize.height, aStride); + if (!data) { + return false; + } + + SkImageInfo info = MakeSkiaImageInfo(aSize, aFormat); + mImage = SkImage::MakeRasterData(info, data, aStride); + if (!mImage) { + return false; + } + + mSize = aSize; + mFormat = aFormat; + mStride = aStride; + return true; +} + +bool SourceSurfaceSkia::InitFromImage(const sk_sp<SkImage>& aImage, + SurfaceFormat aFormat, + DrawTargetSkia* aOwner) { + if (!aImage) { + return false; + } + + mSize = IntSize(aImage->width(), aImage->height()); + + // For the raster image case, we want to use the format and stride + // information that the underlying raster image is using, which is + // reliable. + // For the GPU case (for which peekPixels is false), we can't easily + // figure this information out. It is better to report the originally + // intended format and stride that we will convert to if this GPU + // image is ever read back into a raster image. + SkPixmap pixmap; + if (aImage->peekPixels(&pixmap)) { + mFormat = + aFormat != SurfaceFormat::UNKNOWN + ? aFormat + : SkiaColorTypeToGfxFormat(pixmap.colorType(), pixmap.alphaType()); + mStride = pixmap.rowBytes(); + } else if (aFormat != SurfaceFormat::UNKNOWN) { + mFormat = aFormat; + SkImageInfo info = MakeSkiaImageInfo(mSize, mFormat); + mStride = GetAlignedStride<4>(info.width(), info.bytesPerPixel()); + if (!mStride) { + return false; + } + } else { + return false; + } + + mImage = aImage; + + if (aOwner) { + mDrawTarget = aOwner; + } + + return true; +} + +already_AddRefed<SourceSurface> SourceSurfaceSkia::ExtractSubrect( + const IntRect& aRect) { + if (!mImage || aRect.IsEmpty() || !GetRect().Contains(aRect)) { + return nullptr; + } + SkImageInfo info = MakeSkiaImageInfo(aRect.Size(), mFormat); + size_t stride = GetAlignedStride<4>(info.width(), info.bytesPerPixel()); + if (!stride) { + return nullptr; + } + sk_sp<SkImage> subImage = ReadSkImage(mImage, info, stride, aRect.x, aRect.y); + if (!subImage) { + return nullptr; + } + RefPtr<SourceSurfaceSkia> surface = new SourceSurfaceSkia; + if (!surface->InitFromImage(subImage)) { + return nullptr; + } + return surface.forget().downcast<SourceSurface>(); +} + +uint8_t* SourceSurfaceSkia::GetData() { + if (!mImage) { + return nullptr; + } + SkPixmap pixmap; + if (!mImage->peekPixels(&pixmap)) { + gfxCriticalError() << "Failed accessing pixels for Skia raster image"; + } + return reinterpret_cast<uint8_t*>(pixmap.writable_addr()); +} + +bool SourceSurfaceSkia::Map(MapType, MappedSurface* aMappedSurface) + MOZ_NO_THREAD_SAFETY_ANALYSIS { + mChangeMutex.Lock(); + aMappedSurface->mData = GetData(); + aMappedSurface->mStride = Stride(); + mIsMapped = !!aMappedSurface->mData; + bool isMapped = mIsMapped; + if (!mIsMapped) { + mChangeMutex.Unlock(); + } + // Static analysis will warn due to a conditional Unlock + MOZ_PUSH_IGNORE_THREAD_SAFETY + return isMapped; + MOZ_POP_THREAD_SAFETY +} + +void SourceSurfaceSkia::Unmap() MOZ_NO_THREAD_SAFETY_ANALYSIS { + mChangeMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(mIsMapped); + mIsMapped = false; + mChangeMutex.Unlock(); +} + +void SourceSurfaceSkia::DrawTargetWillChange() { + MutexAutoLock lock(mChangeMutex); + if (mDrawTarget.exchange(nullptr)) { + // Raster snapshots do not use Skia's internal copy-on-write mechanism, + // so we need to do an explicit copy here. + // GPU snapshots, for which peekPixels is false, will already be dealt + // with automatically via the internal copy-on-write mechanism, so we + // don't need to do anything for them here. + SkPixmap pixmap; + if (mImage->peekPixels(&pixmap)) { + mImage = ReadSkImage(mImage, pixmap.info(), pixmap.rowBytes()); + if (!mImage) { + gfxCriticalError() << "Failed copying Skia raster snapshot"; + } + } + } +} + +} // namespace mozilla::gfx diff --git a/gfx/2d/SourceSurfaceSkia.h b/gfx/2d/SourceSurfaceSkia.h new file mode 100644 index 0000000000..e0f085d2d4 --- /dev/null +++ b/gfx/2d/SourceSurfaceSkia.h @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SOURCESURFACESKIA_H_ +#define MOZILLA_GFX_SOURCESURFACESKIA_H_ + +#include "2D.h" +#include "mozilla/Mutex.h" +#include "skia/include/core/SkRefCnt.h" + +class SkImage; +class SkSurface; + +namespace mozilla { + +namespace gfx { + +class DrawTargetSkia; +class SnapshotLock; + +class SourceSurfaceSkia : public DataSourceSurface { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DataSourceSurfaceSkia, override) + + SourceSurfaceSkia(); + virtual ~SourceSurfaceSkia(); + + SurfaceType GetType() const override { return SurfaceType::SKIA; } + IntSize GetSize() const override; + SurfaceFormat GetFormat() const override; + + void GiveSurface(SkSurface* aSurface); + + sk_sp<SkImage> GetImage(Maybe<MutexAutoLock>* aLock); + + bool InitFromData(unsigned char* aData, const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat); + + bool InitFromImage(const sk_sp<SkImage>& aImage, + SurfaceFormat aFormat = SurfaceFormat::UNKNOWN, + DrawTargetSkia* aOwner = nullptr); + + already_AddRefed<SourceSurface> ExtractSubrect(const IntRect& aRect) override; + + uint8_t* GetData() override; + + /** + * The caller is responsible for ensuring aMappedSurface is not null. + */ + bool Map(MapType, MappedSurface* aMappedSurface) override; + + void Unmap() override; + + int32_t Stride() override { return mStride; } + + private: + friend class DrawTargetSkia; + + void DrawTargetWillChange(); + + sk_sp<SkImage> mImage; + // This keeps a surface alive if needed because its DrawTarget has gone away. + sk_sp<SkSurface> mSurface; + SurfaceFormat mFormat; + IntSize mSize; + int32_t mStride; + Atomic<DrawTargetSkia*> mDrawTarget; + Mutex mChangeMutex MOZ_UNANNOTATED; + bool mIsMapped; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SOURCESURFACESKIA_H_ */ diff --git a/gfx/2d/StackArray.h b/gfx/2d/StackArray.h new file mode 100644 index 0000000000..165d45cb12 --- /dev/null +++ b/gfx/2d/StackArray.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* A handy class that will allocate data for size*T objects on the stack and + * otherwise allocate them on the heap. It is similar in purpose to AutoTArray + */ + +template <class T, size_t size> +class StackArray final { + public: + explicit StackArray(size_t count) { + if (count > size) { + mData = new T[count]; + } else { + mData = mStackData; + } + } + ~StackArray() { + if (mData != mStackData) { + delete[] mData; + } + } + T& operator[](size_t n) { return mData[n]; } + const T& operator[](size_t n) const { return mData[n]; } + T* data() { return mData; }; + + private: + T mStackData[size]; + T* mData; +}; diff --git a/gfx/2d/Swizzle.cpp b/gfx/2d/Swizzle.cpp new file mode 100644 index 0000000000..03647348f3 --- /dev/null +++ b/gfx/2d/Swizzle.cpp @@ -0,0 +1,1574 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Swizzle.h" +#include "Logging.h" +#include "Orientation.h" +#include "Tools.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/EndianUtils.h" +#include "mozilla/UniquePtr.h" + +#ifdef USE_SSE2 +# include "mozilla/SSE.h" +#endif + +#ifdef USE_NEON +# include "mozilla/arm.h" +#endif + +#include <new> + +namespace mozilla { +namespace gfx { + +/** + * Convenience macros for dispatching to various format combinations. + */ + +// Hash the formats to a relatively dense value to optimize jump table +// generation. The first 6 formats in SurfaceFormat are the 32-bit BGRA variants +// and are the most common formats dispatched here. Room is reserved in the +// lowish bits for up to these 6 destination formats. If a destination format is +// >= 6, the 6th bit is set to avoid collisions. +#define FORMAT_KEY(aSrcFormat, aDstFormat) \ + (int(aSrcFormat) * 6 + int(aDstFormat) + (int(int(aDstFormat) >= 6) << 6)) + +#define FORMAT_CASE_EXPR(aSrcFormat, aDstFormat, ...) \ + case FORMAT_KEY(aSrcFormat, aDstFormat): \ + __VA_ARGS__; \ + return true; + +#define FORMAT_CASE(aSrcFormat, aDstFormat, ...) \ + FORMAT_CASE_EXPR(aSrcFormat, aDstFormat, FORMAT_CASE_CALL(__VA_ARGS__)) + +#define FORMAT_CASE_ROW(aSrcFormat, aDstFormat, ...) \ + case FORMAT_KEY(aSrcFormat, aDstFormat): \ + return &__VA_ARGS__; + +/** + * Constexpr functions for analyzing format attributes in templates. + */ + +// Whether B comes before R in pixel memory layout. +static constexpr bool IsBGRFormat(SurfaceFormat aFormat) { + return aFormat == SurfaceFormat::B8G8R8A8 || +#if MOZ_LITTLE_ENDIAN() + aFormat == SurfaceFormat::R5G6B5_UINT16 || +#endif + aFormat == SurfaceFormat::B8G8R8X8 || aFormat == SurfaceFormat::B8G8R8; +} + +// Whether the order of B and R need to be swapped to map from src to dst. +static constexpr bool ShouldSwapRB(SurfaceFormat aSrcFormat, + SurfaceFormat aDstFormat) { + return IsBGRFormat(aSrcFormat) != IsBGRFormat(aDstFormat); +} + +// The starting byte of the RGB components in pixel memory. +static constexpr uint32_t RGBByteIndex(SurfaceFormat aFormat) { + return aFormat == SurfaceFormat::A8R8G8B8 || + aFormat == SurfaceFormat::X8R8G8B8 + ? 1 + : 0; +} + +// The byte of the alpha component, which just comes after RGB. +static constexpr uint32_t AlphaByteIndex(SurfaceFormat aFormat) { + return (RGBByteIndex(aFormat) + 3) % 4; +} + +// The endian-dependent bit shift to access RGB of a UINT32 pixel. +static constexpr uint32_t RGBBitShift(SurfaceFormat aFormat) { +#if MOZ_LITTLE_ENDIAN() + return 8 * RGBByteIndex(aFormat); +#else + return 8 - 8 * RGBByteIndex(aFormat); +#endif +} + +// The endian-dependent bit shift to access alpha of a UINT32 pixel. +static constexpr uint32_t AlphaBitShift(SurfaceFormat aFormat) { + return (RGBBitShift(aFormat) + 24) % 32; +} + +// Whether the pixel format should ignore the value of the alpha channel and +// treat it as opaque. +static constexpr bool IgnoreAlpha(SurfaceFormat aFormat) { + return aFormat == SurfaceFormat::B8G8R8X8 || + aFormat == SurfaceFormat::R8G8B8X8 || + aFormat == SurfaceFormat::X8R8G8B8; +} + +// Whether to force alpha to opaque to map from src to dst. +static constexpr bool ShouldForceOpaque(SurfaceFormat aSrcFormat, + SurfaceFormat aDstFormat) { + return IgnoreAlpha(aSrcFormat) != IgnoreAlpha(aDstFormat); +} + +#ifdef USE_SSE2 +/** + * SSE2 optimizations + */ + +template <bool aSwapRB, bool aOpaqueAlpha> +void Premultiply_SSE2(const uint8_t*, int32_t, uint8_t*, int32_t, IntSize); + +# define PREMULTIPLY_SSE2(aSrcFormat, aDstFormat) \ + FORMAT_CASE(aSrcFormat, aDstFormat, \ + Premultiply_SSE2<ShouldSwapRB(aSrcFormat, aDstFormat), \ + ShouldForceOpaque(aSrcFormat, aDstFormat)>) + +template <bool aSwapRB, bool aOpaqueAlpha> +void PremultiplyRow_SSE2(const uint8_t*, uint8_t*, int32_t); + +# define PREMULTIPLY_ROW_SSE2(aSrcFormat, aDstFormat) \ + FORMAT_CASE_ROW( \ + aSrcFormat, aDstFormat, \ + PremultiplyRow_SSE2<ShouldSwapRB(aSrcFormat, aDstFormat), \ + ShouldForceOpaque(aSrcFormat, aDstFormat)>) + +template <bool aSwapRB> +void Unpremultiply_SSE2(const uint8_t*, int32_t, uint8_t*, int32_t, IntSize); + +# define UNPREMULTIPLY_SSE2(aSrcFormat, aDstFormat) \ + FORMAT_CASE(aSrcFormat, aDstFormat, \ + Unpremultiply_SSE2<ShouldSwapRB(aSrcFormat, aDstFormat)>) + +template <bool aSwapRB> +void UnpremultiplyRow_SSE2(const uint8_t*, uint8_t*, int32_t); + +# define UNPREMULTIPLY_ROW_SSE2(aSrcFormat, aDstFormat) \ + FORMAT_CASE_ROW( \ + aSrcFormat, aDstFormat, \ + UnpremultiplyRow_SSE2<ShouldSwapRB(aSrcFormat, aDstFormat)>) + +template <bool aSwapRB, bool aOpaqueAlpha> +void Swizzle_SSE2(const uint8_t*, int32_t, uint8_t*, int32_t, IntSize); + +# define SWIZZLE_SSE2(aSrcFormat, aDstFormat) \ + FORMAT_CASE(aSrcFormat, aDstFormat, \ + Swizzle_SSE2<ShouldSwapRB(aSrcFormat, aDstFormat), \ + ShouldForceOpaque(aSrcFormat, aDstFormat)>) + +template <bool aSwapRB, bool aOpaqueAlpha> +void SwizzleRow_SSE2(const uint8_t*, uint8_t*, int32_t); + +# define SWIZZLE_ROW_SSE2(aSrcFormat, aDstFormat) \ + FORMAT_CASE_ROW( \ + aSrcFormat, aDstFormat, \ + SwizzleRow_SSE2<ShouldSwapRB(aSrcFormat, aDstFormat), \ + ShouldForceOpaque(aSrcFormat, aDstFormat)>) + +template <bool aSwapRB> +void UnpackRowRGB24_SSSE3(const uint8_t*, uint8_t*, int32_t); + +# define UNPACK_ROW_RGB_SSSE3(aDstFormat) \ + FORMAT_CASE_ROW( \ + SurfaceFormat::R8G8B8, aDstFormat, \ + UnpackRowRGB24_SSSE3<ShouldSwapRB(SurfaceFormat::R8G8B8, aDstFormat)>) + +template <bool aSwapRB> +void UnpackRowRGB24_AVX2(const uint8_t*, uint8_t*, int32_t); + +# define UNPACK_ROW_RGB_AVX2(aDstFormat) \ + FORMAT_CASE_ROW( \ + SurfaceFormat::R8G8B8, aDstFormat, \ + UnpackRowRGB24_AVX2<ShouldSwapRB(SurfaceFormat::R8G8B8, aDstFormat)>) + +#endif + +#ifdef USE_NEON +/** + * ARM NEON optimizations + */ + +template <bool aSwapRB, bool aOpaqueAlpha> +void Premultiply_NEON(const uint8_t*, int32_t, uint8_t*, int32_t, IntSize); + +# define PREMULTIPLY_NEON(aSrcFormat, aDstFormat) \ + FORMAT_CASE(aSrcFormat, aDstFormat, \ + Premultiply_NEON<ShouldSwapRB(aSrcFormat, aDstFormat), \ + ShouldForceOpaque(aSrcFormat, aDstFormat)>) + +template <bool aSwapRB, bool aOpaqueAlpha> +void PremultiplyRow_NEON(const uint8_t*, uint8_t*, int32_t); + +# define PREMULTIPLY_ROW_NEON(aSrcFormat, aDstFormat) \ + FORMAT_CASE_ROW( \ + aSrcFormat, aDstFormat, \ + PremultiplyRow_NEON<ShouldSwapRB(aSrcFormat, aDstFormat), \ + ShouldForceOpaque(aSrcFormat, aDstFormat)>) + +template <bool aSwapRB> +void Unpremultiply_NEON(const uint8_t*, int32_t, uint8_t*, int32_t, IntSize); + +# define UNPREMULTIPLY_NEON(aSrcFormat, aDstFormat) \ + FORMAT_CASE(aSrcFormat, aDstFormat, \ + Unpremultiply_NEON<ShouldSwapRB(aSrcFormat, aDstFormat)>) + +template <bool aSwapRB> +void UnpremultiplyRow_NEON(const uint8_t*, uint8_t*, int32_t); + +# define UNPREMULTIPLY_ROW_NEON(aSrcFormat, aDstFormat) \ + FORMAT_CASE_ROW( \ + aSrcFormat, aDstFormat, \ + UnpremultiplyRow_NEON<ShouldSwapRB(aSrcFormat, aDstFormat)>) + +template <bool aSwapRB, bool aOpaqueAlpha> +void Swizzle_NEON(const uint8_t*, int32_t, uint8_t*, int32_t, IntSize); + +# define SWIZZLE_NEON(aSrcFormat, aDstFormat) \ + FORMAT_CASE(aSrcFormat, aDstFormat, \ + Swizzle_NEON<ShouldSwapRB(aSrcFormat, aDstFormat), \ + ShouldForceOpaque(aSrcFormat, aDstFormat)>) + +template <bool aSwapRB, bool aOpaqueAlpha> +void SwizzleRow_NEON(const uint8_t*, uint8_t*, int32_t); + +# define SWIZZLE_ROW_NEON(aSrcFormat, aDstFormat) \ + FORMAT_CASE_ROW( \ + aSrcFormat, aDstFormat, \ + SwizzleRow_NEON<ShouldSwapRB(aSrcFormat, aDstFormat), \ + ShouldForceOpaque(aSrcFormat, aDstFormat)>) + +template <bool aSwapRB> +void UnpackRowRGB24_NEON(const uint8_t*, uint8_t*, int32_t); + +# define UNPACK_ROW_RGB_NEON(aDstFormat) \ + FORMAT_CASE_ROW( \ + SurfaceFormat::R8G8B8, aDstFormat, \ + UnpackRowRGB24_NEON<ShouldSwapRB(SurfaceFormat::R8G8B8, aDstFormat)>) +#endif + +/** + * Premultiplying + */ + +// Fallback premultiply implementation that uses splayed pixel math to reduce +// the multiplications used. That is, the R and B components are isolated from +// the G and A components, which then can be multiplied as if they were two +// 2-component vectors. Otherwise, an approximation if divide-by-255 is used +// which is faster than an actual division. These optimizations are also used +// for the SSE2 and NEON implementations. +template <bool aSwapRB, bool aOpaqueAlpha, uint32_t aSrcRGBShift, + uint32_t aSrcAShift, uint32_t aDstRGBShift, uint32_t aDstAShift> +static void PremultiplyChunkFallback(const uint8_t*& aSrc, uint8_t*& aDst, + int32_t aLength) { + const uint8_t* end = aSrc + 4 * aLength; + do { + // Load and process 1 entire pixel at a time. + uint32_t color = *reinterpret_cast<const uint32_t*>(aSrc); + + uint32_t a = aSrcAShift ? color >> aSrcAShift : color & 0xFF; + + // Isolate the R and B components. + uint32_t rb = (color >> aSrcRGBShift) & 0x00FF00FF; + // Swap the order of R and B if necessary. + if (aSwapRB) { + rb = (rb >> 16) | (rb << 16); + } + // Approximate the multiply by alpha and divide by 255 which is + // essentially: + // c = c*a + 255; c = (c + (c >> 8)) >> 8; + // However, we omit the final >> 8 to fold it with the final shift into + // place depending on desired output format. + rb = rb * a + 0x00FF00FF; + rb = (rb + ((rb >> 8) & 0x00FF00FF)) & 0xFF00FF00; + + // Use same approximation as above, but G is shifted 8 bits left. + // Alpha is left out and handled separately. + uint32_t g = color & (0xFF00 << aSrcRGBShift); + g = g * a + (0xFF00 << aSrcRGBShift); + g = (g + (g >> 8)) & (0xFF0000 << aSrcRGBShift); + + // The above math leaves RGB shifted left by 8 bits. + // Shift them right if required for the output format. + // then combine them back together to produce output pixel. + // Add the alpha back on if the output format is not opaque. + *reinterpret_cast<uint32_t*>(aDst) = + (rb >> (8 - aDstRGBShift)) | (g >> (8 + aSrcRGBShift - aDstRGBShift)) | + (aOpaqueAlpha ? 0xFF << aDstAShift : a << aDstAShift); + + aSrc += 4; + aDst += 4; + } while (aSrc < end); +} + +template <bool aSwapRB, bool aOpaqueAlpha, uint32_t aSrcRGBShift, + uint32_t aSrcAShift, uint32_t aDstRGBShift, uint32_t aDstAShift> +static void PremultiplyRowFallback(const uint8_t* aSrc, uint8_t* aDst, + int32_t aLength) { + PremultiplyChunkFallback<aSwapRB, aOpaqueAlpha, aSrcRGBShift, aSrcAShift, + aDstRGBShift, aDstAShift>(aSrc, aDst, aLength); +} + +template <bool aSwapRB, bool aOpaqueAlpha, uint32_t aSrcRGBShift, + uint32_t aSrcAShift, uint32_t aDstRGBShift, uint32_t aDstAShift> +static void PremultiplyFallback(const uint8_t* aSrc, int32_t aSrcGap, + uint8_t* aDst, int32_t aDstGap, IntSize aSize) { + for (int32_t height = aSize.height; height > 0; height--) { + PremultiplyChunkFallback<aSwapRB, aOpaqueAlpha, aSrcRGBShift, aSrcAShift, + aDstRGBShift, aDstAShift>(aSrc, aDst, aSize.width); + aSrc += aSrcGap; + aDst += aDstGap; + } +} + +#define PREMULTIPLY_FALLBACK_CASE(aSrcFormat, aDstFormat) \ + FORMAT_CASE( \ + aSrcFormat, aDstFormat, \ + PremultiplyFallback<ShouldSwapRB(aSrcFormat, aDstFormat), \ + ShouldForceOpaque(aSrcFormat, aDstFormat), \ + RGBBitShift(aSrcFormat), AlphaBitShift(aSrcFormat), \ + RGBBitShift(aDstFormat), AlphaBitShift(aDstFormat)>) + +#define PREMULTIPLY_FALLBACK(aSrcFormat) \ + PREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::B8G8R8A8) \ + PREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::B8G8R8X8) \ + PREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::R8G8B8A8) \ + PREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::R8G8B8X8) \ + PREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::A8R8G8B8) \ + PREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::X8R8G8B8) + +#define PREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, aDstFormat) \ + FORMAT_CASE_ROW(aSrcFormat, aDstFormat, \ + PremultiplyRowFallback< \ + ShouldSwapRB(aSrcFormat, aDstFormat), \ + ShouldForceOpaque(aSrcFormat, aDstFormat), \ + RGBBitShift(aSrcFormat), AlphaBitShift(aSrcFormat), \ + RGBBitShift(aDstFormat), AlphaBitShift(aDstFormat)>) + +#define PREMULTIPLY_ROW_FALLBACK(aSrcFormat) \ + PREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::B8G8R8A8) \ + PREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::B8G8R8X8) \ + PREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::R8G8B8A8) \ + PREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::R8G8B8X8) \ + PREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::A8R8G8B8) \ + PREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::X8R8G8B8) + +// If rows are tightly packed, and the size of the total area will fit within +// the precision range of a single row, then process all the data as if it was +// a single row. +static inline IntSize CollapseSize(const IntSize& aSize, int32_t aSrcStride, + int32_t aDstStride) { + if (aSrcStride == aDstStride && (aSrcStride & 3) == 0 && + aSrcStride / 4 == aSize.width) { + CheckedInt32 area = CheckedInt32(aSize.width) * CheckedInt32(aSize.height); + if (area.isValid()) { + return IntSize(area.value(), 1); + } + } + return aSize; +} + +static inline int32_t GetStrideGap(int32_t aWidth, SurfaceFormat aFormat, + int32_t aStride) { + CheckedInt32 used = CheckedInt32(aWidth) * BytesPerPixel(aFormat); + if (!used.isValid() || used.value() < 0) { + return -1; + } + return aStride - used.value(); +} + +bool PremultiplyData(const uint8_t* aSrc, int32_t aSrcStride, + SurfaceFormat aSrcFormat, uint8_t* aDst, + int32_t aDstStride, SurfaceFormat aDstFormat, + const IntSize& aSize) { + if (aSize.IsEmpty()) { + return true; + } + IntSize size = CollapseSize(aSize, aSrcStride, aDstStride); + // Find gap from end of row to the start of the next row. + int32_t srcGap = GetStrideGap(aSize.width, aSrcFormat, aSrcStride); + int32_t dstGap = GetStrideGap(aSize.width, aDstFormat, aDstStride); + MOZ_ASSERT(srcGap >= 0 && dstGap >= 0); + if (srcGap < 0 || dstGap < 0) { + return false; + } + +#define FORMAT_CASE_CALL(...) __VA_ARGS__(aSrc, srcGap, aDst, dstGap, size) + +#ifdef USE_SSE2 + if (mozilla::supports_sse2()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + PREMULTIPLY_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8) + PREMULTIPLY_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8X8) + PREMULTIPLY_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8) + PREMULTIPLY_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8) + PREMULTIPLY_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8A8) + PREMULTIPLY_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8X8) + PREMULTIPLY_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8) + PREMULTIPLY_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8) + default: + break; + } +#endif + +#ifdef USE_NEON + if (mozilla::supports_neon()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + PREMULTIPLY_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8) + PREMULTIPLY_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8X8) + PREMULTIPLY_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8) + PREMULTIPLY_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8) + PREMULTIPLY_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8A8) + PREMULTIPLY_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8X8) + PREMULTIPLY_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8) + PREMULTIPLY_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8) + default: + break; + } +#endif + + switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + PREMULTIPLY_FALLBACK(SurfaceFormat::B8G8R8A8) + PREMULTIPLY_FALLBACK(SurfaceFormat::R8G8B8A8) + PREMULTIPLY_FALLBACK(SurfaceFormat::A8R8G8B8) + default: + break; + } + +#undef FORMAT_CASE_CALL + + MOZ_ASSERT(false, "Unsupported premultiply formats"); + return false; +} + +SwizzleRowFn PremultiplyRow(SurfaceFormat aSrcFormat, + SurfaceFormat aDstFormat) { +#ifdef USE_SSE2 + if (mozilla::supports_sse2()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + PREMULTIPLY_ROW_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8) + PREMULTIPLY_ROW_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8X8) + PREMULTIPLY_ROW_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8) + PREMULTIPLY_ROW_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8) + PREMULTIPLY_ROW_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8A8) + PREMULTIPLY_ROW_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8X8) + PREMULTIPLY_ROW_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8) + PREMULTIPLY_ROW_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8) + default: + break; + } +#endif + +#ifdef USE_NEON + if (mozilla::supports_neon()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + PREMULTIPLY_ROW_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8) + PREMULTIPLY_ROW_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8X8) + PREMULTIPLY_ROW_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8) + PREMULTIPLY_ROW_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8) + PREMULTIPLY_ROW_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8A8) + PREMULTIPLY_ROW_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8X8) + PREMULTIPLY_ROW_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8) + PREMULTIPLY_ROW_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8) + default: + break; + } +#endif + + switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + PREMULTIPLY_ROW_FALLBACK(SurfaceFormat::B8G8R8A8) + PREMULTIPLY_ROW_FALLBACK(SurfaceFormat::R8G8B8A8) + PREMULTIPLY_ROW_FALLBACK(SurfaceFormat::A8R8G8B8) + default: + break; + } + + MOZ_ASSERT_UNREACHABLE("Unsupported premultiply formats"); + return nullptr; +} + +/** + * Unpremultiplying + */ + +// Generate a table of 8.16 fixed-point reciprocals representing 1/alpha. +#define UNPREMULQ(x) (0xFF00FFU / (x)) +#define UNPREMULQ_2(x) UNPREMULQ(x), UNPREMULQ((x) + 1) +#define UNPREMULQ_4(x) UNPREMULQ_2(x), UNPREMULQ_2((x) + 2) +#define UNPREMULQ_8(x) UNPREMULQ_4(x), UNPREMULQ_4((x) + 4) +#define UNPREMULQ_16(x) UNPREMULQ_8(x), UNPREMULQ_8((x) + 8) +#define UNPREMULQ_32(x) UNPREMULQ_16(x), UNPREMULQ_16((x) + 16) +static const uint32_t sUnpremultiplyTable[256] = {0, + UNPREMULQ(1), + UNPREMULQ_2(2), + UNPREMULQ_4(4), + UNPREMULQ_8(8), + UNPREMULQ_16(16), + UNPREMULQ_32(32), + UNPREMULQ_32(64), + UNPREMULQ_32(96), + UNPREMULQ_32(128), + UNPREMULQ_32(160), + UNPREMULQ_32(192), + UNPREMULQ_32(224)}; + +// Fallback unpremultiply implementation that uses 8.16 fixed-point reciprocal +// math to eliminate any division by the alpha component. This optimization is +// used for the SSE2 and NEON implementations, with some adaptations. This +// implementation also accesses color components using individual byte accesses +// as this profiles faster than accessing the pixel as a uint32_t and +// shifting/masking to access components. +template <bool aSwapRB, uint32_t aSrcRGBIndex, uint32_t aSrcAIndex, + uint32_t aDstRGBIndex, uint32_t aDstAIndex> +static void UnpremultiplyChunkFallback(const uint8_t*& aSrc, uint8_t*& aDst, + int32_t aLength) { + const uint8_t* end = aSrc + 4 * aLength; + do { + uint8_t r = aSrc[aSrcRGBIndex + (aSwapRB ? 2 : 0)]; + uint8_t g = aSrc[aSrcRGBIndex + 1]; + uint8_t b = aSrc[aSrcRGBIndex + (aSwapRB ? 0 : 2)]; + uint8_t a = aSrc[aSrcAIndex]; + + // Access the 8.16 reciprocal from the table based on alpha. Multiply by + // the reciprocal and shift off the fraction bits to approximate the + // division by alpha. + uint32_t q = sUnpremultiplyTable[a]; + aDst[aDstRGBIndex + 0] = (r * q) >> 16; + aDst[aDstRGBIndex + 1] = (g * q) >> 16; + aDst[aDstRGBIndex + 2] = (b * q) >> 16; + aDst[aDstAIndex] = a; + + aSrc += 4; + aDst += 4; + } while (aSrc < end); +} + +template <bool aSwapRB, uint32_t aSrcRGBIndex, uint32_t aSrcAIndex, + uint32_t aDstRGBIndex, uint32_t aDstAIndex> +static void UnpremultiplyRowFallback(const uint8_t* aSrc, uint8_t* aDst, + int32_t aLength) { + UnpremultiplyChunkFallback<aSwapRB, aSrcRGBIndex, aSrcAIndex, aDstRGBIndex, + aDstAIndex>(aSrc, aDst, aLength); +} + +template <bool aSwapRB, uint32_t aSrcRGBIndex, uint32_t aSrcAIndex, + uint32_t aDstRGBIndex, uint32_t aDstAIndex> +static void UnpremultiplyFallback(const uint8_t* aSrc, int32_t aSrcGap, + uint8_t* aDst, int32_t aDstGap, + IntSize aSize) { + for (int32_t height = aSize.height; height > 0; height--) { + UnpremultiplyChunkFallback<aSwapRB, aSrcRGBIndex, aSrcAIndex, aDstRGBIndex, + aDstAIndex>(aSrc, aDst, aSize.width); + aSrc += aSrcGap; + aDst += aDstGap; + } +} + +#define UNPREMULTIPLY_FALLBACK_CASE(aSrcFormat, aDstFormat) \ + FORMAT_CASE(aSrcFormat, aDstFormat, \ + UnpremultiplyFallback< \ + ShouldSwapRB(aSrcFormat, aDstFormat), \ + RGBByteIndex(aSrcFormat), AlphaByteIndex(aSrcFormat), \ + RGBByteIndex(aDstFormat), AlphaByteIndex(aDstFormat)>) + +#define UNPREMULTIPLY_FALLBACK(aSrcFormat) \ + UNPREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::B8G8R8A8) \ + UNPREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::R8G8B8A8) \ + UNPREMULTIPLY_FALLBACK_CASE(aSrcFormat, SurfaceFormat::A8R8G8B8) + +#define UNPREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, aDstFormat) \ + FORMAT_CASE_ROW(aSrcFormat, aDstFormat, \ + UnpremultiplyRowFallback< \ + ShouldSwapRB(aSrcFormat, aDstFormat), \ + RGBByteIndex(aSrcFormat), AlphaByteIndex(aSrcFormat), \ + RGBByteIndex(aDstFormat), AlphaByteIndex(aDstFormat)>) + +#define UNPREMULTIPLY_ROW_FALLBACK(aSrcFormat) \ + UNPREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::B8G8R8A8) \ + UNPREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::R8G8B8A8) \ + UNPREMULTIPLY_ROW_FALLBACK_CASE(aSrcFormat, SurfaceFormat::A8R8G8B8) + +bool UnpremultiplyData(const uint8_t* aSrc, int32_t aSrcStride, + SurfaceFormat aSrcFormat, uint8_t* aDst, + int32_t aDstStride, SurfaceFormat aDstFormat, + const IntSize& aSize) { + if (aSize.IsEmpty()) { + return true; + } + IntSize size = CollapseSize(aSize, aSrcStride, aDstStride); + // Find gap from end of row to the start of the next row. + int32_t srcGap = GetStrideGap(aSize.width, aSrcFormat, aSrcStride); + int32_t dstGap = GetStrideGap(aSize.width, aDstFormat, aDstStride); + MOZ_ASSERT(srcGap >= 0 && dstGap >= 0); + if (srcGap < 0 || dstGap < 0) { + return false; + } + +#define FORMAT_CASE_CALL(...) __VA_ARGS__(aSrc, srcGap, aDst, dstGap, size) + +#ifdef USE_SSE2 + if (mozilla::supports_sse2()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + UNPREMULTIPLY_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8) + UNPREMULTIPLY_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8) + UNPREMULTIPLY_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8A8) + UNPREMULTIPLY_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8) + default: + break; + } +#endif + +#ifdef USE_NEON + if (mozilla::supports_neon()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + UNPREMULTIPLY_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8) + UNPREMULTIPLY_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8) + UNPREMULTIPLY_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8A8) + UNPREMULTIPLY_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8) + default: + break; + } +#endif + + switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + UNPREMULTIPLY_FALLBACK(SurfaceFormat::B8G8R8A8) + UNPREMULTIPLY_FALLBACK(SurfaceFormat::R8G8B8A8) + UNPREMULTIPLY_FALLBACK(SurfaceFormat::A8R8G8B8) + default: + break; + } + +#undef FORMAT_CASE_CALL + + MOZ_ASSERT(false, "Unsupported unpremultiply formats"); + return false; +} + +SwizzleRowFn UnpremultiplyRow(SurfaceFormat aSrcFormat, + SurfaceFormat aDstFormat) { +#ifdef USE_SSE2 + if (mozilla::supports_sse2()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + UNPREMULTIPLY_ROW_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8) + UNPREMULTIPLY_ROW_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8) + UNPREMULTIPLY_ROW_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8A8) + UNPREMULTIPLY_ROW_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8) + default: + break; + } +#endif + +#ifdef USE_NEON + if (mozilla::supports_neon()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + UNPREMULTIPLY_ROW_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8) + UNPREMULTIPLY_ROW_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8) + UNPREMULTIPLY_ROW_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8A8) + UNPREMULTIPLY_ROW_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8) + default: + break; + } +#endif + + switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + UNPREMULTIPLY_ROW_FALLBACK(SurfaceFormat::B8G8R8A8) + UNPREMULTIPLY_ROW_FALLBACK(SurfaceFormat::R8G8B8A8) + UNPREMULTIPLY_ROW_FALLBACK(SurfaceFormat::A8R8G8B8) + default: + break; + } + + MOZ_ASSERT_UNREACHABLE("Unsupported premultiply formats"); + return nullptr; +} + +/** + * Swizzling + */ + +// Fallback swizzle implementation that uses shifting and masking to reorder +// pixels. +template <bool aSwapRB, bool aOpaqueAlpha, uint32_t aSrcRGBShift, + uint32_t aSrcAShift, uint32_t aDstRGBShift, uint32_t aDstAShift> +static void SwizzleChunkFallback(const uint8_t*& aSrc, uint8_t*& aDst, + int32_t aLength) { + const uint8_t* end = aSrc + 4 * aLength; + do { + uint32_t rgba = *reinterpret_cast<const uint32_t*>(aSrc); + + if (aSwapRB) { + // Handle R and B swaps by exchanging words and masking. + uint32_t rb = + ((rgba << 16) | (rgba >> 16)) & (0x00FF00FF << aSrcRGBShift); + uint32_t ga = rgba & ((0xFF << aSrcAShift) | (0xFF00 << aSrcRGBShift)); + rgba = rb | ga; + } + + // If src and dst shifts differ, rotate left or right to move RGB into + // place, i.e. ARGB -> RGBA or ARGB -> RGBA. + if (aDstRGBShift > aSrcRGBShift) { + rgba = (rgba << 8) | (aOpaqueAlpha ? 0x000000FF : rgba >> 24); + } else if (aSrcRGBShift > aDstRGBShift) { + rgba = (rgba >> 8) | (aOpaqueAlpha ? 0xFF000000 : rgba << 24); + } else if (aOpaqueAlpha) { + rgba |= 0xFF << aDstAShift; + } + + *reinterpret_cast<uint32_t*>(aDst) = rgba; + + aSrc += 4; + aDst += 4; + } while (aSrc < end); +} + +template <bool aSwapRB, bool aOpaqueAlpha, uint32_t aSrcRGBShift, + uint32_t aSrcAShift, uint32_t aDstRGBShift, uint32_t aDstAShift> +static void SwizzleRowFallback(const uint8_t* aSrc, uint8_t* aDst, + int32_t aLength) { + SwizzleChunkFallback<aSwapRB, aOpaqueAlpha, aSrcRGBShift, aSrcAShift, + aDstRGBShift, aDstAShift>(aSrc, aDst, aLength); +} + +template <bool aSwapRB, bool aOpaqueAlpha, uint32_t aSrcRGBShift, + uint32_t aSrcAShift, uint32_t aDstRGBShift, uint32_t aDstAShift> +static void SwizzleFallback(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst, + int32_t aDstGap, IntSize aSize) { + for (int32_t height = aSize.height; height > 0; height--) { + SwizzleChunkFallback<aSwapRB, aOpaqueAlpha, aSrcRGBShift, aSrcAShift, + aDstRGBShift, aDstAShift>(aSrc, aDst, aSize.width); + aSrc += aSrcGap; + aDst += aDstGap; + } +} + +#define SWIZZLE_FALLBACK(aSrcFormat, aDstFormat) \ + FORMAT_CASE( \ + aSrcFormat, aDstFormat, \ + SwizzleFallback<ShouldSwapRB(aSrcFormat, aDstFormat), \ + ShouldForceOpaque(aSrcFormat, aDstFormat), \ + RGBBitShift(aSrcFormat), AlphaBitShift(aSrcFormat), \ + RGBBitShift(aDstFormat), AlphaBitShift(aDstFormat)>) + +#define SWIZZLE_ROW_FALLBACK(aSrcFormat, aDstFormat) \ + FORMAT_CASE_ROW( \ + aSrcFormat, aDstFormat, \ + SwizzleRowFallback<ShouldSwapRB(aSrcFormat, aDstFormat), \ + ShouldForceOpaque(aSrcFormat, aDstFormat), \ + RGBBitShift(aSrcFormat), AlphaBitShift(aSrcFormat), \ + RGBBitShift(aDstFormat), AlphaBitShift(aDstFormat)>) + +// Fast-path for matching formats. +template <int32_t aBytesPerPixel> +static void SwizzleRowCopy(const uint8_t* aSrc, uint8_t* aDst, + int32_t aLength) { + if (aSrc != aDst) { + memcpy(aDst, aSrc, aLength * aBytesPerPixel); + } +} + +// Fast-path for matching formats. +static void SwizzleCopy(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst, + int32_t aDstGap, IntSize aSize, int32_t aBPP) { + if (aSrc != aDst) { + int32_t rowLength = aBPP * aSize.width; + for (int32_t height = aSize.height; height > 0; height--) { + memcpy(aDst, aSrc, rowLength); + aSrc += rowLength + aSrcGap; + aDst += rowLength + aDstGap; + } + } +} + +// Fast-path for conversions that swap all bytes. +template <bool aOpaqueAlpha, uint32_t aSrcAShift, uint32_t aDstAShift> +static void SwizzleChunkSwap(const uint8_t*& aSrc, uint8_t*& aDst, + int32_t aLength) { + const uint8_t* end = aSrc + 4 * aLength; + do { + // Use an endian swap to move the bytes, i.e. BGRA -> ARGB. + uint32_t rgba = *reinterpret_cast<const uint32_t*>(aSrc); +#if MOZ_LITTLE_ENDIAN() + rgba = NativeEndian::swapToBigEndian(rgba); +#else + rgba = NativeEndian::swapToLittleEndian(rgba); +#endif + if (aOpaqueAlpha) { + rgba |= 0xFF << aDstAShift; + } + *reinterpret_cast<uint32_t*>(aDst) = rgba; + aSrc += 4; + aDst += 4; + } while (aSrc < end); +} + +template <bool aOpaqueAlpha, uint32_t aSrcAShift, uint32_t aDstAShift> +static void SwizzleRowSwap(const uint8_t* aSrc, uint8_t* aDst, + int32_t aLength) { + SwizzleChunkSwap<aOpaqueAlpha, aSrcAShift, aDstAShift>(aSrc, aDst, aLength); +} + +template <bool aOpaqueAlpha, uint32_t aSrcAShift, uint32_t aDstAShift> +static void SwizzleSwap(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst, + int32_t aDstGap, IntSize aSize) { + for (int32_t height = aSize.height; height > 0; height--) { + SwizzleChunkSwap<aOpaqueAlpha, aSrcAShift, aDstAShift>(aSrc, aDst, + aSize.width); + aSrc += aSrcGap; + aDst += aDstGap; + } +} + +#define SWIZZLE_SWAP(aSrcFormat, aDstFormat) \ + FORMAT_CASE( \ + aSrcFormat, aDstFormat, \ + SwizzleSwap<ShouldForceOpaque(aSrcFormat, aDstFormat), \ + AlphaBitShift(aSrcFormat), AlphaBitShift(aDstFormat)>) + +#define SWIZZLE_ROW_SWAP(aSrcFormat, aDstFormat) \ + FORMAT_CASE_ROW( \ + aSrcFormat, aDstFormat, \ + SwizzleRowSwap<ShouldForceOpaque(aSrcFormat, aDstFormat), \ + AlphaBitShift(aSrcFormat), AlphaBitShift(aDstFormat)>) + +static void SwizzleChunkSwapRGB24(const uint8_t*& aSrc, uint8_t*& aDst, + int32_t aLength) { + const uint8_t* end = aSrc + 3 * aLength; + do { + uint8_t r = aSrc[0]; + uint8_t g = aSrc[1]; + uint8_t b = aSrc[2]; + aDst[0] = b; + aDst[1] = g; + aDst[2] = r; + aSrc += 3; + aDst += 3; + } while (aSrc < end); +} + +static void SwizzleRowSwapRGB24(const uint8_t* aSrc, uint8_t* aDst, + int32_t aLength) { + SwizzleChunkSwapRGB24(aSrc, aDst, aLength); +} + +static void SwizzleSwapRGB24(const uint8_t* aSrc, int32_t aSrcGap, + uint8_t* aDst, int32_t aDstGap, IntSize aSize) { + for (int32_t height = aSize.height; height > 0; height--) { + SwizzleChunkSwapRGB24(aSrc, aDst, aSize.width); + aSrc += aSrcGap; + aDst += aDstGap; + } +} + +#define SWIZZLE_SWAP_RGB24(aSrcFormat, aDstFormat) \ + FORMAT_CASE(aSrcFormat, aDstFormat, SwizzleSwapRGB24) + +#define SWIZZLE_ROW_SWAP_RGB24(aSrcFormat, aDstFormat) \ + FORMAT_CASE_ROW(aSrcFormat, aDstFormat, SwizzleRowSwapRGB24) + +// Fast-path for conversions that force alpha to opaque. +template <uint32_t aDstAShift> +static void SwizzleChunkOpaqueUpdate(uint8_t*& aBuffer, int32_t aLength) { + const uint8_t* end = aBuffer + 4 * aLength; + do { + uint32_t rgba = *reinterpret_cast<const uint32_t*>(aBuffer); + // Just add on the alpha bits to the source. + rgba |= 0xFF << aDstAShift; + *reinterpret_cast<uint32_t*>(aBuffer) = rgba; + aBuffer += 4; + } while (aBuffer < end); +} + +template <uint32_t aDstAShift> +static void SwizzleChunkOpaqueCopy(const uint8_t*& aSrc, uint8_t* aDst, + int32_t aLength) { + const uint8_t* end = aSrc + 4 * aLength; + do { + uint32_t rgba = *reinterpret_cast<const uint32_t*>(aSrc); + // Just add on the alpha bits to the source. + rgba |= 0xFF << aDstAShift; + *reinterpret_cast<uint32_t*>(aDst) = rgba; + aSrc += 4; + aDst += 4; + } while (aSrc < end); +} + +template <uint32_t aDstAShift> +static void SwizzleRowOpaque(const uint8_t* aSrc, uint8_t* aDst, + int32_t aLength) { + if (aSrc == aDst) { + SwizzleChunkOpaqueUpdate<aDstAShift>(aDst, aLength); + } else { + SwizzleChunkOpaqueCopy<aDstAShift>(aSrc, aDst, aLength); + } +} + +template <uint32_t aDstAShift> +static void SwizzleOpaque(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst, + int32_t aDstGap, IntSize aSize) { + if (aSrc == aDst) { + // Modifying in-place, so just write out the alpha. + for (int32_t height = aSize.height; height > 0; height--) { + SwizzleChunkOpaqueUpdate<aDstAShift>(aDst, aSize.width); + aDst += aDstGap; + } + } else { + for (int32_t height = aSize.height; height > 0; height--) { + SwizzleChunkOpaqueCopy<aDstAShift>(aSrc, aDst, aSize.width); + aSrc += aSrcGap; + aDst += aDstGap; + } + } +} + +#define SWIZZLE_OPAQUE(aSrcFormat, aDstFormat) \ + FORMAT_CASE(aSrcFormat, aDstFormat, SwizzleOpaque<AlphaBitShift(aDstFormat)>) + +#define SWIZZLE_ROW_OPAQUE(aSrcFormat, aDstFormat) \ + FORMAT_CASE_ROW(aSrcFormat, aDstFormat, \ + SwizzleRowOpaque<AlphaBitShift(aDstFormat)>) + +// Packing of 32-bit formats to RGB565. +template <bool aSwapRB, uint32_t aSrcRGBShift, uint32_t aSrcRGBIndex> +static void PackToRGB565(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst, + int32_t aDstGap, IntSize aSize) { + for (int32_t height = aSize.height; height > 0; height--) { + const uint8_t* end = aSrc + 4 * aSize.width; + do { + uint32_t rgba = *reinterpret_cast<const uint32_t*>(aSrc); + + // Isolate the R, G, and B components and shift to final endian-dependent + // locations. + uint16_t rgb565; + if (aSwapRB) { + rgb565 = ((rgba & (0xF8 << aSrcRGBShift)) << (8 - aSrcRGBShift)) | + ((rgba & (0xFC00 << aSrcRGBShift)) >> (5 + aSrcRGBShift)) | + ((rgba & (0xF80000 << aSrcRGBShift)) >> (19 + aSrcRGBShift)); + } else { + rgb565 = ((rgba & (0xF8 << aSrcRGBShift)) >> (3 + aSrcRGBShift)) | + ((rgba & (0xFC00 << aSrcRGBShift)) >> (5 + aSrcRGBShift)) | + ((rgba & (0xF80000 << aSrcRGBShift)) >> (8 + aSrcRGBShift)); + } + + *reinterpret_cast<uint16_t*>(aDst) = rgb565; + + aSrc += 4; + aDst += 2; + } while (aSrc < end); + + aSrc += aSrcGap; + aDst += aDstGap; + } +} + +// Packing of 32-bit formats to 24-bit formats. +template <bool aSwapRB, uint32_t aSrcRGBShift, uint32_t aSrcRGBIndex> +static void PackChunkToRGB24(const uint8_t*& aSrc, uint8_t*& aDst, + int32_t aLength) { + const uint8_t* end = aSrc + 4 * aLength; + do { + uint8_t r = aSrc[aSrcRGBIndex + (aSwapRB ? 2 : 0)]; + uint8_t g = aSrc[aSrcRGBIndex + 1]; + uint8_t b = aSrc[aSrcRGBIndex + (aSwapRB ? 0 : 2)]; + + aDst[0] = r; + aDst[1] = g; + aDst[2] = b; + + aSrc += 4; + aDst += 3; + } while (aSrc < end); +} + +template <bool aSwapRB, uint32_t aSrcRGBShift, uint32_t aSrcRGBIndex> +static void PackRowToRGB24(const uint8_t* aSrc, uint8_t* aDst, + int32_t aLength) { + PackChunkToRGB24<aSwapRB, aSrcRGBShift, aSrcRGBIndex>(aSrc, aDst, aLength); +} + +template <bool aSwapRB, uint32_t aSrcRGBShift, uint32_t aSrcRGBIndex> +static void PackToRGB24(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst, + int32_t aDstGap, IntSize aSize) { + for (int32_t height = aSize.height; height > 0; height--) { + PackChunkToRGB24<aSwapRB, aSrcRGBShift, aSrcRGBIndex>(aSrc, aDst, + aSize.width); + aSrc += aSrcGap; + aDst += aDstGap; + } +} + +#define PACK_RGB_CASE(aSrcFormat, aDstFormat, aPackFunc) \ + FORMAT_CASE(aSrcFormat, aDstFormat, \ + aPackFunc<ShouldSwapRB(aSrcFormat, aDstFormat), \ + RGBBitShift(aSrcFormat), RGBByteIndex(aSrcFormat)>) + +#define PACK_RGB(aDstFormat, aPackFunc) \ + PACK_RGB_CASE(SurfaceFormat::B8G8R8A8, aDstFormat, aPackFunc) \ + PACK_RGB_CASE(SurfaceFormat::B8G8R8X8, aDstFormat, aPackFunc) \ + PACK_RGB_CASE(SurfaceFormat::R8G8B8A8, aDstFormat, aPackFunc) \ + PACK_RGB_CASE(SurfaceFormat::R8G8B8X8, aDstFormat, aPackFunc) \ + PACK_RGB_CASE(SurfaceFormat::A8R8G8B8, aDstFormat, aPackFunc) \ + PACK_RGB_CASE(SurfaceFormat::X8R8G8B8, aDstFormat, aPackFunc) + +#define PACK_ROW_RGB_CASE(aSrcFormat, aDstFormat, aPackFunc) \ + FORMAT_CASE_ROW( \ + aSrcFormat, aDstFormat, \ + aPackFunc<ShouldSwapRB(aSrcFormat, aDstFormat), RGBBitShift(aSrcFormat), \ + RGBByteIndex(aSrcFormat)>) + +#define PACK_ROW_RGB(aDstFormat, aPackFunc) \ + PACK_ROW_RGB_CASE(SurfaceFormat::B8G8R8A8, aDstFormat, aPackFunc) \ + PACK_ROW_RGB_CASE(SurfaceFormat::B8G8R8X8, aDstFormat, aPackFunc) \ + PACK_ROW_RGB_CASE(SurfaceFormat::R8G8B8A8, aDstFormat, aPackFunc) \ + PACK_ROW_RGB_CASE(SurfaceFormat::R8G8B8X8, aDstFormat, aPackFunc) \ + PACK_ROW_RGB_CASE(SurfaceFormat::A8R8G8B8, aDstFormat, aPackFunc) \ + PACK_ROW_RGB_CASE(SurfaceFormat::X8R8G8B8, aDstFormat, aPackFunc) + +// Packing of 32-bit formats to A8. +template <uint32_t aSrcAIndex> +static void PackToA8(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst, + int32_t aDstGap, IntSize aSize) { + for (int32_t height = aSize.height; height > 0; height--) { + const uint8_t* end = aSrc + 4 * aSize.width; + do { + *aDst++ = aSrc[aSrcAIndex]; + aSrc += 4; + } while (aSrc < end); + aSrc += aSrcGap; + aDst += aDstGap; + } +} + +#define PACK_ALPHA_CASE(aSrcFormat, aDstFormat, aPackFunc) \ + FORMAT_CASE(aSrcFormat, aDstFormat, aPackFunc<AlphaByteIndex(aSrcFormat)>) + +#define PACK_ALPHA(aDstFormat, aPackFunc) \ + PACK_ALPHA_CASE(SurfaceFormat::B8G8R8A8, aDstFormat, aPackFunc) \ + PACK_ALPHA_CASE(SurfaceFormat::R8G8B8A8, aDstFormat, aPackFunc) \ + PACK_ALPHA_CASE(SurfaceFormat::A8R8G8B8, aDstFormat, aPackFunc) + +template <bool aSwapRB> +void UnpackRowRGB24(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength) { + // Because we are expanding, we can only process the data back to front in + // case we are performing this in place. + const uint8_t* src = aSrc + 3 * (aLength - 1); + uint32_t* dst = reinterpret_cast<uint32_t*>(aDst + 4 * aLength); + while (src >= aSrc) { + uint8_t r = src[aSwapRB ? 2 : 0]; + uint8_t g = src[1]; + uint8_t b = src[aSwapRB ? 0 : 2]; +#if MOZ_LITTLE_ENDIAN() + *--dst = 0xFF000000 | (b << 16) | (g << 8) | r; +#else + *--dst = 0x000000FF | (b << 8) | (g << 16) | (r << 24); +#endif + src -= 3; + } +} + +// Force instantiation of swizzle variants here. +template void UnpackRowRGB24<false>(const uint8_t*, uint8_t*, int32_t); +template void UnpackRowRGB24<true>(const uint8_t*, uint8_t*, int32_t); + +#define UNPACK_ROW_RGB(aDstFormat) \ + FORMAT_CASE_ROW( \ + SurfaceFormat::R8G8B8, aDstFormat, \ + UnpackRowRGB24<ShouldSwapRB(SurfaceFormat::R8G8B8, aDstFormat)>) + +static void UnpackRowRGB24_To_ARGB(const uint8_t* aSrc, uint8_t* aDst, + int32_t aLength) { + // Because we are expanding, we can only process the data back to front in + // case we are performing this in place. + const uint8_t* src = aSrc + 3 * (aLength - 1); + uint32_t* dst = reinterpret_cast<uint32_t*>(aDst + 4 * aLength); + while (src >= aSrc) { + uint8_t r = src[0]; + uint8_t g = src[1]; + uint8_t b = src[2]; +#if MOZ_LITTLE_ENDIAN() + *--dst = 0x000000FF | (r << 8) | (g << 16) | (b << 24); +#else + *--dst = 0xFF000000 | (r << 24) | (g << 16) | b; +#endif + src -= 3; + } +} + +#define UNPACK_ROW_RGB_TO_ARGB(aDstFormat) \ + FORMAT_CASE_ROW(SurfaceFormat::R8G8B8, aDstFormat, UnpackRowRGB24_To_ARGB) + +bool SwizzleData(const uint8_t* aSrc, int32_t aSrcStride, + SurfaceFormat aSrcFormat, uint8_t* aDst, int32_t aDstStride, + SurfaceFormat aDstFormat, const IntSize& aSize) { + if (aSize.IsEmpty()) { + return true; + } + IntSize size = CollapseSize(aSize, aSrcStride, aDstStride); + // Find gap from end of row to the start of the next row. + int32_t srcGap = GetStrideGap(aSize.width, aSrcFormat, aSrcStride); + int32_t dstGap = GetStrideGap(aSize.width, aDstFormat, aDstStride); + MOZ_ASSERT(srcGap >= 0 && dstGap >= 0); + if (srcGap < 0 || dstGap < 0) { + return false; + } + +#define FORMAT_CASE_CALL(...) __VA_ARGS__(aSrc, srcGap, aDst, dstGap, size) + +#ifdef USE_SSE2 + if (mozilla::supports_sse2()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + SWIZZLE_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8) + SWIZZLE_SSE2(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8X8) + SWIZZLE_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8) + SWIZZLE_SSE2(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8A8) + SWIZZLE_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8) + SWIZZLE_SSE2(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8X8) + SWIZZLE_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8) + SWIZZLE_SSE2(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8A8) + default: + break; + } +#endif + +#ifdef USE_NEON + if (mozilla::supports_neon()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + SWIZZLE_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8) + SWIZZLE_NEON(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8X8) + SWIZZLE_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8) + SWIZZLE_NEON(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8A8) + SWIZZLE_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8) + SWIZZLE_NEON(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8X8) + SWIZZLE_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8) + SWIZZLE_NEON(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8A8) + default: + break; + } +#endif + + switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + SWIZZLE_FALLBACK(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8) + SWIZZLE_FALLBACK(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8X8) + SWIZZLE_FALLBACK(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8) + SWIZZLE_FALLBACK(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8A8) + + SWIZZLE_FALLBACK(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8) + SWIZZLE_FALLBACK(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8X8) + SWIZZLE_FALLBACK(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8) + SWIZZLE_FALLBACK(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8A8) + SWIZZLE_FALLBACK(SurfaceFormat::R8G8B8A8, SurfaceFormat::A8R8G8B8) + SWIZZLE_FALLBACK(SurfaceFormat::R8G8B8X8, SurfaceFormat::X8R8G8B8) + + SWIZZLE_FALLBACK(SurfaceFormat::A8R8G8B8, SurfaceFormat::R8G8B8A8) + SWIZZLE_FALLBACK(SurfaceFormat::X8R8G8B8, SurfaceFormat::R8G8B8X8) + SWIZZLE_FALLBACK(SurfaceFormat::A8R8G8B8, SurfaceFormat::R8G8B8X8) + SWIZZLE_FALLBACK(SurfaceFormat::X8R8G8B8, SurfaceFormat::R8G8B8A8) + + SWIZZLE_SWAP(SurfaceFormat::B8G8R8A8, SurfaceFormat::A8R8G8B8) + SWIZZLE_SWAP(SurfaceFormat::B8G8R8A8, SurfaceFormat::X8R8G8B8) + SWIZZLE_SWAP(SurfaceFormat::B8G8R8X8, SurfaceFormat::X8R8G8B8) + SWIZZLE_SWAP(SurfaceFormat::B8G8R8X8, SurfaceFormat::A8R8G8B8) + SWIZZLE_SWAP(SurfaceFormat::A8R8G8B8, SurfaceFormat::B8G8R8A8) + SWIZZLE_SWAP(SurfaceFormat::A8R8G8B8, SurfaceFormat::B8G8R8X8) + SWIZZLE_SWAP(SurfaceFormat::X8R8G8B8, SurfaceFormat::B8G8R8X8) + SWIZZLE_SWAP(SurfaceFormat::X8R8G8B8, SurfaceFormat::B8G8R8A8) + + SWIZZLE_SWAP_RGB24(SurfaceFormat::R8G8B8, SurfaceFormat::B8G8R8) + SWIZZLE_SWAP_RGB24(SurfaceFormat::B8G8R8, SurfaceFormat::R8G8B8) + + SWIZZLE_OPAQUE(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8X8) + SWIZZLE_OPAQUE(SurfaceFormat::B8G8R8X8, SurfaceFormat::B8G8R8A8) + SWIZZLE_OPAQUE(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8X8) + SWIZZLE_OPAQUE(SurfaceFormat::R8G8B8X8, SurfaceFormat::R8G8B8A8) + SWIZZLE_OPAQUE(SurfaceFormat::A8R8G8B8, SurfaceFormat::X8R8G8B8) + SWIZZLE_OPAQUE(SurfaceFormat::X8R8G8B8, SurfaceFormat::A8R8G8B8) + + PACK_RGB(SurfaceFormat::R5G6B5_UINT16, PackToRGB565) + PACK_RGB(SurfaceFormat::B8G8R8, PackToRGB24) + PACK_RGB(SurfaceFormat::R8G8B8, PackToRGB24) + PACK_ALPHA(SurfaceFormat::A8, PackToA8) + + default: + break; + } + + if (aSrcFormat == aDstFormat) { + // If the formats match, just do a generic copy. + SwizzleCopy(aSrc, srcGap, aDst, dstGap, size, BytesPerPixel(aSrcFormat)); + return true; + } + +#undef FORMAT_CASE_CALL + + MOZ_ASSERT(false, "Unsupported swizzle formats"); + return false; +} + +static bool SwizzleYFlipDataInternal(const uint8_t* aSrc, int32_t aSrcStride, + SurfaceFormat aSrcFormat, uint8_t* aDst, + int32_t aDstStride, + SurfaceFormat aDstFormat, + const IntSize& aSize, + SwizzleRowFn aSwizzleFn) { + if (!aSwizzleFn) { + return false; + } + + // Guarantee our width and height are both greater than zero. + if (aSize.IsEmpty()) { + return true; + } + + // Unlike SwizzleData/PremultiplyData, we don't use the stride gaps directly, + // but we can use it to verify that the stride is valid for our width and + // format. + int32_t srcGap = GetStrideGap(aSize.width, aSrcFormat, aSrcStride); + int32_t dstGap = GetStrideGap(aSize.width, aDstFormat, aDstStride); + MOZ_ASSERT(srcGap >= 0 && dstGap >= 0); + if (srcGap < 0 || dstGap < 0) { + return false; + } + + // Swapping/swizzling to a new buffer is trivial. + if (aSrc != aDst) { + const uint8_t* src = aSrc; + const uint8_t* srcEnd = aSrc + aSize.height * aSrcStride; + uint8_t* dst = aDst + (aSize.height - 1) * aDstStride; + while (src < srcEnd) { + aSwizzleFn(src, dst, aSize.width); + src += aSrcStride; + dst -= aDstStride; + } + return true; + } + + if (aSrcStride != aDstStride) { + return false; + } + + // If we are swizzling in place, then we need a temporary row buffer. + UniquePtr<uint8_t[]> rowBuffer(new (std::nothrow) uint8_t[aDstStride]); + if (!rowBuffer) { + return false; + } + + // Swizzle and swap the top and bottom rows until we meet in the middle. + int32_t middleRow = aSize.height / 2; + uint8_t* top = aDst; + uint8_t* bottom = aDst + (aSize.height - 1) * aDstStride; + for (int32_t row = 0; row < middleRow; ++row) { + memcpy(rowBuffer.get(), bottom, aDstStride); + aSwizzleFn(top, bottom, aSize.width); + aSwizzleFn(rowBuffer.get(), top, aSize.width); + top += aDstStride; + bottom -= aDstStride; + } + + // If there is an odd numbered row, we haven't swizzled it yet. + if (aSize.height % 2 == 1) { + top = aDst + middleRow * aDstStride; + aSwizzleFn(top, top, aSize.width); + } + return true; +} + +bool SwizzleYFlipData(const uint8_t* aSrc, int32_t aSrcStride, + SurfaceFormat aSrcFormat, uint8_t* aDst, + int32_t aDstStride, SurfaceFormat aDstFormat, + const IntSize& aSize) { + return SwizzleYFlipDataInternal(aSrc, aSrcStride, aSrcFormat, aDst, + aDstStride, aDstFormat, aSize, + SwizzleRow(aSrcFormat, aDstFormat)); +} + +bool PremultiplyYFlipData(const uint8_t* aSrc, int32_t aSrcStride, + SurfaceFormat aSrcFormat, uint8_t* aDst, + int32_t aDstStride, SurfaceFormat aDstFormat, + const IntSize& aSize) { + return SwizzleYFlipDataInternal(aSrc, aSrcStride, aSrcFormat, aDst, + aDstStride, aDstFormat, aSize, + PremultiplyRow(aSrcFormat, aDstFormat)); +} + +SwizzleRowFn SwizzleRow(SurfaceFormat aSrcFormat, SurfaceFormat aDstFormat) { +#ifdef USE_SSE2 + if (mozilla::supports_avx2()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + UNPACK_ROW_RGB_AVX2(SurfaceFormat::R8G8B8X8) + UNPACK_ROW_RGB_AVX2(SurfaceFormat::R8G8B8A8) + UNPACK_ROW_RGB_AVX2(SurfaceFormat::B8G8R8X8) + UNPACK_ROW_RGB_AVX2(SurfaceFormat::B8G8R8A8) + default: + break; + } + + if (mozilla::supports_ssse3()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + UNPACK_ROW_RGB_SSSE3(SurfaceFormat::R8G8B8X8) + UNPACK_ROW_RGB_SSSE3(SurfaceFormat::R8G8B8A8) + UNPACK_ROW_RGB_SSSE3(SurfaceFormat::B8G8R8X8) + UNPACK_ROW_RGB_SSSE3(SurfaceFormat::B8G8R8A8) + default: + break; + } + + if (mozilla::supports_sse2()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + SWIZZLE_ROW_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8) + SWIZZLE_ROW_SSE2(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8X8) + SWIZZLE_ROW_SSE2(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8) + SWIZZLE_ROW_SSE2(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8A8) + SWIZZLE_ROW_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8) + SWIZZLE_ROW_SSE2(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8X8) + SWIZZLE_ROW_SSE2(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8) + SWIZZLE_ROW_SSE2(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8A8) + default: + break; + } +#endif + +#ifdef USE_NEON + if (mozilla::supports_neon()) switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + UNPACK_ROW_RGB_NEON(SurfaceFormat::R8G8B8X8) + UNPACK_ROW_RGB_NEON(SurfaceFormat::R8G8B8A8) + UNPACK_ROW_RGB_NEON(SurfaceFormat::B8G8R8X8) + UNPACK_ROW_RGB_NEON(SurfaceFormat::B8G8R8A8) + SWIZZLE_ROW_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8) + SWIZZLE_ROW_NEON(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8X8) + SWIZZLE_ROW_NEON(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8) + SWIZZLE_ROW_NEON(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8A8) + SWIZZLE_ROW_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8) + SWIZZLE_ROW_NEON(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8X8) + SWIZZLE_ROW_NEON(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8) + SWIZZLE_ROW_NEON(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8A8) + default: + break; + } +#endif + + switch (FORMAT_KEY(aSrcFormat, aDstFormat)) { + SWIZZLE_ROW_FALLBACK(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8) + SWIZZLE_ROW_FALLBACK(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8X8) + SWIZZLE_ROW_FALLBACK(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8) + SWIZZLE_ROW_FALLBACK(SurfaceFormat::B8G8R8X8, SurfaceFormat::R8G8B8A8) + + SWIZZLE_ROW_FALLBACK(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8A8) + SWIZZLE_ROW_FALLBACK(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8X8) + SWIZZLE_ROW_FALLBACK(SurfaceFormat::R8G8B8A8, SurfaceFormat::B8G8R8X8) + SWIZZLE_ROW_FALLBACK(SurfaceFormat::R8G8B8X8, SurfaceFormat::B8G8R8A8) + SWIZZLE_ROW_FALLBACK(SurfaceFormat::R8G8B8A8, SurfaceFormat::A8R8G8B8) + SWIZZLE_ROW_FALLBACK(SurfaceFormat::R8G8B8X8, SurfaceFormat::X8R8G8B8) + + SWIZZLE_ROW_FALLBACK(SurfaceFormat::A8R8G8B8, SurfaceFormat::R8G8B8A8) + SWIZZLE_ROW_FALLBACK(SurfaceFormat::X8R8G8B8, SurfaceFormat::R8G8B8X8) + SWIZZLE_ROW_FALLBACK(SurfaceFormat::A8R8G8B8, SurfaceFormat::R8G8B8X8) + SWIZZLE_ROW_FALLBACK(SurfaceFormat::X8R8G8B8, SurfaceFormat::R8G8B8A8) + + SWIZZLE_ROW_OPAQUE(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8X8) + SWIZZLE_ROW_OPAQUE(SurfaceFormat::B8G8R8X8, SurfaceFormat::B8G8R8A8) + SWIZZLE_ROW_OPAQUE(SurfaceFormat::R8G8B8A8, SurfaceFormat::R8G8B8X8) + SWIZZLE_ROW_OPAQUE(SurfaceFormat::R8G8B8X8, SurfaceFormat::R8G8B8A8) + SWIZZLE_ROW_OPAQUE(SurfaceFormat::A8R8G8B8, SurfaceFormat::X8R8G8B8) + SWIZZLE_ROW_OPAQUE(SurfaceFormat::X8R8G8B8, SurfaceFormat::A8R8G8B8) + + SWIZZLE_ROW_SWAP(SurfaceFormat::B8G8R8A8, SurfaceFormat::A8R8G8B8) + SWIZZLE_ROW_SWAP(SurfaceFormat::B8G8R8A8, SurfaceFormat::X8R8G8B8) + SWIZZLE_ROW_SWAP(SurfaceFormat::B8G8R8X8, SurfaceFormat::X8R8G8B8) + SWIZZLE_ROW_SWAP(SurfaceFormat::B8G8R8X8, SurfaceFormat::A8R8G8B8) + SWIZZLE_ROW_SWAP(SurfaceFormat::A8R8G8B8, SurfaceFormat::B8G8R8A8) + SWIZZLE_ROW_SWAP(SurfaceFormat::A8R8G8B8, SurfaceFormat::B8G8R8X8) + SWIZZLE_ROW_SWAP(SurfaceFormat::X8R8G8B8, SurfaceFormat::B8G8R8X8) + SWIZZLE_ROW_SWAP(SurfaceFormat::X8R8G8B8, SurfaceFormat::B8G8R8A8) + + SWIZZLE_ROW_SWAP_RGB24(SurfaceFormat::R8G8B8, SurfaceFormat::B8G8R8) + SWIZZLE_ROW_SWAP_RGB24(SurfaceFormat::B8G8R8, SurfaceFormat::R8G8B8) + + UNPACK_ROW_RGB(SurfaceFormat::R8G8B8X8) + UNPACK_ROW_RGB(SurfaceFormat::R8G8B8A8) + UNPACK_ROW_RGB(SurfaceFormat::B8G8R8X8) + UNPACK_ROW_RGB(SurfaceFormat::B8G8R8A8) + UNPACK_ROW_RGB_TO_ARGB(SurfaceFormat::A8R8G8B8) + UNPACK_ROW_RGB_TO_ARGB(SurfaceFormat::X8R8G8B8) + + PACK_ROW_RGB(SurfaceFormat::R8G8B8, PackRowToRGB24) + PACK_ROW_RGB(SurfaceFormat::B8G8R8, PackRowToRGB24) + + default: + break; + } + + if (aSrcFormat == aDstFormat) { + switch (BytesPerPixel(aSrcFormat)) { + case 4: + return &SwizzleRowCopy<4>; + case 3: + return &SwizzleRowCopy<3>; + default: + break; + } + } + + MOZ_ASSERT_UNREACHABLE("Unsupported swizzle formats"); + return nullptr; +} + +static IntRect ReorientRowRotate0FlipFallback(const uint8_t* aSrc, + int32_t aSrcRow, uint8_t* aDst, + const IntSize& aDstSize, + int32_t aDstStride) { + // Reverse order of pixels in the row. + const uint32_t* src = reinterpret_cast<const uint32_t*>(aSrc); + const uint32_t* end = src + aDstSize.width; + uint32_t* dst = reinterpret_cast<uint32_t*>(aDst + aSrcRow * aDstStride) + + aDstSize.width - 1; + do { + *dst-- = *src++; + } while (src < end); + + return IntRect(0, aSrcRow, aDstSize.width, 1); +} + +static IntRect ReorientRowRotate90FlipFallback(const uint8_t* aSrc, + int32_t aSrcRow, uint8_t* aDst, + const IntSize& aDstSize, + int32_t aDstStride) { + // Copy row of pixels from top to bottom, into left to right columns. + const uint32_t* src = reinterpret_cast<const uint32_t*>(aSrc); + const uint32_t* end = src + aDstSize.height; + uint32_t* dst = reinterpret_cast<uint32_t*>(aDst) + aSrcRow; + int32_t stride = aDstStride / sizeof(uint32_t); + do { + *dst = *src++; + dst += stride; + } while (src < end); + + return IntRect(aSrcRow, 0, 1, aDstSize.height); +} + +static IntRect ReorientRowRotate180FlipFallback(const uint8_t* aSrc, + int32_t aSrcRow, uint8_t* aDst, + const IntSize& aDstSize, + int32_t aDstStride) { + // Copy row of pixels from top to bottom, into bottom to top rows. + uint8_t* dst = aDst + (aDstSize.height - aSrcRow - 1) * aDstStride; + memcpy(dst, aSrc, aDstSize.width * sizeof(uint32_t)); + return IntRect(0, aDstSize.height - aSrcRow - 1, aDstSize.width, 1); +} + +static IntRect ReorientRowRotate270FlipFallback(const uint8_t* aSrc, + int32_t aSrcRow, uint8_t* aDst, + const IntSize& aDstSize, + int32_t aDstStride) { + // Copy row of pixels in reverse order from top to bottom, into right to left + // columns. + const uint32_t* src = reinterpret_cast<const uint32_t*>(aSrc); + const uint32_t* end = src + aDstSize.height; + uint32_t* dst = + reinterpret_cast<uint32_t*>(aDst + (aDstSize.height - 1) * aDstStride) + + aDstSize.width - aSrcRow - 1; + int32_t stride = aDstStride / sizeof(uint32_t); + do { + *dst = *src++; + dst -= stride; + } while (src < end); + + return IntRect(aDstSize.width - aSrcRow - 1, 0, 1, aDstSize.height); +} + +static IntRect ReorientRowRotate0Fallback(const uint8_t* aSrc, int32_t aSrcRow, + uint8_t* aDst, + const IntSize& aDstSize, + int32_t aDstStride) { + // Copy row of pixels into the destination. + uint8_t* dst = aDst + aSrcRow * aDstStride; + memcpy(dst, aSrc, aDstSize.width * sizeof(uint32_t)); + return IntRect(0, aSrcRow, aDstSize.width, 1); +} + +static IntRect ReorientRowRotate90Fallback(const uint8_t* aSrc, int32_t aSrcRow, + uint8_t* aDst, + const IntSize& aDstSize, + int32_t aDstStride) { + // Copy row of pixels from top to bottom, into right to left columns. + const uint32_t* src = reinterpret_cast<const uint32_t*>(aSrc); + const uint32_t* end = src + aDstSize.height; + uint32_t* dst = + reinterpret_cast<uint32_t*>(aDst) + aDstSize.width - aSrcRow - 1; + int32_t stride = aDstStride / sizeof(uint32_t); + do { + *dst = *src++; + dst += stride; + } while (src < end); + + return IntRect(aDstSize.width - aSrcRow - 1, 0, 1, aDstSize.height); +} + +static IntRect ReorientRowRotate180Fallback(const uint8_t* aSrc, + int32_t aSrcRow, uint8_t* aDst, + const IntSize& aDstSize, + int32_t aDstStride) { + // Copy row of pixels in reverse order from top to bottom, into bottom to top + // rows. + const uint32_t* src = reinterpret_cast<const uint32_t*>(aSrc); + const uint32_t* end = src + aDstSize.width; + uint32_t* dst = reinterpret_cast<uint32_t*>( + aDst + (aDstSize.height - aSrcRow - 1) * aDstStride) + + aDstSize.width - 1; + do { + *dst-- = *src++; + } while (src < end); + + return IntRect(0, aDstSize.height - aSrcRow - 1, aDstSize.width, 1); +} + +static IntRect ReorientRowRotate270Fallback(const uint8_t* aSrc, + int32_t aSrcRow, uint8_t* aDst, + const IntSize& aDstSize, + int32_t aDstStride) { + // Copy row of pixels in reverse order from top to bottom, into left to right + // column. + const uint32_t* src = reinterpret_cast<const uint32_t*>(aSrc); + const uint32_t* end = src + aDstSize.height; + uint32_t* dst = + reinterpret_cast<uint32_t*>(aDst + (aDstSize.height - 1) * aDstStride) + + aSrcRow; + int32_t stride = aDstStride / sizeof(uint32_t); + do { + *dst = *src++; + dst -= stride; + } while (src < end); + + return IntRect(aSrcRow, 0, 1, aDstSize.height); +} + +ReorientRowFn ReorientRow(const struct image::Orientation& aOrientation) { + switch (aOrientation.flip) { + case image::Flip::Unflipped: + switch (aOrientation.rotation) { + case image::Angle::D0: + return &ReorientRowRotate0Fallback; + case image::Angle::D90: + return &ReorientRowRotate90Fallback; + case image::Angle::D180: + return &ReorientRowRotate180Fallback; + case image::Angle::D270: + return &ReorientRowRotate270Fallback; + default: + break; + } + break; + case image::Flip::Horizontal: + switch (aOrientation.rotation) { + case image::Angle::D0: + return &ReorientRowRotate0FlipFallback; + case image::Angle::D90: + if (aOrientation.flipFirst) { + return &ReorientRowRotate270FlipFallback; + } else { + return &ReorientRowRotate90FlipFallback; + } + case image::Angle::D180: + return &ReorientRowRotate180FlipFallback; + case image::Angle::D270: + if (aOrientation.flipFirst) { + return &ReorientRowRotate90FlipFallback; + } else { + return &ReorientRowRotate270FlipFallback; + } + default: + break; + } + break; + default: + break; + } + + MOZ_ASSERT_UNREACHABLE("Unhandled orientation!"); + return nullptr; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/Swizzle.h b/gfx/2d/Swizzle.h new file mode 100644 index 0000000000..333490c8c0 --- /dev/null +++ b/gfx/2d/Swizzle.h @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SWIZZLE_H_ +#define MOZILLA_GFX_SWIZZLE_H_ + +#include "Point.h" +#include "Rect.h" + +namespace mozilla { +namespace image { +struct Orientation; +} + +namespace gfx { + +/** + * Premultiplies source and writes it to destination. Source and destination may + * be the same to premultiply in-place. The source format must have an alpha + * channel. + */ +GFX2D_API bool PremultiplyData(const uint8_t* aSrc, int32_t aSrcStride, + SurfaceFormat aSrcFormat, uint8_t* aDst, + int32_t aDstStride, SurfaceFormat aDstFormat, + const IntSize& aSize); + +/** + * Unpremultiplies source and writes it to destination. Source and destination + * may be the same to unpremultiply in-place. Both the source and destination + * formats must have an alpha channel. + */ +GFX2D_API bool UnpremultiplyData(const uint8_t* aSrc, int32_t aSrcStride, + SurfaceFormat aSrcFormat, uint8_t* aDst, + int32_t aDstStride, SurfaceFormat aDstFormat, + const IntSize& aSize); + +/** + * Swizzles source and writes it to destination. Source and destination may be + * the same to swizzle in-place. + */ +GFX2D_API bool SwizzleData(const uint8_t* aSrc, int32_t aSrcStride, + SurfaceFormat aSrcFormat, uint8_t* aDst, + int32_t aDstStride, SurfaceFormat aDstFormat, + const IntSize& aSize); + +/** + * Flips rows of source and swizzles it to destination. Source and destination + * may be the same to swizzle in-place; this will fail if it cannot allocate a + * temporary buffer. + */ +GFX2D_API bool SwizzleYFlipData(const uint8_t* aSrc, int32_t aSrcStride, + SurfaceFormat aSrcFormat, uint8_t* aDst, + int32_t aDstStride, SurfaceFormat aDstFormat, + const IntSize& aSize); + +/** + * Flips rows of source and premultiplies/swizzles it to destination. Source and + * destination may be the same to premultiply/swizzle in-place; this will fail + * if it cannot allocate a temporary buffer. + */ +GFX2D_API bool PremultiplyYFlipData(const uint8_t* aSrc, int32_t aSrcStride, + SurfaceFormat aSrcFormat, uint8_t* aDst, + int32_t aDstStride, + SurfaceFormat aDstFormat, + const IntSize& aSize); + +/** + * Swizzles source and writes it to destination. Source and destination may be + * the same to swizzle in-place. + */ +typedef void (*SwizzleRowFn)(const uint8_t* aSrc, uint8_t* aDst, + int32_t aLength); + +/** + * Get a function pointer to perform premultiplication between two formats. + */ +GFX2D_API SwizzleRowFn PremultiplyRow(SurfaceFormat aSrcFormat, + SurfaceFormat aDstFormat); + +/** + * Get a function pointer to perform unpremultiplication between two formats. + */ +GFX2D_API SwizzleRowFn UnpremultiplyRow(SurfaceFormat aSrcFormat, + SurfaceFormat aDstFormat); + +/** + * Get a function pointer to perform swizzling between two formats. + */ +GFX2D_API SwizzleRowFn SwizzleRow(SurfaceFormat aSrcFormat, + SurfaceFormat aDstFormat); + +/** + * Reorients source and writes it to destination. Returns the dirty rect of + * what was changed in aDst. + */ +typedef IntRect (*ReorientRowFn)(const uint8_t* aSrc, int32_t aSrcRow, + uint8_t* aDst, const IntSize& aDstSize, + int32_t aDstStride); + +/** + * Get a function pointer to perform reorientation by row. + */ +GFX2D_API ReorientRowFn +ReorientRow(const struct image::Orientation& aOrientation); + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SWIZZLE_H_ */ diff --git a/gfx/2d/SwizzleAVX2.cpp b/gfx/2d/SwizzleAVX2.cpp new file mode 100644 index 0000000000..fe8fbf4530 --- /dev/null +++ b/gfx/2d/SwizzleAVX2.cpp @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Swizzle.h" + +#include <immintrin.h> +#include <tmmintrin.h> + +namespace mozilla::gfx { + +template <bool aSwapRB> +void UnpackRowRGB24_SSSE3(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength); + +template <bool aSwapRB> +void UnpackRowRGB24_AVX2(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength) { + // Because this implementation will read an additional 8 bytes of data that + // is ignored and masked over, we cannot use the accelerated version for the + // last 1-10 pixels (3-30 bytes remaining) to guarantee we don't access memory + // outside the buffer (we read in 32 byte chunks). + if (aLength < 11) { + UnpackRowRGB24_SSSE3<aSwapRB>(aSrc, aDst, aLength); + return; + } + + // Because we are expanding, we can only process the data back to front in + // case we are performing this in place. + int32_t alignedRow = (aLength - 4) & ~7; + int32_t remainder = aLength - alignedRow; + + const uint8_t* src = aSrc + alignedRow * 3; + uint8_t* dst = aDst + alignedRow * 4; + + // Handle any 3-10 remaining pixels. + UnpackRowRGB24_SSSE3<aSwapRB>(src, dst, remainder); + + // Used to shuffle the two final 32-bit words which we ignore into the last + // 32-bit word of each 128-bit lane, such that + // RGBR GBRG BRGB RGBR GBRG BRGB RGBR GBRG + // BRGB RGBR GBRG BRGB ZZZZ ZZZZ ZZZZ ZZZZ + // becomes + // RGBR GBRG BRGB RGBR GBRG BRGB ZZZZ ZZZZ + // RGBR GBRG BRGB RGBR GBRG BRGB ZZZZ ZZZZ + const __m256i discardMask = _mm256_set_epi32(7, 5, 4, 3, 6, 2, 1, 0); + + // Used to shuffle 8-bit words within a 128-bit lane, such that we transform + // RGBR GBRG BRGB RGBR GBRG BRGB ZZZZ ZZZZ + // into + // RGBZ RGBZ RGBZ RGBZ RGBZ RGBZ RGBZ RGBZ + // or + // BGRZ BGRZ BGRZ BGRZ BGRZ BGRZ BGRZ BGRZ + const __m256i colorMask = + aSwapRB ? _mm256_set_epi8(15, 9, 10, 11, 14, 6, 7, 8, 13, 3, 4, 5, 12, 0, + 1, 2, 15, 9, 10, 11, 14, 6, 7, 8, 13, 3, 4, 5, + 12, 0, 1, 2) + : _mm256_set_epi8(15, 11, 10, 9, 14, 8, 7, 6, 13, 5, 4, 3, 12, 2, + 1, 0, 15, 11, 10, 9, 14, 8, 7, 6, 13, 5, 4, 3, + 12, 2, 1, 0); + + // Used to transform RGBZ/BGRZ to RGBX/BGRX, or force the alpha opaque. + const __m256i alphaMask = _mm256_set1_epi32(0xFF000000); + + // Process all 8-pixel chunks as one vector. + src -= 8 * 3; + dst -= 8 * 4; + while (src >= aSrc) { + __m256i px = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(src)); + px = _mm256_permutevar8x32_epi32(px, discardMask); + px = _mm256_shuffle_epi8(px, colorMask); + px = _mm256_or_si256(px, alphaMask); + _mm256_storeu_si256(reinterpret_cast<__m256i*>(dst), px); + src -= 8 * 3; + dst -= 8 * 4; + } +} + +// Force instantiation of swizzle variants here. +template void UnpackRowRGB24_AVX2<false>(const uint8_t*, uint8_t*, int32_t); +template void UnpackRowRGB24_AVX2<true>(const uint8_t*, uint8_t*, int32_t); + +} // namespace mozilla::gfx diff --git a/gfx/2d/SwizzleNEON.cpp b/gfx/2d/SwizzleNEON.cpp new file mode 100644 index 0000000000..887e93d632 --- /dev/null +++ b/gfx/2d/SwizzleNEON.cpp @@ -0,0 +1,451 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Swizzle.h" + +#include <arm_neon.h> + +namespace mozilla { +namespace gfx { + +// Load 1-3 pixels into a 4 pixel vector. +static MOZ_ALWAYS_INLINE uint16x8_t LoadRemainder_NEON(const uint8_t* aSrc, + size_t aLength) { + const uint32_t* src32 = reinterpret_cast<const uint32_t*>(aSrc); + uint32x4_t dst32; + if (aLength >= 2) { + // Load first 2 pixels + dst32 = vcombine_u32(vld1_u32(src32), vdup_n_u32(0)); + // Load third pixel + if (aLength >= 3) { + dst32 = vld1q_lane_u32(src32 + 2, dst32, 2); + } + } else { + // Load single pixel + dst32 = vld1q_lane_u32(src32, vdupq_n_u32(0), 0); + } + return vreinterpretq_u16_u32(dst32); +} + +// Store 1-3 pixels from a vector into memory without overwriting. +static MOZ_ALWAYS_INLINE void StoreRemainder_NEON(uint8_t* aDst, size_t aLength, + const uint16x8_t& aSrc) { + uint32_t* dst32 = reinterpret_cast<uint32_t*>(aDst); + uint32x4_t src32 = vreinterpretq_u32_u16(aSrc); + if (aLength >= 2) { + // Store first 2 pixels + vst1_u32(dst32, vget_low_u32(src32)); + // Store third pixel + if (aLength >= 3) { + vst1q_lane_u32(dst32 + 2, src32, 2); + } + } else { + // Store single pixel + vst1q_lane_u32(dst32, src32, 0); + } +} + +// Premultiply vector of 4 pixels using splayed math. +template <bool aSwapRB, bool aOpaqueAlpha> +static MOZ_ALWAYS_INLINE uint16x8_t +PremultiplyVector_NEON(const uint16x8_t& aSrc) { + // Isolate R and B with mask. + const uint16x8_t mask = vdupq_n_u16(0x00FF); + uint16x8_t rb = vandq_u16(aSrc, mask); + // Swap R and B if necessary. + if (aSwapRB) { + rb = vrev32q_u16(rb); + } + // Isolate G and A by shifting down to bottom of word. + uint16x8_t ga = vshrq_n_u16(aSrc, 8); + + // Duplicate alphas to get vector of A1 A1 A2 A2 A3 A3 A4 A4 + uint16x8_t alphas = vtrnq_u16(ga, ga).val[1]; + + // rb = rb*a + 255; rb += rb >> 8; + rb = vmlaq_u16(mask, rb, alphas); + rb = vsraq_n_u16(rb, rb, 8); + + // If format is not opaque, force A to 255 so that A*alpha/255 = alpha + if (!aOpaqueAlpha) { + ga = vorrq_u16(ga, vreinterpretq_u16_u32(vdupq_n_u32(0x00FF0000))); + } + // ga = ga*a + 255; ga += ga >> 8; + ga = vmlaq_u16(mask, ga, alphas); + ga = vsraq_n_u16(ga, ga, 8); + // If format is opaque, force output A to be 255. + if (aOpaqueAlpha) { + ga = vorrq_u16(ga, vreinterpretq_u16_u32(vdupq_n_u32(0xFF000000))); + } + + // Combine back to final pixel with (rb >> 8) | (ga & 0xFF00FF00) + return vsriq_n_u16(ga, rb, 8); +} + +template <bool aSwapRB, bool aOpaqueAlpha> +static MOZ_ALWAYS_INLINE void PremultiplyChunk_NEON(const uint8_t*& aSrc, + uint8_t*& aDst, + int32_t aAlignedRow, + int32_t aRemainder) { + // Process all 4-pixel chunks as one vector. + for (const uint8_t* end = aSrc + aAlignedRow; aSrc < end;) { + uint16x8_t px = vld1q_u16(reinterpret_cast<const uint16_t*>(aSrc)); + px = PremultiplyVector_NEON<aSwapRB, aOpaqueAlpha>(px); + vst1q_u16(reinterpret_cast<uint16_t*>(aDst), px); + aSrc += 4 * 4; + aDst += 4 * 4; + } + + // Handle any 1-3 remaining pixels. + if (aRemainder) { + uint16x8_t px = LoadRemainder_NEON(aSrc, aRemainder); + px = PremultiplyVector_NEON<aSwapRB, aOpaqueAlpha>(px); + StoreRemainder_NEON(aDst, aRemainder, px); + } +} + +template <bool aSwapRB, bool aOpaqueAlpha> +void PremultiplyRow_NEON(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength) { + int32_t alignedRow = 4 * (aLength & ~3); + int32_t remainder = aLength & 3; + PremultiplyChunk_NEON<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow, + remainder); +} + +template <bool aSwapRB, bool aOpaqueAlpha> +void Premultiply_NEON(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst, + int32_t aDstGap, IntSize aSize) { + int32_t alignedRow = 4 * (aSize.width & ~3); + int32_t remainder = aSize.width & 3; + // Fold remainder into stride gap. + aSrcGap += 4 * remainder; + aDstGap += 4 * remainder; + + for (int32_t height = aSize.height; height > 0; height--) { + PremultiplyChunk_NEON<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow, + remainder); + aSrc += aSrcGap; + aDst += aDstGap; + } +} + +// Force instantiation of premultiply variants here. +template void PremultiplyRow_NEON<false, false>(const uint8_t*, uint8_t*, + int32_t); +template void PremultiplyRow_NEON<false, true>(const uint8_t*, uint8_t*, + int32_t); +template void PremultiplyRow_NEON<true, false>(const uint8_t*, uint8_t*, + int32_t); +template void PremultiplyRow_NEON<true, true>(const uint8_t*, uint8_t*, + int32_t); +template void Premultiply_NEON<false, false>(const uint8_t*, int32_t, uint8_t*, + int32_t, IntSize); +template void Premultiply_NEON<false, true>(const uint8_t*, int32_t, uint8_t*, + int32_t, IntSize); +template void Premultiply_NEON<true, false>(const uint8_t*, int32_t, uint8_t*, + int32_t, IntSize); +template void Premultiply_NEON<true, true>(const uint8_t*, int32_t, uint8_t*, + int32_t, IntSize); + +// This generates a table of fixed-point reciprocals representing 1/alpha +// similar to the fallback implementation. However, the reciprocal must +// ultimately be multiplied as an unsigned 9 bit upper part and a signed +// 15 bit lower part to cheaply multiply. Thus, the lower 15 bits of the +// reciprocal is stored 15 bits of the reciprocal are masked off and +// stored in the low word. The upper 9 bits are masked and shifted to fit +// into the high word. These then get independently multiplied with the +// color component and recombined to provide the full recriprocal multiply. +#define UNPREMULQ_NEON(x) \ + ((((0xFF00FFU / (x)) & 0xFF8000U) << 1) | ((0xFF00FFU / (x)) & 0x7FFFU)) +#define UNPREMULQ_NEON_2(x) UNPREMULQ_NEON(x), UNPREMULQ_NEON((x) + 1) +#define UNPREMULQ_NEON_4(x) UNPREMULQ_NEON_2(x), UNPREMULQ_NEON_2((x) + 2) +#define UNPREMULQ_NEON_8(x) UNPREMULQ_NEON_4(x), UNPREMULQ_NEON_4((x) + 4) +#define UNPREMULQ_NEON_16(x) UNPREMULQ_NEON_8(x), UNPREMULQ_NEON_8((x) + 8) +#define UNPREMULQ_NEON_32(x) UNPREMULQ_NEON_16(x), UNPREMULQ_NEON_16((x) + 16) +static const uint32_t sUnpremultiplyTable_NEON[256] = {0, + UNPREMULQ_NEON(1), + UNPREMULQ_NEON_2(2), + UNPREMULQ_NEON_4(4), + UNPREMULQ_NEON_8(8), + UNPREMULQ_NEON_16(16), + UNPREMULQ_NEON_32(32), + UNPREMULQ_NEON_32(64), + UNPREMULQ_NEON_32(96), + UNPREMULQ_NEON_32(128), + UNPREMULQ_NEON_32(160), + UNPREMULQ_NEON_32(192), + UNPREMULQ_NEON_32(224)}; + +// Unpremultiply a vector of 4 pixels using splayed math and a reciprocal table +// that avoids doing any actual division. +template <bool aSwapRB> +static MOZ_ALWAYS_INLINE uint16x8_t +UnpremultiplyVector_NEON(const uint16x8_t& aSrc) { + // Isolate R and B with mask. + uint16x8_t rb = vandq_u16(aSrc, vdupq_n_u16(0x00FF)); + // Swap R and B if necessary. + if (aSwapRB) { + rb = vrev32q_u16(rb); + } + + // Isolate G and A by shifting down to bottom of word. + uint16x8_t ga = vshrq_n_u16(aSrc, 8); + // Extract the alphas for the 4 pixels from the now isolated words. + int a1 = vgetq_lane_u16(ga, 1); + int a2 = vgetq_lane_u16(ga, 3); + int a3 = vgetq_lane_u16(ga, 5); + int a4 = vgetq_lane_u16(ga, 7); + + // First load all of the interleaved low and high portions of the reciprocals + // and combine them a single vector as lo1 hi1 lo2 hi2 lo3 hi3 lo4 hi4 + uint16x8_t q1234 = vreinterpretq_u16_u32(vld1q_lane_u32( + &sUnpremultiplyTable_NEON[a4], + vld1q_lane_u32( + &sUnpremultiplyTable_NEON[a3], + vld1q_lane_u32( + &sUnpremultiplyTable_NEON[a2], + vld1q_lane_u32(&sUnpremultiplyTable_NEON[a1], vdupq_n_u32(0), 0), + 1), + 2), + 3)); + // Transpose the interleaved low/high portions so that we produce + // two separate duplicated vectors for the low and high portions respectively: + // lo1 lo1 lo2 lo2 lo3 lo3 lo4 lo4 and hi1 hi1 hi2 hi2 hi3 hi3 hi4 hi4 + uint16x8x2_t q1234lohi = vtrnq_u16(q1234, q1234); + + // VQDMULH is a signed multiply that doubles (*2) the result, then takes the + // high word. To work around the signedness and the doubling, the low + // portion of the reciprocal only stores the lower 15 bits, which fits in a + // signed 16 bit integer. The high 9 bit portion is effectively also doubled + // by 2 as a side-effect of being shifted for storage. Thus the output scale + // of doing a normal multiply by the high portion and the VQDMULH by the low + // portion are both doubled and can be safely added together. The resulting + // sum just needs to be halved (via VHADD) to thus cancel out the doubling. + // All this combines to produce a reciprocal multiply of the form: + // rb = ((rb * hi) + ((rb * lo * 2) >> 16)) / 2 + rb = vhaddq_u16( + vmulq_u16(rb, q1234lohi.val[1]), + vreinterpretq_u16_s16(vqdmulhq_s16( + vreinterpretq_s16_u16(rb), vreinterpretq_s16_u16(q1234lohi.val[0])))); + + // ga = ((ga * hi) + ((ga * lo * 2) >> 16)) / 2 + ga = vhaddq_u16( + vmulq_u16(ga, q1234lohi.val[1]), + vreinterpretq_u16_s16(vqdmulhq_s16( + vreinterpretq_s16_u16(ga), vreinterpretq_s16_u16(q1234lohi.val[0])))); + + // Combine to the final pixel with ((rb | (ga << 8)) & ~0xFF000000) | (aSrc & + // 0xFF000000), which inserts back in the original alpha value unchanged. + return vbslq_u16(vreinterpretq_u16_u32(vdupq_n_u32(0xFF000000)), aSrc, + vsliq_n_u16(rb, ga, 8)); +} + +template <bool aSwapRB> +static MOZ_ALWAYS_INLINE void UnpremultiplyChunk_NEON(const uint8_t*& aSrc, + uint8_t*& aDst, + int32_t aAlignedRow, + int32_t aRemainder) { + // Process all 4-pixel chunks as one vector. + for (const uint8_t* end = aSrc + aAlignedRow; aSrc < end;) { + uint16x8_t px = vld1q_u16(reinterpret_cast<const uint16_t*>(aSrc)); + px = UnpremultiplyVector_NEON<aSwapRB>(px); + vst1q_u16(reinterpret_cast<uint16_t*>(aDst), px); + aSrc += 4 * 4; + aDst += 4 * 4; + } + + // Handle any 1-3 remaining pixels. + if (aRemainder) { + uint16x8_t px = LoadRemainder_NEON(aSrc, aRemainder); + px = UnpremultiplyVector_NEON<aSwapRB>(px); + StoreRemainder_NEON(aDst, aRemainder, px); + } +} + +template <bool aSwapRB> +void UnpremultiplyRow_NEON(const uint8_t* aSrc, uint8_t* aDst, + int32_t aLength) { + int32_t alignedRow = 4 * (aLength & ~3); + int32_t remainder = aLength & 3; + UnpremultiplyChunk_NEON<aSwapRB>(aSrc, aDst, alignedRow, remainder); +} + +template <bool aSwapRB> +void Unpremultiply_NEON(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst, + int32_t aDstGap, IntSize aSize) { + int32_t alignedRow = 4 * (aSize.width & ~3); + int32_t remainder = aSize.width & 3; + // Fold remainder into stride gap. + aSrcGap += 4 * remainder; + aDstGap += 4 * remainder; + + for (int32_t height = aSize.height; height > 0; height--) { + UnpremultiplyChunk_NEON<aSwapRB>(aSrc, aDst, alignedRow, remainder); + aSrc += aSrcGap; + aDst += aDstGap; + } +} + +// Force instantiation of unpremultiply variants here. +template void UnpremultiplyRow_NEON<false>(const uint8_t*, uint8_t*, int32_t); +template void UnpremultiplyRow_NEON<true>(const uint8_t*, uint8_t*, int32_t); +template void Unpremultiply_NEON<false>(const uint8_t*, int32_t, uint8_t*, + int32_t, IntSize); +template void Unpremultiply_NEON<true>(const uint8_t*, int32_t, uint8_t*, + int32_t, IntSize); + +// Swizzle a vector of 4 pixels providing swaps and opaquifying. +template <bool aSwapRB, bool aOpaqueAlpha> +static MOZ_ALWAYS_INLINE uint16x8_t SwizzleVector_NEON(const uint16x8_t& aSrc) { + // Swap R and B, then add to G and A (forced to 255): + // (((src>>16) | (src << 16)) & 0x00FF00FF) | + // ((src | 0xFF000000) & ~0x00FF00FF) + return vbslq_u16( + vdupq_n_u16(0x00FF), vrev32q_u16(aSrc), + aOpaqueAlpha + ? vorrq_u16(aSrc, vreinterpretq_u16_u32(vdupq_n_u32(0xFF000000))) + : aSrc); +} + +#if 0 +// These specializations currently do not profile faster than the generic versions, +// so disable them for now. + +// Optimized implementations for when there is no R and B swap. +template<> +static MOZ_ALWAYS_INLINE uint16x8_t +SwizzleVector_NEON<false, true>(const uint16x8_t& aSrc) +{ + // Force alpha to 255. + return vorrq_u16(aSrc, vreinterpretq_u16_u32(vdupq_n_u32(0xFF000000))); +} + +template<> +static MOZ_ALWAYS_INLINE uint16x8_t +SwizzleVector_NEON<false, false>(const uint16x8_t& aSrc) +{ + return aSrc; +} +#endif + +template <bool aSwapRB, bool aOpaqueAlpha> +static MOZ_ALWAYS_INLINE void SwizzleChunk_NEON(const uint8_t*& aSrc, + uint8_t*& aDst, + int32_t aAlignedRow, + int32_t aRemainder) { + // Process all 4-pixel chunks as one vector. + for (const uint8_t* end = aSrc + aAlignedRow; aSrc < end;) { + uint16x8_t px = vld1q_u16(reinterpret_cast<const uint16_t*>(aSrc)); + px = SwizzleVector_NEON<aSwapRB, aOpaqueAlpha>(px); + vst1q_u16(reinterpret_cast<uint16_t*>(aDst), px); + aSrc += 4 * 4; + aDst += 4 * 4; + } + + // Handle any 1-3 remaining pixels. + if (aRemainder) { + uint16x8_t px = LoadRemainder_NEON(aSrc, aRemainder); + px = SwizzleVector_NEON<aSwapRB, aOpaqueAlpha>(px); + StoreRemainder_NEON(aDst, aRemainder, px); + } +} + +template <bool aSwapRB, bool aOpaqueAlpha> +void SwizzleRow_NEON(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength) { + int32_t alignedRow = 4 * (aLength & ~3); + int32_t remainder = aLength & 3; + SwizzleChunk_NEON<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow, remainder); +} + +template <bool aSwapRB, bool aOpaqueAlpha> +void Swizzle_NEON(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst, + int32_t aDstGap, IntSize aSize) { + int32_t alignedRow = 4 * (aSize.width & ~3); + int32_t remainder = aSize.width & 3; + // Fold remainder into stride gap. + aSrcGap += 4 * remainder; + aDstGap += 4 * remainder; + + for (int32_t height = aSize.height; height > 0; height--) { + SwizzleChunk_NEON<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow, remainder); + aSrc += aSrcGap; + aDst += aDstGap; + } +} + +// Force instantiation of swizzle variants here. +template void SwizzleRow_NEON<true, false>(const uint8_t*, uint8_t*, int32_t); +template void SwizzleRow_NEON<true, true>(const uint8_t*, uint8_t*, int32_t); +template void Swizzle_NEON<true, false>(const uint8_t*, int32_t, uint8_t*, + int32_t, IntSize); +template void Swizzle_NEON<true, true>(const uint8_t*, int32_t, uint8_t*, + int32_t, IntSize); + +template <bool aSwapRB> +void UnpackRowRGB24(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength); + +template <bool aSwapRB> +void UnpackRowRGB24_NEON(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength) { + // Because this implementation will read an additional 4 bytes of data that + // is ignored and masked over, we cannot use the accelerated version for the + // last 1-5 pixels (3-15 bytes remaining) to guarantee we don't access memory + // outside the buffer (we read in 16 byte chunks). + if (aLength < 6) { + UnpackRowRGB24<aSwapRB>(aSrc, aDst, aLength); + return; + } + + // Because we are expanding, we can only process the data back to front in + // case we are performing this in place. + int32_t alignedRow = (aLength - 2) & ~3; + int32_t remainder = aLength - alignedRow; + + const uint8_t* src = aSrc + alignedRow * 3; + uint8_t* dst = aDst + alignedRow * 4; + + // Handle 2-5 remaining pixels. + UnpackRowRGB24<aSwapRB>(src, dst, remainder); + + uint8x8_t masklo; + uint8x8_t maskhi; + if (aSwapRB) { + static const uint8_t masklo_data[] = {2, 1, 0, 0, 5, 4, 3, 0}; + static const uint8_t maskhi_data[] = {4, 3, 2, 0, 7, 6, 5, 0}; + masklo = vld1_u8(masklo_data); + maskhi = vld1_u8(maskhi_data); + } else { + static const uint8_t masklo_data[] = {0, 1, 2, 0, 3, 4, 5, 0}; + static const uint8_t maskhi_data[] = {2, 3, 4, 0, 5, 6, 7, 0}; + masklo = vld1_u8(masklo_data); + maskhi = vld1_u8(maskhi_data); + } + + uint8x16_t alpha = vreinterpretq_u8_u32(vdupq_n_u32(0xFF000000)); + + // Process all 4-pixel chunks as one vector. + src -= 4 * 3; + dst -= 4 * 4; + while (src >= aSrc) { + uint8x16_t px = vld1q_u8(src); + // G2R2B1G1 R1B0G0R0 -> X1R1G1B1 X0R0G0B0 + uint8x8_t pxlo = vtbl1_u8(vget_low_u8(px), masklo); + // B3G3R3B2 G2R2B1G1 -> X3R3G3B3 X2R2G2B2 + uint8x8_t pxhi = + vtbl1_u8(vext_u8(vget_low_u8(px), vget_high_u8(px), 4), maskhi); + px = vcombine_u8(pxlo, pxhi); + px = vorrq_u8(px, alpha); + vst1q_u8(dst, px); + src -= 4 * 3; + dst -= 4 * 4; + } +} + +// Force instantiation of swizzle variants here. +template void UnpackRowRGB24_NEON<false>(const uint8_t*, uint8_t*, int32_t); +template void UnpackRowRGB24_NEON<true>(const uint8_t*, uint8_t*, int32_t); + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/SwizzleSSE2.cpp b/gfx/2d/SwizzleSSE2.cpp new file mode 100644 index 0000000000..da0853f440 --- /dev/null +++ b/gfx/2d/SwizzleSSE2.cpp @@ -0,0 +1,391 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Swizzle.h" + +#include <emmintrin.h> + +namespace mozilla::gfx { + +// Load 1-3 pixels into a 4 pixel vector. +static MOZ_ALWAYS_INLINE __m128i LoadRemainder_SSE2(const uint8_t* aSrc, + size_t aLength) { + __m128i px; + if (aLength >= 2) { + // Load first 2 pixels + px = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(aSrc)); + // Load third pixel + if (aLength >= 3) { + px = _mm_unpacklo_epi64( + px, + _mm_cvtsi32_si128(*reinterpret_cast<const uint32_t*>(aSrc + 2 * 4))); + } + } else { + // Load single pixel + px = _mm_cvtsi32_si128(*reinterpret_cast<const uint32_t*>(aSrc)); + } + return px; +} + +// Store 1-3 pixels from a vector into memory without overwriting. +static MOZ_ALWAYS_INLINE void StoreRemainder_SSE2(uint8_t* aDst, size_t aLength, + const __m128i& aSrc) { + if (aLength >= 2) { + // Store first 2 pixels + _mm_storel_epi64(reinterpret_cast<__m128i*>(aDst), aSrc); + // Store third pixel + if (aLength >= 3) { + *reinterpret_cast<uint32_t*>(aDst + 2 * 4) = + _mm_cvtsi128_si32(_mm_srli_si128(aSrc, 2 * 4)); + } + } else { + // Store single pixel + *reinterpret_cast<uint32_t*>(aDst) = _mm_cvtsi128_si32(aSrc); + } +} + +// Premultiply vector of 4 pixels using splayed math. +template <bool aSwapRB, bool aOpaqueAlpha> +static MOZ_ALWAYS_INLINE __m128i PremultiplyVector_SSE2(const __m128i& aSrc) { + // Isolate R and B with mask. + const __m128i mask = _mm_set1_epi32(0x00FF00FF); + __m128i rb = _mm_and_si128(mask, aSrc); + // Swap R and B if necessary. + if (aSwapRB) { + rb = _mm_shufflelo_epi16(rb, _MM_SHUFFLE(2, 3, 0, 1)); + rb = _mm_shufflehi_epi16(rb, _MM_SHUFFLE(2, 3, 0, 1)); + } + // Isolate G and A by shifting down to bottom of word. + __m128i ga = _mm_srli_epi16(aSrc, 8); + + // Duplicate alphas to get vector of A1 A1 A2 A2 A3 A3 A4 A4 + __m128i alphas = _mm_shufflelo_epi16(ga, _MM_SHUFFLE(3, 3, 1, 1)); + alphas = _mm_shufflehi_epi16(alphas, _MM_SHUFFLE(3, 3, 1, 1)); + + // rb = rb*a + 255; rb += rb >> 8; + rb = _mm_add_epi16(_mm_mullo_epi16(rb, alphas), mask); + rb = _mm_add_epi16(rb, _mm_srli_epi16(rb, 8)); + + // If format is not opaque, force A to 255 so that A*alpha/255 = alpha + if (!aOpaqueAlpha) { + ga = _mm_or_si128(ga, _mm_set1_epi32(0x00FF0000)); + } + // ga = ga*a + 255; ga += ga >> 8; + ga = _mm_add_epi16(_mm_mullo_epi16(ga, alphas), mask); + ga = _mm_add_epi16(ga, _mm_srli_epi16(ga, 8)); + // If format is opaque, force output A to be 255. + if (aOpaqueAlpha) { + ga = _mm_or_si128(ga, _mm_set1_epi32(0xFF000000)); + } + + // Combine back to final pixel with (rb >> 8) | (ga & 0xFF00FF00) + rb = _mm_srli_epi16(rb, 8); + ga = _mm_andnot_si128(mask, ga); + return _mm_or_si128(rb, ga); +} + +// Premultiply vector of aAlignedRow + aRemainder pixels. +template <bool aSwapRB, bool aOpaqueAlpha> +static MOZ_ALWAYS_INLINE void PremultiplyChunk_SSE2(const uint8_t*& aSrc, + uint8_t*& aDst, + int32_t aAlignedRow, + int32_t aRemainder) { + // Process all 4-pixel chunks as one vector. + for (const uint8_t* end = aSrc + aAlignedRow; aSrc < end;) { + __m128i px = _mm_loadu_si128(reinterpret_cast<const __m128i*>(aSrc)); + px = PremultiplyVector_SSE2<aSwapRB, aOpaqueAlpha>(px); + _mm_storeu_si128(reinterpret_cast<__m128i*>(aDst), px); + aSrc += 4 * 4; + aDst += 4 * 4; + } + + // Handle any 1-3 remaining pixels. + if (aRemainder) { + __m128i px = LoadRemainder_SSE2(aSrc, aRemainder); + px = PremultiplyVector_SSE2<aSwapRB, aOpaqueAlpha>(px); + StoreRemainder_SSE2(aDst, aRemainder, px); + } +} + +// Premultiply vector of aLength pixels. +template <bool aSwapRB, bool aOpaqueAlpha> +void PremultiplyRow_SSE2(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength) { + int32_t alignedRow = 4 * (aLength & ~3); + int32_t remainder = aLength & 3; + PremultiplyChunk_SSE2<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow, + remainder); +} + +template <bool aSwapRB, bool aOpaqueAlpha> +void Premultiply_SSE2(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst, + int32_t aDstGap, IntSize aSize) { + int32_t alignedRow = 4 * (aSize.width & ~3); + int32_t remainder = aSize.width & 3; + // Fold remainder into stride gap. + aSrcGap += 4 * remainder; + aDstGap += 4 * remainder; + + for (int32_t height = aSize.height; height > 0; height--) { + PremultiplyChunk_SSE2<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow, + remainder); + aSrc += aSrcGap; + aDst += aDstGap; + } +} + +// Force instantiation of premultiply variants here. +template void PremultiplyRow_SSE2<false, false>(const uint8_t*, uint8_t*, + int32_t); +template void PremultiplyRow_SSE2<false, true>(const uint8_t*, uint8_t*, + int32_t); +template void PremultiplyRow_SSE2<true, false>(const uint8_t*, uint8_t*, + int32_t); +template void PremultiplyRow_SSE2<true, true>(const uint8_t*, uint8_t*, + int32_t); +template void Premultiply_SSE2<false, false>(const uint8_t*, int32_t, uint8_t*, + int32_t, IntSize); +template void Premultiply_SSE2<false, true>(const uint8_t*, int32_t, uint8_t*, + int32_t, IntSize); +template void Premultiply_SSE2<true, false>(const uint8_t*, int32_t, uint8_t*, + int32_t, IntSize); +template void Premultiply_SSE2<true, true>(const uint8_t*, int32_t, uint8_t*, + int32_t, IntSize); + +// This generates a table of fixed-point reciprocals representing 1/alpha +// similar to the fallback implementation. However, the reciprocal must fit +// in 16 bits to multiply cheaply. Observe that reciprocals of smaller alphas +// require more bits than for larger alphas. We take advantage of this by +// shifting the reciprocal down by either 3 or 8 bits depending on whether +// the alpha value is less than 0x20. This is easy to then undo by multiplying +// the color component to be unpremultiplying by either 8 or 0x100, +// respectively. The 16 bit reciprocal is duplicated into both words of a +// uint32_t here to reduce unpacking overhead. +#define UNPREMULQ_SSE2(x) \ + (0x10001U * (0xFF0220U / ((x) * ((x) < 0x20 ? 0x100 : 8)))) +#define UNPREMULQ_SSE2_2(x) UNPREMULQ_SSE2(x), UNPREMULQ_SSE2((x) + 1) +#define UNPREMULQ_SSE2_4(x) UNPREMULQ_SSE2_2(x), UNPREMULQ_SSE2_2((x) + 2) +#define UNPREMULQ_SSE2_8(x) UNPREMULQ_SSE2_4(x), UNPREMULQ_SSE2_4((x) + 4) +#define UNPREMULQ_SSE2_16(x) UNPREMULQ_SSE2_8(x), UNPREMULQ_SSE2_8((x) + 8) +#define UNPREMULQ_SSE2_32(x) UNPREMULQ_SSE2_16(x), UNPREMULQ_SSE2_16((x) + 16) +static const uint32_t sUnpremultiplyTable_SSE2[256] = {0, + UNPREMULQ_SSE2(1), + UNPREMULQ_SSE2_2(2), + UNPREMULQ_SSE2_4(4), + UNPREMULQ_SSE2_8(8), + UNPREMULQ_SSE2_16(16), + UNPREMULQ_SSE2_32(32), + UNPREMULQ_SSE2_32(64), + UNPREMULQ_SSE2_32(96), + UNPREMULQ_SSE2_32(128), + UNPREMULQ_SSE2_32(160), + UNPREMULQ_SSE2_32(192), + UNPREMULQ_SSE2_32(224)}; + +// Unpremultiply a vector of 4 pixels using splayed math and a reciprocal table +// that avoids doing any actual division. +template <bool aSwapRB> +static MOZ_ALWAYS_INLINE __m128i UnpremultiplyVector_SSE2(const __m128i& aSrc) { + // Isolate R and B with mask. + __m128i rb = _mm_and_si128(aSrc, _mm_set1_epi32(0x00FF00FF)); + // Swap R and B if necessary. + if (aSwapRB) { + rb = _mm_shufflelo_epi16(rb, _MM_SHUFFLE(2, 3, 0, 1)); + rb = _mm_shufflehi_epi16(rb, _MM_SHUFFLE(2, 3, 0, 1)); + } + + // Isolate G and A by shifting down to bottom of word. + __m128i ga = _mm_srli_epi16(aSrc, 8); + // Extract the alphas for the 4 pixels from the now isolated words. + int a1 = _mm_extract_epi16(ga, 1); + int a2 = _mm_extract_epi16(ga, 3); + int a3 = _mm_extract_epi16(ga, 5); + int a4 = _mm_extract_epi16(ga, 7); + + // Load the 16 bit reciprocals from the table for each alpha. + // The reciprocals are doubled in each uint32_t entry. + // Unpack them to a final vector of duplicated reciprocals of + // the form Q1 Q1 Q2 Q2 Q3 Q3 Q4 Q4. + __m128i q12 = + _mm_unpacklo_epi32(_mm_cvtsi32_si128(sUnpremultiplyTable_SSE2[a1]), + _mm_cvtsi32_si128(sUnpremultiplyTable_SSE2[a2])); + __m128i q34 = + _mm_unpacklo_epi32(_mm_cvtsi32_si128(sUnpremultiplyTable_SSE2[a3]), + _mm_cvtsi32_si128(sUnpremultiplyTable_SSE2[a4])); + __m128i q1234 = _mm_unpacklo_epi64(q12, q34); + + // Check if the alphas are less than 0x20, so that we can undo + // scaling of the reciprocals as appropriate. + __m128i scale = _mm_cmplt_epi32(ga, _mm_set1_epi32(0x00200000)); + // Produce scale factors by ((a < 0x20) ^ 8) & 0x108, + // such that scale is 0x100 if < 0x20, and 8 otherwise. + scale = _mm_xor_si128(scale, _mm_set1_epi16(8)); + scale = _mm_and_si128(scale, _mm_set1_epi16(0x108)); + // Isolate G now so that we don't accidentally unpremultiply A. + ga = _mm_and_si128(ga, _mm_set1_epi32(0x000000FF)); + + // Scale R, B, and G as required depending on reciprocal precision. + rb = _mm_mullo_epi16(rb, scale); + ga = _mm_mullo_epi16(ga, scale); + + // Multiply R, B, and G by the reciprocal, only taking the high word + // too effectively shift right by 16. + rb = _mm_mulhi_epu16(rb, q1234); + ga = _mm_mulhi_epu16(ga, q1234); + + // Combine back to final pixel with rb | (ga << 8) | (aSrc & 0xFF000000), + // which will add back on the original alpha value unchanged. + ga = _mm_slli_si128(ga, 1); + ga = _mm_or_si128(ga, _mm_and_si128(aSrc, _mm_set1_epi32(0xFF000000))); + return _mm_or_si128(rb, ga); +} + +template <bool aSwapRB> +static MOZ_ALWAYS_INLINE void UnpremultiplyChunk_SSE2(const uint8_t*& aSrc, + uint8_t*& aDst, + int32_t aAlignedRow, + int32_t aRemainder) { + // Process all 4-pixel chunks as one vector. + for (const uint8_t* end = aSrc + aAlignedRow; aSrc < end;) { + __m128i px = _mm_loadu_si128(reinterpret_cast<const __m128i*>(aSrc)); + px = UnpremultiplyVector_SSE2<aSwapRB>(px); + _mm_storeu_si128(reinterpret_cast<__m128i*>(aDst), px); + aSrc += 4 * 4; + aDst += 4 * 4; + } + + // Handle any 1-3 remaining pixels. + if (aRemainder) { + __m128i px = LoadRemainder_SSE2(aSrc, aRemainder); + px = UnpremultiplyVector_SSE2<aSwapRB>(px); + StoreRemainder_SSE2(aDst, aRemainder, px); + } +} + +template <bool aSwapRB> +void UnpremultiplyRow_SSE2(const uint8_t* aSrc, uint8_t* aDst, + int32_t aLength) { + int32_t alignedRow = 4 * (aLength & ~3); + int32_t remainder = aLength & 3; + UnpremultiplyChunk_SSE2<aSwapRB>(aSrc, aDst, alignedRow, remainder); +} + +template <bool aSwapRB> +void Unpremultiply_SSE2(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst, + int32_t aDstGap, IntSize aSize) { + int32_t alignedRow = 4 * (aSize.width & ~3); + int32_t remainder = aSize.width & 3; + // Fold remainder into stride gap. + aSrcGap += 4 * remainder; + aDstGap += 4 * remainder; + + for (int32_t height = aSize.height; height > 0; height--) { + UnpremultiplyChunk_SSE2<aSwapRB>(aSrc, aDst, alignedRow, remainder); + aSrc += aSrcGap; + aDst += aDstGap; + } +} + +// Force instantiation of unpremultiply variants here. +template void UnpremultiplyRow_SSE2<false>(const uint8_t*, uint8_t*, int32_t); +template void UnpremultiplyRow_SSE2<true>(const uint8_t*, uint8_t*, int32_t); +template void Unpremultiply_SSE2<false>(const uint8_t*, int32_t, uint8_t*, + int32_t, IntSize); +template void Unpremultiply_SSE2<true>(const uint8_t*, int32_t, uint8_t*, + int32_t, IntSize); + +// Swizzle a vector of 4 pixels providing swaps and opaquifying. +template <bool aSwapRB, bool aOpaqueAlpha> +static MOZ_ALWAYS_INLINE __m128i SwizzleVector_SSE2(const __m128i& aSrc) { + // Isolate R and B. + __m128i rb = _mm_and_si128(aSrc, _mm_set1_epi32(0x00FF00FF)); + // Swap R and B. + rb = _mm_shufflelo_epi16(rb, _MM_SHUFFLE(2, 3, 0, 1)); + rb = _mm_shufflehi_epi16(rb, _MM_SHUFFLE(2, 3, 0, 1)); + // Isolate G and A. + __m128i ga = _mm_and_si128(aSrc, _mm_set1_epi32(0xFF00FF00)); + // Force alpha to 255 if necessary. + if (aOpaqueAlpha) { + ga = _mm_or_si128(ga, _mm_set1_epi32(0xFF000000)); + } + // Combine everything back together. + return _mm_or_si128(rb, ga); +} + +#if 0 +// These specializations currently do not profile faster than the generic versions, +// so disable them for now. + +// Optimized implementations for when there is no R and B swap. +template<> +MOZ_ALWAYS_INLINE __m128i +SwizzleVector_SSE2<false, true>(const __m128i& aSrc) +{ + // Force alpha to 255. + return _mm_or_si128(aSrc, _mm_set1_epi32(0xFF000000)); +} + +template<> +MOZ_ALWAYS_INLINE __m128i +SwizzleVector_SSE2<false, false>(const __m128i& aSrc) +{ + return aSrc; +} +#endif + +template <bool aSwapRB, bool aOpaqueAlpha> +static MOZ_ALWAYS_INLINE void SwizzleChunk_SSE2(const uint8_t*& aSrc, + uint8_t*& aDst, + int32_t aAlignedRow, + int32_t aRemainder) { + // Process all 4-pixel chunks as one vector. + for (const uint8_t* end = aSrc + aAlignedRow; aSrc < end;) { + __m128i px = _mm_loadu_si128(reinterpret_cast<const __m128i*>(aSrc)); + px = SwizzleVector_SSE2<aSwapRB, aOpaqueAlpha>(px); + _mm_storeu_si128(reinterpret_cast<__m128i*>(aDst), px); + aSrc += 4 * 4; + aDst += 4 * 4; + } + + // Handle any 1-3 remaining pixels. + if (aRemainder) { + __m128i px = LoadRemainder_SSE2(aSrc, aRemainder); + px = SwizzleVector_SSE2<aSwapRB, aOpaqueAlpha>(px); + StoreRemainder_SSE2(aDst, aRemainder, px); + } +} + +template <bool aSwapRB, bool aOpaqueAlpha> +void SwizzleRow_SSE2(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength) { + int32_t alignedRow = 4 * (aLength & ~3); + int32_t remainder = aLength & 3; + SwizzleChunk_SSE2<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow, remainder); +} + +template <bool aSwapRB, bool aOpaqueAlpha> +void Swizzle_SSE2(const uint8_t* aSrc, int32_t aSrcGap, uint8_t* aDst, + int32_t aDstGap, IntSize aSize) { + int32_t alignedRow = 4 * (aSize.width & ~3); + int32_t remainder = aSize.width & 3; + // Fold remainder into stride gap. + aSrcGap += 4 * remainder; + aDstGap += 4 * remainder; + + for (int32_t height = aSize.height; height > 0; height--) { + SwizzleChunk_SSE2<aSwapRB, aOpaqueAlpha>(aSrc, aDst, alignedRow, remainder); + aSrc += aSrcGap; + aDst += aDstGap; + } +} + +// Force instantiation of swizzle variants here. +template void SwizzleRow_SSE2<true, false>(const uint8_t*, uint8_t*, int32_t); +template void SwizzleRow_SSE2<true, true>(const uint8_t*, uint8_t*, int32_t); +template void Swizzle_SSE2<true, false>(const uint8_t*, int32_t, uint8_t*, + int32_t, IntSize); +template void Swizzle_SSE2<true, true>(const uint8_t*, int32_t, uint8_t*, + int32_t, IntSize); + +} // namespace mozilla::gfx diff --git a/gfx/2d/SwizzleSSSE3.cpp b/gfx/2d/SwizzleSSSE3.cpp new file mode 100644 index 0000000000..eac5d856fb --- /dev/null +++ b/gfx/2d/SwizzleSSSE3.cpp @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Swizzle.h" + +#include <emmintrin.h> +#include <tmmintrin.h> + +namespace mozilla::gfx { + +template <bool aSwapRB> +void UnpackRowRGB24(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength); + +template <bool aSwapRB> +void UnpackRowRGB24_SSSE3(const uint8_t* aSrc, uint8_t* aDst, int32_t aLength) { + // Because this implementation will read an additional 4 bytes of data that + // is ignored and masked over, we cannot use the accelerated version for the + // last 1-5 pixels (3-15 bytes remaining) to guarantee we don't access memory + // outside the buffer (we read in 16 byte chunks). + if (aLength < 6) { + UnpackRowRGB24<aSwapRB>(aSrc, aDst, aLength); + return; + } + + // Because we are expanding, we can only process the data back to front in + // case we are performing this in place. + int32_t alignedRow = (aLength - 2) & ~3; + int32_t remainder = aLength - alignedRow; + + const uint8_t* src = aSrc + alignedRow * 3; + uint8_t* dst = aDst + alignedRow * 4; + + // Handle 2-5 remaining pixels. + UnpackRowRGB24<aSwapRB>(src, dst, remainder); + + __m128i mask; + if (aSwapRB) { + mask = _mm_set_epi8(15, 9, 10, 11, 14, 6, 7, 8, 13, 3, 4, 5, 12, 0, 1, 2); + } else { + mask = _mm_set_epi8(15, 11, 10, 9, 14, 8, 7, 6, 13, 5, 4, 3, 12, 2, 1, 0); + } + + __m128i alpha = _mm_set1_epi32(0xFF000000); + + // Process all 4-pixel chunks as one vector. + src -= 4 * 3; + dst -= 4 * 4; + while (src >= aSrc) { + __m128i px = _mm_loadu_si128(reinterpret_cast<const __m128i*>(src)); + px = _mm_shuffle_epi8(px, mask); + px = _mm_or_si128(px, alpha); + _mm_storeu_si128(reinterpret_cast<__m128i*>(dst), px); + src -= 4 * 3; + dst -= 4 * 4; + } +} + +// Force instantiation of swizzle variants here. +template void UnpackRowRGB24_SSSE3<false>(const uint8_t*, uint8_t*, int32_t); +template void UnpackRowRGB24_SSSE3<true>(const uint8_t*, uint8_t*, int32_t); + +} // namespace mozilla::gfx diff --git a/gfx/2d/Tools.h b/gfx/2d/Tools.h new file mode 100644 index 0000000000..90ef272b48 --- /dev/null +++ b/gfx/2d/Tools.h @@ -0,0 +1,198 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TOOLS_H_ +#define MOZILLA_GFX_TOOLS_H_ + +#include <math.h> + +#include <utility> + +#include "Point.h" +#include "Types.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/MemoryReporting.h" // for MallocSizeOf + +namespace mozilla { +namespace gfx { + +static inline bool IsOperatorBoundByMask(CompositionOp aOp) { + switch (aOp) { + case CompositionOp::OP_IN: + case CompositionOp::OP_OUT: + case CompositionOp::OP_DEST_IN: + case CompositionOp::OP_DEST_ATOP: + case CompositionOp::OP_SOURCE: + return false; + default: + return true; + } +} + +template <class T> +struct ClassStorage { + char bytes[sizeof(T)]; + + const T* addr() const { return (const T*)bytes; } + T* addr() { return (T*)(void*)bytes; } +}; + +static inline bool FuzzyEqual(Float aA, Float aB, Float aErr) { + if ((aA + aErr >= aB) && (aA - aErr <= aB)) { + return true; + } + return false; +} + +static inline void NudgeToInteger(float* aVal) { + float r = floorf(*aVal + 0.5f); + // The error threshold should be proportional to the rounded value. This + // bounds the relative error introduced by the nudge operation. However, + // when the rounded value is 0, the error threshold can't be proportional + // to the rounded value (we'd never round), so we just choose the same + // threshold as for a rounded value of 1. + if (FuzzyEqual(r, *aVal, r == 0.0f ? 1e-6f : fabs(r * 1e-6f))) { + *aVal = r; + } +} + +static inline void NudgeToInteger(float* aVal, float aErr) { + float r = floorf(*aVal + 0.5f); + if (FuzzyEqual(r, *aVal, aErr)) { + *aVal = r; + } +} + +static inline void NudgeToInteger(double* aVal) { + float f = float(*aVal); + NudgeToInteger(&f); + *aVal = f; +} + +static inline Float Distance(Point aA, Point aB) { + return hypotf(aB.x - aA.x, aB.y - aA.y); +} + +template <typename T, int alignment = 16> +struct AlignedArray final { + typedef T value_type; + + AlignedArray() : mPtr(nullptr), mStorage(nullptr), mCount(0) {} + + explicit MOZ_ALWAYS_INLINE AlignedArray(size_t aCount, bool aZero = false) + : mPtr(nullptr), mStorage(nullptr), mCount(0) { + Realloc(aCount, aZero); + } + + MOZ_ALWAYS_INLINE ~AlignedArray() { Dealloc(); } + + void Dealloc() { + // If we fail this assert we'll need to uncomment the loop below to make + // sure dtors are properly invoked. If we do that, we should check that the + // comment about compiler dead code elimination is in fact true for all the + // compilers that we care about. + static_assert(std::is_trivially_destructible<T>::value, + "Destructors must be invoked for this type"); +#if 0 + for (size_t i = 0; i < mCount; ++i) { + // Since we used the placement |operator new| function to construct the + // elements of this array we need to invoke their destructors manually. + // For types where the destructor does nothing the compiler's dead code + // elimination step should optimize this loop away. + mPtr[i].~T(); + } +#endif + + free(mStorage); + mStorage = nullptr; + mPtr = nullptr; + } + + MOZ_ALWAYS_INLINE void Realloc(size_t aCount, bool aZero = false) { + free(mStorage); + CheckedInt32 storageByteCount = + CheckedInt32(sizeof(T)) * aCount + (alignment - 1); + if (!storageByteCount.isValid()) { + mStorage = nullptr; + mPtr = nullptr; + mCount = 0; + return; + } + // We don't create an array of T here, since we don't want ctors to be + // invoked at the wrong places if we realign below. + if (aZero) { + // calloc can be more efficient than new[] for large chunks, + // so we use calloc/malloc/free for everything. + mStorage = static_cast<uint8_t*>(calloc(1u, storageByteCount.value())); + } else { + mStorage = static_cast<uint8_t*>(malloc(storageByteCount.value())); + } + if (!mStorage) { + mStorage = nullptr; + mPtr = nullptr; + mCount = 0; + return; + } + if (uintptr_t(mStorage) % alignment) { + // Our storage does not start at a <alignment>-byte boundary. Make sure + // mPtr does! + mPtr = (T*)(uintptr_t(mStorage) + alignment - + (uintptr_t(mStorage) % alignment)); + } else { + mPtr = (T*)(mStorage); + } + // Now that mPtr is pointing to the aligned position we can use placement + // |operator new| to invoke any ctors at the correct positions. For types + // that have a no-op default constructor the compiler's dead code + // elimination step should optimize this away. + mPtr = new (mPtr) T[aCount]; + mCount = aCount; + } + + void Swap(AlignedArray<T, alignment>& aOther) { + std::swap(mPtr, aOther.mPtr); + std::swap(mStorage, aOther.mStorage); + std::swap(mCount, aOther.mCount); + } + + size_t HeapSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(mStorage); + } + + MOZ_ALWAYS_INLINE operator T*() { return mPtr; } + + T* mPtr; + + private: + uint8_t* mStorage; + size_t mCount; +}; + +/** + * Returns aWidth * aBytesPerPixel increased, if necessary, so that it divides + * exactly into |alignment|. + * + * Note that currently |alignment| must be a power-of-2. If for some reason we + * want to support NPOT alignment we can revert back to this functions old + * implementation. + */ +template <int alignment> +int32_t GetAlignedStride(int32_t aWidth, int32_t aBytesPerPixel) { + static_assert(alignment > 0 && (alignment & (alignment - 1)) == 0, + "This implementation currently require power-of-two alignment"); + const int32_t mask = alignment - 1; + CheckedInt32 stride = + CheckedInt32(aWidth) * CheckedInt32(aBytesPerPixel) + CheckedInt32(mask); + if (stride.isValid()) { + return stride.value() & ~mask; + } + return 0; +} + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_TOOLS_H_ */ diff --git a/gfx/2d/Triangle.h b/gfx/2d/Triangle.h new file mode 100644 index 0000000000..2e6fa2f54c --- /dev/null +++ b/gfx/2d/Triangle.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TRIANGLE_H +#define MOZILLA_GFX_TRIANGLE_H + +#include <algorithm> +#include <utility> + +#include "Point.h" +#include "Rect.h" + +namespace mozilla { +namespace gfx { + +/** + * A simple triangle data structure. + */ +template <class Units, class F = Float> +struct TriangleTyped { + PointTyped<Units, F> p1, p2, p3; + + TriangleTyped() : p1(), p2(), p3() {} + + TriangleTyped(PointTyped<Units, F> aP1, PointTyped<Units, F> aP2, + PointTyped<Units, F> aP3) + : p1(aP1), p2(aP2), p3(aP3) {} + + RectTyped<Units, F> BoundingBox() const { + F minX = std::min(std::min(p1.x, p2.x), p3.x); + F maxX = std::max(std::max(p1.x, p2.x), p3.x); + + F minY = std::min(std::min(p1.y, p2.y), p3.y); + F maxY = std::max(std::max(p1.y, p2.y), p3.y); + + return RectTyped<Units, F>(minX, minY, maxX - minX, maxY - minY); + } +}; + +typedef TriangleTyped<UnknownUnits, Float> Triangle; + +template <class Units, class F = Float> +struct TexturedTriangleTyped : public TriangleTyped<Units, F> { + explicit TexturedTriangleTyped(const TriangleTyped<Units, F>& aTriangle) + : TriangleTyped<Units, F>(aTriangle) {} + + explicit TexturedTriangleTyped(TriangleTyped<Units, F>&& aTriangle) + : TriangleTyped<Units, F>(std::move(aTriangle)) {} + + TriangleTyped<Units, F> textureCoords; +}; + +typedef TexturedTriangleTyped<UnknownUnits, Float> TexturedTriangle; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_TRIANGLE_H */ diff --git a/gfx/2d/Types.cpp b/gfx/2d/Types.cpp new file mode 100644 index 0000000000..89de1182eb --- /dev/null +++ b/gfx/2d/Types.cpp @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Types.h" + +#include "nsPrintfCString.h" + +#include <ostream> + +namespace mozilla { + +std::ostream& operator<<(std::ostream& aOut, const Side& aSide) { +#define Emit(x) \ + case x: \ + aOut << #x; \ + break + + switch (aSide) { + Emit(eSideTop); + Emit(eSideBottom); + Emit(eSideLeft); + Emit(eSideRight); + default: + NS_ERROR("unknown side"); + aOut << int(aSide); + break; + } + +#undef Emit + return aOut; +} + +namespace gfx { + +std::ostream& operator<<(std::ostream& aOut, const SurfaceFormat& aFormat) { +#define Emit(x) \ + case x: \ + aOut << #x; \ + break + + switch (aFormat) { + Emit(SurfaceFormat::B8G8R8A8); + Emit(SurfaceFormat::B8G8R8X8); + Emit(SurfaceFormat::R8G8B8A8); + Emit(SurfaceFormat::R8G8B8X8); + Emit(SurfaceFormat::A8R8G8B8); + Emit(SurfaceFormat::X8R8G8B8); + Emit(SurfaceFormat::R8G8B8); + Emit(SurfaceFormat::B8G8R8); + Emit(SurfaceFormat::R5G6B5_UINT16); + Emit(SurfaceFormat::A8); + Emit(SurfaceFormat::A16); + Emit(SurfaceFormat::R8G8); + Emit(SurfaceFormat::R16G16); + Emit(SurfaceFormat::YUV); + Emit(SurfaceFormat::NV12); + Emit(SurfaceFormat::P016); + Emit(SurfaceFormat::P010); + Emit(SurfaceFormat::YUV422); + Emit(SurfaceFormat::HSV); + Emit(SurfaceFormat::Lab); + Emit(SurfaceFormat::Depth); + default: + NS_ERROR("unknown surface format"); + aOut << "???"; + } + +#undef Emit + + return aOut; +} + +std::ostream& operator<<(std::ostream& aOut, const DeviceColor& aColor) { + aOut << nsPrintfCString("dev_rgba(%d, %d, %d, %f)", uint8_t(aColor.r * 255.f), + uint8_t(aColor.g * 255.f), uint8_t(aColor.b * 255.f), + aColor.a) + .get(); + return aOut; +} + +std::ostream& operator<<(std::ostream& aOut, const SamplingFilter& aFilter) { + switch (aFilter) { + case SamplingFilter::GOOD: + aOut << "SamplingFilter::GOOD"; + break; + case SamplingFilter::LINEAR: + aOut << "SamplingFilter::LINEAR"; + break; + case SamplingFilter::POINT: + aOut << "SamplingFilter::POINT"; + break; + default: + aOut << "???"; + } + return aOut; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/2d/Types.h b/gfx/2d/Types.h new file mode 100644 index 0000000000..caefacc116 --- /dev/null +++ b/gfx/2d/Types.h @@ -0,0 +1,1144 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TYPES_H_ +#define MOZILLA_GFX_TYPES_H_ + +#include "mozilla/DefineEnum.h" // for MOZ_DEFINE_ENUM_CLASS_WITH_BASE +#include "mozilla/EndianUtils.h" +#include "mozilla/EnumeratedRange.h" +#include "mozilla/MacroArgs.h" // for MOZ_CONCAT +#include "mozilla/TypedEnumBits.h" + +#include <iosfwd> // for ostream +#include <stddef.h> +#include <stdint.h> +#include <optional> + +namespace mozilla { +namespace gfx { + +typedef float Float; +typedef double Double; + +enum class SurfaceType : int8_t { + DATA, /* Data surface - bitmap in memory */ + D2D1_BITMAP, /* Surface wrapping a ID2D1Bitmap */ + D2D1_DRAWTARGET, /* Surface made from a D2D draw target */ + CAIRO, /* Surface wrapping a cairo surface */ + CAIRO_IMAGE, /* Data surface wrapping a cairo image surface */ + COREGRAPHICS_IMAGE, /* Surface wrapping a CoreGraphics Image */ + COREGRAPHICS_CGCONTEXT, /* Surface wrapping a CG context */ + SKIA, /* Surface wrapping a Skia bitmap */ + D2D1_1_IMAGE, /* A D2D 1.1 ID2D1Image SourceSurface */ + RECORDING, /* Surface used for recording */ + DATA_SHARED, /* Data surface using shared memory */ + DATA_RECYCLING_SHARED, /* Data surface using shared memory */ + OFFSET, /* Offset */ + DATA_ALIGNED, /* Data surface using aligned heap memory */ + DATA_SHARED_WRAPPER, /* Shared memory mapped in from another process */ + BLOB_IMAGE, /* Recorded blob image */ + DATA_MAPPED, /* Data surface wrapping a ScopedMap */ + WEBGL, /* Surface wrapping a DrawTargetWebgl texture */ +}; + +enum class SurfaceFormat : int8_t { + // The following values are named to reflect layout of colors in memory, from + // lowest byte to highest byte. The 32-bit value layout depends on machine + // endianness. + // in-memory 32-bit LE value 32-bit BE value + B8G8R8A8, // [BB, GG, RR, AA] 0xAARRGGBB 0xBBGGRRAA + B8G8R8X8, // [BB, GG, RR, 00] 0x00RRGGBB 0xBBGGRR00 + R8G8B8A8, // [RR, GG, BB, AA] 0xAABBGGRR 0xRRGGBBAA + R8G8B8X8, // [RR, GG, BB, 00] 0x00BBGGRR 0xRRGGBB00 + A8R8G8B8, // [AA, RR, GG, BB] 0xBBGGRRAA 0xAARRGGBB + X8R8G8B8, // [00, RR, GG, BB] 0xBBGGRR00 0x00RRGGBB + + R8G8B8, + B8G8R8, + + // The _UINT16 suffix here indicates that the name reflects the layout when + // viewed as a uint16_t value. In memory these values are stored using native + // endianness. + R5G6B5_UINT16, // 0bRRRRRGGGGGGBBBBB + + // This one is a single-byte, so endianness isn't an issue. + A8, + A16, + + R8G8, + R16G16, + + // These ones are their own special cases. + YUV, + NV12, // YUV 4:2:0 image with a plane of 8 bit Y samples followed by + // an interleaved U/V plane containing 8 bit 2x2 subsampled + // colour difference samples. + P016, // Similar to NV12, but with 16 bits plane values + P010, // Identical to P016 but the 6 least significant bits are 0. + // With DXGI in theory entirely compatible, however practice has + // shown that it's not the case. + YUV422, // Single plane YUV 4:2:2 interleaved as Y`0 Cb Y`1 Cr. + HSV, + Lab, + Depth, + + // This represents the unknown format. + UNKNOWN, // TODO: Replace uses with Maybe<SurfaceFormat>. + +// The following values are endian-independent synonyms. The _UINT32 suffix +// indicates that the name reflects the layout when viewed as a uint32_t +// value. +#if MOZ_LITTLE_ENDIAN() + A8R8G8B8_UINT32 = B8G8R8A8, // 0xAARRGGBB + X8R8G8B8_UINT32 = B8G8R8X8, // 0x00RRGGBB +#elif MOZ_BIG_ENDIAN() + A8R8G8B8_UINT32 = A8R8G8B8, // 0xAARRGGBB + X8R8G8B8_UINT32 = X8R8G8B8, // 0x00RRGGBB +#else +# error "bad endianness" +#endif + + // The following values are OS and endian-independent synonyms. + // + // TODO(aosmond): When everything blocking bug 1581828 has been resolved, we + // can make this use R8B8G8A8 and R8B8G8X8 for non-Windows platforms. + OS_RGBA = A8R8G8B8_UINT32, + OS_RGBX = X8R8G8B8_UINT32 +}; + +struct SurfaceFormatInfo { + bool hasColor; + bool hasAlpha; + bool isYuv; + std::optional<uint8_t> bytesPerPixel; +}; +inline std::optional<SurfaceFormatInfo> Info(const SurfaceFormat aFormat) { + auto info = SurfaceFormatInfo{}; + + switch (aFormat) { + case SurfaceFormat::B8G8R8A8: + case SurfaceFormat::R8G8B8A8: + case SurfaceFormat::A8R8G8B8: + info.hasColor = true; + info.hasAlpha = true; + break; + + case SurfaceFormat::B8G8R8X8: + case SurfaceFormat::R8G8B8X8: + case SurfaceFormat::X8R8G8B8: + case SurfaceFormat::R8G8B8: + case SurfaceFormat::B8G8R8: + case SurfaceFormat::R5G6B5_UINT16: + case SurfaceFormat::R8G8: + case SurfaceFormat::R16G16: + case SurfaceFormat::HSV: + case SurfaceFormat::Lab: + info.hasColor = true; + info.hasAlpha = false; + break; + + case SurfaceFormat::A8: + case SurfaceFormat::A16: + info.hasColor = false; + info.hasAlpha = true; + break; + + case SurfaceFormat::YUV: + case SurfaceFormat::NV12: + case SurfaceFormat::P016: + case SurfaceFormat::P010: + case SurfaceFormat::YUV422: + info.hasColor = true; + info.hasAlpha = false; + info.isYuv = true; + break; + + case SurfaceFormat::Depth: + info.hasColor = false; + info.hasAlpha = false; + info.isYuv = false; + break; + + case SurfaceFormat::UNKNOWN: + break; + } + + // - + // bytesPerPixel + + switch (aFormat) { + case SurfaceFormat::B8G8R8A8: + case SurfaceFormat::R8G8B8A8: + case SurfaceFormat::A8R8G8B8: + case SurfaceFormat::B8G8R8X8: + case SurfaceFormat::R8G8B8X8: + case SurfaceFormat::X8R8G8B8: + case SurfaceFormat::R16G16: + info.bytesPerPixel = 4; + break; + + case SurfaceFormat::R8G8B8: + case SurfaceFormat::B8G8R8: + info.bytesPerPixel = 3; + break; + + case SurfaceFormat::R5G6B5_UINT16: + case SurfaceFormat::R8G8: + case SurfaceFormat::A16: + case SurfaceFormat::Depth: // uint16_t + info.bytesPerPixel = 2; + break; + + case SurfaceFormat::A8: + info.bytesPerPixel = 1; + break; + + case SurfaceFormat::HSV: + case SurfaceFormat::Lab: + info.bytesPerPixel = 3 * sizeof(float); + break; + + case SurfaceFormat::YUV: + case SurfaceFormat::NV12: + case SurfaceFormat::P016: + case SurfaceFormat::P010: + case SurfaceFormat::YUV422: + case SurfaceFormat::UNKNOWN: + break; // No bytesPerPixel per se. + } + + // - + + if (aFormat == SurfaceFormat::UNKNOWN) { + return {}; + } + return info; +} + +std::ostream& operator<<(std::ostream& aOut, const SurfaceFormat& aFormat); + +// Represents the bit-shifts required to access color channels when the layout +// is viewed as a uint32_t value. +enum class SurfaceFormatBit : uint32_t { +#if MOZ_LITTLE_ENDIAN() + R8G8B8A8_R = 0, + R8G8B8A8_G = 8, + R8G8B8A8_B = 16, + R8G8B8A8_A = 24, +#elif MOZ_BIG_ENDIAN() + R8G8B8A8_A = 0, + R8G8B8A8_B = 8, + R8G8B8A8_G = 16, + R8G8B8A8_R = 24, +#else +# error "bad endianness" +#endif + + // The following values are endian-independent for A8R8G8B8_UINT32. + A8R8G8B8_UINT32_B = 0, + A8R8G8B8_UINT32_G = 8, + A8R8G8B8_UINT32_R = 16, + A8R8G8B8_UINT32_A = 24, + + // The following values are OS and endian-independent. + // + // TODO(aosmond): When everything blocking bug 1581828 has been resolved, we + // can make this use R8G8B8A8_X for non-Windows platforms. + OS_R = A8R8G8B8_UINT32_R, + OS_G = A8R8G8B8_UINT32_G, + OS_B = A8R8G8B8_UINT32_B, + OS_A = A8R8G8B8_UINT32_A, +}; + +inline uint32_t operator<<(uint8_t a, SurfaceFormatBit b) { + return a << static_cast<uint32_t>(b); +} + +inline uint32_t operator>>(uint32_t a, SurfaceFormatBit b) { + return a >> static_cast<uint32_t>(b); +} + +static inline int BytesPerPixel(SurfaceFormat aFormat) { + // TODO: return Info(aFormat).value().bytesPerPixel.value(); + switch (aFormat) { + case SurfaceFormat::A8: + return 1; + case SurfaceFormat::R5G6B5_UINT16: + case SurfaceFormat::A16: + return 2; + case SurfaceFormat::R8G8B8: + case SurfaceFormat::B8G8R8: + return 3; + case SurfaceFormat::HSV: + case SurfaceFormat::Lab: + return 3 * sizeof(float); + case SurfaceFormat::Depth: + return sizeof(uint16_t); + default: + return 4; + } +} + +inline bool IsOpaque(SurfaceFormat aFormat) { + // TODO: return Info(aFormat).value().hasAlpha; + switch (aFormat) { + case SurfaceFormat::B8G8R8X8: + case SurfaceFormat::R8G8B8X8: + case SurfaceFormat::X8R8G8B8: + case SurfaceFormat::R5G6B5_UINT16: + case SurfaceFormat::R8G8B8: + case SurfaceFormat::B8G8R8: + case SurfaceFormat::R8G8: + case SurfaceFormat::HSV: + case SurfaceFormat::Lab: + case SurfaceFormat::Depth: + case SurfaceFormat::YUV: + case SurfaceFormat::NV12: + case SurfaceFormat::P010: + case SurfaceFormat::P016: + case SurfaceFormat::YUV422: + return true; + default: + return false; + } +} + +// These are standardized Coding-independent Code Points +// See [Rec. ITU-T H.273 +// (12/2016)](https://www.itu.int/rec/T-REC-H.273-201612-I/en) +// +// We deliberately use an unscoped enum with fixed uint8_t representation since +// all possible values [0, 255] are legal, but it's unwieldy to declare 200+ +// "RESERVED" enumeration values. Having a fixed underlying type avoids any +// potential UB and avoids the need for a cast when passing these values across +// FFI to functions like qcms_profile_create_cicp. +namespace CICP { +enum ColourPrimaries : uint8_t { + CP_RESERVED_MIN = 0, // 0, 3, [13, 21], [23, 255] are all reserved + CP_BT709 = 1, + CP_UNSPECIFIED = 2, + CP_BT470M = 4, + CP_BT470BG = 5, + CP_BT601 = 6, + CP_SMPTE240 = 7, + CP_GENERIC_FILM = 8, + CP_BT2020 = 9, + CP_XYZ = 10, + CP_SMPTE431 = 11, + CP_SMPTE432 = 12, + CP_EBU3213 = 22, +}; + +inline bool IsReserved(ColourPrimaries aIn) { + switch (aIn) { + case CP_BT709: + case CP_UNSPECIFIED: + case CP_BT470M: + case CP_BT470BG: + case CP_BT601: + case CP_SMPTE240: + case CP_GENERIC_FILM: + case CP_BT2020: + case CP_XYZ: + case CP_SMPTE431: + case CP_SMPTE432: + case CP_EBU3213: + return false; + default: + return true; + } +} + +enum TransferCharacteristics : uint8_t { + TC_RESERVED_MIN = 0, // 0, 3, [19, 255] are all reserved + TC_BT709 = 1, + TC_UNSPECIFIED = 2, + TC_BT470M = 4, + TC_BT470BG = 5, + TC_BT601 = 6, + TC_SMPTE240 = 7, + TC_LINEAR = 8, + TC_LOG_100 = 9, + TC_LOG_100_SQRT10 = 10, + TC_IEC61966 = 11, + TC_BT_1361 = 12, + TC_SRGB = 13, + TC_BT2020_10BIT = 14, + TC_BT2020_12BIT = 15, + TC_SMPTE2084 = 16, + TC_SMPTE428 = 17, + TC_HLG = 18, +}; + +inline bool IsReserved(TransferCharacteristics aIn) { + switch (aIn) { + case TC_BT709: + case TC_UNSPECIFIED: + case TC_BT470M: + case TC_BT470BG: + case TC_BT601: + case TC_SMPTE240: + case TC_LINEAR: + case TC_LOG_100: + case TC_LOG_100_SQRT10: + case TC_IEC61966: + case TC_BT_1361: + case TC_SRGB: + case TC_BT2020_10BIT: + case TC_BT2020_12BIT: + case TC_SMPTE2084: + case TC_SMPTE428: + case TC_HLG: + return false; + default: + return true; + } +} + +enum MatrixCoefficients : uint8_t { + MC_IDENTITY = 0, + MC_BT709 = 1, + MC_UNSPECIFIED = 2, + MC_RESERVED_MIN = 3, // 3, [15, 255] are all reserved + MC_FCC = 4, + MC_BT470BG = 5, + MC_BT601 = 6, + MC_SMPTE240 = 7, + MC_YCGCO = 8, + MC_BT2020_NCL = 9, + MC_BT2020_CL = 10, + MC_SMPTE2085 = 11, + MC_CHROMAT_NCL = 12, + MC_CHROMAT_CL = 13, + MC_ICTCP = 14, +}; + +inline bool IsReserved(MatrixCoefficients aIn) { + switch (aIn) { + case MC_IDENTITY: + case MC_BT709: + case MC_UNSPECIFIED: + case MC_RESERVED_MIN: + case MC_FCC: + case MC_BT470BG: + case MC_BT601: + case MC_SMPTE240: + case MC_YCGCO: + case MC_BT2020_NCL: + case MC_BT2020_CL: + case MC_SMPTE2085: + case MC_CHROMAT_NCL: + case MC_CHROMAT_CL: + case MC_ICTCP: + return false; + default: + return true; + } +} +} // namespace CICP + +// The matrix coeffiecients used for YUV to RGB conversion. +enum class YUVColorSpace : uint8_t { + BT601, + BT709, + BT2020, + Identity, // Todo: s/YUVColorSpace/ColorSpace/, s/Identity/SRGB/ + Default = BT709, + _First = BT601, + _Last = Identity, +}; + +enum class ColorDepth : uint8_t { + COLOR_8, + COLOR_10, + COLOR_12, + COLOR_16, + _First = COLOR_8, + _Last = COLOR_16, +}; + +enum class TransferFunction : uint8_t { + BT709, + SRGB, + PQ, + HLG, + _First = BT709, + _Last = HLG, + Default = BT709, +}; + +enum class ColorRange : uint8_t { + LIMITED, + FULL, + _First = LIMITED, + _Last = FULL, +}; + +// Really "YcbcrColorColorSpace" +enum class YUVRangedColorSpace : uint8_t { + BT601_Narrow = 0, + BT601_Full, + BT709_Narrow, + BT709_Full, + BT2020_Narrow, + BT2020_Full, + GbrIdentity, + + _First = BT601_Narrow, + _Last = GbrIdentity, + Default = BT709_Narrow, +}; + +// I can either come up with a longer "very clever" name that doesn't conflict +// with FilterSupport.h, embrace and expand FilterSupport, or rename the old +// one. +// Some times Worse Is Better. +enum class ColorSpace2 : uint8_t { + Display, + UNKNOWN = Display, // We feel sufficiently bad about this TODO. + SRGB, + DISPLAY_P3, + BT601_525, // aka smpte170m NTSC + BT709, // Same gamut as SRGB, but different gamma. + BT601_625 = + BT709, // aka bt470bg PAL. Basically BT709, just Xg is 0.290 not 0.300. + BT2020, + _First = Display, + _Last = BT2020, +}; + +inline ColorSpace2 ToColorSpace2(const YUVColorSpace in) { + switch (in) { + case YUVColorSpace::BT601: + return ColorSpace2::BT601_525; + case YUVColorSpace::BT709: + return ColorSpace2::BT709; + case YUVColorSpace::BT2020: + return ColorSpace2::BT2020; + case YUVColorSpace::Identity: + return ColorSpace2::SRGB; + } + MOZ_ASSERT_UNREACHABLE(); +} + +inline YUVColorSpace ToYUVColorSpace(const ColorSpace2 in) { + switch (in) { + case ColorSpace2::BT601_525: + return YUVColorSpace::BT601; + case ColorSpace2::BT709: + return YUVColorSpace::BT709; + case ColorSpace2::BT2020: + return YUVColorSpace::BT2020; + case ColorSpace2::SRGB: + return YUVColorSpace::Identity; + + case ColorSpace2::UNKNOWN: + case ColorSpace2::DISPLAY_P3: + MOZ_CRASH("Bad ColorSpace2 for ToYUVColorSpace"); + } + MOZ_ASSERT_UNREACHABLE(); +} + +struct FromYUVRangedColorSpaceT final { + const YUVColorSpace space; + const ColorRange range; +}; + +inline FromYUVRangedColorSpaceT FromYUVRangedColorSpace( + const YUVRangedColorSpace s) { + switch (s) { + case YUVRangedColorSpace::BT601_Narrow: + return {YUVColorSpace::BT601, ColorRange::LIMITED}; + case YUVRangedColorSpace::BT601_Full: + return {YUVColorSpace::BT601, ColorRange::FULL}; + + case YUVRangedColorSpace::BT709_Narrow: + return {YUVColorSpace::BT709, ColorRange::LIMITED}; + case YUVRangedColorSpace::BT709_Full: + return {YUVColorSpace::BT709, ColorRange::FULL}; + + case YUVRangedColorSpace::BT2020_Narrow: + return {YUVColorSpace::BT2020, ColorRange::LIMITED}; + case YUVRangedColorSpace::BT2020_Full: + return {YUVColorSpace::BT2020, ColorRange::FULL}; + + case YUVRangedColorSpace::GbrIdentity: + return {YUVColorSpace::Identity, ColorRange::FULL}; + } + MOZ_CRASH("bad YUVRangedColorSpace"); +} + +// Todo: This should go in the CPP. +inline YUVRangedColorSpace ToYUVRangedColorSpace(const YUVColorSpace space, + const ColorRange range) { + bool narrow; + switch (range) { + case ColorRange::FULL: + narrow = false; + break; + case ColorRange::LIMITED: + narrow = true; + break; + } + + switch (space) { + case YUVColorSpace::Identity: + MOZ_ASSERT(range == ColorRange::FULL); + return YUVRangedColorSpace::GbrIdentity; + + case YUVColorSpace::BT601: + return narrow ? YUVRangedColorSpace::BT601_Narrow + : YUVRangedColorSpace::BT601_Full; + + case YUVColorSpace::BT709: + return narrow ? YUVRangedColorSpace::BT709_Narrow + : YUVRangedColorSpace::BT709_Full; + + case YUVColorSpace::BT2020: + return narrow ? YUVRangedColorSpace::BT2020_Narrow + : YUVRangedColorSpace::BT2020_Full; + } + MOZ_CRASH("bad YUVColorSpace"); +} + +template <typename DescriptorT> +inline YUVRangedColorSpace GetYUVRangedColorSpace(const DescriptorT& d) { + return ToYUVRangedColorSpace(d.yUVColorSpace(), d.colorRange()); +} + +static inline SurfaceFormat SurfaceFormatForColorDepth(ColorDepth aColorDepth) { + SurfaceFormat format = SurfaceFormat::A8; + switch (aColorDepth) { + case ColorDepth::COLOR_8: + break; + case ColorDepth::COLOR_10: + case ColorDepth::COLOR_12: + case ColorDepth::COLOR_16: + format = SurfaceFormat::A16; + break; + } + return format; +} + +static inline uint8_t BitDepthForColorDepth(ColorDepth aColorDepth) { + uint8_t depth = 8; + switch (aColorDepth) { + case ColorDepth::COLOR_8: + break; + case ColorDepth::COLOR_10: + depth = 10; + break; + case ColorDepth::COLOR_12: + depth = 12; + break; + case ColorDepth::COLOR_16: + depth = 16; + break; + } + return depth; +} + +static inline ColorDepth ColorDepthForBitDepth(uint8_t aBitDepth) { + ColorDepth depth = ColorDepth::COLOR_8; + switch (aBitDepth) { + case 8: + break; + case 10: + depth = ColorDepth::COLOR_10; + break; + case 12: + depth = ColorDepth::COLOR_12; + break; + case 16: + depth = ColorDepth::COLOR_16; + break; + } + return depth; +} + +// 10 and 12 bits color depth image are using 16 bits integers for storage +// As such we need to rescale the value from 10 or 12 bits to 16. +static inline uint32_t RescalingFactorForColorDepth(ColorDepth aColorDepth) { + uint32_t factor = 1; + switch (aColorDepth) { + case ColorDepth::COLOR_8: + break; + case ColorDepth::COLOR_10: + factor = 64; + break; + case ColorDepth::COLOR_12: + factor = 16; + break; + case ColorDepth::COLOR_16: + break; + } + return factor; +} + +enum class ChromaSubsampling : uint8_t { + FULL, + HALF_WIDTH, + HALF_WIDTH_AND_HEIGHT, + _First = FULL, + _Last = HALF_WIDTH_AND_HEIGHT, +}; + +template <typename T> +static inline T ChromaSize(const T& aYSize, ChromaSubsampling aSubsampling) { + switch (aSubsampling) { + case ChromaSubsampling::FULL: + return aYSize; + case ChromaSubsampling::HALF_WIDTH: + return T((aYSize.width + 1) / 2, aYSize.height); + case ChromaSubsampling::HALF_WIDTH_AND_HEIGHT: + return T((aYSize.width + 1) / 2, (aYSize.height + 1) / 2); + } + MOZ_CRASH("bad ChromaSubsampling"); +} + +enum class FilterType : int8_t { + BLEND = 0, + TRANSFORM, + MORPHOLOGY, + COLOR_MATRIX, + FLOOD, + TILE, + TABLE_TRANSFER, + DISCRETE_TRANSFER, + LINEAR_TRANSFER, + GAMMA_TRANSFER, + CONVOLVE_MATRIX, + DISPLACEMENT_MAP, + TURBULENCE, + ARITHMETIC_COMBINE, + COMPOSITE, + DIRECTIONAL_BLUR, + GAUSSIAN_BLUR, + POINT_DIFFUSE, + POINT_SPECULAR, + SPOT_DIFFUSE, + SPOT_SPECULAR, + DISTANT_DIFFUSE, + DISTANT_SPECULAR, + CROP, + PREMULTIPLY, + UNPREMULTIPLY, + OPACITY +}; + +enum class DrawTargetType : int8_t { + SOFTWARE_RASTER = 0, + HARDWARE_RASTER, + VECTOR +}; + +enum class BackendType : int8_t { + NONE = 0, + DIRECT2D, // Used for version independent D2D objects. + CAIRO, + SKIA, + RECORDING, + DIRECT2D1_1, + WEBRENDER_TEXT, + WEBGL, + + // Add new entries above this line. + BACKEND_LAST +}; + +enum class RecorderType : int8_t { + UNKNOWN, + PRIVATE, + MEMORY, + CANVAS, + PRFILEDESC, + WEBRENDER +}; + +enum class FontType : int8_t { + DWRITE, + GDI, + MAC, + FONTCONFIG, + FREETYPE, + UNKNOWN +}; + +enum class NativeSurfaceType : int8_t { + D3D10_TEXTURE, + CAIRO_CONTEXT, + CGCONTEXT, + CGCONTEXT_ACCELERATED, + OPENGL_TEXTURE, + WEBGL_CONTEXT +}; + +enum class FontStyle : int8_t { NORMAL, ITALIC, BOLD, BOLD_ITALIC }; + +enum class FontHinting : int8_t { NONE, LIGHT, NORMAL, FULL }; + +enum class CompositionOp : int8_t { + OP_CLEAR, + OP_OVER, + OP_ADD, + OP_ATOP, + OP_OUT, + OP_IN, + OP_SOURCE, + OP_DEST_IN, + OP_DEST_OUT, + OP_DEST_OVER, + OP_DEST_ATOP, + OP_XOR, + OP_MULTIPLY, + OP_SCREEN, + OP_OVERLAY, + OP_DARKEN, + OP_LIGHTEN, + OP_COLOR_DODGE, + OP_COLOR_BURN, + OP_HARD_LIGHT, + OP_SOFT_LIGHT, + OP_DIFFERENCE, + OP_EXCLUSION, + OP_HUE, + OP_SATURATION, + OP_COLOR, + OP_LUMINOSITY, + OP_COUNT +}; + +enum class Axis : int8_t { X_AXIS, Y_AXIS, BOTH }; + +enum class ExtendMode : int8_t { + CLAMP, // Do not repeat + REPEAT, // Repeat in both axis + REPEAT_X, // Only X axis + REPEAT_Y, // Only Y axis + REFLECT // Mirror the image +}; + +enum class FillRule : int8_t { FILL_WINDING, FILL_EVEN_ODD }; + +enum class AntialiasMode : int8_t { NONE, GRAY, SUBPIXEL, DEFAULT }; + +// See https://en.wikipedia.org/wiki/Texture_filtering +enum class SamplingFilter : int8_t { + GOOD, + LINEAR, + POINT, + SENTINEL // one past the last valid value +}; + +std::ostream& operator<<(std::ostream& aOut, const SamplingFilter& aFilter); + +// clang-format off +MOZ_DEFINE_ENUM_CLASS_WITH_BASE(PatternType, int8_t, ( + COLOR, + SURFACE, + LINEAR_GRADIENT, + RADIAL_GRADIENT, + CONIC_GRADIENT +)); +// clang-format on + +enum class JoinStyle : int8_t { + BEVEL, + ROUND, + MITER, //!< Mitered if within the miter limit, else, if the backed supports + //!< it (D2D), the miter is clamped. If the backend does not support + //!< miter clamping the behavior is as for MITER_OR_BEVEL. + MITER_OR_BEVEL //!< Mitered if within the miter limit, else beveled. +}; + +enum class CapStyle : int8_t { BUTT, ROUND, SQUARE }; + +enum class SamplingBounds : int8_t { UNBOUNDED, BOUNDED }; + +// Moz2d version for SVG mask types +enum class LuminanceType : int8_t { + LUMINANCE, + LINEARRGB, +}; + +/* Color is stored in non-premultiplied form in sRGB color space */ +struct sRGBColor { + public: + constexpr sRGBColor() : r(0.0f), g(0.0f), b(0.0f), a(0.0f) {} + constexpr sRGBColor(Float aR, Float aG, Float aB, Float aA) + : r(aR), g(aG), b(aB), a(aA) {} + constexpr sRGBColor(Float aR, Float aG, Float aB) + : r(aR), g(aG), b(aB), a(1.0f) {} + + static constexpr sRGBColor White(float aA) { + return sRGBColor(1.f, 1.f, 1.f, aA); + } + + static constexpr sRGBColor Black(float aA) { + return sRGBColor(0.f, 0.f, 0.f, aA); + } + + static constexpr sRGBColor OpaqueWhite() { return White(1.f); } + + static constexpr sRGBColor OpaqueBlack() { return Black(1.f); } + + static constexpr sRGBColor FromU8(uint8_t aR, uint8_t aG, uint8_t aB, + uint8_t aA) { + return sRGBColor(float(aR) / 255.f, float(aG) / 255.f, float(aB) / 255.f, + float(aA) / 255.f); + } + + static constexpr sRGBColor FromABGR(uint32_t aColor) { + return sRGBColor(((aColor >> 0) & 0xff) * (1.0f / 255.0f), + ((aColor >> 8) & 0xff) * (1.0f / 255.0f), + ((aColor >> 16) & 0xff) * (1.0f / 255.0f), + ((aColor >> 24) & 0xff) * (1.0f / 255.0f)); + } + + // The "Unusual" prefix is to avoid unintentionally using this function when + // FromABGR(), which is much more common, is needed. + static constexpr sRGBColor UnusualFromARGB(uint32_t aColor) { + return sRGBColor(((aColor >> 16) & 0xff) * (1.0f / 255.0f), + ((aColor >> 8) & 0xff) * (1.0f / 255.0f), + ((aColor >> 0) & 0xff) * (1.0f / 255.0f), + ((aColor >> 24) & 0xff) * (1.0f / 255.0f)); + } + + constexpr uint32_t ToABGR() const { + return uint32_t(r * 255.0f) | uint32_t(g * 255.0f) << 8 | + uint32_t(b * 255.0f) << 16 | uint32_t(a * 255.0f) << 24; + } + + constexpr sRGBColor Premultiplied() const { + return sRGBColor(r * a, g * a, b * a, a); + } + + constexpr sRGBColor Unpremultiplied() const { + return a > 0.f ? sRGBColor(r / a, g / a, b / a, a) : *this; + } + + // The "Unusual" prefix is to avoid unintentionally using this function when + // ToABGR(), which is much more common, is needed. + uint32_t UnusualToARGB() const { + return uint32_t(b * 255.0f) | uint32_t(g * 255.0f) << 8 | + uint32_t(r * 255.0f) << 16 | uint32_t(a * 255.0f) << 24; + } + + bool operator==(const sRGBColor& aColor) const { + return r == aColor.r && g == aColor.g && b == aColor.b && a == aColor.a; + } + + bool operator!=(const sRGBColor& aColor) const { return !(*this == aColor); } + + Float r, g, b, a; +}; + +/* Color is stored in non-premultiplied form in device color space */ +struct DeviceColor { + public: + DeviceColor() : r(0.0f), g(0.0f), b(0.0f), a(0.0f) {} + DeviceColor(Float aR, Float aG, Float aB, Float aA) + : r(aR), g(aG), b(aB), a(aA) {} + DeviceColor(Float aR, Float aG, Float aB) : r(aR), g(aG), b(aB), a(1.0f) {} + + /* The following Mask* variants are helpers used to make it clear when a + * particular color is being used for masking purposes. These masks should + * never be colored managed. */ + static DeviceColor Mask(float aC, float aA) { + return DeviceColor(aC, aC, aC, aA); + } + + static DeviceColor MaskWhite(float aA) { return Mask(1.f, aA); } + + static DeviceColor MaskBlack(float aA) { return Mask(0.f, aA); } + + static DeviceColor MaskOpaqueWhite() { return MaskWhite(1.f); } + + static DeviceColor MaskOpaqueBlack() { return MaskBlack(1.f); } + + static DeviceColor FromU8(uint8_t aR, uint8_t aG, uint8_t aB, uint8_t aA) { + return DeviceColor(float(aR) / 255.f, float(aG) / 255.f, float(aB) / 255.f, + float(aA) / 255.f); + } + + static DeviceColor FromABGR(uint32_t aColor) { + DeviceColor newColor(((aColor >> 0) & 0xff) * (1.0f / 255.0f), + ((aColor >> 8) & 0xff) * (1.0f / 255.0f), + ((aColor >> 16) & 0xff) * (1.0f / 255.0f), + ((aColor >> 24) & 0xff) * (1.0f / 255.0f)); + + return newColor; + } + + // The "Unusual" prefix is to avoid unintentionally using this function when + // FromABGR(), which is much more common, is needed. + static DeviceColor UnusualFromARGB(uint32_t aColor) { + DeviceColor newColor(((aColor >> 16) & 0xff) * (1.0f / 255.0f), + ((aColor >> 8) & 0xff) * (1.0f / 255.0f), + ((aColor >> 0) & 0xff) * (1.0f / 255.0f), + ((aColor >> 24) & 0xff) * (1.0f / 255.0f)); + + return newColor; + } + + uint32_t ToABGR() const { + return uint32_t(r * 255.0f) | uint32_t(g * 255.0f) << 8 | + uint32_t(b * 255.0f) << 16 | uint32_t(a * 255.0f) << 24; + } + + // The "Unusual" prefix is to avoid unintentionally using this function when + // ToABGR(), which is much more common, is needed. + uint32_t UnusualToARGB() const { + return uint32_t(b * 255.0f) | uint32_t(g * 255.0f) << 8 | + uint32_t(r * 255.0f) << 16 | uint32_t(a * 255.0f) << 24; + } + + bool operator==(const DeviceColor& aColor) const { + return r == aColor.r && g == aColor.g && b == aColor.b && a == aColor.a; + } + + bool operator!=(const DeviceColor& aColor) const { + return !(*this == aColor); + } + + friend std::ostream& operator<<(std::ostream& aOut, + const DeviceColor& aColor); + + Float r, g, b, a; +}; + +struct GradientStop { + bool operator<(const GradientStop& aOther) const { + return offset < aOther.offset; + } + + Float offset; + DeviceColor color; +}; + +enum class JobStatus { Complete, Wait, Yield, Error }; + +} // namespace gfx +} // namespace mozilla + +// XXX: temporary +typedef mozilla::gfx::SurfaceFormat gfxImageFormat; + +#if defined(XP_WIN) && defined(MOZ_GFX) +# ifdef GFX2D_INTERNAL +# define GFX2D_API __declspec(dllexport) +# else +# define GFX2D_API __declspec(dllimport) +# endif +#else +# define GFX2D_API +#endif + +namespace mozilla { + +// Side constants for use in various places. +enum Side : uint8_t { eSideTop, eSideRight, eSideBottom, eSideLeft }; + +std::ostream& operator<<(std::ostream&, const mozilla::Side&); + +constexpr auto AllPhysicalSides() { + return mozilla::MakeInclusiveEnumeratedRange(eSideTop, eSideLeft); +} + +enum class SideBits { + eNone = 0, + eTop = 1 << eSideTop, + eRight = 1 << eSideRight, + eBottom = 1 << eSideBottom, + eLeft = 1 << eSideLeft, + eTopBottom = SideBits::eTop | SideBits::eBottom, + eLeftRight = SideBits::eLeft | SideBits::eRight, + eAll = SideBits::eTopBottom | SideBits::eLeftRight +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SideBits) + +inline constexpr SideBits SideToSideBit(mozilla::Side aSide) { + return SideBits(1 << aSide); +} + +enum Corner : uint8_t { + // This order is important! + eCornerTopLeft = 0, + eCornerTopRight = 1, + eCornerBottomRight = 2, + eCornerBottomLeft = 3 +}; + +// RectCornerRadii::radii depends on this value. It is not being added to +// Corner because we want to lift the responsibility to handle it in the +// switch-case. +constexpr int eCornerCount = 4; + +constexpr auto AllPhysicalCorners() { + return mozilla::MakeInclusiveEnumeratedRange(eCornerTopLeft, + eCornerBottomLeft); +} + +// Indices into "half corner" arrays (nsStyleCorners e.g.) +enum HalfCorner : uint8_t { + // This order is important! + eCornerTopLeftX = 0, + eCornerTopLeftY = 1, + eCornerTopRightX = 2, + eCornerTopRightY = 3, + eCornerBottomRightX = 4, + eCornerBottomRightY = 5, + eCornerBottomLeftX = 6, + eCornerBottomLeftY = 7 +}; + +constexpr auto AllPhysicalHalfCorners() { + return mozilla::MakeInclusiveEnumeratedRange(eCornerTopLeftX, + eCornerBottomLeftY); +} + +// The result of these conversion functions are exhaustively checked in +// nsFrame.cpp, which also serves as usage examples. + +constexpr bool HalfCornerIsX(HalfCorner aHalfCorner) { + return !(aHalfCorner % 2); +} + +constexpr Corner HalfToFullCorner(HalfCorner aHalfCorner) { + return Corner(aHalfCorner / 2); +} + +constexpr HalfCorner FullToHalfCorner(Corner aCorner, bool aIsVertical) { + return HalfCorner(aCorner * 2 + aIsVertical); +} + +constexpr bool SideIsVertical(mozilla::Side aSide) { return aSide % 2; } + +// @param aIsSecond when true, return the clockwise second of the two +// corners associated with aSide. For example, with aSide = eSideBottom the +// result is eCornerBottomRight when aIsSecond is false, and +// eCornerBottomLeft when aIsSecond is true. +constexpr Corner SideToFullCorner(mozilla::Side aSide, bool aIsSecond) { + return Corner((aSide + aIsSecond) % 4); +} + +// @param aIsSecond see SideToFullCorner. +// @param aIsParallel return the half-corner that is parallel with aSide +// when aIsParallel is true. For example with aSide=eSideTop, aIsSecond=true +// the result is eCornerTopRightX when aIsParallel is true, and +// eCornerTopRightY when aIsParallel is false (because "X" is parallel with +// eSideTop/eSideBottom, similarly "Y" is parallel with +// eSideLeft/eSideRight) +constexpr HalfCorner SideToHalfCorner(mozilla::Side aSide, bool aIsSecond, + bool aIsParallel) { + return HalfCorner(((aSide + aIsSecond) * 2 + (aSide + !aIsParallel) % 2) % 8); +} + +} // namespace mozilla + +#endif /* MOZILLA_GFX_TYPES_H_ */ diff --git a/gfx/2d/UnscaledFontDWrite.h b/gfx/2d/UnscaledFontDWrite.h new file mode 100644 index 0000000000..feed553790 --- /dev/null +++ b/gfx/2d/UnscaledFontDWrite.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_UNSCALEDFONTDWRITE_H_ +#define MOZILLA_GFX_UNSCALEDFONTDWRITE_H_ + +#include <dwrite.h> + +#include "2D.h" + +namespace mozilla { +namespace gfx { + +class ScaledFontDWrite; + +class UnscaledFontDWrite final : public UnscaledFont { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFontDWrite, override) + UnscaledFontDWrite(const RefPtr<IDWriteFontFace>& aFontFace, + const RefPtr<IDWriteFont>& aFont) + : mFontFace(aFontFace), mFont(aFont) {} + + FontType GetType() const override { return FontType::DWRITE; } + + const RefPtr<IDWriteFontFace>& GetFontFace() const { return mFontFace; } + const RefPtr<IDWriteFont>& GetFont() const { return mFont; } + + bool GetFontFileData(FontFileDataOutput aDataCallback, void* aBaton) override; + + already_AddRefed<ScaledFont> CreateScaledFont( + Float aGlyphSize, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength, const FontVariation* aVariations, + uint32_t aNumVariations) override; + + already_AddRefed<ScaledFont> CreateScaledFontFromWRFont( + Float aGlyphSize, const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions, + const FontVariation* aVariations, uint32_t aNumVariations) override; + + bool GetFontDescriptor(FontDescriptorOutput aCb, void* aBaton) override; + + static already_AddRefed<UnscaledFont> CreateFromFontDescriptor( + const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex); + + private: + bool InitBold(); + + RefPtr<IDWriteFontFace> mFontFace; + RefPtr<IDWriteFontFace> mFontFaceBold; + RefPtr<IDWriteFont> mFont; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_UNSCALEDFONTDWRITE_H_ */ diff --git a/gfx/2d/UnscaledFontFreeType.cpp b/gfx/2d/UnscaledFontFreeType.cpp new file mode 100644 index 0000000000..97af653ed6 --- /dev/null +++ b/gfx/2d/UnscaledFontFreeType.cpp @@ -0,0 +1,242 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "UnscaledFontFreeType.h" +#include "NativeFontResourceFreeType.h" +#include "ScaledFontFreeType.h" +#include "Logging.h" +#include "StackArray.h" + +#include FT_MULTIPLE_MASTERS_H +#include FT_TRUETYPE_TABLES_H + +#include <dlfcn.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> + +namespace mozilla::gfx { + +bool UnscaledFontFreeType::GetFontFileData(FontFileDataOutput aDataCallback, + void* aBaton) { + if (!mFile.empty()) { + int fd = open(mFile.c_str(), O_RDONLY); + if (fd < 0) { + return false; + } + struct stat buf; + if (fstat(fd, &buf) < 0 || + // Don't erroneously read directories as files. + !S_ISREG(buf.st_mode) || + // Verify the file size fits in a uint32_t. + buf.st_size <= 0 || off_t(uint32_t(buf.st_size)) != buf.st_size) { + close(fd); + return false; + } + uint32_t length = buf.st_size; + uint8_t* fontData = reinterpret_cast<uint8_t*>( + mmap(nullptr, length, PROT_READ, MAP_PRIVATE, fd, 0)); + close(fd); + if (fontData == MAP_FAILED) { + return false; + } + aDataCallback(fontData, length, mIndex, aBaton); + munmap(fontData, length); + return true; + } + + bool success = false; + FT_ULong length = 0; + // Request the SFNT file. This may not always succeed for all font types. + if (FT_Load_Sfnt_Table(mFace->GetFace(), 0, 0, nullptr, &length) == + FT_Err_Ok) { + uint8_t* fontData = new uint8_t[length]; + if (FT_Load_Sfnt_Table(mFace->GetFace(), 0, 0, fontData, &length) == + FT_Err_Ok) { + aDataCallback(fontData, length, 0, aBaton); + success = true; + } + delete[] fontData; + } + return success; +} + +bool UnscaledFontFreeType::GetFontDescriptor(FontDescriptorOutput aCb, + void* aBaton) { + if (mFile.empty()) { + return false; + } + + aCb(reinterpret_cast<const uint8_t*>(mFile.data()), mFile.size(), mIndex, + aBaton); + return true; +} + +RefPtr<SharedFTFace> UnscaledFontFreeType::InitFace() { + if (mFace) { + return mFace; + } + if (mFile.empty()) { + return nullptr; + } + mFace = Factory::NewSharedFTFace(nullptr, mFile.c_str(), mIndex); + if (!mFace) { + gfxWarning() << "Failed initializing FreeType face from filename"; + return nullptr; + } + return mFace; +} + +void UnscaledFontFreeType::GetVariationSettingsFromFace( + std::vector<FontVariation>* aVariations, FT_Face aFace) { + if (!aFace || !(aFace->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS)) { + return; + } + + typedef FT_Error (*GetVarFunc)(FT_Face, FT_MM_Var**); + typedef FT_Error (*DoneVarFunc)(FT_Library, FT_MM_Var*); + typedef FT_Error (*GetVarDesignCoordsFunc)(FT_Face, FT_UInt, FT_Fixed*); +#if MOZ_TREE_FREETYPE + GetVarFunc getVar = &FT_Get_MM_Var; + DoneVarFunc doneVar = &FT_Done_MM_Var; + GetVarDesignCoordsFunc getCoords = &FT_Get_Var_Design_Coordinates; +#else + static GetVarFunc getVar; + static DoneVarFunc doneVar; + static GetVarDesignCoordsFunc getCoords; + static bool firstTime = true; + if (firstTime) { + firstTime = false; + getVar = (GetVarFunc)dlsym(RTLD_DEFAULT, "FT_Get_MM_Var"); + doneVar = (DoneVarFunc)dlsym(RTLD_DEFAULT, "FT_Done_MM_Var"); + getCoords = (GetVarDesignCoordsFunc)dlsym(RTLD_DEFAULT, + "FT_Get_Var_Design_Coordinates"); + } + if (!getVar || !getCoords) { + return; + } +#endif + + FT_MM_Var* mmVar = nullptr; + if ((*getVar)(aFace, &mmVar) == FT_Err_Ok) { + aVariations->reserve(mmVar->num_axis); + StackArray<FT_Fixed, 32> coords(mmVar->num_axis); + if ((*getCoords)(aFace, mmVar->num_axis, coords.data()) == FT_Err_Ok) { + bool changed = false; + for (uint32_t i = 0; i < mmVar->num_axis; i++) { + if (coords[i] != mmVar->axis[i].def) { + changed = true; + } + aVariations->push_back(FontVariation{uint32_t(mmVar->axis[i].tag), + float(coords[i] / 65536.0)}); + } + if (!changed) { + aVariations->clear(); + } + } + if (doneVar) { + (*doneVar)(aFace->glyph->library, mmVar); + } else { + free(mmVar); + } + } +} + +void UnscaledFontFreeType::ApplyVariationsToFace( + const FontVariation* aVariations, uint32_t aNumVariations, FT_Face aFace) { + if (!aFace || !(aFace->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS)) { + return; + } + + typedef FT_Error (*SetVarDesignCoordsFunc)(FT_Face, FT_UInt, FT_Fixed*); +#ifdef MOZ_TREE_FREETYPE + SetVarDesignCoordsFunc setCoords = &FT_Set_Var_Design_Coordinates; +#else + typedef FT_Error (*SetVarDesignCoordsFunc)(FT_Face, FT_UInt, FT_Fixed*); + static SetVarDesignCoordsFunc setCoords; + static bool firstTime = true; + if (firstTime) { + firstTime = false; + setCoords = (SetVarDesignCoordsFunc)dlsym(RTLD_DEFAULT, + "FT_Set_Var_Design_Coordinates"); + } + if (!setCoords) { + return; + } +#endif + + StackArray<FT_Fixed, 32> coords(aNumVariations); + for (uint32_t i = 0; i < aNumVariations; i++) { + coords[i] = std::round(aVariations[i].mValue * 65536.0f); + } + if ((*setCoords)(aFace, aNumVariations, coords.data()) != FT_Err_Ok) { + // ignore the problem? + } +} + +#ifdef MOZ_WIDGET_ANDROID + +already_AddRefed<ScaledFont> UnscaledFontFreeType::CreateScaledFont( + Float aGlyphSize, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength, const FontVariation* aVariations, + uint32_t aNumVariations) { + if (aInstanceDataLength < sizeof(ScaledFontFreeType::InstanceData)) { + gfxWarning() << "FreeType scaled font instance data is truncated."; + return nullptr; + } + const ScaledFontFreeType::InstanceData& instanceData = + *reinterpret_cast<const ScaledFontFreeType::InstanceData*>(aInstanceData); + + RefPtr<SharedFTFace> face(InitFace()); + if (!face) { + gfxWarning() << "Attempted to deserialize FreeType scaled font without " + "FreeType face"; + return nullptr; + } + + if (aNumVariations > 0 && face->GetData()) { + if (RefPtr<SharedFTFace> varFace = face->GetData()->CloneFace()) { + face = varFace; + } + } + + // Only apply variations if we have an explicitly cloned face. + if (aNumVariations > 0 && face != GetFace()) { + ApplyVariationsToFace(aVariations, aNumVariations, face->GetFace()); + } + + RefPtr<ScaledFontFreeType> scaledFont = new ScaledFontFreeType( + std::move(face), this, aGlyphSize, instanceData.mApplySyntheticBold); + + return scaledFont.forget(); +} + +already_AddRefed<ScaledFont> UnscaledFontFreeType::CreateScaledFontFromWRFont( + Float aGlyphSize, const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions, + const FontVariation* aVariations, uint32_t aNumVariations) { + ScaledFontFreeType::InstanceData instanceData(aOptions, aPlatformOptions); + return CreateScaledFont(aGlyphSize, reinterpret_cast<uint8_t*>(&instanceData), + sizeof(instanceData), aVariations, aNumVariations); +} + +already_AddRefed<UnscaledFont> UnscaledFontFreeType::CreateFromFontDescriptor( + const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex) { + if (aDataLength == 0) { + gfxWarning() << "FreeType font descriptor is truncated."; + return nullptr; + } + const char* path = reinterpret_cast<const char*>(aData); + RefPtr<UnscaledFont> unscaledFont = + new UnscaledFontFreeType(std::string(path, aDataLength), aIndex); + return unscaledFont.forget(); +} + +#endif // MOZ_WIDGET_ANDROID + +} // namespace mozilla::gfx diff --git a/gfx/2d/UnscaledFontFreeType.h b/gfx/2d/UnscaledFontFreeType.h new file mode 100644 index 0000000000..2194c8e776 --- /dev/null +++ b/gfx/2d/UnscaledFontFreeType.h @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_UNSCALEDFONTFREETYPE_H_ +#define MOZILLA_GFX_UNSCALEDFONTFREETYPE_H_ + +#include <cairo-ft.h> + +#include "2D.h" + +namespace mozilla { +namespace gfx { + +class ScaledFontFreeType; +class ScaledFontFontconfig; + +class UnscaledFontFreeType : public UnscaledFont { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFontFreeType, override) + explicit UnscaledFontFreeType(const RefPtr<SharedFTFace>& aFace) + : mFace(aFace), mIndex(0) {} + explicit UnscaledFontFreeType(const char* aFile, uint32_t aIndex = 0, + RefPtr<SharedFTFace> aFace = nullptr) + : mFace(std::move(aFace)), mFile(aFile), mIndex(aIndex) {} + explicit UnscaledFontFreeType(std::string&& aFile, uint32_t aIndex = 0, + RefPtr<SharedFTFace> aFace = nullptr) + : mFace(std::move(aFace)), mFile(std::move(aFile)), mIndex(aIndex) {} + + FontType GetType() const override { return FontType::FREETYPE; } + + const RefPtr<SharedFTFace>& GetFace() const { return mFace; } + const std::string& GetFile() const { return mFile; } + uint32_t GetIndex() const { return mIndex; } + + bool GetFontFileData(FontFileDataOutput aDataCallback, void* aBaton) override; + + bool GetFontDescriptor(FontDescriptorOutput aCb, void* aBaton) override; + + RefPtr<SharedFTFace> InitFace(); + +#ifdef MOZ_WIDGET_ANDROID + static already_AddRefed<UnscaledFont> CreateFromFontDescriptor( + const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex); + + already_AddRefed<ScaledFont> CreateScaledFont( + Float aGlyphSize, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength, const FontVariation* aVariations, + uint32_t aNumVariations) override; + + already_AddRefed<ScaledFont> CreateScaledFontFromWRFont( + Float aGlyphSize, const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions, + const FontVariation* aVariations, uint32_t aNumVariations) override; +#endif + + protected: + RefPtr<SharedFTFace> mFace; + std::string mFile; + uint32_t mIndex; + + friend class ScaledFontFreeType; + friend class ScaledFontFontconfig; + + static void GetVariationSettingsFromFace( + std::vector<FontVariation>* aVariations, FT_Face aFace); + + static void ApplyVariationsToFace(const FontVariation* aVariations, + uint32_t aNumVariations, FT_Face aFace); +}; + +#ifdef MOZ_WIDGET_GTK +class UnscaledFontFontconfig : public UnscaledFontFreeType { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFontFontconfig, override) + explicit UnscaledFontFontconfig(const RefPtr<SharedFTFace>& aFace) + : UnscaledFontFreeType(aFace) {} + explicit UnscaledFontFontconfig(const char* aFile, uint32_t aIndex = 0, + RefPtr<SharedFTFace> aFace = nullptr) + : UnscaledFontFreeType(aFile, aIndex, std::move(aFace)) {} + explicit UnscaledFontFontconfig(std::string&& aFile, uint32_t aIndex = 0, + RefPtr<SharedFTFace> aFace = nullptr) + : UnscaledFontFreeType(std::move(aFile), aIndex, std::move(aFace)) {} + + FontType GetType() const override { return FontType::FONTCONFIG; } + + static already_AddRefed<UnscaledFont> CreateFromFontDescriptor( + const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex); + + already_AddRefed<ScaledFont> CreateScaledFont( + Float aGlyphSize, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength, const FontVariation* aVariations, + uint32_t aNumVariations) override; + + already_AddRefed<ScaledFont> CreateScaledFontFromWRFont( + Float aGlyphSize, const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions, + const FontVariation* aVariations, uint32_t aNumVariations) override; +}; + +extern bool FcPatternAllowsBitmaps(FcPattern* aPattern, bool aAntialias, + bool aHinting); +#endif + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_UNSCALEDFONTFREETYPE_H_ */ diff --git a/gfx/2d/UnscaledFontGDI.h b/gfx/2d/UnscaledFontGDI.h new file mode 100644 index 0000000000..3601110491 --- /dev/null +++ b/gfx/2d/UnscaledFontGDI.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_UNSCALEDFONTGDI_H_ +#define MOZILLA_GFX_UNSCALEDFONTGDI_H_ + +#include "2D.h" +#include <windows.h> + +namespace mozilla { +namespace gfx { + +class UnscaledFontGDI final : public UnscaledFont { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFontGDI, override) + explicit UnscaledFontGDI(const LOGFONT& aLogFont) : mLogFont(aLogFont) {} + + FontType GetType() const override { return FontType::GDI; } + + const LOGFONT& GetLogFont() const { return mLogFont; } + + bool GetFontFileData(FontFileDataOutput aDataCallback, void* aBaton) override; + + bool GetFontDescriptor(FontDescriptorOutput aCb, void* aBaton) override; + + bool GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) override; + + static already_AddRefed<UnscaledFont> CreateFromFontDescriptor( + const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex); + + already_AddRefed<ScaledFont> CreateScaledFont( + Float aGlyphSize, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength, const FontVariation* aVariations, + uint32_t aNumVariations) override; + + private: + LOGFONT mLogFont; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_UNSCALEDFONTGDI_H_ */ diff --git a/gfx/2d/UnscaledFontMac.h b/gfx/2d/UnscaledFontMac.h new file mode 100644 index 0000000000..72524feeb4 --- /dev/null +++ b/gfx/2d/UnscaledFontMac.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_UNSCALEDFONTMAC_H_ +#define MOZILLA_GFX_UNSCALEDFONTMAC_H_ + +#ifdef MOZ_WIDGET_COCOA +# include <ApplicationServices/ApplicationServices.h> +#else +# include <CoreGraphics/CoreGraphics.h> +# include <CoreText/CoreText.h> +#endif + +#include "2D.h" + +namespace mozilla { +namespace gfx { + +class UnscaledFontMac final : public UnscaledFont { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(UnscaledFontMac, override) + explicit UnscaledFontMac(CGFontRef aFont, bool aIsDataFont = false) + : mFont(aFont), mIsDataFont(aIsDataFont) { + CFRetain(mFont); + } + explicit UnscaledFontMac(CTFontDescriptorRef aFontDesc, CGFontRef aFont, + bool aIsDataFont = false) + : mFontDesc(aFontDesc), mFont(aFont), mIsDataFont(aIsDataFont) { + CFRetain(mFontDesc); + CFRetain(mFont); + } + + virtual ~UnscaledFontMac() { + if (mCTAxesCache) { + CFRelease(mCTAxesCache); + } + if (mCGAxesCache) { + CFRelease(mCGAxesCache); + } + if (mFontDesc) { + CFRelease(mFontDesc); + } + if (mFont) { + CFRelease(mFont); + } + } + + FontType GetType() const override { return FontType::MAC; } + + CGFontRef GetFont() const { return mFont; } + + bool GetFontFileData(FontFileDataOutput aDataCallback, void* aBaton) override; + + bool IsDataFont() const { return mIsDataFont; } + + already_AddRefed<ScaledFont> CreateScaledFont( + Float aGlyphSize, const uint8_t* aInstanceData, + uint32_t aInstanceDataLength, const FontVariation* aVariations, + uint32_t aNumVariations) override; + + already_AddRefed<ScaledFont> CreateScaledFontFromWRFont( + Float aGlyphSize, const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions, + const FontVariation* aVariations, uint32_t aNumVariations) override; + + static CGFontRef CreateCGFontWithVariations(CGFontRef aFont, + CFArrayRef& aCGAxesCache, + CFArrayRef& aCTAxesCache, + uint32_t aVariationCount, + const FontVariation* aVariations); + + // Generate a font descriptor to send to WebRender. The descriptor consists + // of a string that concatenates the PostScript name of the font and the path + // to the font file, and an "index" that indicates the length of the psname + // part of the string (= starting offset of the path). + bool GetFontDescriptor(FontDescriptorOutput aCb, void* aBaton) override; + + CFArrayRef& CGAxesCache() { return mCGAxesCache; } + CFArrayRef& CTAxesCache() { return mCTAxesCache; } + + static already_AddRefed<UnscaledFont> CreateFromFontDescriptor( + const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex); + + private: + CTFontDescriptorRef mFontDesc = nullptr; + CGFontRef mFont = nullptr; + CFArrayRef mCGAxesCache = nullptr; // Cached arrays of variation axis details + CFArrayRef mCTAxesCache = nullptr; + bool mIsDataFont; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_UNSCALEDFONTMAC_H_ */ diff --git a/gfx/2d/UserData.h b/gfx/2d/UserData.h new file mode 100644 index 0000000000..16a7db343e --- /dev/null +++ b/gfx/2d/UserData.h @@ -0,0 +1,214 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_USERDATA_H_ +#define MOZILLA_GFX_USERDATA_H_ + +#include <stdlib.h> +#include "Types.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Mutex.h" + +namespace mozilla { +namespace gfx { + +struct UserDataKey { + int unused; +}; + +/* this class is basically a clone of the user data concept from cairo */ +class UserData { + public: + typedef void (*DestroyFunc)(void* data); + + UserData() : count(0), entries(nullptr) {} + + /* Attaches untyped userData associated with key. destroy is called on + * destruction */ + void Add(UserDataKey* key, void* userData, DestroyFunc destroy) { + for (int i = 0; i < count; i++) { + if (key == entries[i].key) { + if (entries[i].destroy) { + entries[i].destroy(entries[i].userData); + } + entries[i].userData = userData; + entries[i].destroy = destroy; + return; + } + } + + // We could keep entries in a std::vector instead of managing it by hand + // but that would propagate an stl dependency out which we'd rather not + // do (see bug 666609). Plus, the entries array is expect to stay small + // so doing a realloc everytime we add a new entry shouldn't be too costly + entries = + static_cast<Entry*>(realloc(entries, sizeof(Entry) * (count + 1))); + + if (!entries) { + MOZ_CRASH("GFX: UserData::Add"); + } + + entries[count].key = key; + entries[count].userData = userData; + entries[count].destroy = destroy; + + count++; + } + + /* Remove and return user data associated with key, without destroying it */ + void* Remove(UserDataKey* key) { + for (int i = 0; i < count; i++) { + if (key == entries[i].key) { + void* userData = entries[i].userData; + // decrement before looping so entries[i+1] doesn't read past the end: + --count; + for (; i < count; i++) { + entries[i] = entries[i + 1]; + } + return userData; + } + } + return nullptr; + } + + /* Remove and destroy a given key */ + void RemoveAndDestroy(UserDataKey* key) { + for (int i = 0; i < count; i++) { + if (key == entries[i].key) { + if (entries[i].destroy) { + entries[i].destroy(entries[i].userData); + } + // decrement before looping so entries[i+1] doesn't read past the end: + --count; + for (; i < count; i++) { + entries[i] = entries[i + 1]; + } + } + } + } + + /* Retrives the userData for the associated key */ + void* Get(UserDataKey* key) const { + for (int i = 0; i < count; i++) { + if (key == entries[i].key) { + return entries[i].userData; + } + } + return nullptr; + } + + bool Has(UserDataKey* key) { + for (int i = 0; i < count; i++) { + if (key == entries[i].key) { + return true; + } + } + return false; + } + + void Destroy() { + if (!entries) { + return; + } + for (int i = 0; i < count; i++) { + if (entries[i].destroy) { + entries[i].destroy(entries[i].userData); + } + } + free(entries); + entries = nullptr; + count = 0; + } + + ~UserData() { Destroy(); } + + private: + struct Entry { + const UserDataKey* key; + void* userData; + DestroyFunc destroy; + }; + + int count; + Entry* entries; +}; + +class ThreadSafeUserData { + protected: + struct LockedUserData : public UserData { + Mutex mLock; + + LockedUserData() : mLock("LockedUserData::mLock") {} + }; + + public: + ~ThreadSafeUserData() { + if (LockedUserData* userData = mUserData.exchange(nullptr)) { + { + MutexAutoLock lock(userData->mLock); + userData->Destroy(); + } + delete userData; + } + } + + void Add(UserDataKey* key, void* value, UserData::DestroyFunc destroy) { + LockedUserData* userData = GetUserData(); + MutexAutoLock lock(userData->mLock); + userData->Add(key, value, destroy); + } + + void* Remove(UserDataKey* key) { + LockedUserData* userData = GetUserData(); + MutexAutoLock lock(userData->mLock); + return userData->Remove(key); + } + + void RemoveAndDestroy(UserDataKey* key) { + LockedUserData* userData = GetUserData(); + MutexAutoLock lock(userData->mLock); + userData->RemoveAndDestroy(key); + } + + void* Get(UserDataKey* key) const { + LockedUserData* userData = GetUserData(); + MutexAutoLock lock(userData->mLock); + return userData->Get(key); + } + + bool Has(UserDataKey* key) { + LockedUserData* userData = GetUserData(); + MutexAutoLock lock(userData->mLock); + return userData->Has(key); + } + + private: + LockedUserData* GetUserData() const { + LockedUserData* userData = mUserData; + if (!userData) { + userData = new LockedUserData; + if (!mUserData.compareExchange(nullptr, userData)) { + delete userData; + userData = mUserData; + MOZ_ASSERT(userData); + } + } + return userData; + } + + // The Mutex class is quite large. For small, frequent classes (ScaledFont, + // SourceSurface, etc.) this can add a lot of memory overhead, especially if + // UserData is only infrequently used. To avoid this, we only allocate the + // LockedUserData if it is actually used. If unused, it only adds a single + // pointer as overhead. + mutable Atomic<LockedUserData*> mUserData; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_USERDATA_H_ */ diff --git a/gfx/2d/genshaders.sh b/gfx/2d/genshaders.sh new file mode 100644 index 0000000000..ce1b750676 --- /dev/null +++ b/gfx/2d/genshaders.sh @@ -0,0 +1,12 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +fxc ShadersD2D.fx -nologo -FhShadersD2D.h -Tfx_4_0 -Vn d2deffect +fxc ShadersD2D1.hlsl -ESampleRadialGradientPS -nologo -Tps_4_0_level_9_3 -Fhtmpfile -VnSampleRadialGradientPS +cat tmpfile > ShadersD2D1.h +fxc ShadersD2D1.hlsl -ESampleRadialGradientA0PS -nologo -Tps_4_0_level_9_3 -Fhtmpfile -VnSampleRadialGradientA0PS +cat tmpfile >> ShadersD2D1.h +fxc ShadersD2D1.hlsl -ESampleConicGradientPS -nologo -Tps_4_0_level_9_3 -Fhtmpfile -VnSampleConicGradientPS +cat tmpfile >> ShadersD2D1.h +rm tmpfile diff --git a/gfx/2d/gfx2d.sln b/gfx/2d/gfx2d.sln new file mode 100644 index 0000000000..40a137a1c9 --- /dev/null +++ b/gfx/2d/gfx2d.sln @@ -0,0 +1,29 @@ +
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gfx2d", "gfx2d.vcxproj", "{49E973D7-53C9-3D66-BE58-52125FAE193D}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "unittest", "unittest\unittest.vcxproj", "{CCF4BC8B-0CED-47CA-B621-ABF1832527D9}"
+ ProjectSection(ProjectDependencies) = postProject
+ {49E973D7-53C9-3D66-BE58-52125FAE193D} = {49E973D7-53C9-3D66-BE58-52125FAE193D}
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {49E973D7-53C9-3D66-BE58-52125FAE193D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {49E973D7-53C9-3D66-BE58-52125FAE193D}.Debug|Win32.Build.0 = Debug|Win32
+ {49E973D7-53C9-3D66-BE58-52125FAE193D}.Release|Win32.ActiveCfg = Release|Win32
+ {49E973D7-53C9-3D66-BE58-52125FAE193D}.Release|Win32.Build.0 = Release|Win32
+ {CCF4BC8B-0CED-47CA-B621-ABF1832527D9}.Debug|Win32.ActiveCfg = Debug|Win32
+ {CCF4BC8B-0CED-47CA-B621-ABF1832527D9}.Debug|Win32.Build.0 = Debug|Win32
+ {CCF4BC8B-0CED-47CA-B621-ABF1832527D9}.Release|Win32.ActiveCfg = Release|Win32
+ {CCF4BC8B-0CED-47CA-B621-ABF1832527D9}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/gfx/2d/gfx2d.vcxproj b/gfx/2d/gfx2d.vcxproj new file mode 100644 index 0000000000..8b46078998 --- /dev/null +++ b/gfx/2d/gfx2d.vcxproj @@ -0,0 +1,139 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <Keyword>Win32Proj</Keyword>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <ExecutablePath>$(DXSDK_DIR)\Utilities\bin\x86;$(ExecutablePath)</ExecutablePath>
+ <IncludePath>$(ProjectDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PreprocessorDefinitions>USE_SSE2;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions);MFBT_STAND_ALONE;XP_WIN</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <Optimization>Disabled</Optimization>
+ </ClCompile>
+ <Link>
+ <TargetMachine>MachineX86</TargetMachine>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <EntryPointSymbol>
+ </EntryPointSymbol>
+ </Link>
+ <PreBuildEvent>
+ <Command>xcopy $(ProjectDir)..\..\mfbt\*.h mozilla\ /Y</Command>
+ <Message>Copying MFBT files</Message>
+ </PreBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <PreprocessorDefinitions>USE_SSE2;WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <AdditionalIncludeDirectories>./</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <TargetMachine>MachineX86</TargetMachine>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Windows</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="2D.h" />
+ <ClInclude Include="BaseMargin.h" />
+ <ClInclude Include="BasePoint.h" />
+ <ClInclude Include="BaseRect.h" />
+ <ClInclude Include="BaseSize.h" />
+ <ClInclude Include="DrawEventRecorder.h" />
+ <ClInclude Include="DrawTargetD2D.h" />
+ <ClInclude Include="DrawTargetDual.h" />
+ <ClInclude Include="DrawTargetRecording.h" />
+ <ClInclude Include="GradientStopsD2D.h" />
+ <ClInclude Include="HelpersD2D.h" />
+ <ClInclude Include="ImageScaling.h" />
+ <ClInclude Include="Logging.h" />
+ <ClInclude Include="Matrix.h" />
+ <ClInclude Include="PathD2D.h" />
+ <ClInclude Include="PathHelpers.h" />
+ <ClInclude Include="PathRecording.h" />
+ <ClInclude Include="Point.h" />
+ <ClInclude Include="RecordedEvent.h" />
+ <ClInclude Include="RecordingTypes.h" />
+ <ClInclude Include="Rect.h" />
+ <ClInclude Include="ScaledFontBase.h" />
+ <ClInclude Include="ScaledFontDWrite.h" />
+ <ClInclude Include="SourceSurfaceD2D.h" />
+ <ClInclude Include="SourceSurfaceD2DTarget.h" />
+ <ClInclude Include="SourceSurfaceRawData.h" />
+ <ClInclude Include="Tools.h" />
+ <ClInclude Include="Types.h" />
+ <ClInclude Include="UserData.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="DrawEventRecorder.cpp" />
+ <ClCompile Include="DrawTargetD2D.cpp" />
+ <ClCompile Include="DrawTargetDual.cpp" />
+ <ClCompile Include="DrawTargetRecording.cpp" />
+ <ClCompile Include="Factory.cpp" />
+ <ClCompile Include="ImageScaling.cpp" />
+ <ClCompile Include="ImageScalingSSE2.cpp" />
+ <ClCompile Include="Matrix.cpp" />
+ <ClCompile Include="PathD2D.cpp" />
+ <ClCompile Include="PathRecording.cpp" />
+ <ClCompile Include="RecordedEvent.cpp" />
+ <ClCompile Include="ScaledFontBase.cpp" />
+ <ClCompile Include="ScaledFontDWrite.cpp" />
+ <ClCompile Include="SourceSurfaceD2D.cpp" />
+ <ClCompile Include="SourceSurfaceD2DTarget.cpp" />
+ <ClCompile Include="SourceSurfaceRawData.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="Makefile.in" />
+ <CustomBuild Include="ShadersD2D.fx">
+ <FileType>Document</FileType>
+ <Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">fxc /Tfx_4_0 /FhShadersD2D.h ShadersD2D.fx /Vn d2deffect</Command>
+ <Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">ShadersD2D.h</Outputs>
+ </CustomBuild>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
diff --git a/gfx/2d/moz.build b/gfx/2d/moz.build new file mode 100644 index 0000000000..c04530c72b --- /dev/null +++ b/gfx/2d/moz.build @@ -0,0 +1,227 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS.mozilla += [ + "GenericRefCounted.h", +] + +EXPORTS.mozilla.gfx += [ + "2D.h", + "BaseCoord.h", + "BaseMargin.h", + "BasePoint.h", + "BasePoint3D.h", + "BasePoint4D.h", + "BaseRect.h", + "BaseSize.h", + "BezierUtils.h", + "Blur.h", + "BorrowedContext.h", + "Coord.h", + "CriticalSection.h", + "DataSurfaceHelpers.h", + "DrawEventRecorder.h", + "DrawTargetOffset.h", + "DrawTargetRecording.h", + "DrawTargetSkia.h", + "Filters.h", + "FontVariation.h", + "Helpers.h", + "HelpersCairo.h", + "InlineTranslator.h", + "IterableArena.h", + "Logging.h", + "LoggingConstants.h", + "Matrix.h", + "MatrixFwd.h", + "NumericTools.h", + "PathHelpers.h", + "PathSkia.h", + "PatternHelpers.h", + "Point.h", + "Polygon.h", + "Quaternion.h", + "RecordedEvent.h", + "RecordingTypes.h", + "Rect.h", + "RectAbsolute.h", + "Scale.h", + "ScaleFactor.h", + "ScaleFactors2D.h", + "SourceSurfaceCairo.h", + "SourceSurfaceRawData.h", + "StackArray.h", + "Swizzle.h", + "Tools.h", + "Triangle.h", + "Types.h", + "UserData.h", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("cocoa", "uikit"): + EXPORTS.mozilla.gfx += [ + "MacIOSurface.h", + "ScaledFontBase.h", + "ScaledFontMac.h", + "UnscaledFontMac.h", + ] + UNIFIED_SOURCES += [ + "NativeFontResourceMac.cpp", + "ScaledFontMac.cpp", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + EXPORTS.mozilla.gfx += [ + "DWriteSettings.h", + "UnscaledFontDWrite.h", + "UnscaledFontGDI.h", + ] + SOURCES += [ + "ConicGradientEffectD2D1.cpp", + "DrawTargetD2D1.cpp", + "DWriteSettings.cpp", + "ExtendInputEffectD2D1.cpp", + "FilterNodeD2D1.cpp", + "NativeFontResourceDWrite.cpp", + "NativeFontResourceGDI.cpp", + "PathD2D.cpp", + "RadialGradientEffectD2D1.cpp", + "ScaledFontDWrite.cpp", + "ScaledFontWin.cpp", + "SourceSurfaceD2D1.cpp", + ] + DEFINES["WIN32"] = True + +if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("android", "gtk"): + EXPORTS.mozilla.gfx += [ + "UnscaledFontFreeType.h", + ] + SOURCES += [ + "NativeFontResourceFreeType.cpp", + "UnscaledFontFreeType.cpp", + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + SOURCES += [ + "ScaledFontFontconfig.cpp", + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": + SOURCES += [ + "ScaledFontFreeType.cpp", + ] + +EXPORTS.mozilla.gfx += [ + "ConvolutionFilter.h", + "HelpersSkia.h", +] + +# Are we targeting x86 or x64? If so, build SSE2 files. +if CONFIG["INTEL_ARCHITECTURE"]: + SOURCES += [ + "BlurSSE2.cpp", + "ConvolutionFilterAVX2.cpp", + "ConvolutionFilterSSE2.cpp", + "FilterProcessingSSE2.cpp", + "ImageScalingSSE2.cpp", + "SwizzleAVX2.cpp", + "SwizzleSSE2.cpp", + "SwizzleSSSE3.cpp", + ] + DEFINES["USE_SSE2"] = True + # The file uses SSE2 intrinsics, so it needs special compile flags on some + # compilers. + SOURCES["BlurSSE2.cpp"].flags += CONFIG["SSE2_FLAGS"] + SOURCES["ConvolutionFilterAVX2.cpp"].flags += ["-mavx2"] + SOURCES["ConvolutionFilterSSE2.cpp"].flags += CONFIG["SSE2_FLAGS"] + SOURCES["FilterProcessingSSE2.cpp"].flags += CONFIG["SSE2_FLAGS"] + SOURCES["ImageScalingSSE2.cpp"].flags += CONFIG["SSE2_FLAGS"] + SOURCES["SwizzleAVX2.cpp"].flags += ["-mavx2"] + SOURCES["SwizzleSSE2.cpp"].flags += CONFIG["SSE2_FLAGS"] + SOURCES["SwizzleSSSE3.cpp"].flags += CONFIG["SSSE3_FLAGS"] +elif CONFIG["TARGET_CPU"].startswith("mips"): + SOURCES += [ + "BlurLS3.cpp", + ] + +UNIFIED_SOURCES += [ + "BezierUtils.cpp", + "Blur.cpp", + "BufferEdgePad.cpp", + "BufferUnrotate.cpp", + "ConvolutionFilter.cpp", + "DataSourceSurface.cpp", + "DataSurfaceHelpers.cpp", + "DrawEventRecorder.cpp", + "DrawTarget.cpp", + "DrawTargetCairo.cpp", + "DrawTargetOffset.cpp", + "DrawTargetRecording.cpp", + "DrawTargetSkia.cpp", + "Factory.cpp", + "FilterNodeSoftware.cpp", + "FilterProcessing.cpp", + "FilterProcessingScalar.cpp", + "ImageScaling.cpp", + "Matrix.cpp", + "NativeFontResource.cpp", + "Path.cpp", + "PathCairo.cpp", + "PathHelpers.cpp", + "PathRecording.cpp", + "PathSkia.cpp", + "Quaternion.cpp", + "RecordedEvent.cpp", + "ScaledFontBase.cpp", + "SFNTData.cpp", + "SkConvolver.cpp", + "SourceSurfaceCairo.cpp", + "SourceSurfaceRawData.cpp", + "SourceSurfaceSkia.cpp", + "Swizzle.cpp", + "Types.cpp", +] + +SOURCES += [ + "InlineTranslator.cpp", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + SOURCES += [ + "MacIOSurface.cpp", + ] + +if CONFIG["TARGET_CPU"] == "aarch64" or CONFIG["BUILD_ARM_NEON"]: + SOURCES += [ + "BlurNEON.cpp", + "ConvolutionFilterNEON.cpp", + "LuminanceNEON.cpp", + "SwizzleNEON.cpp", + ] + DEFINES["USE_NEON"] = True + SOURCES["BlurNEON.cpp"].flags += CONFIG["NEON_FLAGS"] + SOURCES["ConvolutionFilterNEON.cpp"].flags += CONFIG["NEON_FLAGS"] + SOURCES["LuminanceNEON.cpp"].flags += CONFIG["NEON_FLAGS"] + SOURCES["SwizzleNEON.cpp"].flags += CONFIG["NEON_FLAGS"] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +for var in ("USE_CAIRO", "MOZ2D_HAS_MOZ_CAIRO"): + DEFINES[var] = True + +if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("android", "gtk"): + DEFINES["MOZ_ENABLE_FREETYPE"] = True + +CXXFLAGS += ["-Werror=switch"] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("android", "gtk"): + CXXFLAGS += CONFIG["CAIRO_FT_CFLAGS"] + +LOCAL_INCLUDES += CONFIG["SKIA_INCLUDES"] +LOCAL_INCLUDES += [ + "/gfx/cairo/cairo/src", +] diff --git a/gfx/2d/unittest/Main.cpp b/gfx/2d/unittest/Main.cpp new file mode 100644 index 0000000000..0a6c9b0401 --- /dev/null +++ b/gfx/2d/unittest/Main.cpp @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SanityChecks.h" +#include "TestPoint.h" +#include "TestScaling.h" +#include "TestBugs.h" +#ifdef WIN32 +# include "TestDrawTargetD2D.h" +#endif + +#include <string> +#include <sstream> + +struct TestObject { + TestBase* test; + std::string name; +}; + +int main() { + TestObject tests[] = { + {new SanityChecks(), "Sanity Checks"}, +#ifdef WIN32 + {new TestDrawTargetD2D(), "DrawTarget (D2D)"}, +#endif + {new TestPoint(), "Point Tests"}, + {new TestScaling(), "Scaling Tests"} {new TestBugs(), "Bug Tests"}}; + + int totalFailures = 0; + int totalTests = 0; + std::stringstream message; + printf("------ STARTING RUNNING TESTS ------\n"); + for (int i = 0; i < sizeof(tests) / sizeof(TestObject); i++) { + message << "--- RUNNING TESTS: " << tests[i].name << " ---\n"; + printf(message.str().c_str()); + message.str(""); + int failures = 0; + totalTests += tests[i].test->RunTests(&failures); + totalFailures += failures; + // Done with this test! + delete tests[i].test; + } + message << "------ FINISHED RUNNING TESTS ------\nTests run: " << totalTests + << " - Passes: " << totalTests - totalFailures + << " - Failures: " << totalFailures << "\n"; + printf(message.str().c_str()); + return totalFailures; +} diff --git a/gfx/2d/unittest/SanityChecks.cpp b/gfx/2d/unittest/SanityChecks.cpp new file mode 100644 index 0000000000..c8db1dd69d --- /dev/null +++ b/gfx/2d/unittest/SanityChecks.cpp @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SanityChecks.h" + +SanityChecks::SanityChecks() { REGISTER_TEST(SanityChecks, AlwaysPasses); } + +void SanityChecks::AlwaysPasses() { + bool testMustPass = true; + + VERIFY(testMustPass); +} diff --git a/gfx/2d/unittest/SanityChecks.h b/gfx/2d/unittest/SanityChecks.h new file mode 100644 index 0000000000..6161b783eb --- /dev/null +++ b/gfx/2d/unittest/SanityChecks.h @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#pragma once + +#include "TestBase.h" + +class SanityChecks : public TestBase { + public: + SanityChecks(); + + void AlwaysPasses(); +}; diff --git a/gfx/2d/unittest/TestBase.cpp b/gfx/2d/unittest/TestBase.cpp new file mode 100644 index 0000000000..bf78008489 --- /dev/null +++ b/gfx/2d/unittest/TestBase.cpp @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TestBase.h" + +#include <sstream> + +int TestBase::RunTests(int* aFailures) { + int testsRun = 0; + *aFailures = 0; + + for (unsigned int i = 0; i < mTests.size(); i++) { + std::stringstream stream; + stream << "Test (" << mTests[i].name << "): "; + LogMessage(stream.str()); + stream.str(""); + + mTestFailed = false; + + // Don't try this at home! We know these are actually pointers to members + // of child clases, so we reinterpret cast those child class pointers to + // TestBase and then call the functions. Because the compiler believes + // these function calls are members of TestBase. + ((*reinterpret_cast<TestBase*>((mTests[i].implPointer))).* + (mTests[i].funcCall))(); + + if (!mTestFailed) { + LogMessage("PASSED\n"); + } else { + LogMessage("FAILED\n"); + (*aFailures)++; + } + testsRun++; + } + + return testsRun; +} + +void TestBase::LogMessage(std::string aMessage) { + printf("%s", aMessage.c_str()); +} diff --git a/gfx/2d/unittest/TestBase.h b/gfx/2d/unittest/TestBase.h new file mode 100644 index 0000000000..fb9326e856 --- /dev/null +++ b/gfx/2d/unittest/TestBase.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#pragma once + +#include <string> +#include <vector> + +#if defined(_MSC_VER) && !defined(__clang__) +// On MSVC otherwise our generic member pointer trick doesn't work. +// JYA: Do we still need this? +# pragma pointers_to_members(full_generality, single_inheritance) +#endif + +#define VERIFY(arg) \ + if (!(arg)) { \ + LogMessage("VERIFY FAILED: " #arg "\n"); \ + mTestFailed = true; \ + } + +#define REGISTER_TEST(className, testName) \ + mTests.push_back( \ + Test(static_cast<TestCall>(&className::testName), #testName, this)) + +class TestBase { + public: + TestBase() = default; + + typedef void (TestBase::*TestCall)(); + + int RunTests(int* aFailures); + + protected: + static void LogMessage(std::string aMessage); + + struct Test { + Test(TestCall aCall, std::string aName, void* aImplPointer) + : funcCall(aCall), name(aName), implPointer(aImplPointer) {} + TestCall funcCall; + std::string name; + void* implPointer; + }; + std::vector<Test> mTests; + + bool mTestFailed; + + private: + // This doesn't really work with our generic member pointer trick. + TestBase(const TestBase& aOther); +}; diff --git a/gfx/2d/unittest/TestBugs.cpp b/gfx/2d/unittest/TestBugs.cpp new file mode 100644 index 0000000000..545166007a --- /dev/null +++ b/gfx/2d/unittest/TestBugs.cpp @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TestBugs.h" +#include "2D.h" +#include <string.h> + +using namespace mozilla; +using namespace mozilla::gfx; + +TestBugs::TestBugs() { + REGISTER_TEST(TestBugs, CairoClip918671); + REGISTER_TEST(TestBugs, PushPopClip950550); +} + +void TestBugs::CairoClip918671() { + RefPtr<DrawTarget> dt = Factory::CreateDrawTarget( + BackendType::CAIRO, IntSize(100, 100), SurfaceFormat::B8G8R8A8); + RefPtr<DrawTarget> ref = Factory::CreateDrawTarget( + BackendType::CAIRO, IntSize(100, 100), SurfaceFormat::B8G8R8A8); + // Create a path that extends around the center rect but doesn't intersect it. + RefPtr<PathBuilder> pb1 = dt->CreatePathBuilder(); + pb1->MoveTo(Point(10, 10)); + pb1->LineTo(Point(90, 10)); + pb1->LineTo(Point(90, 20)); + pb1->LineTo(Point(10, 20)); + pb1->Close(); + pb1->MoveTo(Point(90, 90)); + pb1->LineTo(Point(91, 90)); + pb1->LineTo(Point(91, 91)); + pb1->LineTo(Point(91, 90)); + pb1->Close(); + + RefPtr<Path> path1 = pb1->Finish(); + dt->PushClip(path1); + + // This center rect must NOT be rectilinear! + RefPtr<PathBuilder> pb2 = dt->CreatePathBuilder(); + pb2->MoveTo(Point(50, 50)); + pb2->LineTo(Point(55, 51)); + pb2->LineTo(Point(54, 55)); + pb2->LineTo(Point(50, 56)); + pb2->Close(); + + RefPtr<Path> path2 = pb2->Finish(); + dt->PushClip(path2); + + dt->FillRect(Rect(0, 0, 100, 100), ColorPattern(DeviceColor(1, 0, 0))); + + RefPtr<SourceSurface> surf1 = dt->Snapshot(); + RefPtr<SourceSurface> surf2 = ref->Snapshot(); + + RefPtr<DataSourceSurface> dataSurf1 = surf1->GetDataSurface(); + RefPtr<DataSourceSurface> dataSurf2 = surf2->GetDataSurface(); + + DataSourceSurface::ScopedMap map1(dataSurf1, DataSourceSurface::READ); + DataSourceSurface::ScopedMap map2(dataSurf2, DataSourceSurface::READ); + for (int y = 0; y < dt->GetSize().height; y++) { + VERIFY(memcmp(map1.GetData() + y * map1.GetStride(), + map2.GetData() + y * map2.GetStride(), + dataSurf1->GetSize().width * 4) == 0); + } +} + +void TestBugs::PushPopClip950550() { + RefPtr<DrawTarget> dt = Factory::CreateDrawTarget( + BackendType::CAIRO, IntSize(500, 500), SurfaceFormat::B8G8R8A8); + dt->PushClipRect(Rect(0, 0, 100, 100)); + Matrix m(1, 0, 0, 1, 45, -100); + dt->SetTransform(m); + dt->PopClip(); + + // We fail the test if we assert in this call because our draw target's + // transforms are out of sync. + dt->FillRect(Rect(50, 50, 50, 50), + ColorPattern(DeviceColor(0.5f, 0, 0, 1.0f))); +} diff --git a/gfx/2d/unittest/TestBugs.h b/gfx/2d/unittest/TestBugs.h new file mode 100644 index 0000000000..c337b05cff --- /dev/null +++ b/gfx/2d/unittest/TestBugs.h @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#pragma once + +#include "TestBase.h" + +class TestBugs : public TestBase { + public: + TestBugs(); + + void CairoClip918671(); + void PushPopClip950550(); +}; diff --git a/gfx/2d/unittest/TestCairo.cpp b/gfx/2d/unittest/TestCairo.cpp new file mode 100644 index 0000000000..4ed7ad4d4a --- /dev/null +++ b/gfx/2d/unittest/TestCairo.cpp @@ -0,0 +1,99 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#include "cairo.h" + +#include "gtest/gtest.h" + +namespace mozilla { +namespace layers { + +static void TryCircle(double centerX, double centerY, double radius) { + printf("TestCairo:TryArcs centerY %f, radius %f\n", centerY, radius); + + cairo_surface_t* surf = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 8, 21); + ASSERT_TRUE(surf != nullptr); + + cairo_t* cairo = cairo_create(surf); + ASSERT_TRUE(cairo != nullptr); + + cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE); + cairo_arc(cairo, 0.0, centerY, radius, 0.0, 6.2831853071795862); + cairo_fill_preserve(cairo); + + cairo_surface_destroy(surf); + cairo_destroy(cairo); +} + +TEST(Cairo, Simple) +{ + TryCircle(0.0, 0.0, 14.0); + TryCircle(0.0, 1.0, 22.4); + TryCircle(1.0, 0.0, 1422.4); + TryCircle(1.0, 1.0, 3422.4); + TryCircle(-10.0, 1.0, -2); +} + +TEST(Cairo, Bug825721) +{ + // OK: + TryCircle(0.0, 0.0, 8761126469220696064.0); + TryCircle(0.0, 1.0, 8761126469220696064.0); + + // OK: + TryCircle(1.0, 0.0, 5761126469220696064.0); + + // This was the crash in 825721. Note that centerY has to be non-zero, + // and radius has to be not only large, but in particular range. + // 825721 has a band-aid fix, where the crash is inevitable, but does + // not fix the cause. The same code crashes in cairo standalone. + TryCircle(0.0, 1.0, 5761126469220696064.0); +} + +TEST(Cairo, Bug1063486) +{ + double x1, y1, x2, y2; + const double epsilon = .01; + + cairo_surface_t* surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + ASSERT_TRUE(surf != nullptr); + + cairo_t* cairo = cairo_create(surf); + ASSERT_TRUE(cairo != nullptr); + + printf("Path 1\n"); + cairo_move_to(cairo, -20, -10); + cairo_line_to(cairo, 20, -10); + cairo_line_to(cairo, 20, 10); + cairo_curve_to(cairo, 10, 10, -10, 10, -20, 10); + cairo_curve_to(cairo, -30, 10, -30, -10, -20, -10); + + cairo_path_extents(cairo, &x1, &y1, &x2, &y2); + + ASSERT_LT(std::abs(-27.5 - x1), epsilon); // the failing coordinate + ASSERT_LT(std::abs(-10 - y1), epsilon); + ASSERT_LT(std::abs(20 - x2), epsilon); + ASSERT_LT(std::abs(10 - y2), epsilon); + + printf("Path 2\n"); + cairo_new_path(cairo); + cairo_move_to(cairo, 10, 30); + cairo_line_to(cairo, 90, 30); + cairo_curve_to(cairo, 30, 30, 30, 30, 10, 30); + cairo_curve_to(cairo, 0, 30, 0, 0, 30, 5); + + cairo_path_extents(cairo, &x1, &y1, &x2, &y2); + + ASSERT_LT(std::abs(4.019531 - x1), epsilon); // the failing coordinate + ASSERT_LT(std::abs(4.437500 - y1), epsilon); + ASSERT_LT(std::abs(90. - x2), epsilon); + ASSERT_LT(std::abs(30. - y2), epsilon); + + cairo_surface_destroy(surf); + cairo_destroy(cairo); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/2d/unittest/TestDrawTargetBase.cpp b/gfx/2d/unittest/TestDrawTargetBase.cpp new file mode 100644 index 0000000000..2ef817cd86 --- /dev/null +++ b/gfx/2d/unittest/TestDrawTargetBase.cpp @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TestDrawTargetBase.h" +#include <sstream> + +using namespace mozilla; +using namespace mozilla::gfx; + +TestDrawTargetBase::TestDrawTargetBase() { + REGISTER_TEST(TestDrawTargetBase, Initialized); + REGISTER_TEST(TestDrawTargetBase, FillCompletely); + REGISTER_TEST(TestDrawTargetBase, FillRect); +} + +void TestDrawTargetBase::Initialized() { VERIFY(mDT); } + +void TestDrawTargetBase::FillCompletely() { + mDT->FillRect(Rect(0, 0, DT_WIDTH, DT_HEIGHT), + ColorPattern(DeviceColor(0, 0.5f, 0, 1.0f))); + + RefreshSnapshot(); + + VerifyAllPixels(DeviceColor(0, 0.5f, 0, 1.0f)); +} + +void TestDrawTargetBase::FillRect() { + mDT->FillRect(Rect(0, 0, DT_WIDTH, DT_HEIGHT), + ColorPattern(DeviceColor(0, 0.5f, 0, 1.0f))); + mDT->FillRect(Rect(50, 50, 50, 50), + ColorPattern(DeviceColor(0.5f, 0, 0, 1.0f))); + + RefreshSnapshot(); + + VerifyPixel(IntPoint(49, 49), DeviceColor(0, 0.5f, 0, 1.0f)); + VerifyPixel(IntPoint(50, 50), DeviceColor(0.5f, 0, 0, 1.0f)); + VerifyPixel(IntPoint(99, 99), DeviceColor(0.5f, 0, 0, 1.0f)); + VerifyPixel(IntPoint(100, 100), DeviceColor(0, 0.5f, 0, 1.0f)); +} + +void TestDrawTargetBase::RefreshSnapshot() { + RefPtr<SourceSurface> snapshot = mDT->Snapshot(); + mDataSnapshot = snapshot->GetDataSurface(); +} + +void TestDrawTargetBase::VerifyAllPixels(const DeviceColor& aColor) { + uint32_t* colVal = (uint32_t*)mDataSnapshot->GetData(); + + uint32_t expected = RGBAPixelFromColor(aColor); + + for (int y = 0; y < DT_HEIGHT; y++) { + for (int x = 0; x < DT_WIDTH; x++) { + if (colVal[y * (mDataSnapshot->Stride() / 4) + x] != expected) { + LogMessage("VerifyAllPixels Failed\n"); + mTestFailed = true; + return; + } + } + } +} + +void TestDrawTargetBase::VerifyPixel(const IntPoint& aPoint, + mozilla::gfx::DeviceColor& aColor) { + uint32_t* colVal = (uint32_t*)mDataSnapshot->GetData(); + + uint32_t expected = RGBAPixelFromColor(aColor); + uint32_t rawActual = + colVal[aPoint.y * (mDataSnapshot->Stride() / 4) + aPoint.x]; + + if (rawActual != expected) { + stringstream message; + uint32_t actb = rawActual & 0xFF; + uint32_t actg = (rawActual & 0xFF00) >> 8; + uint32_t actr = (rawActual & 0xFF0000) >> 16; + uint32_t acta = (rawActual & 0xFF000000) >> 24; + uint32_t expb = expected & 0xFF; + uint32_t expg = (expected & 0xFF00) >> 8; + uint32_t expr = (expected & 0xFF0000) >> 16; + uint32_t expa = (expected & 0xFF000000) >> 24; + + message << "Verify Pixel (" << aPoint.x << "x" << aPoint.y + << ") Failed." + " Expected (" + << expr << "," << expg << "," << expb << "," << expa + << ") " + " Got (" + << actr << "," << actg << "," << actb << "," << acta << ")\n"; + + LogMessage(message.str()); + mTestFailed = true; + return; + } +} + +uint32_t TestDrawTargetBase::RGBAPixelFromColor(const DeviceColor& aColor) { + return uint8_t((aColor.b * 255) + 0.5f) | + uint8_t((aColor.g * 255) + 0.5f) << 8 | + uint8_t((aColor.r * 255) + 0.5f) << 16 | + uint8_t((aColor.a * 255) + 0.5f) << 24; +} diff --git a/gfx/2d/unittest/TestDrawTargetBase.h b/gfx/2d/unittest/TestDrawTargetBase.h new file mode 100644 index 0000000000..3f13a63106 --- /dev/null +++ b/gfx/2d/unittest/TestDrawTargetBase.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#pragma once + +#include "2D.h" +#include "TestBase.h" + +#define DT_WIDTH 500 +#define DT_HEIGHT 500 + +/* This general DrawTarget test class can be reimplemented by a child class + * with optional additional drawtarget-specific tests. And is intended to run + * on a 500x500 32 BPP drawtarget. + */ +class TestDrawTargetBase : public TestBase { + public: + void Initialized(); + void FillCompletely(); + void FillRect(); + + protected: + TestDrawTargetBase(); + + void RefreshSnapshot(); + + void VerifyAllPixels(const mozilla::gfx::DeviceColor& aColor); + void VerifyPixel(const mozilla::gfx::IntPoint& aPoint, + mozilla::gfx::DeviceColor& aColor); + + uint32_t RGBAPixelFromColor(const mozilla::gfx::DeviceColor& aColor); + + RefPtr<mozilla::gfx::DrawTarget> mDT; + RefPtr<mozilla::gfx::DataSourceSurface> mDataSnapshot; +}; diff --git a/gfx/2d/unittest/TestDrawTargetD2D.cpp b/gfx/2d/unittest/TestDrawTargetD2D.cpp new file mode 100644 index 0000000000..cba63129fd --- /dev/null +++ b/gfx/2d/unittest/TestDrawTargetD2D.cpp @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TestDrawTargetD2D.h" + +using namespace mozilla::gfx; +TestDrawTargetD2D::TestDrawTargetD2D() { + ::D3D10CreateDevice1( + nullptr, D3D10_DRIVER_TYPE_HARDWARE, nullptr, + D3D10_CREATE_DEVICE_BGRA_SUPPORT | + D3D10_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS, + D3D10_FEATURE_LEVEL_10_0, D3D10_1_SDK_VERSION, getter_AddRefs(mDevice)); + + Factory::SetDirect3D10Device(mDevice); + + mDT = Factory::CreateDrawTarget(BackendType::DIRECT2D, + IntSize(DT_WIDTH, DT_HEIGHT), + SurfaceFormat::B8G8R8A8); +} diff --git a/gfx/2d/unittest/TestDrawTargetD2D.h b/gfx/2d/unittest/TestDrawTargetD2D.h new file mode 100644 index 0000000000..f4c88336a4 --- /dev/null +++ b/gfx/2d/unittest/TestDrawTargetD2D.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#pragma once + +#include "TestDrawTargetBase.h" + +#include <d3d10_1.h> + +class TestDrawTargetD2D : public TestDrawTargetBase { + public: + TestDrawTargetD2D(); + + private: + RefPtr<ID3D10Device1> mDevice; +}; diff --git a/gfx/2d/unittest/TestPoint.cpp b/gfx/2d/unittest/TestPoint.cpp new file mode 100644 index 0000000000..e79ff01ab3 --- /dev/null +++ b/gfx/2d/unittest/TestPoint.cpp @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TestPoint.h" + +#include "Point.h" + +using namespace mozilla::gfx; + +TestPoint::TestPoint() { + REGISTER_TEST(TestPoint, Addition); + REGISTER_TEST(TestPoint, Subtraction); + REGISTER_TEST(TestPoint, RoundToMultiple); +} + +void TestPoint::Addition() { + Point a, b; + a.x = 2; + a.y = 2; + b.x = 5; + b.y = -5; + + a += b; + + VERIFY(a.x == 7.f); + VERIFY(a.y == -3.f); +} + +void TestPoint::Subtraction() { + Point a, b; + a.x = 2; + a.y = 2; + b.x = 5; + b.y = -5; + + a -= b; + + VERIFY(a.x == -3.f); + VERIFY(a.y == 7.f); +} + +void TestPoint::RoundToMultiple() { + const int32_t roundTo = 2; + + IntPoint p(478, -394); + VERIFY(p.RoundedToMultiple(roundTo) == p); + + IntPoint p2(478, 393); + VERIFY(p2.RoundedToMultiple(roundTo) != p2); +} diff --git a/gfx/2d/unittest/TestPoint.h b/gfx/2d/unittest/TestPoint.h new file mode 100644 index 0000000000..cb5b6a3de3 --- /dev/null +++ b/gfx/2d/unittest/TestPoint.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#pragma once + +#include "TestBase.h" + +class TestPoint : public TestBase { + public: + TestPoint(); + + void Addition(); + void Subtraction(); + void RoundToMultiple(); +}; diff --git a/gfx/2d/unittest/TestScaling.cpp b/gfx/2d/unittest/TestScaling.cpp new file mode 100644 index 0000000000..fd15455f26 --- /dev/null +++ b/gfx/2d/unittest/TestScaling.cpp @@ -0,0 +1,235 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TestScaling.h" + +#include "ImageScaling.h" + +using namespace mozilla::gfx; + +TestScaling::TestScaling() { + REGISTER_TEST(TestScaling, BasicHalfScale); + REGISTER_TEST(TestScaling, DoubleHalfScale); + REGISTER_TEST(TestScaling, UnevenHalfScale); + REGISTER_TEST(TestScaling, OddStrideHalfScale); + REGISTER_TEST(TestScaling, VerticalHalfScale); + REGISTER_TEST(TestScaling, HorizontalHalfScale); + REGISTER_TEST(TestScaling, MixedHalfScale); +} + +void TestScaling::BasicHalfScale() { + std::vector<uint8_t> data; + data.resize(500 * 500 * 4); + + uint32_t* pixels = reinterpret_cast<uint32_t*>(&data.front()); + for (int y = 0; y < 500; y += 2) { + for (int x = 0; x < 500; x += 2) { + pixels[y * 500 + x] = 0xff00ff00; + pixels[y * 500 + x + 1] = 0xff00ffff; + pixels[(y + 1) * 500 + x] = 0xff000000; + pixels[(y + 1) * 500 + x + 1] = 0xff0000ff; + } + } + ImageHalfScaler scaler(&data.front(), 500 * 4, IntSize(500, 500)); + + scaler.ScaleForSize(IntSize(220, 240)); + + VERIFY(scaler.GetSize().width == 250); + VERIFY(scaler.GetSize().height == 250); + + pixels = (uint32_t*)scaler.GetScaledData(); + + for (int y = 0; y < 250; y++) { + for (int x = 0; x < 250; x++) { + VERIFY(pixels[y * (scaler.GetStride() / 4) + x] == 0xff007f7f); + } + } +} + +void TestScaling::DoubleHalfScale() { + std::vector<uint8_t> data; + data.resize(500 * 500 * 4); + + uint32_t* pixels = reinterpret_cast<uint32_t*>(&data.front()); + for (int y = 0; y < 500; y += 2) { + for (int x = 0; x < 500; x += 2) { + pixels[y * 500 + x] = 0xff00ff00; + pixels[y * 500 + x + 1] = 0xff00ffff; + pixels[(y + 1) * 500 + x] = 0xff000000; + pixels[(y + 1) * 500 + x + 1] = 0xff0000ff; + } + } + ImageHalfScaler scaler(&data.front(), 500 * 4, IntSize(500, 500)); + + scaler.ScaleForSize(IntSize(120, 110)); + VERIFY(scaler.GetSize().width == 125); + VERIFY(scaler.GetSize().height == 125); + + pixels = (uint32_t*)scaler.GetScaledData(); + + for (int y = 0; y < 125; y++) { + for (int x = 0; x < 125; x++) { + VERIFY(pixels[y * (scaler.GetStride() / 4) + x] == 0xff007f7f); + } + } +} + +void TestScaling::UnevenHalfScale() { + std::vector<uint8_t> data; + // Use a 16-byte aligned stride still, we test none-aligned strides + // separately. + data.resize(499 * 500 * 4); + + uint32_t* pixels = reinterpret_cast<uint32_t*>(&data.front()); + for (int y = 0; y < 500; y += 2) { + for (int x = 0; x < 500; x += 2) { + pixels[y * 500 + x] = 0xff00ff00; + if (x < 498) { + pixels[y * 500 + x + 1] = 0xff00ffff; + } + if (y < 498) { + pixels[(y + 1) * 500 + x] = 0xff000000; + if (x < 498) { + pixels[(y + 1) * 500 + x + 1] = 0xff0000ff; + } + } + } + } + ImageHalfScaler scaler(&data.front(), 500 * 4, IntSize(499, 499)); + + scaler.ScaleForSize(IntSize(220, 220)); + VERIFY(scaler.GetSize().width == 249); + VERIFY(scaler.GetSize().height == 249); + + pixels = (uint32_t*)scaler.GetScaledData(); + + for (int y = 0; y < 249; y++) { + for (int x = 0; x < 249; x++) { + VERIFY(pixels[y * (scaler.GetStride() / 4) + x] == 0xff007f7f); + } + } +} + +void TestScaling::OddStrideHalfScale() { + std::vector<uint8_t> data; + // Use a 4-byte aligned stride to test if that doesn't cause any issues. + data.resize(499 * 499 * 4); + + uint32_t* pixels = reinterpret_cast<uint32_t*>(&data.front()); + for (int y = 0; y < 500; y += 2) { + for (int x = 0; x < 500; x += 2) { + pixels[y * 499 + x] = 0xff00ff00; + if (x < 498) { + pixels[y * 499 + x + 1] = 0xff00ffff; + } + if (y < 498) { + pixels[(y + 1) * 499 + x] = 0xff000000; + if (x < 498) { + pixels[(y + 1) * 499 + x + 1] = 0xff0000ff; + } + } + } + } + ImageHalfScaler scaler(&data.front(), 499 * 4, IntSize(499, 499)); + + scaler.ScaleForSize(IntSize(220, 220)); + VERIFY(scaler.GetSize().width == 249); + VERIFY(scaler.GetSize().height == 249); + + pixels = (uint32_t*)scaler.GetScaledData(); + + for (int y = 0; y < 249; y++) { + for (int x = 0; x < 249; x++) { + VERIFY(pixels[y * (scaler.GetStride() / 4) + x] == 0xff007f7f); + } + } +} +void TestScaling::VerticalHalfScale() { + std::vector<uint8_t> data; + data.resize(500 * 500 * 4); + + uint32_t* pixels = reinterpret_cast<uint32_t*>(&data.front()); + for (int y = 0; y < 500; y += 2) { + for (int x = 0; x < 500; x += 2) { + pixels[y * 500 + x] = 0xff00ff00; + pixels[y * 500 + x + 1] = 0xff00ffff; + pixels[(y + 1) * 500 + x] = 0xff000000; + pixels[(y + 1) * 500 + x + 1] = 0xff0000ff; + } + } + ImageHalfScaler scaler(&data.front(), 500 * 4, IntSize(500, 500)); + + scaler.ScaleForSize(IntSize(400, 240)); + VERIFY(scaler.GetSize().width == 500); + VERIFY(scaler.GetSize().height == 250); + + pixels = (uint32_t*)scaler.GetScaledData(); + + for (int y = 0; y < 250; y++) { + for (int x = 0; x < 500; x += 2) { + VERIFY(pixels[y * (scaler.GetStride() / 4) + x] == 0xff007f00); + VERIFY(pixels[y * (scaler.GetStride() / 4) + x + 1] == 0xff007fff); + } + } +} + +void TestScaling::HorizontalHalfScale() { + std::vector<uint8_t> data; + data.resize(520 * 500 * 4); + + uint32_t* pixels = reinterpret_cast<uint32_t*>(&data.front()); + for (int y = 0; y < 500; y++) { + for (int x = 0; x < 520; x += 8) { + pixels[y * 520 + x] = 0xff00ff00; + pixels[y * 520 + x + 1] = 0xff00ffff; + pixels[y * 520 + x + 2] = 0xff000000; + pixels[y * 520 + x + 3] = 0xff0000ff; + pixels[y * 520 + x + 4] = 0xffff00ff; + pixels[y * 520 + x + 5] = 0xff0000ff; + pixels[y * 520 + x + 6] = 0xffffffff; + pixels[y * 520 + x + 7] = 0xff0000ff; + } + } + ImageHalfScaler scaler(&data.front(), 520 * 4, IntSize(520, 500)); + + scaler.ScaleForSize(IntSize(240, 400)); + VERIFY(scaler.GetSize().width == 260); + VERIFY(scaler.GetSize().height == 500); + + pixels = (uint32_t*)scaler.GetScaledData(); + + for (int y = 0; y < 500; y++) { + for (int x = 0; x < 260; x += 4) { + VERIFY(pixels[y * (scaler.GetStride() / 4) + x] == 0xff00ff7f); + VERIFY(pixels[y * (scaler.GetStride() / 4) + x + 1] == 0xff00007f); + VERIFY(pixels[y * (scaler.GetStride() / 4) + x + 2] == 0xff7f00ff); + VERIFY(pixels[y * (scaler.GetStride() / 4) + x + 3] == 0xff7f7fff); + } + } +} + +void TestScaling::MixedHalfScale() { + std::vector<uint8_t> data; + data.resize(500 * 500 * 4); + + uint32_t* pixels = reinterpret_cast<uint32_t*>(&data.front()); + for (int y = 0; y < 500; y += 2) { + for (int x = 0; x < 500; x += 2) { + pixels[y * 500 + x] = 0xff00ff00; + pixels[y * 500 + x + 1] = 0xff00ffff; + pixels[(y + 1) * 500 + x] = 0xff000000; + pixels[(y + 1) * 500 + x + 1] = 0xff0000ff; + } + } + ImageHalfScaler scaler(&data.front(), 500 * 4, IntSize(500, 500)); + + scaler.ScaleForSize(IntSize(120, 240)); + VERIFY(scaler.GetSize().width == 125); + VERIFY(scaler.GetSize().height == 250); + scaler.ScaleForSize(IntSize(240, 120)); + VERIFY(scaler.GetSize().width == 250); + VERIFY(scaler.GetSize().height == 125); +} diff --git a/gfx/2d/unittest/TestScaling.h b/gfx/2d/unittest/TestScaling.h new file mode 100644 index 0000000000..dbbcda91fa --- /dev/null +++ b/gfx/2d/unittest/TestScaling.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#pragma once + +#include "TestBase.h" + +class TestScaling : public TestBase { + public: + TestScaling(); + + void BasicHalfScale(); + void DoubleHalfScale(); + void UnevenHalfScale(); + void OddStrideHalfScale(); + void VerticalHalfScale(); + void HorizontalHalfScale(); + void MixedHalfScale(); +}; diff --git a/gfx/2d/unittest/unittest.vcxproj b/gfx/2d/unittest/unittest.vcxproj new file mode 100644 index 0000000000..7ddf925303 --- /dev/null +++ b/gfx/2d/unittest/unittest.vcxproj @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{CCF4BC8B-0CED-47CA-B621-ABF1832527D9}</ProjectGuid>
+ <RootNamespace>unittest</RootNamespace>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LibraryPath>$(DXSDK_DIR)\Lib\x86;$(VCInstallDir)lib;$(VCInstallDir)atlmfc\lib;$(WindowsSdkDir)lib;$(FrameworkSDKDir)\lib</LibraryPath>
+ <IncludePath>$(ProjectDir)..\;$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LibraryPath>$(DXSDK_DIR)\Lib\x86;$(VCInstallDir)lib;$(VCInstallDir)lib;$(VCInstallDir)atlmfc\lib;$(WindowsSdkDir)lib;$(FrameworkSDKDir)\lib</LibraryPath>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>../</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_MBCS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalDependencies>../$(Configuration)/gfx2d.lib;dxguid.lib;d3d10_1.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <AdditionalIncludeDirectories>../</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_MBCS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalDependencies>../$(Configuration)/gfx2d.lib;dxguid.lib;d3d10_1.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="Main.cpp" />
+ <ClCompile Include="SanityChecks.cpp" />
+ <ClCompile Include="TestBase.cpp" />
+ <ClCompile Include="TestDrawTargetBase.cpp" />
+ <ClCompile Include="TestDrawTargetD2D.cpp" />
+ <ClCompile Include="TestPoint.cpp" />
+ <ClCompile Include="TestScaling.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="TestDrawTargetBase.h" />
+ <ClInclude Include="SanityChecks.h" />
+ <ClInclude Include="TestBase.h" />
+ <ClInclude Include="TestDrawTargetD2D.h" />
+ <ClInclude Include="TestPoint.h" />
+ <ClInclude Include="TestScaling.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
\ No newline at end of file |