Skip to main content

newter_compiler/renderer/
state.rs

1//! wgpu state: device, queue, pipeline for drawing rectangles (and text when glyphon supports wgpu 22+).
2
3use 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], // width, height
23    _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, // corner radius
32    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)] // must stay alive — shared Arc used by text_atlas/viewport
47    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        // 1. Collect and upload rect vertices
261        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        // 2. Collect text nodes
278        let mut text_nodes = Vec::new();
279        collect_text_nodes(root, &mut text_nodes);
280
281        // 3. Update glyphon viewport
282        self.glyphon_viewport.update(&self.queue, glyphon::Resolution {
283            width: viewport_w as u32,
284            height: viewport_h as u32,
285        });
286
287        // 4. Create buffers for each text node
288        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        // 5. Build TextAreas and prepare renderer
304        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}