Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,28 @@ pnpm tauri build

The built application will be in `src-tauri/target/release/bundle/`.

### Linux Rendering

On Linux, the dashboard defaults to CPU tile painting to reduce Skia's GPU
memory usage while keeping GPU compositing and asynchronous scrolling enabled.
Savings depend on the WebKit version. Explicit WebKit rendering overrides are
preserved. To use GPU tile painting instead:

```bash
WEBKIT_SKIA_ENABLE_CPU_RENDERING=0 ./Codex-Switcher_*.AppImage
```

On systems using Mesa, software OpenGL can reduce GPU allocations further while
keeping WebKit's threaded compositor and scrolling. This uses more CPU during
scrolling and more system RAM. To try it for one launch:

```bash
LIBGL_ALWAYS_SOFTWARE=1 WEBKIT_SKIA_ENABLE_CPU_RENDERING=1 ./Codex-Switcher_*.AppImage
```

This setting also applies to graphical child processes started by the app.
The desktop compositor may still allocate GPU memory to display the window.

### Run the Dashboard in a Browser

You can also serve the built dashboard over HTTP instead of opening the Tauri shell.
Expand Down
20 changes: 14 additions & 6 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@ mod linux_gtk;

fn main() {
#[cfg(target_os = "linux")]
for variable in ["GTK_MODULES", "GTK3_MODULES"] {
if let Some(filtered) = std::env::var_os(variable)
.as_deref()
.and_then(linux_gtk::without_appmenu_module)
{
unsafe { std::env::set_var(variable, filtered) };
{
for variable in ["GTK_MODULES", "GTK3_MODULES"] {
if let Some(filtered) = std::env::var_os(variable)
.as_deref()
.and_then(linux_gtk::without_appmenu_module)
{
unsafe { std::env::set_var(variable, filtered) };
}
}

// CPU tile painting reduces Skia's GPU allocations while keeping GPU
// compositing and asynchronous scrolling. Preserve explicit overrides.
if std::env::var_os("WEBKIT_SKIA_ENABLE_CPU_RENDERING").is_none() {
unsafe { std::env::set_var("WEBKIT_SKIA_ENABLE_CPU_RENDERING", "1") };
}
}

Expand Down
37 changes: 33 additions & 4 deletions tests/linuxGtkStartup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ import test from "node:test";

const probeSource = `
pub fn run() {
for name in ["GTK_MODULES", "GTK3_MODULES", "GDK_BACKEND"] {
for name in [
"GTK_MODULES",
"GTK3_MODULES",
"GDK_BACKEND",
"WEBKIT_DISABLE_COMPOSITING_MODE",
"WEBKIT_SKIA_ENABLE_CPU_RENDERING",
"WEBKIT_DISABLE_DMABUF_RENDERER",
] {
match std::env::var_os(name) {
Some(value) => println!("{}={}", name, value.to_string_lossy()),
None => println!("{}=<unset>", name),
Expand All @@ -17,7 +24,7 @@ pub fn run() {
}
`;

test("Linux entry point filters modules before starting Tauri", {
test("Linux entry point applies GTK and rendering defaults before starting Tauri", {
skip: process.platform !== "linux",
}, async (t) => {
const directory = mkdtempSync(join(tmpdir(), "codex-switcher-startup-"));
Expand All @@ -43,8 +50,20 @@ test("Linux entry point filters modules before starting Tauri", {
gtk3?: string;
expectedModules?: string;
expectedGtk3?: string;
cpuRendering?: string;
expectedCpuRendering?: string;
}[] = [
{ name: "leaves unset variables unset" },
{ name: "leaves unset GTK modules unset" },
{
name: "preserves an explicit GPU painting override",
cpuRendering: "0",
expectedCpuRendering: "0",
},
{
name: "preserves an explicitly empty painting override",
cpuRendering: "",
expectedCpuRendering: "",
},
{ name: "preserves explicitly empty values", modules: "", gtk3: "", expectedModules: "", expectedGtk3: "" },
{
name: "handles the reported Ubuntu environment",
Expand Down Expand Up @@ -82,20 +101,30 @@ test("Linux entry point filters modules before starting Tauri", {
},
];

for (const { name, modules, gtk3, expectedModules, expectedGtk3 } of cases) {
for (const {
name, modules, gtk3, expectedModules, expectedGtk3,
cpuRendering, expectedCpuRendering = "1",
} of cases) {
await t.test(name, () => {
const env: Record<string, string | undefined> = { ...process.env, GDK_BACKEND: "wayland" };
delete env.GTK_MODULES;
delete env.GTK3_MODULES;
delete env.WEBKIT_DISABLE_COMPOSITING_MODE;
delete env.WEBKIT_SKIA_ENABLE_CPU_RENDERING;
delete env.WEBKIT_DISABLE_DMABUF_RENDERER;
if (modules !== undefined) env.GTK_MODULES = modules;
if (gtk3 !== undefined) env.GTK3_MODULES = gtk3;
if (cpuRendering !== undefined) env.WEBKIT_SKIA_ENABLE_CPU_RENDERING = cpuRendering;
const result = spawnSync(executable, [], { env, encoding: "utf8" });
assert.ifError(result.error);
assert.equal(result.status, 0, result.stderr);
assert.equal(result.stdout, [
`GTK_MODULES=${expectedModules ?? "<unset>"}`,
`GTK3_MODULES=${expectedGtk3 ?? "<unset>"}`,
"GDK_BACKEND=wayland",
"WEBKIT_DISABLE_COMPOSITING_MODE=<unset>",
`WEBKIT_SKIA_ENABLE_CPU_RENDERING=${expectedCpuRendering}`,
"WEBKIT_DISABLE_DMABUF_RENDERER=<unset>",
"",
].join("\n"));
});
Expand Down