mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
* Windows: vendor egui-wgpu 0.36.2 as published (#577) The crate as released on crates.io, plus egui's LICENSE-APACHE and LICENSE-MIT from the 0.36.2 tag (the published crate leaves them out). Not used yet: the next commit patches it and adds it to [patch.crates-io]. Co-Authored-By: Claude Opus 5.5 <[email protected]> * Windows: a window larger than the GPU's texture limit can't close PdfCraft (#577) egui-wgpu 0.36.2 configures a window's surface at the window's own size. Beyond the device's max_texture_dimension_2d, Surface::configure fails validation and wgpu's default error handler panics. eframe configures the surface before the app is created, so no app code can intercept it (emilk/egui#8361). #578 raised the device's limit to the adapter's; this removes the crash for windows larger than even that. vendor/egui-wgpu: `winit::surface_fit` shrinks both sides by one factor so neither exceeds the limit (the longer side lands exactly on it), and egui draws at that factor into the surface. On DX12, PdfCraft's Windows default, the swapchain is stretched over the window (DXGI_SCALING_STRETCH), so the window stays complete and lined up with the pointer, only softer; wgpu's GL backend copies it unscaled into a corner instead. It applies wherever a surface is sized: install, resize (start-up restore, maximise, DPI change, fullscreen) and a recreated surface, which starts again from the window's own size. Depth and MSAA textures follow. A surface logs a warning when it becomes fitted. Why only an Outlook launch hit it (read from the code, not observed): winit creates the window at CW_USEDEFAULT, so Windows places it, and a launcher that hands ShellExecute its own window gets new windows on its monitor. winit then converts the restored logical size at that monitor's scale: 3440 points became 8600 pixels on a 250% panel. eframe's clamp compares points against the largest monitor, so it let 3440 through. Tests: surface_fit with the issue's 8600 x 3423 and edge sizes (zero, u32::MAX, a zero limit, the longer side exactly on the limit); egui-wgpu resolves to vendor/egui-wgpu and the fit stays wired into the paths that size a surface. Co-Authored-By: Claude Opus 5.5 <[email protected]> * Windows: a GPU that can't set up the window falls back to OpenGL (#519) On a 2015 Intel GPU with no D3D12 driver, wgpu picked its GL adapter over WARP and Surface::configure failed (GpuWaitTimeout). wgpu's default error handler panics, and eframe configures the root surface before the app is created, so the OpenGL retry from #530 never ran. Vendored egui-wgpu (PdfCraft patch #519): winit::catch_errors runs a closure inside wgpu error scopes (validation, out of memory, internal) and returns the error. Painter::add_surface configures each new window's surface inside it and returns a failure as WgpuError::ConfigureSurface, so eframe returns Error::Wgpu and main retries with glow. Later reconfigures are unchanged. Tests: a real device returns a wgpu validation error from catch_errors instead of panicking (red with the scopes removed), and add_surface keeps the wiring (red with it removed). Docs: vendor/README.md patch 2, docs/development.md "Renderer fallback" and PDFCRAFT_RENDERER, ROADMAP. Co-Authored-By: Claude Opus 5.5 <[email protected]> * vendor/egui-wgpu: drop the stray .cargo-ok marker * Tests: the GPU error-scope test skips on a machine without a GPU adapter --------- Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: Brandon Thomas <[email protected]>
This commit is contained in:
8 files changed
+160
-6
No files matched your search
Generated
+1
@@ -3550,6 +3550,7 @@ dependencies = [
|
||||
"fmv-macos-events",
|
||||
"log",
|
||||
"pdfcraft-ui-egui",
|
||||
"pollster 1.0.1",
|
||||
"rustls-native-certs",
|
||||
"serde_json",
|
||||
"ureq",
|
||||
|
||||
@@ -138,6 +138,8 @@ M0 → M1 → M2 → M3 → M4 must happen in order. After M4, M5–M12 can run
|
||||
|
||||
- **2026-10-09 (M14, #577):** The desktop GPU setup enables the selected adapter's supported 2D texture extent instead of egui-wgpu's fixed 8192-pixel extent. This removes the artificial surface ceiling behind a mixed-DPI Windows startup crash when opening Outlook PDF attachments. Synthetic regressions cover the observed 8600 × 3423 surface, downlevel adapters and preservation of other device limits. Hardware resolution limits still apply; milestone and overall effort estimates are unchanged.
|
||||
- **2026-10-09 (M14, #577 follow-up):** A window larger than the GPU's texture limit no longer closes PdfCraft. Vendored egui-wgpu 0.36.2 fits the window's surface within `max_texture_dimension_2d` and draws egui at the matching scale, on every path that sizes a surface (start-up restore, resize, maximise, DPI change, a recreated surface); upstream is emilk/egui#8361. Root cause of the Outlook-only trigger: winit creates the window on the launching app's monitor and converts the restored size at that monitor's scale. Unit tests cover the 8600 × 3423 case and edge sizes; a Windows Sandbox (WARP) run of a window one pixel over the limit panicked before and kept drawing after. Estimates unchanged.
|
||||
- **2026-10-09 (M14, #577 follow-up):** A window larger than the GPU's texture limit no longer closes PdfCraft. Vendored egui-wgpu 0.36.2 fits the window's surface within `max_texture_dimension_2d` and draws egui at the matching scale, on every path that sizes a surface (start-up restore, resize, maximise, DPI change, a recreated surface); upstream is emilk/egui#8361. Root cause of the Outlook-only trigger: winit creates the window on the launching app's monitor and converts the restored size at that monitor's scale. Unit tests cover the 8600 × 3423 case and edge sizes; a Windows Sandbox (WARP) run of a window one pixel over the limit panicked before and kept drawing after. Estimates unchanged.
|
||||
- **2026-10-09 (M14, #519):** A GPU that can't configure the window's surface at launch no longer panics, so PdfCraft's OpenGL retry runs. Vendored egui-wgpu configures a new window's surface inside wgpu error scopes (`winit::catch_errors`) and returns a failure as an error, so eframe returns `Error::Wgpu` and the existing OpenGL retry (#530) runs (a device lost during that configure still panics, on the first frame). Before, wgpu's default handler panicked: on a 2015 Intel GPU with no D3D12 driver wgpu picked the GL adapter over WARP and timed out in `Surface::configure` (`GpuWaitTimeout`). A real-device regression test (a wgpu validation error inside `catch_errors`) panicked with the scopes removed; in Windows Sandbox a forced configure failure panicked unpatched and retried with OpenGL patched (Sandbox has no OpenGL 2.0, so the OpenGL window itself was not shown there). Defaulting Windows to DX12 only (WARP instead of wgpu's GL backend) is left to the maintainers. Overall estimate unchanged (about 30–35%).
|
||||
|
||||
- **2026-10-09 (M5, Fill & Sign flatten on save):** Preferences and `doc_save` can bake Fill & Sign text, marks and signatures into the page when saving. The option defaults off. Other comments and form fields stay, a second flatten does not reuse an XObject name already on the page, and a document that cannot be changed is left unwritten. Overall estimate unchanged (about 30–35%).
|
||||
- **2026-10-09 (M5.7, #434 partial):** Natural-size image stamps placed at a click or through `stamp_custom` remain upright on rotated pages, with a displayed-space-sized rectangle and a counterrotated standard appearance matrix. Move, resize, restyle and undo/redo preserve that orientation. Native and external Poppler save/reopen rendering cover all four quarter-turns; UI tests verify the displayed bounds. Explicit rectangle placement, image signatures, PDF-page stamps and image-file orientation metadata remain outside this repair. #434 stays open; milestone table and effort estimates remain unchanged.
|
||||
|
||||
@@ -28,6 +28,10 @@ log = { workspace = true }
|
||||
# behind a safe API. Same version as PhotoCraft. Pinned exactly: review the diff before bumping.
|
||||
fmv-macos-events = "=0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
# Waits for wgpu's adapter, device and error-scope futures in tests (already in the tree through wgpu).
|
||||
pollster = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
# Icon + VERSIONINFO for pdfcraft.exe; build.rs only uses it when targeting Windows.
|
||||
winresource = "0.1"
|
||||
@@ -702,6 +702,78 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_gpu_error_while_a_window_is_set_up_is_returned_not_a_panic() {
|
||||
// Issue #519: on a 2015 Intel GPU, wgpu's GL backend couldn't configure the window's
|
||||
// surface (`GpuWaitTimeout`), and wgpu's default error handler panicked before the app was
|
||||
// created, so PdfCraft never got to retry with OpenGL. vendor/egui-wgpu configures a new
|
||||
// window's surface inside `catch_errors`. A real device on PdfCraft's own GPU settings,
|
||||
// and an error wgpu raises before anything reaches the driver: a texture one pixel wider
|
||||
// than the device allows.
|
||||
use eframe::wgpu;
|
||||
let native = super::native_options(false, eframe::Renderer::Wgpu);
|
||||
let eframe::egui_wgpu::WgpuSetup::CreateNew(setup) = &native.wgpu_options.wgpu_setup else {
|
||||
panic!("default setup creates its own instance")
|
||||
};
|
||||
let instance = pollster::block_on(native.wgpu_options.wgpu_setup.new_instance());
|
||||
let options = wgpu::RequestAdapterOptions { power_preference: setup.power_preference, ..Default::default() };
|
||||
// CI runners have a software adapter (WARP, llvmpipe); a machine without any skips.
|
||||
let Ok(adapter) = pollster::block_on(instance.request_adapter(&options)) else {
|
||||
eprintln!("skipping: no GPU adapter on this machine");
|
||||
return;
|
||||
};
|
||||
let Ok((device, _queue)) = pollster::block_on(adapter.request_device(&(setup.device_descriptor)(&adapter))) else {
|
||||
eprintln!("skipping: the adapter gives no device");
|
||||
return;
|
||||
};
|
||||
let texture = |width: u32| {
|
||||
device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("issue-519"),
|
||||
size: wgpu::Extent3d { width, height: 1, depth_or_array_layers: 1 },
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
})
|
||||
};
|
||||
let too_wide = device.limits().max_texture_dimension_2d.saturating_add(1);
|
||||
let caught = pollster::block_on(eframe::egui_wgpu::winit::catch_errors(&device, || texture(too_wide)));
|
||||
assert!(matches!(caught, Err(wgpu::Error::Validation { .. })), "{caught:?}");
|
||||
// The device stays usable, and nothing is left behind to catch later errors by mistake.
|
||||
let fine = pollster::block_on(eframe::egui_wgpu::winit::catch_errors(&device, || texture(16)));
|
||||
assert!(fine.is_ok(), "{fine:?}");
|
||||
assert_eq!(pollster::block_on(eframe::egui_wgpu::winit::catch_errors(&device, || 7)).ok(), Some(7));
|
||||
// Nested: the inner call keeps its own error, and the outer one still catches what follows.
|
||||
let caught = pollster::block_on(eframe::egui_wgpu::winit::catch_errors(&device, || {
|
||||
let inner = pollster::block_on(eframe::egui_wgpu::winit::catch_errors(&device, || texture(too_wide)));
|
||||
assert!(inner.is_err(), "inner: {inner:?}");
|
||||
texture(too_wide)
|
||||
}));
|
||||
assert!(matches!(caught, Err(wgpu::Error::Validation { .. })), "outer: {caught:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn egui_wgpu_returns_a_surface_it_cannot_configure_as_an_error() {
|
||||
// Issue #519: `catch_errors` is tested above; this keeps it around the first configure of
|
||||
// every new window's surface, and its error reaching eframe as `WgpuError` (eframe then
|
||||
// returns `Error::Wgpu`, which `main` retries with OpenGL). Without it a failed configure
|
||||
// panics before the app is created, and the OpenGL retry never runs.
|
||||
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
|
||||
let lock = std::fs::read_to_string(root.join("Cargo.lock")).unwrap();
|
||||
let egui_wgpu = lock.split("[[package]]").find(|p| p.contains("\nname = \"egui-wgpu\"\n")).expect("egui-wgpu is in Cargo.lock");
|
||||
assert!(!egui_wgpu.contains("\nsource = "), "egui-wgpu must resolve to vendor/egui-wgpu, not:{egui_wgpu}");
|
||||
let painter = std::fs::read_to_string(root.join("vendor/egui-wgpu/src/winit.rs")).unwrap().replace("\r\n", "\n");
|
||||
let add_surface = painter.split("async fn add_surface(").nth(1).and_then(|s| s.split("\n fn ").next()).expect("add_surface");
|
||||
for patch in [
|
||||
"let installed = catch_errors(&device, || {\n self.install_surface(surface, viewport_id, size.width, size.height, false);\n })",
|
||||
"return Err(crate::WgpuError::ConfigureSurface(error));",
|
||||
] {
|
||||
assert!(add_surface.contains(patch), "vendor/egui-wgpu lost its surface configure patch: {patch}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn winit_carries_the_windows_11_monitor_scale_fix() {
|
||||
// Issue #324: winit 0.30.13 as released nudges a window dragged onto a monitor with another
|
||||
|
||||
@@ -36,6 +36,21 @@ at a correspondingly lower scale instead of panicking in `Surface::configure` (v
|
||||
stretched over the window: softer, but complete and lined up with the pointer. wgpu's GL backend
|
||||
copies it unscaled into a corner of the window instead.
|
||||
|
||||
## Renderer fallback
|
||||
|
||||
PdfCraft draws with wgpu, and starts again with eframe's own OpenGL renderer (glow) when wgpu fails
|
||||
before the app is created: no adapter, no device, or a window surface the device can't configure.
|
||||
The last of these was a panic, not an error, because wgpu's default error handler panics and
|
||||
eframe configures the surface before any app code runs; a 2015 Intel GPU whose driver timed out in
|
||||
`Surface::configure` on wgpu's GL backend closed PdfCraft at launch (#519). Vendored egui-wgpu
|
||||
configures a new window's surface inside `winit::catch_errors` and returns a validation,
|
||||
out-of-memory or internal error as `WgpuError::ConfigureSurface` (see `vendor/README.md`). Still a
|
||||
panic, as upstream: an error in a later reconfigure (resizing, a lost surface); a device lost
|
||||
during that first configure (wgpu reports it only to a callback, so the first frame panics, after
|
||||
the app has started); and a window shown at zero size, which is first configured on resize. If
|
||||
OpenGL can't start either, PdfCraft exits with both errors in the log. The fallback is logged at
|
||||
`error`, with the wgpu error. `PDFCRAFT_RENDERER` skips it or chooses OpenGL from the start.
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Variable | Effect |
|
||||
@@ -45,5 +60,6 @@ copies it unscaled into a corner of the window instead.
|
||||
| `XDG_DATA_HOME` | Linux/FreeBSD: base of the settings folder (`pdfcraft/`), the log folder and crash recovery |
|
||||
| `WGPU_POWER_PREF` | GPU choice; by default PdfCraft prefers the low-power (integrated) GPU |
|
||||
| `WGPU_BACKEND` | Graphics backend; by default Windows uses Direct3D 12, falling back to OpenGL |
|
||||
| `PDFCRAFT_RENDERER` | `gl` starts with OpenGL (glow) and never loads wgpu; `wgpu` reports a wgpu failure instead of retrying with OpenGL; unset, wgpu is retried with OpenGL when it can't start (see [Renderer fallback](#renderer-fallback)) |
|
||||
| `CRAFT_FONTS_DIR` | Build time: a [craft-fonts](https://github.com/storytold/craft-fonts) checkout to embed (Japanese fonts) |
|
||||
| `PDFCRAFT_SYSTEM_FONTS` | `0` stops the desktop app from using an installed font for characters its embedded fonts lack (`cargo xtask screenshots` sets it) |
|
||||
Vendored
+1
-1
@@ -14,7 +14,7 @@ the fix. Vendoring copyleft code is never allowed (plan/adr/0001).
|
||||
| lopdf | 0.45.0 | MIT | (1) PNG predictor rows longer than the data are refused before their buffers are allocated (a fuzzed `/Columns 4294967295` allocated two 4 GiB rows), and empty frames return without row buffers; (2) predictor row widths use saturating arithmetic or reject overflow; (3) sub-byte TIFF Predictor 2 scratch is bounded by the samples present in the input instead of the declared `/Colors`, aligned with [lopdf#589](https://github.com/J-F-Liu/lopdf/pull/589) | (1) `inspect::tests::png_predictor_rows_longer_than_the_data_are_refused_before_allocating`, `inspect::tests::empty_png_predictor_frames_do_not_allocate_rows`; (2) `inspect::tests::tiff_subbyte_predictor_row_width_overflow_is_refused`; (3) `inspect::tests::tiff_subbyte_predictor_scratch_is_bounded_by_available_samples`, `inspect::tests::tiff_subbyte_predictors_keep_components_rows_and_padding` |
|
||||
| hayro-syntax | 0.7.2 | Apache-2.0 OR MIT | (1) Page-tree cycle guard in `resolve_pages` (visited set + depth cap): a `/Kids` loop among compressed objects overflowed the stack and aborted the process; (2) `ccitt_size_ok`: CCITT images wider than 2^20 columns or over 2^28 pixels are not decoded (a fuzzed `/Columns 4294967295` allocated 4 GiB); (3) Flate/LZW predictor row widths are saturating `usize`, a zero width is refused and a row longer than the data is not allocated (a fuzzed `/Columns 9223372036854775807` wrapped to a 2^61-byte allocation and aborted); (4) the inline-image end-of-data check (`EI`) doesn't nest inside its own look-ahead and examines at most 256 candidates and steps (50 images with "EI" in their data ran for over ten minutes); (5) a stream whose `/Length` is wrong is recovered only when `stream` is the next token — scanning ahead to a later object's `stream` made an indirect appearance-state dictionary (Quartz checkboxes: `/AP /N` → `<< /Yes … /Off … >>`) render as a non-form, so the widget was skipped; (6) JPX rescaling uses an exact integer sample range for 1–32-bit components (32 shifted past u32, and floating-point maxima at 25–31 bits wrapped to zero), and refuses zero-sized rows; (7) `decode_limit`: Flate (through flate2 and the fallback inflater), LZW and RunLength never write past `MAX_DECODED_STREAM` (256 MiB), or, for the last filter of an image of known size, past twice its pixel bytes (a predictor tag byte per byte), at most 1 GiB; `Stream::decoded_within` for budgeted callers; a page's `/Contents` array shares one `MAX_DECODED_STREAM` (a 2 MB page whose content stream or 8 × 8 image inflated to 2 GB took the renderer past 3 GB in under a second; naming the stream twenty times in `/Contents`, 5 GB); (8) high-precision numbers (jsPDF MediaBox coordinates with over 19 digits) parse without overflowing the u64 mantissa: fractional digits past it are dropped, integer digits past it scale the value (a real, kept finite); (9) large dictionary indexes are built on first use and shared across repeated reads within each PDF (#307); the validated-byte cache is bounded, reader contexts remain separate, repairs and inline/content dictionaries bypass sharing, and small dictionaries stay eager; (10) decoded object-stream slots publish once under contention, cold loading rejects recursive dependencies, and xref repair serializes without holding recursive read guards; this permits one parser per native render pool with private worker caches; (11) `sync.rs`: a poisoned lock is recovered instead of unwrapped (a worker panic no longer cascades into every reader of the same PDF), and xref repair takes a blocking write lock instead of asserting a non-blocking one; (12) `SegmentList` (`util.rs`) returns `None` for an out-of-range index instead of panicking | (1) `cos/tests/writer.rs::page_tree_loops_in_object_streams_do_not_crash_readers`; (2) `raster::tests::absurd_mask_and_fax_sizes_are_refused_before_decoding`; (3) `raster::tests::absurd_predictor_columns_and_line_widths_render`; (4) `raster::tests::inline_images_with_ei_in_their_data_parse_in_linear_time`; (5) `raster::tests::indirect_appearance_state_dict_is_not_another_stream`; (6) `raster::tests::jpx_stream_bit_depth_boundaries_do_not_panic`, `raster::tests::jpx_32_bit_image_does_not_prevent_other_page_content_from_rendering`, `raster::tests::jpx_gray_rgb_and_alpha_keep_eight_bit_samples`, `raster::tests::jpx_streams_reject_out_of_range_bit_depths`; (7) `raster::tests::decompression_bombs_stop_at_the_stream_limit`, `raster::tests::page_contents_and_nested_forms_share_one_budget`; (8) `inspect::tests::jspdf_high_precision_mediabox_does_not_overflow_dimensions`, `object::number::tests::high_precision_float_does_not_overflow_mantissa` (hayro-syntax); (9) `object::dict::lazy_index_tests`, `raster::tests::large_resource_indexes_preserve_pixels_and_text`; (10) `data::tests`, `xref::tests::concurrent_cold_object_stream_reads_all_succeed`, `raster::tests::shared_parser_is_lazy_and_initializes_once_under_contention`; (11) `data::tests::panicking_initializer_wakes_waiters_and_prevents_retries`, `xref::tests::concurrent_cold_object_stream_reads_all_succeed`; (12) `util::tests::segment_list_rejects_out_of_range_indices` |
|
||||
| winit | 0.30.13 | Apache-2.0 | (1) on Windows 11 (build 22000 and later), `WM_DPICHANGED` applies the rect Windows suggests. It used to nudge the window onto the monitor `MonitorFromWindow` names, which during a drag is still the monitor being left; the nudge sent another `WM_DPICHANGED`, so a window dragged to a monitor with another scale factor grew and stayed on the wrong monitor at the wrong scale (#324). Backport of winit master 488c036a ([winit#4341](https://github.com/rust-windowing/winit/pull/4341)), which no 0.30 release has; Windows 10 keeps the old adjustment, as on winit master. The published crate's asset files (`docs/res/*.svg`, `examples/data/*.png`) are left out, so its `window` example doesn't build | (1) `pdfcraft` `tests::winit_carries_the_windows_11_monitor_scale_fix` keeps the patch in the build; the drag itself needs two monitors with different scale factors on Windows 11 |
|
||||
| egui-wgpu | 0.36.2 | MIT OR Apache-2.0 | (1) `winit::surface_fit`: a window's surface is configured within the device's `max_texture_dimension_2d`, both sides shrunk by one factor that egui then draws at (`pixels_per_point × render_scale`), so the whole window is drawn into the surface (on DX12 it is stretched over the window and lines up with the pointer; wgpu's GL backend copies it unscaled into a corner); the longer side becomes exactly the limit; the depth and MSAA textures follow, and a recreated surface starts again from the window's own size. egui-wgpu configured the window's size as is, and `Surface::configure` panics beyond the limit: a window restored at another monitor's scale (opening an Outlook attachment on a mixed-DPI laptop asked an 8192-pixel device for 8600 × 3423, #577) or stretched across monitors closed PdfCraft, before its window appeared when it happened at start-up ([emilk/egui#8361](https://github.com/emilk/egui/issues/8361)). The published crate leaves out egui's licence files; `LICENSE-APACHE` and `LICENSE-MIT` are from the 0.36.2 tag | (1) `pdfcraft` `tests::a_window_larger_than_the_gpu_limit_gets_a_surface_within_it`, `tests::surface_fit_never_empties_or_overflows_a_surface`, and `tests::egui_wgpu_carries_the_surface_size_fit`, which keeps the patch in the build and in the paths that size a surface |
|
||||
| egui-wgpu | 0.36.2 | MIT OR Apache-2.0 | (1) `winit::surface_fit`: a window's surface is configured within the device's `max_texture_dimension_2d`, both sides shrunk by one factor that egui then draws at (`pixels_per_point × render_scale`), so the whole window is drawn into the surface (on DX12 it is stretched over the window and lines up with the pointer; wgpu's GL backend copies it unscaled into a corner); the longer side becomes exactly the limit; the depth and MSAA textures follow, and a recreated surface starts again from the window's own size. egui-wgpu configured the window's size as is, and `Surface::configure` panics beyond the limit: a window restored at another monitor's scale (opening an Outlook attachment on a mixed-DPI laptop asked an 8192-pixel device for 8600 × 3423, #577) or stretched across monitors closed PdfCraft, before its window appeared when it happened at start-up ([emilk/egui#8361](https://github.com/emilk/egui/issues/8361)). The published crate leaves out egui's licence files; `LICENSE-APACHE` and `LICENSE-MIT` are from the 0.36.2 tag; (2) `winit::catch_errors`: a new window's surface is configured inside wgpu error scopes (validation, out of memory, internal), and a failure is returned as `WgpuError::ConfigureSurface` (for the root window at start-up eframe returns it as `Error::Wgpu`, which PdfCraft retries with OpenGL); nothing is kept for the window. wgpu's default handler panicked instead, and eframe configures the root surface before the app is created, so a driver that can't configure one closed PdfCraft at launch (a 2015 Intel GPU through wgpu's GL backend timed out waiting for the GPU to go idle, `GpuWaitTimeout`, #519). Unchanged, so still a panic: an error in a later reconfigure (resize, a lost surface), a device lost during the first configure (wgpu reports it only to a callback), and a window shown at zero size (first configured on resize) | (1) `pdfcraft` `tests::a_window_larger_than_the_gpu_limit_gets_a_surface_within_it`, `tests::surface_fit_never_empties_or_overflows_a_surface`, and `tests::egui_wgpu_carries_the_surface_size_fit`, which keeps the patch in the build and in the paths that size a surface; (2) `pdfcraft` `tests::a_gpu_error_while_a_window_is_set_up_is_returned_not_a_panic` (a real device: an error inside `catch_errors` is returned, not a panic) and `tests::egui_wgpu_returns_a_surface_it_cannot_configure_as_an_error`, which keeps it around `add_surface`'s configure |
|
||||
|
||||
Temporary: hayro is the bootstrap renderer and lopdf the bootstrap inspector (ADR-0004); M2.6 replaces them with our own devices and `cos`/`model`.
|
||||
|
||||
|
||||
Vendored
+6
@@ -62,6 +62,12 @@ pub enum WgpuError {
|
||||
#[cfg(feature = "winit")]
|
||||
#[error(transparent)]
|
||||
HandleError(#[from] ::winit::raw_window_handle::HandleError),
|
||||
|
||||
/// PdfCraft patch (#519): the device raised an error while a new window's surface was
|
||||
/// configured (see [`winit::catch_errors`]).
|
||||
#[cfg(feature = "winit")]
|
||||
#[error("The window's surface couldn't be configured: {0}")]
|
||||
ConfigureSurface(wgpu::Error),
|
||||
}
|
||||
|
||||
/// Runtime-mutable subset of [`WgpuConfiguration`].
|
||||
|
||||
Vendored
+58
-5
@@ -63,6 +63,41 @@ pub fn surface_fit(width: u32, height: u32, max_side: u32) -> (u32, u32, f32) {
|
||||
(fit(width), fit(height), scale as f32)
|
||||
}
|
||||
|
||||
/// PdfCraft patch (#519): runs `f` and returns an error it raises on `device`, instead of
|
||||
/// handing it to wgpu's uncaptured-error handler, which panics by default.
|
||||
///
|
||||
/// eframe configures a window's surface before the app is created, so no app code can install
|
||||
/// a handler in time, and a surface the device can't configure closed the app before its window
|
||||
/// appeared: wgpu's GL backend on a 2015 Intel driver timed out waiting for the GPU to go idle
|
||||
/// (`ConfigureSurfaceError::GpuWaitTimeout`). Returned as an error instead, eframe returns
|
||||
/// `Error::Wgpu` and the app can fall back (PdfCraft retries with OpenGL).
|
||||
///
|
||||
/// Validation, out-of-memory and internal errors are caught, one scope each; each scope keeps
|
||||
/// the first error of its kind, and a validation error is returned before an out-of-memory one,
|
||||
/// which is returned before an internal one. Not caught: a lost device (wgpu reports that only
|
||||
/// to the device-lost callback, so `f` seems to succeed), a panic inside `f`, and errors raised
|
||||
/// on other threads (wgpu's error scopes belong to the calling thread).
|
||||
pub async fn catch_errors<T>(
|
||||
device: &wgpu::Device,
|
||||
f: impl FnOnce() -> T,
|
||||
) -> Result<T, wgpu::Error> {
|
||||
let validation = device.push_error_scope(wgpu::ErrorFilter::Validation);
|
||||
let out_of_memory = device.push_error_scope(wgpu::ErrorFilter::OutOfMemory);
|
||||
let internal = device.push_error_scope(wgpu::ErrorFilter::Internal);
|
||||
// Should `f` panic, the guards pop their scopes as they drop, innermost first.
|
||||
let value = f();
|
||||
// Popped innermost first, as wgpu requires; each pop takes effect at once.
|
||||
let internal = internal.pop();
|
||||
let out_of_memory = out_of_memory.pop();
|
||||
let validation = validation.pop();
|
||||
let (validation, out_of_memory, internal) =
|
||||
(validation.await, out_of_memory.await, internal.await);
|
||||
match validation.or(out_of_memory).or(internal) {
|
||||
Some(error) => Err(error),
|
||||
None => Ok(value),
|
||||
}
|
||||
}
|
||||
|
||||
/// Everything you need to paint egui with [`wgpu`] on [`winit`].
|
||||
///
|
||||
/// Alternatively you can use [`crate::Renderer`] directly.
|
||||
@@ -283,13 +318,31 @@ impl Painter {
|
||||
viewport_id: ViewportId,
|
||||
size: winit::dpi::PhysicalSize<u32>,
|
||||
) -> Result<(), crate::WgpuError> {
|
||||
if self.render_state.is_none() {
|
||||
let render_state =
|
||||
// PdfCraft patch (#519): the device is kept for `catch_errors` below.
|
||||
let render_state = match self.render_state.take() {
|
||||
Some(render_state) => render_state,
|
||||
None => {
|
||||
RenderState::create(&self.config, &self.instance, Some(&surface), self.options)
|
||||
.await?;
|
||||
self.render_state = Some(render_state);
|
||||
.await?
|
||||
}
|
||||
};
|
||||
let device = render_state.device.clone();
|
||||
self.render_state = Some(render_state);
|
||||
// PdfCraft patch (#519): a surface the device can't configure is returned as an error
|
||||
// instead of wgpu's default handler panicking; for the root window at start-up, eframe
|
||||
// returns it as `Error::Wgpu` before the app is created, and the app can fall back.
|
||||
// Nothing is kept for the window. A window whose size is zero here is first configured
|
||||
// later, on resize, outside this.
|
||||
let installed = catch_errors(&device, || {
|
||||
self.install_surface(surface, viewport_id, size.width, size.height, false);
|
||||
})
|
||||
.await;
|
||||
if let Err(error) = installed {
|
||||
self.surfaces.remove(&viewport_id);
|
||||
self.depth_texture_view.remove(&viewport_id);
|
||||
self.msaa_texture_view.remove(&viewport_id);
|
||||
return Err(crate::WgpuError::ConfigureSurface(error));
|
||||
}
|
||||
self.install_surface(surface, viewport_id, size.width, size.height, false);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user