From 0788e70afe12165f0db1a9bf00cba0d91c029a28 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Wed, 12 Jun 2024 12:51:51 +0200 Subject: [PATCH] Add an option to enable/disable hardware acceleration (bug 1902012) --- extensions/chromium/preferences_schema.json | 5 +++++ src/core/default_appearance.js | 2 +- src/display/api.js | 5 ++++- src/display/base_factory.js | 9 +++++++-- src/display/display_utils.js | 4 ++-- src/display/editor/tools.js | 2 +- src/display/node_utils.js | 4 ++++ src/display/text_layer.js | 5 ++++- web/app.js | 3 +++ web/app_options.js | 5 +++++ web/pdf_page_view.js | 10 +++++++++- web/pdf_thumbnail_view.js | 13 ++++++++++--- web/pdf_thumbnail_viewer.js | 5 +++++ web/pdf_viewer.js | 6 ++++++ 14 files changed, 66 insertions(+), 12 deletions(-) diff --git a/extensions/chromium/preferences_schema.json b/extensions/chromium/preferences_schema.json index 19bb7d7c642eed..d62821f3cf88c9 100644 --- a/extensions/chromium/preferences_schema.json +++ b/extensions/chromium/preferences_schema.json @@ -45,6 +45,11 @@ "type": "boolean", "default": false }, + "enableHWA": { + "description": "Whether to enable hardware acceleration.", + "type": "boolean", + "default": false + }, "enableML": { "type": "boolean", "default": false diff --git a/src/core/default_appearance.js b/src/core/default_appearance.js index cb84b1a7e7ac21..740a037c645db5 100644 --- a/src/core/default_appearance.js +++ b/src/core/default_appearance.js @@ -242,7 +242,7 @@ class FakeUnicodeFont { this.fontFamily = fontFamily; const canvas = new OffscreenCanvas(1, 1); - this.ctxMeasure = canvas.getContext("2d"); + this.ctxMeasure = canvas.getContext("2d", { willReadFrequently: true }); if (!FakeUnicodeFont._fontNameId) { FakeUnicodeFont._fontNameId = 1; diff --git a/src/display/api.js b/src/display/api.js index fd7ba98cc9f813..f872300e55e24a 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -213,6 +213,8 @@ const DefaultStandardFontDataFactory = * when creating canvases. The default value is {new DOMCanvasFactory()}. * @property {Object} [filterFactory] - A factory instance that will be used * 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`. */ /** @@ -314,8 +316,9 @@ function getDocument(src) { standardFontDataUrl && isValidFetchUrl(cMapUrl, document.baseURI) && isValidFetchUrl(standardFontDataUrl, document.baseURI)); + const enableHWA = src.enableHWA === true; const canvasFactory = - src.canvasFactory || new DefaultCanvasFactory({ ownerDocument }); + src.canvasFactory || new DefaultCanvasFactory({ ownerDocument, enableHWA }); const filterFactory = src.filterFactory || new DefaultFilterFactory({ docId, ownerDocument }); diff --git a/src/display/base_factory.js b/src/display/base_factory.js index 09280cf903e3ad..9d806b051e5320 100644 --- a/src/display/base_factory.js +++ b/src/display/base_factory.js @@ -46,10 +46,13 @@ class BaseFilterFactory { } class BaseCanvasFactory { - constructor() { + #enableHWA = false; + + constructor({ enableHWA = false }) { if (this.constructor === BaseCanvasFactory) { unreachable("Cannot initialize BaseCanvasFactory."); } + this.#enableHWA = enableHWA; } create(width, height) { @@ -59,7 +62,9 @@ class BaseCanvasFactory { const canvas = this._createCanvas(width, height); return { canvas, - context: canvas.getContext("2d"), + context: canvas.getContext("2d", { + willReadFrequently: !this.#enableHWA, + }), }; } diff --git a/src/display/display_utils.js b/src/display/display_utils.js index adf9cbdea9d729..459df1f7c6b550 100644 --- a/src/display/display_utils.js +++ b/src/display/display_utils.js @@ -477,8 +477,8 @@ class DOMFilterFactory extends BaseFilterFactory { } class DOMCanvasFactory extends BaseCanvasFactory { - constructor({ ownerDocument = globalThis.document } = {}) { - super(); + constructor({ ownerDocument = globalThis.document, enableHWA = false } = {}) { + super({ enableHWA }); this._document = ownerDocument; } diff --git a/src/display/editor/tools.js b/src/display/editor/tools.js index dd277b7c3f523c..14444cf9461fa4 100644 --- a/src/display/editor/tools.js +++ b/src/display/editor/tools.js @@ -98,7 +98,7 @@ class ImageManager { // behavior in Safari. const svg = `data:image/svg+xml;charset=UTF-8,`; const canvas = new OffscreenCanvas(1, 3); - const ctx = canvas.getContext("2d"); + const ctx = canvas.getContext("2d", { willReadFrequently: true }); const image = new Image(); image.src = svg; const promise = image.decode().then(() => { diff --git a/src/display/node_utils.js b/src/display/node_utils.js index aca4ad750fad7d..941969ca3c95a9 100644 --- a/src/display/node_utils.js +++ b/src/display/node_utils.js @@ -117,6 +117,10 @@ const fetchData = function (url) { class NodeFilterFactory extends BaseFilterFactory {} class NodeCanvasFactory extends BaseCanvasFactory { + constructor(options = {}) { + super(options); + } + /** * @ignore */ diff --git a/src/display/text_layer.js b/src/display/text_layer.js index 1ff8ac26b5e6ef..df35dfb4e5b739 100644 --- a/src/display/text_layer.js +++ b/src/display/text_layer.js @@ -447,7 +447,10 @@ class TextLayer { canvas.className = "hiddenCanvasElement"; canvas.lang = lang; document.body.append(canvas); - canvasContext = canvas.getContext("2d", { alpha: false }); + canvasContext = canvas.getContext("2d", { + alpha: false, + willReadFrequently: true, + }); this.#canvasContexts.set(lang, canvasContext); } return canvasContext; diff --git a/web/app.js b/web/app.js index d75800ae38766f..16a81300ed7aee 100644 --- a/web/app.js +++ b/web/app.js @@ -439,6 +439,7 @@ const PDFViewerApplication = { ) : null; + const enableHWA = AppOptions.get("enableHWA"); const pdfViewer = new PDFViewer({ container, viewer, @@ -465,6 +466,7 @@ const PDFViewerApplication = { pageColors, mlManager: this.mlManager, abortSignal: this._globalAbortController.signal, + enableHWA, }); this.pdfViewer = pdfViewer; @@ -480,6 +482,7 @@ const PDFViewerApplication = { linkService: pdfLinkService, pageColors, abortSignal: this._globalAbortController.signal, + enableHWA, }); pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer); } diff --git a/web/app_options.js b/web/app_options.js index 61311172cf5cba..417729754130b6 100644 --- a/web/app_options.js +++ b/web/app_options.js @@ -310,6 +310,11 @@ const defaultOptions = { value: "", kind: OptionKind.API, }, + enableHWA: { + /** @type {boolean} */ + value: false, + kind: OptionKind.API + OptionKind.PREFERENCE + OptionKind.VIEWER, + }, enableXfa: { /** @type {boolean} */ value: true, diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index b40f2dcc507c54..0e110523c85a07 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -81,6 +81,8 @@ import { XfaLayerBuilder } from "./xfa_layer_builder.js"; * @property {IL10n} [l10n] - Localization service. * @property {Object} [layerProperties] - The object that is used to lookup * the necessary layer-properties. + * @property {boolean} [enableHWA] - Enables hardware acceleration for + * rendering. The default value is `false`. */ const DEFAULT_LAYER_PROPERTIES = @@ -113,6 +115,8 @@ const LAYERS_ORDER = new Map([ class PDFPageView { #annotationMode = AnnotationMode.ENABLE_FORMS; + #enableHWA = false; + #hasRestrictedScaling = false; #layerProperties = null; @@ -163,6 +167,7 @@ class PDFPageView { this.maxCanvasPixels = options.maxCanvasPixels ?? AppOptions.get("maxCanvasPixels"); this.pageColors = options.pageColors || null; + this.#enableHWA = !!options.enableHWA; this.eventBus = options.eventBus; this.renderingQueue = options.renderingQueue; @@ -981,7 +986,10 @@ class PDFPageView { canvasWrapper.append(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()); if ( diff --git a/web/pdf_thumbnail_view.js b/web/pdf_thumbnail_view.js index 7355067e474d30..8be3b62ad250f0 100644 --- a/web/pdf_thumbnail_view.js +++ b/web/pdf_thumbnail_view.js @@ -44,6 +44,8 @@ const THUMBNAIL_WIDTH = 98; // px * @property {Object} [pageColors] - Overwrites background and foreground colors * with user defined ones in order to improve readability in high contrast * mode. + * @property {boolean} [enableHWA] - Enables hardware acceleration for + * rendering. The default value is `false`. */ class TempImageFactory { @@ -92,6 +94,7 @@ class PDFThumbnailView { linkService, renderingQueue, pageColors, + enableHWA, }) { this.id = id; this.renderingId = "thumbnail" + id; @@ -103,6 +106,7 @@ class PDFThumbnailView { this.pdfPageRotate = defaultViewport.rotation; this._optionalContentConfigPromise = optionalContentConfigPromise || null; this.pageColors = pageColors || null; + this.enableHWA = enableHWA || false; this.eventBus = eventBus; this.linkService = linkService; @@ -196,11 +200,14 @@ class PDFThumbnailView { this.resume = null; } - #getPageDrawContext(upscaleFactor = 1) { + #getPageDrawContext(upscaleFactor = 1, enableHWA = this.enableHWA) { // Keep the no-thumbnail outline visible, i.e. `data-loaded === false`, // until rendering/image conversion is complete, to avoid display issues. 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(); canvas.width = (upscaleFactor * this.canvasWidth * outputScale.sx) | 0; @@ -340,7 +347,7 @@ class PDFThumbnailView { } #reduceImage(img) { - const { ctx, canvas } = this.#getPageDrawContext(); + const { ctx, canvas } = this.#getPageDrawContext(1, true); if (img.width <= 2 * canvas.width) { ctx.drawImage( diff --git a/web/pdf_thumbnail_viewer.js b/web/pdf_thumbnail_viewer.js index f15c85b5fc6213..7ec1b98376fcd8 100644 --- a/web/pdf_thumbnail_viewer.js +++ b/web/pdf_thumbnail_viewer.js @@ -44,6 +44,8 @@ const THUMBNAIL_SELECTED_CLASS = "selected"; * mode. * @property {AbortSignal} [abortSignal] - The AbortSignal for the window * events. + * @property {boolean} [enableHWA] - Enables hardware acceleration for + * rendering. The default value is `false`. */ /** @@ -60,12 +62,14 @@ class PDFThumbnailViewer { renderingQueue, pageColors, abortSignal, + enableHWA, }) { this.container = container; this.eventBus = eventBus; this.linkService = linkService; this.renderingQueue = renderingQueue; this.pageColors = pageColors || null; + this.enableHWA = enableHWA || false; this.scroll = watchScroll( this.container, @@ -206,6 +210,7 @@ class PDFThumbnailViewer { linkService: this.linkService, renderingQueue: this.renderingQueue, pageColors: this.pageColors, + enableHWA: this.enableHWA, }); this._thumbnails.push(thumbnail); } diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 3d373c804bb339..187835508979c8 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -123,6 +123,8 @@ function isValidAnnotationEditorMode(mode) { * @property {Object} [pageColors] - Overwrites background and foreground colors * with user defined ones in order to improve readability in high contrast * mode. + * @property {boolean} [enableHWA] - Enables hardware acceleration for + * rendering. The default value is `false`. */ class PDFPageViewBuffer { @@ -211,6 +213,8 @@ class PDFViewer { #containerTopLeft = null; + #enableHWA = false; + #enableHighlightFloatingButton = false; #enablePermissions = false; @@ -296,6 +300,7 @@ class PDFViewer { this.#enablePermissions = options.enablePermissions || false; this.pageColors = options.pageColors || null; this.#mlManager = options.mlManager || null; + this.#enableHWA = options.enableHWA || false; this.defaultRenderingQueue = !options.renderingQueue; if ( @@ -943,6 +948,7 @@ class PDFViewer { pageColors, l10n: this.l10n, layerProperties: this._layerProperties, + enableHWA: this.#enableHWA, }); this._pages.push(pageView); }