summaryrefslogtreecommitdiffstats
path: root/wgpu/src/image
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2024-05-23 13:29:45 +0200
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2024-05-23 13:29:45 +0200
commitd8ba6b0673a33724a177f3a1ba59705527280142 (patch)
tree89482c8d1e3a03e00b3a8151abbb81e30ae5898c /wgpu/src/image
parent72ed8bcc8def9956e25f3720a3095fc96bb2eef0 (diff)
parent468794d918eb06c1dbebb33c32b10017ad335f05 (diff)
downloadiced-d8ba6b0673a33724a177f3a1ba59705527280142.tar.gz
iced-d8ba6b0673a33724a177f3a1ba59705527280142.tar.bz2
iced-d8ba6b0673a33724a177f3a1ba59705527280142.zip
Merge branch 'master' into feat/text-macro
Diffstat (limited to 'wgpu/src/image')
-rw-r--r--wgpu/src/image/atlas.rs53
-rw-r--r--wgpu/src/image/atlas/allocator.rs4
-rw-r--r--wgpu/src/image/atlas/layer.rs8
-rw-r--r--wgpu/src/image/cache.rs86
-rw-r--r--wgpu/src/image/mod.rs597
-rw-r--r--wgpu/src/image/null.rs10
-rw-r--r--wgpu/src/image/raster.rs19
-rw-r--r--wgpu/src/image/vector.rs18
8 files changed, 778 insertions, 17 deletions
diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs
index ea36e06d..a1ec0d7b 100644
--- a/wgpu/src/image/atlas.rs
+++ b/wgpu/src/image/atlas.rs
@@ -15,15 +15,23 @@ pub const SIZE: u32 = 2048;
use crate::core::Size;
use crate::graphics::color;
+use std::sync::Arc;
+
#[derive(Debug)]
pub struct Atlas {
texture: wgpu::Texture,
texture_view: wgpu::TextureView,
+ texture_bind_group: wgpu::BindGroup,
+ texture_layout: Arc<wgpu::BindGroupLayout>,
layers: Vec<Layer>,
}
impl Atlas {
- pub fn new(device: &wgpu::Device, backend: wgpu::Backend) -> Self {
+ pub fn new(
+ device: &wgpu::Device,
+ backend: wgpu::Backend,
+ texture_layout: Arc<wgpu::BindGroupLayout>,
+ ) -> Self {
let layers = match backend {
// On the GL backend we start with 2 layers, to help wgpu figure
// out that this texture is `GL_TEXTURE_2D_ARRAY` rather than `GL_TEXTURE_2D`
@@ -60,15 +68,27 @@ impl Atlas {
..Default::default()
});
+ let texture_bind_group =
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("iced_wgpu::image texture atlas bind group"),
+ layout: &texture_layout,
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::TextureView(&texture_view),
+ }],
+ });
+
Atlas {
texture,
texture_view,
+ texture_bind_group,
+ texture_layout,
layers,
}
}
- pub fn view(&self) -> &wgpu::TextureView {
- &self.texture_view
+ pub fn bind_group(&self) -> &wgpu::BindGroup {
+ &self.texture_bind_group
}
pub fn layer_count(&self) -> usize {
@@ -94,7 +114,7 @@ impl Atlas {
entry
};
- log::info!("Allocated atlas entry: {entry:?}");
+ log::debug!("Allocated atlas entry: {entry:?}");
// It is a webgpu requirement that:
// BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0
@@ -147,13 +167,20 @@ impl Atlas {
}
}
- log::info!("Current atlas: {self:?}");
+ if log::log_enabled!(log::Level::Debug) {
+ log::debug!(
+ "Atlas layers: {} (busy: {}, allocations: {})",
+ self.layer_count(),
+ self.layers.iter().filter(|layer| !layer.is_empty()).count(),
+ self.layers.iter().map(Layer::allocations).sum::<usize>(),
+ );
+ }
Some(entry)
}
pub fn remove(&mut self, entry: &Entry) {
- log::info!("Removing atlas entry: {entry:?}");
+ log::debug!("Removing atlas entry: {entry:?}");
match entry {
Entry::Contiguous(allocation) => {
@@ -266,7 +293,7 @@ impl Atlas {
}
fn deallocate(&mut self, allocation: &Allocation) {
- log::info!("Deallocating atlas: {allocation:?}");
+ log::debug!("Deallocating atlas: {allocation:?}");
match allocation {
Allocation::Full { layer } => {
@@ -414,5 +441,17 @@ impl Atlas {
dimension: Some(wgpu::TextureViewDimension::D2Array),
..Default::default()
});
+
+ self.texture_bind_group =
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("iced_wgpu::image texture atlas bind group"),
+ layout: &self.texture_layout,
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::TextureView(
+ &self.texture_view,
+ ),
+ }],
+ });
}
}
diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs
index 204a5c26..a51ac1f5 100644
--- a/wgpu/src/image/atlas/allocator.rs
+++ b/wgpu/src/image/atlas/allocator.rs
@@ -33,6 +33,10 @@ impl Allocator {
pub fn is_empty(&self) -> bool {
self.allocations == 0
}
+
+ pub fn allocations(&self) -> usize {
+ self.allocations
+ }
}
pub struct Region {
diff --git a/wgpu/src/image/atlas/layer.rs b/wgpu/src/image/atlas/layer.rs
index cf089601..fd6788d9 100644
--- a/wgpu/src/image/atlas/layer.rs
+++ b/wgpu/src/image/atlas/layer.rs
@@ -11,4 +11,12 @@ impl Layer {
pub fn is_empty(&self) -> bool {
matches!(self, Layer::Empty)
}
+
+ pub fn allocations(&self) -> usize {
+ match self {
+ Layer::Empty => 0,
+ Layer::Busy(allocator) => allocator.allocations(),
+ Layer::Full => 1,
+ }
+ }
}
diff --git a/wgpu/src/image/cache.rs b/wgpu/src/image/cache.rs
new file mode 100644
index 00000000..94f7071d
--- /dev/null
+++ b/wgpu/src/image/cache.rs
@@ -0,0 +1,86 @@
+use crate::core::{self, Size};
+use crate::image::atlas::{self, Atlas};
+
+use std::sync::Arc;
+
+#[derive(Debug)]
+pub struct Cache {
+ atlas: Atlas,
+ #[cfg(feature = "image")]
+ raster: crate::image::raster::Cache,
+ #[cfg(feature = "svg")]
+ vector: crate::image::vector::Cache,
+}
+
+impl Cache {
+ pub fn new(
+ device: &wgpu::Device,
+ backend: wgpu::Backend,
+ layout: Arc<wgpu::BindGroupLayout>,
+ ) -> Self {
+ Self {
+ atlas: Atlas::new(device, backend, layout),
+ #[cfg(feature = "image")]
+ raster: crate::image::raster::Cache::default(),
+ #[cfg(feature = "svg")]
+ vector: crate::image::vector::Cache::default(),
+ }
+ }
+
+ pub fn bind_group(&self) -> &wgpu::BindGroup {
+ self.atlas.bind_group()
+ }
+
+ pub fn layer_count(&self) -> usize {
+ self.atlas.layer_count()
+ }
+
+ #[cfg(feature = "image")]
+ pub fn measure_image(&mut self, handle: &core::image::Handle) -> Size<u32> {
+ self.raster.load(handle).dimensions()
+ }
+
+ #[cfg(feature = "svg")]
+ pub fn measure_svg(&mut self, handle: &core::svg::Handle) -> Size<u32> {
+ self.vector.load(handle).viewport_dimensions()
+ }
+
+ #[cfg(feature = "image")]
+ pub fn upload_raster(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ handle: &core::image::Handle,
+ ) -> Option<&atlas::Entry> {
+ self.raster.upload(device, encoder, handle, &mut self.atlas)
+ }
+
+ #[cfg(feature = "svg")]
+ pub fn upload_vector(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ handle: &core::svg::Handle,
+ color: Option<core::Color>,
+ size: [f32; 2],
+ scale: f32,
+ ) -> Option<&atlas::Entry> {
+ self.vector.upload(
+ device,
+ encoder,
+ handle,
+ color,
+ size,
+ scale,
+ &mut self.atlas,
+ )
+ }
+
+ pub fn trim(&mut self) {
+ #[cfg(feature = "image")]
+ self.raster.trim(&mut self.atlas);
+
+ #[cfg(feature = "svg")]
+ self.vector.trim(&mut self.atlas);
+ }
+}
diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs
new file mode 100644
index 00000000..daa2fe16
--- /dev/null
+++ b/wgpu/src/image/mod.rs
@@ -0,0 +1,597 @@
+pub(crate) mod cache;
+pub(crate) use cache::Cache;
+
+mod atlas;
+
+#[cfg(feature = "image")]
+mod raster;
+
+#[cfg(feature = "svg")]
+mod vector;
+
+use crate::core::{Rectangle, Size, Transformation};
+use crate::Buffer;
+
+use bytemuck::{Pod, Zeroable};
+
+use std::mem;
+use std::sync::Arc;
+
+pub use crate::graphics::Image;
+
+pub type Batch = Vec<Image>;
+
+#[derive(Debug)]
+pub struct Pipeline {
+ pipeline: wgpu::RenderPipeline,
+ backend: wgpu::Backend,
+ nearest_sampler: wgpu::Sampler,
+ linear_sampler: wgpu::Sampler,
+ texture_layout: Arc<wgpu::BindGroupLayout>,
+ constant_layout: wgpu::BindGroupLayout,
+ layers: Vec<Layer>,
+ prepare_layer: usize,
+}
+
+impl Pipeline {
+ pub fn new(
+ device: &wgpu::Device,
+ format: wgpu::TextureFormat,
+ backend: wgpu::Backend,
+ ) -> Self {
+ let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+ address_mode_u: wgpu::AddressMode::ClampToEdge,
+ address_mode_v: wgpu::AddressMode::ClampToEdge,
+ address_mode_w: wgpu::AddressMode::ClampToEdge,
+ min_filter: wgpu::FilterMode::Nearest,
+ mag_filter: wgpu::FilterMode::Nearest,
+ mipmap_filter: wgpu::FilterMode::Nearest,
+ ..Default::default()
+ });
+
+ let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+ address_mode_u: wgpu::AddressMode::ClampToEdge,
+ address_mode_v: wgpu::AddressMode::ClampToEdge,
+ address_mode_w: wgpu::AddressMode::ClampToEdge,
+ min_filter: wgpu::FilterMode::Linear,
+ mag_filter: wgpu::FilterMode::Linear,
+ mipmap_filter: wgpu::FilterMode::Linear,
+ ..Default::default()
+ });
+
+ let constant_layout =
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ label: Some("iced_wgpu::image constants layout"),
+ entries: &[
+ wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::VERTEX,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: false,
+ min_binding_size: wgpu::BufferSize::new(
+ mem::size_of::<Uniforms>() as u64,
+ ),
+ },
+ count: None,
+ },
+ wgpu::BindGroupLayoutEntry {
+ binding: 1,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Sampler(
+ wgpu::SamplerBindingType::Filtering,
+ ),
+ count: None,
+ },
+ ],
+ });
+
+ let texture_layout =
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ label: Some("iced_wgpu::image texture atlas layout"),
+ entries: &[wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Texture {
+ sample_type: wgpu::TextureSampleType::Float {
+ filterable: true,
+ },
+ view_dimension: wgpu::TextureViewDimension::D2Array,
+ multisampled: false,
+ },
+ count: None,
+ }],
+ });
+
+ let layout =
+ device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+ label: Some("iced_wgpu::image pipeline layout"),
+ push_constant_ranges: &[],
+ bind_group_layouts: &[&constant_layout, &texture_layout],
+ });
+
+ let shader =
+ device.create_shader_module(wgpu::ShaderModuleDescriptor {
+ label: Some("iced_wgpu image shader"),
+ source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
+ concat!(
+ include_str!("../shader/vertex.wgsl"),
+ "\n",
+ include_str!("../shader/image.wgsl"),
+ ),
+ )),
+ });
+
+ let pipeline =
+ device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ label: Some("iced_wgpu::image pipeline"),
+ layout: Some(&layout),
+ vertex: wgpu::VertexState {
+ module: &shader,
+ entry_point: "vs_main",
+ buffers: &[wgpu::VertexBufferLayout {
+ array_stride: mem::size_of::<Instance>() as u64,
+ step_mode: wgpu::VertexStepMode::Instance,
+ attributes: &wgpu::vertex_attr_array!(
+ // Position
+ 0 => Float32x2,
+ // Center
+ 1 => Float32x2,
+ // Scale
+ 2 => Float32x2,
+ // Rotation
+ 3 => Float32,
+ // Opacity
+ 4 => Float32,
+ // Atlas position
+ 5 => Float32x2,
+ // Atlas scale
+ 6 => Float32x2,
+ // Layer
+ 7 => Sint32,
+ ),
+ }],
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: &shader,
+ entry_point: "fs_main",
+ targets: &[Some(wgpu::ColorTargetState {
+ format,
+ blend: Some(wgpu::BlendState {
+ color: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::SrcAlpha,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ alpha: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::One,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add,
+ },
+ }),
+ write_mask: wgpu::ColorWrites::ALL,
+ })],
+ }),
+ primitive: wgpu::PrimitiveState {
+ topology: wgpu::PrimitiveTopology::TriangleList,
+ front_face: wgpu::FrontFace::Cw,
+ ..Default::default()
+ },
+ depth_stencil: None,
+ multisample: wgpu::MultisampleState {
+ count: 1,
+ mask: !0,
+ alpha_to_coverage_enabled: false,
+ },
+ multiview: None,
+ });
+
+ Pipeline {
+ pipeline,
+ backend,
+ nearest_sampler,
+ linear_sampler,
+ texture_layout: Arc::new(texture_layout),
+ constant_layout,
+ layers: Vec::new(),
+ prepare_layer: 0,
+ }
+ }
+
+ pub fn create_cache(&self, device: &wgpu::Device) -> Cache {
+ Cache::new(device, self.backend, self.texture_layout.clone())
+ }
+
+ pub fn prepare(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ belt: &mut wgpu::util::StagingBelt,
+ cache: &mut Cache,
+ images: &Batch,
+ transformation: Transformation,
+ scale: f32,
+ ) {
+ let transformation = transformation * Transformation::scale(scale);
+
+ let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
+ let linear_instances: &mut Vec<Instance> = &mut Vec::new();
+
+ for image in images {
+ match &image {
+ #[cfg(feature = "image")]
+ Image::Raster {
+ handle,
+ filter_method,
+ bounds,
+ rotation,
+ opacity,
+ } => {
+ if let Some(atlas_entry) =
+ cache.upload_raster(device, encoder, handle)
+ {
+ add_instances(
+ [bounds.x, bounds.y],
+ [bounds.width, bounds.height],
+ f32::from(*rotation),
+ *opacity,
+ atlas_entry,
+ match filter_method {
+ crate::core::image::FilterMethod::Nearest => {
+ nearest_instances
+ }
+ crate::core::image::FilterMethod::Linear => {
+ linear_instances
+ }
+ },
+ );
+ }
+ }
+ #[cfg(not(feature = "image"))]
+ Image::Raster { .. } => {}
+
+ #[cfg(feature = "svg")]
+ Image::Vector {
+ handle,
+ color,
+ bounds,
+ rotation,
+ opacity,
+ } => {
+ let size = [bounds.width, bounds.height];
+
+ if let Some(atlas_entry) = cache.upload_vector(
+ device, encoder, handle, *color, size, scale,
+ ) {
+ add_instances(
+ [bounds.x, bounds.y],
+ size,
+ f32::from(*rotation),
+ *opacity,
+ atlas_entry,
+ nearest_instances,
+ );
+ }
+ }
+ #[cfg(not(feature = "svg"))]
+ Image::Vector { .. } => {}
+ }
+ }
+
+ if nearest_instances.is_empty() && linear_instances.is_empty() {
+ return;
+ }
+
+ if self.layers.len() <= self.prepare_layer {
+ self.layers.push(Layer::new(
+ device,
+ &self.constant_layout,
+ &self.nearest_sampler,
+ &self.linear_sampler,
+ ));
+ }
+
+ let layer = &mut self.layers[self.prepare_layer];
+
+ layer.prepare(
+ device,
+ encoder,
+ belt,
+ nearest_instances,
+ linear_instances,
+ transformation,
+ );
+
+ self.prepare_layer += 1;
+ }
+
+ pub fn render<'a>(
+ &'a self,
+ cache: &'a Cache,
+ layer: usize,
+ bounds: Rectangle<u32>,
+ render_pass: &mut wgpu::RenderPass<'a>,
+ ) {
+ if let Some(layer) = self.layers.get(layer) {
+ render_pass.set_pipeline(&self.pipeline);
+
+ render_pass.set_scissor_rect(
+ bounds.x,
+ bounds.y,
+ bounds.width,
+ bounds.height,
+ );
+
+ render_pass.set_bind_group(1, cache.bind_group(), &[]);
+
+ layer.render(render_pass);
+ }
+ }
+
+ pub fn end_frame(&mut self) {
+ self.prepare_layer = 0;
+ }
+}
+
+#[derive(Debug)]
+struct Layer {
+ uniforms: wgpu::Buffer,
+ nearest: Data,
+ linear: Data,
+}
+
+impl Layer {
+ fn new(
+ device: &wgpu::Device,
+ constant_layout: &wgpu::BindGroupLayout,
+ nearest_sampler: &wgpu::Sampler,
+ linear_sampler: &wgpu::Sampler,
+ ) -> Self {
+ let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some("iced_wgpu::image uniforms buffer"),
+ size: mem::size_of::<Uniforms>() as u64,
+ usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
+ mapped_at_creation: false,
+ });
+
+ let nearest =
+ Data::new(device, constant_layout, nearest_sampler, &uniforms);
+
+ let linear =
+ Data::new(device, constant_layout, linear_sampler, &uniforms);
+
+ Self {
+ uniforms,
+ nearest,
+ linear,
+ }
+ }
+
+ fn prepare(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ belt: &mut wgpu::util::StagingBelt,
+ nearest_instances: &[Instance],
+ linear_instances: &[Instance],
+ transformation: Transformation,
+ ) {
+ let uniforms = Uniforms {
+ transform: transformation.into(),
+ };
+
+ let bytes = bytemuck::bytes_of(&uniforms);
+
+ belt.write_buffer(
+ encoder,
+ &self.uniforms,
+ 0,
+ (bytes.len() as u64).try_into().expect("Sized uniforms"),
+ device,
+ )
+ .copy_from_slice(bytes);
+
+ self.nearest
+ .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>) {
+ self.nearest.render(render_pass);
+ self.linear.render(render_pass);
+ }
+}
+
+#[derive(Debug)]
+struct Data {
+ constants: wgpu::BindGroup,
+ instances: Buffer<Instance>,
+ instance_count: usize,
+}
+
+impl Data {
+ pub fn new(
+ device: &wgpu::Device,
+ constant_layout: &wgpu::BindGroupLayout,
+ sampler: &wgpu::Sampler,
+ uniforms: &wgpu::Buffer,
+ ) -> Self {
+ let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("iced_wgpu::image constants bind group"),
+ layout: constant_layout,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::Buffer(
+ wgpu::BufferBinding {
+ buffer: uniforms,
+ offset: 0,
+ size: None,
+ },
+ ),
+ },
+ wgpu::BindGroupEntry {
+ binding: 1,
+ resource: wgpu::BindingResource::Sampler(sampler),
+ },
+ ],
+ });
+
+ let instances = Buffer::new(
+ device,
+ "iced_wgpu::image instance buffer",
+ Instance::INITIAL,
+ wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+ );
+
+ Self {
+ constants,
+ instances,
+ instance_count: 0,
+ }
+ }
+
+ fn upload(
+ &mut self,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ belt: &mut wgpu::util::StagingBelt,
+ instances: &[Instance],
+ ) {
+ self.instance_count = instances.len();
+
+ if self.instance_count == 0 {
+ return;
+ }
+
+ let _ = self.instances.resize(device, instances.len());
+ let _ = self.instances.write(device, encoder, belt, 0, instances);
+ }
+
+ fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
+ if self.instance_count == 0 {
+ return;
+ }
+
+ render_pass.set_bind_group(0, &self.constants, &[]);
+ render_pass.set_vertex_buffer(0, self.instances.slice(..));
+
+ render_pass.draw(0..6, 0..self.instance_count as u32);
+ }
+}
+
+#[repr(C)]
+#[derive(Debug, Clone, Copy, Zeroable, Pod)]
+struct Instance {
+ _position: [f32; 2],
+ _center: [f32; 2],
+ _size: [f32; 2],
+ _rotation: f32,
+ _opacity: f32,
+ _position_in_atlas: [f32; 2],
+ _size_in_atlas: [f32; 2],
+ _layer: u32,
+}
+
+impl Instance {
+ pub const INITIAL: usize = 20;
+}
+
+#[repr(C)]
+#[derive(Debug, Clone, Copy, Zeroable, Pod)]
+struct Uniforms {
+ transform: [f32; 16],
+}
+
+fn add_instances(
+ image_position: [f32; 2],
+ image_size: [f32; 2],
+ rotation: f32,
+ opacity: f32,
+ entry: &atlas::Entry,
+ instances: &mut Vec<Instance>,
+) {
+ let center = [
+ image_position[0] + image_size[0] / 2.0,
+ image_position[1] + image_size[1] / 2.0,
+ ];
+
+ match entry {
+ atlas::Entry::Contiguous(allocation) => {
+ add_instance(
+ image_position,
+ center,
+ image_size,
+ rotation,
+ opacity,
+ allocation,
+ instances,
+ );
+ }
+ atlas::Entry::Fragmented { fragments, size } => {
+ let scaling_x = image_size[0] / size.width as f32;
+ let scaling_y = image_size[1] / size.height as f32;
+
+ for fragment in fragments {
+ let allocation = &fragment.allocation;
+
+ let [x, y] = image_position;
+ let (fragment_x, fragment_y) = fragment.position;
+ let Size {
+ width: fragment_width,
+ height: 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, center, size, rotation, opacity, allocation,
+ instances,
+ );
+ }
+ }
+ }
+}
+
+#[inline]
+fn add_instance(
+ position: [f32; 2],
+ center: [f32; 2],
+ size: [f32; 2],
+ rotation: f32,
+ opacity: f32,
+ allocation: &atlas::Allocation,
+ instances: &mut Vec<Instance>,
+) {
+ let (x, y) = allocation.position();
+ let Size { width, height } = allocation.size();
+ let layer = allocation.layer();
+
+ let instance = Instance {
+ _position: position,
+ _center: center,
+ _size: size,
+ _rotation: rotation,
+ _opacity: opacity,
+ _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);
+}
diff --git a/wgpu/src/image/null.rs b/wgpu/src/image/null.rs
new file mode 100644
index 00000000..c06d56be
--- /dev/null
+++ b/wgpu/src/image/null.rs
@@ -0,0 +1,10 @@
+pub use crate::graphics::Image;
+
+#[derive(Debug, Default)]
+pub struct Batch;
+
+impl Batch {
+ pub fn push(&mut self, _image: Image) {}
+
+ pub fn clear(&mut self) {}
+}
diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs
index a6cba76a..4d3c3125 100644
--- a/wgpu/src/image/raster.rs
+++ b/wgpu/src/image/raster.rs
@@ -4,13 +4,13 @@ use crate::graphics;
use crate::graphics::image::image_rs;
use crate::image::atlas::{self, Atlas};
-use std::collections::{HashMap, HashSet};
+use rustc_hash::{FxHashMap, FxHashSet};
/// Entry in cache corresponding to an image handle
#[derive(Debug)]
pub enum Memory {
/// Image data on host
- Host(image_rs::ImageBuffer<image_rs::Rgba<u8>, Vec<u8>>),
+ Host(image_rs::ImageBuffer<image_rs::Rgba<u8>, image::Bytes>),
/// Storage entry
Device(atlas::Entry),
/// Image not found
@@ -38,8 +38,9 @@ impl Memory {
/// Caches image raster data
#[derive(Debug, Default)]
pub struct Cache {
- map: HashMap<u64, Memory>,
- hits: HashSet<u64>,
+ map: FxHashMap<image::Id, Memory>,
+ hits: FxHashSet<image::Id>,
+ should_trim: bool,
}
impl Cache {
@@ -50,11 +51,13 @@ impl Cache {
}
let memory = match graphics::image::load(handle) {
- Ok(image) => Memory::Host(image.to_rgba8()),
+ Ok(image) => Memory::Host(image),
Err(image_rs::error::ImageError::IoError(_)) => Memory::NotFound,
Err(_) => Memory::Invalid,
};
+ self.should_trim = true;
+
self.insert(handle, memory);
self.get(handle).unwrap()
}
@@ -86,6 +89,11 @@ impl Cache {
/// Trim cache misses from cache
pub fn trim(&mut self, atlas: &mut Atlas) {
+ // Only trim if new entries have landed in the `Cache`
+ if !self.should_trim {
+ return;
+ }
+
let hits = &self.hits;
self.map.retain(|k, memory| {
@@ -101,6 +109,7 @@ impl Cache {
});
self.hits.clear();
+ self.should_trim = false;
}
fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> {
diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs
index d9be50d7..c6d829af 100644
--- a/wgpu/src/image/vector.rs
+++ b/wgpu/src/image/vector.rs
@@ -5,7 +5,7 @@ use crate::image::atlas::{self, Atlas};
use resvg::tiny_skia;
use resvg::usvg::{self, TreeTextToPath};
-use std::collections::{HashMap, HashSet};
+use rustc_hash::{FxHashMap, FxHashSet};
use std::fs;
/// Entry in cache corresponding to an svg handle
@@ -33,10 +33,11 @@ impl Svg {
/// Caches svg vector and raster data
#[derive(Debug, Default)]
pub struct Cache {
- svgs: HashMap<u64, Svg>,
- rasterized: HashMap<(u64, u32, u32, ColorFilter), atlas::Entry>,
- svg_hits: HashSet<u64>,
- rasterized_hits: HashSet<(u64, u32, u32, ColorFilter)>,
+ svgs: FxHashMap<u64, Svg>,
+ rasterized: FxHashMap<(u64, u32, u32, ColorFilter), atlas::Entry>,
+ svg_hits: FxHashSet<u64>,
+ rasterized_hits: FxHashSet<(u64, u32, u32, ColorFilter)>,
+ should_trim: bool,
}
type ColorFilter = Option<[u8; 4]>;
@@ -76,6 +77,8 @@ impl Cache {
}
}
+ self.should_trim = true;
+
let _ = self.svgs.insert(handle.id(), svg);
self.svgs.get(&handle.id()).unwrap()
}
@@ -176,6 +179,10 @@ impl Cache {
/// Load svg and upload raster data
pub fn trim(&mut self, atlas: &mut Atlas) {
+ if !self.should_trim {
+ return;
+ }
+
let svg_hits = &self.svg_hits;
let rasterized_hits = &self.rasterized_hits;
@@ -191,6 +198,7 @@ impl Cache {
});
self.svg_hits.clear();
self.rasterized_hits.clear();
+ self.should_trim = false;
}
}