summaryrefslogtreecommitdiffstats
path: root/wgpu/src/image.rs
diff options
context:
space:
mode:
Diffstat (limited to 'wgpu/src/image.rs')
-rw-r--r--wgpu/src/image.rs352
1 files changed, 246 insertions, 106 deletions
diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs
index f7ed67c3..d3603676 100644
--- a/wgpu/src/image.rs
+++ b/wgpu/src/image.rs
@@ -1,15 +1,23 @@
+mod atlas;
+
#[cfg(feature = "image")]
mod raster;
+
#[cfg(feature = "svg")]
mod vector;
use crate::Transformation;
-use iced_native::{image, svg, Rectangle};
+use atlas::Atlas;
+use iced_native::Rectangle;
+use std::cell::RefCell;
use std::mem;
-#[cfg(any(feature = "image", feature = "svg"))]
-use std::cell::RefCell;
+#[cfg(feature = "image")]
+use iced_native::image;
+
+#[cfg(feature = "svg")]
+use iced_native::svg;
#[derive(Debug)]
pub struct Pipeline {
@@ -24,7 +32,10 @@ pub struct Pipeline {
indices: wgpu::Buffer,
instances: wgpu::Buffer,
constants: wgpu::BindGroup,
+ texture: wgpu::BindGroup,
+ texture_version: usize,
texture_layout: wgpu::BindGroupLayout,
+ texture_atlas: Atlas,
}
impl Pipeline {
@@ -174,6 +185,21 @@ impl Pipeline {
format: wgpu::VertexFormat::Float2,
offset: 4 * 2,
},
+ wgpu::VertexAttributeDescriptor {
+ shader_location: 3,
+ format: wgpu::VertexFormat::Float2,
+ offset: 4 * 4,
+ },
+ wgpu::VertexAttributeDescriptor {
+ shader_location: 4,
+ format: wgpu::VertexFormat::Float2,
+ offset: 4 * 6,
+ },
+ wgpu::VertexAttributeDescriptor {
+ shader_location: 5,
+ format: wgpu::VertexFormat::Uint,
+ offset: 4 * 8,
+ },
],
},
],
@@ -191,13 +217,26 @@ impl Pipeline {
.fill_from_slice(&QUAD_INDICES);
let instances = device.create_buffer(&wgpu::BufferDescriptor {
- size: mem::size_of::<Instance>() as u64,
+ size: mem::size_of::<Instance>() as u64 * Instance::MAX as u64,
usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST,
});
+ let texture_atlas = Atlas::new(device);
+
+ let texture = device.create_bind_group(&wgpu::BindGroupDescriptor {
+ layout: &texture_layout,
+ bindings: &[wgpu::Binding {
+ binding: 0,
+ resource: wgpu::BindingResource::TextureView(
+ &texture_atlas.view(),
+ ),
+ }],
+ });
+
Pipeline {
#[cfg(feature = "image")]
raster_cache: RefCell::new(raster::Cache::new()),
+
#[cfg(feature = "svg")]
vector_cache: RefCell::new(vector::Cache::new()),
@@ -207,7 +246,10 @@ impl Pipeline {
indices,
instances,
constants: constant_bind_group,
+ texture,
+ texture_version: texture_atlas.layer_count(),
texture_layout,
+ texture_atlas,
}
}
@@ -231,12 +273,72 @@ impl Pipeline {
&mut self,
device: &mut wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
- instances: &[Image],
+ images: &[Image],
transformation: Transformation,
bounds: Rectangle<u32>,
target: &wgpu::TextureView,
_scale: f32,
) {
+ let instances: &mut Vec<Instance> = &mut Vec::new();
+
+ #[cfg(feature = "image")]
+ let mut raster_cache = self.raster_cache.borrow_mut();
+
+ #[cfg(feature = "svg")]
+ let mut vector_cache = self.vector_cache.borrow_mut();
+
+ for image in images {
+ match &image.handle {
+ #[cfg(feature = "image")]
+ Handle::Raster(handle) => {
+ if let Some(atlas_entry) = raster_cache.upload(
+ handle,
+ device,
+ encoder,
+ &mut self.texture_atlas,
+ ) {
+ add_instances(image, atlas_entry, instances);
+ }
+ }
+ #[cfg(feature = "svg")]
+ Handle::Vector(handle) => {
+ if let Some(atlas_entry) = vector_cache.upload(
+ handle,
+ image.size,
+ _scale,
+ device,
+ encoder,
+ &mut self.texture_atlas,
+ ) {
+ add_instances(image, atlas_entry, instances);
+ }
+ }
+ }
+ }
+
+ if instances.is_empty() {
+ return;
+ }
+
+ let texture_version = self.texture_atlas.layer_count();
+
+ if self.texture_version != texture_version {
+ log::info!("Atlas has grown. Recreating bind group...");
+
+ self.texture =
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ layout: &self.texture_layout,
+ bindings: &[wgpu::Binding {
+ binding: 0,
+ resource: wgpu::BindingResource::TextureView(
+ &self.texture_atlas.view(),
+ ),
+ }],
+ });
+
+ self.texture_version = texture_version;
+ }
+
let uniforms_buffer = device
.create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)
.fill_from_slice(&[Uniforms {
@@ -251,123 +353,90 @@ impl Pipeline {
std::mem::size_of::<Uniforms>() as u64,
);
- // TODO: Batch draw calls using a texture atlas
- // Guillotière[1] by @nical can help us a lot here.
- //
- // [1]: https://github.com/nical/guillotiere
- for image in instances {
- let uploaded_texture = match &image.handle {
- Handle::Raster(_handle) => {
- #[cfg(feature = "image")]
- {
- let mut cache = self.raster_cache.borrow_mut();
- let memory = cache.load(&_handle);
-
- memory.upload(device, encoder, &self.texture_layout)
- }
-
- #[cfg(not(feature = "image"))]
- None
- }
- Handle::Vector(_handle) => {
- #[cfg(feature = "svg")]
- {
- let mut cache = self.vector_cache.borrow_mut();
-
- cache.upload(
- _handle,
- image.scale,
- _scale,
- device,
- encoder,
- &self.texture_layout,
- )
- }
-
- #[cfg(not(feature = "svg"))]
- None
- }
- };
-
- if let Some(texture) = uploaded_texture {
- let instance_buffer = device
- .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)
- .fill_from_slice(&[Instance {
- _position: image.position,
- _scale: image.scale,
- }]);
-
- encoder.copy_buffer_to_buffer(
- &instance_buffer,
- 0,
- &self.instances,
- 0,
- mem::size_of::<Instance>() as u64,
- );
-
- {
- let mut render_pass = encoder.begin_render_pass(
- &wgpu::RenderPassDescriptor {
- color_attachments: &[
- wgpu::RenderPassColorAttachmentDescriptor {
- attachment: target,
- resolve_target: None,
- load_op: wgpu::LoadOp::Load,
- store_op: wgpu::StoreOp::Store,
- clear_color: wgpu::Color {
- r: 0.0,
- g: 0.0,
- b: 0.0,
- a: 0.0,
- },
- },
- ],
- depth_stencil_attachment: None,
+ let instances_buffer = device
+ .create_buffer_mapped(instances.len(), wgpu::BufferUsage::COPY_SRC)
+ .fill_from_slice(&instances);
+
+ let mut i = 0;
+ let total = instances.len();
+
+ while i < total {
+ let end = (i + Instance::MAX).min(total);
+ let amount = end - i;
+
+ encoder.copy_buffer_to_buffer(
+ &instances_buffer,
+ (i * std::mem::size_of::<Instance>()) as u64,
+ &self.instances,
+ 0,
+ (amount * std::mem::size_of::<Instance>()) as u64,
+ );
+
+ let mut render_pass =
+ encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ color_attachments: &[
+ wgpu::RenderPassColorAttachmentDescriptor {
+ attachment: target,
+ resolve_target: None,
+ load_op: wgpu::LoadOp::Load,
+ store_op: wgpu::StoreOp::Store,
+ clear_color: wgpu::Color {
+ r: 0.0,
+ g: 0.0,
+ b: 0.0,
+ a: 0.0,
+ },
},
- );
-
- render_pass.set_pipeline(&self.pipeline);
- render_pass.set_bind_group(0, &self.constants, &[]);
- render_pass.set_bind_group(1, &texture, &[]);
- render_pass.set_index_buffer(&self.indices, 0);
- render_pass.set_vertex_buffers(
- 0,
- &[(&self.vertices, 0), (&self.instances, 0)],
- );
- render_pass.set_scissor_rect(
- bounds.x,
- bounds.y,
- bounds.width,
- bounds.height,
- );
-
- render_pass.draw_indexed(
- 0..QUAD_INDICES.len() as u32,
- 0,
- 0..1 as u32,
- );
- }
- }
+ ],
+ depth_stencil_attachment: None,
+ });
+
+ render_pass.set_pipeline(&self.pipeline);
+ render_pass.set_bind_group(0, &self.constants, &[]);
+ render_pass.set_bind_group(1, &self.texture, &[]);
+ render_pass.set_index_buffer(&self.indices, 0);
+ render_pass.set_vertex_buffers(
+ 0,
+ &[(&self.vertices, 0), (&self.instances, 0)],
+ );
+
+ render_pass.set_scissor_rect(
+ bounds.x,
+ bounds.y,
+ bounds.width,
+ bounds.height,
+ );
+
+ render_pass.draw_indexed(
+ 0..QUAD_INDICES.len() as u32,
+ 0,
+ 0..amount as u32,
+ );
+
+ i += Instance::MAX;
}
}
pub fn trim_cache(&mut self) {
#[cfg(feature = "image")]
- self.raster_cache.borrow_mut().trim();
+ self.raster_cache.borrow_mut().trim(&mut self.texture_atlas);
#[cfg(feature = "svg")]
- self.vector_cache.borrow_mut().trim();
+ self.vector_cache.borrow_mut().trim(&mut self.texture_atlas);
}
}
pub struct Image {
pub handle: Handle,
pub position: [f32; 2],
- pub scale: [f32; 2],
+ pub size: [f32; 2],
}
pub enum Handle {
+ #[cfg(feature = "image")]
Raster(image::Handle),
+
+ #[cfg(feature = "svg")]
Vector(svg::Handle),
}
@@ -395,10 +464,17 @@ const QUAD_VERTS: [Vertex; 4] = [
];
#[repr(C)]
-#[derive(Clone, Copy)]
+#[derive(Debug, Clone, Copy)]
struct Instance {
_position: [f32; 2],
- _scale: [f32; 2],
+ _size: [f32; 2],
+ _position_in_atlas: [f32; 2],
+ _size_in_atlas: [f32; 2],
+ _layer: u32,
+}
+
+impl Instance {
+ pub const MAX: usize = 1_000;
}
#[repr(C)]
@@ -406,3 +482,67 @@ struct Instance {
struct Uniforms {
transform: [f32; 16],
}
+
+fn add_instances(
+ image: &Image,
+ entry: &atlas::Entry,
+ instances: &mut Vec<Instance>,
+) {
+ match entry {
+ atlas::Entry::Contiguous(allocation) => {
+ add_instance(image.position, image.size, allocation, instances);
+ }
+ atlas::Entry::Fragmented { fragments, size } => {
+ let scaling_x = image.size[0] / size.0 as f32;
+ let scaling_y = image.size[1] / size.1 as f32;
+
+ for fragment in fragments {
+ let allocation = &fragment.allocation;
+
+ let [x, y] = image.position;
+ let (fragment_x, fragment_y) = fragment.position;
+ let (fragment_width, fragment_height) = allocation.size();
+
+ let position = [
+ x + fragment_x as f32 * scaling_x,
+ y + fragment_y as f32 * scaling_y,
+ ];
+
+ let size = [
+ fragment_width as f32 * scaling_x,
+ fragment_height as f32 * scaling_y,
+ ];
+
+ add_instance(position, size, allocation, instances);
+ }
+ }
+ }
+}
+
+#[inline]
+fn add_instance(
+ position: [f32; 2],
+ size: [f32; 2],
+ allocation: &atlas::Allocation,
+ instances: &mut Vec<Instance>,
+) {
+ let (x, y) = allocation.position();
+ let (width, height) = allocation.size();
+ let layer = allocation.layer();
+
+ let instance = Instance {
+ _position: position,
+ _size: size,
+ _position_in_atlas: [
+ (x as f32 + 0.5) / atlas::SIZE as f32,
+ (y as f32 + 0.5) / atlas::SIZE as f32,
+ ],
+ _size_in_atlas: [
+ (width as f32 - 1.0) / atlas::SIZE as f32,
+ (height as f32 - 1.0) / atlas::SIZE as f32,
+ ],
+ _layer: layer as u32,
+ };
+
+ instances.push(instance);
+}