Bug 1429508 - Allow created mask surfaces to be clipped to the necessary size when replaying a recording. r?jrmuizel draft
authorJamie Nicol <jnicol@mozilla.com>
Mon, 05 Feb 2018 17:59:42 +0000
changeset 751479 21cb882c9a4ba9b251e61f08c680ab2d920f255e
parent 751478 8b64cc3e93885b80882cb8699fbc109129760e2e
push id97975
push userbmo:jnicol@mozilla.com
push dateTue, 06 Feb 2018 11:35:20 +0000
reviewersjrmuizel
bugs1429508
milestone60.0a1
Bug 1429508 - Allow created mask surfaces to be clipped to the necessary size when replaying a recording. r?jrmuizel Add a command CreateClippedDrawTarget to DrawTarget, which takes the max required size and a transform between this draw target and the one to be created. The created draw target may have its size clipped to the size of this draw target, transformed to the new target's space. This means that the new surface will be large enough so that it is rendered to this draw target correctly, but not necessarily any larger. Usually this will just create a draw target of the requested size, for simplicity. However, when replaying a recorded draw target we do clip the size to the base draw target's size. This is done using a DrawTargetTiled, so when applying the mask in PopLayer, we must take the SourceSurface's offset in to account. MozReview-Commit-ID: 89ONElphzLu
gfx/2d/2D.h
gfx/2d/DrawTargetRecording.cpp
gfx/2d/DrawTargetRecording.h
gfx/2d/DrawTargetSkia.cpp
gfx/2d/RecordedEvent.h
gfx/2d/RecordedEventImpl.h
layout/painting/nsDisplayList.cpp
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -1283,16 +1283,27 @@ public:
   virtual already_AddRefed<DrawTarget>
     CreateShadowDrawTarget(const IntSize &aSize, SurfaceFormat aFormat,
                            float aSigma) const
   {
     return CreateSimilarDrawTarget(aSize, aFormat);
   }
 
   /**
+   * 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 IntSize& aMaxSize,
+                                                     const Matrix& aTransform,
+                                                     SurfaceFormat aFormat) const
+  {
+    return CreateSimilarDrawTarget(aMaxSize, aFormat);
+  }
+
+  /**
    * 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
   {
--- a/gfx/2d/DrawTargetRecording.cpp
+++ b/gfx/2d/DrawTargetRecording.cpp
@@ -246,19 +246,16 @@ DrawTargetRecording::DrawTargetRecording
 
 DrawTargetRecording::DrawTargetRecording(const DrawTargetRecording *aDT,
                                          IntSize aSize,
                                          SurfaceFormat aFormat)
   : mRecorder(aDT->mRecorder)
   , mFinalDT(aDT->mFinalDT)
   , mSize(aSize)
 {
-  mRecorder->RecordEvent(RecordedCreateSimilarDrawTarget(this,
-                                                         aSize,
-                                                         aFormat));
   mFormat = aFormat;
 }
 
 DrawTargetRecording::~DrawTargetRecording()
 {
   mRecorder->RecordEvent(RecordedDrawTargetDestruction(ReferencePtr(this)));
 }
 
@@ -562,19 +559,30 @@ DrawTargetRecording::CreateSourceSurface
   MOZ_ASSERT(false);
   return nullptr;
 }
 
 already_AddRefed<DrawTarget>
 DrawTargetRecording::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const
 {
   RefPtr<DrawTarget> similarDT = new DrawTargetRecording(this, aSize, aFormat);
+  mRecorder->RecordEvent(RecordedCreateSimilarDrawTarget(similarDT.get(),
+                                                         aSize,
+                                                         aFormat));
   return similarDT.forget();
 }
 
+RefPtr<DrawTarget>
+DrawTargetRecording::CreateClippedDrawTarget(const IntSize& aMaxSize, const Matrix& aTransform, SurfaceFormat aFormat) const
+{
+  RefPtr<DrawTarget> similarDT = new DrawTargetRecording(this, aMaxSize, aFormat);
+  mRecorder->RecordEvent(RecordedCreateClippedDrawTarget(similarDT.get(), aMaxSize, aTransform, aFormat));
+  return similarDT;
+}
+
 already_AddRefed<PathBuilder>
 DrawTargetRecording::CreatePathBuilder(FillRule aFillRule) const
 {
   RefPtr<PathBuilder> builder = mFinalDT->CreatePathBuilder(aFillRule);
   return MakeAndAddRef<PathBuilderRecording>(builder, aFillRule);
 }
 
 already_AddRefed<GradientStops>
--- a/gfx/2d/DrawTargetRecording.h
+++ b/gfx/2d/DrawTargetRecording.h
@@ -265,16 +265,24 @@ public:
     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 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 IntSize& aMaxSize,
+                                                     const Matrix& aTransform,
+                                                     SurfaceFormat aFormat) const override;
+
   /*
    * Create a path builder with the specified fillmode.
    *
    * We need the fill mode up front because of Direct2D.
    * ID2D1SimplifiedGeometrySink requires the fill mode
    * to be set before calling BeginFigure().
    */
   virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override;
--- a/gfx/2d/DrawTargetSkia.cpp
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -2157,16 +2157,18 @@ DrawTargetSkia::PopLayer()
       layerMat.preTranslate(layerBounds.x(), layerBounds.y());
 
       if (layerImage) {
         paint.setShader(layerImage->makeShader(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, &layerMat));
       } else {
         paint.setColor(SK_ColorTRANSPARENT);
       }
 
+      maskMat.postTranslate(layer.mMask->GetRect().X(), layer.mMask->GetRect().Y());
+
       sk_sp<SkImage> alphaMask = ExtractAlphaForSurface(layer.mMask);
       if (!alphaMask) {
         gfxDebug() << *this << ": PopLayer() failed to extract alpha for mask";
       } else {
         mCanvas->save();
 
         // The layer may be smaller than the canvas size, so make sure drawing is
         // clipped to within the bounds of the layer.
--- a/gfx/2d/RecordedEvent.h
+++ b/gfx/2d/RecordedEvent.h
@@ -247,16 +247,17 @@ public:
     SCALEDFONTDESTRUCTION,
     MASKSURFACE,
     FILTERNODECREATION,
     FILTERNODEDESTRUCTION,
     DRAWFILTER,
     FILTERNODESETATTRIBUTE,
     FILTERNODESETINPUT,
     CREATESIMILARDRAWTARGET,
+    CREATECLIPPEDDRAWTARGET,
     FONTDATA,
     FONTDESC,
     PUSHLAYER,
     POPLAYER,
     UNSCALEDFONTCREATION,
     UNSCALEDFONTDESTRUCTION,
   };
   static const uint32_t kTotalEventTypes = RecordedEvent::FILTERNODESETINPUT + 1;
--- a/gfx/2d/RecordedEventImpl.h
+++ b/gfx/2d/RecordedEventImpl.h
@@ -141,16 +141,48 @@ public:
 
 private:
   friend class RecordedEvent;
 
   template<class S>
   MOZ_IMPLICIT RecordedCreateSimilarDrawTarget(S &aStream);
 };
 
+class RecordedCreateClippedDrawTarget : public RecordedEventDerived<RecordedCreateClippedDrawTarget> {
+public:
+  RecordedCreateClippedDrawTarget(ReferencePtr aRefPtr, const IntSize& aMaxSize, const Matrix& aTransform, SurfaceFormat aFormat)
+    : RecordedEventDerived(CREATECLIPPEDDRAWTARGET)
+    , mRefPtr(aRefPtr)
+    , mMaxSize(aMaxSize)
+    , mTransform(aTransform)
+    , mFormat(aFormat)
+  {
+  }
+
+  virtual bool PlayEvent(Translator *aTranslator) const override;
+
+  template<class S>
+  void Record(S &aStream) const;
+  virtual void OutputSimpleEventInfo(std::stringstream &aStringStream) const override;
+
+  virtual std::string GetName() const override { return "CreateClippedDrawTarget"; }
+  virtual ReferencePtr GetObjectRef() const override { return mRefPtr; }
+
+  ReferencePtr mRefPtr;
+  IntSize mMaxSize;
+  Matrix mTransform;
+  SurfaceFormat mFormat;
+
+private:
+  friend class RecordedEvent;
+
+  template<class S>
+  MOZ_IMPLICIT RecordedCreateClippedDrawTarget(S &aStream);
+};
+
 class RecordedFillRect : public RecordedDrawingEvent<RecordedFillRect> {
 public:
   RecordedFillRect(DrawTarget *aDT, const Rect &aRect, const Pattern &aPattern, const DrawOptions &aOptions)
     : RecordedDrawingEvent(FILLRECT, aDT), mRect(aRect), mOptions(aOptions)
   {
     StorePattern(mPattern, aPattern);
   }
 
@@ -1667,16 +1699,70 @@ RecordedCreateSimilarDrawTarget::Recorde
 }
 
 inline void
 RecordedCreateSimilarDrawTarget::OutputSimpleEventInfo(std::stringstream &aStringStream) const
 {
   aStringStream << "[" << mRefPtr << "] CreateSimilarDrawTarget (Size: " << mSize.width << "x" << mSize.height << ")";
 }
 
+inline bool
+RecordedCreateClippedDrawTarget::PlayEvent(Translator *aTranslator) const
+{
+  const IntRect baseRect = aTranslator->GetReferenceDrawTarget()->GetRect();
+  const IntRect transformedRect = RoundedToInt(mTransform.Inverse().TransformBounds(IntRectToRect(baseRect)));
+  const IntRect intersection = IntRect(IntPoint(0, 0), mMaxSize).Intersect(transformedRect);
+
+  RefPtr<DrawTarget> newDT = aTranslator->GetReferenceDrawTarget()->CreateSimilarDrawTarget(intersection.Size(), SurfaceFormat::A8);
+  // It's overkill to use a TiledDrawTarget for a single tile
+  // but it was the easiest way to get the offset handling working
+  gfx::TileSet tileset;
+  gfx::Tile tile;
+  tile.mDrawTarget = newDT;
+  tile.mTileOrigin = gfx::IntPoint(intersection.X(), intersection.Y());
+  tileset.mTiles = &tile;
+  tileset.mTileCount = 1;
+  newDT = gfx::Factory::CreateTiledDrawTarget(tileset);
+
+  // 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, mMaxSize);
+  WriteElement(aStream, mTransform);
+  WriteElement(aStream, mFormat);
+}
+
+template<class S>
+RecordedCreateClippedDrawTarget::RecordedCreateClippedDrawTarget(S &aStream)
+  : RecordedEventDerived(CREATECLIPPEDDRAWTARGET)
+{
+  ReadElement(aStream, mRefPtr);
+  ReadElement(aStream, mMaxSize);
+  ReadElement(aStream, mTransform);
+  ReadElement(aStream, mFormat);
+}
+
+inline void
+RecordedCreateClippedDrawTarget::OutputSimpleEventInfo(std::stringstream &aStringStream) const
+{
+  aStringStream << "[" << mRefPtr << "] CreateClippedDrawTarget ()";
+}
+
 struct GenericPattern
 {
   GenericPattern(const PatternStorage &aStorage, Translator *aTranslator)
     : mPattern(nullptr), mTranslator(aTranslator)
   {
     mStorage = const_cast<PatternStorage*>(&aStorage);
   }
 
@@ -3316,16 +3402,17 @@ RecordedFilterNodeSetInput::OutputSimple
     f(SNAPSHOT, RecordedSnapshot); \
     f(SCALEDFONTCREATION, RecordedScaledFontCreation); \
     f(SCALEDFONTCREATIONBYINDEX, RecordedScaledFontCreationByIndex); \
     f(SCALEDFONTDESTRUCTION, RecordedScaledFontDestruction); \
     f(MASKSURFACE, RecordedMaskSurface); \
     f(FILTERNODESETATTRIBUTE, RecordedFilterNodeSetAttribute); \
     f(FILTERNODESETINPUT, RecordedFilterNodeSetInput); \
     f(CREATESIMILARDRAWTARGET, RecordedCreateSimilarDrawTarget); \
+    f(CREATECLIPPEDDRAWTARGET, RecordedCreateClippedDrawTarget); \
     f(FONTDATA, RecordedFontData); \
     f(FONTDESC, RecordedFontDescriptor); \
     f(PUSHLAYER, RecordedPushLayer); \
     f(POPLAYER, RecordedPopLayer); \
     f(UNSCALEDFONTCREATION, RecordedUnscaledFontCreation); \
     f(UNSCALEDFONTDESTRUCTION, RecordedUnscaledFontDestruction);
 
 template<class S>
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -790,43 +790,44 @@ GenerateAndPushTextMask(nsIFrame* aFrame
                               NS_RGB(255, 255, 255),
                               nsDisplayListBuilderMode::PAINTING_SELECTION_BACKGROUND);
   }
 
   // Evaluate required surface size.
   IntRect drawRect =
     RoundedOut(ToRect(sourceCtx->GetClipExtents(gfxContext::eDeviceSpace)));
 
+  Matrix currentMatrix = sourceCtx->CurrentMatrix();
+  Matrix maskTransform = currentMatrix *
+                         Matrix::Translation(-drawRect.x, -drawRect.y);
+  maskTransform.Invert();
+
   // Create a mask surface.
   RefPtr<DrawTarget> sourceTarget = sourceCtx->GetDrawTarget();
   RefPtr<DrawTarget> maskDT =
-    sourceTarget->CreateSimilarDrawTarget(drawRect.Size(),
+    sourceTarget->CreateClippedDrawTarget(drawRect.Size(),
+                                          maskTransform * currentMatrix,
                                           SurfaceFormat::A8);
   if (!maskDT || !maskDT->IsValid()) {
     return false;
   }
   RefPtr<gfxContext> maskCtx = gfxContext::CreatePreservingTransformOrNull(maskDT);
   MOZ_ASSERT(maskCtx);
-  Matrix currentMatrix = sourceCtx->CurrentMatrix();
   maskCtx->SetMatrix(Matrix::Translation(bounds.TopLeft().ToUnknownPoint()) *
                      currentMatrix *
                      Matrix::Translation(-drawRect.TopLeft()));
 
   // Shade text shape into mask A8 surface.
   nsLayoutUtils::PaintFrame(maskCtx, aFrame,
                             nsRect(nsPoint(0, 0), aFrame->GetSize()),
                             NS_RGB(255, 255, 255),
                             nsDisplayListBuilderMode::GENERATE_GLYPH);
 
   // Push the generated mask into aContext, so that the caller can pop and
   // blend with it.
-  Matrix maskTransform = currentMatrix *
-                         Matrix::Translation(-drawRect.x, -drawRect.y);
-  maskTransform.Invert();
-
   RefPtr<SourceSurface> maskSurface = maskDT->Snapshot();
   sourceCtx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 1.0, maskSurface, maskTransform);
 
   return true;
 }
 
 /* static */ void
 nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(Layer* aLayer,