Large document: a notice explains a slow CPU fallback redraw (#1015) (#1427)

* Large document: a notice explains a slow CPU fallback redraw (#1015)

A full refresh that falls back to the CPU compositor blocks the canvas for as
long as it runs, with only a log line to say why. The first one per document
that takes over a second now posts a notice: how long it took, which edits
redraw the whole document, and for the GPU memory budget fallback, that
raising Memory usage raises the budget (up to a quarter of RAM).

* Slow-refresh notice test: check the real "Memory usage" casing; add Polish rows

The last assertion looked for "Memory Usage" while the string says
"Memory usage", so it passed vacuously. The budget case now asserts the
hint is present too. pl.tsv gets rows for the new strings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
authored and GitHub committed 2026-10-08 20:53:39 -04:00
1 parent 7a9a2684a1
commit f3fda94c87
17 files changed
+127 -1

No files matched your search

+3
View File
@@ -951,6 +951,9 @@ fn ensure_gpu(app: &mut PhotocraftApp, idx: usize, visible: DRect) -> bool {
log::info!("{e}; using the CPU compositor");
}
app.perf.record(r.kind, r.px, r.composite_ms, r.upload_ms);
if r.kind == "full" {
crate::notices::slow_cpu_refresh(app, id.0, r.composite_ms + r.upload_ms, r.fallback.as_deref());
}
if r.kind.starts_with("gpu") {
app.perf.gpu_uploads = r.uploads;
} else {
+4 -1
View File
@@ -386,7 +386,7 @@ impl GpuCanvas {
// compositor does it, and edits in the view (damage rects) stay on the GPU.
if region == doc.bounds() && !comp.fits_budget(doc, region) {
res.compositor = Some(comp);
return Err(photocraft_gpu::Unsupported("layers exceed the GPU memory budget; full refresh on the CPU".into()));
return Err(photocraft_gpu::Unsupported(OVER_BUDGET.into()));
}
if fresh {
let tex = DocTextures::new(device, res, size, self.tile, format);
@@ -1040,6 +1040,9 @@ fn texel_to_f32(format: wgpu::TextureFormat, b: &[u8]) -> [f32; 4] {
/// Floor of the compositor's memory budget: enough for a viewport's pages of a dozen layers.
pub const MIN_GPU_BUDGET: u64 = 512 << 20;
/// Why a full refresh whose layer pages don't fit [`memory_budget`] goes to the CPU compositor.
pub const OVER_BUDGET: &str = "layers exceed the GPU memory budget; full refresh on the CPU";
/// GPU memory the wgpu compositor may hold for layer pages and effect maps: what Memory Usage
/// (`allowance`, Preferences › Performance) leaves after the document's pixels and History
/// (`pixels`), at most a quarter of physical memory (`ram`; 16 GB assumed when unknown), and at
+5
View File
@@ -2196,3 +2196,8 @@ cameraRaw Color Barvy
Fuchsia Fuchsiová
Violet Fialová
Reset Obnovit
Large document: redrawing on the CPU Velký dokument: překreslování na CPU
This redraw took {seconds} s on the CPU. Toto překreslení na CPU trvalo {seconds} s.
Edits that change the whole document, such as a layer's blend mode, opacity or visibility, redraw all of it. Úpravy, které mění celý dokument, například režim prolnutí, krytí nebo viditelnost vrstvy, jej překreslí celý.
Its layers don't fit the GPU memory budget. Raising Memory usage in Preferences › Performance raises the budget, up to a quarter of this computer's memory. Vrstvy se nevejdou do rozpočtu paměti GPU. Zvýšením hodnoty Využití paměti v Předvolbách › Výkon se rozpočet zvětší, nejvýše na čtvrtinu paměti tohoto počítače.
The GPU compositor wasn't used: {reason} Kompozitor GPU nebyl použit: {reason}
Can't render this file because it contains an unexpected character in line 6 and column 47.
+5
View File
@@ -2235,3 +2235,8 @@ layerLabel No Color Keine Farbe
Indigo Indigo
Fuchsia Fuchsia
Violet Violett
Large document: redrawing on the CPU Großes Dokument: Neuzeichnen auf der CPU
This redraw took {seconds} s on the CPU. Dieses Neuzeichnen auf der CPU dauerte {seconds} s.
Edits that change the whole document, such as a layer's blend mode, opacity or visibility, redraw all of it. Änderungen, die das ganze Dokument betreffen, etwa Füllmethode, Deckkraft oder Sichtbarkeit einer Ebene, zeichnen es vollständig neu.
Its layers don't fit the GPU memory budget. Raising Memory usage in Preferences › Performance raises the budget, up to a quarter of this computer's memory. Die Ebenen passen nicht in das GPU-Speicherbudget. Eine höhere Speichernutzung unter Voreinstellungen › Leistung erhöht das Budget, bis zu einem Viertel des Arbeitsspeichers dieses Computers.
The GPU compositor wasn't used: {reason} Der GPU-Compositor wurde nicht verwendet: {reason}
Can't render this file because it contains an unexpected character in line 6 and column 47.
+5
View File
@@ -2266,3 +2266,8 @@ cameraRaw Amount Cantidad
Fuchsia Fucsia
Violet Violeta
Reset Restaurar
Large document: redrawing on the CPU Documento grande: redibujando en la CPU
This redraw took {seconds} s on the CPU. Este redibujado en la CPU tardó {seconds} s.
Edits that change the whole document, such as a layer's blend mode, opacity or visibility, redraw all of it. Las ediciones que cambian todo el documento, como el modo de fusión, la opacidad o la visibilidad de una capa, lo redibujan por completo.
Its layers don't fit the GPU memory budget. Raising Memory usage in Preferences › Performance raises the budget, up to a quarter of this computer's memory. Las capas no caben en el presupuesto de memoria de la GPU. Aumentar Uso de memoria en Preferencias › Rendimiento amplía el presupuesto, hasta una cuarta parte de la memoria de este equipo.
The GPU compositor wasn't used: {reason} No se usó el compositor de GPU: {reason}
Can't render this file because it contains an unexpected character in line 4 and column 47.
+5
View File
@@ -2624,3 +2624,8 @@ cameraRaw Blending Fusion
Indigo Indigo
Fuchsia Fuchsia
Violet Violet
Large document: redrawing on the CPU Document volumineux : redessin sur le CPU
This redraw took {seconds} s on the CPU. Ce redessin sur le CPU a pris {seconds} s.
Edits that change the whole document, such as a layer's blend mode, opacity or visibility, redraw all of it. Les modifications qui touchent tout le document, comme le mode de fusion, l'opacité ou la visibilité d'un calque, le redessinent entièrement.
Its layers don't fit the GPU memory budget. Raising Memory usage in Preferences › Performance raises the budget, up to a quarter of this computer's memory. Les calques dépassent le budget de mémoire GPU. Augmenter Utilisation de la mémoire dans Préférences › Performances relève ce budget, jusqu'au quart de la mémoire de cet ordinateur.
The GPU compositor wasn't used: {reason} Le compositeur GPU n'a pas été utilisé : {reason}
Can't render this file because it contains an unexpected character in line 6 and column 47.
+5
View File
@@ -2611,3 +2611,8 @@ cameraRaw Blending Pembauran
Indigo Nila
Fuchsia Fuksia
Violet Ungu
Large document: redrawing on the CPU Dokumen besar: menggambar ulang di CPU
This redraw took {seconds} s on the CPU. Penggambaran ulang di CPU ini memakan waktu {seconds} dtk.
Edits that change the whole document, such as a layer's blend mode, opacity or visibility, redraw all of it. Suntingan yang mengubah seluruh dokumen, seperti mode campuran, opasitas, atau visibilitas layer, menggambar ulang semuanya.
Its layers don't fit the GPU memory budget. Raising Memory usage in Preferences › Performance raises the budget, up to a quarter of this computer's memory. Layer tidak muat dalam anggaran memori GPU. Menaikkan Pemakaian memori di Preferensi › Performa menaikkan anggaran, hingga seperempat memori komputer ini.
The GPU compositor wasn't used: {reason} Kompositor GPU tidak digunakan: {reason}
Can't render this file because it contains an unexpected character in line 4 and column 47.
+5
View File
@@ -2267,3 +2267,8 @@ cameraRaw Amount Quantità
Indigo Indaco
Fuchsia Fucsia
Violet Viola
Large document: redrawing on the CPU Documento grande: ridisegno sulla CPU
This redraw took {seconds} s on the CPU. Questo ridisegno sulla CPU ha richiesto {seconds} s.
Edits that change the whole document, such as a layer's blend mode, opacity or visibility, redraw all of it. Le modifiche che cambiano tutto il documento, come metodo di fusione, opacità o visibilità di un livello, lo ridisegnano per intero.
Its layers don't fit the GPU memory budget. Raising Memory usage in Preferences › Performance raises the budget, up to a quarter of this computer's memory. I livelli non rientrano nel budget di memoria della GPU. Aumentare Uso memoria in Preferenze › Prestazioni alza il budget, fino a un quarto della memoria di questo computer.
The GPU compositor wasn't used: {reason} Il compositore GPU non è stato usato: {reason}
1 # Italian (it) catalog. Columns, separated by a TAB: context <TAB> source <TAB> translation
2267
2268
2269
2270
2271
2272
2273
2274
+5
View File
@@ -2179,3 +2179,8 @@ cameraRaw Color カラー
Fuchsia フクシア
Violet 紫
Reset 初期化
Large document: redrawing on the CPU 大きなドキュメント:CPUで再描画しています
This redraw took {seconds} s on the CPU. この再描画はCPUで{seconds}秒かかりました。
Edits that change the whole document, such as a layer's blend mode, opacity or visibility, redraw all of it. レイヤーの描画モード、不透明度、表示/非表示など、ドキュメント全体に関わる編集では全体が再描画されます。
Its layers don't fit the GPU memory budget. Raising Memory usage in Preferences › Performance raises the budget, up to a quarter of this computer's memory. レイヤーがGPUメモリの予算に収まりません。環境設定 › パフォーマンスのメモリ使用量を増やすと、このコンピューターのメモリの4分の1まで予算が増えます。
The GPU compositor wasn't used: {reason} GPUコンポジターは使用されませんでした:{reason}
Can't render this file because it contains an unexpected character in line 6 and column 47.
+5
View File
@@ -2779,3 +2779,8 @@ cameraRaw Color 색상
Seafoam 바다 거품색
Indigo 남색
Fuchsia 자홍색
Large document: redrawing on the CPU 큰 문서: CPU에서 다시 그리는 중
This redraw took {seconds} s on the CPU. 이번 다시 그리기는 CPU에서 {seconds}초 걸렸습니다.
Edits that change the whole document, such as a layer's blend mode, opacity or visibility, redraw all of it. 레이어의 혼합 모드, 불투명도, 표시 여부처럼 문서 전체를 바꾸는 편집은 문서 전체를 다시 그립니다.
Its layers don't fit the GPU memory budget. Raising Memory usage in Preferences › Performance raises the budget, up to a quarter of this computer's memory. 레이어가 GPU 메모리 예산에 맞지 않습니다. 환경 설정 › 성능에서 메모리 사용량을 늘리면 이 컴퓨터 메모리의 4분의 1까지 예산이 늘어납니다.
The GPU compositor wasn't used: {reason} GPU 합성기를 사용하지 않았습니다: {reason}
1 # Korean (ko) catalog. UTF-8: context<TAB>English source<TAB>translation.
2779
2780
2781
2782
2783
2784
2785
2786
+5
View File
@@ -3228,3 +3228,8 @@ cameraRaw Roughness Chropowatość
Seafoam Morski
Indigo Indygo
Fuchsia Fuksja
Large document: redrawing on the CPU Duży dokument: odświeżanie na CPU
This redraw took {seconds} s on the CPU. To odświeżenie na CPU trwało {seconds} s.
Edits that change the whole document, such as a layer's blend mode, opacity or visibility, redraw all of it. Zmiany obejmujące cały dokument, takie jak tryb mieszania, krycie lub widoczność warstwy, odświeżają go w całości.
Its layers don't fit the GPU memory budget. Raising Memory usage in Preferences › Performance raises the budget, up to a quarter of this computer's memory. Warstwy nie mieszczą się w budżecie pamięci GPU. Zwiększenie wartości Użycie pamięci w Preferencje › Wydajność zwiększa budżet, maksymalnie do jednej czwartej pamięci tego komputera.
The GPU compositor wasn't used: {reason} Kompozytor GPU nie został użyty: {reason}
Can't render this file because it contains an unexpected character in line 6 and column 47.
+5
View File
@@ -2631,3 +2631,8 @@ cameraRaw Whites Brancos
Indigo Índigo
Fuchsia Fúcsia
Violet Violeta
Large document: redrawing on the CPU Documento grande: redesenhando na CPU
This redraw took {seconds} s on the CPU. Este redesenho na CPU levou {seconds} s.
Edits that change the whole document, such as a layer's blend mode, opacity or visibility, redraw all of it. Edições que mudam o documento inteiro, como modo de mesclagem, opacidade ou visibilidade de uma camada, redesenham tudo.
Its layers don't fit the GPU memory budget. Raising Memory usage in Preferences › Performance raises the budget, up to a quarter of this computer's memory. As camadas não cabem no orçamento de memória da GPU. Aumentar Uso de memória em Preferências › Desempenho eleva o orçamento, até um quarto da memória deste computador.
The GPU compositor wasn't used: {reason} O compositor de GPU não foi usado: {reason}
Can't render this file because it contains an unexpected character in line 6 and column 47.
+5
View File
@@ -2471,3 +2471,8 @@ cameraRaw Roughness Неровность
Indigo Индиго
Fuchsia Фуксия
Violet Фиолетовый
Large document: redrawing on the CPU Большой документ: перерисовка на CPU
This redraw took {seconds} s on the CPU. Эта перерисовка на CPU заняла {seconds} с.
Edits that change the whole document, such as a layer's blend mode, opacity or visibility, redraw all of it. Правки, затрагивающие весь документ, например режим наложения, непрозрачность или видимость слоя, перерисовывают его целиком.
Its layers don't fit the GPU memory budget. Raising Memory usage in Preferences › Performance raises the budget, up to a quarter of this computer's memory. Слои не помещаются в бюджет памяти GPU. Увеличение параметра «Память» в разделе Настройки › Производительность повышает бюджет, но не больше четверти памяти этого компьютера.
The GPU compositor wasn't used: {reason} Компоновщик GPU не использовался: {reason}
Can't render this file because it contains an unexpected character in line 6 and column 47.
+5
View File
@@ -2204,3 +2204,8 @@ cameraRaw Blending 混合
Fuchsia 紫红色
Violet 紫罗兰色
Reset 复位
Large document: redrawing on the CPU 大型文档:正在用 CPU 重绘
This redraw took {seconds} s on the CPU. 此次 CPU 重绘耗时 {seconds} 秒。
Edits that change the whole document, such as a layer's blend mode, opacity or visibility, redraw all of it. 更改整个文档的编辑(如图层的混合模式、不透明度或可见性)会重绘整个文档。
Its layers don't fit the GPU memory budget. Raising Memory usage in Preferences › Performance raises the budget, up to a quarter of this computer's memory. 图层超出 GPU 内存预算。在 首选项 › 性能 中提高内存使用量可增加预算,最多为本机内存的四分之一。
The GPU compositor wasn't used: {reason} 未使用 GPU 合成器:{reason}
1 # Simplified Chinese (zh-hans) catalog. UTF-8, TAB-separated columns:
2204
2205
2206
2207
2208
2209
2210
2211
+5
View File
@@ -2173,3 +2173,8 @@ cameraRaw Blending 混合
Fuchsia 紫紅色
Violet 紫羅蘭色
Reset 重設
Large document: redrawing on the CPU 大型文件:正在以 CPU 重繪
This redraw took {seconds} s on the CPU. 此次 CPU 重繪耗時 {seconds} 秒。
Edits that change the whole document, such as a layer's blend mode, opacity or visibility, redraw all of it. 變更整份文件的編輯(例如圖層的混合模式、不透明度或可見度)會重繪整份文件。
Its layers don't fit the GPU memory budget. Raising Memory usage in Preferences › Performance raises the budget, up to a quarter of this computer's memory. 圖層超出 GPU 記憶體預算。在 偏好設定 › 效能 中提高記憶體使用量可增加預算,最多為本機記憶體的四分之一。
The GPU compositor wasn't used: {reason} 未使用 GPU 合成器:{reason}
Can't render this file because it contains an unexpected character in line 6 and column 47.
+51
View File
@@ -12,6 +12,8 @@ const WAYLAND_FILE_DROP_DISMISSED: &str = "ui.waylandFileDropGuidanceDismissed";
pub const MAX_NOTICES: usize = 3;
/// Lines shown per notice before "…and N more".
const MAX_LINES: usize = 8;
/// A full canvas refresh on the CPU compositor slower than this (ms) gets a notice.
const SLOW_CPU_REFRESH_MS: f64 = 1000.0;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Notice {
@@ -62,6 +64,30 @@ pub fn wayland_file_drop_guidance(app: &mut PhotocraftApp) {
);
}
/// A full refresh of document `doc` fell back to the CPU compositor (`reason`, the GPU's) and took
/// `ms`. The canvas doesn't respond while it runs, so once per document say why it was slow and
/// which edits will do it again (#1015).
pub fn slow_cpu_refresh(app: &mut PhotocraftApp, doc: u64, ms: f64, reason: Option<&str>) {
if ms.is_nan() || ms < SLOW_CPU_REFRESH_MS || app.ui.slow_refresh_noticed.contains(&doc) {
return;
}
app.ui.slow_refresh_noticed.push(doc);
let seconds = format!("{:.1}", ms / 1000.0);
let mut lines = vec![
crate::i18n::fmt(tl!("This redraw took {seconds} s on the CPU."), &[("seconds", &seconds)]),
tl!("Edits that change the whole document, such as a layer's blend mode, opacity or visibility, redraw all of it.").into(),
];
if reason == Some(crate::gpu_canvas::OVER_BUDGET) {
lines.push(
tl!("Its layers don't fit the GPU memory budget. Raising Memory usage in Preferences › Performance raises the budget, up to a quarter of this computer's memory.")
.into(),
);
} else if let Some(r) = reason {
lines.push(crate::i18n::fmt(tl!("The GPU compositor wasn't used: {reason}"), &[("reason", r)]));
}
post(app, tl!("Large document: redrawing on the CPU"), lines, false, None);
}
fn dismiss(app: &mut PhotocraftApp, id: u64) {
let dismiss_pref = app.ui.notices.iter().find(|notice| notice.id == id).and_then(|notice| notice.dismiss_pref.clone());
app.ui.notices.retain(|notice| notice.id != id);
@@ -206,6 +232,31 @@ mod tests {
assert!(app.ui.notices.is_empty());
}
#[test]
fn a_slow_cpu_refresh_is_explained_once_per_document() {
let mut app = PhotocraftApp::new(Session::new(), Services::default());
slow_cpu_refresh(&mut app, 1, 400.0, Some(crate::gpu_canvas::OVER_BUDGET));
slow_cpu_refresh(&mut app, 1, f64::NAN, Some(crate::gpu_canvas::OVER_BUDGET));
assert!(app.ui.notices.is_empty(), "a quick refresh needs no explanation");
slow_cpu_refresh(&mut app, 1, 55_000.0, Some(crate::gpu_canvas::OVER_BUDGET));
assert_eq!(app.ui.notices.len(), 1);
let text = app.ui.notices[0].lines.join(" ");
assert!(text.contains("55.0 s"), "{text}");
assert!(text.contains("blend mode, opacity or visibility"), "{text}");
assert!(text.contains("Memory usage in Preferences › Performance"), "{text}");
assert!(!app.ui.notices[0].error);
slow_cpu_refresh(&mut app, 1, 55_000.0, Some(crate::gpu_canvas::OVER_BUDGET));
assert_eq!(app.ui.notices.len(), 1, "once per document");
slow_cpu_refresh(&mut app, 2, 3_000.0, Some("Blend If on `Overlay` (composited on the CPU)"));
assert_eq!(app.ui.notices.len(), 2);
let text = app.ui.notices[1].lines.join(" ");
assert!(text.contains("Blend If on `Overlay`"), "{text}");
assert!(!text.contains("Memory usage"), "the budget hint is only for the budget fallback: {text}");
}
#[test]
fn dismissing_a_notice_writes_its_own_preference_key() {
let mut app = PhotocraftApp::new(Session::new(), Services::default());
+4
View File
@@ -873,6 +873,9 @@ pub struct UiState {
/// Pending GPU fallback warning, visible to automation.
#[serde(default)]
pub gpu_fallback_notice: Option<String>,
/// Documents (ids) whose slow full refresh on the CPU compositor has had its notice.
#[serde(default)]
pub slow_refresh_noticed: Vec<u64>,
/// Status bar info field, Home screen (see `chrome_ui`).
#[serde(default)]
pub chrome: crate::chrome_ui::ChromeState,
@@ -939,6 +942,7 @@ impl Default for UiState {
status_error: false,
notices: Vec::new(),
gpu_fallback_notice: None,
slow_refresh_noticed: Vec::new(),
chrome: Default::default(),
camera_raw_scope: Default::default(),
camera_raw_preview: Default::default(),