1use super::icons::{FORMAT as ICON_FORMAT, GpuIcons, Icons};
2use super::{
3 Draw, FX, Fill, Fit, FlatScene, Glyphs, INSTANCE_ATTRIBUTES, Instance, KIND_SHADOW, LIT, Order,
4 Shape, Step, pack_shadow, pack_shape, shader,
5};
6use crate::text::{GpuAtlas, PackedGlyph, TextSpace, pack_glyphs, pack_quads, screen_bounds};
7use bytemuck::{Pod, Zeroable};
8use pfx_gpu::{GpuProfiler, wgpu};
9use std::ops::Range;
10use wgpu::util::DeviceExt;
11
12pub const COLOUR_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float;
13pub const ID_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R32Uint;
14const TEXT_SLOT: u64 = 256;
15const TEXT_CACHE: usize = 64;
16const BASE_PIPELINES: usize = 6;
17const ABSORBED_AREA: f32 = 128.0 * 128.0;
18const GRADIENT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba32Float;
19const GRADIENT_TEXELS: u32 = 12;
20
21pub struct FlatIcons {
22 pub icons: Icons,
23 gpu: GpuIcons,
24}
25
26impl FlatIcons {
27 pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, icons: Icons) -> Self {
28 let gpu = icons.upload(device, queue);
29 Self { icons, gpu }
30 }
31
32 pub fn view(&self) -> &wgpu::TextureView {
33 &self.gpu.view
34 }
35}
36
37pub struct Environment {
38 _texture: wgpu::Texture,
39 view: wgpu::TextureView,
40 mips: u32,
41 pub intensity: f32,
42}
43
44impl Environment {
45 pub fn new(
46 device: &wgpu::Device,
47 queue: &wgpu::Queue,
48 width: u32,
49 height: u32,
50 linear: &[[f32; 4]],
51 intensity: f32,
52 ) -> Result<Self, String> {
53 if width == 0 || height == 0 || linear.len() != (width * height) as usize {
54 return Err("the environment needs width × height linear RGBA texels".into());
55 }
56 if width > 4096 || height > 4096 {
57 return Err("the environment is larger than 4096 texels a side".into());
58 }
59 if !intensity.is_finite() || intensity < 0.0 {
60 return Err("the environment intensity must be finite and nonnegative".into());
61 }
62 let mut levels = vec![(width, height, linear.to_vec())];
63 while let Some((w, h, texels)) = levels.last().filter(|(w, h, _)| *w > 1 || *h > 1) {
64 let (nw, nh) = ((w / 2).max(1), (h / 2).max(1));
65 let mut next = vec![[0.0f32; 4]; (nw * nh) as usize];
66 for y in 0..nh {
67 for x in 0..nw {
68 let mut sum = [0.0f32; 4];
69 for (dx, dy) in [(0, 0), (1, 0), (0, 1), (1, 1)] {
70 let sx = (x * 2 + dx).min(w - 1);
71 let sy = (y * 2 + dy).min(h - 1);
72 let texel = texels[(sy * w + sx) as usize];
73 for channel in 0..4 {
74 sum[channel] += texel[channel] * 0.25;
75 }
76 }
77 next[(y * nw + x) as usize] = sum;
78 }
79 }
80 levels.push((nw, nh, next));
81 }
82 let mips = levels.len() as u32;
83 let texture = device.create_texture(&wgpu::TextureDescriptor {
84 label: Some("flat environment"),
85 size: wgpu::Extent3d {
86 width,
87 height,
88 depth_or_array_layers: 1,
89 },
90 mip_level_count: mips,
91 sample_count: 1,
92 dimension: wgpu::TextureDimension::D2,
93 format: wgpu::TextureFormat::Rgba16Float,
94 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
95 view_formats: &[],
96 });
97 for (level, (w, h, texels)) in levels.iter().enumerate() {
98 let halves = texels
99 .iter()
100 .flatten()
101 .map(|value| half::f16::from_f32(*value).to_bits())
102 .collect::<Vec<_>>();
103 queue.write_texture(
104 wgpu::TexelCopyTextureInfo {
105 texture: &texture,
106 mip_level: level as u32,
107 origin: wgpu::Origin3d::ZERO,
108 aspect: wgpu::TextureAspect::All,
109 },
110 bytemuck::cast_slice(&halves),
111 wgpu::TexelCopyBufferLayout {
112 offset: 0,
113 bytes_per_row: Some(w * 8),
114 rows_per_image: Some(*h),
115 },
116 wgpu::Extent3d {
117 width: *w,
118 height: *h,
119 depth_or_array_layers: 1,
120 },
121 );
122 }
123 let view = texture.create_view(&Default::default());
124 Ok(Self {
125 _texture: texture,
126 view,
127 mips,
128 intensity,
129 })
130 }
131
132 pub fn mips(&self) -> u32 {
133 self.mips
134 }
135}
136
137pub struct FlatTarget<'a> {
138 pub colour: &'a wgpu::TextureView,
139 pub ids: Option<&'a wgpu::TextureView>,
140 pub size: [u32; 2],
141 pub clear: Option<[f32; 4]>,
142 pub clear_ids: bool,
143}
144
145#[repr(C)]
146#[derive(Clone, Copy, Pod, Zeroable)]
147struct View {
148 size: [f32; 4],
149 light: [f32; 4],
150 environment: [f32; 4],
151}
152
153#[repr(C)]
154#[derive(Clone, Copy, Pod, Zeroable)]
155struct LayerQuad {
156 rect: [f32; 4],
157 opacity: [f32; 4],
158}
159
160#[repr(C)]
161#[derive(Clone, Copy, Pod, Zeroable)]
162struct TextUniform {
163 matrix: [[f32; 4]; 4],
164 view_projection: [[f32; 4]; 4],
165 model: [[f32; 4]; 4],
166 rest_normal: [f32; 4],
167 atlas_size: [f32; 2],
168 mode: u32,
169 deformer: u32,
170 cells: [u32; 4],
171}
172
173#[derive(Clone, Debug, PartialEq)]
174enum Op {
175 Instances {
176 range: Range<u32>,
177 lit: bool,
178 },
179 Text {
180 glyphs: Range<u32>,
181 slot: u32,
182 atlas: usize,
183 id: u32,
184 },
185 Layer {
186 quad: u32,
187 },
188 EndLayer {
189 quad: u32,
190 },
191}
192
193struct Pool<T: Pod> {
194 label: &'static str,
195 usage: wgpu::BufferUsages,
196 buffer: Option<wgpu::Buffer>,
197 capacity: u64,
198 items: Vec<T>,
199}
200
201impl<T: Pod> Pool<T> {
202 fn new(label: &'static str, usage: wgpu::BufferUsages) -> Self {
203 Self {
204 label,
205 usage,
206 buffer: None,
207 capacity: 0,
208 items: Vec::new(),
209 }
210 }
211
212 fn upload(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) -> bool {
213 let bytes = (self.items.len() * std::mem::size_of::<T>()) as u64;
214 if bytes == 0 {
215 return false;
216 }
217 let grown = self.buffer.is_none() || bytes > self.capacity;
218 if grown {
219 self.capacity = bytes.next_power_of_two().max(4096);
220 self.buffer = Some(device.create_buffer(&wgpu::BufferDescriptor {
221 label: Some(self.label),
222 size: self.capacity,
223 usage: self.usage | wgpu::BufferUsages::COPY_DST,
224 mapped_at_creation: false,
225 }));
226 }
227 if let Some(buffer) = &self.buffer {
228 queue.write_buffer(buffer, 0, bytemuck::cast_slice(&self.items));
229 }
230 grown
231 }
232}
233
234struct Finish {
235 format: wgpu::TextureFormat,
236 linear: bool,
237 blend: bool,
238 pipeline: wgpu::RenderPipeline,
239}
240
241pub struct FlatPass {
242 view_layout: wgpu::BindGroupLayout,
243 layer_layout: wgpu::BindGroupLayout,
244 text_layout: wgpu::BindGroupLayout,
245 finish_layout: wgpu::BindGroupLayout,
246 colour: wgpu::RenderPipeline,
247 ids: wgpu::RenderPipeline,
248 lit: Option<(wgpu::RenderPipeline, wgpu::RenderPipeline)>,
249 module: wgpu::ShaderModule,
250 main_layout: wgpu::PipelineLayout,
251 clear: wgpu::RenderPipeline,
252 composite: wgpu::RenderPipeline,
253 text: wgpu::RenderPipeline,
254 text_ids: wgpu::RenderPipeline,
255 finish_module: wgpu::ShaderModule,
256 finish_pipeline_layout: wgpu::PipelineLayout,
257 finishes: Vec<Finish>,
258 uniform: wgpu::Buffer,
259 icon_sampler: wgpu::Sampler,
260 environment_sampler: wgpu::Sampler,
261 text_sampler: wgpu::Sampler,
262 empty_icons: wgpu::TextureView,
263 empty_sprites: wgpu::TextureView,
264 sprite_sampler: wgpu::Sampler,
265 empty_environment: wgpu::TextureView,
266 empty_gradients: wgpu::TextureView,
267 gradients: Option<(wgpu::Texture, wgpu::TextureView, u32)>,
268 gradient_rows: Vec<[[f32; 4]; GRADIENT_TEXELS as usize]>,
269 uploaded_rows: Vec<[[f32; 4]; GRADIENT_TEXELS as usize]>,
270 bind: Option<(
271 wgpu::TextureView,
272 wgpu::TextureView,
273 wgpu::TextureView,
274 wgpu::TextureView,
275 wgpu::BindGroup,
276 )>,
277 text_binds: Vec<(wgpu::TextureView, wgpu::BindGroup)>,
278 instances: Pool<Instance>,
279 quads: Pool<LayerQuad>,
280 glyphs: Pool<PackedGlyph>,
281 text_uniforms: Pool<[u8; TEXT_SLOT as usize]>,
282 scratch: Vec<PackedGlyph>,
283 text_items: Vec<(usize, usize, Range<u32>, u32)>,
284 order: Order,
285 ops: Vec<Op>,
286 layer: Option<(wgpu::Texture, wgpu::TextureView, wgpu::BindGroup, [u32; 2])>,
287 colour_target: Option<(wgpu::Texture, wgpu::TextureView, [u32; 2])>,
288 size: [u32; 2],
289 capacity: Option<[u32; 2]>,
290 clip: Option<[u32; 4]>,
291 passes: u32,
292 made: usize,
293}
294
295fn instance_attributes() -> [wgpu::VertexAttribute; INSTANCE_ATTRIBUTES as usize] {
296 std::array::from_fn(|index| wgpu::VertexAttribute {
297 format: if index == 9 {
298 wgpu::VertexFormat::Uint32x4
299 } else {
300 wgpu::VertexFormat::Float32x4
301 },
302 offset: (index * 16) as u64,
303 shader_location: index as u32,
304 })
305}
306
307pub fn view_entries() -> Vec<wgpu::BindGroupLayoutEntry> {
308 let texture = |binding| wgpu::BindGroupLayoutEntry {
309 binding,
310 visibility: wgpu::ShaderStages::FRAGMENT,
311 ty: wgpu::BindingType::Texture {
312 sample_type: wgpu::TextureSampleType::Float { filterable: true },
313 view_dimension: wgpu::TextureViewDimension::D2,
314 multisampled: false,
315 },
316 count: None,
317 };
318 let sampler = |binding| wgpu::BindGroupLayoutEntry {
319 binding,
320 visibility: wgpu::ShaderStages::FRAGMENT,
321 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
322 count: None,
323 };
324 vec![
325 wgpu::BindGroupLayoutEntry {
326 binding: 0,
327 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
328 ty: wgpu::BindingType::Buffer {
329 ty: wgpu::BufferBindingType::Uniform,
330 has_dynamic_offset: false,
331 min_binding_size: None,
332 },
333 count: None,
334 },
335 texture(1),
336 sampler(2),
337 texture(3),
338 sampler(4),
339 texture(6),
340 sampler(7),
341 wgpu::BindGroupLayoutEntry {
342 binding: 5,
343 visibility: wgpu::ShaderStages::FRAGMENT,
344 ty: wgpu::BindingType::Texture {
345 sample_type: wgpu::TextureSampleType::Float { filterable: false },
346 view_dimension: wgpu::TextureViewDimension::D2,
347 multisampled: false,
348 },
349 count: None,
350 },
351 ]
352}
353
354pub fn layer_entries() -> Vec<wgpu::BindGroupLayoutEntry> {
355 vec![wgpu::BindGroupLayoutEntry {
356 binding: 0,
357 visibility: wgpu::ShaderStages::FRAGMENT,
358 ty: wgpu::BindingType::Texture {
359 sample_type: wgpu::TextureSampleType::Float { filterable: false },
360 view_dimension: wgpu::TextureViewDimension::D2,
361 multisampled: false,
362 },
363 count: None,
364 }]
365}
366
367pub fn text_entries() -> Vec<wgpu::BindGroupLayoutEntry> {
368 let mut entries = crate::text::text_entries();
369 entries[0].ty = wgpu::BindingType::Buffer {
370 ty: wgpu::BufferBindingType::Uniform,
371 has_dynamic_offset: true,
372 min_binding_size: wgpu::BufferSize::new(std::mem::size_of::<TextUniform>() as u64),
373 };
374 entries
375}
376
377pub fn finish_entries() -> Vec<wgpu::BindGroupLayoutEntry> {
378 layer_entries()
379}
380
381fn empty(
382 device: &wgpu::Device,
383 queue: &wgpu::Queue,
384 format: wgpu::TextureFormat,
385 bytes: &[u8],
386) -> wgpu::TextureView {
387 device
388 .create_texture_with_data(
389 queue,
390 &wgpu::TextureDescriptor {
391 label: Some("flat empty input"),
392 size: wgpu::Extent3d {
393 width: 1,
394 height: 1,
395 depth_or_array_layers: 1,
396 },
397 mip_level_count: 1,
398 sample_count: 1,
399 dimension: wgpu::TextureDimension::D2,
400 format,
401 usage: wgpu::TextureUsages::TEXTURE_BINDING,
402 view_formats: &[],
403 },
404 wgpu::util::TextureDataOrder::LayerMajor,
405 bytes,
406 )
407 .create_view(&Default::default())
408}
409
410fn colour_state(format: wgpu::TextureFormat, blend: bool) -> wgpu::ColorTargetState {
411 wgpu::ColorTargetState {
412 format,
413 blend: blend.then_some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
414 write_mask: wgpu::ColorWrites::ALL,
415 }
416}
417
418#[allow(clippy::too_many_arguments)]
419fn pipeline(
420 device: &wgpu::Device,
421 label: &str,
422 layout: &wgpu::PipelineLayout,
423 module: &wgpu::ShaderModule,
424 entries: (&str, &str),
425 buffers: &[Option<wgpu::VertexBufferLayout<'_>>],
426 target: wgpu::ColorTargetState,
427 topology: wgpu::PrimitiveTopology,
428) -> wgpu::RenderPipeline {
429 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
430 label: Some(label),
431 layout: Some(layout),
432 vertex: wgpu::VertexState {
433 module,
434 entry_point: Some(entries.0),
435 buffers,
436 compilation_options: Default::default(),
437 },
438 primitive: wgpu::PrimitiveState {
439 topology,
440 cull_mode: None,
441 ..Default::default()
442 },
443 depth_stencil: None,
444 multisample: Default::default(),
445 fragment: Some(wgpu::FragmentState {
446 module,
447 entry_point: Some(entries.1),
448 targets: &[Some(target)],
449 compilation_options: Default::default(),
450 }),
451 multiview_mask: None,
452 cache: None,
453 })
454}
455
456fn rect_union(a: [f32; 4], b: [f32; 4]) -> [f32; 4] {
457 [
458 a[0].min(b[0]),
459 a[1].min(b[1]),
460 a[2].max(b[2]),
461 a[3].max(b[3]),
462 ]
463}
464
465impl FlatPass {
466 pub fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Self {
467 Self::with_source(device, queue, shader::source())
468 }
469
470 pub(crate) fn with_source(device: &wgpu::Device, queue: &wgpu::Queue, source: String) -> Self {
471 let group = |label: &str, entries: &[wgpu::BindGroupLayoutEntry]| {
472 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
473 label: Some(label),
474 entries,
475 })
476 };
477 let view_layout = group("flat view", &view_entries());
478 let layer_layout = group("flat layer", &layer_entries());
479 let text_layout = group("flat text", &text_entries());
480 let finish_layout = group("flat finish", &finish_entries());
481 let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
482 label: Some("flat pass"),
483 source: wgpu::ShaderSource::Wgsl(source.into()),
484 });
485 let text_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
486 label: Some("flat text"),
487 source: wgpu::ShaderSource::Wgsl(crate::text::shader_source().into()),
488 });
489 let finish_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
490 label: Some("flat finish"),
491 source: wgpu::ShaderSource::Wgsl(shader::FINISH_WGSL.into()),
492 });
493 let main_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
494 label: Some("flat"),
495 bind_group_layouts: &[Some(&view_layout)],
496 immediate_size: 0,
497 });
498 let composite_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
499 label: Some("flat layer composite"),
500 bind_group_layouts: &[Some(&view_layout), Some(&layer_layout)],
501 immediate_size: 0,
502 });
503 let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
504 label: Some("flat text"),
505 bind_group_layouts: &[Some(&text_layout)],
506 immediate_size: 0,
507 });
508 let finish_pipeline_layout =
509 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
510 label: Some("flat finish"),
511 bind_group_layouts: &[Some(&finish_layout)],
512 immediate_size: 0,
513 });
514 let attributes = instance_attributes();
515 let instance_buffers = [Some(wgpu::VertexBufferLayout {
516 array_stride: std::mem::size_of::<Instance>() as u64,
517 step_mode: wgpu::VertexStepMode::Instance,
518 attributes: &attributes,
519 })];
520 let quad_attributes = [
521 wgpu::VertexAttribute {
522 format: wgpu::VertexFormat::Float32x4,
523 offset: 0,
524 shader_location: 0,
525 },
526 wgpu::VertexAttribute {
527 format: wgpu::VertexFormat::Float32x4,
528 offset: 16,
529 shader_location: 1,
530 },
531 ];
532 let quad_buffers = [Some(wgpu::VertexBufferLayout {
533 array_stride: std::mem::size_of::<LayerQuad>() as u64,
534 step_mode: wgpu::VertexStepMode::Instance,
535 attributes: &quad_attributes,
536 })];
537 let glyph_attributes: [wgpu::VertexAttribute; 6] =
538 std::array::from_fn(|index| wgpu::VertexAttribute {
539 format: wgpu::VertexFormat::Float32x4,
540 offset: (index * 16) as u64,
541 shader_location: index as u32,
542 });
543 let glyph_buffers = [Some(wgpu::VertexBufferLayout {
544 array_stride: std::mem::size_of::<PackedGlyph>() as u64,
545 step_mode: wgpu::VertexStepMode::Instance,
546 attributes: &glyph_attributes,
547 })];
548 let strip = wgpu::PrimitiveTopology::TriangleStrip;
549 let list = wgpu::PrimitiveTopology::TriangleList;
550 let colour = pipeline(
551 device,
552 "flat colour",
553 &main_layout,
554 &module,
555 ("flat_vertex", "flat_colour"),
556 &instance_buffers,
557 colour_state(COLOUR_FORMAT, true),
558 strip,
559 );
560 let ids = pipeline(
561 device,
562 "flat ids",
563 &main_layout,
564 &module,
565 ("flat_id_vertex", "flat_id"),
566 &instance_buffers,
567 wgpu::ColorTargetState {
568 format: ID_FORMAT,
569 blend: None,
570 write_mask: wgpu::ColorWrites::ALL,
571 },
572 strip,
573 );
574 let clear = pipeline(
575 device,
576 "flat layer clear",
577 &main_layout,
578 &module,
579 ("flat_layer_vertex", "flat_layer_clear"),
580 &quad_buffers,
581 colour_state(COLOUR_FORMAT, false),
582 strip,
583 );
584 let composite = pipeline(
585 device,
586 "flat layer composite",
587 &composite_layout,
588 &module,
589 ("flat_layer_vertex", "flat_layer_composite"),
590 &quad_buffers,
591 colour_state(COLOUR_FORMAT, true),
592 strip,
593 );
594 let text = pipeline(
595 device,
596 "flat text",
597 &text_pipeline_layout,
598 &text_module,
599 ("vs_main", "fs_main"),
600 &glyph_buffers,
601 colour_state(COLOUR_FORMAT, true),
602 list,
603 );
604 let text_ids = pipeline(
605 device,
606 "flat text ids",
607 &text_pipeline_layout,
608 &text_module,
609 ("vs_main", "fs_id"),
610 &glyph_buffers,
611 wgpu::ColorTargetState {
612 format: ID_FORMAT,
613 blend: None,
614 write_mask: wgpu::ColorWrites::ALL,
615 },
616 list,
617 );
618 let uniform = device.create_buffer(&wgpu::BufferDescriptor {
619 label: Some("flat view"),
620 size: std::mem::size_of::<View>() as u64,
621 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
622 mapped_at_creation: false,
623 });
624 let linear = |label, mipmaps: bool, repeat: bool| {
625 device.create_sampler(&wgpu::SamplerDescriptor {
626 label: Some(label),
627 address_mode_u: if repeat {
628 wgpu::AddressMode::Repeat
629 } else {
630 wgpu::AddressMode::ClampToEdge
631 },
632 address_mode_v: wgpu::AddressMode::ClampToEdge,
633 mag_filter: wgpu::FilterMode::Linear,
634 min_filter: wgpu::FilterMode::Linear,
635 mipmap_filter: if mipmaps {
636 wgpu::MipmapFilterMode::Linear
637 } else {
638 wgpu::MipmapFilterMode::Nearest
639 },
640 ..Default::default()
641 })
642 };
643 let text_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
644 label: Some("flat text"),
645 mag_filter: wgpu::FilterMode::Linear,
646 min_filter: wgpu::FilterMode::Linear,
647 mipmap_filter: wgpu::MipmapFilterMode::Linear,
648 lod_max_clamp: 3.0,
649 ..Default::default()
650 });
651 let far = half::f16::from_f32(1.0e4).to_bits().to_le_bytes();
652 Self {
653 view_layout,
654 layer_layout,
655 text_layout,
656 finish_layout,
657 colour,
658 ids,
659 lit: None,
660 module,
661 main_layout,
662 clear,
663 composite,
664 text,
665 text_ids,
666 finish_module,
667 finish_pipeline_layout,
668 finishes: Vec::new(),
669 uniform,
670 icon_sampler: linear("flat icons", false, false),
671 environment_sampler: linear("flat environment", true, true),
672 text_sampler,
673 empty_icons: empty(device, queue, ICON_FORMAT, &far),
674 empty_sprites: empty(device, queue, super::sprites::SPRITE_FORMAT, &[255; 4]),
675 sprite_sampler: super::sprites::sampler(device, super::SpriteFilter::Linear),
676 empty_environment: empty(device, queue, wgpu::TextureFormat::Rgba16Float, &[0; 8]),
677 empty_gradients: empty(device, queue, GRADIENT_FORMAT, &[0; 16]),
678 gradients: None,
679 gradient_rows: Vec::new(),
680 uploaded_rows: Vec::new(),
681 bind: None,
682 text_binds: Vec::new(),
683 instances: Pool::new("flat instances", wgpu::BufferUsages::VERTEX),
684 quads: Pool::new("flat layer quads", wgpu::BufferUsages::VERTEX),
685 glyphs: Pool::new("flat glyphs", wgpu::BufferUsages::VERTEX),
686 text_uniforms: Pool::new("flat text uniforms", wgpu::BufferUsages::UNIFORM),
687 scratch: Vec::new(),
688 text_items: Vec::new(),
689 order: Order::default(),
690 ops: Vec::new(),
691 layer: None,
692 colour_target: None,
693 size: [0, 0],
694 capacity: None,
695 clip: None,
696 passes: 0,
697 made: BASE_PIPELINES,
698 }
699 }
700
701 pub fn target_textures(&self) -> Vec<wgpu::Texture> {
702 self.layer
703 .iter()
704 .map(|layer| layer.0.clone())
705 .chain(self.colour_target.iter().map(|target| target.0.clone()))
706 .collect()
707 }
708
709 pub fn set_clip(&mut self, clip: Option<[u32; 4]>) {
710 self.clip = clip;
711 }
712
713 pub fn set_capacity(&mut self, capacity: Option<[u32; 2]>) {
714 self.capacity = capacity;
715 }
716
717 fn viewport(&self) -> Option<[u32; 2]> {
718 self.capacity.map(|_| self.size)
719 }
720
721 pub fn pipelines_made(&self) -> usize {
722 self.made
723 }
724
725 pub fn warm(
726 &mut self,
727 device: &wgpu::Device,
728 size: [u32; 2],
729 finishes: &[(wgpu::TextureFormat, bool, bool)],
730 colour: bool,
731 ) {
732 self.ensure_lit(device);
733 self.ensure_layer(device, size);
734 if colour {
735 self.colour_target(device, size);
736 }
737 for &(format, linear, blend) in finishes {
738 self.finish_index(device, format, linear, blend);
739 }
740 }
741
742 pub fn instance_count(&self) -> usize {
743 self.instances.items.len()
744 }
745
746 pub fn instances(&self) -> &[Instance] {
747 &self.instances.items
748 }
749
750 pub fn layer_count(&self) -> usize {
751 self.quads.items.len()
752 }
753
754 fn push_instance(&mut self, instance: Instance) {
755 let start = self.instances.items.len() as u32;
756 let lit = instance.info[0] & (LIT | FX) != 0;
757 let [x0, y0, x1, y1] = instance.pixel_bounds();
758 let small = (x1 - x0) * (y1 - y0) <= ABSORBED_AREA;
759 self.instances.items.push(instance);
760 match self.ops.last_mut() {
761 Some(Op::Instances {
762 range,
763 lit: run_lit,
764 }) if range.end == start && (*run_lit == lit || (*run_lit && small)) => {
765 range.end = start + 1
766 }
767 _ => self.ops.push(Op::Instances {
768 range: start..start + 1,
769 lit,
770 }),
771 }
772 }
773
774 fn ensure_lit(&mut self, device: &wgpu::Device) {
775 if self.lit.is_some() {
776 return;
777 }
778 let attributes = instance_attributes();
779 let buffers = [Some(wgpu::VertexBufferLayout {
780 array_stride: std::mem::size_of::<Instance>() as u64,
781 step_mode: wgpu::VertexStepMode::Instance,
782 attributes: &attributes,
783 })];
784 let colour = pipeline(
785 device,
786 "flat colour lit",
787 &self.main_layout,
788 &self.module,
789 ("flat_vertex", "flat_colour_lit"),
790 &buffers,
791 colour_state(COLOUR_FORMAT, true),
792 wgpu::PrimitiveTopology::TriangleStrip,
793 );
794 let ids = pipeline(
795 device,
796 "flat ids lit",
797 &self.main_layout,
798 &self.module,
799 ("flat_id_vertex_rich", "flat_id_lit"),
800 &buffers,
801 wgpu::ColorTargetState {
802 format: ID_FORMAT,
803 blend: None,
804 write_mask: wgpu::ColorWrites::ALL,
805 },
806 wgpu::PrimitiveTopology::TriangleStrip,
807 );
808 self.lit = Some((colour, ids));
809 self.made += 2;
810 }
811
812 fn text_bind(&mut self, device: &wgpu::Device, atlas: &GpuAtlas) -> Result<usize, String> {
813 if let Some(index) = self
814 .text_binds
815 .iter()
816 .position(|(view, _)| view == atlas.view())
817 {
818 return Ok(index);
819 }
820 let buffer = self
821 .text_uniforms
822 .buffer
823 .as_ref()
824 .ok_or("flat text uniforms are missing")?;
825 let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
826 label: Some("flat text"),
827 layout: &self.text_layout,
828 entries: &[
829 wgpu::BindGroupEntry {
830 binding: 0,
831 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
832 buffer,
833 offset: 0,
834 size: wgpu::BufferSize::new(std::mem::size_of::<TextUniform>() as u64),
835 }),
836 },
837 wgpu::BindGroupEntry {
838 binding: 1,
839 resource: wgpu::BindingResource::TextureView(atlas.view()),
840 },
841 wgpu::BindGroupEntry {
842 binding: 2,
843 resource: wgpu::BindingResource::Sampler(&self.text_sampler),
844 },
845 ],
846 });
847 self.text_binds.push((atlas.view().clone(), group));
848 Ok(self.text_binds.len() - 1)
849 }
850
851 pub fn prepare(
852 &mut self,
853 device: &wgpu::Device,
854 queue: &wgpu::Queue,
855 scene: &FlatScene<'_>,
856 size: [u32; 2],
857 ) -> Result<Fit, String> {
858 let fit = Fit::new(scene.layout, size)?;
859 self.size = size;
860 self.instances.items.clear();
861 self.quads.items.clear();
862 self.glyphs.items.clear();
863 self.text_uniforms.items.clear();
864 self.ops.clear();
865 self.gradient_rows.clear();
866 self.order.build(scene.draws, scene.groups.len())?;
867 for group in scene.groups {
868 if !(0.0..=1.0).contains(&group.opacity) {
869 return Err("a group's opacity must be in 0..=1".into());
870 }
871 }
872 let icons = scene.icons.map(|icons| &icons.icons);
873 let clip = fit.clip_from_layout();
874 let mut open_quad: Option<(u32, [f32; 4])> = None;
875 let mut text_items = std::mem::take(&mut self.text_items);
876 text_items.clear();
877 if self.text_binds.len() >= TEXT_CACHE {
878 self.text_binds.clear();
879 }
880 let steps = std::mem::take(&mut self.order.steps);
881 for step in &steps {
882 match *step {
883 Step::Layer(group) => {
884 let quad = self.quads.items.len() as u32;
885 self.quads.items.push(LayerQuad {
886 rect: [f32::MAX, f32::MAX, f32::MIN, f32::MIN],
887 opacity: [scene.groups[usize::from(group)].opacity, 0.0, 0.0, 0.0],
888 });
889 self.ops.push(Op::Layer { quad });
890 open_quad = Some((quad, [f32::MAX, f32::MAX, f32::MIN, f32::MIN]));
891 }
892 Step::EndLayer(_) => {
893 if let Some((quad, rect)) = open_quad.take() {
894 let clamped = [
895 rect[0].floor().max(0.0),
896 rect[1].floor().max(0.0),
897 rect[2].ceil().min(size[0] as f32),
898 rect[3].ceil().min(size[1] as f32),
899 ];
900 self.quads.items[quad as usize].rect = clamped;
901 self.ops.push(Op::EndLayer { quad });
902 }
903 }
904 Step::Draw(index) => {
905 let draw = &scene.draws[index];
906 let bounds =
907 self.push_draw(draw, scene, &fit, icons, &clip, &mut text_items)?;
908 if let (Some((_, rect)), Some(bounds)) = (open_quad.as_mut(), bounds) {
909 *rect = rect_union(*rect, bounds);
910 }
911 }
912 }
913 }
914 self.order.steps = steps;
915 self.instances.upload(device, queue);
916 self.quads.upload(device, queue);
917 self.glyphs.upload(device, queue);
918 if self.text_uniforms.upload(device, queue) {
919 self.text_binds.clear();
920 }
921 for (op_index, text_index, glyphs, slot) in text_items.drain(..) {
922 let atlas = self.text_bind(device, scene.text[text_index].atlas)?;
923 if let Op::Text {
924 atlas: bound,
925 glyphs: range,
926 slot: at,
927 ..
928 } = &mut self.ops[op_index]
929 {
930 *bound = atlas;
931 *range = glyphs;
932 *at = slot;
933 }
934 }
935 self.text_items = text_items;
936 let light = scene.light;
937 let key = super::normalize3(light.key);
938 let (mips, intensity, present) = scene.environment.map_or((1.0, 0.0, 0.0), |environment| {
939 (environment.mips as f32, environment.intensity, 1.0)
940 });
941 queue.write_buffer(
942 &self.uniform,
943 0,
944 bytemuck::bytes_of(&View {
945 size: [
946 size[0] as f32,
947 size[1] as f32,
948 1.0 / size[0] as f32,
949 1.0 / size[1] as f32,
950 ],
951 light: [key[0], key[1], key[2], light.ambient.clamp(0.0, 1.0)],
952 environment: [mips, intensity, present, 0.0],
953 }),
954 );
955 self.upload_gradients(device, queue);
956 let icon_view = scene.icons.map_or(&self.empty_icons, |icons| icons.view());
957 let environment_view = scene
958 .environment
959 .map_or(&self.empty_environment, |environment| &environment.view);
960 let gradient_view = self
961 .gradients
962 .as_ref()
963 .filter(|_| !self.gradient_rows.is_empty())
964 .map_or(&self.empty_gradients, |gradients| &gradients.1);
965 let (sprite_view, sprite_sampler) = scene
966 .sprites
967 .map_or((&self.empty_sprites, &self.sprite_sampler), |sprites| {
968 (&sprites.view, &sprites.sampler)
969 });
970 let stale = self
971 .bind
972 .as_ref()
973 .is_none_or(|(icons, environment, gradients, sprites, _)| {
974 icons != icon_view
975 || environment != environment_view
976 || gradients != gradient_view
977 || sprites != sprite_view
978 });
979 if stale {
980 let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
981 label: Some("flat view"),
982 layout: &self.view_layout,
983 entries: &[
984 wgpu::BindGroupEntry {
985 binding: 0,
986 resource: self.uniform.as_entire_binding(),
987 },
988 wgpu::BindGroupEntry {
989 binding: 1,
990 resource: wgpu::BindingResource::TextureView(icon_view),
991 },
992 wgpu::BindGroupEntry {
993 binding: 2,
994 resource: wgpu::BindingResource::Sampler(&self.icon_sampler),
995 },
996 wgpu::BindGroupEntry {
997 binding: 3,
998 resource: wgpu::BindingResource::TextureView(environment_view),
999 },
1000 wgpu::BindGroupEntry {
1001 binding: 4,
1002 resource: wgpu::BindingResource::Sampler(&self.environment_sampler),
1003 },
1004 wgpu::BindGroupEntry {
1005 binding: 5,
1006 resource: wgpu::BindingResource::TextureView(gradient_view),
1007 },
1008 wgpu::BindGroupEntry {
1009 binding: 6,
1010 resource: wgpu::BindingResource::TextureView(sprite_view),
1011 },
1012 wgpu::BindGroupEntry {
1013 binding: 7,
1014 resource: wgpu::BindingResource::Sampler(sprite_sampler),
1015 },
1016 ],
1017 });
1018 self.bind = Some((
1019 icon_view.clone(),
1020 environment_view.clone(),
1021 gradient_view.clone(),
1022 sprite_view.clone(),
1023 group,
1024 ));
1025 }
1026 if !self.quads.items.is_empty() {
1027 self.ensure_layer(device, self.capacity.unwrap_or(size));
1028 }
1029 if self
1030 .ops
1031 .iter()
1032 .any(|op| matches!(op, Op::Instances { lit: true, .. }))
1033 {
1034 self.ensure_lit(device);
1035 }
1036 self.passes = 1 + 2 * self.quads.items.len() as u32;
1037 Ok(fit)
1038 }
1039
1040 fn push_draw(
1041 &mut self,
1042 draw: &Draw,
1043 scene: &FlatScene<'_>,
1044 fit: &Fit,
1045 icons: Option<&Icons>,
1046 clip: &super::Matrix,
1047 text_items: &mut Vec<(usize, usize, Range<u32>, u32)>,
1048 ) -> Result<Option<[f32; 4]>, String> {
1049 if let Shape::Text(index) = draw.shape {
1050 let text = scene
1051 .text
1052 .get(index)
1053 .ok_or_else(|| format!("a draw names text {index}, which is missing"))?;
1054 if !super::positive(draw.opacity) || super::placed(&draw.transform, fit).is_none() {
1055 return Ok(None);
1056 }
1057 self.scratch.clear();
1058 let mode = match text.glyphs {
1059 Glyphs::Paragraph(paragraph) => {
1060 self.scratch.extend(pack_glyphs(¶graph.glyphs));
1061 u32::from(paragraph.atlas.channels == 3)
1062 }
1063 Glyphs::Quads(quads) => {
1064 self.scratch.extend(pack_quads(quads));
1065 match text.atlas.format() {
1066 wgpu::TextureFormat::Rgba16Float => 2,
1067 wgpu::TextureFormat::Rgba8Unorm => 1,
1068 _ => 0,
1069 }
1070 }
1071 };
1072 if self.scratch.is_empty() {
1073 return Ok(None);
1074 }
1075 let tint = text.colour.0;
1076 for glyph in &mut self.scratch {
1077 for (channel, value) in glyph.color.iter_mut().zip(tint).take(3) {
1078 *channel *= value;
1079 }
1080 glyph.color[3] *= tint[3] * draw.opacity.min(1.0);
1081 }
1082 let matrix = super::multiply(*clip, draw.transform);
1083 let bounds = screen_bounds(
1084 &TextSpace::Surface {
1085 model_view_projection: matrix,
1086 },
1087 self.scratch.iter().copied(),
1088 self.size,
1089 );
1090 let start = self.glyphs.items.len() as u32;
1091 self.glyphs.items.extend_from_slice(&self.scratch);
1092 let end = self.glyphs.items.len() as u32;
1093 let slot = self.text_uniforms.items.len() as u32;
1094 let size = text.atlas.size();
1095 let uniform = TextUniform {
1096 matrix,
1097 view_projection: super::IDENTITY,
1098 model: super::IDENTITY,
1099 rest_normal: [0.0, 0.0, 1.0, 0.0],
1100 atlas_size: [size[0] as f32, size[1] as f32],
1101 mode,
1102 deformer: u32::MAX,
1103 cells: [crate::text::DEFORMED_CELLS, draw.id, 0, 0],
1104 };
1105 let mut bytes = [0u8; TEXT_SLOT as usize];
1106 bytes[..std::mem::size_of::<TextUniform>()]
1107 .copy_from_slice(bytemuck::bytes_of(&uniform));
1108 self.text_uniforms.items.push(bytes);
1109 text_items.push((self.ops.len(), index, start..end, slot));
1110 self.ops.push(Op::Text {
1111 glyphs: start..end,
1112 slot,
1113 atlas: 0,
1114 id: draw.id,
1115 });
1116 return Ok(bounds);
1117 }
1118 let mut bounds = None;
1119 if let Some(shadow) = pack_shadow(draw, fit, scene.curve, &scene.light) {
1120 bounds = Some(shadow.pixel_bounds());
1121 self.push_instance(shadow);
1122 }
1123 if let Some(mut shape) = pack_shape(draw, fit, icons) {
1124 if let Fill::Gradient(gradient) = &draw.fill {
1125 let packed = gradient.packed();
1126 let row = match self.gradient_rows.iter().position(|row| *row == packed) {
1127 Some(row) => row,
1128 None => {
1129 self.gradient_rows.push(packed);
1130 self.gradient_rows.len() - 1
1131 }
1132 };
1133 shape.fill_bottom[0] = row as f32;
1134 }
1135 let rect = shape.pixel_bounds();
1136 bounds = Some(bounds.map_or(rect, |bounds| rect_union(bounds, rect)));
1137 self.push_instance(shape);
1138 }
1139 Ok(bounds)
1140 }
1141
1142 fn upload_gradients(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
1143 let rows = self.gradient_rows.len() as u32;
1144 if rows == 0 {
1145 return;
1146 }
1147 if self
1148 .gradients
1149 .as_ref()
1150 .is_none_or(|gradients| gradients.2 < rows)
1151 {
1152 let capacity = rows.next_power_of_two().max(8);
1153 let texture = device.create_texture(&wgpu::TextureDescriptor {
1154 label: Some("flat gradients"),
1155 size: wgpu::Extent3d {
1156 width: GRADIENT_TEXELS,
1157 height: capacity,
1158 depth_or_array_layers: 1,
1159 },
1160 mip_level_count: 1,
1161 sample_count: 1,
1162 dimension: wgpu::TextureDimension::D2,
1163 format: GRADIENT_FORMAT,
1164 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1165 view_formats: &[],
1166 });
1167 let view = texture.create_view(&Default::default());
1168 self.gradients = Some((texture, view, capacity));
1169 self.uploaded_rows.clear();
1170 }
1171 if self.uploaded_rows == self.gradient_rows {
1172 return;
1173 }
1174 let texture = &self.gradients.as_ref().unwrap().0;
1175 queue.write_texture(
1176 texture.as_image_copy(),
1177 bytemuck::cast_slice(&self.gradient_rows),
1178 wgpu::TexelCopyBufferLayout {
1179 offset: 0,
1180 bytes_per_row: Some(GRADIENT_TEXELS * 16),
1181 rows_per_image: Some(rows),
1182 },
1183 wgpu::Extent3d {
1184 width: GRADIENT_TEXELS,
1185 height: rows,
1186 depth_or_array_layers: 1,
1187 },
1188 );
1189 self.uploaded_rows.clone_from(&self.gradient_rows);
1190 }
1191
1192 fn ensure_layer(&mut self, device: &wgpu::Device, size: [u32; 2]) {
1193 if self.layer.as_ref().is_some_and(|layer| layer.3 == size) {
1194 return;
1195 }
1196 let texture = device.create_texture(&wgpu::TextureDescriptor {
1197 label: Some("flat group layer"),
1198 size: wgpu::Extent3d {
1199 width: size[0],
1200 height: size[1],
1201 depth_or_array_layers: 1,
1202 },
1203 mip_level_count: 1,
1204 sample_count: 1,
1205 dimension: wgpu::TextureDimension::D2,
1206 format: COLOUR_FORMAT,
1207 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1208 view_formats: &[],
1209 });
1210 let view = texture.create_view(&Default::default());
1211 let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1212 label: Some("flat group layer"),
1213 layout: &self.layer_layout,
1214 entries: &[wgpu::BindGroupEntry {
1215 binding: 0,
1216 resource: wgpu::BindingResource::TextureView(&view),
1217 }],
1218 });
1219 self.layer = Some((texture, view, group, size));
1220 }
1221
1222 pub fn colour_target(&mut self, device: &wgpu::Device, size: [u32; 2]) -> wgpu::TextureView {
1223 if self
1224 .colour_target
1225 .as_ref()
1226 .is_none_or(|target| target.2 != size)
1227 {
1228 let texture = device.create_texture(&wgpu::TextureDescriptor {
1229 label: Some("flat colour"),
1230 size: wgpu::Extent3d {
1231 width: size[0],
1232 height: size[1],
1233 depth_or_array_layers: 1,
1234 },
1235 mip_level_count: 1,
1236 sample_count: 1,
1237 dimension: wgpu::TextureDimension::D2,
1238 format: COLOUR_FORMAT,
1239 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
1240 | wgpu::TextureUsages::TEXTURE_BINDING
1241 | wgpu::TextureUsages::COPY_SRC,
1242 view_formats: &[],
1243 });
1244 let view = texture.create_view(&Default::default());
1245 self.colour_target = Some((texture, view, size));
1246 }
1247 self.colour_target.as_ref().unwrap().1.clone()
1248 }
1249
1250 fn draw_op(&self, pass: &mut wgpu::RenderPass<'_>, op: &Op, ids: bool) {
1251 match op {
1252 Op::Instances { range, lit } => {
1253 let Some(buffer) = &self.instances.buffer else {
1254 return;
1255 };
1256 let pipeline = match (lit, &self.lit, ids) {
1257 (false, _, false) => &self.colour,
1258 (false, _, true) => &self.ids,
1259 (true, Some((colour, _)), false) => colour,
1260 (true, Some((_, lit_ids)), true) => lit_ids,
1261 (true, None, _) => return,
1262 };
1263 pass.set_pipeline(pipeline);
1264 pass.set_bind_group(0, &self.bind.as_ref().unwrap().4, &[]);
1265 pass.set_vertex_buffer(0, buffer.slice(..));
1266 pass.draw(0..4, range.clone());
1267 }
1268 Op::Text {
1269 glyphs,
1270 slot,
1271 atlas,
1272 id,
1273 } => {
1274 if ids && *id == 0 {
1275 return;
1276 }
1277 let (Some(buffer), Some((_, group))) =
1278 (&self.glyphs.buffer, self.text_binds.get(*atlas))
1279 else {
1280 return;
1281 };
1282 pass.set_pipeline(if ids { &self.text_ids } else { &self.text });
1283 pass.set_bind_group(0, group, &[(u64::from(*slot) * TEXT_SLOT) as u32]);
1284 pass.set_vertex_buffer(0, buffer.slice(..));
1285 pass.draw(0..6, glyphs.clone());
1286 }
1287 Op::Layer { .. } | Op::EndLayer { .. } => {}
1288 }
1289 }
1290
1291 fn quad(&self, pass: &mut wgpu::RenderPass<'_>, quad: u32, composite: bool) {
1292 let Some(buffer) = &self.quads.buffer else {
1293 return;
1294 };
1295 if composite {
1296 pass.set_pipeline(&self.composite);
1297 pass.set_bind_group(1, &self.layer.as_ref().unwrap().2, &[]);
1298 } else {
1299 pass.set_pipeline(&self.clear);
1300 }
1301 pass.set_bind_group(0, &self.bind.as_ref().unwrap().4, &[]);
1302 pass.set_vertex_buffer(0, buffer.slice(..));
1303 pass.draw(0..4, quad..quad + 1);
1304 }
1305
1306 pub fn encode_colour(
1307 &self,
1308 encoder: &mut wgpu::CommandEncoder,
1309 target: &wgpu::TextureView,
1310 clear: Option<[f32; 4]>,
1311 mut profiler: Option<&mut GpuProfiler>,
1312 ) {
1313 let timing = profiler
1314 .as_deref_mut()
1315 .and_then(|profiler| profiler.pass("flat"));
1316 let mut index = 0;
1317 let last = self.passes.saturating_sub(1);
1318 let writes = |index: u32| {
1319 profiler.as_deref().and_then(|profiler| {
1320 profiler
1321 .render_writes(timing)
1322 .map(|writes| wgpu::RenderPassTimestampWrites {
1323 beginning_of_pass_write_index: writes
1324 .beginning_of_pass_write_index
1325 .filter(|_| index == 0),
1326 end_of_pass_write_index: writes
1327 .end_of_pass_write_index
1328 .filter(|_| index == last),
1329 ..writes
1330 })
1331 .filter(|writes| {
1332 writes.beginning_of_pass_write_index.is_some()
1333 || writes.end_of_pass_write_index.is_some()
1334 })
1335 })
1336 };
1337 let viewport = self.viewport();
1338 let begin = |encoder: &mut wgpu::CommandEncoder,
1339 view: &wgpu::TextureView,
1340 load: wgpu::LoadOp<wgpu::Color>,
1341 index: u32| {
1342 let mut pass = encoder
1343 .begin_render_pass(&wgpu::RenderPassDescriptor {
1344 multiview_mask: None,
1345 label: Some("flat"),
1346 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1347 view,
1348 resolve_target: None,
1349 depth_slice: None,
1350 ops: wgpu::Operations {
1351 load,
1352 store: wgpu::StoreOp::Store,
1353 },
1354 })],
1355 depth_stencil_attachment: None,
1356 timestamp_writes: writes(index),
1357 occlusion_query_set: None,
1358 })
1359 .forget_lifetime();
1360 if let Some(viewport) = viewport {
1361 crate::viewport::apply(&mut pass, viewport);
1362 }
1363 pass
1364 };
1365 let load = clear.map_or(wgpu::LoadOp::Load, |c| {
1366 wgpu::LoadOp::Clear(wgpu::Color {
1367 r: f64::from(c[0]),
1368 g: f64::from(c[1]),
1369 b: f64::from(c[2]),
1370 a: f64::from(c[3]),
1371 })
1372 });
1373 let mut pass = begin(encoder, target, load, index);
1374 let layer_view = self.layer.as_ref().map(|layer| &layer.1);
1375 for op in &self.ops {
1376 match op {
1377 Op::Layer { quad } => {
1378 drop(pass);
1379 index += 1;
1380 pass = begin(encoder, layer_view.unwrap(), wgpu::LoadOp::Load, index);
1381 self.quad(&mut pass, *quad, false);
1382 }
1383 Op::EndLayer { quad } => {
1384 drop(pass);
1385 index += 1;
1386 pass = begin(encoder, target, wgpu::LoadOp::Load, index);
1387 self.quad(&mut pass, *quad, true);
1388 }
1389 op => self.draw_op(&mut pass, op, false),
1390 }
1391 }
1392 }
1393
1394 pub fn encode_ids(
1395 &self,
1396 encoder: &mut wgpu::CommandEncoder,
1397 ids: &wgpu::TextureView,
1398 clear: bool,
1399 mut profiler: Option<&mut GpuProfiler>,
1400 ) {
1401 let timing = profiler
1402 .as_deref_mut()
1403 .and_then(|profiler| profiler.pass("flat ids"));
1404 let writes = profiler
1405 .as_deref()
1406 .and_then(|profiler| profiler.render_writes(timing));
1407 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1408 multiview_mask: None,
1409 label: Some("flat ids"),
1410 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1411 view: ids,
1412 resolve_target: None,
1413 depth_slice: None,
1414 ops: wgpu::Operations {
1415 load: if clear {
1416 wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT)
1417 } else {
1418 wgpu::LoadOp::Load
1419 },
1420 store: wgpu::StoreOp::Store,
1421 },
1422 })],
1423 depth_stencil_attachment: None,
1424 timestamp_writes: writes,
1425 occlusion_query_set: None,
1426 });
1427 if let Some(viewport) = self.viewport() {
1428 crate::viewport::apply(&mut pass, viewport);
1429 }
1430 for op in &self.ops {
1431 self.draw_op(&mut pass, op, true);
1432 }
1433 }
1434
1435 pub fn has_ids(&self) -> bool {
1436 self.instances
1437 .items
1438 .iter()
1439 .any(|instance| instance.kind() != KIND_SHADOW)
1440 || self
1441 .ops
1442 .iter()
1443 .any(|op| matches!(op, Op::Text { id, .. } if *id != 0))
1444 }
1445
1446 fn finish_index(
1447 &mut self,
1448 device: &wgpu::Device,
1449 format: wgpu::TextureFormat,
1450 linear: bool,
1451 blend: bool,
1452 ) -> usize {
1453 match self.finishes.iter().position(|finish| {
1454 finish.format == format && finish.linear == linear && finish.blend == blend
1455 }) {
1456 Some(index) => index,
1457 None => {
1458 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1459 label: Some("flat finish"),
1460 layout: Some(&self.finish_pipeline_layout),
1461 vertex: wgpu::VertexState {
1462 module: &self.finish_module,
1463 entry_point: Some("flat_finish_vertex"),
1464 buffers: &[],
1465 compilation_options: Default::default(),
1466 },
1467 primitive: Default::default(),
1468 depth_stencil: None,
1469 multisample: Default::default(),
1470 fragment: Some(wgpu::FragmentState {
1471 module: &self.finish_module,
1472 entry_point: Some(if linear {
1473 "flat_finish_linear"
1474 } else {
1475 "flat_finish_copy"
1476 }),
1477 targets: &[Some(colour_state(format, blend))],
1478 compilation_options: Default::default(),
1479 }),
1480 multiview_mask: None,
1481 cache: None,
1482 });
1483 self.finishes.push(Finish {
1484 format,
1485 linear,
1486 blend,
1487 pipeline,
1488 });
1489 self.made += 1;
1490 self.finishes.len() - 1
1491 }
1492 }
1493 }
1494
1495 #[allow(clippy::too_many_arguments)]
1496 pub fn encode_finish(
1497 &mut self,
1498 device: &wgpu::Device,
1499 encoder: &mut wgpu::CommandEncoder,
1500 source: &wgpu::TextureView,
1501 target: &wgpu::TextureView,
1502 format: wgpu::TextureFormat,
1503 linear: bool,
1504 blend: bool,
1505 mut profiler: Option<&mut GpuProfiler>,
1506 ) {
1507 let index = self.finish_index(device, format, linear, blend);
1508 let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1509 label: Some("flat finish"),
1510 layout: &self.finish_layout,
1511 entries: &[wgpu::BindGroupEntry {
1512 binding: 0,
1513 resource: wgpu::BindingResource::TextureView(source),
1514 }],
1515 });
1516 let timing = profiler
1517 .as_deref_mut()
1518 .and_then(|profiler| profiler.pass("flat finish"));
1519 let writes = profiler
1520 .as_deref()
1521 .and_then(|profiler| profiler.render_writes(timing));
1522 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1523 multiview_mask: None,
1524 label: Some("flat finish"),
1525 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1526 view: target,
1527 resolve_target: None,
1528 depth_slice: None,
1529 ops: wgpu::Operations {
1530 load: if blend {
1531 wgpu::LoadOp::Load
1532 } else {
1533 wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT)
1534 },
1535 store: wgpu::StoreOp::Store,
1536 },
1537 })],
1538 depth_stencil_attachment: None,
1539 timestamp_writes: writes,
1540 occlusion_query_set: None,
1541 });
1542 if let Some(viewport) = self.viewport() {
1543 crate::viewport::apply(&mut pass, viewport);
1544 }
1545 if let Some([x, y, width, height]) = self.clip {
1546 pass.set_scissor_rect(x, y, width, height);
1547 }
1548 pass.set_pipeline(&self.finishes[index].pipeline);
1549 pass.set_bind_group(0, &group, &[]);
1550 pass.draw(0..3, 0..1);
1551 }
1552
1553 pub fn render(
1554 &mut self,
1555 device: &wgpu::Device,
1556 queue: &wgpu::Queue,
1557 encoder: &mut wgpu::CommandEncoder,
1558 scene: &FlatScene<'_>,
1559 target: FlatTarget<'_>,
1560 mut profiler: Option<&mut GpuProfiler>,
1561 ) -> Result<Fit, String> {
1562 let fit = self.prepare(device, queue, scene, target.size)?;
1563 self.encode_colour(
1564 encoder,
1565 target.colour,
1566 target.clear,
1567 profiler.as_deref_mut(),
1568 );
1569 if let Some(ids) = target.ids {
1570 self.encode_ids(encoder, ids, target.clear_ids, profiler);
1571 }
1572 Ok(fit)
1573 }
1574}