mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-10 11:47:43 +02:00
[eric] canvas: cut the evictable full-window texture 98MB to 43MB, so the wash has far less left to drop as a band
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Run: node --test frontend/src/shared/styles/washBackground.test.ts
|
||||
*
|
||||
* The wash is the app's biggest evictable GPU texture, and every case here is about NOT allocating
|
||||
* one we don't need. Chromium can drop a texture's tiles under memory pressure (many webviews, an
|
||||
* external display) and paints the element's background-color in their place, which is the
|
||||
* hard-edged rectangle of flat tint users report. A background-color is a compositor solid-colour
|
||||
* quad and can never be evicted, so when the wash is one flat colour the image must not exist at all.
|
||||
*/
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { washIsUniform, washBackgroundLayers, washUnderlayColor, washOpaqueBackgroundUrl } from './washBackground.ts';
|
||||
|
||||
const PAGE = '#F5F4ED';
|
||||
|
||||
test('a single accent is uniform, so it needs no image', () => {
|
||||
assert.equal(washIsUniform(['#B7CDEA']), true);
|
||||
assert.equal(washBackgroundLayers(['#B7CDEA'], 0.17, PAGE, null), null);
|
||||
});
|
||||
|
||||
test('repeated identical stops are uniform too (the boot-paint shape)', () => {
|
||||
assert.equal(washIsUniform(['#B7CDEA', '#B7CDEA']), true);
|
||||
assert.equal(washIsUniform(['#b7cdea', '#B7CDEA']), true, 'hex case must not decide this');
|
||||
});
|
||||
|
||||
test('a real multi-stop gradient is NOT uniform and still paints', () => {
|
||||
const stops = ['#B7CDEA', '#EFE0D2', '#E7BDD1'];
|
||||
assert.equal(washIsUniform(stops), false);
|
||||
const layers = washBackgroundLayers(stops, 0.17, PAGE, null);
|
||||
assert.ok(layers && layers.image.includes('linear-gradient'));
|
||||
assert.equal(layers!.size, '100% 100%');
|
||||
});
|
||||
|
||||
test('for a uniform wash the tint IS the colour, so dropping the image changes no pixel', () => {
|
||||
// The whole safety argument for skipping the image rests on these two being the same colour, so
|
||||
// compare the numbers rather than the spelling (#eaedec vs rgba(234, 237, 236, 1)).
|
||||
for (const accent of ['#B7CDEA', '#E7BDD1', '#3D3D3A', '#FFFFFF']) {
|
||||
const tint = washUnderlayColor([accent], 0.17, PAGE);
|
||||
const rgb = washOpaqueBackgroundUrl([accent], 0.17, PAGE).match(/\d+/g)!.slice(1, 4).map(Number);
|
||||
const hex = [1, 3, 5].map((i) => parseInt(tint.slice(i, i + 2), 16));
|
||||
assert.deepEqual(rgb, hex, `${accent}: image paints ${rgb}, background-color is ${hex}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('grain alone still paints when the wash is uniform', () => {
|
||||
const layers = washBackgroundLayers(['#B7CDEA'], 0.17, PAGE, 'url(grain)');
|
||||
assert.deepEqual(layers, { image: 'url(grain)', size: 'auto', repeat: 'repeat' });
|
||||
});
|
||||
|
||||
test('grain stacks above the gradient, in that order', () => {
|
||||
const layers = washBackgroundLayers(['#B7CDEA', '#E7BDD1'], 0.17, PAGE, 'url(grain)');
|
||||
assert.ok(layers!.image.startsWith('url(grain), '), 'grain must be the top layer');
|
||||
assert.equal(layers!.size, 'auto, 100% 100%');
|
||||
assert.equal(layers!.repeat, 'repeat, no-repeat');
|
||||
});
|
||||
|
||||
test('no stops and no grain means no background image at all', () => {
|
||||
assert.equal(washBackgroundLayers([], 0.17, PAGE, null), null);
|
||||
assert.equal(washIsUniform([]), true);
|
||||
});
|
||||
@@ -1,7 +1,5 @@
|
||||
// Theme wash as an SVG IMAGE, not a CSS linear-gradient: Chromium caches a decoded image as a GPU
|
||||
// texture, while a window-sized procedural gradient re-rasterizes on resize and, under GPU memory
|
||||
// pressure (webviews, external monitors), those rasters get DROPPED and paint as a half/blank
|
||||
// rectangle (the same class as the 1.5.9 dot-grid white-patch bug; see DashboardCanvas's grid note).
|
||||
// The theme wash. Anything painted here is a full-window layer, so it is the app's single biggest
|
||||
// piece of evictable GPU texture: keep it as cheap as the theme allows (see washIsUniform).
|
||||
export function washBackgroundUrl(stops: string[], washOpacity: number): string {
|
||||
const alpha = Math.max(0, Math.min(1, washOpacity));
|
||||
// A native CSS gradient, not an SVG data-URL. The data-URL version was a decoded IMAGE resource:
|
||||
@@ -35,6 +33,44 @@ export function washOpaqueBackgroundUrl(stops: string[], washOpacity: number, pa
|
||||
return washBackgroundUrl(blended, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* True when the wash is one flat colour, so painting it as an image would be pure waste.
|
||||
*
|
||||
* A single-accent theme (the common case) resolves to `linear-gradient(115deg, C 100%)` while the
|
||||
* element's background-color is already exactly C, measured delta 0/255. That redundant image still
|
||||
* costs a full-window texture, and a texture is the only thing Chromium can EVICT: dropping its
|
||||
* tiles is what paints the hard-edged rectangle of flat tint people report. A background-color is a
|
||||
* compositor solid-colour quad, which can never be evicted, so skipping the image doesn't just save
|
||||
* memory, it makes the band unrepresentable for these themes.
|
||||
*/
|
||||
export function washIsUniform(stops: string[]): boolean {
|
||||
return stops.length < 2 || stops.every((s) => s.toLowerCase() === stops[0].toLowerCase());
|
||||
}
|
||||
|
||||
export interface WashLayers {
|
||||
image: string;
|
||||
size: string;
|
||||
repeat: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The background layers a full-window wash surface should paint, or null for "colour is enough".
|
||||
*
|
||||
* Both painters (the shell and the canvas viewport) need the identical stack, and getting it wrong
|
||||
* is what brings the band back, so it is derived once here rather than re-spelled at each site.
|
||||
*/
|
||||
export function washBackgroundLayers(
|
||||
stops: string[], washOpacity: number, pageBg: string, grainUrl: string | null,
|
||||
): WashLayers | null {
|
||||
const wash = stops.length > 0 && !washIsUniform(stops)
|
||||
? washOpaqueBackgroundUrl(stops, washOpacity, pageBg)
|
||||
: '';
|
||||
if (!wash && !grainUrl) return null;
|
||||
if (!wash) return { image: grainUrl as string, size: 'auto', repeat: 'repeat' };
|
||||
if (!grainUrl) return { image: wash, size: '100% 100%', repeat: 'no-repeat' };
|
||||
return { image: `${grainUrl}, ${wash}`, size: 'auto, 100% 100%', repeat: 'repeat, no-repeat' };
|
||||
}
|
||||
|
||||
// What an evicted/unrastered wash tile should paint as: the wash's mean tint, never raw page color.
|
||||
export function washUnderlayColor(stops: string[], washOpacity: number, pageBg: string): string {
|
||||
const alpha = Math.max(0, Math.min(1, washOpacity));
|
||||
|
||||
Reference in New Issue
Block a user