Update sampler along with texture on wgpu backend (#5122)

<!--
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).
This commit is contained in:
valadaptive 2024-09-19 03:16:42 -04:00 committed by GitHub
parent f4ed394a85
commit bb9e874c83
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 72 additions and 24 deletions

View File

@ -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,
@ -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>,
@ -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(
@ -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 {
@ -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: &[
@ -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)
}
@ -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
@ -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.");