Skip to content

Commit ecc56a6

Browse files
committed
Don't update the visible canvas at 60 fps (bug 1936605)
Instead, we update the visible canvas every 500ms. With large canvas, updating at 60fps lead to a lot gfx transactions and it can take a lot of time. For example, with wuppertal_2012.pdf on Windows, displaying it at 150% takes around 14 min !!! without this patch when it takes only around 14 sec with. Even at 30% it helps to improve the performance by around 20%.
1 parent 2e10ff6 commit ecc56a6

4 files changed

Lines changed: 70 additions & 3 deletions

File tree

web/app.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,15 +326,19 @@ const PDFViewerApplication = {
326326
}
327327
}
328328
if (params.has("pdfbug")) {
329-
AppOptions.setAll({ pdfBug: true, fontExtraProperties: true });
330-
331329
const enabled = params.get("pdfbug").split(",");
332330
try {
333331
await loadPDFBug();
334332
this._PDFBug.init(mainContainer, enabled);
335333
} catch (ex) {
336334
console.error("_parseHashParams:", ex);
337335
}
336+
337+
const debugOpts = { pdfBug: true, fontExtraProperties: true };
338+
if (globalThis.StepperManager?.enabled) {
339+
debugOpts.minDurationToUpdateCanvas = 0;
340+
}
341+
AppOptions.setAll(debugOpts);
338342
}
339343
// It is not possible to change locale for the (various) extension builds.
340344
if (
@@ -519,6 +523,7 @@ const PDFViewerApplication = {
519523
enableHWA,
520524
supportsPinchToZoom: this.supportsPinchToZoom,
521525
enableAutoLinking: AppOptions.get("enableAutoLinking"),
526+
minDurationToUpdateCanvas: AppOptions.get("minDurationToUpdateCanvas"),
522527
}));
523528

524529
renderingQueue.setViewer(pdfViewer);

web/app_options.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,11 @@ const defaultOptions = {
299299
value: 2 ** 25,
300300
kind: OptionKind.VIEWER,
301301
},
302+
minDurationToUpdateCanvas: {
303+
/** @type {number} */
304+
value: 500, // ms
305+
kind: OptionKind.VIEWER,
306+
},
302307
forcePageColors: {
303308
/** @type {boolean} */
304309
value: false,

web/base_pdf_page_view.js

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,18 @@ class BasePDFPageView {
2121

2222
#loadingId = null;
2323

24+
#minDurationToUpdateCanvas = 0;
25+
2426
#renderError = null;
2527

2628
#renderingState = RenderingStates.INITIAL;
2729

2830
#showCanvas = null;
2931

32+
#startTime = 0;
33+
34+
#tempCanvas = null;
35+
3036
canvas = null;
3137

3238
/** @type {null | HTMLDivElement} */
@@ -51,6 +57,7 @@ class BasePDFPageView {
5157
this.id = options.id;
5258
this.pageColors = options.pageColors || null;
5359
this.renderingQueue = options.renderingQueue;
60+
this.#minDurationToUpdateCanvas = options.minDurationToUpdateCanvas ?? 500;
5461
}
5562

5663
get renderingState() {
@@ -71,6 +78,9 @@ class BasePDFPageView {
7178
switch (state) {
7279
case RenderingStates.PAUSED:
7380
this.div.classList.remove("loading");
81+
// Display the canvas as it has been drawn.
82+
this.#startTime = 0;
83+
this.#showCanvas?.(false);
7484
break;
7585
case RenderingStates.RUNNING:
7686
this.div.classList.add("loadingIcon");
@@ -82,10 +92,12 @@ class BasePDFPageView {
8292
this.div.classList.add("loading");
8393
this.#loadingId = null;
8494
}, 0);
95+
this.#startTime = Date.now();
8596
break;
8697
case RenderingStates.INITIAL:
8798
case RenderingStates.FINISHED:
8899
this.div.classList.remove("loadingIcon", "loading");
100+
this.#startTime = 0;
89101
break;
90102
}
91103
}
@@ -100,10 +112,41 @@ class BasePDFPageView {
100112
// have a final flash we just display it once all the drawing is done.
101113
const updateOnFirstShow = !prevCanvas && !hasHCM && !hideUntilComplete;
102114

103-
const canvas = (this.canvas = document.createElement("canvas"));
115+
let canvas = (this.canvas = document.createElement("canvas"));
104116

105117
this.#showCanvas = isLastShow => {
106118
if (updateOnFirstShow) {
119+
let tempCanvas = this.#tempCanvas;
120+
if (!isLastShow && this.#minDurationToUpdateCanvas > 0) {
121+
// We draw on the canvas at 60fps (in using `requestAnimationFrame`),
122+
// so if the canvas is large, updating it at 60fps can be a way too
123+
// much and can cause some serious performance issues.
124+
// To avoid that we only update the canvas every
125+
// `this.#minDurationToUpdateCanvas` ms.
126+
127+
if (Date.now() - this.#startTime < this.#minDurationToUpdateCanvas) {
128+
return;
129+
}
130+
if (!tempCanvas) {
131+
tempCanvas = this.#tempCanvas = canvas;
132+
canvas = this.canvas = canvas.cloneNode(false);
133+
onShow(canvas);
134+
}
135+
}
136+
137+
if (tempCanvas) {
138+
const ctx = canvas.getContext("2d", {
139+
alpha: false,
140+
});
141+
ctx.drawImage(tempCanvas, 0, 0);
142+
if (isLastShow) {
143+
this.#resetTempCanvas();
144+
} else {
145+
this.#startTime = Date.now();
146+
}
147+
return;
148+
}
149+
107150
// Don't add the canvas until the first draw callback, or until
108151
// drawing is complete when `!this.renderingQueue`, to prevent black
109152
// flickering.
@@ -152,6 +195,14 @@ class BasePDFPageView {
152195
canvas.remove();
153196
canvas.width = canvas.height = 0;
154197
this.canvas = null;
198+
this.#resetTempCanvas();
199+
}
200+
201+
#resetTempCanvas() {
202+
if (this.#tempCanvas) {
203+
this.#tempCanvas.width = this.#tempCanvas.height = 0;
204+
this.#tempCanvas = null;
205+
}
155206
}
156207

157208
async _drawCanvas(options, onCancel, onFinish) {

web/pdf_viewer.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ function isValidAnnotationEditorMode(mode) {
139139
* The default value is `true`.
140140
* @property {boolean} [enableAutoLinking] - Enable creation of hyperlinks from
141141
* text that look like URLs. The default value is `true`.
142+
* @property {number} [minDurationToUpdateCanvas] - Minimum duration to wait
143+
* before updating the canvas. The default value is `500`.
142144
*/
143145

144146
class PDFPageViewBuffer {
@@ -243,6 +245,8 @@ class PDFViewer {
243245

244246
#eventAbortController = null;
245247

248+
#minDurationToUpdateCanvas = 0;
249+
246250
#mlManager = null;
247251

248252
#scrollTimeoutId = null;
@@ -342,6 +346,7 @@ class PDFViewer {
342346
this.#enableHWA = options.enableHWA || false;
343347
this.#supportsPinchToZoom = options.supportsPinchToZoom !== false;
344348
this.#enableAutoLinking = options.enableAutoLinking !== false;
349+
this.#minDurationToUpdateCanvas = options.minDurationToUpdateCanvas ?? 500;
345350

346351
this.defaultRenderingQueue = !options.renderingQueue;
347352
if (
@@ -1003,6 +1008,7 @@ class PDFViewer {
10031008
layerProperties: this._layerProperties,
10041009
enableHWA: this.#enableHWA,
10051010
enableAutoLinking: this.#enableAutoLinking,
1011+
minDurationToUpdateCanvas: this.#minDurationToUpdateCanvas,
10061012
});
10071013
this._pages.push(pageView);
10081014
}

0 commit comments

Comments
 (0)