re-enable skia caching

This permits consecutive path drawing or text drawing to combine
if their graphics state matches. This speeds up rendering considerably
when PDFium draws in many small parts. It also allows discarding
changes to the clip state that have no effect from draw to draw.

All corpus tests draw equivalently with caching enabled or disabled.

Change the member order in CPDF_PageRenderContext so the device is
flushed before the referencing annotation is deleted.

Add more printf style debugging for test draws.

R=dsinclair@chromium.org

Review-Url: https://codereview.chromium.org/2546803003
diff --git a/core/fpdfapi/cpdf_pagerendercontext.h b/core/fpdfapi/cpdf_pagerendercontext.h
index 9ddd075..27244e7 100644
--- a/core/fpdfapi/cpdf_pagerendercontext.h
+++ b/core/fpdfapi/cpdf_pagerendercontext.h
@@ -21,10 +21,10 @@
   CPDF_PageRenderContext();
   ~CPDF_PageRenderContext();
 
+  std::unique_ptr<CPDF_AnnotList> m_pAnnots;
   std::unique_ptr<CFX_RenderDevice> m_pDevice;
   std::unique_ptr<CPDF_RenderContext> m_pContext;
   std::unique_ptr<CPDF_ProgressiveRenderer> m_pRenderer;
-  std::unique_ptr<CPDF_AnnotList> m_pAnnots;
   std::unique_ptr<CPDF_RenderOptions> m_pOptions;
 };
 
diff --git a/core/fpdfapi/render/cpdf_renderstatus.cpp b/core/fpdfapi/render/cpdf_renderstatus.cpp
index 88dbb03..b23b98c 100644
--- a/core/fpdfapi/render/cpdf_renderstatus.cpp
+++ b/core/fpdfapi/render/cpdf_renderstatus.cpp
@@ -898,6 +898,7 @@
   context.AppendLayer(pPattern->form(), &mtPattern2Bitmap);
   context.Render(&bitmap_device, &options, nullptr);
 #if defined _SKIA_SUPPORT_PATHS_
+  bitmap_device.Flush();
   pBitmap->UnPreMultiply();
 #endif
   return pBitmap;
@@ -1553,6 +1554,7 @@
                            pFormResource, true);
   bitmap_render.ProcessObjectNoClip(pPageObj, &new_matrix);
 #if defined _SKIA_SUPPORT_PATHS_
+  bitmap_device.Flush();
   bitmap->UnPreMultiply();
 #endif
   m_bStopped = bitmap_render.m_bStopped;
diff --git a/core/fxge/cfx_renderdevice.h b/core/fxge/cfx_renderdevice.h
index d38b298..800d0c7 100644
--- a/core/fxge/cfx_renderdevice.h
+++ b/core/fxge/cfx_renderdevice.h
@@ -227,6 +227,8 @@
                                int top,
                                int bitmap_alpha,
                                int blend_type);
+#endif
+#if defined _SKIA_SUPPORT_ || defined _SKIA_SUPPORT_PATHS_
   void Flush();
 #endif
 
diff --git a/core/fxge/ge/cfx_renderdevice.cpp b/core/fxge/ge/cfx_renderdevice.cpp
index 64fa6ff..9c67a7d 100644
--- a/core/fxge/ge/cfx_renderdevice.cpp
+++ b/core/fxge/ge/cfx_renderdevice.cpp
@@ -354,9 +354,13 @@
       m_RenderCaps(0),
       m_DeviceClass(0) {}
 
-CFX_RenderDevice::~CFX_RenderDevice() {}
+CFX_RenderDevice::~CFX_RenderDevice() {
+#if defined _SKIA_SUPPORT_ || defined _SKIA_SUPPORT_PATHS_
+  Flush();
+#endif
+}
 
-#ifdef _SKIA_SUPPORT_
+#if defined _SKIA_SUPPORT_ || defined _SKIA_SUPPORT_PATHS_
 void CFX_RenderDevice::Flush() {
   m_pDeviceDriver.reset();
 }
@@ -615,6 +619,9 @@
           blend_type)) {
     return false;
   }
+#if defined _SKIA_SUPPORT_ || defined _SKIA_SUPPORT_PATHS_
+  bitmap_device.GetDeviceDriver()->Flush();
+#endif
   FX_RECT src_rect(0, 0, FXSYS_round(rect.Width() * fScaleX),
                    FXSYS_round(rect.Height() * fScaleY));
   return m_pDeviceDriver->SetDIBits(&bitmap, 0, &src_rect, rect.left, rect.top,
diff --git a/core/fxge/ifx_renderdevicedriver.cpp b/core/fxge/ifx_renderdevicedriver.cpp
index 07a4c27..77af00f 100644
--- a/core/fxge/ifx_renderdevicedriver.cpp
+++ b/core/fxge/ifx_renderdevicedriver.cpp
@@ -99,3 +99,7 @@
                                              int blend_type) {
   return false;
 }
+
+#if defined _SKIA_SUPPORT_ || _SKIA_SUPPORT_PATHS_
+void IFX_RenderDeviceDriver::Flush() {}
+#endif
diff --git a/core/fxge/ifx_renderdevicedriver.h b/core/fxge/ifx_renderdevicedriver.h
index 6a99276..6a5b63b 100644
--- a/core/fxge/ifx_renderdevicedriver.h
+++ b/core/fxge/ifx_renderdevicedriver.h
@@ -103,6 +103,9 @@
                                int top,
                                int bitmap_alpha,
                                int blend_type);
+#if defined _SKIA_SUPPORT_ || defined _SKIA_SUPPORT_PATHS_
+  virtual void Flush();
+#endif
 };
 
 #endif  // CORE_FXGE_IFX_RENDERDEVICEDRIVER_H_
diff --git a/core/fxge/skia/fx_skia_device.cpp b/core/fxge/skia/fx_skia_device.cpp
index 84db838..9c0ea07 100644
--- a/core/fxge/skia/fx_skia_device.cpp
+++ b/core/fxge/skia/fx_skia_device.cpp
@@ -155,6 +155,25 @@
 #define SHOW_SKIA_PATH 0  // set to 1 to print the path contents
 #define DRAW_SKIA_CLIP 0  // set to 1 to draw a green rectangle around the clip
 
+#if SHOW_SKIA_PATH
+void DebugShowSkiaPaint(const SkPaint& paint) {
+  if (SkPaint::kFill_Style == paint.getStyle()) {
+    printf("fill 0x%08x\n", paint.getColor());
+  } else {
+    printf("stroke 0x%08x width %g\n", paint.getColor(),
+           paint.getStrokeWidth());
+  }
+}
+
+void DebugShowCanvasMatrix(const SkCanvas* canvas) {
+  SkMatrix matrix = canvas->getTotalMatrix();
+  SkScalar m[9];
+  matrix.get9(m);
+  printf("matrix (%g,%g,%g) (%g,%g,%g) (%g,%g,%g)\n", m[0], m[1], m[2], m[3],
+         m[4], m[5], m[6], m[7], m[8]);
+}
+#endif  // SHOW_SKIA_PATH
+
 void DebugShowSkiaPath(const SkPath& path) {
 #if SHOW_SKIA_PATH
   char buffer[4096];
@@ -165,18 +184,8 @@
 #endif  // SHOW_SKIA_PATH
 }
 
-void DebugShowCanvasMatrix(const SkCanvas* canvas) {
-#if SHOW_SKIA_PATH
-  SkMatrix matrix = canvas->getTotalMatrix();
-  SkScalar m[9];
-  matrix.get9(m);
-  printf("matrix (%g,%g,%g) (%g,%g,%g) (%g,%g,%g)\n", m[0], m[1], m[2], m[3],
-         m[4], m[5], m[6], m[7], m[8]);
-#endif  // SHOW_SKIA_PATH
-}
-
-#if SHOW_SKIA_PATH
 void DebugShowCanvasClip(const SkCanvas* canvas) {
+#if SHOW_SKIA_PATH
   SkRect local;
   SkIRect device;
   canvas->getClipBounds(&local);
@@ -187,8 +196,10 @@
          device.fRight, device.fBottom);
   const SkClipStack* clipStack = canvas->getClipStack();
   clipStack->dump();
+#endif  // SHOW_SKIA_PATH
 }
 
+#if SHOW_SKIA_PATH
 void DebugShowSkiaPaint(const SkPaint& paint) {
   if (SkPaint::kFill_Style == paint.getStyle()) {
     printf("fill 0x%08x\n", paint.getColor());
@@ -634,7 +645,6 @@
 
 }  // namespace
 
-#ifdef _SKIA_SUPPORT_
 // Encapsulate the state used for successive text and path draws so that
 // they can be combined.
 class SkiaState {
@@ -644,19 +654,35 @@
     kPath,
   };
 
+  enum class Accumulator {
+    kNone,
+    kPath,
+    kText,
+    kOther,
+  };
+
   // mark all cached state as uninitialized
-  SkiaState()
-      : m_pFont(nullptr),
+  explicit SkiaState(CFX_SkiaDeviceDriver* pDriver)
+      : m_pDriver(pDriver),
+        m_pFont(nullptr),
         m_fontSize(0),
         m_fillColor(0),
         m_strokeColor(0),
         m_blendType(0),
         m_commandIndex(0),
-        m_drawText(false),
-        m_drawPath(false),
+        m_drawIndex(INT_MAX),
+        m_clipIndex(0),
+        m_type(Accumulator::kNone),
+        m_fillFullCover(false),
         m_fillPath(false),
         m_groupKnockout(false),
-        m_debugDisable(false) {}
+        m_debugDisable(false)
+#if SHOW_SKIA_PATH
+        ,
+        m_debugSaveCounter(0)
+#endif
+  {
+  }
 
   bool DrawPath(const CFX_PathData* pPathData,
                 const CFX_Matrix* pMatrix,
@@ -664,21 +690,20 @@
                 uint32_t fill_color,
                 uint32_t stroke_color,
                 int fill_mode,
-                int blend_type,
-                CFX_SkiaDeviceDriver* pDriver) {
+                int blend_type) {
     if (m_debugDisable)
       return false;
-    if (m_commandIndex < m_commands.count())
-      FlushCommands(pDriver);
-    if (m_drawText)
-      FlushText(pDriver);
-    if (m_drawPath &&
-        DrawChanged(pMatrix, pDrawState, fill_color, stroke_color, fill_mode,
-                    blend_type, pDriver->m_bGroupKnockout)) {
-      FlushPath(pDriver);
+    Dump(__func__);
+    int drawIndex = SkTMin(m_drawIndex, m_commands.count());
+    if (Accumulator::kText == m_type || drawIndex != m_commandIndex ||
+        (Accumulator::kPath == m_type &&
+         DrawChanged(pMatrix, pDrawState, fill_color, stroke_color, fill_mode,
+                     blend_type, m_pDriver->m_bGroupKnockout))) {
+      Flush();
     }
-    if (!m_drawPath) {
+    if (Accumulator::kPath != m_type) {
       m_skPath.reset();
+      m_fillFullCover = !!(fill_mode & FXFILL_FULLCOVER);
       m_fillPath = (fill_mode & 3) && fill_color;
       m_skPath.setFillType((fill_mode & 3) == FXFILL_ALTERNATE
                                ? SkPath::kEvenOdd_FillType
@@ -688,27 +713,31 @@
       m_fillColor = fill_color;
       m_strokeColor = stroke_color;
       m_blendType = blend_type;
-      m_groupKnockout = pDriver->m_bGroupKnockout;
+      m_groupKnockout = m_pDriver->m_bGroupKnockout;
       if (pMatrix)
         m_drawMatrix = *pMatrix;
+      m_drawIndex = m_commandIndex;
+      m_type = Accumulator::kPath;
     }
     SkPath skPath = BuildPath(pPathData);
     SkPoint delta;
     if (MatrixOffset(pMatrix, &delta))
       skPath.offset(delta.fX, delta.fY);
     m_skPath.addPath(skPath);
-    m_drawPath = true;
     return true;
   }
 
-  void FlushPath(CFX_SkiaDeviceDriver* pDriver) {
+  void FlushPath() {
+    Dump(__func__);
     SkMatrix skMatrix = ToSkMatrix(m_drawMatrix);
     SkPaint skPaint;
     skPaint.setAntiAlias(true);
+    if (m_fillFullCover)
+      skPaint.setBlendMode(SkBlendMode::kPlus);
     int stroke_alpha = FXARGB_A(m_strokeColor);
     if (stroke_alpha)
-      pDriver->PaintStroke(&skPaint, &m_drawState, skMatrix);
-    SkCanvas* skCanvas = pDriver->SkiaCanvas();
+      m_pDriver->PaintStroke(&skPaint, &m_drawState, skMatrix);
+    SkCanvas* skCanvas = m_pDriver->SkiaCanvas();
     skCanvas->save();
     skCanvas->concat(skMatrix);
     if (m_fillPath) {
@@ -725,17 +754,24 @@
       }
       skPaint.setStyle(SkPaint::kFill_Style);
       skPaint.setColor(m_fillColor);
+#ifdef _SKIA_SUPPORT_PATHS_
+      m_pDriver->PreMultiply();
+#endif  // _SKIA_SUPPORT_PATHS_
+      DebugShowSkiaDrawPath(skCanvas, skPaint, *fillPath);
       skCanvas->drawPath(*fillPath, skPaint);
     }
     if (stroke_alpha) {
-      DebugShowSkiaPath(m_skPath);
-      DebugShowCanvasMatrix(skCanvas);
       skPaint.setStyle(SkPaint::kStroke_Style);
       skPaint.setColor(m_strokeColor);
+#ifdef _SKIA_SUPPORT_PATHS_
+      m_pDriver->PreMultiply();
+#endif  // _SKIA_SUPPORT_PATHS_
+      DebugShowSkiaDrawPath(skCanvas, skPaint, m_skPath);
       skCanvas->drawPath(m_skPath, skPaint);
     }
     skCanvas->restore();
-    m_drawPath = false;
+    m_drawIndex = INT_MAX;
+    m_type = Accumulator::kNone;
   }
 
   bool DrawText(int nChars,
@@ -743,31 +779,36 @@
                 CFX_Font* pFont,
                 const CFX_Matrix* pMatrix,
                 FX_FLOAT font_size,
-                uint32_t color,
-                CFX_SkiaDeviceDriver* pDriver) {
+                uint32_t color) {
     if (m_debugDisable)
       return false;
-    if (m_commandIndex < m_commands.count())
-      FlushCommands(pDriver);
-    if (m_drawPath)
-      FlushPath(pDriver);
-    if (m_drawText && FontChanged(pFont, pMatrix, font_size, color))
-      FlushText(pDriver);
-    if (!m_drawText) {
+    Dump(__func__);
+    int drawIndex = SkTMin(m_drawIndex, m_commands.count());
+    if (Accumulator::kPath == m_type || drawIndex != m_commandIndex ||
+        (Accumulator::kText == m_type &&
+         FontChanged(pFont, pMatrix, font_size, color))) {
+      Flush();
+    }
+    if (Accumulator::kText != m_type) {
       m_positions.setCount(0);
       m_glyphs.setCount(0);
       m_pFont = pFont;
       m_fontSize = font_size;
       m_fillColor = color;
       m_drawMatrix = *pMatrix;
+      m_drawIndex = m_commandIndex;
+      m_type = Accumulator::kText;
     }
     int count = m_positions.count();
     m_positions.setCount(nChars + count);
     m_glyphs.setCount(nChars + count);
     SkScalar flip = m_fontSize < 0 ? -1 : 1;
+    SkScalar vFlip = flip;
+    if (pFont->IsVertical())
+      vFlip *= -1;
     for (int index = 0; index < nChars; ++index) {
       const FXTEXT_CHARPOS& cp = pCharPos[index];
-      m_positions[index + count] = {cp.m_OriginX * flip, cp.m_OriginY * flip};
+      m_positions[index + count] = {cp.m_OriginX * flip, cp.m_OriginY * vFlip};
       m_glyphs[index + count] = (uint16_t)cp.m_GlyphIndex;
     }
     SkPoint delta;
@@ -775,13 +816,11 @@
       for (int index = 0; index < nChars; ++index)
         m_positions[index + count].offset(delta.fX * flip, -delta.fY * flip);
     }
-    m_drawText = true;
     return true;
   }
 
-  void FlushText(CFX_SkiaDeviceDriver* pDriver) {
-    SkScalar flip = m_fontSize < 0 ? -1 : 1;
-    SkMatrix skMatrix = ToFlippedSkMatrix(m_drawMatrix, flip);
+  void FlushText() {
+    Dump(__func__);
     SkPaint skPaint;
     skPaint.setAntiAlias(true);
     skPaint.setColor(m_fillColor);
@@ -793,60 +832,78 @@
     skPaint.setHinting(SkPaint::kNo_Hinting);
     skPaint.setTextSize(SkTAbs(m_fontSize));
     skPaint.setSubpixelText(true);
-    SkCanvas* skCanvas = pDriver->SkiaCanvas();
+    SkCanvas* skCanvas = m_pDriver->SkiaCanvas();
     skCanvas->save();
+    SkScalar flip = m_fontSize < 0 ? -1 : 1;
+    SkMatrix skMatrix = ToFlippedSkMatrix(m_drawMatrix, flip);
     skCanvas->concat(skMatrix);
+#ifdef _SKIA_SUPPORT_PATHS_
+    m_pDriver->PreMultiply();
+#endif  // _SKIA_SUPPORT_PATHS_
     skCanvas->drawPosText(m_glyphs.begin(), m_glyphs.count() * 2,
                           m_positions.begin(), skPaint);
     skCanvas->restore();
-    m_drawText = false;
+    m_drawIndex = INT_MAX;
+    m_type = Accumulator::kNone;
   }
 
   bool SetClipFill(const CFX_PathData* pPathData,
                    const CFX_Matrix* pMatrix,
-                   int fill_mode,
-                   CFX_SkiaDeviceDriver* pDriver) {
+                   int fill_mode) {
     if (m_debugDisable)
       return false;
+    Dump(__func__);
     SkPath skClipPath = BuildPath(pPathData);
     skClipPath.setFillType((fill_mode & 3) == FXFILL_ALTERNATE
                                ? SkPath::kEvenOdd_FillType
                                : SkPath::kWinding_FillType);
     SkMatrix skMatrix = ToSkMatrix(*pMatrix);
     skClipPath.transform(skMatrix);
-    return SetClip(skClipPath, pDriver);
+    return SetClip(skClipPath);
   }
 
-  bool SetClip(const SkPath& skClipPath, CFX_SkiaDeviceDriver* pDriver) {
+  bool SetClip(const SkPath& skClipPath) {
+    // if a pending draw depends on clip state that is cached, flush it and draw
     if (m_commandIndex < m_commands.count()) {
       if (m_commands[m_commandIndex] == Clip::kPath &&
           m_clips[m_commandIndex] == skClipPath) {
         ++m_commandIndex;
         return true;
       }
-      FlushCommands(pDriver);
+      Flush();
     }
-    Flush(pDriver);
-    m_commands.push(Clip::kPath);
+    while (m_clipIndex > m_commandIndex) {
+      do {
+        --m_clipIndex;
+        SkASSERT(m_clipIndex >= 0);
+      } while (m_commands[m_clipIndex] != Clip::kSave);
+      m_pDriver->SkiaCanvas()->restore();
+    }
+    if (m_commandIndex < m_commands.count()) {
+      m_commands[m_commandIndex] = Clip::kPath;
+      m_clips[m_commandIndex] = skClipPath;
+    } else {
+      m_commands.push(Clip::kPath);
+      m_clips.push_back(skClipPath);
+    }
     ++m_commandIndex;
-    m_clips.push_back(skClipPath);
-    return false;
+    return true;
   }
 
   bool SetClipStroke(const CFX_PathData* pPathData,
                      const CFX_Matrix* pMatrix,
-                     const CFX_GraphStateData* pGraphState,
-                     CFX_SkiaDeviceDriver* pDriver) {
+                     const CFX_GraphStateData* pGraphState) {
     if (m_debugDisable)
       return false;
+    Dump(__func__);
     SkPath skPath = BuildPath(pPathData);
     SkMatrix skMatrix = ToSkMatrix(*pMatrix);
     SkPaint skPaint;
-    pDriver->PaintStroke(&skPaint, pGraphState, skMatrix);
+    m_pDriver->PaintStroke(&skPaint, pGraphState, skMatrix);
     SkPath dst_path;
     skPaint.getFillPath(skPath, &dst_path);
     dst_path.transform(skMatrix);
-    return SetClip(dst_path, pDriver);
+    return SetClip(dst_path);
   }
 
   bool MatrixOffset(const CFX_Matrix* pMatrix, SkPoint* delta) {
@@ -871,60 +928,38 @@
     return true;
   }
 
-  void FlushCommands(CFX_SkiaDeviceDriver* pDriver) {
-    if (m_commandIndex == m_commands.count())
-      return;
-    if (m_commandIndex < m_commands.count())
-      pDriver->SkiaCanvas()->restore();
-    int index = m_commands.count() - 1;
-    if (m_commandIndex == index && m_commands[index] == Clip::kSave)
-      return;
-    for (; index > m_commandIndex; --index) {
-      if (m_commands[index] == Clip::kSave)
-        pDriver->SkiaCanvas()->restore();
-    }
-
-    if (m_commandIndex > 0)
-      pDriver->SkiaCanvas()->save();
-    while (index > 0 && m_commands[index] != Clip::kSave)
-      --index;
-    while (++index < m_commandIndex) {
-      SkASSERT(m_commands[index] == Clip::kPath);
-      pDriver->SkiaCanvas()->clipPath(m_clips[index], SkCanvas::kIntersect_Op,
-                                      true);
-    }
-    m_commands.setCount(m_commandIndex);
-    m_clips.resize_back(m_commandIndex);
-  }
-
   // returns true if caller should apply command to skia canvas
-  bool ClipSave(CFX_SkiaDeviceDriver* pDriver) {
+  bool ClipSave() {
     if (m_debugDisable)
       return false;
+    Dump(__func__);
     int count = m_commands.count();
-    if (m_commandIndex < count) {
-      if (m_commands[m_commandIndex] == Clip::kSave) {
+    if (m_commandIndex < count - 1) {
+      if (Clip::kSave == m_commands[m_commandIndex + 1]) {
         ++m_commandIndex;
         return true;
       }
-      FlushCommands(pDriver);
+      Flush();
+      AdjustClip(m_commandIndex);
+      m_commands[++m_commandIndex] = Clip::kSave;
+      m_clips[m_commandIndex] = m_skEmptyPath;
+    } else {
+      AdjustClip(m_commandIndex);
+      m_commands.push(Clip::kSave);
+      m_clips.push_back(m_skEmptyPath);
+      ++m_commandIndex;
     }
-    Flush(pDriver);
-    m_commands.push(Clip::kSave);
-    ++m_commandIndex;
-    m_clips.push_back(m_skEmptyPath);
-    return false;
+    return true;
   }
 
-  bool ClipRestore(CFX_SkiaDeviceDriver* pDriver) {
+  bool ClipRestore() {
     if (m_debugDisable)
       return false;
-    while (m_commandIndex > 0) {
-      if (m_commands[--m_commandIndex] == Clip::kSave)
-        return true;
+    Dump(__func__);
+    while (Clip::kSave != m_commands[--m_commandIndex]) {
+      SkASSERT(m_commandIndex > 0);
     }
-    Flush(pDriver);
-    return false;
+    return true;
   }
 
   bool DrawChanged(const CFX_Matrix* pMatrix,
@@ -933,7 +968,7 @@
                    uint32_t stroke_color,
                    int fill_mode,
                    int blend_type,
-                   bool group_knockout) {
+                   bool group_knockout) const {
     return MatrixChanged(pMatrix, m_drawMatrix) ||
            StateChanged(pState, m_drawState) || fill_color != m_fillColor ||
            stroke_color != m_strokeColor ||
@@ -945,12 +980,13 @@
   bool FontChanged(CFX_Font* pFont,
                    const CFX_Matrix* pMatrix,
                    FX_FLOAT font_size,
-                   uint32_t color) {
+                   uint32_t color) const {
     return pFont != m_pFont || MatrixChanged(pMatrix, m_drawMatrix) ||
            font_size != m_fontSize || color != m_fillColor;
   }
 
-  bool MatrixChanged(const CFX_Matrix* pMatrix, const CFX_Matrix& refMatrix) {
+  bool MatrixChanged(const CFX_Matrix* pMatrix,
+                     const CFX_Matrix& refMatrix) const {
     CFX_Matrix identityMatrix;
     if (!pMatrix)
       pMatrix = &identityMatrix;
@@ -959,7 +995,7 @@
   }
 
   bool StateChanged(const CFX_GraphStateData* pState,
-                    const CFX_GraphStateData& refState) {
+                    const CFX_GraphStateData& refState) const {
     CFX_GraphStateData identityState;
     if (!pState)
       pState = &identityState;
@@ -971,7 +1007,7 @@
   }
 
   bool DashChanged(const CFX_GraphStateData* pState,
-                   const CFX_GraphStateData& refState) {
+                   const CFX_GraphStateData& refState) const {
     bool dashArray = pState && pState->m_DashArray;
     if (!dashArray && !refState.m_DashArray)
       return false;
@@ -983,39 +1019,97 @@
     }
     for (int index = 0; index < pState->m_DashCount; ++index) {
       if (pState->m_DashArray[index] != refState.m_DashArray[index])
-        return false;
+        return true;
     }
     return true;
   }
 
-  void Flush(CFX_SkiaDeviceDriver* pDriver) {
-    if (m_drawPath)
-      FlushPath(pDriver);
-    if (m_drawText)
-      FlushText(pDriver);
+  void AdjustClip(int limit) {
+    while (m_clipIndex > limit) {
+      do {
+        --m_clipIndex;
+        SkASSERT(m_clipIndex >= 0);
+      } while (m_commands[m_clipIndex] != Clip::kSave);
+      m_pDriver->SkiaCanvas()->restore();
+    }
+    while (m_clipIndex < limit) {
+      if (Clip::kSave == m_commands[m_clipIndex]) {
+        m_pDriver->SkiaCanvas()->save();
+      } else {
+        SkASSERT(Clip::kPath == m_commands[m_clipIndex]);
+        m_pDriver->SkiaCanvas()->clipPath(m_clips[m_clipIndex],
+                                          SkCanvas::kIntersect_Op, true);
+      }
+      ++m_clipIndex;
+    }
   }
 
-  void Dump(const CFX_SkiaDeviceDriver* pDriver) const {
+  void Flush() {
+    if (m_debugDisable)
+      return;
+    Dump(__func__);
+    if (Accumulator::kPath == m_type || Accumulator::kText == m_type) {
+      AdjustClip(SkTMin(m_drawIndex, m_commands.count()));
+      Accumulator::kPath == m_type ? FlushPath() : FlushText();
+    }
+  }
+
+  void FlushForDraw() {
+    if (m_debugDisable)
+      return;
+    Flush();                     // draw any pending text or path
+    AdjustClip(m_commandIndex);  // set up clip stack with any pending state
+  }
+
 #if SHOW_SKIA_PATH
-    SkDebugf("\n\nSkia Save Count %d:\n", pDriver->m_pCanvas->getSaveCount());
-    pDriver->m_pCanvas->getClipStack()->dump();
-    SkDebugf("Cache:\n");
+  void DumpPrefix(int index) const {
+    if (index != m_commandIndex && index != m_drawIndex &&
+        index != m_clipIndex) {
+      printf("     ");
+      return;
+    }
+    printf("%c%c%c> ", index == m_commandIndex ? 'x' : '-',
+           index == m_drawIndex ? 'd' : '-', index == m_clipIndex ? 'c' : '-');
+  }
+
+  void DumpEndPrefix() const {
+    int index = m_commands.count();
+    if (index != m_commandIndex && index > m_drawIndex && index != m_clipIndex)
+      return;
+    printf("%c%c%c>\n", index == m_commandIndex ? 'x' : '-',
+           index <= m_drawIndex ? 'd' : '-', index == m_clipIndex ? 'c' : '-');
+  }
+#endif  // SHOW_SKIA_PATH
+
+  void Dump(const char* where) const {
+#if SHOW_SKIA_PATH
+    printf("\n%s\nSkia Save Count %d:\n", where,
+           m_pDriver->m_pCanvas->getSaveCount());
+    m_pDriver->m_pCanvas->getClipStack()->dump();
+    printf("Cache:\n");
     for (int index = 0; index < m_commands.count(); ++index) {
-      SkDebugf("%s ", m_commandIndex == index ? "-->" : "   ");
+      DumpPrefix(index);
       switch (m_commands[index]) {
         case Clip::kSave:
-          SkDebugf("Save\n");
+          printf("Save %d\n", ++m_debugSaveCounter);
           break;
         case Clip::kPath:
           m_clips[index].dump();
           break;
         default:
-          SkDebugf("unknown\n");
+          printf("unknown\n");
       }
     }
-    if (m_commandIndex == m_commands.count())
-      SkDebugf("-->\n");
+    DumpEndPrefix();
 #endif  // SHOW_SKIA_PATH
+#ifdef SK_DEBUG
+    int skCanvasSaveCount = m_pDriver->m_pCanvas->getSaveCount();
+    int cacheSaveCount = 1;
+    SkASSERT(m_clipIndex <= m_commands.count());
+    for (int index = 0; index < m_clipIndex; ++index)
+      cacheSaveCount += Clip::kSave == m_commands[index];
+    SkASSERT(skCanvasSaveCount == cacheSaveCount);
+#endif
   }
 
  private:
@@ -1029,19 +1123,24 @@
   CFX_GraphStateData m_clipState;
   CFX_GraphStateData m_drawState;
   CFX_Matrix m_clipMatrix;
+  CFX_SkiaDeviceDriver* m_pDriver;
   CFX_Font* m_pFont;
   FX_FLOAT m_fontSize;
   uint32_t m_fillColor;
   uint32_t m_strokeColor;
   int m_blendType;
   int m_commandIndex;  // active position in clip command stack
-  bool m_drawText;
-  bool m_drawPath;
+  int m_drawIndex;     // position of the pending path or text draw
+  int m_clipIndex;     // position reflecting depth of canvas clip stacck
+  Accumulator m_type;  // type of pending draw
+  bool m_fillFullCover;
   bool m_fillPath;
   bool m_groupKnockout;
   bool m_debugDisable;  // turn off cache for debugging
+#if SHOW_SKIA_PATH
+  mutable int m_debugSaveCounter;
+#endif
 };
-#endif  // _SKIA_SUPPORT_
 
 // convert a stroking path to scanlines
 void CFX_SkiaDeviceDriver::PaintStroke(SkPaint* spaint,
@@ -1115,9 +1214,7 @@
     : m_pBitmap(pBitmap),
       m_pOriDevice(pOriDevice),
       m_pRecorder(nullptr),
-#ifdef _SKIA_SUPPORT_
-      m_pCache(new SkiaState),
-#endif
+      m_pCache(new SkiaState(this)),
 #ifdef _SKIA_SUPPORT_PATHS_
       m_pClipRgn(nullptr),
       m_FillFlags(0),
@@ -1141,7 +1238,7 @@
     : m_pBitmap(nullptr),
       m_pOriDevice(nullptr),
       m_pRecorder(new SkPictureRecorder),
-      m_pCache(new SkiaState),
+      m_pCache(new SkiaState(this)),
       m_bGroupKnockout(false) {
   m_pRecorder->beginRecording(SkIntToScalar(size_x), SkIntToScalar(size_y));
   m_pCanvas = m_pRecorder->getRecordingCanvas();
@@ -1151,7 +1248,7 @@
     : m_pBitmap(nullptr),
       m_pOriDevice(nullptr),
       m_pRecorder(recorder),
-      m_pCache(new SkiaState),
+      m_pCache(new SkiaState(this)),
       m_bGroupKnockout(false) {
   m_pCanvas = m_pRecorder->getRecordingCanvas();
 }
@@ -1164,10 +1261,7 @@
 }
 
 void CFX_SkiaDeviceDriver::Flush() {
-#ifdef _SKIA_SUPPORT_
-  m_pCache->Flush(this);
-  m_pCache->FlushCommands(this);
-#endif
+  m_pCache->Flush();
 }
 
 bool CFX_SkiaDeviceDriver::DrawDeviceText(int nChars,
@@ -1176,12 +1270,10 @@
                                           const CFX_Matrix* pObject2Device,
                                           FX_FLOAT font_size,
                                           uint32_t color) {
-#ifdef _SKIA_SUPPORT_
   if (m_pCache->DrawText(nChars, pCharPos, pFont, pObject2Device, font_size,
-                         color, this)) {
+                         color)) {
     return true;
   }
-#endif
   sk_sp<SkTypeface> typeface(SkSafeRef(pFont->GetDeviceCache()));
   SkPaint paint;
   paint.setAntiAlias(true);
@@ -1269,9 +1361,7 @@
 }
 
 void CFX_SkiaDeviceDriver::SaveState() {
-#ifdef _SKIA_SUPPORT_
-  if (!m_pCache->ClipSave(this))
-#endif
+  if (!m_pCache->ClipSave())
     m_pCanvas->save();
 
 #ifdef _SKIA_SUPPORT_PATHS_
@@ -1283,17 +1373,10 @@
 }
 
 void CFX_SkiaDeviceDriver::RestoreState(bool bKeepSaved) {
-#ifdef _SKIA_SUPPORT_
-  if (!m_pCache->ClipRestore(this))
-#endif
+  if (!m_pCache->ClipRestore())
     m_pCanvas->restore();
-  if (bKeepSaved
-#ifdef _SKIA_SUPPORT_
-      && !m_pCache->ClipSave(this)
-#endif
-          ) {
+  if (bKeepSaved && !m_pCache->ClipSave())
     m_pCanvas->save();
-  }
 #ifdef _SKIA_SUPPORT_PATHS_
   m_pClipRgn.reset();
 
@@ -1346,10 +1429,7 @@
     ) {
   CFX_Matrix identity;
   const CFX_Matrix* deviceMatrix = pObject2Device ? pObject2Device : &identity;
-#ifdef _SKIA_SUPPORT_
-  if (m_pCache->SetClipFill(pPathData, deviceMatrix, fill_mode, this))
-    return true;
-#endif  // _SKIA_SUPPORT_
+  bool cached = m_pCache->SetClipFill(pPathData, deviceMatrix, fill_mode);
 
 #ifdef _SKIA_SUPPORT_PATHS_
   m_FillFlags = fill_mode;
@@ -1365,15 +1445,18 @@
           CFX_FloatRect(0, 0, (FX_FLOAT)GetDeviceCaps(FXDC_PIXEL_WIDTH),
                         (FX_FLOAT)GetDeviceCaps(FXDC_PIXEL_HEIGHT)));
       // note that PDF's y-axis goes up; Skia's y-axis goes down
-      SkRect skClipRect =
-          SkRect::MakeLTRB(rectf.left, rectf.bottom, rectf.right, rectf.top);
-      DebugDrawSkiaClipRect(m_pCanvas, skClipRect);
-      m_pCanvas->clipRect(skClipRect, SkCanvas::kIntersect_Op, true);
+      if (!cached) {
+        SkRect skClipRect =
+            SkRect::MakeLTRB(rectf.left, rectf.bottom, rectf.right, rectf.top);
+        DebugDrawSkiaClipRect(m_pCanvas, skClipRect);
+        m_pCanvas->clipRect(skClipRect, SkCanvas::kIntersect_Op, true);
+      }
 
 #ifdef _SKIA_SUPPORT_PATHS_
       FX_RECT rect = rectf.GetOuterRect();
       m_pClipRgn->IntersectRect(rect);
 #endif  // _SKIA_SUPPORT_PATHS_
+      DebugShowCanvasClip(m_pCanvas);
       return true;
     }
   }
@@ -1384,13 +1467,16 @@
   SkMatrix skMatrix = ToSkMatrix(*deviceMatrix);
   skClipPath.transform(skMatrix);
   DebugShowSkiaPath(skClipPath);
-  DebugDrawSkiaClipPath(m_pCanvas, skClipPath);
-  m_pCanvas->clipPath(skClipPath, SkCanvas::kIntersect_Op, true);
+  if (!cached) {
+    DebugDrawSkiaClipPath(m_pCanvas, skClipPath);
+    m_pCanvas->clipPath(skClipPath, SkCanvas::kIntersect_Op, true);
+  }
 #ifdef _SKIA_SUPPORT_PATHS_
   FX_RECT clipBox(0, 0, GetDeviceCaps(FXDC_PIXEL_WIDTH),
                   GetDeviceCaps(FXDC_PIXEL_HEIGHT));
   SetClipMask(clipBox, skClipPath);
 #endif  // _SKIA_SUPPORT_PATHS_
+  DebugShowCanvasClip(m_pCanvas);
   return true;
 }
 
@@ -1399,10 +1485,7 @@
     const CFX_Matrix* pObject2Device,      // optional transformation
     const CFX_GraphStateData* pGraphState  // graphic state, for pen attributes
     ) {
-#ifdef _SKIA_SUPPORT_
-  if (m_pCache->SetClipStroke(pPathData, pObject2Device, pGraphState, this))
-    return true;
-#endif  // _SKIA_SUPPORT_
+  bool cached = m_pCache->SetClipStroke(pPathData, pObject2Device, pGraphState);
 
 #ifdef _SKIA_SUPPORT_PATHS_
   if (!m_pClipRgn) {
@@ -1418,16 +1501,16 @@
   SkPath dst_path;
   skPaint.getFillPath(skPath, &dst_path);
   dst_path.transform(skMatrix);
-  DebugDrawSkiaClipPath(m_pCanvas, dst_path);
-#ifdef _SKIA_SUPPORT_
-  m_pCanvas->clipPath(dst_path, SkCanvas::kIntersect_Op, true);
-#endif  // _SKIA_SUPPORT_
-
+  if (!cached) {
+    DebugDrawSkiaClipPath(m_pCanvas, dst_path);
+    m_pCanvas->clipPath(dst_path, SkCanvas::kIntersect_Op, true);
+  }
 #ifdef _SKIA_SUPPORT_PATHS_
   FX_RECT clipBox(0, 0, GetDeviceCaps(FXDC_PIXEL_WIDTH),
                   GetDeviceCaps(FXDC_PIXEL_HEIGHT));
   SetClipMask(clipBox, dst_path);
 #endif  // _SKIA_SUPPORT_PATHS_
+  DebugShowCanvasClip(m_pCanvas);
   return true;
 }
 
@@ -1441,15 +1524,10 @@
     int blend_type) {
   if (fill_mode & FX_ZEROAREA_FILL)
     return true;
-#ifdef _SKIA_SUPPORT_
   if (m_pCache->DrawPath(pPathData, pObject2Device, pGraphState, fill_color,
-                         stroke_color, fill_mode, blend_type, this)) {
+                         stroke_color, fill_mode, blend_type)) {
     return true;
   }
-#endif
-  SkIRect rect;
-  rect.set(0, 0, GetDeviceCaps(FXDC_PIXEL_WIDTH),
-           GetDeviceCaps(FXDC_PIXEL_HEIGHT));
   SkMatrix skMatrix;
   if (pObject2Device)
     skMatrix = ToSkMatrix(*pObject2Device);
@@ -1513,6 +1591,7 @@
 bool CFX_SkiaDeviceDriver::FillRectWithBlend(const FX_RECT* pRect,
                                              uint32_t fill_color,
                                              int blend_type) {
+  m_pCache->FlushForDraw();
   SkPaint spaint;
   spaint.setAntiAlias(true);
   spaint.setColor(fill_color);
@@ -1530,6 +1609,7 @@
                                        const FX_RECT& clip_rect,
                                        int alpha,
                                        bool bAlphaMode) {
+  m_pCache->FlushForDraw();
   ShadingType shadingType = pPattern->GetShadingType();
   if (kAxialShading != shadingType && kRadialShading != shadingType &&
       kCoonsPatchMeshShading != shadingType) {
@@ -1724,6 +1804,7 @@
   if (!srcBuffer)
     return true;
 #ifdef _SKIA_SUPPORT_
+  m_pCache->FlushForDraw();
   int srcWidth = m_pBitmap->GetWidth();
   int srcHeight = m_pBitmap->GetHeight();
   int srcRowBytes = srcWidth * sizeof(uint32_t);
@@ -1749,10 +1830,11 @@
 #endif  // _SKIA_SUPPORT_
 
 #ifdef _SKIA_SUPPORT_PATHS_
+  Flush();
   m_pBitmap->UnPreMultiply();
   FX_RECT rect(left, top, left + pBitmap->GetWidth(),
                top + pBitmap->GetHeight());
-  CFX_DIBitmap* pBack = nullptr;
+  std::unique_ptr<CFX_DIBitmap> pBack;
   if (m_pOriDevice) {
     pBack = m_pOriDevice->Clone(&rect);
     if (!pBack)
@@ -1771,12 +1853,11 @@
   top = std::min(top, 0);
   if (m_bRgbByteOrder) {
     RgbByteOrderTransferBitmap(pBitmap, 0, 0, rect.Width(), rect.Height(),
-                               pBack, left, top);
+                               pBack.get(), left, top);
   } else {
-    bRet = pBitmap->TransferBitmap(0, 0, rect.Width(), rect.Height(), pBack,
-                                   left, top);
+    bRet = pBitmap->TransferBitmap(0, 0, rect.Width(), rect.Height(),
+                                   pBack.get(), left, top);
   }
-  delete pBack;
   return bRet;
 #endif  // _SKIA_SUPPORT_PATHS_
 }
@@ -1802,6 +1883,7 @@
 #endif  // _SKIA_SUPPORT_
 
 #ifdef _SKIA_SUPPORT_PATHS_
+  Flush();
   if (pBitmap->IsAlphaMask()) {
     return m_pBitmap->CompositeMask(
         left, top, pSrcRect->Width(), pSrcRect->Height(), pBitmap, argb,
@@ -1824,6 +1906,7 @@
                                          uint32_t flags,
                                          int blend_type) {
 #ifdef _SKIA_SUPPORT_
+  m_pCache->FlushForDraw();
   if (!m_pBitmap->GetBuffer())
     return true;
   CFX_Matrix m(dest_width, 0, 0, -dest_height, dest_left,
@@ -1846,6 +1929,7 @@
     FX_RECT rect(0, 0, dest_width, dest_height);
     return SetDIBits(pSource, argb, &rect, dest_left, dest_top, blend_type);
   }
+  Flush();
   FX_RECT dest_rect(dest_left, dest_top, dest_left + dest_width,
                     dest_top + dest_height);
   dest_rect.Normalize();
@@ -1871,6 +1955,7 @@
                                        void*& handle,
                                        int blend_type) {
 #ifdef _SKIA_SUPPORT_
+  m_pCache->FlushForDraw();
   DebugValidate(m_pBitmap, m_pOriDevice);
   SkColorTable* ct = nullptr;
   std::unique_ptr<uint8_t, FxFreeDeleter> dst8Storage;
@@ -1912,6 +1997,7 @@
 #endif  // _SKIA_SUPPORT_
 
 #ifdef _SKIA_SUPPORT_PATHS_
+  Flush();
   if (!m_pBitmap->GetBuffer())
     return true;
   m_pBitmap->UnPreMultiply();
@@ -1925,10 +2011,12 @@
 
 bool CFX_SkiaDeviceDriver::ContinueDIBits(void* handle, IFX_Pause* pPause) {
 #ifdef _SKIA_SUPPORT_
+  m_pCache->FlushForDraw();
   return false;
 #endif  // _SKIA_SUPPORT_
 
 #ifdef _SKIA_SUPPORT_PATHS_
+  Flush();
   if (!m_pBitmap->GetBuffer()) {
     return true;
   }
diff --git a/core/fxge/skia/fx_skia_device.h b/core/fxge/skia/fx_skia_device.h
index 06b7be3..c83f991 100644
--- a/core/fxge/skia/fx_skia_device.h
+++ b/core/fxge/skia/fx_skia_device.h
@@ -152,8 +152,9 @@
                    const CFX_GraphStateData* pGraphState,
                    const SkMatrix& matrix);
   void Clear(uint32_t color);
-  void Flush();
+  void Flush() override;
   SkPictureRecorder* GetRecorder() const { return m_pRecorder; }
+  void PreMultiply() { m_pBitmap->PreMultiply(); }
   static void PreMultiply(CFX_DIBitmap* pDIBitmap);
   SkCanvas* SkiaCanvas() { return m_pCanvas; }
   void DebugVerifyBitmapIsPreMultiplied() const;
@@ -166,9 +167,7 @@
   CFX_DIBitmap* m_pOriDevice;
   SkCanvas* m_pCanvas;
   SkPictureRecorder* const m_pRecorder;
-#ifdef _SKIA_SUPPORT_
   std::unique_ptr<SkiaState> m_pCache;
-#endif
 #ifdef _SKIA_SUPPORT_PATHS_
   std::unique_ptr<CFX_ClipRgn> m_pClipRgn;
   std::vector<std::unique_ptr<CFX_ClipRgn>> m_StateStack;
@@ -177,6 +176,6 @@
 #endif
   bool m_bGroupKnockout;
 };
-#endif  // defined(_SKIA_SUPPORT_)
+#endif  // defined _SKIA_SUPPORT_ || defined _SKIA_SUPPORT_PATHS_
 
 #endif  // CORE_FXGE_SKIA_FX_SKIA_DEVICE_H_
diff --git a/fpdfsdk/fpdfformfill.cpp b/fpdfsdk/fpdfformfill.cpp
index 44204b7..6b8cbaa 100644
--- a/fpdfsdk/fpdfformfill.cpp
+++ b/fpdfsdk/fpdfformfill.cpp
@@ -137,10 +137,11 @@
     pPageView->PageView_OnDraw(pDevice.get(), &matrix, &options);
 #endif  // PDF_ENABLE_XFA
 
-#ifdef _SKIA_SUPPORT_PATHS
+  pDevice->RestoreState(false);
+#ifdef _SKIA_SUPPORT_PATHS_
+  pDevice->Flush();
   CFXBitmapFromFPDFBitmap(bitmap)->UnPreMultiply();
 #endif
-  pDevice->RestoreState(false);
   delete options.m_pOCContext;
   options.m_pOCContext = nullptr;
 }
diff --git a/fpdfsdk/fpdfview.cpp b/fpdfsdk/fpdfview.cpp
index f0c269a..fb87c83 100644
--- a/fpdfsdk/fpdfview.cpp
+++ b/fpdfsdk/fpdfview.cpp
@@ -720,6 +720,7 @@
                          rotate, flags, true, nullptr);
 
 #ifdef _SKIA_SUPPORT_PATHS_
+  pDevice->Flush();
   pBitmap->UnPreMultiply();
 #endif
   pPage->SetRenderContext(nullptr);