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}