Skip to content

Commit

Permalink
Update sampler along with texture on wgpu backend (#5122)
Browse files Browse the repository at this point in the history
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md)
before opening a Pull Request!

* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.

Please be patient! I will review your PR, but my time is limited!
-->

* Closes #5121
* [x] I have followed the instructions in the PR template

This unifies the code paths in `update_texture` somewhat, so that the
texture sampler and bind group are always replaced.

Not sure whether removing and reinserting the texture from and into the
`textures` map, or creating a new bind group, has much of a performance
impact. An alternative, as described in #5121, would be to split the
functionality for updating a texture's data from updating its options,
so that we don't have to unconditionally update the bind group (or do
something like store the options to check if they're changed).
  • Loading branch information
valadaptive authored Sep 19, 2024
1 parent f4ed394 commit bb9e874
Showing 1 changed file with 72 additions and 24 deletions.
96 changes: 72 additions & 24 deletions crates/egui-wgpu/src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,18 @@ struct SlicedBuffer {
capacity: wgpu::BufferAddress,
}

pub struct Texture {
/// The texture may be None if the `TextureId` is just a handle to a user-provided bind-group.
pub texture: Option<wgpu::Texture>,

/// Bindgroup for the texture + sampler.
pub bind_group: wgpu::BindGroup,

/// Options describing the sampler used in the bind group. This may be None if the `TextureId`
/// is just a handle to a user-provided bind-group.
pub options: Option<epaint::textures::TextureOptions>,
}

/// Renderer for a egui based GUI.
pub struct Renderer {
pipeline: wgpu::RenderPipeline,
Expand All @@ -178,7 +190,7 @@ pub struct Renderer {
/// Map of egui texture IDs to textures and their associated bindgroups (texture view +
/// sampler). The texture may be None if the `TextureId` is just a handle to a user-provided
/// sampler.
textures: HashMap<epaint::TextureId, (Option<wgpu::Texture>, wgpu::BindGroup)>,
textures: HashMap<epaint::TextureId, Texture>,
next_user_texture_id: u64,
samplers: HashMap<epaint::textures::TextureOptions, wgpu::Sampler>,

Expand Down Expand Up @@ -454,7 +466,7 @@ impl Renderer {
let index_buffer_slice = index_buffer_slices.next().unwrap();
let vertex_buffer_slice = vertex_buffer_slices.next().unwrap();

if let Some((_texture, bind_group)) = self.textures.get(&mesh.texture_id) {
if let Some(Texture { bind_group, .. }) = self.textures.get(&mesh.texture_id) {
render_pass.set_bind_group(1, bind_group, &[]);
render_pass.set_index_buffer(
self.index_buffer.buffer.slice(
Expand Down Expand Up @@ -577,26 +589,41 @@ impl Renderer {
);
};

if let Some(pos) = image_delta.pos {
// 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, _bind_group) = self
let Texture {
texture,
bind_group,
options,
} = self
.textures
.get(&id)
.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,
};
queue_write_data_to_texture(
texture.as_ref().expect("Tried to update user texture."),

(
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
},
)
} else {
// allocate a new texture
// 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 = {
crate::profile_scope!("create_texture");
device.create_texture(&wgpu::TextureDescriptor {
Expand All @@ -610,11 +637,16 @@ impl Renderer {
view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb],
})
};
let origin = wgpu::Origin3d::ZERO;
(texture, origin, None)
};

let bind_group = bind_group.unwrap_or_else(|| {
let sampler = self
.samplers
.entry(image_delta.options)
.or_insert_with(|| create_sampler(image_delta.options, device));
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label,
layout: &self.texture_bind_group_layout,
entries: &[
Expand All @@ -629,25 +661,31 @@ impl Renderer {
resource: wgpu::BindingResource::Sampler(sampler),
},
],
});
let origin = wgpu::Origin3d::ZERO;
queue_write_data_to_texture(&texture, origin);
self.textures.insert(id, (Some(texture), bind_group));
};
})
});

queue_write_data_to_texture(&texture, origin);
self.textures.insert(
id,
Texture {
texture: Some(texture),
bind_group,
options: Some(image_delta.options),
},
);
}

pub fn free_texture(&mut self, id: &epaint::TextureId) {
self.textures.remove(id);
if let Some(texture) = self.textures.remove(id).and_then(|t| t.texture) {
texture.destroy();
}
}

/// Get the WGPU texture and bind group associated to a texture that has been allocated by egui.
///
/// This could be used by custom paint hooks to render images that have been added through
/// [`epaint::Context::load_texture`](https://docs.rs/egui/latest/egui/struct.Context.html#method.load_texture).
pub fn texture(
&self,
id: &epaint::TextureId,
) -> Option<&(Option<wgpu::Texture>, wgpu::BindGroup)> {
pub fn texture(&self, id: &epaint::TextureId) -> Option<&Texture> {
self.textures.get(id)
}

Expand Down Expand Up @@ -735,7 +773,14 @@ impl Renderer {
});

let id = epaint::TextureId::User(self.next_user_texture_id);
self.textures.insert(id, (None, bind_group));
self.textures.insert(
id,
Texture {
texture: None,
bind_group,
options: None,
},
);
self.next_user_texture_id += 1;

id
Expand All @@ -755,7 +800,10 @@ impl Renderer {
) {
crate::profile_function!();

let (_user_texture, user_texture_binding) = self
let Texture {
bind_group: user_texture_binding,
..
} = self
.textures
.get_mut(&id)
.expect("Tried to update a texture that has not been allocated yet.");
Expand Down

0 comments on commit bb9e874

Please sign in to comment.