Skip to main content

cryoglyph/
cache.rs

1use crate::{GlyphToRender, Params};
2use wgpu::{
3    BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutEntry,
4    BindingResource, BindingType, BlendState, Buffer, BufferBindingType, ColorTargetState,
5    ColorWrites, DepthStencilState, Device, FilterMode, FragmentState, MultisampleState,
6    PipelineCompilationOptions, PipelineLayout, PipelineLayoutDescriptor, PrimitiveState,
7    PrimitiveTopology, RenderPipeline, RenderPipelineDescriptor, Sampler, SamplerBindingType,
8    SamplerDescriptor, ShaderModule, ShaderModuleDescriptor, ShaderSource, ShaderStages,
9    TextureFormat, TextureSampleType, TextureView, TextureViewDimension, VertexFormat, VertexState,
10};
11
12use std::borrow::Cow;
13use std::mem;
14use std::num::NonZeroU64;
15use std::ops::Deref;
16use std::sync::{Arc, Mutex};
17
18#[derive(Debug, Clone)]
19pub struct Cache(Arc<Inner>);
20
21#[derive(Debug)]
22struct Inner {
23    sampler: Sampler,
24    shader: ShaderModule,
25    vertex_buffers: [wgpu::VertexBufferLayout<'static>; 1],
26    atlas_layout: BindGroupLayout,
27    uniforms_layout: BindGroupLayout,
28    pipeline_layout: PipelineLayout,
29    cache: Mutex<
30        Vec<(
31            TextureFormat,
32            MultisampleState,
33            Option<DepthStencilState>,
34            RenderPipeline,
35        )>,
36    >,
37}
38
39impl Cache {
40    pub fn new(device: &Device) -> Self {
41        let sampler = device.create_sampler(&SamplerDescriptor {
42            label: Some("glyphon sampler"),
43            min_filter: FilterMode::Nearest,
44            mag_filter: FilterMode::Nearest,
45            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
46            lod_min_clamp: 0f32,
47            lod_max_clamp: 0f32,
48            ..Default::default()
49        });
50
51        let shader = device.create_shader_module(ShaderModuleDescriptor {
52            label: Some("glyphon shader"),
53            source: ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
54        });
55
56        let vertex_buffer_layout = wgpu::VertexBufferLayout {
57            array_stride: mem::size_of::<GlyphToRender>() as wgpu::BufferAddress,
58            step_mode: wgpu::VertexStepMode::Instance,
59            attributes: &[
60                wgpu::VertexAttribute {
61                    format: VertexFormat::Sint32x2,
62                    offset: 0,
63                    shader_location: 0,
64                },
65                wgpu::VertexAttribute {
66                    format: VertexFormat::Uint32,
67                    offset: mem::size_of::<u32>() as u64 * 2,
68                    shader_location: 1,
69                },
70                wgpu::VertexAttribute {
71                    format: VertexFormat::Uint32,
72                    offset: mem::size_of::<u32>() as u64 * 3,
73                    shader_location: 2,
74                },
75                wgpu::VertexAttribute {
76                    format: VertexFormat::Uint32,
77                    offset: mem::size_of::<u32>() as u64 * 4,
78                    shader_location: 3,
79                },
80                wgpu::VertexAttribute {
81                    format: VertexFormat::Uint32,
82                    offset: mem::size_of::<u32>() as u64 * 5,
83                    shader_location: 4,
84                },
85                wgpu::VertexAttribute {
86                    format: VertexFormat::Float32,
87                    offset: mem::size_of::<u32>() as u64 * 6,
88                    shader_location: 5,
89                },
90            ],
91        };
92
93        let atlas_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
94            entries: &[
95                BindGroupLayoutEntry {
96                    binding: 0,
97                    visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
98                    ty: BindingType::Texture {
99                        multisampled: false,
100                        view_dimension: TextureViewDimension::D2,
101                        sample_type: TextureSampleType::Float { filterable: true },
102                    },
103                    count: None,
104                },
105                BindGroupLayoutEntry {
106                    binding: 1,
107                    visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
108                    ty: BindingType::Texture {
109                        multisampled: false,
110                        view_dimension: TextureViewDimension::D2,
111                        sample_type: TextureSampleType::Float { filterable: true },
112                    },
113                    count: None,
114                },
115                BindGroupLayoutEntry {
116                    binding: 2,
117                    visibility: ShaderStages::FRAGMENT,
118                    ty: BindingType::Sampler(SamplerBindingType::Filtering),
119                    count: None,
120                },
121            ],
122            label: Some("glyphon atlas bind group layout"),
123        });
124
125        let uniforms_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
126            entries: &[BindGroupLayoutEntry {
127                binding: 0,
128                visibility: ShaderStages::VERTEX,
129                ty: BindingType::Buffer {
130                    ty: BufferBindingType::Uniform,
131                    has_dynamic_offset: false,
132                    min_binding_size: NonZeroU64::new(mem::size_of::<Params>() as u64),
133                },
134                count: None,
135            }],
136            label: Some("glyphon uniforms bind group layout"),
137        });
138
139        let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
140            label: None,
141            bind_group_layouts: &[&atlas_layout, &uniforms_layout],
142            immediate_size: 0,
143        });
144
145        Self(Arc::new(Inner {
146            sampler,
147            shader,
148            vertex_buffers: [vertex_buffer_layout],
149            uniforms_layout,
150            atlas_layout,
151            pipeline_layout,
152            cache: Mutex::new(Vec::new()),
153        }))
154    }
155
156    pub(crate) fn create_atlas_bind_group(
157        &self,
158        device: &Device,
159        color_atlas: &TextureView,
160        mask_atlas: &TextureView,
161    ) -> BindGroup {
162        device.create_bind_group(&BindGroupDescriptor {
163            layout: &self.0.atlas_layout,
164            entries: &[
165                BindGroupEntry {
166                    binding: 0,
167                    resource: BindingResource::TextureView(color_atlas),
168                },
169                BindGroupEntry {
170                    binding: 1,
171                    resource: BindingResource::TextureView(mask_atlas),
172                },
173                BindGroupEntry {
174                    binding: 2,
175                    resource: BindingResource::Sampler(&self.0.sampler),
176                },
177            ],
178            label: Some("glyphon atlas bind group"),
179        })
180    }
181
182    pub(crate) fn create_uniforms_bind_group(&self, device: &Device, buffer: &Buffer) -> BindGroup {
183        device.create_bind_group(&BindGroupDescriptor {
184            layout: &self.0.uniforms_layout,
185            entries: &[BindGroupEntry {
186                binding: 0,
187                resource: buffer.as_entire_binding(),
188            }],
189            label: Some("glyphon uniforms bind group"),
190        })
191    }
192
193    pub(crate) fn get_or_create_pipeline(
194        &self,
195        device: &Device,
196        format: TextureFormat,
197        multisample: MultisampleState,
198        depth_stencil: Option<DepthStencilState>,
199    ) -> RenderPipeline {
200        let Inner {
201            cache,
202            pipeline_layout,
203            shader,
204            vertex_buffers,
205            ..
206        } = self.0.deref();
207
208        let mut cache = cache.lock().expect("Write pipeline cache");
209
210        cache
211            .iter()
212            .find(|(fmt, ms, ds, _)| fmt == &format && ms == &multisample && ds == &depth_stencil)
213            .map(|(_, _, _, p)| p.clone())
214            .unwrap_or_else(|| {
215                let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
216                    label: Some("glyphon pipeline"),
217                    layout: Some(pipeline_layout),
218                    vertex: VertexState {
219                        module: shader,
220                        entry_point: Some("vs_main"),
221                        buffers: vertex_buffers,
222                        compilation_options: PipelineCompilationOptions::default(),
223                    },
224                    fragment: Some(FragmentState {
225                        module: shader,
226                        entry_point: Some("fs_main"),
227                        targets: &[Some(ColorTargetState {
228                            format,
229                            blend: Some(BlendState::ALPHA_BLENDING),
230                            write_mask: ColorWrites::default(),
231                        })],
232                        compilation_options: PipelineCompilationOptions::default(),
233                    }),
234                    primitive: PrimitiveState {
235                        topology: PrimitiveTopology::TriangleStrip,
236                        ..PrimitiveState::default()
237                    },
238                    depth_stencil: depth_stencil.clone(),
239                    multisample,
240                    multiview_mask: None,
241                    cache: None,
242                });
243
244                cache.push((format, multisample, depth_stencil, pipeline.clone()));
245
246                pipeline
247            })
248            .clone()
249    }
250}