|
| 1 | +use glyphon::{ |
| 2 | + Attrs, Buffer, Cache, Color, ColorMode, Family, FontSystem, Metrics, Resolution, Shaping, |
| 3 | + SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer, Viewport, Weight, |
| 4 | +}; |
| 5 | +use std::sync::Arc; |
| 6 | +use wgpu::{ |
| 7 | + CommandEncoderDescriptor, CompositeAlphaMode, DeviceDescriptor, Instance, InstanceDescriptor, |
| 8 | + LoadOp, MultisampleState, Operations, PresentMode, RenderPassColorAttachment, |
| 9 | + RenderPassDescriptor, RequestAdapterOptions, SurfaceConfiguration, TextureFormat, |
| 10 | + TextureUsages, TextureViewDescriptor, |
| 11 | +}; |
| 12 | +use winit::{ |
| 13 | + dpi::{LogicalSize, PhysicalSize}, |
| 14 | + event::WindowEvent, |
| 15 | + event_loop::EventLoop, |
| 16 | + window::Window, |
| 17 | +}; |
| 18 | + |
| 19 | +const TEXT: &str = "The quick brown fox jumped over the lazy doggo. 🐕"; |
| 20 | +const WEIGHT: Weight = Weight::NORMAL; |
| 21 | +const SIZES: [f32; 16] = [ |
| 22 | + 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 18.0, 20.0, 22.0, 24.0, 28.0, 32.0, 48.0, |
| 23 | +]; |
| 24 | +const LINE_HEIGHT: f32 = 1.15; |
| 25 | +const BG_COLOR: wgpu::Color = wgpu::Color::WHITE; |
| 26 | +const FONT_COLOR: Color = Color::rgb(0, 0, 0); |
| 27 | +//const BG_COLOR: wgpu::Color = wgpu::Color::BLACK; |
| 28 | +//const FONT_COLOR: Color = Color::rgb(255, 255, 255); |
| 29 | +const USE_WEB_COLORS: bool = true; |
| 30 | + |
| 31 | +fn main() { |
| 32 | + let event_loop = EventLoop::new().unwrap(); |
| 33 | + event_loop |
| 34 | + .run_app(&mut Application { window_state: None }) |
| 35 | + .unwrap(); |
| 36 | +} |
| 37 | + |
| 38 | +struct WindowState { |
| 39 | + device: wgpu::Device, |
| 40 | + queue: wgpu::Queue, |
| 41 | + surface: wgpu::Surface<'static>, |
| 42 | + surface_config: SurfaceConfiguration, |
| 43 | + physical_size: PhysicalSize<i32>, |
| 44 | + scale_factor: f32, |
| 45 | + |
| 46 | + font_system: FontSystem, |
| 47 | + swash_cache: SwashCache, |
| 48 | + viewport: glyphon::Viewport, |
| 49 | + atlas: glyphon::TextAtlas, |
| 50 | + text_renderer: glyphon::TextRenderer, |
| 51 | + buffers: Vec<glyphon::Buffer>, |
| 52 | + |
| 53 | + // Make sure that the winit window is last in the struct so that |
| 54 | + // it is dropped after the wgpu surface is dropped, otherwise the |
| 55 | + // program may crash when closed. This is probably a bug in wgpu. |
| 56 | + window: Arc<Window>, |
| 57 | +} |
| 58 | + |
| 59 | +impl WindowState { |
| 60 | + async fn new(window: Arc<Window>) -> Self { |
| 61 | + let physical_size = window.inner_size(); |
| 62 | + let scale_factor = window.scale_factor() as f32; |
| 63 | + |
| 64 | + // Set up surface |
| 65 | + let instance = Instance::new(InstanceDescriptor::default()); |
| 66 | + let adapter = instance |
| 67 | + .request_adapter(&RequestAdapterOptions::default()) |
| 68 | + .await |
| 69 | + .unwrap(); |
| 70 | + let (device, queue) = adapter |
| 71 | + .request_device(&DeviceDescriptor::default(), None) |
| 72 | + .await |
| 73 | + .unwrap(); |
| 74 | + |
| 75 | + let (color_mode, swapchain_format) = if USE_WEB_COLORS { |
| 76 | + (ColorMode::Web, TextureFormat::Bgra8Unorm) |
| 77 | + } else { |
| 78 | + (ColorMode::Accurate, TextureFormat::Bgra8UnormSrgb) |
| 79 | + }; |
| 80 | + |
| 81 | + let surface = instance |
| 82 | + .create_surface(window.clone()) |
| 83 | + .expect("Create surface"); |
| 84 | + let surface_config = SurfaceConfiguration { |
| 85 | + usage: TextureUsages::RENDER_ATTACHMENT, |
| 86 | + format: swapchain_format, |
| 87 | + width: physical_size.width, |
| 88 | + height: physical_size.height, |
| 89 | + present_mode: PresentMode::Fifo, |
| 90 | + alpha_mode: CompositeAlphaMode::Opaque, |
| 91 | + view_formats: vec![], |
| 92 | + desired_maximum_frame_latency: 2, |
| 93 | + }; |
| 94 | + surface.configure(&device, &surface_config); |
| 95 | + |
| 96 | + let logical_width = physical_size.width as f32 / scale_factor; |
| 97 | + |
| 98 | + // Set up text renderer |
| 99 | + let mut font_system = FontSystem::new(); |
| 100 | + let swash_cache = SwashCache::new(); |
| 101 | + let cache = Cache::new(&device); |
| 102 | + let viewport = Viewport::new(&device, &cache); |
| 103 | + let mut atlas = |
| 104 | + TextAtlas::with_color_mode(&device, &queue, &cache, swapchain_format, color_mode); |
| 105 | + let text_renderer = |
| 106 | + TextRenderer::new(&mut atlas, &device, MultisampleState::default(), None); |
| 107 | + |
| 108 | + let attrs = Attrs::new().family(Family::SansSerif).weight(WEIGHT); |
| 109 | + let shaping = Shaping::Advanced; |
| 110 | + |
| 111 | + let buffers: Vec<glyphon::Buffer> = SIZES |
| 112 | + .iter() |
| 113 | + .copied() |
| 114 | + .map(|s| { |
| 115 | + let mut text_buffer = |
| 116 | + Buffer::new(&mut font_system, Metrics::relative(s, LINE_HEIGHT)); |
| 117 | + |
| 118 | + text_buffer.set_size(&mut font_system, Some(logical_width - 20.0), None); |
| 119 | + |
| 120 | + text_buffer.set_text( |
| 121 | + &mut font_system, |
| 122 | + &format!("size {s}: {TEXT}"), |
| 123 | + attrs, |
| 124 | + shaping, |
| 125 | + ); |
| 126 | + |
| 127 | + text_buffer.shape_until_scroll(&mut font_system, false); |
| 128 | + |
| 129 | + text_buffer |
| 130 | + }) |
| 131 | + .collect(); |
| 132 | + |
| 133 | + Self { |
| 134 | + device, |
| 135 | + queue, |
| 136 | + surface, |
| 137 | + surface_config, |
| 138 | + physical_size: physical_size.cast(), |
| 139 | + scale_factor: scale_factor as f32, |
| 140 | + font_system, |
| 141 | + swash_cache, |
| 142 | + viewport, |
| 143 | + atlas, |
| 144 | + text_renderer, |
| 145 | + buffers, |
| 146 | + window, |
| 147 | + } |
| 148 | + } |
| 149 | +} |
| 150 | + |
| 151 | +struct Application { |
| 152 | + window_state: Option<WindowState>, |
| 153 | +} |
| 154 | + |
| 155 | +impl winit::application::ApplicationHandler for Application { |
| 156 | + fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { |
| 157 | + if self.window_state.is_some() { |
| 158 | + return; |
| 159 | + } |
| 160 | + |
| 161 | + // Set up window |
| 162 | + let (width, height) = (800, 600); |
| 163 | + let window_attributes = Window::default_attributes() |
| 164 | + .with_inner_size(LogicalSize::new(width as f64, height as f64)) |
| 165 | + .with_title("glyphon text sizes test"); |
| 166 | + let window = Arc::new(event_loop.create_window(window_attributes).unwrap()); |
| 167 | + |
| 168 | + self.window_state = Some(pollster::block_on(WindowState::new(window))); |
| 169 | + } |
| 170 | + |
| 171 | + fn window_event( |
| 172 | + &mut self, |
| 173 | + event_loop: &winit::event_loop::ActiveEventLoop, |
| 174 | + _window_id: winit::window::WindowId, |
| 175 | + event: WindowEvent, |
| 176 | + ) { |
| 177 | + let Some(state) = &mut self.window_state else { |
| 178 | + return; |
| 179 | + }; |
| 180 | + |
| 181 | + let WindowState { |
| 182 | + window, |
| 183 | + device, |
| 184 | + queue, |
| 185 | + surface, |
| 186 | + surface_config, |
| 187 | + font_system, |
| 188 | + swash_cache, |
| 189 | + viewport, |
| 190 | + atlas, |
| 191 | + text_renderer, |
| 192 | + buffers, |
| 193 | + scale_factor, |
| 194 | + physical_size, |
| 195 | + .. |
| 196 | + } = state; |
| 197 | + |
| 198 | + match event { |
| 199 | + WindowEvent::Resized(size) => { |
| 200 | + surface_config.width = size.width; |
| 201 | + surface_config.height = size.height; |
| 202 | + surface.configure(&device, &surface_config); |
| 203 | + window.request_redraw(); |
| 204 | + |
| 205 | + *scale_factor = window.scale_factor() as f32; |
| 206 | + *physical_size = size.cast(); |
| 207 | + |
| 208 | + let logical_width = size.width as f32 / *scale_factor; |
| 209 | + |
| 210 | + for b in buffers.iter_mut() { |
| 211 | + b.set_size(font_system, Some(logical_width - 20.0), None); |
| 212 | + b.shape_until_scroll(font_system, false); |
| 213 | + } |
| 214 | + } |
| 215 | + WindowEvent::RedrawRequested => { |
| 216 | + viewport.update( |
| 217 | + &queue, |
| 218 | + Resolution { |
| 219 | + width: surface_config.width, |
| 220 | + height: surface_config.height, |
| 221 | + }, |
| 222 | + ); |
| 223 | + |
| 224 | + let scale_factor = *scale_factor; |
| 225 | + |
| 226 | + let left = 10.0 * scale_factor; |
| 227 | + let mut top = 10.0 * scale_factor; |
| 228 | + |
| 229 | + let bounds_left = left.floor() as i32; |
| 230 | + let bounds_right = physical_size.width - 10; |
| 231 | + |
| 232 | + let text_areas: Vec<TextArea> = buffers |
| 233 | + .iter() |
| 234 | + .map(|b| { |
| 235 | + let a = TextArea { |
| 236 | + buffer: b, |
| 237 | + left, |
| 238 | + top, |
| 239 | + scale: scale_factor, |
| 240 | + bounds: TextBounds { |
| 241 | + left: bounds_left, |
| 242 | + top: top.floor() as i32, |
| 243 | + right: bounds_right, |
| 244 | + bottom: top.floor() as i32 + physical_size.height, |
| 245 | + }, |
| 246 | + default_color: FONT_COLOR, |
| 247 | + }; |
| 248 | + |
| 249 | + let total_lines = b |
| 250 | + .layout_runs() |
| 251 | + .fold(0usize, |total_lines, _| total_lines + 1); |
| 252 | + |
| 253 | + top += (total_lines as f32 * b.metrics().line_height + 5.0) * scale_factor; |
| 254 | + |
| 255 | + a |
| 256 | + }) |
| 257 | + .collect(); |
| 258 | + |
| 259 | + text_renderer |
| 260 | + .prepare( |
| 261 | + device, |
| 262 | + queue, |
| 263 | + font_system, |
| 264 | + atlas, |
| 265 | + viewport, |
| 266 | + text_areas, |
| 267 | + swash_cache, |
| 268 | + ) |
| 269 | + .unwrap(); |
| 270 | + |
| 271 | + let frame = surface.get_current_texture().unwrap(); |
| 272 | + let view = frame.texture.create_view(&TextureViewDescriptor::default()); |
| 273 | + let mut encoder = |
| 274 | + device.create_command_encoder(&CommandEncoderDescriptor { label: None }); |
| 275 | + { |
| 276 | + let mut pass = encoder.begin_render_pass(&RenderPassDescriptor { |
| 277 | + label: None, |
| 278 | + color_attachments: &[Some(RenderPassColorAttachment { |
| 279 | + view: &view, |
| 280 | + resolve_target: None, |
| 281 | + ops: Operations { |
| 282 | + load: LoadOp::Clear(BG_COLOR), |
| 283 | + store: wgpu::StoreOp::Store, |
| 284 | + }, |
| 285 | + })], |
| 286 | + depth_stencil_attachment: None, |
| 287 | + timestamp_writes: None, |
| 288 | + occlusion_query_set: None, |
| 289 | + }); |
| 290 | + |
| 291 | + text_renderer.render(&atlas, &viewport, &mut pass).unwrap(); |
| 292 | + } |
| 293 | + |
| 294 | + queue.submit(Some(encoder.finish())); |
| 295 | + frame.present(); |
| 296 | + |
| 297 | + atlas.trim(); |
| 298 | + } |
| 299 | + WindowEvent::CloseRequested => event_loop.exit(), |
| 300 | + _ => {} |
| 301 | + } |
| 302 | + } |
| 303 | +} |
0 commit comments