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