diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index c42b0aa68..c44a70153 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -688,28 +688,30 @@ impl GlowWinitRunning<'_> { egui_winit.handle_platform_output_with_event_loop(&window, event_loop, platform_output); + // Upload textures even when not visible: the atlas dirty region is already + // consumed, so dropping the delta would desync the font texture. + let has_texture_updates = !textures_delta.set.is_empty() || !textures_delta.free.is_empty(); + if is_visible || has_texture_updates { + // We may need to switch contexts again, because of immediate viewports: + frame_timer.pause(); + change_gl_context(current_gl_context, not_current_gl_context, gl_surface); + frame_timer.resume(); + } + + for (id, image_delta) in &textures_delta.set { + painter.set_texture(*id, image_delta); + } + if is_visible { let clipped_primitives = integration.egui_ctx.tessellate(shapes, pixels_per_point); - { - // We may need to switch contexts again, because of immediate viewports: - frame_timer.pause(); - change_gl_context(current_gl_context, not_current_gl_context, gl_surface); - frame_timer.resume(); - } - let screen_size_in_pixels: [u32; 2] = window.inner_size().into(); if !clear_before_update { painter.clear(screen_size_in_pixels, clear_color); } - painter.paint_and_update_textures( - screen_size_in_pixels, - pixels_per_point, - &clipped_primitives, - &textures_delta, - ); + painter.paint_primitives(screen_size_in_pixels, pixels_per_point, &clipped_primitives); { for action in viewport.actions_requested.drain(..) { @@ -771,6 +773,11 @@ impl GlowWinitRunning<'_> { } } + // Free textures *after* painting, since they may still be used in the frame we just drew. + for id in &textures_delta.free { + painter.free_texture(*id); + } + glutin.handle_viewport_output(event_loop, &integration.egui_ctx, &viewport_output); integration.report_frame_time(frame_timer.total_time_sec()); // don't count auto-save time as part of regular frame time diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index e55f7581a..ff850af33 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -729,6 +729,16 @@ impl Renderer { }); queue_write_data_to_texture(&texture, origin); + + // A full update must (re)create the texture at exactly the delta's size, + // or glyph UVs (normalized by the CPU atlas size) will sample the wrong rows. + debug_assert!( + image_delta.pos.is_some() || [texture.width(), texture.height()] == [width, height], + "egui texture {id:?}: GPU texture is {}x{} but full delta is {width}x{height}", + texture.width(), + texture.height(), + ); + self.textures.insert( id, Texture { diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 78817e879..2da393e49 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -543,6 +543,21 @@ impl Painter { commands_submitted: false, }; + { + // Upload textures before the surface-dependent early-returns below: + // uploads only need the device + queue, and the atlas dirty region is + // already consumed, so dropping the delta would desync the font texture. + let mut renderer = render_state.renderer.write(); + for (id, image_delta) in &textures_delta.set { + renderer.update_texture( + &render_state.device, + &render_state.queue, + *id, + image_delta, + ); + } + } + let Some(surface_state) = self.surfaces.get_mut(&viewport_id) else { return vsync_sec; }; @@ -562,15 +577,6 @@ impl Painter { let user_cmd_bufs = { let mut renderer = render_state.renderer.write(); - for (id, image_delta) in &textures_delta.set { - renderer.update_texture( - &render_state.device, - &render_state.queue, - *id, - image_delta, - ); - } - renderer.update_buffers( &render_state.device, &render_state.queue,