Skip to main content

zenthra_render/
text_pipeline.rs

1use bytemuck::{Pod, Zeroable};
2use wgpu::util::DeviceExt;
3
4use zenthra_core::GlyphInstance;
5
6#[repr(C)]
7#[derive(Copy, Clone, Pod, Zeroable)]
8struct Uniforms {
9    screen_size: [f32; 2],
10    _padding: [f32; 2],
11}
12
13pub struct TextPipeline {
14    pipeline: wgpu::RenderPipeline,
15    uniform_buffer: wgpu::Buffer,
16    uniform_bg: wgpu::BindGroup,
17    atlas_bg: Option<wgpu::BindGroup>,
18    atlas_bgl: wgpu::BindGroupLayout,
19    instance_buffer: Option<wgpu::Buffer>,
20    instance_count: u32,
21}
22
23impl TextPipeline {
24    pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
25        let shader = device.create_shader_module(wgpu::include_wgsl!("shaders/text.wgsl"));
26
27        let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
28            label: Some("Text Uniform Buffer"),
29            size: std::mem::size_of::<Uniforms>() as u64,
30            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
31            mapped_at_creation: false,
32        });
33
34        let uniform_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
35            label: Some("Text Uniform BGL"),
36            entries: &[wgpu::BindGroupLayoutEntry {
37                binding: 0,
38                visibility: wgpu::ShaderStages::VERTEX,
39                ty: wgpu::BindingType::Buffer {
40                    ty: wgpu::BufferBindingType::Uniform,
41                    has_dynamic_offset: false,
42                    min_binding_size: None,
43                },
44                count: None,
45            }],
46        });
47
48        let uniform_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
49            label: Some("Text Uniform BG"),
50            layout: &uniform_bgl,
51            entries: &[wgpu::BindGroupEntry {
52                binding: 0,
53                resource: uniform_buffer.as_entire_binding(),
54            }],
55        });
56
57        let atlas_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
58            label: Some("Text Atlas BGL"),
59            entries: &[
60                wgpu::BindGroupLayoutEntry {
61                    binding: 0,
62                    visibility: wgpu::ShaderStages::FRAGMENT,
63                    ty: wgpu::BindingType::Texture {
64                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
65                        view_dimension: wgpu::TextureViewDimension::D2,
66                        multisampled: false,
67                    },
68                    count: None,
69                },
70                wgpu::BindGroupLayoutEntry {
71                    binding: 1,
72                    visibility: wgpu::ShaderStages::FRAGMENT,
73                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
74                    count: None,
75                },
76            ],
77        });
78
79        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
80            label: Some("Text Pipeline Layout"),
81            bind_group_layouts: &[Some(&uniform_bgl), Some(&atlas_bgl)],
82            immediate_size: 0,
83        });
84
85        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
86            label: Some("Text Pipeline"),
87            layout: Some(&layout),
88            vertex: wgpu::VertexState {
89                module: &shader,
90                entry_point: Some("vs_main"),
91                buffers: &[GlyphInstance::desc()],
92                compilation_options: Default::default(),
93            },
94            fragment: Some(wgpu::FragmentState {
95                module: &shader,
96                entry_point: Some("fs_main"),
97                targets: &[Some(wgpu::ColorTargetState {
98                    format,
99                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
100                    write_mask: wgpu::ColorWrites::ALL,
101                })],
102                compilation_options: Default::default(),
103            }),
104            primitive: wgpu::PrimitiveState::default(),
105            depth_stencil: None,
106            multisample: wgpu::MultisampleState::default(),
107            multiview_mask: None,
108            cache: None,
109        });
110
111        Self {
112            pipeline,
113            uniform_buffer,
114            uniform_bg,
115            atlas_bg: None,
116            atlas_bgl,
117            instance_buffer: None,
118            instance_count: 0,
119        }
120    }
121
122    pub fn set_atlas(&mut self, device: &wgpu::Device, view: &wgpu::TextureView) {
123        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
124            label: Some("Text Atlas Sampler"),
125            mag_filter: wgpu::FilterMode::Linear,
126            min_filter: wgpu::FilterMode::Linear,
127            ..Default::default()
128        });
129        self.atlas_bg = Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
130            label: Some("Text Atlas BG"),
131            layout: &self.atlas_bgl,
132            entries: &[
133                wgpu::BindGroupEntry {
134                    binding: 0,
135                    resource: wgpu::BindingResource::TextureView(view),
136                },
137                wgpu::BindGroupEntry {
138                    binding: 1,
139                    resource: wgpu::BindingResource::Sampler(&sampler),
140                },
141            ],
142        }));
143    }
144
145    pub fn prepare(
146        &mut self,
147        device: &wgpu::Device,
148        queue: &wgpu::Queue,
149        width: u32,
150        height: u32,
151        instances: &[GlyphInstance],
152    ) {
153        queue.write_buffer(
154            &self.uniform_buffer,
155            0,
156            bytemuck::bytes_of(&Uniforms {
157                screen_size: [width as f32, height as f32],
158                _padding: [0.0; 2],
159            }),
160        );
161
162        if instances.is_empty() {
163            self.instance_count = 0;
164            return;
165        }
166
167        self.instance_buffer = Some(
168            device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
169                label: Some("Glyph Instance Buffer"),
170                contents: bytemuck::cast_slice(instances),
171                usage: wgpu::BufferUsages::VERTEX,
172            }),
173        );
174        self.instance_count = instances.len() as u32;
175    }
176
177    pub fn draw<'a>(&'a self, pass: &mut wgpu::RenderPass<'a>) {
178        let Some(atlas_bg) = &self.atlas_bg else { return };
179        if self.instance_count == 0 {
180            return;
181        }
182        let Some(buf) = &self.instance_buffer else { return };
183        pass.set_pipeline(&self.pipeline);
184        pass.set_bind_group(0, &self.uniform_bg, &[]);
185        pass.set_bind_group(1, atlas_bg, &[]);
186        pass.set_vertex_buffer(0, buf.slice(..));
187        pass.draw(0..6, 0..self.instance_count);
188    }
189}