1pub(crate) mod cache;
2pub(crate) use cache::Cache;
3
4mod atlas;
5
6#[cfg(feature = "image")]
7mod raster;
8
9#[cfg(feature = "svg")]
10mod vector;
11
12use crate::Buffer;
13use crate::core::{Rectangle, Size, Transformation};
14
15use bytemuck::{Pod, Zeroable};
16
17use std::mem;
18use std::sync::Arc;
19
20pub use crate::graphics::Image;
21
22pub type Batch = Vec<Image>;
23
24#[derive(Debug)]
25pub struct Pipeline {
26 pipeline: wgpu::RenderPipeline,
27 backend: wgpu::Backend,
28 nearest_sampler: wgpu::Sampler,
29 linear_sampler: wgpu::Sampler,
30 texture_layout: Arc<wgpu::BindGroupLayout>,
31 constant_layout: wgpu::BindGroupLayout,
32 layers: Vec<Layer>,
33 prepare_layer: usize,
34}
35
36impl Pipeline {
37 pub fn new(
38 device: &wgpu::Device,
39 format: wgpu::TextureFormat,
40 backend: wgpu::Backend,
41 ) -> Self {
42 let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
43 address_mode_u: wgpu::AddressMode::ClampToEdge,
44 address_mode_v: wgpu::AddressMode::ClampToEdge,
45 address_mode_w: wgpu::AddressMode::ClampToEdge,
46 min_filter: wgpu::FilterMode::Nearest,
47 mag_filter: wgpu::FilterMode::Nearest,
48 mipmap_filter: wgpu::FilterMode::Nearest,
49 ..Default::default()
50 });
51
52 let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
53 address_mode_u: wgpu::AddressMode::ClampToEdge,
54 address_mode_v: wgpu::AddressMode::ClampToEdge,
55 address_mode_w: wgpu::AddressMode::ClampToEdge,
56 min_filter: wgpu::FilterMode::Linear,
57 mag_filter: wgpu::FilterMode::Linear,
58 mipmap_filter: wgpu::FilterMode::Linear,
59 ..Default::default()
60 });
61
62 let constant_layout =
63 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
64 label: Some("iced_wgpu::image constants layout"),
65 entries: &[
66 wgpu::BindGroupLayoutEntry {
67 binding: 0,
68 visibility: wgpu::ShaderStages::VERTEX,
69 ty: wgpu::BindingType::Buffer {
70 ty: wgpu::BufferBindingType::Uniform,
71 has_dynamic_offset: false,
72 min_binding_size: wgpu::BufferSize::new(
73 mem::size_of::<Uniforms>() as u64,
74 ),
75 },
76 count: None,
77 },
78 wgpu::BindGroupLayoutEntry {
79 binding: 1,
80 visibility: wgpu::ShaderStages::FRAGMENT,
81 ty: wgpu::BindingType::Sampler(
82 wgpu::SamplerBindingType::Filtering,
83 ),
84 count: None,
85 },
86 ],
87 });
88
89 let texture_layout =
90 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
91 label: Some("iced_wgpu::image texture atlas layout"),
92 entries: &[wgpu::BindGroupLayoutEntry {
93 binding: 0,
94 visibility: wgpu::ShaderStages::FRAGMENT,
95 ty: wgpu::BindingType::Texture {
96 sample_type: wgpu::TextureSampleType::Float {
97 filterable: true,
98 },
99 view_dimension: wgpu::TextureViewDimension::D2Array,
100 multisampled: false,
101 },
102 count: None,
103 }],
104 });
105
106 let layout =
107 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
108 label: Some("iced_wgpu::image pipeline layout"),
109 push_constant_ranges: &[],
110 bind_group_layouts: &[&constant_layout, &texture_layout],
111 });
112
113 let shader =
114 device.create_shader_module(wgpu::ShaderModuleDescriptor {
115 label: Some("iced_wgpu image shader"),
116 source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
117 concat!(
118 include_str!("../shader/vertex.wgsl"),
119 "\n",
120 include_str!("../shader/image.wgsl"),
121 ),
122 )),
123 });
124
125 let pipeline =
126 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
127 label: Some("iced_wgpu::image pipeline"),
128 layout: Some(&layout),
129 vertex: wgpu::VertexState {
130 module: &shader,
131 entry_point: Some("vs_main"),
132 buffers: &[wgpu::VertexBufferLayout {
133 array_stride: mem::size_of::<Instance>() as u64,
134 step_mode: wgpu::VertexStepMode::Instance,
135 attributes: &wgpu::vertex_attr_array!(
136 0 => Float32x2,
138 1 => Float32x2,
140 2 => Float32x2,
142 3 => Float32,
144 4 => Float32,
146 5 => Float32x2,
148 6 => Float32x2,
150 7 => Sint32,
152 8 => Uint32,
154 ),
155 }],
156 compilation_options:
157 wgpu::PipelineCompilationOptions::default(),
158 },
159 fragment: Some(wgpu::FragmentState {
160 module: &shader,
161 entry_point: Some("fs_main"),
162 targets: &[Some(wgpu::ColorTargetState {
163 format,
164 blend: Some(wgpu::BlendState {
165 color: wgpu::BlendComponent {
166 src_factor: wgpu::BlendFactor::SrcAlpha,
167 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
168 operation: wgpu::BlendOperation::Add,
169 },
170 alpha: wgpu::BlendComponent {
171 src_factor: wgpu::BlendFactor::One,
172 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
173 operation: wgpu::BlendOperation::Add,
174 },
175 }),
176 write_mask: wgpu::ColorWrites::ALL,
177 })],
178 compilation_options:
179 wgpu::PipelineCompilationOptions::default(),
180 }),
181 primitive: wgpu::PrimitiveState {
182 topology: wgpu::PrimitiveTopology::TriangleList,
183 front_face: wgpu::FrontFace::Cw,
184 ..Default::default()
185 },
186 depth_stencil: None,
187 multisample: wgpu::MultisampleState {
188 count: 1,
189 mask: !0,
190 alpha_to_coverage_enabled: false,
191 },
192 multiview: None,
193 cache: None,
194 });
195
196 Pipeline {
197 pipeline,
198 backend,
199 nearest_sampler,
200 linear_sampler,
201 texture_layout: Arc::new(texture_layout),
202 constant_layout,
203 layers: Vec::new(),
204 prepare_layer: 0,
205 }
206 }
207
208 pub fn create_cache(&self, device: &wgpu::Device) -> Cache {
209 Cache::new(device, self.backend, self.texture_layout.clone())
210 }
211
212 pub fn prepare(
213 &mut self,
214 device: &wgpu::Device,
215 encoder: &mut wgpu::CommandEncoder,
216 belt: &mut wgpu::util::StagingBelt,
217 cache: &mut Cache,
218 images: &Batch,
219 transformation: Transformation,
220 scale: f32,
221 ) {
222 let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
223 let linear_instances: &mut Vec<Instance> = &mut Vec::new();
224
225 for image in images {
226 match &image {
227 #[cfg(feature = "image")]
228 Image::Raster(image, bounds) => {
229 if let Some(atlas_entry) =
230 cache.upload_raster(device, encoder, &image.handle)
231 {
232 add_instances(
233 [bounds.x, bounds.y],
234 [bounds.width, bounds.height],
235 f32::from(image.rotation),
236 image.opacity,
237 image.snap,
238 atlas_entry,
239 match image.filter_method {
240 crate::core::image::FilterMethod::Nearest => {
241 nearest_instances
242 }
243 crate::core::image::FilterMethod::Linear => {
244 linear_instances
245 }
246 },
247 );
248 }
249 }
250 #[cfg(not(feature = "image"))]
251 Image::Raster { .. } => {}
252
253 #[cfg(feature = "svg")]
254 Image::Vector(svg, bounds) => {
255 let size = [bounds.width, bounds.height];
256
257 if let Some(atlas_entry) = cache.upload_vector(
258 device,
259 encoder,
260 &svg.handle,
261 svg.color,
262 size,
263 scale,
264 ) {
265 add_instances(
266 [bounds.x, bounds.y],
267 size,
268 f32::from(svg.rotation),
269 svg.opacity,
270 true,
271 atlas_entry,
272 nearest_instances,
273 );
274 }
275 }
276 #[cfg(not(feature = "svg"))]
277 Image::Vector { .. } => {}
278 }
279 }
280
281 if nearest_instances.is_empty() && linear_instances.is_empty() {
282 return;
283 }
284
285 if self.layers.len() <= self.prepare_layer {
286 self.layers.push(Layer::new(
287 device,
288 &self.constant_layout,
289 &self.nearest_sampler,
290 &self.linear_sampler,
291 ));
292 }
293
294 let layer = &mut self.layers[self.prepare_layer];
295
296 layer.prepare(
297 device,
298 encoder,
299 belt,
300 nearest_instances,
301 linear_instances,
302 transformation,
303 scale,
304 );
305
306 self.prepare_layer += 1;
307 }
308
309 pub fn render<'a>(
310 &'a self,
311 cache: &'a Cache,
312 layer: usize,
313 bounds: Rectangle<u32>,
314 render_pass: &mut wgpu::RenderPass<'a>,
315 ) {
316 if let Some(layer) = self.layers.get(layer) {
317 render_pass.set_pipeline(&self.pipeline);
318
319 render_pass.set_scissor_rect(
320 bounds.x,
321 bounds.y,
322 bounds.width,
323 bounds.height,
324 );
325
326 render_pass.set_bind_group(1, cache.bind_group(), &[]);
327
328 layer.render(render_pass);
329 }
330 }
331
332 pub fn end_frame(&mut self) {
333 self.prepare_layer = 0;
334 }
335}
336
337#[derive(Debug)]
338struct Layer {
339 uniforms: wgpu::Buffer,
340 nearest: Data,
341 linear: Data,
342}
343
344impl Layer {
345 fn new(
346 device: &wgpu::Device,
347 constant_layout: &wgpu::BindGroupLayout,
348 nearest_sampler: &wgpu::Sampler,
349 linear_sampler: &wgpu::Sampler,
350 ) -> Self {
351 let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
352 label: Some("iced_wgpu::image uniforms buffer"),
353 size: mem::size_of::<Uniforms>() as u64,
354 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
355 mapped_at_creation: false,
356 });
357
358 let nearest =
359 Data::new(device, constant_layout, nearest_sampler, &uniforms);
360
361 let linear =
362 Data::new(device, constant_layout, linear_sampler, &uniforms);
363
364 Self {
365 uniforms,
366 nearest,
367 linear,
368 }
369 }
370
371 fn prepare(
372 &mut self,
373 device: &wgpu::Device,
374 encoder: &mut wgpu::CommandEncoder,
375 belt: &mut wgpu::util::StagingBelt,
376 nearest_instances: &[Instance],
377 linear_instances: &[Instance],
378 transformation: Transformation,
379 scale_factor: f32,
380 ) {
381 let uniforms = Uniforms {
382 transform: transformation.into(),
383 scale_factor,
384 _padding: [0.0; 3],
385 };
386
387 let bytes = bytemuck::bytes_of(&uniforms);
388
389 belt.write_buffer(
390 encoder,
391 &self.uniforms,
392 0,
393 (bytes.len() as u64).try_into().expect("Sized uniforms"),
394 device,
395 )
396 .copy_from_slice(bytes);
397
398 self.nearest
399 .upload(device, encoder, belt, nearest_instances);
400
401 self.linear.upload(device, encoder, belt, linear_instances);
402 }
403
404 fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
405 self.nearest.render(render_pass);
406 self.linear.render(render_pass);
407 }
408}
409
410#[derive(Debug)]
411struct Data {
412 constants: wgpu::BindGroup,
413 instances: Buffer<Instance>,
414 instance_count: usize,
415}
416
417impl Data {
418 pub fn new(
419 device: &wgpu::Device,
420 constant_layout: &wgpu::BindGroupLayout,
421 sampler: &wgpu::Sampler,
422 uniforms: &wgpu::Buffer,
423 ) -> Self {
424 let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
425 label: Some("iced_wgpu::image constants bind group"),
426 layout: constant_layout,
427 entries: &[
428 wgpu::BindGroupEntry {
429 binding: 0,
430 resource: wgpu::BindingResource::Buffer(
431 wgpu::BufferBinding {
432 buffer: uniforms,
433 offset: 0,
434 size: None,
435 },
436 ),
437 },
438 wgpu::BindGroupEntry {
439 binding: 1,
440 resource: wgpu::BindingResource::Sampler(sampler),
441 },
442 ],
443 });
444
445 let instances = Buffer::new(
446 device,
447 "iced_wgpu::image instance buffer",
448 Instance::INITIAL,
449 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
450 );
451
452 Self {
453 constants,
454 instances,
455 instance_count: 0,
456 }
457 }
458
459 fn upload(
460 &mut self,
461 device: &wgpu::Device,
462 encoder: &mut wgpu::CommandEncoder,
463 belt: &mut wgpu::util::StagingBelt,
464 instances: &[Instance],
465 ) {
466 self.instance_count = instances.len();
467
468 if self.instance_count == 0 {
469 return;
470 }
471
472 let _ = self.instances.resize(device, instances.len());
473 let _ = self.instances.write(device, encoder, belt, 0, instances);
474 }
475
476 fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
477 if self.instance_count == 0 {
478 return;
479 }
480
481 render_pass.set_bind_group(0, &self.constants, &[]);
482 render_pass.set_vertex_buffer(0, self.instances.slice(..));
483
484 render_pass.draw(0..6, 0..self.instance_count as u32);
485 }
486}
487
488#[repr(C)]
489#[derive(Debug, Clone, Copy, Zeroable, Pod)]
490struct Instance {
491 _position: [f32; 2],
492 _center: [f32; 2],
493 _size: [f32; 2],
494 _rotation: f32,
495 _opacity: f32,
496 _position_in_atlas: [f32; 2],
497 _size_in_atlas: [f32; 2],
498 _layer: u32,
499 _snap: u32,
500}
501
502impl Instance {
503 pub const INITIAL: usize = 20;
504}
505
506#[repr(C)]
507#[derive(Debug, Clone, Copy, Zeroable, Pod)]
508struct Uniforms {
509 transform: [f32; 16],
510 scale_factor: f32,
511 _padding: [f32; 3],
514}
515
516fn add_instances(
517 image_position: [f32; 2],
518 image_size: [f32; 2],
519 rotation: f32,
520 opacity: f32,
521 snap: bool,
522 entry: &atlas::Entry,
523 instances: &mut Vec<Instance>,
524) {
525 let center = [
526 image_position[0] + image_size[0] / 2.0,
527 image_position[1] + image_size[1] / 2.0,
528 ];
529
530 match entry {
531 atlas::Entry::Contiguous(allocation) => {
532 add_instance(
533 image_position,
534 center,
535 image_size,
536 rotation,
537 opacity,
538 snap,
539 allocation,
540 instances,
541 );
542 }
543 atlas::Entry::Fragmented { fragments, size } => {
544 let scaling_x = image_size[0] / size.width as f32;
545 let scaling_y = image_size[1] / size.height as f32;
546
547 for fragment in fragments {
548 let allocation = &fragment.allocation;
549
550 let [x, y] = image_position;
551 let (fragment_x, fragment_y) = fragment.position;
552 let Size {
553 width: fragment_width,
554 height: fragment_height,
555 } = allocation.size();
556
557 let position = [
558 x + fragment_x as f32 * scaling_x,
559 y + fragment_y as f32 * scaling_y,
560 ];
561
562 let size = [
563 fragment_width as f32 * scaling_x,
564 fragment_height as f32 * scaling_y,
565 ];
566
567 add_instance(
568 position, center, size, rotation, opacity, snap,
569 allocation, instances,
570 );
571 }
572 }
573 }
574}
575
576#[inline]
577fn add_instance(
578 position: [f32; 2],
579 center: [f32; 2],
580 size: [f32; 2],
581 rotation: f32,
582 opacity: f32,
583 snap: bool,
584 allocation: &atlas::Allocation,
585 instances: &mut Vec<Instance>,
586) {
587 let (x, y) = allocation.position();
588 let Size { width, height } = allocation.size();
589 let layer = allocation.layer();
590
591 let instance = Instance {
592 _position: position,
593 _center: center,
594 _size: size,
595 _rotation: rotation,
596 _opacity: opacity,
597 _position_in_atlas: [
598 (x as f32 + 0.5) / atlas::SIZE as f32,
599 (y as f32 + 0.5) / atlas::SIZE as f32,
600 ],
601 _size_in_atlas: [
602 (width as f32 - 1.0) / atlas::SIZE as f32,
603 (height as f32 - 1.0) / atlas::SIZE as f32,
604 ],
605 _layer: layer as u32,
606 _snap: snap as u32,
607 };
608
609 instances.push(instance);
610}