1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3use std::sync::Arc;
4
5use anyhow::{Context, Result};
6use wgpu::util::DeviceExt;
7use winit::window::Window;
8
9use crate::camera::{Camera2D, CameraUniform};
10use crate::camera3d::{Camera, Camera3D};
11use crate::frame::Frame;
12use crate::mesh::{MeshBatcher, MeshId, MeshInstance, MeshRegistry, MeshVertex};
13use crate::primitives::PrimitiveBatcher;
14use crate::sprite::SpriteBatcher;
15use crate::target::Targets;
16use crate::text::TextSystem;
17use crate::texture::{TextureId, TextureRegistry};
18#[cfg(feature = "vector")]
19use crate::vector::VectorPass;
20
21pub struct Graphics {
22 pub(crate) device: wgpu::Device,
23 pub(crate) queue: wgpu::Queue,
24 surface: wgpu::Surface<'static>,
25 surface_config: wgpu::SurfaceConfiguration,
26 window: Arc<Window>,
27 pub camera: Camera2D,
28 camera_buf: wgpu::Buffer,
29 pub(crate) camera_bg: wgpu::BindGroup,
30 pub(crate) sprites: SpriteBatcher,
31 pub(crate) primitives: PrimitiveBatcher,
32 pub(crate) text: TextSystem,
33 pub(crate) textures: TextureRegistry,
34 pub(crate) clear_color: [f32; 4],
35 texture_paths: HashMap<PathBuf, TextureId>,
36 sample_count: u32,
37 depth_format: Option<wgpu::TextureFormat>,
38 msaa_view: Option<wgpu::TextureView>,
40 depth_view: Option<wgpu::TextureView>,
42 pub camera3d: Camera3D,
44 camera3d_buf: wgpu::Buffer,
45 camera3d_bg: wgpu::BindGroup,
46 meshes: MeshRegistry,
47 mesh_batcher: MeshBatcher,
48 #[cfg(feature = "vector")]
50 pub(crate) vector: VectorPass,
51 capture_copy_src: bool,
53 pending_screenshot: Option<PathBuf>,
55}
56
57impl Graphics {
58 pub async fn new(
59 window: Arc<Window>,
60 vsync: bool,
61 depth_format: Option<wgpu::TextureFormat>,
62 msaa_samples: u32,
63 ) -> Result<Self> {
64 let size = window.inner_size();
65 let (width, height) = (size.width.max(1), size.height.max(1));
66
67 let mut idesc = wgpu::InstanceDescriptor::new_without_display_handle();
68 idesc.backends = wgpu::Backends::PRIMARY;
69 let instance = wgpu::Instance::new(idesc);
70 let surface = instance
71 .create_surface(window.clone())
72 .context("create_surface")?;
73 let adapter = instance
74 .request_adapter(&wgpu::RequestAdapterOptions {
75 power_preference: wgpu::PowerPreference::HighPerformance,
76 compatible_surface: Some(&surface),
77 force_fallback_adapter: false,
78 })
79 .await
80 .context("no compatible adapter")?;
81
82 let required_limits = if cfg!(feature = "vector") {
84 wgpu::Limits::default().using_resolution(adapter.limits())
85 } else {
86 wgpu::Limits::downlevel_defaults().using_resolution(adapter.limits())
87 };
88 let (device, queue) = adapter
89 .request_device(&wgpu::DeviceDescriptor {
90 label: Some("toolkit.device"),
91 required_features: wgpu::Features::empty(),
92 required_limits,
93 memory_hints: wgpu::MemoryHints::Performance,
94 ..Default::default()
95 })
96 .await
97 .context("request_device")?;
98
99 let caps = surface.get_capabilities(&adapter);
100 let format = caps
101 .formats
102 .iter()
103 .copied()
104 .find(|f| f.is_srgb())
105 .unwrap_or(caps.formats[0]);
106 let present_mode = if vsync {
107 wgpu::PresentMode::AutoVsync
108 } else {
109 wgpu::PresentMode::AutoNoVsync
110 };
111 let capture_copy_src = caps.usages.contains(wgpu::TextureUsages::COPY_SRC);
114 let surface_usage = if capture_copy_src {
115 wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC
116 } else {
117 wgpu::TextureUsages::RENDER_ATTACHMENT
118 };
119 let surface_config = wgpu::SurfaceConfiguration {
120 usage: surface_usage,
121 format,
122 width,
123 height,
124 present_mode,
125 desired_maximum_frame_latency: 2,
126 alpha_mode: caps.alpha_modes[0],
127 view_formats: vec![],
128 };
129 surface.configure(&device, &surface_config);
130
131 let camera = Camera2D::new(width as f32, height as f32);
132 let camera_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
133 label: Some("camera.buf"),
134 contents: bytemuck::bytes_of(&CameraUniform {
135 view_proj: camera.view_proj(),
136 }),
137 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
138 });
139 let camera_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
140 label: Some("camera.bgl"),
141 entries: &[wgpu::BindGroupLayoutEntry {
142 binding: 0,
143 visibility: wgpu::ShaderStages::VERTEX,
144 ty: wgpu::BindingType::Buffer {
145 ty: wgpu::BufferBindingType::Uniform,
146 has_dynamic_offset: false,
147 min_binding_size: None,
148 },
149 count: None,
150 }],
151 });
152 let camera_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
153 label: Some("camera.bg"),
154 layout: &camera_bgl,
155 entries: &[wgpu::BindGroupEntry {
156 binding: 0,
157 resource: camera_buf.as_entire_binding(),
158 }],
159 });
160
161 let camera3d = Camera3D::new(width as f32 / height.max(1) as f32);
162 let camera3d_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
163 label: Some("camera3d.buf"),
164 contents: bytemuck::bytes_of(&CameraUniform {
165 view_proj: camera3d.view_proj(),
166 }),
167 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
168 });
169 let camera3d_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
170 label: Some("camera3d.bg"),
171 layout: &camera_bgl,
172 entries: &[wgpu::BindGroupEntry {
173 binding: 0,
174 resource: camera3d_buf.as_entire_binding(),
175 }],
176 });
177
178 let sample_count = msaa_samples.max(1);
179 let textures = TextureRegistry::new(&device, &queue);
180 let sprites = SpriteBatcher::new(
181 &device,
182 format,
183 &camera_bgl,
184 &textures.layout,
185 sample_count,
186 depth_format,
187 );
188 let primitives =
189 PrimitiveBatcher::new(&device, format, &camera_bgl, sample_count, depth_format);
190 let text = TextSystem::new(
191 &device,
192 &queue,
193 format,
194 width,
195 height,
196 sample_count,
197 depth_format,
198 );
199 let (msaa_view, depth_view) =
200 make_attachments(&device, &surface_config, sample_count, depth_format);
201 let meshes = MeshRegistry::new();
202 let mesh_batcher =
203 MeshBatcher::new(&device, format, &camera_bgl, sample_count, depth_format);
204 #[cfg(feature = "vector")]
205 let vector = VectorPass::new(&device, format, width, height);
206
207 Ok(Self {
208 device,
209 queue,
210 surface,
211 surface_config,
212 window,
213 camera,
214 camera_buf,
215 camera_bg,
216 sprites,
217 primitives,
218 text,
219 textures,
220 clear_color: [0.0, 0.0, 0.0, 1.0],
221 texture_paths: HashMap::new(),
222 sample_count,
223 depth_format,
224 msaa_view,
225 depth_view,
226 camera3d,
227 camera3d_buf,
228 camera3d_bg,
229 meshes,
230 mesh_batcher,
231 #[cfg(feature = "vector")]
232 vector,
233 capture_copy_src,
234 pending_screenshot: None,
235 })
236 }
237
238 pub fn resize(&mut self, width: u32, height: u32) {
239 if width == 0 || height == 0 {
240 return;
241 }
242 self.surface_config.width = width;
243 self.surface_config.height = height;
244 self.surface.configure(&self.device, &self.surface_config);
245 let (msaa_view, depth_view) = make_attachments(
246 &self.device,
247 &self.surface_config,
248 self.sample_count,
249 self.depth_format,
250 );
251 self.msaa_view = msaa_view;
252 self.depth_view = depth_view;
253 self.camera.resize(width as f32, height as f32);
254 self.camera3d.resize(width as f32, height as f32);
255 self.text.resize(&self.queue, width, height);
256 #[cfg(feature = "vector")]
257 self.vector.resize(&self.device, width, height);
258 }
259
260 pub fn window(&self) -> &Arc<Window> {
261 &self.window
262 }
263
264 pub fn size(&self) -> (u32, u32) {
265 (self.surface_config.width, self.surface_config.height)
266 }
267
268 pub fn surface_format(&self) -> wgpu::TextureFormat {
269 self.surface_config.format
270 }
271
272 pub fn device(&self) -> &wgpu::Device {
273 &self.device
274 }
275 pub fn queue(&self) -> &wgpu::Queue {
276 &self.queue
277 }
278
279 pub fn load_texture(&mut self, path: impl AsRef<Path>) -> Result<TextureId> {
280 let path = path.as_ref();
281 let id = self.textures.load_file(&self.device, &self.queue, path)?;
282 let canon = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
283 self.texture_paths.insert(canon, id);
284 Ok(id)
285 }
286
287 pub fn refresh_textures<I, P>(&mut self, changed_paths: I) -> usize
290 where
291 I: IntoIterator<Item = P>,
292 P: AsRef<Path>,
293 {
294 let mut n = 0;
295 for p in changed_paths {
296 let canon = p
297 .as_ref()
298 .canonicalize()
299 .unwrap_or_else(|_| p.as_ref().to_path_buf());
300 if let Some(&id) = self.texture_paths.get(&canon) {
301 match self.textures.reload(&self.device, &self.queue, id, &canon) {
302 Ok(()) => n += 1,
303 Err(e) => log::warn!("reload {} failed: {e:?}", canon.display()),
304 }
305 }
306 }
307 n
308 }
309
310 pub fn create_texture_rgba(
311 &mut self,
312 width: u32,
313 height: u32,
314 rgba: &[u8],
315 label: Option<&str>,
316 ) -> TextureId {
317 self.textures
318 .create_from_rgba(&self.device, &self.queue, width, height, rgba, label)
319 }
320
321 pub fn white_texture(&self) -> TextureId {
322 self.textures.white()
323 }
324
325 pub fn create_mesh(&mut self, vertices: &[MeshVertex], indices: &[u16]) -> MeshId {
328 self.meshes.create(&self.device, vertices, indices)
329 }
330
331 pub fn draw_mesh(&mut self, mesh: MeshId, instance: MeshInstance) {
334 self.mesh_batcher.push(mesh, instance);
335 }
336
337 pub fn request_screenshot(&mut self, path: impl Into<PathBuf>) {
340 if !self.capture_copy_src {
341 log::warn!("screenshot unsupported: surface does not allow COPY_SRC read-back");
342 return;
343 }
344 self.pending_screenshot = Some(path.into());
345 }
346
347 pub fn begin_frame(&mut self) -> Result<Frame> {
348 self.queue.write_buffer(
349 &self.camera_buf,
350 0,
351 bytemuck::bytes_of(&CameraUniform {
352 view_proj: self.camera.view_proj(),
353 }),
354 );
355 self.queue.write_buffer(
356 &self.camera3d_buf,
357 0,
358 bytemuck::bytes_of(&CameraUniform {
359 view_proj: self.camera3d.view_proj(),
360 }),
361 );
362
363 let surface_texture = match self.surface.get_current_texture() {
364 wgpu::CurrentSurfaceTexture::Success(t)
365 | wgpu::CurrentSurfaceTexture::Suboptimal(t) => t,
366 other => anyhow::bail!("surface unavailable: {other:?}"),
367 };
368 let view = surface_texture
369 .texture
370 .create_view(&wgpu::TextureViewDescriptor::default());
371 let encoder = self
372 .device
373 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
374 label: Some("frame.encoder"),
375 });
376 Ok(Frame {
377 encoder: Some(encoder),
378 view,
379 surface_texture: Some(surface_texture),
380 clear_color: self.clear_color,
381 flushed: false,
382 })
383 }
384
385 pub fn present(&mut self, mut frame: Frame) {
386 if !frame.flushed {
387 self.flush_into(&mut frame);
388 }
389
390 if let Some(encoder) = frame.encoder.take() {
391 self.queue.submit(std::iter::once(encoder.finish()));
392 }
393
394 let shot_path = self.pending_screenshot.take();
397 if let (Some(path), Some(st)) = (&shot_path, frame.surface_texture.as_ref()) {
398 self.save_screenshot(&st.texture, path);
399 }
400
401 if let Some(st) = frame.surface_texture.take() {
402 self.window.pre_present_notify();
403 st.present();
404 }
405 }
406
407 fn save_screenshot(&self, texture: &wgpu::Texture, path: &Path) {
411 match miniscreenshot_wgpu::capture(&self.device, &self.queue, texture) {
412 Ok(shot) => match shot.save(path) {
413 Ok(()) => log::info!("saved screenshot {}", path.display()),
414 Err(e) => log::warn!("screenshot save failed: {e}"),
415 },
416 Err(e) => log::warn!("screenshot capture failed: {e}"),
417 }
418 }
419
420 pub fn flush_pending(&mut self, frame: &mut Frame) {
424 self.flush_into(frame);
425 }
426
427 pub(crate) fn flush_into(&mut self, frame: &mut Frame) {
428 if frame.flushed {
429 return;
430 }
431 let Some(encoder) = frame.encoder.as_mut() else {
432 return;
433 };
434 let mut layers = std::collections::BTreeSet::new();
438 self.sprites.collect_layers(&mut layers);
439 self.primitives.collect_layers(&mut layers);
440
441 self.sprites.upload(&self.device, &self.queue);
445 self.primitives.upload(&self.device, &self.queue);
446
447 let (color, resolve) = match self.msaa_view.as_ref() {
450 Some(msaa) => (msaa, Some(&frame.view)),
451 None => (&frame.view, None),
452 };
453 let targets = Targets {
454 color,
455 resolve,
456 depth: self.depth_view.as_ref(),
457 };
458
459 let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
461 label: Some("clear.pass"),
462 color_attachments: &[Some(targets.color_attachment(wgpu::LoadOp::Clear(
463 wgpu::Color {
464 r: frame.clear_color[0] as f64,
465 g: frame.clear_color[1] as f64,
466 b: frame.clear_color[2] as f64,
467 a: frame.clear_color[3] as f64,
468 },
469 )))],
470 depth_stencil_attachment: targets.depth_attachment(wgpu::LoadOp::Clear(1.0)),
471 occlusion_query_set: None,
472 timestamp_writes: None,
473 multiview_mask: None,
474 });
475
476 self.mesh_batcher.draw(
479 &self.device,
480 &self.queue,
481 &self.meshes,
482 encoder,
483 &targets,
484 &self.camera3d_bg,
485 );
486
487 for &layer in &layers {
488 self.sprites
489 .draw_layer(layer, encoder, &targets, &self.camera_bg, &self.textures);
490 self.primitives
491 .draw_layer(layer, encoder, &targets, &self.camera_bg);
492 }
493
494 self.text
495 .flush(&self.device, &self.queue, encoder, &targets);
496
497 #[cfg(feature = "vector")]
499 self.vector
500 .render_and_composite(&self.device, &self.queue, encoder, &frame.view);
501
502 self.sprites.clear();
503 self.primitives.clear();
504 self.mesh_batcher.clear();
505 frame.flushed = true;
506 }
507}
508
509fn make_attachments(
512 device: &wgpu::Device,
513 config: &wgpu::SurfaceConfiguration,
514 sample_count: u32,
515 depth_format: Option<wgpu::TextureFormat>,
516) -> (Option<wgpu::TextureView>, Option<wgpu::TextureView>) {
517 let size = wgpu::Extent3d {
518 width: config.width,
519 height: config.height,
520 depth_or_array_layers: 1,
521 };
522 let msaa_view = (sample_count > 1).then(|| {
523 device
524 .create_texture(&wgpu::TextureDescriptor {
525 label: Some("msaa.color"),
526 size,
527 mip_level_count: 1,
528 sample_count,
529 dimension: wgpu::TextureDimension::D2,
530 format: config.format,
531 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
532 view_formats: &[],
533 })
534 .create_view(&wgpu::TextureViewDescriptor::default())
535 });
536 let depth_view = depth_format.map(|format| {
537 device
538 .create_texture(&wgpu::TextureDescriptor {
539 label: Some("depth"),
540 size,
541 mip_level_count: 1,
542 sample_count,
543 dimension: wgpu::TextureDimension::D2,
544 format,
545 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
546 view_formats: &[],
547 })
548 .create_view(&wgpu::TextureViewDescriptor::default())
549 });
550 (msaa_view, depth_view)
551}