Merge pull request #2357 from iced-rs/wgpu/use-staging-belt

Use a `StagingBelt` in `iced_wgpu` for regular buffer uploads
This commit is contained in:
Héctor Ramón 2024-03-30 23:49:26 +01:00 committed by GitHub
commit c7a4fad4a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 156 additions and 53 deletions

View file

@ -129,7 +129,7 @@ cosmic-text = "0.10"
dark-light = "1.0" dark-light = "1.0"
futures = "0.3" futures = "0.3"
glam = "0.25" glam = "0.25"
glyphon = "0.5" glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "ceed55403ce53e120ce9d1fae17dcfe388726118" }
guillotiere = "0.6" guillotiere = "0.6"
half = "2.2" half = "2.2"
image = "0.24" image = "0.24"
@ -155,7 +155,6 @@ thiserror = "1.0"
tiny-skia = "0.11" tiny-skia = "0.11"
tokio = "1.0" tokio = "1.0"
tracing = "0.1" tracing = "0.1"
xxhash-rust = { version = "0.8", features = ["xxh3"] }
unicode-segmentation = "1.0" unicode-segmentation = "1.0"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
wasm-timer = "0.2" wasm-timer = "0.2"

View file

@ -21,10 +21,10 @@ log.workspace = true
num-traits.workspace = true num-traits.workspace = true
once_cell.workspace = true once_cell.workspace = true
palette.workspace = true palette.workspace = true
rustc-hash.workspace = true
smol_str.workspace = true smol_str.workspace = true
thiserror.workspace = true thiserror.workspace = true
web-time.workspace = true web-time.workspace = true
xxhash-rust.workspace = true
dark-light.workspace = true dark-light.workspace = true
dark-light.optional = true dark-light.optional = true

View file

@ -1,7 +1,7 @@
/// The hasher used to compare layouts. /// The hasher used to compare layouts.
#[allow(missing_debug_implementations)] // Doesn't really make sense to have debug on the hasher state anyways. #[allow(missing_debug_implementations)] // Doesn't really make sense to have debug on the hasher state anyways.
#[derive(Default)] #[derive(Default)]
pub struct Hasher(xxhash_rust::xxh3::Xxh3); pub struct Hasher(rustc_hash::FxHasher);
impl core::hash::Hasher for Hasher { impl core::hash::Hasher for Hasher {
fn write(&mut self, bytes: &[u8]) { fn write(&mut self, bytes: &[u8]) {

View file

@ -34,7 +34,6 @@ raw-window-handle.workspace = true
rustc-hash.workspace = true rustc-hash.workspace = true
thiserror.workspace = true thiserror.workspace = true
unicode-segmentation.workspace = true unicode-segmentation.workspace = true
xxhash-rust.workspace = true
image.workspace = true image.workspace = true
image.optional = true image.optional = true

View file

@ -2,9 +2,9 @@
use crate::core::{Font, Size}; use crate::core::{Font, Size};
use crate::text; use crate::text;
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
use std::collections::hash_map; use std::collections::hash_map;
use std::hash::{BuildHasher, Hash, Hasher}; use std::hash::{Hash, Hasher};
/// A store of recently used sections of text. /// A store of recently used sections of text.
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
@ -13,11 +13,8 @@ pub struct Cache {
entries: FxHashMap<KeyHash, Entry>, entries: FxHashMap<KeyHash, Entry>,
aliases: FxHashMap<KeyHash, KeyHash>, aliases: FxHashMap<KeyHash, KeyHash>,
recently_used: FxHashSet<KeyHash>, recently_used: FxHashSet<KeyHash>,
hasher: HashBuilder,
} }
type HashBuilder = xxhash_rust::xxh3::Xxh3Builder;
impl Cache { impl Cache {
/// Creates a new empty [`Cache`]. /// Creates a new empty [`Cache`].
pub fn new() -> Self { pub fn new() -> Self {
@ -35,7 +32,7 @@ impl Cache {
font_system: &mut cosmic_text::FontSystem, font_system: &mut cosmic_text::FontSystem,
key: Key<'_>, key: Key<'_>,
) -> (KeyHash, &mut Entry) { ) -> (KeyHash, &mut Entry) {
let hash = key.hash(self.hasher.build_hasher()); let hash = key.hash(FxHasher::default());
if let Some(hash) = self.aliases.get(&hash) { if let Some(hash) = self.aliases.get(&hash) {
let _ = self.recently_used.insert(*hash); let _ = self.recently_used.insert(*hash);
@ -77,7 +74,7 @@ impl Cache {
] { ] {
if key.bounds != bounds { if key.bounds != bounds {
let _ = self.aliases.insert( let _ = self.aliases.insert(
Key { bounds, ..key }.hash(self.hasher.build_hasher()), Key { bounds, ..key }.hash(FxHasher::default()),
hash, hash,
); );
} }

View file

@ -25,7 +25,6 @@ log.workspace = true
rustc-hash.workspace = true rustc-hash.workspace = true
softbuffer.workspace = true softbuffer.workspace = true
tiny-skia.workspace = true tiny-skia.workspace = true
xxhash-rust.workspace = true
resvg.workspace = true resvg.workspace = true
resvg.optional = true resvg.optional = true

View file

@ -1,3 +1,4 @@
use crate::buffer;
use crate::core::{Color, Size, Transformation}; use crate::core::{Color, Size, Transformation};
use crate::graphics::backend; use crate::graphics::backend;
use crate::graphics::color; use crate::graphics::color;
@ -30,6 +31,7 @@ pub struct Backend {
pipeline_storage: pipeline::Storage, pipeline_storage: pipeline::Storage,
#[cfg(any(feature = "image", feature = "svg"))] #[cfg(any(feature = "image", feature = "svg"))]
image_pipeline: image::Pipeline, image_pipeline: image::Pipeline,
staging_belt: wgpu::util::StagingBelt,
} }
impl Backend { impl Backend {
@ -61,6 +63,13 @@ impl Backend {
#[cfg(any(feature = "image", feature = "svg"))] #[cfg(any(feature = "image", feature = "svg"))]
image_pipeline, image_pipeline,
// TODO: Resize belt smartly (?)
// It would be great if the `StagingBelt` API exposed methods
// for introspection to detect when a resize may be worth it.
staging_belt: wgpu::util::StagingBelt::new(
buffer::MAX_WRITE_SIZE as u64,
),
} }
} }
@ -105,6 +114,8 @@ impl Backend {
&layers, &layers,
); );
self.staging_belt.finish();
self.render( self.render(
device, device,
encoder, encoder,
@ -123,12 +134,20 @@ impl Backend {
self.image_pipeline.end_frame(); self.image_pipeline.end_frame();
} }
/// Recalls staging memory for future uploads.
///
/// This method should be called after the command encoder
/// has been submitted.
pub fn recall(&mut self) {
self.staging_belt.recall();
}
fn prepare( fn prepare(
&mut self, &mut self,
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, queue: &wgpu::Queue,
format: wgpu::TextureFormat, format: wgpu::TextureFormat,
_encoder: &mut wgpu::CommandEncoder, encoder: &mut wgpu::CommandEncoder,
scale_factor: f32, scale_factor: f32,
target_size: Size<u32>, target_size: Size<u32>,
transformation: Transformation, transformation: Transformation,
@ -144,7 +163,8 @@ impl Backend {
if !layer.quads.is_empty() { if !layer.quads.is_empty() {
self.quad_pipeline.prepare( self.quad_pipeline.prepare(
device, device,
queue, encoder,
&mut self.staging_belt,
&layer.quads, &layer.quads,
transformation, transformation,
scale_factor, scale_factor,
@ -157,7 +177,8 @@ impl Backend {
self.triangle_pipeline.prepare( self.triangle_pipeline.prepare(
device, device,
queue, encoder,
&mut self.staging_belt,
&layer.meshes, &layer.meshes,
scaled, scaled,
); );
@ -171,8 +192,8 @@ impl Backend {
self.image_pipeline.prepare( self.image_pipeline.prepare(
device, device,
queue, encoder,
_encoder, &mut self.staging_belt,
&layer.images, &layer.images,
scaled, scaled,
scale_factor, scale_factor,
@ -184,6 +205,7 @@ impl Backend {
self.text_pipeline.prepare( self.text_pipeline.prepare(
device, device,
queue, queue,
encoder,
&layer.text, &layer.text,
layer.bounds, layer.bounds,
scale_factor, scale_factor,

View file

@ -1,6 +1,13 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use std::num::NonZeroU64;
use std::ops::RangeBounds; use std::ops::RangeBounds;
pub const MAX_WRITE_SIZE: usize = 100 * 1024;
#[allow(unsafe_code)]
const MAX_WRITE_SIZE_U64: NonZeroU64 =
unsafe { NonZeroU64::new_unchecked(MAX_WRITE_SIZE as u64) };
#[derive(Debug)] #[derive(Debug)]
pub struct Buffer<T> { pub struct Buffer<T> {
label: &'static str, label: &'static str,
@ -61,12 +68,46 @@ impl<T: bytemuck::Pod> Buffer<T> {
/// Returns the size of the written bytes. /// Returns the size of the written bytes.
pub fn write( pub fn write(
&mut self, &mut self,
queue: &wgpu::Queue, device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
offset: usize, offset: usize,
contents: &[T], contents: &[T],
) -> usize { ) -> usize {
let bytes: &[u8] = bytemuck::cast_slice(contents); let bytes: &[u8] = bytemuck::cast_slice(contents);
queue.write_buffer(&self.raw, offset as u64, bytes); let mut bytes_written = 0;
// Split write into multiple chunks if necessary
while bytes_written + MAX_WRITE_SIZE < bytes.len() {
belt.write_buffer(
encoder,
&self.raw,
(offset + bytes_written) as u64,
MAX_WRITE_SIZE_U64,
device,
)
.copy_from_slice(
&bytes[bytes_written..bytes_written + MAX_WRITE_SIZE],
);
bytes_written += MAX_WRITE_SIZE;
}
// There will always be some bytes left, since the previous
// loop guarantees `bytes_written < bytes.len()`
let bytes_left = ((bytes.len() - bytes_written) as u64)
.try_into()
.expect("non-empty write");
// Write them
belt.write_buffer(
encoder,
&self.raw,
(offset + bytes_written) as u64,
bytes_left,
device,
)
.copy_from_slice(&bytes[bytes_written..]);
self.offsets.push(offset as u64); self.offsets.push(offset as u64);

View file

@ -83,21 +83,31 @@ impl Layer {
fn prepare( fn prepare(
&mut self, &mut self,
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
nearest_instances: &[Instance], nearest_instances: &[Instance],
linear_instances: &[Instance], linear_instances: &[Instance],
transformation: Transformation, transformation: Transformation,
) { ) {
queue.write_buffer( let uniforms = Uniforms {
transform: transformation.into(),
};
let bytes = bytemuck::bytes_of(&uniforms);
belt.write_buffer(
encoder,
&self.uniforms, &self.uniforms,
0, 0,
bytemuck::bytes_of(&Uniforms { (bytes.len() as u64).try_into().expect("Sized uniforms"),
transform: transformation.into(), device,
}), )
); .copy_from_slice(bytes);
self.nearest.upload(device, queue, nearest_instances); self.nearest
self.linear.upload(device, queue, linear_instances); .upload(device, encoder, belt, nearest_instances);
self.linear.upload(device, encoder, belt, linear_instances);
} }
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
@ -158,7 +168,8 @@ impl Data {
fn upload( fn upload(
&mut self, &mut self,
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
instances: &[Instance], instances: &[Instance],
) { ) {
self.instance_count = instances.len(); self.instance_count = instances.len();
@ -168,7 +179,7 @@ impl Data {
} }
let _ = self.instances.resize(device, instances.len()); let _ = self.instances.resize(device, instances.len());
let _ = self.instances.write(queue, 0, instances); let _ = self.instances.write(device, encoder, belt, 0, instances);
} }
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
@ -383,8 +394,8 @@ impl Pipeline {
pub fn prepare( pub fn prepare(
&mut self, &mut self,
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder, encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
images: &[layer::Image], images: &[layer::Image],
transformation: Transformation, transformation: Transformation,
_scale: f32, _scale: f32,
@ -501,7 +512,8 @@ impl Pipeline {
layer.prepare( layer.prepare(
device, device,
queue, encoder,
belt,
nearest_instances, nearest_instances,
linear_instances, linear_instances,
transformation, transformation,

View file

@ -57,7 +57,8 @@ impl Pipeline {
pub fn prepare( pub fn prepare(
&mut self, &mut self,
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
quads: &Batch, quads: &Batch,
transformation: Transformation, transformation: Transformation,
scale: f32, scale: f32,
@ -67,7 +68,7 @@ impl Pipeline {
} }
let layer = &mut self.layers[self.prepare_layer]; let layer = &mut self.layers[self.prepare_layer];
layer.prepare(device, queue, quads, transformation, scale); layer.prepare(device, encoder, belt, quads, transformation, scale);
self.prepare_layer += 1; self.prepare_layer += 1;
} }
@ -162,7 +163,8 @@ impl Layer {
pub fn prepare( pub fn prepare(
&mut self, &mut self,
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
quads: &Batch, quads: &Batch,
transformation: Transformation, transformation: Transformation,
scale: f32, scale: f32,
@ -171,15 +173,25 @@ impl Layer {
let _ = info_span!("Wgpu::Quad", "PREPARE").entered(); let _ = info_span!("Wgpu::Quad", "PREPARE").entered();
let uniforms = Uniforms::new(transformation, scale); let uniforms = Uniforms::new(transformation, scale);
let bytes = bytemuck::bytes_of(&uniforms);
queue.write_buffer( belt.write_buffer(
encoder,
&self.constants_buffer, &self.constants_buffer,
0, 0,
bytemuck::bytes_of(&uniforms), (bytes.len() as u64).try_into().expect("Sized uniforms"),
); device,
)
.copy_from_slice(bytes);
self.solid.prepare(device, queue, &quads.solids); if !quads.solids.is_empty() {
self.gradient.prepare(device, queue, &quads.gradients); self.solid.prepare(device, encoder, belt, &quads.solids);
}
if !quads.gradients.is_empty() {
self.gradient
.prepare(device, encoder, belt, &quads.gradients);
}
} }
} }

View file

@ -46,11 +46,12 @@ impl Layer {
pub fn prepare( pub fn prepare(
&mut self, &mut self,
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
instances: &[Gradient], instances: &[Gradient],
) { ) {
let _ = self.instances.resize(device, instances.len()); let _ = self.instances.resize(device, instances.len());
let _ = self.instances.write(queue, 0, instances); let _ = self.instances.write(device, encoder, belt, 0, instances);
self.instance_count = instances.len(); self.instance_count = instances.len();
} }

View file

@ -40,11 +40,12 @@ impl Layer {
pub fn prepare( pub fn prepare(
&mut self, &mut self,
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
instances: &[Solid], instances: &[Solid],
) { ) {
let _ = self.instances.resize(device, instances.len()); let _ = self.instances.resize(device, instances.len());
let _ = self.instances.write(queue, 0, instances); let _ = self.instances.write(device, encoder, belt, 0, instances);
self.instance_count = instances.len(); self.instance_count = instances.len();
} }

View file

@ -53,6 +53,7 @@ impl Pipeline {
&mut self, &mut self,
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
sections: &[Text<'_>], sections: &[Text<'_>],
layer_bounds: Rectangle, layer_bounds: Rectangle,
scale_factor: f32, scale_factor: f32,
@ -262,6 +263,7 @@ impl Pipeline {
let result = renderer.prepare( let result = renderer.prepare(
device, device,
queue, queue,
encoder,
font_system, font_system,
&mut self.atlas, &mut self.atlas,
glyphon::Resolution { glyphon::Resolution {

View file

@ -48,7 +48,8 @@ impl Layer {
fn prepare( fn prepare(
&mut self, &mut self,
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
solid: &solid::Pipeline, solid: &solid::Pipeline,
gradient: &gradient::Pipeline, gradient: &gradient::Pipeline,
meshes: &[Mesh<'_>], meshes: &[Mesh<'_>],
@ -103,33 +104,47 @@ impl Layer {
let uniforms = let uniforms =
Uniforms::new(transformation * mesh.transformation()); Uniforms::new(transformation * mesh.transformation());
index_offset += index_offset += self.index_buffer.write(
self.index_buffer.write(queue, index_offset, indices); device,
encoder,
belt,
index_offset,
indices,
);
self.index_strides.push(indices.len() as u32); self.index_strides.push(indices.len() as u32);
match mesh { match mesh {
Mesh::Solid { buffers, .. } => { Mesh::Solid { buffers, .. } => {
solid_vertex_offset += self.solid.vertices.write( solid_vertex_offset += self.solid.vertices.write(
queue, device,
encoder,
belt,
solid_vertex_offset, solid_vertex_offset,
&buffers.vertices, &buffers.vertices,
); );
solid_uniform_offset += self.solid.uniforms.write( solid_uniform_offset += self.solid.uniforms.write(
queue, device,
encoder,
belt,
solid_uniform_offset, solid_uniform_offset,
&[uniforms], &[uniforms],
); );
} }
Mesh::Gradient { buffers, .. } => { Mesh::Gradient { buffers, .. } => {
gradient_vertex_offset += self.gradient.vertices.write( gradient_vertex_offset += self.gradient.vertices.write(
queue, device,
encoder,
belt,
gradient_vertex_offset, gradient_vertex_offset,
&buffers.vertices, &buffers.vertices,
); );
gradient_uniform_offset += self.gradient.uniforms.write( gradient_uniform_offset += self.gradient.uniforms.write(
queue, device,
encoder,
belt,
gradient_uniform_offset, gradient_uniform_offset,
&[uniforms], &[uniforms],
); );
@ -237,7 +252,8 @@ impl Pipeline {
pub fn prepare( pub fn prepare(
&mut self, &mut self,
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
meshes: &[Mesh<'_>], meshes: &[Mesh<'_>],
transformation: Transformation, transformation: Transformation,
) { ) {
@ -252,7 +268,8 @@ impl Pipeline {
let layer = &mut self.layers[self.prepare_layer]; let layer = &mut self.layers[self.prepare_layer];
layer.prepare( layer.prepare(
device, device,
queue, encoder,
belt,
&self.solid, &self.solid,
&self.gradient, &self.gradient,
meshes, meshes,

View file

@ -243,6 +243,7 @@ pub fn present<T: AsRef<str>>(
// Submit work // Submit work
let _submission = compositor.queue.submit(Some(encoder.finish())); let _submission = compositor.queue.submit(Some(encoder.finish()));
backend.recall();
frame.present(); frame.present();
Ok(()) Ok(())