Add an option to enable/disable hardware acceleration (bug 1902012)

This commit is contained in:
Calixte Denizet 2024-06-12 12:51:51 +02:00
parent 341ff40e74
commit ff6180a4c9
13 changed files with 62 additions and 12 deletions

View file

@ -45,6 +45,11 @@
"type": "boolean", "type": "boolean",
"default": false "default": false
}, },
"enableHWA": {
"description": "Whether to enable hardware acceleration.",
"type": "boolean",
"default": false
},
"enableML": { "enableML": {
"type": "boolean", "type": "boolean",
"default": false "default": false

View file

@ -242,7 +242,7 @@ class FakeUnicodeFont {
this.fontFamily = fontFamily; this.fontFamily = fontFamily;
const canvas = new OffscreenCanvas(1, 1); const canvas = new OffscreenCanvas(1, 1);
this.ctxMeasure = canvas.getContext("2d"); this.ctxMeasure = canvas.getContext("2d", { willReadFrequently: true });
if (!FakeUnicodeFont._fontNameId) { if (!FakeUnicodeFont._fontNameId) {
FakeUnicodeFont._fontNameId = 1; FakeUnicodeFont._fontNameId = 1;

View file

@ -213,6 +213,8 @@ const DefaultStandardFontDataFactory =
* when creating canvases. The default value is {new DOMCanvasFactory()}. * when creating canvases. The default value is {new DOMCanvasFactory()}.
* @property {Object} [filterFactory] - A factory instance that will be used * @property {Object} [filterFactory] - A factory instance that will be used
* to create SVG filters when rendering some images on the main canvas. * to create SVG filters when rendering some images on the main canvas.
* @property {boolean} [enableHWA] - Enables hardware acceleration for
* rendering. The default value is `false`.
*/ */
/** /**
@ -297,6 +299,7 @@ function getDocument(src) {
const disableStream = src.disableStream === true; const disableStream = src.disableStream === true;
const disableAutoFetch = src.disableAutoFetch === true; const disableAutoFetch = src.disableAutoFetch === true;
const pdfBug = src.pdfBug === true; const pdfBug = src.pdfBug === true;
const enableHWA = src.enableHWA === true;
// Parameters whose default values depend on other parameters. // Parameters whose default values depend on other parameters.
const length = rangeTransport ? rangeTransport.length : src.length ?? NaN; const length = rangeTransport ? rangeTransport.length : src.length ?? NaN;
@ -315,7 +318,7 @@ function getDocument(src) {
isValidFetchUrl(cMapUrl, document.baseURI) && isValidFetchUrl(cMapUrl, document.baseURI) &&
isValidFetchUrl(standardFontDataUrl, document.baseURI)); isValidFetchUrl(standardFontDataUrl, document.baseURI));
const canvasFactory = const canvasFactory =
src.canvasFactory || new DefaultCanvasFactory({ ownerDocument }); src.canvasFactory || new DefaultCanvasFactory({ ownerDocument, enableHWA });
const filterFactory = const filterFactory =
src.filterFactory || new DefaultFilterFactory({ docId, ownerDocument }); src.filterFactory || new DefaultFilterFactory({ docId, ownerDocument });

View file

@ -46,10 +46,13 @@ class BaseFilterFactory {
} }
class BaseCanvasFactory { class BaseCanvasFactory {
constructor() { #enableHWA = false;
constructor({ enableHWA = false } = {}) {
if (this.constructor === BaseCanvasFactory) { if (this.constructor === BaseCanvasFactory) {
unreachable("Cannot initialize BaseCanvasFactory."); unreachable("Cannot initialize BaseCanvasFactory.");
} }
this.#enableHWA = enableHWA;
} }
create(width, height) { create(width, height) {
@ -59,7 +62,9 @@ class BaseCanvasFactory {
const canvas = this._createCanvas(width, height); const canvas = this._createCanvas(width, height);
return { return {
canvas, canvas,
context: canvas.getContext("2d"), context: canvas.getContext("2d", {
willReadFrequently: !this.#enableHWA,
}),
}; };
} }

View file

@ -477,8 +477,8 @@ class DOMFilterFactory extends BaseFilterFactory {
} }
class DOMCanvasFactory extends BaseCanvasFactory { class DOMCanvasFactory extends BaseCanvasFactory {
constructor({ ownerDocument = globalThis.document } = {}) { constructor({ ownerDocument = globalThis.document, enableHWA = false } = {}) {
super(); super({ enableHWA });
this._document = ownerDocument; this._document = ownerDocument;
} }

View file

@ -98,7 +98,7 @@ class ImageManager {
// behavior in Safari. // behavior in Safari.
const svg = `data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 1 1" width="1" height="1" xmlns="http://www.w3.org/2000/svg"><rect width="1" height="1" style="fill:red;"/></svg>`; const svg = `data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 1 1" width="1" height="1" xmlns="http://www.w3.org/2000/svg"><rect width="1" height="1" style="fill:red;"/></svg>`;
const canvas = new OffscreenCanvas(1, 3); const canvas = new OffscreenCanvas(1, 3);
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d", { willReadFrequently: true });
const image = new Image(); const image = new Image();
image.src = svg; image.src = svg;
const promise = image.decode().then(() => { const promise = image.decode().then(() => {

View file

@ -447,7 +447,10 @@ class TextLayer {
canvas.className = "hiddenCanvasElement"; canvas.className = "hiddenCanvasElement";
canvas.lang = lang; canvas.lang = lang;
document.body.append(canvas); document.body.append(canvas);
canvasContext = canvas.getContext("2d", { alpha: false }); canvasContext = canvas.getContext("2d", {
alpha: false,
willReadFrequently: true,
});
this.#canvasContexts.set(lang, canvasContext); this.#canvasContexts.set(lang, canvasContext);
} }
return canvasContext; return canvasContext;

View file

@ -439,6 +439,7 @@ const PDFViewerApplication = {
) )
: null; : null;
const enableHWA = AppOptions.get("enableHWA");
const pdfViewer = new PDFViewer({ const pdfViewer = new PDFViewer({
container, container,
viewer, viewer,
@ -465,6 +466,7 @@ const PDFViewerApplication = {
pageColors, pageColors,
mlManager: this.mlManager, mlManager: this.mlManager,
abortSignal: this._globalAbortController.signal, abortSignal: this._globalAbortController.signal,
enableHWA,
}); });
this.pdfViewer = pdfViewer; this.pdfViewer = pdfViewer;
@ -480,6 +482,7 @@ const PDFViewerApplication = {
linkService: pdfLinkService, linkService: pdfLinkService,
pageColors, pageColors,
abortSignal: this._globalAbortController.signal, abortSignal: this._globalAbortController.signal,
enableHWA,
}); });
pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer); pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
} }

View file

@ -310,6 +310,11 @@ const defaultOptions = {
value: "", value: "",
kind: OptionKind.API, kind: OptionKind.API,
}, },
enableHWA: {
/** @type {boolean} */
value: false,
kind: OptionKind.API + OptionKind.VIEWER + OptionKind.PREFERENCE,
},
enableXfa: { enableXfa: {
/** @type {boolean} */ /** @type {boolean} */
value: true, value: true,

View file

@ -81,6 +81,8 @@ import { XfaLayerBuilder } from "./xfa_layer_builder.js";
* @property {IL10n} [l10n] - Localization service. * @property {IL10n} [l10n] - Localization service.
* @property {Object} [layerProperties] - The object that is used to lookup * @property {Object} [layerProperties] - The object that is used to lookup
* the necessary layer-properties. * the necessary layer-properties.
* @property {boolean} [enableHWA] - Enables hardware acceleration for
* rendering. The default value is `false`.
*/ */
const DEFAULT_LAYER_PROPERTIES = const DEFAULT_LAYER_PROPERTIES =
@ -113,6 +115,8 @@ const LAYERS_ORDER = new Map([
class PDFPageView { class PDFPageView {
#annotationMode = AnnotationMode.ENABLE_FORMS; #annotationMode = AnnotationMode.ENABLE_FORMS;
#enableHWA = false;
#hasRestrictedScaling = false; #hasRestrictedScaling = false;
#layerProperties = null; #layerProperties = null;
@ -163,6 +167,7 @@ class PDFPageView {
this.maxCanvasPixels = this.maxCanvasPixels =
options.maxCanvasPixels ?? AppOptions.get("maxCanvasPixels"); options.maxCanvasPixels ?? AppOptions.get("maxCanvasPixels");
this.pageColors = options.pageColors || null; this.pageColors = options.pageColors || null;
this.#enableHWA = options.enableHWA || false;
this.eventBus = options.eventBus; this.eventBus = options.eventBus;
this.renderingQueue = options.renderingQueue; this.renderingQueue = options.renderingQueue;
@ -981,7 +986,10 @@ class PDFPageView {
canvasWrapper.append(canvas); canvasWrapper.append(canvas);
this.canvas = canvas; this.canvas = canvas;
const ctx = canvas.getContext("2d", { alpha: false }); const ctx = canvas.getContext("2d", {
alpha: false,
willReadFrequently: !this.#enableHWA,
});
const outputScale = (this.outputScale = new OutputScale()); const outputScale = (this.outputScale = new OutputScale());
if ( if (

View file

@ -44,6 +44,8 @@ const THUMBNAIL_WIDTH = 98; // px
* @property {Object} [pageColors] - Overwrites background and foreground colors * @property {Object} [pageColors] - Overwrites background and foreground colors
* with user defined ones in order to improve readability in high contrast * with user defined ones in order to improve readability in high contrast
* mode. * mode.
* @property {boolean} [enableHWA] - Enables hardware acceleration for
* rendering. The default value is `false`.
*/ */
class TempImageFactory { class TempImageFactory {
@ -92,6 +94,7 @@ class PDFThumbnailView {
linkService, linkService,
renderingQueue, renderingQueue,
pageColors, pageColors,
enableHWA,
}) { }) {
this.id = id; this.id = id;
this.renderingId = "thumbnail" + id; this.renderingId = "thumbnail" + id;
@ -103,6 +106,7 @@ class PDFThumbnailView {
this.pdfPageRotate = defaultViewport.rotation; this.pdfPageRotate = defaultViewport.rotation;
this._optionalContentConfigPromise = optionalContentConfigPromise || null; this._optionalContentConfigPromise = optionalContentConfigPromise || null;
this.pageColors = pageColors || null; this.pageColors = pageColors || null;
this.enableHWA = enableHWA || false;
this.eventBus = eventBus; this.eventBus = eventBus;
this.linkService = linkService; this.linkService = linkService;
@ -196,11 +200,14 @@ class PDFThumbnailView {
this.resume = null; this.resume = null;
} }
#getPageDrawContext(upscaleFactor = 1) { #getPageDrawContext(upscaleFactor = 1, enableHWA = this.enableHWA) {
// Keep the no-thumbnail outline visible, i.e. `data-loaded === false`, // Keep the no-thumbnail outline visible, i.e. `data-loaded === false`,
// until rendering/image conversion is complete, to avoid display issues. // until rendering/image conversion is complete, to avoid display issues.
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d", { alpha: false }); const ctx = canvas.getContext("2d", {
alpha: false,
willReadFrequently: !enableHWA,
});
const outputScale = new OutputScale(); const outputScale = new OutputScale();
canvas.width = (upscaleFactor * this.canvasWidth * outputScale.sx) | 0; canvas.width = (upscaleFactor * this.canvasWidth * outputScale.sx) | 0;
@ -340,7 +347,7 @@ class PDFThumbnailView {
} }
#reduceImage(img) { #reduceImage(img) {
const { ctx, canvas } = this.#getPageDrawContext(); const { ctx, canvas } = this.#getPageDrawContext(1, true);
if (img.width <= 2 * canvas.width) { if (img.width <= 2 * canvas.width) {
ctx.drawImage( ctx.drawImage(

View file

@ -44,6 +44,8 @@ const THUMBNAIL_SELECTED_CLASS = "selected";
* mode. * mode.
* @property {AbortSignal} [abortSignal] - The AbortSignal for the window * @property {AbortSignal} [abortSignal] - The AbortSignal for the window
* events. * events.
* @property {boolean} [enableHWA] - Enables hardware acceleration for
* rendering. The default value is `false`.
*/ */
/** /**
@ -60,12 +62,14 @@ class PDFThumbnailViewer {
renderingQueue, renderingQueue,
pageColors, pageColors,
abortSignal, abortSignal,
enableHWA,
}) { }) {
this.container = container; this.container = container;
this.eventBus = eventBus; this.eventBus = eventBus;
this.linkService = linkService; this.linkService = linkService;
this.renderingQueue = renderingQueue; this.renderingQueue = renderingQueue;
this.pageColors = pageColors || null; this.pageColors = pageColors || null;
this.enableHWA = enableHWA || false;
this.scroll = watchScroll( this.scroll = watchScroll(
this.container, this.container,
@ -206,6 +210,7 @@ class PDFThumbnailViewer {
linkService: this.linkService, linkService: this.linkService,
renderingQueue: this.renderingQueue, renderingQueue: this.renderingQueue,
pageColors: this.pageColors, pageColors: this.pageColors,
enableHWA: this.enableHWA,
}); });
this._thumbnails.push(thumbnail); this._thumbnails.push(thumbnail);
} }

View file

@ -123,6 +123,8 @@ function isValidAnnotationEditorMode(mode) {
* @property {Object} [pageColors] - Overwrites background and foreground colors * @property {Object} [pageColors] - Overwrites background and foreground colors
* with user defined ones in order to improve readability in high contrast * with user defined ones in order to improve readability in high contrast
* mode. * mode.
* @property {boolean} [enableHWA] - Enables hardware acceleration for
* rendering. The default value is `false`.
*/ */
class PDFPageViewBuffer { class PDFPageViewBuffer {
@ -211,6 +213,8 @@ class PDFViewer {
#containerTopLeft = null; #containerTopLeft = null;
#enableHWA = false;
#enableHighlightFloatingButton = false; #enableHighlightFloatingButton = false;
#enablePermissions = false; #enablePermissions = false;
@ -296,6 +300,7 @@ class PDFViewer {
this.#enablePermissions = options.enablePermissions || false; this.#enablePermissions = options.enablePermissions || false;
this.pageColors = options.pageColors || null; this.pageColors = options.pageColors || null;
this.#mlManager = options.mlManager || null; this.#mlManager = options.mlManager || null;
this.#enableHWA = options.enableHWA || false;
this.defaultRenderingQueue = !options.renderingQueue; this.defaultRenderingQueue = !options.renderingQueue;
if ( if (
@ -943,6 +948,7 @@ class PDFViewer {
pageColors, pageColors,
l10n: this.l10n, l10n: this.l10n,
layerProperties: this._layerProperties, layerProperties: this._layerProperties,
enableHWA: this.#enableHWA,
}); });
this._pages.push(pageView); this._pages.push(pageView);
} }