mirror of
https://github.com/emilk/egui.git
synced 2026-06-27 07:03:14 -04:00
## Symptom Fix this long-standing, occasional bug, that can cause text to look compressed and "drunk": <img width="552" height="226" alt="Screenshot 2026-06-22 at 13 12 56" src="https://github.com/user-attachments/assets/9b1abad4-5ef6-4771-8168-f201afc341ab" /> ## Root cause `epaint::TextureAtlas::take_delta` is fire-and-forget: it resets the dirty region as soon as it hands out a delta, assuming the delta will be uploaded. Atlas growth always emits a **full** `ImageDelta` (`pos: None`) which recreates the GPU texture at the new size — *as long as it is applied*. But both native integrations applied `textures_delta` inside skippable code paths: - **wgpu** (`egui-wgpu/src/winit.rs`): textures were uploaded only *after* surface-dependent early-returns (`render_state` / `surfaces.get_mut(viewport_id)` missing). Texture uploads are device-level and don't need a surface. - **glow** (`eframe/src/native/glow_integration.rs`): textures were uploaded only inside `if is_visible { … }` (and after a viewport-missing early-return), while `integration.update` still ran and grew the atlas. The root window even starts hidden on purpose (`with_visible(false)`, to avoid a startup white flash), so the very first frames hit this. When the delta was dropped, the GPU font texture stayed smaller than the CPU-side atlas; every glyph UV (normalized by the CPU atlas size) then sampled the wrong rows until the next full atlas recreation. wgpu/Metal can't detect this — the read is in-bounds, just the wrong row. ## Fixes - **wgpu**: apply `textures_delta.set` right after `render_state` is obtained, **before** any surface-dependent early-return. `free` still runs after submit (unchanged). - **glow**: apply `textures_delta.set` (and `free`) regardless of `is_visible`, making the GL context current when there's anything to upload; only tessellation/paint/swap stay gated on visibility. - **debug assert** in `egui-wgpu`'s `Renderer::update_texture`: a full delta must (re)create the GPU texture at exactly the delta size — catches any future CPU/GPU size desync at the source. ## wgpu ruled out Confirmed the desync is **not** inside wgpu: Metal `create_texture` uses the exact descriptor size, and `queue.write_texture` validates against the texture's own live `desc` — a single texture can't have CPU/GPU sizes disagree. The mismatch is born at the egui boundary (atlas size for UVs vs. last-applied upload), which wgpu cannot see. ## Testing note A headless regression test of `paint_and_update_textures` isn't practical (it needs a real winit window; `render_state` is private with no surface-less setter). I verified the failure *mechanism* separately on macOS/Metal (texture lagging the atlas → silent wrong-row sampling, no wgpu error), but that demo did not exercise the fixed code path, so it's not included. The fixes rest on the reasoning above. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
egui-wgpu
This crates provides bindings between egui and wgpu.
This was originally hosted at https://github.com/hasenbanck/egui_wgpu_backend