1use crate::layout::{LayoutNode, LayoutKind};
4use bytemuck::{Pod, Zeroable};
5use winit::window::Window;
6
7#[repr(C)]
8#[derive(Copy, Clone, Pod, Zeroable)]
9struct Vertex {
10 position: [f32; 2],
11 color: [f32; 4],
12 rect_pixels: [f32; 4],
13 rect_radius: f32,
14 stroke_color: [f32; 4],
15 stroke_width: f32,
16 viewport: [f32; 2],
17}
18
19#[repr(C)]
20#[derive(Copy, Clone, Pod, Zeroable)]
21struct Uniforms {
22 viewport: [f32; 2], _pad: [f32; 2],
24}
25
26pub struct DrawRect {
27 pub x: f32,
28 pub y: f32,
29 pub w: f32,
30 pub h: f32,
31 pub r: f32, pub fill: [f32; 4],
33 pub stroke: Option<[f32; 4]>,
34}
35
36pub struct RendererState {
37 surface: wgpu::Surface<'static>,
38 device: wgpu::Device,
39 queue: wgpu::Queue,
40 pub config: wgpu::SurfaceConfiguration,
41 pipeline: wgpu::RenderPipeline,
42 vertex_buffer: wgpu::Buffer,
43 vertex_count: u32,
44 font_system: glyphon::FontSystem,
45 swash_cache: glyphon::SwashCache,
46 #[allow(dead_code)] glyphon_cache: glyphon::Cache,
48 glyphon_viewport: glyphon::Viewport,
49 text_atlas: glyphon::TextAtlas,
50 text_renderer: glyphon::TextRenderer,
51}
52
53impl RendererState {
54 pub async fn new(window: &Window) -> Result<Self, String> {
55 let size = window.inner_size();
56 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
57 backends: wgpu::Backends::all(),
58 ..Default::default()
59 });
60 let surface = instance.create_surface(window).map_err(|e| e.to_string())?;
61 let surface: wgpu::Surface<'static> = unsafe { std::mem::transmute(surface) };
62 let adapter = instance
63 .request_adapter(&wgpu::RequestAdapterOptions {
64 power_preference: wgpu::PowerPreference::HighPerformance,
65 compatible_surface: Some(&surface),
66 force_fallback_adapter: false,
67 })
68 .await
69 .ok_or("No GPU adapter")?;
70 let (device, queue) = adapter
71 .request_device(
72 &wgpu::DeviceDescriptor {
73 label: Some("newter device"),
74 required_features: wgpu::Features::empty(),
75 required_limits: wgpu::Limits::default(),
76 memory_hints: wgpu::MemoryHints::Performance,
77 },
78 None,
79 )
80 .await
81 .map_err(|e| e.to_string())?;
82
83 let config = wgpu::SurfaceConfiguration {
84 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
85 format: wgpu::TextureFormat::Bgra8UnormSrgb,
86 width: size.width,
87 height: size.height,
88 present_mode: wgpu::PresentMode::Fifo,
89 alpha_mode: wgpu::CompositeAlphaMode::Opaque,
90 view_formats: vec![],
91 desired_maximum_frame_latency: 2,
92 };
93 surface.configure(&device, &config);
94
95 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
96 label: Some("rect shader"),
97 source: wgpu::ShaderSource::Wgsl(include_str!("rect.wgsl").into()),
98 });
99
100 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
101 label: Some("rect pipeline layout"),
102 bind_group_layouts: &[],
103 push_constant_ranges: &[],
104 });
105
106 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
107 label: Some("rect pipeline"),
108 layout: Some(&pipeline_layout),
109 vertex: wgpu::VertexState {
110 module: &shader,
111 entry_point: "vs_main",
112 buffers: &[wgpu::VertexBufferLayout {
113 array_stride: std::mem::size_of::<Vertex>() as u64,
114 step_mode: wgpu::VertexStepMode::Vertex,
115 attributes: &[
116 wgpu::VertexAttribute {
117 offset: 0,
118 shader_location: 0,
119 format: wgpu::VertexFormat::Float32x2,
120 },
121 wgpu::VertexAttribute {
122 offset: std::mem::size_of::<[f32; 2]>() as u64,
123 shader_location: 1,
124 format: wgpu::VertexFormat::Float32x4,
125 },
126 wgpu::VertexAttribute {
127 offset: (std::mem::size_of::<[f32; 2]>() + std::mem::size_of::<[f32; 4]>()) as u64,
128 shader_location: 2,
129 format: wgpu::VertexFormat::Float32x4,
130 },
131 wgpu::VertexAttribute {
132 offset: (std::mem::size_of::<[f32; 2]>() + std::mem::size_of::<[f32; 4]>() * 2) as u64,
133 shader_location: 3,
134 format: wgpu::VertexFormat::Float32,
135 },
136 wgpu::VertexAttribute {
137 offset: (std::mem::size_of::<[f32; 2]>() + std::mem::size_of::<[f32; 4]>() * 2 + 4) as u64,
138 shader_location: 4,
139 format: wgpu::VertexFormat::Float32x4,
140 },
141 wgpu::VertexAttribute {
142 offset: (std::mem::size_of::<[f32; 2]>() + std::mem::size_of::<[f32; 4]>() * 3 + 4) as u64,
143 shader_location: 5,
144 format: wgpu::VertexFormat::Float32,
145 },
146 wgpu::VertexAttribute {
147 offset: (std::mem::size_of::<[f32; 2]>() + std::mem::size_of::<[f32; 4]>() * 3 + 4 + 4) as u64,
148 shader_location: 6,
149 format: wgpu::VertexFormat::Float32x2,
150 },
151 ],
152 }],
153 compilation_options: Default::default(),
154 },
155 fragment: Some(wgpu::FragmentState {
156 module: &shader,
157 entry_point: "fs_main",
158 targets: &[Some(wgpu::ColorTargetState {
159 format: config.format,
160 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
161 write_mask: wgpu::ColorWrites::ALL,
162 })],
163 compilation_options: Default::default(),
164 }),
165 primitive: wgpu::PrimitiveState {
166 topology: wgpu::PrimitiveTopology::TriangleList,
167 strip_index_format: None,
168 front_face: wgpu::FrontFace::Ccw,
169 cull_mode: None,
170 polygon_mode: wgpu::PolygonMode::Fill,
171 unclipped_depth: false,
172 conservative: false,
173 },
174 depth_stencil: None,
175 multisample: wgpu::MultisampleState::default(),
176 multiview: None,
177 cache: None,
178 });
179
180 let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
181 label: Some("rect vertex buffer"),
182 size: (100_000 * std::mem::size_of::<Vertex>()) as u64,
183 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
184 mapped_at_creation: false,
185 });
186
187 let font_system = glyphon::FontSystem::new();
188 let swash_cache = glyphon::SwashCache::new();
189 let glyphon_cache = glyphon::Cache::new(&device);
190 let glyphon_viewport = glyphon::Viewport::new(&device, &glyphon_cache);
191 let mut text_atlas = glyphon::TextAtlas::new(&device, &queue, &glyphon_cache, config.format);
192 let text_renderer = glyphon::TextRenderer::new(
193 &mut text_atlas,
194 &device,
195 wgpu::MultisampleState::default(),
196 None,
197 );
198
199 Ok(Self {
200 surface,
201 device,
202 queue,
203 config,
204 pipeline,
205 vertex_buffer,
206 vertex_count: 0,
207 font_system,
208 swash_cache,
209 glyphon_cache,
210 glyphon_viewport,
211 text_atlas,
212 text_renderer,
213 })
214 }
215
216 pub fn resize(&mut self, width: u32, height: u32) {
217 if width > 0 && height > 0 {
218 self.config.width = width;
219 self.config.height = height;
220 self.surface.configure(&self.device, &self.config);
221 }
222 }
223
224 fn rect_to_vertices(rect: &DrawRect, viewport_w: f32, viewport_h: f32) -> [Vertex; 6] {
225 let x0 = (rect.x / viewport_w) * 2.0 - 1.0;
226 let y0 = 1.0 - (rect.y / viewport_h) * 2.0;
227 let x1 = ((rect.x + rect.w) / viewport_w) * 2.0 - 1.0;
228 let y1 = 1.0 - ((rect.y + rect.h) / viewport_h) * 2.0;
229 let c = rect.fill;
230 let rp = [rect.x, rect.y, rect.w, rect.h];
231 let rr = rect.r;
232 let (stroke_color, stroke_width) = rect
233 .stroke
234 .map(|s| (s, 1.0))
235 .unwrap_or(([0.0, 0.0, 0.0, 0.0], 0.0));
236 let viewport = [viewport_w, viewport_h];
237 let v = |pos: [f32; 2]| Vertex {
238 position: pos,
239 color: c,
240 rect_pixels: rp,
241 rect_radius: rr,
242 stroke_color,
243 stroke_width,
244 viewport,
245 };
246 [
247 v([x0, y0]),
248 v([x1, y0]),
249 v([x0, y1]),
250 v([x0, y1]),
251 v([x1, y0]),
252 v([x1, y1]),
253 ]
254 }
255
256 pub fn draw_layout(&mut self, root: &LayoutNode) {
257 let viewport_w = self.config.width as f32;
258 let viewport_h = self.config.height as f32;
259
260 let mut rects = Vec::new();
262 collect_rects(root, viewport_w, viewport_h, &mut rects);
263 let mut vertices: Vec<Vertex> = Vec::new();
264 for r in &rects {
265 let v = Self::rect_to_vertices(r, viewport_w, viewport_h);
266 vertices.extend_from_slice(&v);
267 }
268 self.vertex_count = vertices.len() as u32;
269 if self.vertex_count > 0 {
270 self.queue.write_buffer(
271 &self.vertex_buffer,
272 0,
273 bytemuck::cast_slice(&vertices),
274 );
275 }
276
277 let mut text_nodes = Vec::new();
279 collect_text_nodes(root, &mut text_nodes);
280
281 self.glyphon_viewport.update(&self.queue, glyphon::Resolution {
283 width: viewport_w as u32,
284 height: viewport_h as u32,
285 });
286
287 let mut buffers: Vec<glyphon::Buffer> = Vec::new();
289 for tn in &text_nodes {
290 let metrics = glyphon::Metrics::new(tn.font_size, tn.font_size * 1.2);
291 let mut buffer = glyphon::Buffer::new(&mut self.font_system, metrics);
292 buffer.set_size(&mut self.font_system, Some(tn.w), Some(tn.h));
293 buffer.set_text(
294 &mut self.font_system,
295 &tn.text,
296 glyphon::Attrs::new().family(glyphon::Family::SansSerif),
297 glyphon::Shaping::Advanced,
298 );
299 buffer.shape_until_scroll(&mut self.font_system, false);
300 buffers.push(buffer);
301 }
302
303 let text_areas: Vec<glyphon::TextArea> = text_nodes
305 .iter()
306 .zip(buffers.iter())
307 .map(|(tn, buf)| {
308 let default_color = if tn.has_dark_bg {
309 glyphon::Color::rgb(226, 226, 232)
310 } else {
311 glyphon::Color::rgb(26, 26, 32)
312 };
313 glyphon::TextArea {
314 buffer: buf,
315 left: tn.x,
316 top: tn.y,
317 scale: 1.0,
318 bounds: glyphon::TextBounds {
319 left: tn.x as i32,
320 top: tn.y as i32,
321 right: (tn.x + tn.w) as i32,
322 bottom: (tn.y + tn.h) as i32,
323 },
324 default_color,
325 custom_glyphs: &[],
326 }
327 })
328 .collect();
329
330 let _ = self.text_renderer.prepare(
331 &self.device,
332 &self.queue,
333 &mut self.font_system,
334 &mut self.text_atlas,
335 &self.glyphon_viewport,
336 text_areas,
337 &mut self.swash_cache,
338 );
339 }
340
341 pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
342 let output = self.surface.get_current_texture()?;
343 let view = output
344 .texture
345 .create_view(&wgpu::TextureViewDescriptor::default());
346 let mut encoder = self
347 .device
348 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
349 label: Some("render encoder"),
350 });
351 {
352 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
353 label: Some("render pass"),
354 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
355 view: &view,
356 resolve_target: None,
357 ops: wgpu::Operations {
358 load: wgpu::LoadOp::Clear(wgpu::Color {
359 r: 0.95,
360 g: 0.95,
361 b: 0.97,
362 a: 1.0,
363 }),
364 store: wgpu::StoreOp::Store,
365 },
366 })],
367 depth_stencil_attachment: None,
368 timestamp_writes: None,
369 occlusion_query_set: None,
370 });
371 pass.set_pipeline(&self.pipeline);
372 pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
373 pass.draw(0..self.vertex_count, 0..1);
374
375 let _ = self.text_renderer.render(
376 &self.text_atlas,
377 &self.glyphon_viewport,
378 &mut pass,
379 );
380 }
381 self.queue.submit(std::iter::once(encoder.finish()));
382 output.present();
383 Ok(())
384 }
385}
386
387struct TextNode {
388 text: String,
389 x: f32,
390 y: f32,
391 w: f32,
392 h: f32,
393 font_size: f32,
394 has_dark_bg: bool,
395}
396
397fn collect_text_nodes(node: &LayoutNode, out: &mut Vec<TextNode>) {
398 if let Some(ref text) = node.text {
399 let has_dark_bg = node.fill
400 .map(|(r, g, b, _)| (r as f32 * 0.299 + g as f32 * 0.587 + b as f32 * 0.114) < 128.0)
401 .unwrap_or(false);
402 out.push(TextNode {
403 text: text.clone(),
404 x: node.rect.x,
405 y: node.rect.y,
406 w: node.rect.w,
407 h: node.rect.h,
408 font_size: node.font_size.max(8.0),
409 has_dark_bg,
410 });
411 }
412 for child in &node.children {
413 collect_text_nodes(child, out);
414 }
415}
416
417fn collect_rects(node: &LayoutNode, _vw: f32, _vh: f32, out: &mut Vec<DrawRect>) {
418 let r = &node.rect;
419 let fill = node
420 .fill
421 .map(|(r, g, b, a)| [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, a as f32 / 255.0])
422 .unwrap_or([0.9, 0.9, 0.9, 1.0]);
423 let stroke = node.stroke.map(|(r, g, b, a)| {
424 [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, a as f32 / 255.0]
425 });
426 match node.kind {
427 LayoutKind::Box | LayoutKind::Row | LayoutKind::Column | LayoutKind::Grid | LayoutKind::Stack | LayoutKind::Center
428 | LayoutKind::Button | LayoutKind::Input | LayoutKind::Modal => {
429 out.push(DrawRect {
430 x: r.x,
431 y: r.y,
432 w: r.w,
433 h: r.h,
434 r: node.radius,
435 fill,
436 stroke,
437 });
438 }
439 LayoutKind::Text | LayoutKind::Spacer | LayoutKind::Image => {
440 if node.kind == LayoutKind::Text && node.fill.is_some() {
441 out.push(DrawRect {
442 x: r.x,
443 y: r.y,
444 w: r.w,
445 h: r.h,
446 r: node.radius,
447 fill,
448 stroke,
449 });
450 }
451 }
452 }
453 for child in &node.children {
454 collect_rects(child, _vw, _vh, out);
455 }
456}