1use soul_terminal_core::{Color, RenderCommand};
2use wgpu::util::DeviceExt;
3
4use crate::backend::{RenderBackend, RenderError};
5use crate::glyph::GlyphPipeline;
6use crate::quad::{QuadPipeline, QuadUniforms};
7use crate::texture::TexturePipeline;
8
9pub struct WgpuBackend {
12 device: wgpu::Device,
13 queue: wgpu::Queue,
14 surface: wgpu::Surface<'static>,
15 surface_config: wgpu::SurfaceConfiguration,
16 quad_pipeline: QuadPipeline,
17 glyph_pipeline: GlyphPipeline,
18 texture_pipeline: TexturePipeline,
19 width: u32,
20 height: u32,
21 scale_factor: f32,
22 current_texture: Option<wgpu::SurfaceTexture>,
23}
24
25impl WgpuBackend {
26 pub async fn new(
28 surface: wgpu::Surface<'static>,
29 adapter: &wgpu::Adapter,
30 width: u32,
31 height: u32,
32 scale_factor: f32,
33 ) -> Result<Self, RenderError> {
34 let (device, queue) = adapter
35 .request_device(&wgpu::DeviceDescriptor {
36 label: Some("soul_terminal_device"),
37 required_features: wgpu::Features::empty(),
38 required_limits: wgpu::Limits::downlevel_webgl2_defaults()
39 .using_resolution(adapter.limits()),
40 memory_hints: wgpu::MemoryHints::default(),
41 trace: wgpu::Trace::Off,
42 })
43 .await
44 .map_err(|e| RenderError::DeviceError(e.to_string()))?;
45
46 let surface_caps = surface.get_capabilities(adapter);
47 let format = surface_caps
48 .formats
49 .iter()
50 .find(|f| f.is_srgb())
51 .copied()
52 .unwrap_or(surface_caps.formats[0]);
53
54 let surface_config = wgpu::SurfaceConfiguration {
55 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
56 format,
57 width,
58 height,
59 present_mode: wgpu::PresentMode::AutoVsync,
60 alpha_mode: surface_caps.alpha_modes[0],
61 view_formats: vec![],
62 desired_maximum_frame_latency: 2,
63 };
64 surface.configure(&device, &surface_config);
65
66 let quad_pipeline = QuadPipeline::new(&device, format);
67 let glyph_pipeline = GlyphPipeline::new(&device, &queue, format);
68 let texture_pipeline = TexturePipeline::new(&device);
69
70 Ok(Self {
71 device,
72 queue,
73 surface,
74 surface_config,
75 quad_pipeline,
76 glyph_pipeline,
77 texture_pipeline,
78 width,
79 height,
80 scale_factor,
81 current_texture: None,
82 })
83 }
84
85 fn process_command(&mut self, cmd: &RenderCommand) {
86 match cmd {
87 RenderCommand::FillRect {
88 rect,
89 color,
90 corner_radius,
91 } => {
92 self.quad_pipeline.push_rect(
93 rect.x,
94 rect.y,
95 rect.width,
96 rect.height,
97 color.to_array(),
98 *corner_radius,
99 );
100 }
101 RenderCommand::DrawBorder { rect, border } => {
102 let w = border.width;
103 let c = border.color.to_array();
104 let r = border.radius;
105 self.quad_pipeline
107 .push_rect(rect.x, rect.y, rect.width, w, c, r);
108 self.quad_pipeline.push_rect(
110 rect.x,
111 rect.y + rect.height - w,
112 rect.width,
113 w,
114 c,
115 r,
116 );
117 self.quad_pipeline
119 .push_rect(rect.x, rect.y, w, rect.height, c, 0.0);
120 self.quad_pipeline.push_rect(
122 rect.x + rect.width - w,
123 rect.y,
124 w,
125 rect.height,
126 c,
127 0.0,
128 );
129 }
130 RenderCommand::DrawText {
131 text,
132 x,
133 y,
134 color,
135 font_size,
136 bold,
137 italic,
138 monospace,
139 } => {
140 self.glyph_pipeline.push_text(
141 text,
142 *x,
143 *y,
144 color.to_array(),
145 *font_size,
146 *bold,
147 *italic,
148 *monospace,
149 );
150 }
151 RenderCommand::DrawShadow {
152 rect,
153 color,
154 offset_x,
155 offset_y,
156 blur_radius,
157 corner_radius,
158 } => {
159 let expand = *blur_radius;
161 self.quad_pipeline.push_rect(
162 rect.x + offset_x - expand,
163 rect.y + offset_y - expand,
164 rect.width + expand * 2.0,
165 rect.height + expand * 2.0,
166 color.to_array(),
167 corner_radius + expand,
168 );
169 }
170 RenderCommand::DrawImage {
171 rect: _,
172 texture_id: _,
173 } => {
174 }
176 RenderCommand::DrawLine {
177 x1,
178 y1,
179 x2,
180 y2,
181 color,
182 width,
183 } => {
184 let dx = x2 - x1;
186 let dy = y2 - y1;
187 let len = (dx * dx + dy * dy).sqrt();
188 if len > 0.0 {
189 if dy.abs() < 0.001 {
190 self.quad_pipeline
192 .push_rect(*x1, *y1 - width / 2.0, len, *width, color.to_array(), 0.0);
193 } else if dx.abs() < 0.001 {
194 self.quad_pipeline
196 .push_rect(*x1 - width / 2.0, *y1, *width, len, color.to_array(), 0.0);
197 } else {
198 self.quad_pipeline
200 .push_rect(*x1, *y1, dx.abs(), dy.abs(), color.to_array(), 0.0);
201 }
202 }
203 }
204 RenderCommand::PushClip { .. } | RenderCommand::PopClip => {
205 }
207 }
208 }
209}
210
211impl RenderBackend for WgpuBackend {
212 fn begin_frame(&mut self, _clear_color: Color) -> Result<(), RenderError> {
213 self.quad_pipeline.clear();
214 self.glyph_pipeline.clear();
215
216 let output = self
217 .surface
218 .get_current_texture()
219 .map_err(|e| RenderError::SurfaceError(e.to_string()))?;
220
221 self.current_texture = Some(output);
222 Ok(())
223 }
224
225 fn submit(&mut self, commands: &[RenderCommand]) -> Result<(), RenderError> {
226 for cmd in commands {
227 self.process_command(cmd);
228 }
229 Ok(())
230 }
231
232 fn present(&mut self) -> Result<(), RenderError> {
233 let output = self
234 .current_texture
235 .take()
236 .ok_or_else(|| RenderError::SurfaceError("no current texture".into()))?;
237
238 let view = output
239 .texture
240 .create_view(&wgpu::TextureViewDescriptor::default());
241
242 self.queue.write_buffer(
244 &self.quad_pipeline.uniform_buffer,
245 0,
246 bytemuck::cast_slice(&[QuadUniforms {
247 screen_size: [self.width as f32, self.height as f32],
248 _padding: [0.0; 2],
249 }]),
250 );
251
252 let vertex_buffer;
254 let index_buffer;
255 let has_quads = !self.quad_pipeline.vertices.is_empty();
256
257 if has_quads {
258 vertex_buffer =
259 self.device
260 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
261 label: Some("quad_vertex_buffer"),
262 contents: bytemuck::cast_slice(&self.quad_pipeline.vertices),
263 usage: wgpu::BufferUsages::VERTEX,
264 });
265 index_buffer =
266 self.device
267 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
268 label: Some("quad_index_buffer"),
269 contents: bytemuck::cast_slice(&self.quad_pipeline.indices),
270 usage: wgpu::BufferUsages::INDEX,
271 });
272 } else {
273 vertex_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
275 label: None,
276 size: 4,
277 usage: wgpu::BufferUsages::VERTEX,
278 mapped_at_creation: false,
279 });
280 index_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
281 label: None,
282 size: 4,
283 usage: wgpu::BufferUsages::INDEX,
284 mapped_at_creation: false,
285 });
286 }
287
288 self.glyph_pipeline
290 .prepare(&self.device, &self.queue, self.width, self.height);
291
292 let mut encoder = self
293 .device
294 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
295 label: Some("soul_terminal_encoder"),
296 });
297
298 {
299 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
300 label: Some("soul_terminal_render_pass"),
301 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
302 view: &view,
303 resolve_target: None,
304 ops: wgpu::Operations {
305 load: wgpu::LoadOp::Clear(wgpu::Color {
306 r: 0.04,
307 g: 0.04,
308 b: 0.06,
309 a: 1.0,
310 }),
311 store: wgpu::StoreOp::Store,
312 },
313 })],
314 depth_stencil_attachment: None,
315 timestamp_writes: None,
316 occlusion_query_set: None,
317 });
318
319 if has_quads {
321 render_pass.set_pipeline(&self.quad_pipeline.pipeline);
322 render_pass.set_bind_group(0, &self.quad_pipeline.uniform_bind_group, &[]);
323 render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
324 render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint32);
325 render_pass.draw_indexed(0..self.quad_pipeline.indices.len() as u32, 0, 0..1);
326 }
327
328 self.glyph_pipeline.render(&mut render_pass);
330 }
331
332 self.queue.submit(std::iter::once(encoder.finish()));
333 output.present();
334
335 self.glyph_pipeline.trim();
336
337 Ok(())
338 }
339
340 fn resize(&mut self, width: u32, height: u32) {
341 if width > 0 && height > 0 {
342 self.width = width;
343 self.height = height;
344 self.surface_config.width = width;
345 self.surface_config.height = height;
346 self.surface.configure(&self.device, &self.surface_config);
347 }
348 }
349
350 fn load_texture(&mut self, width: u32, height: u32, data: &[u8]) -> Result<u64, RenderError> {
351 self.texture_pipeline
352 .load(&self.device, &self.queue, width, height, data)
353 }
354
355 fn size(&self) -> (u32, u32) {
356 (self.width, self.height)
357 }
358
359 fn scale_factor(&self) -> f32 {
360 self.scale_factor
361 }
362}