feat(desktop): add option to disable GPU hardware acceleration

Adds a 'Hardware acceleration (GPU)' toggle in Appearance > Performance
settings so users hitting GPU driver incompatibilities (blank window,
display glitches) can turn it off without manually passing --disable-gpu.

The option is read before Electron's ready event straight from the DB
provider (core options aren't wired up that early, cf. #10559) and calls
app.disableHardwareAcceleration(). Local-only (not synced) since GPU
compatibility is per-machine; requires an app restart.

Closes #10572

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Elian Doran
2026-07-25 10:59:29 +03:00
co-authored by Claude Opus 4.8
parent eacdeea3ed
commit fa41709d63
7 changed files with 54 additions and 0 deletions
@@ -1579,6 +1579,8 @@
"enable-shadows": "Shadows",
"enable-backdrop-effects": "Background effects for menus, popups and panels",
"enable-smooth-scroll": "Smooth scrolling",
"enable-hardware-acceleration": "Hardware acceleration (GPU)",
"enable-hardware-acceleration-description": "Disable this if the app shows a blank window or has display glitches caused by your GPU drivers. Requires an app restart.",
"app-restart-required": "Requires app restart"
},
"zoom_factor": {
@@ -658,6 +658,8 @@ function Performance() {
{isElectron() && <SmoothScrollEnabledOption />}
{isElectron() && <HardwareAccelerationOption />}
</OptionsSection>;
}
@@ -673,6 +675,18 @@ function SmoothScrollEnabledOption() {
/>;
}
function HardwareAccelerationOption() {
const [ hardwareAccelerationEnabled, setHardwareAccelerationEnabled ] = useTriliumOptionBool("hardwareAccelerationEnabled");
return <OptionsRowWithToggle
name="hardware-acceleration-enabled"
label={t("ui-performance.enable-hardware-acceleration")}
description={t("ui-performance.enable-hardware-acceleration-description")}
currentValue={hardwareAccelerationEnabled}
onChange={setHardwareAccelerationEnabled}
/>;
}
function MaxContentWidth() {
const [maxContentWidth, setMaxContentWidth] = useTriliumOption("maxContentWidth");
const [centerContent, setCenterContent] = useTriliumOptionBool("centerContent");
+28
View File
@@ -53,6 +53,8 @@ const h = vi.hoisted(() => ({
isPrimaryInstance: true as boolean,
allWindows: [] as unknown[],
smoothScroll: "true" as string | null,
hardwareAcceleration: "true" as string | null,
disableHardwareAcceleration: vi.fn(),
// When true, reading an option throws (simulates first run before the schema exists).
dbUninitialized: false as boolean,
isDbInitialized: true as boolean,
@@ -91,6 +93,7 @@ vi.mock("electron", () => {
setName: (...a: unknown[]) => h.setName(...a),
setUserTasks: (...a: unknown[]) => h.setUserTasks(...a),
commandLine: { appendSwitch: (...a: unknown[]) => h.appendSwitch(...a) },
disableHardwareAcceleration: (...a: unknown[]) => h.disableHardwareAcceleration(...a),
on: (event: string, cb: Handler) => h.appOn.set(event, cb),
quit: (...a: unknown[]) => h.quit(...a),
exit: (...a: unknown[]) => h.exit(...a),
@@ -183,6 +186,7 @@ vi.mock("@triliumnext/server/src/sql_provider.js", () => ({
if (h.dbUninitialized) throw new Error("no such table: options");
const values: Record<string, string | null> = {
smoothScrollEnabled: h.smoothScroll,
hardwareAccelerationEnabled: h.hardwareAcceleration,
locale: h.locale,
formattingLocale: h.formattingLocale
};
@@ -264,6 +268,8 @@ function resetState() {
h.isPrimaryInstance = true;
h.allWindows = [];
h.smoothScroll = "true";
h.hardwareAcceleration = "true";
h.disableHardwareAcceleration.mockClear();
h.dbUninitialized = false;
h.isDbInitialized = true;
h.securitySettings = {};
@@ -406,6 +412,28 @@ describe("main() bootstrap", () => {
expect(switches).not.toContain("disable-smooth-scrolling");
});
// #10572: hardware acceleration is disabled before `ready` from the shared provider
// (same reason as smooth-scroll — core options aren't wired up yet).
it("disables hardware acceleration when the option is off", async () => {
setPlatform("darwin");
h.hardwareAcceleration = "false";
const { main } = await importMain();
await main();
expect(h.disableHardwareAcceleration).toHaveBeenCalled();
});
it("keeps hardware acceleration when the option is on, absent, or the DB has no schema yet", async () => {
for (const scenario of [{ value: "true" as string | null }, { value: null }, { value: "false" as string | null, uninitialized: true }]) {
resetState();
setPlatform("darwin");
h.hardwareAcceleration = scenario.value;
if (scenario.uninitialized) h.dbUninitialized = true;
const { main } = await importMain();
await main();
expect(h.disableHardwareAcceleration).not.toHaveBeenCalled();
}
});
// Regression for #10559 (sibling of the smooth-scroll bug): the --lang switch is set
// before core init from the same provider, so it must reflect the configured locale
// instead of always falling back to "en".
+6
View File
@@ -191,6 +191,12 @@ export async function main() {
if (readDbOption(dbProvider, "smoothScrollEnabled") === "false") {
app.commandLine.appendSwitch("disable-smooth-scrolling");
}
// Lets users work around GPU driver incompatibilities that render a blank window
// (see #10572) without having to pass --disable-gpu on the command line. Like the
// switch above, this must run before `ready`.
if (readDbOption(dbProvider, "hardwareAccelerationEnabled") === "false") {
app.disableHardwareAcceleration();
}
// The IPC provider just registers an `ipcMain.on` listener; no TCP socket
// or session parser needed, so we can init it here (before startTriliumServer).
@@ -138,6 +138,8 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi
shadowsEnabled: boolean;
backdropEffectsEnabled: boolean;
smoothScrollEnabled: boolean;
/** Desktop only: when disabled, Electron starts with GPU hardware acceleration turned off (workaround for GPU driver incompatibilities that cause a blank window). Requires an app restart. */
hardwareAccelerationEnabled: boolean;
codeNoteTheme: string;
codeNoteThemeMatchesApp: boolean;
codeNoteThemeLight: string;
@@ -84,6 +84,7 @@ const ALLOWED_OPTIONS = new Set<OptionNames>([
"motionEnabled",
"shadowsEnabled",
"smoothScrollEnabled",
"hardwareAccelerationEnabled",
"backdropEffectsEnabled",
"maxContentWidth",
"centerContent",
@@ -244,6 +244,7 @@ const defaultOptions: DefaultOption[] = [
{ name: "shadowsEnabled", value: "true", isSynced: false },
{ name: "backdropEffectsEnabled", value: "true", isSynced: false },
{ name: "smoothScrollEnabled", value: "true", isSynced: false },
{ name: "hardwareAccelerationEnabled", value: "true", isSynced: false },
{ name: "newLayout", value: "true", isSynced: true },
// PDF