wgpu: Cache atlas bind groups (#64434)
Previously, we created texture atlas bind groups every frame (once per sprite group). This showed up in delta web mobile profiling. This PR reuses them across frames, which saved ~5% frame time on the delta web thread view on a pixel 10 pro. --- Release Notes: - [GPUI]: Improved performance on wgpu by caching atlas bind groups
This commit is contained in:
1 parent
329142ebfc
commit
5795c06954
2 files changed
+128
-22
No files matched your search
@@ -35,10 +35,13 @@ struct WgpuAtlasTextures {
|
||||
color_texture_format: wgpu::TextureFormat,
|
||||
storage: WgpuAtlasStorage,
|
||||
pending_uploads: Vec<PendingUpload>,
|
||||
next_texture_generation: u64,
|
||||
}
|
||||
|
||||
pub struct WgpuTextureInfo {
|
||||
pub view: wgpu::TextureView,
|
||||
/// Distinguishes new textures that reuse an [`AtlasTextureId`].
|
||||
pub generation: u64,
|
||||
}
|
||||
|
||||
impl WgpuAtlas {
|
||||
@@ -55,6 +58,7 @@ impl WgpuAtlas {
|
||||
color_texture_format,
|
||||
storage: WgpuAtlasStorage::default(),
|
||||
pending_uploads: Vec::new(),
|
||||
next_texture_generation: 0,
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -76,6 +80,7 @@ impl WgpuAtlas {
|
||||
let texture = &lock.backend.storage[id];
|
||||
WgpuTextureInfo {
|
||||
view: texture.view.clone(),
|
||||
generation: texture.generation,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,12 +216,15 @@ impl WgpuAtlasTextures {
|
||||
|
||||
let texture_list = &mut self.storage[kind];
|
||||
let index = texture_list.free_list.pop();
|
||||
let generation = self.next_texture_generation;
|
||||
self.next_texture_generation = self.next_texture_generation.wrapping_add(1);
|
||||
|
||||
let atlas_texture = WgpuAtlasTexture {
|
||||
id: AtlasTextureId {
|
||||
index: index.unwrap_or(texture_list.textures.len()) as u32,
|
||||
kind,
|
||||
},
|
||||
generation,
|
||||
allocator: BucketedAtlasAllocator::new(device_size_to_etagere(size)),
|
||||
format,
|
||||
texture,
|
||||
@@ -339,6 +347,7 @@ impl ops::Index<AtlasTextureId> for WgpuAtlasStorage {
|
||||
|
||||
struct WgpuAtlasTexture {
|
||||
id: AtlasTextureId,
|
||||
generation: u64,
|
||||
allocator: BucketedAtlasAllocator,
|
||||
texture: wgpu::Texture,
|
||||
view: wgpu::TextureView,
|
||||
@@ -501,6 +510,45 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reused_texture_id_has_new_generation() -> anyhow::Result<()> {
|
||||
let (device, queue) = test_device_and_queue()?;
|
||||
let atlas = WgpuAtlas::new(device, queue, wgpu::TextureFormat::Bgra8Unorm);
|
||||
let size = Size {
|
||||
width: DevicePixels(700),
|
||||
height: DevicePixels(700),
|
||||
};
|
||||
let make_key = |image_id| {
|
||||
AtlasKey::Image(RenderImageParams {
|
||||
image_id: ImageId(image_id),
|
||||
frame_index: 0,
|
||||
})
|
||||
};
|
||||
let insert = |key: AtlasKey| {
|
||||
atlas
|
||||
.get_or_insert_with(key, &mut || {
|
||||
Ok(Some((
|
||||
size,
|
||||
Cow::Owned(vec![0; size.width.0 as usize * size.height.0 as usize * 4]),
|
||||
)))
|
||||
})
|
||||
.expect("allocation should succeed")
|
||||
.expect("callback returns Some")
|
||||
};
|
||||
|
||||
let first_key = make_key(1);
|
||||
let first_tile = insert(first_key.clone());
|
||||
let first_generation = atlas.get_texture_info(first_tile.texture_id).generation;
|
||||
atlas.remove(&first_key);
|
||||
|
||||
let second_tile = insert(make_key(2));
|
||||
let second_generation = atlas.get_texture_info(second_tile.texture_id).generation;
|
||||
|
||||
assert_eq!(second_tile.texture_id, first_tile.texture_id);
|
||||
assert_ne!(second_generation, first_generation);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swizzle_upload_data_preserves_bgra_uploads() {
|
||||
let input = vec![0x10, 0x20, 0x30, 0x40];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{CompositorGpuHint, WgpuAtlas, WgpuContext};
|
||||
use anyhow::{Context as _, Result};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use collections::FxHashMap;
|
||||
use gpui::{
|
||||
AtlasTextureId, Background, Bounds, DevicePixels, GpuSpecs, Path, Point, PrimitiveBatch,
|
||||
ScaledPixels, Scene, Size, get_gamma_correction_ratios,
|
||||
@@ -8,6 +9,7 @@ use gpui::{
|
||||
use log::warn;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
||||
use smallvec::SmallVec;
|
||||
use std::cell::RefCell;
|
||||
use std::num::NonZeroU64;
|
||||
use std::ops::Range;
|
||||
@@ -184,6 +186,7 @@ struct WgpuResources {
|
||||
pipelines: WgpuPipelines,
|
||||
bind_group_layouts: WgpuBindGroupLayouts,
|
||||
atlas_sampler: wgpu::Sampler,
|
||||
atlas_texture_bind_groups: FxHashMap<AtlasTextureId, CachedTextureBindGroup>,
|
||||
globals_buffer: wgpu::Buffer,
|
||||
globals_bind_group: wgpu::BindGroup,
|
||||
path_globals_bind_group: wgpu::BindGroup,
|
||||
@@ -194,6 +197,11 @@ struct WgpuResources {
|
||||
path_msaa_view: Option<wgpu::TextureView>,
|
||||
}
|
||||
|
||||
struct CachedTextureBindGroup {
|
||||
texture_generation: u64,
|
||||
bind_group: wgpu::BindGroup,
|
||||
}
|
||||
|
||||
impl WgpuResources {
|
||||
fn invalidate_intermediate_textures(&mut self) {
|
||||
self.path_intermediate_texture = None;
|
||||
@@ -568,6 +576,7 @@ impl WgpuRenderer {
|
||||
pipelines,
|
||||
bind_group_layouts,
|
||||
atlas_sampler,
|
||||
atlas_texture_bind_groups: FxHashMap::default(),
|
||||
globals_buffer,
|
||||
globals_bind_group,
|
||||
path_globals_bind_group,
|
||||
@@ -1418,6 +1427,7 @@ impl WgpuRenderer {
|
||||
scene.polychrome_sprites.len(),
|
||||
)
|
||||
})?;
|
||||
self.prepare_texture_bind_groups(scene);
|
||||
|
||||
let mut encoder =
|
||||
self.resources()
|
||||
@@ -1498,13 +1508,15 @@ impl WgpuRenderer {
|
||||
instance_range(range),
|
||||
&mut pass,
|
||||
),
|
||||
PrimitiveBatch::MonochromeSprites { texture_id, range } => self.draw_sprites(
|
||||
&instance_bindings.monochrome_sprites,
|
||||
texture_id,
|
||||
&self.resources().pipelines.mono_sprites,
|
||||
instance_range(range),
|
||||
&mut pass,
|
||||
),
|
||||
PrimitiveBatch::MonochromeSprites { texture_id, range } => {
|
||||
self.draw_sprites(
|
||||
&instance_bindings.monochrome_sprites,
|
||||
texture_id,
|
||||
&self.resources().pipelines.mono_sprites,
|
||||
instance_range(range),
|
||||
&mut pass,
|
||||
)?;
|
||||
}
|
||||
PrimitiveBatch::SubpixelSprites { texture_id, range } => {
|
||||
let resources = self.resources();
|
||||
self.draw_sprites(
|
||||
@@ -1517,15 +1529,17 @@ impl WgpuRenderer {
|
||||
.unwrap_or(&resources.pipelines.mono_sprites),
|
||||
instance_range(range),
|
||||
&mut pass,
|
||||
);
|
||||
)?;
|
||||
}
|
||||
PrimitiveBatch::PolychromeSprites { texture_id, range } => {
|
||||
self.draw_sprites(
|
||||
&instance_bindings.polychrome_sprites,
|
||||
texture_id,
|
||||
&self.resources().pipelines.poly_sprites,
|
||||
instance_range(range),
|
||||
&mut pass,
|
||||
)?;
|
||||
}
|
||||
PrimitiveBatch::PolychromeSprites { texture_id, range } => self.draw_sprites(
|
||||
&instance_bindings.polychrome_sprites,
|
||||
texture_id,
|
||||
&self.resources().pipelines.poly_sprites,
|
||||
instance_range(range),
|
||||
&mut pass,
|
||||
),
|
||||
// Surfaces are macOS-only for video playback and are not
|
||||
// implemented by the WGPU renderer.
|
||||
PrimitiveBatch::Surfaces(_surfaces) => {}
|
||||
@@ -1602,6 +1616,47 @@ impl WgpuRenderer {
|
||||
})
|
||||
}
|
||||
|
||||
fn prepare_texture_bind_groups(&mut self, scene: &Scene) {
|
||||
let mut texture_ids = SmallVec::<[AtlasTextureId; 8]>::new();
|
||||
for batch in scene.batches() {
|
||||
let texture_id = match batch {
|
||||
PrimitiveBatch::MonochromeSprites { texture_id, .. }
|
||||
| PrimitiveBatch::SubpixelSprites { texture_id, .. }
|
||||
| PrimitiveBatch::PolychromeSprites { texture_id, .. } => texture_id,
|
||||
_ => continue,
|
||||
};
|
||||
if !texture_ids.contains(&texture_id) {
|
||||
texture_ids.push(texture_id);
|
||||
}
|
||||
}
|
||||
|
||||
self.resources_mut()
|
||||
.atlas_texture_bind_groups
|
||||
.retain(|texture_id, _| texture_ids.contains(texture_id));
|
||||
|
||||
for texture_id in texture_ids {
|
||||
let texture_info = self.atlas.get_texture_info(texture_id);
|
||||
let is_current = self
|
||||
.resources()
|
||||
.atlas_texture_bind_groups
|
||||
.get(&texture_id)
|
||||
.is_some_and(|cached| cached.texture_generation == texture_info.generation);
|
||||
if is_current {
|
||||
continue;
|
||||
}
|
||||
|
||||
let bind_group =
|
||||
self.create_texture_bind_group("atlas_texture_bind_group", &texture_info.view);
|
||||
self.resources_mut().atlas_texture_bind_groups.insert(
|
||||
texture_id,
|
||||
CachedTextureBindGroup {
|
||||
texture_generation: texture_info.generation,
|
||||
bind_group,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_instances(
|
||||
&self,
|
||||
instances: &InstanceBinding,
|
||||
@@ -1628,22 +1683,25 @@ impl WgpuRenderer {
|
||||
pipeline: &wgpu::RenderPipeline,
|
||||
range: Range<u32>,
|
||||
pass: &mut wgpu::RenderPass<'_>,
|
||||
) {
|
||||
) -> Result<()> {
|
||||
if range.is_empty() {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
let texture_info = self.atlas.get_texture_info(texture_id);
|
||||
let texture =
|
||||
self.create_texture_bind_group("atlas_texture_bind_group", &texture_info.view);
|
||||
let resources = self.resources();
|
||||
let texture = resources
|
||||
.atlas_texture_bind_groups
|
||||
.get(&texture_id)
|
||||
.context("missing atlas texture bind group")?;
|
||||
pass.set_pipeline(pipeline);
|
||||
pass.set_bind_group(0, &self.resources().globals_bind_group, &[]);
|
||||
pass.set_bind_group(0, &resources.globals_bind_group, &[]);
|
||||
pass.set_bind_group(1, &sprite_instances.bind_group, &[]);
|
||||
pass.set_bind_group(2, &texture, &[]);
|
||||
pass.set_bind_group(2, &texture.bind_group, &[]);
|
||||
pass.draw(
|
||||
0..4,
|
||||
sprite_instances.first_instance + range.start
|
||||
..sprite_instances.first_instance + range.end,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn instance_bytes<T>(instances: &[T]) -> &[u8] {
|
||||
|
||||
Reference in new issue
Block a user