summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcaryclark <caryclark@google.com>2016-12-06 13:49:34 -0800
committerCommit bot <commit-bot@chromium.org>2016-12-06 13:49:34 -0800
commit8f875507a986d10335e40a5f7c1679aff9770d0a (patch)
tree7e466d2741ea5e36b35422d7aa03326a754afff4
parent6bdb56cad7f9e31183b90152324281ac52c4563d (diff)
downloadpdfium-chromium/2944.tar.xz
re-enable skia cachingchromium/2944
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
-rw-r--r--core/fpdfapi/cpdf_pagerendercontext.h2
-rw-r--r--core/fpdfapi/render/cpdf_renderstatus.cpp2
-rw-r--r--core/fxge/cfx_renderdevice.h2
-rw-r--r--core/fxge/ge/cfx_renderdevice.cpp11
-rw-r--r--core/fxge/ifx_renderdevicedriver.cpp4
-rw-r--r--core/fxge/ifx_renderdevicedriver.h3
-rw-r--r--core/fxge/skia/fx_skia_device.cpp452
-rw-r--r--core/fxge/skia/fx_skia_device.h7
-rw-r--r--fpdfsdk/fpdfformfill.cpp5
-rw-r--r--fpdfsdk/fpdfview.cpp1
10 files changed, 298 insertions, 191 deletions
diff --git a/core/fpdfapi/cpdf_pagerendercontext.h b/core/fpdfapi/cpdf_pagerendercontext.h
index 9ddd075778..27244e7fc3 100644
--- a/core/fpdfapi/cpdf_pagerendercontext.h
+++ b/core/fpdfapi/cpdf_pagerendercontext.h
@@ -21,10 +21,10 @@ class CPDF_PageRenderContext {
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 88dbb030ce..b23b98cdb1 100644
--- a/core/fpdfapi/render/cpdf_renderstatus.cpp
+++ b/core/fpdfapi/render/cpdf_renderstatus.cpp
@@ -898,6 +898,7 @@ std::unique_ptr<CFX_DIBitmap> DrawPatternBitmap(
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 @@ bool CPDF_RenderStatus::ProcessTransparency(CPDF_PageObject* pPageObj,
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 d38b2981ba..800d0c7c52 100644
--- a/core/fxge/cfx_renderdevice.h
+++ b/core/fxge/cfx_renderdevice.h
@@ -227,6 +227,8 @@ class CFX_RenderDevice {
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 64fa6ff452..9c67a7d1e7 100644
--- a/core/fxge/ge/cfx_renderdevice.cpp
+++ b/core/fxge/ge/cfx_renderdevice.cpp
@@ -354,9 +354,13 @@ CFX_RenderDevice::CFX_RenderDevice()
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 @@ bool CFX_RenderDevice::DrawFillStrokePath(const CFX_PathData* pPathData,
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 07a4c27277..77af00f430 100644
--- a/core/fxge/ifx_renderdevicedriver.cpp
+++ b/core/fxge/ifx_renderdevicedriver.cpp
@@ -99,3 +99,7 @@ bool IFX_RenderDeviceDriver::SetBitsWithMask(const CFX_DIBSource* pBitmap,
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 6a99276eba..6a5b63ba32 100644
--- a/core/fxge/ifx_renderdevicedriver.h
+++ b/core/fxge/ifx_renderdevicedriver.h
@@ -103,6 +103,9 @@ class IFX_RenderDeviceDriver {
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 84db838807..9c0ea07b3c 100644
--- a/core/fxge/skia/fx_skia_device.cpp
+++ b/core/fxge/skia/fx_skia_device.cpp
@@ -155,28 +155,37 @@ void RgbByteOrderTransferBitmap(CFX_DIBitmap* pBitmap,
#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
-void DebugShowSkiaPath(const SkPath& path) {
#if SHOW_SKIA_PATH
- char buffer[4096];
- sk_bzero(buffer, sizeof(buffer));
- SkMemoryWStream stream(buffer, sizeof(buffer));
- path.dump(&stream, false, false);
- printf("%s", buffer);
-#endif // 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) {
-#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
}
+#endif // SHOW_SKIA_PATH
+void DebugShowSkiaPath(const SkPath& path) {
#if SHOW_SKIA_PATH
+ char buffer[4096];
+ sk_bzero(buffer, sizeof(buffer));
+ SkMemoryWStream stream(buffer, sizeof(buffer));
+ path.dump(&stream, false, false);
+ printf("%s", buffer);
+#endif // SHOW_SKIA_PATH
+}
+
void DebugShowCanvasClip(const SkCanvas* canvas) {
+#if SHOW_SKIA_PATH
SkRect local;
SkIRect device;
canvas->getClipBounds(&local);
@@ -187,8 +196,10 @@ void DebugShowCanvasClip(const SkCanvas* canvas) {
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 @@ bool Upsample(const CFX_DIBSource* pSource,
} // 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 @@ class SkiaState {
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 @@ class SkiaState {
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);
- }
- if (!m_drawPath) {
+ 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 (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 @@ class SkiaState {
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 @@ class SkiaState {
}
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 @@ class SkiaState {
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 @@ class SkiaState {
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 @@ class SkiaState {
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();
+ }
+ 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);
}
- Flush(pDriver);
- m_commands.push(Clip::kPath);
++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 @@ class SkiaState {
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 @@ class SkiaState {
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 @@ class SkiaState {
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 @@ class SkiaState {
}
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 @@ class SkiaState {
}
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 @@ class SkiaState {
}
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 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 Dump(const CFX_SkiaDeviceDriver* pDriver) const {
+ 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
+ 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
- SkDebugf("\n\nSkia Save Count %d:\n", pDriver->m_pCanvas->getSaveCount());
- pDriver->m_pCanvas->getClipStack()->dump();
- SkDebugf("Cache:\n");
+ 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 @@ class SkiaState {
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 @@ CFX_SkiaDeviceDriver::CFX_SkiaDeviceDriver(CFX_DIBitmap* pBitmap,
: 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 @@ CFX_SkiaDeviceDriver::CFX_SkiaDeviceDriver(int size_x, int size_y)
: 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 @@ CFX_SkiaDeviceDriver::CFX_SkiaDeviceDriver(SkPictureRecorder* recorder)
: 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 @@ CFX_SkiaDeviceDriver::~CFX_SkiaDeviceDriver() {
}
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 @@ bool CFX_SkiaDeviceDriver::DrawDeviceText(int nChars,
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 @@ int CFX_SkiaDeviceDriver::GetDeviceCaps(int caps_id) const {
}
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::SaveState() {
}
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 @@ bool CFX_SkiaDeviceDriver::SetClip_PathFill(
) {
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 @@ bool CFX_SkiaDeviceDriver::SetClip_PathFill(
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 @@ bool CFX_SkiaDeviceDriver::SetClip_PathFill(
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 @@ bool CFX_SkiaDeviceDriver::SetClip_PathStroke(
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 @@ bool CFX_SkiaDeviceDriver::SetClip_PathStroke(
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 @@ bool CFX_SkiaDeviceDriver::DrawPath(
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::DrawCosmeticLine(FX_FLOAT x1,
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 @@ bool CFX_SkiaDeviceDriver::DrawShading(const CPDF_ShadingPattern* pPattern,
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 @@ bool CFX_SkiaDeviceDriver::GetDIBits(CFX_DIBitmap* pBitmap, int left, int top) {
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 @@ bool CFX_SkiaDeviceDriver::GetDIBits(CFX_DIBitmap* pBitmap, int left, int top) {
#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 @@ bool CFX_SkiaDeviceDriver::GetDIBits(CFX_DIBitmap* pBitmap, int left, int top) {
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 @@ bool CFX_SkiaDeviceDriver::SetDIBits(const CFX_DIBSource* pBitmap,
#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 @@ bool CFX_SkiaDeviceDriver::StretchDIBits(const CFX_DIBSource* pSource,
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 @@ bool CFX_SkiaDeviceDriver::StretchDIBits(const CFX_DIBSource* pSource,
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 @@ bool CFX_SkiaDeviceDriver::StartDIBits(const CFX_DIBSource* pSource,
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 @@ bool CFX_SkiaDeviceDriver::StartDIBits(const CFX_DIBSource* pSource,
#endif // _SKIA_SUPPORT_
#ifdef _SKIA_SUPPORT_PATHS_
+ Flush();
if (!m_pBitmap->GetBuffer())
return true;
m_pBitmap->UnPreMultiply();
@@ -1925,10 +2011,12 @@ bool CFX_SkiaDeviceDriver::StartDIBits(const CFX_DIBSource* pSource,
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 06b7be3d82..c83f9919e0 100644
--- a/core/fxge/skia/fx_skia_device.h
+++ b/core/fxge/skia/fx_skia_device.h
@@ -152,8 +152,9 @@ class CFX_SkiaDeviceDriver : public IFX_RenderDeviceDriver {
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 @@ class CFX_SkiaDeviceDriver : public IFX_RenderDeviceDriver {
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 @@ class CFX_SkiaDeviceDriver : public IFX_RenderDeviceDriver {
#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 44204b766a..6b8cbaa59d 100644
--- a/fpdfsdk/fpdfformfill.cpp
+++ b/fpdfsdk/fpdfformfill.cpp
@@ -137,10 +137,11 @@ void FFLCommon(FPDF_FORMHANDLE hHandle,
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 f0c269a312..fb87c838d4 100644
--- a/fpdfsdk/fpdfview.cpp
+++ b/fpdfsdk/fpdfview.cpp
@@ -720,6 +720,7 @@ DLLEXPORT void STDCALL FPDF_RenderPageBitmap(FPDF_BITMAP bitmap,
rotate, flags, true, nullptr);
#ifdef _SKIA_SUPPORT_PATHS_
+ pDevice->Flush();
pBitmap->UnPreMultiply();
#endif
pPage->SetRenderContext(nullptr);