emilk/egui · error
Tried to update user texture.
Error message
Tried to update user texture.
What it means
egui-wgpu's update_texture panics with "Tried to update user texture." when an ImageDelta carries a position (an update to an existing texture) but the Texture entry stored under that egui TextureId has texture == None. A None texture means the id refers to a user-managed native wgpu texture registered via register_native_texture, not one egui itself allocated, so egui has no texture of its own to re-upload pixels into. The library panics instead of returning a Result because texture updates happen during the render callback.
Solutions
- Never send ImageDelta updates with pos for a TextureId registered via register_native_texture; user textures must be updated by you with your own wgpu queue.write_texture call.
- If the image should be managed by egui, allocate it through egui (e.g. ColorImage loaded into a TextureHandle) so the Texture entry has Some(texture) and Some(options).
- To replace a native texture's contents, create the new texture, then re-register it (register_native_texture) or use update_texture_with_options on the new id rather than issuing a pos-update on the old id.
- Verify which code path produced the TextureId (allocate_user_texture vs register_native_texture) before issuing deltas.
Example fix
// before: egui tries to partially update a native texture
let id = renderer.register_native_texture(&device, &view, wgpu::FilterMode::Linear);
ui.image(egui::ImageSource::Texture(egui::TextureId::User(id.into())));
textures.set(id, ImageDelta { image, pos: Some([0, 0]), options: Default::default() }); // panics
// after: update the native texture yourself
queue.write_texture(
wgpu::TexelCopyTextureInfo { texture: &native_texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All },
&pixels,
wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(4 * width), rows_per_image: None },
size,
); Defensive patterns
Strategy: validation
Validate before calling
// Rust: verify the id was allocated by egui, not registered as a native/user texture
fn can_send_pos_delta(managed_ids: &HashSet<egui::TextureId>, id: egui::TextureId) -> bool {
managed_ids.contains(&id)
} Type guard
fn is_egui_managed(managed_ids: &HashSet<egui::TextureId>, id: egui::TextureId) -> bool {
managed_ids.contains(&id)
} Prevention
- Track the provenance of every TextureId (native vs egui-allocated) in your own registry.
- Never emit ImageDelta with Some(pos) for register_native_texture ids; upload to native textures with queue.write_texture yourself.
- Prefer TextureHandle-based images so egui manages allocation and updates end to end.
When it happens
Trigger: Calling Renderer::update_texture/update (or painting an egui Image) with a TextureId created by register_native_texture, while the ImageDelta has Some(pos), i.e. egui treats it as a partial update of an existing texture. Also triggered when egui's TextureManager emits a delta with pos for a handle that was backed by a native texture.
Common situations: Mixing egui::load::TextureHandle images with register_native_texture-created ids; passing an egui user texture id into code that assumes egui allocated it; screen-reader/font-atlas partial updates applied to a native texture; version migrations where the same TextureId is now backed by a native texture.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Tried to update a texture that has not been allocated yet.
- Context::root_ui was called while the root Ui was already…
- Could not create Date
- Could not create Date
- Failed to create image
AI-assisted analysis of emilk/egui@441971a776 (2026-09-12).
Data as JSON: /api/errors/65c8e3cc251a79b4.
Report an issue: GitHub.
Appendix: source
Thrown at crates/egui-wgpu/src/renderer.rs:700
size,
);
};
// Use same label for all resources associated with this texture id (no point in retyping the type)
let label_str = format!("egui_texid_{id:?}");
let label = Some(label_str.as_str());
let (texture, origin, bind_group) = if let Some(pos) = image_delta.pos {
// update the existing texture
let Texture {
texture,
bind_group,
options,
} = self
.textures
.remove(&id)
.expect("Tried to update a texture that has not been allocated yet.");
let texture = texture.expect("Tried to update user texture.");
let options = options.expect("Tried to update user texture.");
let origin = wgpu::Origin3d {
x: pos[0] as u32,
y: pos[1] as u32,
z: 0,
};
(
texture,
origin,
// If the TextureOptions are the same as the previous ones, we can reuse the bind group. Otherwise we
// have to recreate it.
if image_delta.options == options {
Some(bind_group)
} else {
None
},
)View on GitHub (pinned to 441971a776)