1pub(crate) mod builder;
2pub(crate) mod wgpu_backend;
3
4use std::num::NonZeroU32;
5
6use wgpu::Adapter;
7use wgpu::BindGroup;
8#[cfg(test)]
9use wgpu::Buffer;
10#[cfg(test)]
11use wgpu::BufferDescriptor;
12#[cfg(test)]
13use wgpu::BufferUsages;
14use wgpu::CommandEncoder;
15use wgpu::Device;
16use wgpu::Extent3d;
17use wgpu::Queue;
18use wgpu::RenderPipeline;
19use wgpu::Surface;
20use wgpu::SurfaceConfiguration;
21use wgpu::SurfaceTexture;
22#[cfg(test)]
23use wgpu::Texture;
24use wgpu::TextureDescriptor;
25use wgpu::TextureDimension;
26use wgpu::TextureFormat;
27use wgpu::TextureUsages;
28use wgpu::TextureView;
29use wgpu::TextureViewDescriptor;
30
31pub trait PostProcessor {
33 type UserData;
37
38 fn compile(
42 device: &Device,
43 text_view: &TextureView,
44 surface_config: &SurfaceConfiguration,
45 user_data: Self::UserData,
46 ) -> Self;
47
48 fn resize(
51 &mut self,
52 device: &Device,
53 text_view: &TextureView,
54 surface_config: &SurfaceConfiguration,
55 );
56
57 fn process(
68 &mut self,
69 encoder: &mut CommandEncoder,
70 queue: &Queue,
71 text_view: &TextureView,
72 surface_config: &SurfaceConfiguration,
73 surface_view: &TextureView,
74 );
75
76 fn needs_update(&self) -> bool {
82 false
83 }
84}
85
86pub struct Dimensions {
88 pub width: NonZeroU32,
89 pub height: NonZeroU32,
90}
91
92impl From<(NonZeroU32, NonZeroU32)> for Dimensions {
93 fn from((width, height): (NonZeroU32, NonZeroU32)) -> Self {
94 Self { width, height }
95 }
96}
97
98#[derive(Clone, Copy, Debug, Default)]
101#[non_exhaustive]
102pub enum Viewport {
103 #[default]
105 Full,
106 Shrink { width: u32, height: u32 },
109}
110
111mod private {
112 use wgpu::Surface;
113
114 #[cfg(test)]
115 use crate::backend::HeadlessSurface;
116 #[cfg(test)]
117 use crate::backend::HeadlessTarget;
118 use crate::backend::RenderTarget;
119
120 pub trait Sealed {}
121
122 pub struct Token;
123
124 impl Sealed for Surface<'_> {}
125 impl Sealed for RenderTarget {}
126
127 #[cfg(test)]
128 impl Sealed for HeadlessTarget {}
129
130 #[cfg(test)]
131 impl Sealed for HeadlessSurface {}
132}
133
134pub trait RenderTexture: private::Sealed + Sized {
136 fn get_view(
138 &self,
139 _token: private::Token,
140 ) -> &TextureView;
141 fn present(
143 self,
144 _token: private::Token,
145 ) {
146 }
147}
148
149impl RenderTexture for RenderTarget {
150 fn get_view(
151 &self,
152 _token: private::Token,
153 ) -> &TextureView {
154 &self.view
155 }
156
157 fn present(
158 self,
159 _token: private::Token,
160 ) {
161 self.texture.present();
162 }
163}
164
165#[cfg(test)]
166impl RenderTexture for HeadlessTarget {
167 fn get_view(
168 &self,
169 _token: private::Token,
170 ) -> &TextureView {
171 &self.view
172 }
173}
174
175pub trait RenderSurface<'s>: private::Sealed {
177 type Target: RenderTexture;
178
179 fn wgpu_surface(
180 &self,
181 _token: private::Token,
182 ) -> Option<&Surface<'s>>;
183
184 fn get_default_config(
185 &self,
186 adapter: &Adapter,
187 width: u32,
188 height: u32,
189 _token: private::Token,
190 ) -> Option<SurfaceConfiguration>;
191
192 fn configure(
193 &mut self,
194 device: &Device,
195 config: &SurfaceConfiguration,
196 _token: private::Token,
197 );
198
199 fn get_current_texture(
200 &self,
201 _token: private::Token,
202 ) -> Option<Self::Target>;
203}
204
205pub struct RenderTarget {
206 texture: SurfaceTexture,
207 view: TextureView,
208}
209
210impl<'s> RenderSurface<'s> for Surface<'s> {
211 type Target = RenderTarget;
212
213 fn wgpu_surface(
214 &self,
215 _token: private::Token,
216 ) -> Option<&Surface<'s>> {
217 Some(self)
218 }
219
220 fn get_default_config(
221 &self,
222 adapter: &Adapter,
223 width: u32,
224 height: u32,
225 _token: private::Token,
226 ) -> Option<SurfaceConfiguration> {
227 self.get_default_config(adapter, width, height)
228 }
229
230 fn configure(
231 &mut self,
232 device: &Device,
233 config: &SurfaceConfiguration,
234 _token: private::Token,
235 ) {
236 Surface::configure(self, device, config);
237 }
238
239 fn get_current_texture(
240 &self,
241 _token: private::Token,
242 ) -> Option<Self::Target> {
243 let output = match self.get_current_texture() {
244 Ok(output) => output,
245 Err(err) => {
246 error!("{err}");
247 return None;
248 }
249 };
250
251 let view = output
252 .texture
253 .create_view(&TextureViewDescriptor::default());
254
255 Some(RenderTarget {
256 texture: output,
257 view,
258 })
259 }
260}
261
262#[cfg(test)]
263pub(crate) struct HeadlessTarget {
264 view: TextureView,
265}
266
267#[cfg(test)]
268pub(crate) struct HeadlessSurface {
269 pub(crate) texture: Option<Texture>,
270 pub(crate) buffer: Option<Buffer>,
271 pub(crate) buffer_width: u32,
272 pub(crate) width: u32,
273 pub(crate) height: u32,
274 pub(crate) format: TextureFormat,
275}
276
277#[cfg(test)]
278impl HeadlessSurface {
279 fn new(format: TextureFormat) -> Self {
280 Self {
281 format,
282 ..Default::default()
283 }
284 }
285}
286
287#[cfg(test)]
288impl Default for HeadlessSurface {
289 fn default() -> Self {
290 Self {
291 texture: Default::default(),
292 buffer: Default::default(),
293 buffer_width: Default::default(),
294 width: Default::default(),
295 height: Default::default(),
296 format: TextureFormat::Rgba8Unorm,
297 }
298 }
299}
300
301#[cfg(test)]
302impl RenderSurface<'static> for HeadlessSurface {
303 type Target = HeadlessTarget;
304
305 fn wgpu_surface(
306 &self,
307 _token: private::Token,
308 ) -> Option<&Surface<'static>> {
309 None
310 }
311
312 fn get_default_config(
313 &self,
314 _adapter: &Adapter,
315 width: u32,
316 height: u32,
317 _token: private::Token,
318 ) -> Option<SurfaceConfiguration> {
319 Some(SurfaceConfiguration {
320 usage: TextureUsages::RENDER_ATTACHMENT,
321 format: self.format,
322 width,
323 height,
324 present_mode: wgpu::PresentMode::Immediate,
325 desired_maximum_frame_latency: 2,
326 alpha_mode: wgpu::CompositeAlphaMode::Auto,
327 view_formats: vec![],
328 })
329 }
330
331 fn configure(
332 &mut self,
333 device: &Device,
334 config: &SurfaceConfiguration,
335 _token: private::Token,
336 ) {
337 self.texture = Some(device.create_texture(&TextureDescriptor {
338 label: None,
339 size: Extent3d {
340 width: config.width,
341 height: config.height,
342 depth_or_array_layers: 1,
343 },
344 mip_level_count: 1,
345 sample_count: 1,
346 dimension: TextureDimension::D2,
347 format: self.format,
348 usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_SRC,
349 view_formats: &[],
350 }));
351
352 self.buffer_width = config.width * 4;
353 self.buffer = Some(device.create_buffer(&BufferDescriptor {
354 label: None,
355 size: (self.buffer_width * config.height) as u64,
356 usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,
357 mapped_at_creation: false,
358 }));
359 self.width = config.width;
360 self.height = config.height;
361 }
362
363 fn get_current_texture(
364 &self,
365 _token: private::Token,
366 ) -> Option<Self::Target> {
367 self.texture.as_ref().map(|t| HeadlessTarget {
368 view: t.create_view(&TextureViewDescriptor::default()),
369 })
370 }
371}
372
373#[repr(C)]
374#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
375struct TextBgVertexMember {
376 vertex: [f32; 2],
377 bg_color: u32,
378}
379
380#[repr(C)]
382#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
383struct TextVertexMember {
384 vertex: [f32; 2],
385 uv: [f32; 2],
386 fg_color: u32,
387 underline_pos: u32,
388 underline_color: u32,
389 strikeout_pos: u32,
390 strikeout_color: u32,
391}
392
393struct TextCacheBgPipeline {
394 pipeline: RenderPipeline,
395 fs_uniforms: BindGroup,
396}
397
398struct TextCacheFgPipeline {
399 pipeline: RenderPipeline,
400 fs_uniforms: BindGroup,
401 atlas_bindings: BindGroup,
402}
403
404struct WgpuState {
405 text_dest_view: TextureView,
406}
407
408fn build_wgpu_state(
409 device: &Device,
410 drawable_width: u32,
411 drawable_height: u32,
412) -> WgpuState {
413 let text_dest = device.create_texture(&TextureDescriptor {
414 label: Some("Text Compositor Out"),
415 size: Extent3d {
416 width: drawable_width.max(1),
417 height: drawable_height.max(1),
418 depth_or_array_layers: 1,
419 },
420 mip_level_count: 1,
421 sample_count: 1,
422 dimension: TextureDimension::D2,
423 format: TextureFormat::Rgba8Unorm,
424 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::RENDER_ATTACHMENT,
425 view_formats: &[],
426 });
427
428 let text_dest_view = text_dest.create_view(&TextureViewDescriptor::default());
429
430 WgpuState { text_dest_view }
431}