iced-yoda/wgpu/src/image/mod.rs

759 lines
23 KiB
Rust
Raw Normal View History

pub(crate) mod cache;
pub(crate) use cache::Cache;
mod atlas;
#[cfg(feature = "image")]
mod raster;
#[cfg(feature = "svg")]
mod vector;
use crate::Buffer;
2025-10-25 22:53:44 +02:00
use crate::core::border;
2025-02-21 01:22:56 +01:00
use crate::core::{Rectangle, Size, Transformation};
use crate::graphics::Shell;
use bytemuck::{Pod, Zeroable};
use std::mem;
2025-10-25 22:53:44 +02:00
use std::sync::Arc;
pub use crate::graphics::Image;
pub type Batch = Vec<Image>;
2022-11-29 19:50:58 -08:00
#[derive(Debug, Clone)]
pub struct Pipeline {
raw: wgpu::RenderPipeline,
backend: wgpu::Backend,
nearest_sampler: wgpu::Sampler,
linear_sampler: wgpu::Sampler,
texture_layout: wgpu::BindGroupLayout,
constant_layout: wgpu::BindGroupLayout,
}
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,
2020-08-27 13:41:00 +02:00
..Default::default()
});
let constant_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2020-08-31 14:41:41 +02:00
label: Some("iced_wgpu::image constants layout"),
2020-08-27 13:41:00 +02:00
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
2021-08-19 03:06:35 +02:00
visibility: wgpu::ShaderStages::VERTEX,
2021-02-03 20:24:48 +01:00
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
2020-08-27 13:41:00 +02:00
min_binding_size: wgpu::BufferSize::new(
mem::size_of::<Uniforms>() as u64,
),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
2021-08-19 03:06:35 +02:00
visibility: wgpu::ShaderStages::FRAGMENT,
2022-01-04 16:18:51 +07:00
ty: wgpu::BindingType::Sampler(
wgpu::SamplerBindingType::Filtering,
),
count: None,
},
],
});
let texture_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2020-08-31 14:41:41 +02:00
label: Some("iced_wgpu::image texture atlas layout"),
2020-08-27 13:41:00 +02:00
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
2021-08-19 03:06:35 +02:00
visibility: wgpu::ShaderStages::FRAGMENT,
2021-02-03 20:24:48 +01:00
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float {
2021-02-06 16:04:43 +01:00
filterable: true,
2021-02-03 20:24:48 +01:00
},
view_dimension: wgpu::TextureViewDimension::D2Array,
multisampled: false,
},
2020-08-27 13:41:00 +02:00
count: None,
}],
});
let layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
2020-08-31 14:41:41 +02:00
label: Some("iced_wgpu::image pipeline layout"),
2020-08-27 13:41:00 +02:00
push_constant_ranges: &[],
bind_group_layouts: &[&constant_layout, &texture_layout],
});
2021-04-12 23:23:47 -07:00
let shader =
2022-07-02 15:39:42 +08:00
device.create_shader_module(wgpu::ShaderModuleDescriptor {
2023-04-01 16:10:28 -04:00
label: Some("iced_wgpu image shader"),
2021-04-12 23:23:47 -07:00
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
concat!(
include_str!("../shader/vertex.wgsl"),
"\n",
include_str!("../shader/image.wgsl"),
),
2021-04-12 23:23:47 -07:00
)),
});
let pipeline =
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
2020-08-31 14:41:41 +02:00
label: Some("iced_wgpu::image pipeline"),
2020-08-27 13:41:00 +02:00
layout: Some(&layout),
2021-02-03 20:24:48 +01:00
vertex: wgpu::VertexState {
2021-04-11 18:55:57 -07:00
module: &shader,
2024-11-05 13:32:14 +01:00
entry_point: Some("vs_main"),
buffers: &[wgpu::VertexBufferLayout {
array_stride: mem::size_of::<Instance>() as u64,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &wgpu::vertex_attr_array!(
// Center
2025-10-25 22:53:44 +02:00
0 => Float32x2,
// Clip bounds
1 => Float32x4,
// Border radius
2 => Float32x4,
// Tile
3 => Float32x4,
// Rotation
4 => Float32,
2025-10-25 22:53:44 +02:00
// Opacity
5 => Float32,
// Atlas position
6 => Float32x2,
2025-10-25 22:53:44 +02:00
// Atlas scale
7 => Float32x2,
// Layer
2025-10-25 22:53:44 +02:00
8 => Sint32,
// Snap
2025-10-25 22:53:44 +02:00
9 => Uint32,
),
}],
2024-05-01 16:19:08 +02:00
compilation_options:
wgpu::PipelineCompilationOptions::default(),
},
2021-02-03 20:24:48 +01:00
fragment: Some(wgpu::FragmentState {
2021-04-11 18:55:57 -07:00
module: &shader,
2024-11-05 13:32:14 +01:00
entry_point: Some("fs_main"),
2022-07-02 15:39:42 +08:00
targets: &[Some(wgpu::ColorTargetState {
2021-02-03 20:24:48 +01:00
format,
2021-04-11 18:55:57 -07:00
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,
},
}),
2021-08-19 03:06:35 +02:00
write_mask: wgpu::ColorWrites::ALL,
2022-07-02 15:39:42 +08:00
})],
2024-05-01 16:19:08 +02:00
compilation_options:
wgpu::PipelineCompilationOptions::default(),
2021-02-03 20:24:48 +01:00
}),
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,
},
2021-12-20 20:48:28 +01:00
multiview: None,
2024-07-19 19:10:23 +02:00
cache: None,
});
Pipeline {
raw: pipeline,
backend,
nearest_sampler,
linear_sampler,
texture_layout,
constant_layout,
}
}
pub fn create_cache(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
shell: &Shell,
) -> Cache {
Cache::new(
device,
queue,
self.backend,
self.texture_layout.clone(),
shell,
)
}
}
#[derive(Default)]
pub struct State {
layers: Vec<Layer>,
prepare_layer: usize,
2025-10-25 22:53:44 +02:00
nearest_instances: Vec<Instance>,
linear_instances: Vec<Instance>,
}
impl State {
pub fn new() -> Self {
Self::default()
}
pub fn prepare(
&mut self,
pipeline: &Pipeline,
device: &wgpu::Device,
belt: &mut wgpu::util::StagingBelt,
encoder: &mut wgpu::CommandEncoder,
cache: &mut Cache,
images: &Batch,
transformation: Transformation,
scale: f32,
) {
if self.layers.len() <= self.prepare_layer {
self.layers.push(Layer::new(
device,
&pipeline.constant_layout,
&pipeline.nearest_sampler,
&pipeline.linear_sampler,
));
}
let layer = &mut self.layers[self.prepare_layer];
2025-10-25 22:53:44 +02:00
let mut atlas: Option<Arc<wgpu::BindGroup>> = None;
for image in images {
2020-05-19 22:55:12 +02:00
match &image {
#[cfg(feature = "image")]
Image::Raster {
image,
bounds,
clip_bounds,
} => {
if let Some((atlas_entry, bind_group)) = cache
.upload_raster(device, encoder, belt, &image.handle)
{
match atlas.as_mut() {
None => {
atlas = Some(bind_group.clone());
}
Some(atlas) if atlas != bind_group => {
layer.push(
atlas,
2025-10-25 22:53:44 +02:00
&self.nearest_instances,
&self.linear_instances,
);
2025-10-25 22:53:44 +02:00
*atlas = Arc::clone(bind_group);
}
_ => {}
}
2020-05-19 22:55:12 +02:00
add_instances(
2025-10-25 22:53:44 +02:00
*bounds,
*clip_bounds,
2025-10-25 22:53:44 +02:00
image.border_radius,
f32::from(image.rotation),
image.opacity,
image.snap,
2020-05-19 22:55:12 +02:00
atlas_entry,
2026-02-03 16:45:44 -05:00
match image.filter_method {
crate::core::image::FilterMethod::Nearest => {
2025-10-25 22:53:44 +02:00
&mut self.nearest_instances
}
crate::core::image::FilterMethod::Linear => {
2025-10-25 22:53:44 +02:00
&mut self.linear_instances
}
},
2020-05-19 22:55:12 +02:00
);
}
}
#[cfg(not(feature = "image"))]
Image::Raster { .. } => continue,
2020-05-19 22:55:12 +02:00
#[cfg(feature = "svg")]
Image::Vector {
svg,
bounds,
clip_bounds,
} => {
if let Some((atlas_entry, bind_group)) = cache
.upload_vector(
device,
encoder,
belt,
&svg.handle,
svg.color,
2025-10-25 22:53:44 +02:00
bounds.size(),
scale,
)
{
match atlas.as_mut() {
None => {
atlas = Some(bind_group.clone());
}
Some(atlas) if atlas != bind_group => {
layer.push(
atlas,
2025-10-25 22:53:44 +02:00
&self.nearest_instances,
&self.linear_instances,
);
*atlas = bind_group.clone();
}
_ => {}
}
2020-05-19 22:55:12 +02:00
add_instances(
2025-10-25 22:53:44 +02:00
*bounds,
*clip_bounds,
2025-10-25 22:53:44 +02:00
border::radius(0),
2024-08-04 04:52:55 +02:00
f32::from(svg.rotation),
svg.opacity,
true,
2020-05-19 22:55:12 +02:00
atlas_entry,
2025-10-25 22:53:44 +02:00
&mut self.nearest_instances,
2020-05-19 22:55:12 +02:00
);
}
}
2020-05-19 22:55:12 +02:00
#[cfg(not(feature = "svg"))]
Image::Vector { .. } => continue,
}
}
2025-10-25 22:53:44 +02:00
if let Some(atlas) = &atlas {
layer.push(atlas, &self.nearest_instances, &self.linear_instances);
2020-08-27 13:41:00 +02:00
}
2025-10-25 22:53:44 +02:00
layer.prepare(
device,
encoder,
belt,
transformation,
scale,
&self.nearest_instances,
&self.linear_instances,
);
self.prepare_layer += 1;
2025-10-25 22:53:44 +02:00
self.nearest_instances.clear();
self.linear_instances.clear();
}
pub fn render<'a>(
&'a self,
pipeline: &'a Pipeline,
layer: usize,
bounds: Rectangle<u32>,
render_pass: &mut wgpu::RenderPass<'a>,
) {
if let Some(layer) = self.layers.get(layer) {
render_pass.set_pipeline(&pipeline.raw);
render_pass.set_scissor_rect(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
);
layer.render(render_pass);
}
}
pub fn trim(&mut self) {
for layer in &mut self.layers[..self.prepare_layer] {
layer.clear();
}
self.prepare_layer = 0;
}
}
#[derive(Debug)]
struct Layer {
uniforms: wgpu::Buffer,
instances: Buffer<Instance>,
nearest: Vec<Group>,
nearest_layout: wgpu::BindGroup,
2025-10-25 22:53:44 +02:00
nearest_total: usize,
linear: Vec<Group>,
linear_layout: wgpu::BindGroup,
2025-10-25 22:53:44 +02:00
linear_total: usize,
}
#[derive(Debug)]
struct Group {
2025-10-25 22:53:44 +02:00
atlas: Arc<wgpu::BindGroup>,
instance_count: usize,
}
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 instances = Buffer::new(
device,
"iced_wgpu::image instance buffer",
Instance::INITIAL,
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
);
let nearest_layout =
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(
nearest_sampler,
),
},
],
});
let linear_layout =
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(
linear_sampler,
),
},
],
});
Self {
uniforms,
instances,
nearest: Vec::new(),
nearest_layout,
2025-10-25 22:53:44 +02:00
nearest_total: 0,
linear: Vec::new(),
linear_layout,
2025-10-25 22:53:44 +02:00
linear_total: 0,
}
}
fn prepare(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
transformation: Transformation,
scale_factor: f32,
2025-10-25 22:53:44 +02:00
nearest: &[Instance],
linear: &[Instance],
) {
let uniforms = Uniforms {
transform: transformation.into(),
scale_factor,
_padding: [0.0; 3],
};
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);
2025-10-25 22:53:44 +02:00
let _ = self
.instances
.resize(device, self.nearest_total + self.linear_total);
let mut offset = 0;
if !nearest.is_empty() {
offset += self.instances.write(device, encoder, belt, 0, nearest);
}
if !linear.is_empty() {
let _ = self.instances.write(device, encoder, belt, offset, linear);
}
}
fn push(
&mut self,
2025-10-25 22:53:44 +02:00
atlas: &Arc<wgpu::BindGroup>,
nearest: &[Instance],
linear: &[Instance],
) {
2025-10-25 22:53:44 +02:00
let new_nearest = nearest.len() - self.nearest_total;
2025-10-25 22:53:44 +02:00
if new_nearest > 0 {
self.nearest.push(Group {
atlas: atlas.clone(),
2025-10-25 22:53:44 +02:00
instance_count: new_nearest,
});
2025-10-25 22:53:44 +02:00
self.nearest_total = nearest.len();
}
2025-10-25 22:53:44 +02:00
let new_linear = linear.len() - self.linear_total;
2025-10-25 22:53:44 +02:00
if new_linear > 0 {
self.linear.push(Group {
atlas: atlas.clone(),
2025-10-25 22:53:44 +02:00
instance_count: new_linear,
});
2025-10-25 22:53:44 +02:00
self.linear_total = linear.len();
}
}
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
render_pass.set_vertex_buffer(0, self.instances.slice(..));
let mut offset = 0;
if !self.nearest.is_empty() {
render_pass.set_bind_group(0, &self.nearest_layout, &[]);
for group in &self.nearest {
2025-10-25 22:53:44 +02:00
render_pass.set_bind_group(1, group.atlas.as_ref(), &[]);
render_pass
.draw(0..6, offset..offset + group.instance_count as u32);
offset += group.instance_count as u32;
}
}
if !self.linear.is_empty() {
render_pass.set_bind_group(0, &self.linear_layout, &[]);
for group in &self.linear {
2025-10-25 22:53:44 +02:00
render_pass.set_bind_group(1, group.atlas.as_ref(), &[]);
render_pass
.draw(0..6, offset..offset + group.instance_count as u32);
offset += group.instance_count as u32;
}
}
}
fn clear(&mut self) {
self.nearest.clear();
2025-10-25 22:53:44 +02:00
self.nearest_total = 0;
self.linear.clear();
2025-10-25 22:53:44 +02:00
self.linear_total = 0;
}
}
2019-12-01 19:03:05 +01:00
#[repr(C)]
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
struct Instance {
_center: [f32; 2],
2025-10-25 22:53:44 +02:00
_clip_bounds: [f32; 4],
_border_radius: [f32; 4],
_tile: [f32; 4],
_rotation: f32,
_opacity: f32,
_position_in_atlas: [f32; 2],
_size_in_atlas: [f32; 2],
_layer: u32,
_snap: u32,
}
impl Instance {
pub const INITIAL: usize = 20;
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
struct Uniforms {
transform: [f32; 16],
scale_factor: f32,
// Uniforms must be aligned to their largest member,
// this uses a mat4x4<f32> which aligns to 16, so align to that
_padding: [f32; 3],
}
fn add_instances(
2025-10-25 22:53:44 +02:00
bounds: Rectangle,
clip_bounds: Rectangle,
border_radius: border::Radius,
rotation: f32,
opacity: f32,
snap: bool,
entry: &atlas::Entry,
instances: &mut Vec<Instance>,
) {
let center = [
2025-10-25 22:53:44 +02:00
bounds.x + bounds.width / 2.0,
bounds.y + bounds.height / 2.0,
];
let clip_bounds = [
clip_bounds.x,
clip_bounds.y,
clip_bounds.width,
clip_bounds.height,
];
2025-10-25 22:53:44 +02:00
let border_radius = border_radius.into();
match entry {
atlas::Entry::Contiguous(allocation) => {
add_instance(
center,
2025-10-25 22:53:44 +02:00
clip_bounds,
border_radius,
[bounds.x, bounds.y, bounds.width, bounds.height],
rotation,
opacity,
snap,
allocation,
instances,
);
}
atlas::Entry::Fragmented { fragments, size } => {
2025-10-25 22:53:44 +02:00
let scaling_x = bounds.width / size.width as f32;
let scaling_y = bounds.height / size.height as f32;
for fragment in fragments {
let allocation = &fragment.allocation;
let (fragment_x, fragment_y) = fragment.position;
2025-10-25 22:53:44 +02:00
let Size {
width: fragment_width,
height: fragment_height,
} = allocation.size();
2025-10-25 22:53:44 +02:00
let tile = [
bounds.x + fragment_x as f32 * scaling_x,
bounds.y + fragment_y as f32 * scaling_y,
fragment_width as f32 * scaling_x,
fragment_height as f32 * scaling_y,
];
add_instance(
2025-10-25 22:53:44 +02:00
center,
clip_bounds,
border_radius,
tile,
rotation,
opacity,
snap,
allocation,
instances,
);
}
}
}
}
#[inline]
fn add_instance(
center: [f32; 2],
2025-10-25 22:53:44 +02:00
clip_bounds: [f32; 4],
border_radius: [f32; 4],
tile: [f32; 4],
rotation: f32,
opacity: f32,
snap: bool,
allocation: &atlas::Allocation,
instances: &mut Vec<Instance>,
) {
let (x, y) = allocation.position();
let Size { width, height } = allocation.size();
let layer = allocation.layer();
let atlas_size = allocation.atlas_size();
let instance = Instance {
_center: center,
2025-10-25 22:53:44 +02:00
_clip_bounds: clip_bounds,
_border_radius: border_radius,
_tile: tile,
_rotation: rotation,
_opacity: opacity,
_position_in_atlas: [
x as f32 / atlas_size as f32,
y as f32 / atlas_size as f32,
],
_size_in_atlas: [
width as f32 / atlas_size as f32,
height as f32 / atlas_size as f32,
],
_layer: layer as u32,
_snap: snap as u32,
};
instances.push(instance);
}