From 211d19090a8f8b336b4e033a213520ee803d642c Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Thu, 30 Jul 2026 22:48:52 +0200 Subject: [PATCH] Leave a hosted viewport's texture uploads to the viewport hosting it A pass ends by taking the `Context`'s texture uploads for the backend to apply. That is wrong for a hosted viewport: the application paints it, and paints the viewport hosting it, so the uploads were taken by code that has no way to give them back. `TextureManager::set` and `free` both assert on ids the pass had already finished with, and a freed texture cannot be re-queued at all, since `free` decrements a retain count that has already reached zero. Leaving them in place means the hosting viewport's pass takes them, as it would have without the hosted viewport, and its backend applies them as usual. Immediate viewports keep taking theirs, because they are painted during the nested pass and their painter needs the uploads by then. `TextureManager::pending_delta` lets an application that paints part way through a frame apply what is pending without taking it. Applying an upload twice is harmless; the second time re-uploads the same data. Co-Authored-By: Claude Opus 5 (1M context) --- crates/egui/src/context.rs | 12 +++++++++++- crates/epaint/src/textures.rs | 9 +++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index c96db4f47..ba8bcadb8 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2599,7 +2599,17 @@ impl ContextImpl { } // Inform the backend of all textures that have been updated (including font atlas). - let textures_delta = self.tex_manager.0.write().take_delta(); + // + // A hosted viewport is the exception. It is painted by the application, which also + // paints the viewport hosting it, so leaving the uploads in place lets that one pick + // them up. Taking them here would hand them to code with no way to give them back: + // a freed texture cannot be re-queued, because `TextureManager::free` decrements a + // retain count that has already reached zero. + let textures_delta = if viewport.class == ViewportClass::Hosted { + Default::default() + } else { + self.tex_manager.0.write().take_delta() + }; let mut platform_output: PlatformOutput = std::mem::take(&mut viewport.output); diff --git a/crates/epaint/src/textures.rs b/crates/epaint/src/textures.rs index 1c4104a6e..c51f20e38 100644 --- a/crates/epaint/src/textures.rs +++ b/crates/epaint/src/textures.rs @@ -103,6 +103,15 @@ impl TextureManager { std::mem::take(&mut self.delta) } + /// Changes since the last [`Self::take_delta`], without taking them. + /// + /// Useful if you need to paint something part-way through a frame, before whoever owns + /// the painting subsystem has had a chance to apply these. Applying them twice does no + /// harm; the second time simply re-uploads the same data. + pub fn pending_delta(&self) -> &TexturesDelta { + &self.delta + } + /// Get meta-data about a specific texture. pub fn meta(&self, id: TextureId) -> Option<&TextureMeta> { self.metas.get(&id)