Skip to main content

game_toolkit_gfx/
graphics.rs

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::shapes::ShapeBatcher;
15use crate::sprite::SpriteBatcher;
16use crate::target::Targets;
17use crate::text::TextSystem;
18use crate::texture::{TextureId, TextureRegistry, TextureWrap};
19#[cfg(feature = "vector")]
20use crate::vector::VectorPass;
21
22pub struct Graphics {
23    pub(crate) device: wgpu::Device,
24    pub(crate) queue: wgpu::Queue,
25    surface: wgpu::Surface<'static>,
26    surface_config: wgpu::SurfaceConfiguration,
27    window: Arc<Window>,
28    pub camera: Camera2D,
29    camera_buf: wgpu::Buffer,
30    pub(crate) camera_bg: wgpu::BindGroup,
31    pub(crate) sprites: SpriteBatcher,
32    pub(crate) primitives: PrimitiveBatcher,
33    pub(crate) shapes: ShapeBatcher,
34    pub(crate) text: TextSystem,
35    pub(crate) textures: TextureRegistry,
36    pub(crate) clear_color: [f32; 4],
37    texture_paths: HashMap<PathBuf, TextureId>,
38    sample_count: u32,
39    depth_format: Option<wgpu::TextureFormat>,
40    /// Multisampled color target (resolved to the surface) when `sample_count > 1`.
41    msaa_view: Option<wgpu::TextureView>,
42    /// Depth attachment when `depth_format` is set. Recreated on resize.
43    depth_view: Option<wgpu::TextureView>,
44    /// Perspective camera for 3D meshes. Set its fields to move the view.
45    pub camera3d: Camera3D,
46    camera3d_buf: wgpu::Buffer,
47    camera3d_bg: wgpu::BindGroup,
48    meshes: MeshRegistry,
49    mesh_batcher: MeshBatcher,
50    /// Vello vector backend; composites over the 2D layers each frame. `None` on adapters
51    /// without compute shaders (WebGL2), where vector drawing is skipped.
52    #[cfg(feature = "vector")]
53    pub(crate) vector: Option<VectorPass>,
54    /// Whether the surface supports `COPY_SRC` (required to read frames back for screenshots).
55    capture_copy_src: bool,
56    /// Path to write a screenshot of the next presented frame, set by `request_screenshot`.
57    pending_screenshot: Option<PathBuf>,
58}
59
60impl Graphics {
61    pub async fn new(
62        window: Arc<Window>,
63        vsync: bool,
64        depth_format: Option<wgpu::TextureFormat>,
65        msaa_samples: u32,
66    ) -> Result<Self> {
67        let size = window.inner_size();
68        let (width, height) = (size.width.max(1), size.height.max(1));
69
70        let mut idesc = wgpu::InstanceDescriptor::new_without_display_handle();
71        // Web: WebGPU where the browser has it, WebGL2 otherwise. Native: Vulkan/Metal/DX12.
72        idesc.backends = if cfg!(target_arch = "wasm32") {
73            wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL
74        } else {
75            wgpu::Backends::PRIMARY
76        };
77        // A browser may expose `navigator.gpu` yet hand out no adapter (headless Chrome, blocked
78        // GPUs). This probes for a real WebGPU adapter and drops to WebGL2 when there is none;
79        // natively it is a plain `Instance::new`.
80        let instance = wgpu::util::new_instance_with_webgpu_detection(idesc).await;
81        let surface = instance
82            .create_surface(window.clone())
83            .context("create_surface")?;
84        let adapter = instance
85            .request_adapter(&wgpu::RequestAdapterOptions {
86                power_preference: wgpu::PowerPreference::HighPerformance,
87                compatible_surface: Some(&surface),
88                force_fallback_adapter: false,
89                apply_limit_buckets: false,
90            })
91            .await
92            .context("no compatible adapter")?;
93
94        let backend = adapter.get_info().backend;
95        log::info!("wgpu adapter: {:?} ({backend:?})", adapter.get_info().name);
96        // WebGL2 can't meet even the downlevel limits (and has no compute, so no vello).
97        // Otherwise vello needs the standard (non-downlevel) limits; bump them when it's on.
98        let required_limits = if backend == wgpu::Backend::Gl {
99            wgpu::Limits::downlevel_webgl2_defaults()
100        } else if cfg!(feature = "vector") {
101            wgpu::Limits::default()
102        } else {
103            wgpu::Limits::downlevel_defaults()
104        }
105        .using_resolution(adapter.limits());
106        let (device, queue) = adapter
107            .request_device(&wgpu::DeviceDescriptor {
108                label: Some("toolkit.device"),
109                required_features: wgpu::Features::empty(),
110                required_limits,
111                memory_hints: wgpu::MemoryHints::Performance,
112                ..Default::default()
113            })
114            .await
115            .context("request_device")?;
116
117        let caps = surface.get_capabilities(&adapter);
118        let surface_format = caps
119            .formats
120            .iter()
121            .copied()
122            .find(wgpu::TextureFormat::is_srgb)
123            .unwrap_or(caps.formats[0]);
124        // Always render through an sRGB view, so shaders write linear color and the hardware
125        // encodes it, the same on every platform. Surfaces without an sRGB format (WebGPU
126        // canvases) still allow the sRGB variant as a view format.
127        let format = surface_format.add_srgb_suffix();
128        let present_mode = if vsync {
129            wgpu::PresentMode::AutoVsync
130        } else {
131            wgpu::PresentMode::AutoNoVsync
132        };
133        // Add COPY_SRC when the surface supports it so frames can be read back for
134        // screenshots; fall back to render-only otherwise (screenshots become a no-op).
135        let capture_copy_src = caps.usages.contains(wgpu::TextureUsages::COPY_SRC);
136        let surface_usage = if capture_copy_src {
137            wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC
138        } else {
139            wgpu::TextureUsages::RENDER_ATTACHMENT
140        };
141        let surface_config = wgpu::SurfaceConfiguration {
142            usage: surface_usage,
143            format: surface_format,
144            width,
145            height,
146            present_mode,
147            desired_maximum_frame_latency: 2,
148            alpha_mode: caps.alpha_modes[0],
149            view_formats: if format == surface_format {
150                vec![]
151            } else {
152                vec![format]
153            },
154            color_space: wgpu::SurfaceColorSpace::Auto,
155        };
156        surface.configure(&device, &surface_config);
157
158        let camera = Camera2D::new(width as f32, height as f32);
159        let camera_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
160            label: Some("camera.buf"),
161            contents: bytemuck::bytes_of(&CameraUniform {
162                view_proj: camera.view_proj(),
163            }),
164            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
165        });
166        let camera_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
167            label: Some("camera.bgl"),
168            entries: &[wgpu::BindGroupLayoutEntry {
169                binding: 0,
170                visibility: wgpu::ShaderStages::VERTEX,
171                ty: wgpu::BindingType::Buffer {
172                    ty: wgpu::BufferBindingType::Uniform,
173                    has_dynamic_offset: false,
174                    min_binding_size: None,
175                },
176                count: None,
177            }],
178        });
179        let camera_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
180            label: Some("camera.bg"),
181            layout: &camera_bgl,
182            entries: &[wgpu::BindGroupEntry {
183                binding: 0,
184                resource: camera_buf.as_entire_binding(),
185            }],
186        });
187
188        let camera3d = Camera3D::new(width as f32 / height.max(1) as f32);
189        let camera3d_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
190            label: Some("camera3d.buf"),
191            contents: bytemuck::bytes_of(&CameraUniform {
192                view_proj: camera3d.view_proj(),
193            }),
194            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
195        });
196        let camera3d_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
197            label: Some("camera3d.bg"),
198            layout: &camera_bgl,
199            entries: &[wgpu::BindGroupEntry {
200                binding: 0,
201                resource: camera3d_buf.as_entire_binding(),
202            }],
203        });
204
205        let sample_count = msaa_samples.max(1);
206        let textures = TextureRegistry::new(&device, &queue);
207        let sprites = SpriteBatcher::new(
208            &device,
209            format,
210            &camera_bgl,
211            &textures.layout,
212            sample_count,
213            depth_format,
214        );
215        let primitives =
216            PrimitiveBatcher::new(&device, format, &camera_bgl, sample_count, depth_format);
217        let shapes = ShapeBatcher::new(
218            &device,
219            format,
220            &camera_bgl,
221            &textures.layout,
222            sample_count,
223            depth_format,
224        );
225        let text = TextSystem::new(
226            &device,
227            &queue,
228            format,
229            width,
230            height,
231            sample_count,
232            depth_format,
233        );
234        let (msaa_view, depth_view) =
235            make_attachments(&device, &surface_config, sample_count, depth_format);
236        let meshes = MeshRegistry::new();
237        let mesh_batcher =
238            MeshBatcher::new(&device, format, &camera_bgl, sample_count, depth_format);
239        #[cfg(feature = "vector")]
240        let vector = if adapter
241            .get_downlevel_capabilities()
242            .flags
243            .contains(wgpu::DownlevelFlags::COMPUTE_SHADERS)
244        {
245            Some(VectorPass::new(&device, format, width, height))
246        } else {
247            log::error!("vector graphics disabled: adapter has no compute shaders ({backend:?})");
248            None
249        };
250
251        Ok(Self {
252            device,
253            queue,
254            surface,
255            surface_config,
256            window,
257            camera,
258            camera_buf,
259            camera_bg,
260            sprites,
261            primitives,
262            shapes,
263            text,
264            textures,
265            clear_color: [0.0, 0.0, 0.0, 1.0],
266            texture_paths: HashMap::new(),
267            sample_count,
268            depth_format,
269            msaa_view,
270            depth_view,
271            camera3d,
272            camera3d_buf,
273            camera3d_bg,
274            meshes,
275            mesh_batcher,
276            #[cfg(feature = "vector")]
277            vector,
278            capture_copy_src,
279            pending_screenshot: None,
280        })
281    }
282
283    pub fn resize(&mut self, width: u32, height: u32) {
284        if width == 0 || height == 0 {
285            return;
286        }
287        self.surface_config.width = width;
288        self.surface_config.height = height;
289        self.surface.configure(&self.device, &self.surface_config);
290        let (msaa_view, depth_view) = make_attachments(
291            &self.device,
292            &self.surface_config,
293            self.sample_count,
294            self.depth_format,
295        );
296        self.msaa_view = msaa_view;
297        self.depth_view = depth_view;
298        self.camera.resize(width as f32, height as f32);
299        self.camera3d.resize(width as f32, height as f32);
300        self.text.resize(&self.queue, width, height);
301        #[cfg(feature = "vector")]
302        if let Some(vector) = self.vector.as_mut() {
303            vector.resize(&self.device, width, height);
304        }
305    }
306
307    pub fn window(&self) -> &Arc<Window> {
308        &self.window
309    }
310
311    pub fn size(&self) -> (u32, u32) {
312        (self.surface_config.width, self.surface_config.height)
313    }
314
315    /// The color format the frame is rendered in: always an sRGB format, possibly a view of a
316    /// non-sRGB surface. Build render pipelines that draw into a [`Frame`] with this.
317    pub fn surface_format(&self) -> wgpu::TextureFormat {
318        render_format(&self.surface_config)
319    }
320
321    pub fn device(&self) -> &wgpu::Device {
322        &self.device
323    }
324    pub fn queue(&self) -> &wgpu::Queue {
325        &self.queue
326    }
327
328    /// Load a PNG from disk and remember its path for [`Graphics::refresh_textures`]. Not
329    /// available on the web; use [`Graphics::load_texture_bytes`] (or `ctx.load_texture`,
330    /// which picks the right one per platform).
331    #[cfg(not(target_arch = "wasm32"))]
332    pub fn load_texture(&mut self, path: impl AsRef<Path>) -> Result<TextureId> {
333        let path = path.as_ref();
334        let id = self.textures.load_file(&self.device, &self.queue, path)?;
335        let canon = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
336        self.texture_paths.insert(canon, id);
337        Ok(id)
338    }
339
340    /// Size `[width, height]` in pixels that `s` takes up drawn at `size_px` (the widest line,
341    /// and the height of all lines), for aligning text or sizing a box to fit. Uses the same
342    /// layout as drawing, so the result matches `Painter::text` exactly; measuring and then
343    /// drawing the same string shapes it once.
344    pub fn measure_text(&mut self, s: &str, size_px: f32) -> [f32; 2] {
345        self.text.measure(s, size_px)
346    }
347
348    /// Register an extra font face (TTF/OTF bytes) as a glyph fallback for text rendering.
349    pub fn load_font_bytes(&mut self, bytes: Vec<u8>) {
350        self.text.load_font_bytes(bytes);
351    }
352
353    /// Decode a PNG held in memory and upload it. `label` names the texture in GPU debuggers
354    /// and error messages.
355    pub fn load_texture_bytes(&mut self, bytes: &[u8], label: Option<&str>) -> Result<TextureId> {
356        self.textures
357            .load_bytes(&self.device, &self.queue, bytes, label)
358    }
359
360    /// Re-decode any texture whose backing file is in `changed_paths` and update its bind
361    /// group in place. Returns the number of textures actually reloaded.
362    pub fn refresh_textures<I, P>(&mut self, changed_paths: I) -> usize
363    where
364        I: IntoIterator<Item = P>,
365        P: AsRef<Path>,
366    {
367        let mut n = 0;
368        for p in changed_paths {
369            let canon = p
370                .as_ref()
371                .canonicalize()
372                .unwrap_or_else(|_| p.as_ref().to_path_buf());
373            if let Some(&id) = self.texture_paths.get(&canon) {
374                match self.textures.reload(&self.device, &self.queue, id, &canon) {
375                    Ok(()) => n += 1,
376                    Err(e) => log::warn!("reload {} failed: {e:?}", canon.display()),
377                }
378            }
379        }
380        n
381    }
382
383    pub fn create_texture_rgba(
384        &mut self,
385        width: u32,
386        height: u32,
387        rgba: &[u8],
388        label: Option<&str>,
389    ) -> TextureId {
390        self.textures
391            .create_from_rgba(&self.device, &self.queue, width, height, rgba, label)
392    }
393
394    /// Overwrite the pixels of an existing texture without reallocating it, for content that
395    /// changes every frame (a software-rendered framebuffer, a video frame). `rgba` must hold
396    /// `width * height * 4` bytes for the texture's size; create the texture once with
397    /// [`Self::create_texture_rgba`]. Unknown ids and size mismatches are logged and ignored.
398    pub fn update_texture_rgba(&mut self, id: TextureId, rgba: &[u8]) {
399        if !self.textures.write_rgba(&self.queue, id, rgba) {
400            log::warn!("update_texture_rgba: unknown {id:?} or wrong data size");
401        }
402    }
403
404    pub fn white_texture(&self) -> TextureId {
405        self.textures.white()
406    }
407
408    /// Choose what `id` shows for UVs outside `0..1`: [`TextureWrap::Clamp`] (the default for
409    /// every texture) stretches the edge texels, [`TextureWrap::Repeat`] and
410    /// [`TextureWrap::MirrorRepeat`] tile. Set `Repeat` on a texture to tile it across one
411    /// quad (`Painter::quad_textured` with UVs up to `[4.0, 10.0]`, a sprite with
412    /// [`crate::SpriteInstance::with_uv`] beyond `1.0`) instead of drawing a quad per tile.
413    /// Keep atlases on `Clamp`: their regions sample right up to the edge, and with wrapping
414    /// a UV of exactly `1.0` bleeds in the opposite edge. The mode survives hot-reloads.
415    /// Unknown ids are ignored.
416    pub fn set_texture_wrap(&mut self, id: TextureId, wrap: TextureWrap) {
417        if !self.textures.set_wrap(&self.device, id, wrap) {
418            log::warn!("set_texture_wrap: unknown {id:?}");
419        }
420    }
421
422    /// The [`TextureWrap`] mode of `id`, or `None` for an unknown id.
423    pub fn texture_wrap(&self, id: TextureId) -> Option<TextureWrap> {
424        self.textures.get(id).map(|t| t.wrap)
425    }
426
427    /// Pixel size `[width, height]` of a loaded texture, or `None` for an unknown id. Follows
428    /// hot-reloads, so it reports the size of the current contents.
429    pub fn texture_size(&self, id: TextureId) -> Option<[u32; 2]> {
430        self.textures.get(id).map(|t| t.size)
431    }
432
433    /// Upload a static mesh (positions + normals, indexed) and return its handle. Meshes are
434    /// drawn depth-tested via [`Graphics::draw_mesh`] using the perspective [`Self::camera3d`].
435    pub fn create_mesh(&mut self, vertices: &[MeshVertex], indices: &[u16]) -> MeshId {
436        self.meshes.create(&self.device, vertices, indices)
437    }
438
439    /// Queue one instance of `mesh` for this frame. Meshes render before the 2D layers, so
440    /// 2D sprites, primitives and text draw on top of them.
441    pub fn draw_mesh(&mut self, mesh: MeshId, instance: MeshInstance) {
442        self.mesh_batcher.push(mesh, instance);
443    }
444
445    /// Save a PNG of the next presented frame to `path`. The read-back stalls that frame, so
446    /// this is for debug/dev use. A no-op (with a warning) if the surface lacks `COPY_SRC`,
447    /// and on the web, where there is neither a blocking read-back nor a filesystem.
448    pub fn request_screenshot(&mut self, path: impl Into<PathBuf>) {
449        if cfg!(target_arch = "wasm32") {
450            log::warn!("screenshot unsupported on the web");
451            return;
452        }
453        if !self.capture_copy_src {
454            log::warn!("screenshot unsupported: surface does not allow COPY_SRC read-back");
455            return;
456        }
457        self.pending_screenshot = Some(path.into());
458    }
459
460    pub fn begin_frame(&mut self) -> Result<Frame> {
461        self.queue.write_buffer(
462            &self.camera_buf,
463            0,
464            bytemuck::bytes_of(&CameraUniform {
465                view_proj: self.camera.view_proj(),
466            }),
467        );
468        self.queue.write_buffer(
469            &self.camera3d_buf,
470            0,
471            bytemuck::bytes_of(&CameraUniform {
472                view_proj: self.camera3d.view_proj(),
473            }),
474        );
475
476        let surface_texture = match self.surface.get_current_texture() {
477            wgpu::CurrentSurfaceTexture::Success(t)
478            | wgpu::CurrentSurfaceTexture::Suboptimal(t) => t,
479            other => anyhow::bail!("surface unavailable: {other:?}"),
480        };
481        let view = surface_texture
482            .texture
483            .create_view(&wgpu::TextureViewDescriptor {
484                format: Some(render_format(&self.surface_config)),
485                ..Default::default()
486            });
487        let encoder = self
488            .device
489            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
490                label: Some("frame.encoder"),
491            });
492        Ok(Frame {
493            encoder: Some(encoder),
494            view,
495            surface_texture: Some(surface_texture),
496            clear_color: self.clear_color,
497            flushed: false,
498        })
499    }
500
501    pub fn present(&mut self, mut frame: Frame) {
502        if !frame.flushed {
503            self.flush_into(&mut frame);
504        }
505
506        if let Some(encoder) = frame.encoder.take() {
507            self.queue.submit(std::iter::once(encoder.finish()));
508        }
509
510        // Capture the just-submitted surface before presenting (the queue runs the read-back
511        // copy after the render submit, so it sees the finished frame).
512        let shot_path = self.pending_screenshot.take();
513        if let (Some(path), Some(st)) = (&shot_path, frame.surface_texture.as_ref()) {
514            self.save_screenshot(&st.texture, path);
515        }
516
517        if let Some(st) = frame.surface_texture.take() {
518            self.window.pre_present_notify();
519            self.queue.present(st);
520        }
521    }
522
523    #[cfg(target_arch = "wasm32")]
524    fn save_screenshot(&self, _texture: &wgpu::Texture, _path: &Path) {}
525
526    /// Read the surface texture back to the CPU and save it. Synchronous (blocks on a poll),
527    /// so only used for the occasional debug screenshot. `miniscreenshot-wgpu` handles the
528    /// staging copy, row-padding strip, and BGRA->RGBA conversion.
529    #[cfg(not(target_arch = "wasm32"))]
530    fn save_screenshot(&self, texture: &wgpu::Texture, path: &Path) {
531        match miniscreenshot_wgpu::capture(&self.device, &self.queue, texture) {
532            Ok(shot) => match shot.save(path) {
533                Ok(()) => log::info!("saved screenshot {}", path.display()),
534                Err(e) => log::warn!("screenshot save failed: {e}"),
535            },
536            Err(e) => log::warn!("screenshot capture failed: {e}"),
537        }
538    }
539
540    /// Flush all queued 2D layers (sprites + shapes + primitives + text) into the current `frame`.
541    /// Called automatically when a [`crate::Painter`] is dropped; call manually before
542    /// stacking other passes (e.g. egui) when no painter was created.
543    pub fn flush_pending(&mut self, frame: &mut Frame) {
544        self.flush_into(frame);
545    }
546
547    pub(crate) fn flush_into(&mut self, frame: &mut Frame) {
548        if frame.flushed {
549            return;
550        }
551        let Some(encoder) = frame.encoder.as_mut() else {
552            return;
553        };
554        // Interleave sprites, filled shapes, circles and layered text by layer so a circle on
555        // layer -1 draws under a sprite on layer 0; within a layer, sprites draw first, then
556        // shapes, then circles, then text. Unlayered text (`Painter::text`) draws on top.
557        let mut layers = std::collections::BTreeSet::new();
558        self.sprites.collect_layers(&mut layers);
559        self.shapes.collect_layers(&mut layers);
560        self.primitives.collect_layers(&mut layers);
561        self.text.collect_layers(&mut layers);
562
563        // Upload each batcher's instances exactly once before drawing: `queue.write_buffer`
564        // is not part of the encoder command stream, so writing per layer pass would clobber
565        // earlier layers' instance data.
566        self.sprites.upload(&self.device, &self.queue);
567        self.shapes.upload(&self.device, &self.queue);
568        self.primitives.upload(&self.device, &self.queue);
569        self.text.prepare(&self.device, &self.queue);
570
571        // When multisampling, draws target the MSAA texture and resolve to the surface;
572        // otherwise they target the surface directly.
573        let (color, resolve) = match self.msaa_view.as_ref() {
574            Some(msaa) => (msaa, Some(&frame.view)),
575            None => (&frame.view, None),
576        };
577        let targets = Targets {
578            color,
579            resolve,
580            depth: self.depth_view.as_ref(),
581        };
582
583        // Clear color (and depth) once up front; every layer pass then loads onto it.
584        let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
585            label: Some("clear.pass"),
586            color_attachments: &[Some(targets.color_attachment(wgpu::LoadOp::Clear(
587                wgpu::Color {
588                    // sRGB like every painter color; the clear value is linear.
589                    r: srgb_to_linear(frame.clear_color[0]),
590                    g: srgb_to_linear(frame.clear_color[1]),
591                    b: srgb_to_linear(frame.clear_color[2]),
592                    a: frame.clear_color[3] as f64,
593                },
594            )))],
595            depth_stencil_attachment: targets.depth_attachment(wgpu::LoadOp::Clear(1.0)),
596            occlusion_query_set: None,
597            timestamp_writes: None,
598            multiview_mask: None,
599        });
600
601        // 3D meshes draw first, depth-tested, so the 2D layers (which do not write depth)
602        // composite on top of them.
603        self.mesh_batcher.draw(
604            &self.device,
605            &self.queue,
606            &self.meshes,
607            encoder,
608            &targets,
609            &self.camera3d_bg,
610        );
611
612        for &layer in &layers {
613            self.sprites
614                .draw_layer(layer, encoder, &targets, &self.camera_bg, &self.textures);
615            self.shapes
616                .draw_layer(layer, encoder, &targets, &self.camera_bg, &self.textures);
617            self.primitives
618                .draw_layer(layer, encoder, &targets, &self.camera_bg);
619            self.text.draw_layer(Some(layer), encoder, &targets);
620        }
621
622        self.text.draw_layer(None, encoder, &targets);
623        self.text.finish();
624
625        // Vector content composites on top of the 2D layers, directly onto the surface.
626        #[cfg(feature = "vector")]
627        if let Some(vector) = self.vector.as_mut() {
628            vector.render_and_composite(&self.device, &self.queue, encoder, &frame.view);
629        }
630
631        self.sprites.clear();
632        self.shapes.clear();
633        self.primitives.clear();
634        self.mesh_batcher.clear();
635        frame.flushed = true;
636    }
637}
638
639/// Allocate the MSAA color target (when `sample_count > 1`) and depth target (when a depth
640/// format is set), both sized to the surface. Returns `(msaa_view, depth_view)`.
641fn make_attachments(
642    device: &wgpu::Device,
643    config: &wgpu::SurfaceConfiguration,
644    sample_count: u32,
645    depth_format: Option<wgpu::TextureFormat>,
646) -> (Option<wgpu::TextureView>, Option<wgpu::TextureView>) {
647    let size = wgpu::Extent3d {
648        width: config.width,
649        height: config.height,
650        depth_or_array_layers: 1,
651    };
652    let msaa_view = (sample_count > 1).then(|| {
653        device
654            .create_texture(&wgpu::TextureDescriptor {
655                label: Some("msaa.color"),
656                size,
657                mip_level_count: 1,
658                sample_count,
659                dimension: wgpu::TextureDimension::D2,
660                format: render_format(config),
661                usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
662                view_formats: &[],
663            })
664            .create_view(&wgpu::TextureViewDescriptor::default())
665    });
666    let depth_view = depth_format.map(|format| {
667        device
668            .create_texture(&wgpu::TextureDescriptor {
669                label: Some("depth"),
670                size,
671                mip_level_count: 1,
672                sample_count,
673                dimension: wgpu::TextureDimension::D2,
674                format,
675                usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
676                view_formats: &[],
677            })
678            .create_view(&wgpu::TextureViewDescriptor::default())
679    });
680    (msaa_view, depth_view)
681}
682
683/// The sRGB format frames are rendered in: the surface's own if it is sRGB, otherwise the
684/// sRGB view format `Graphics::new` added.
685fn render_format(config: &wgpu::SurfaceConfiguration) -> wgpu::TextureFormat {
686    if config.format.is_srgb() {
687        return config.format;
688    }
689    config
690        .view_formats
691        .iter()
692        .copied()
693        .find(wgpu::TextureFormat::is_srgb)
694        .unwrap_or(config.format)
695}
696
697/// Decode one sRGB color component to linear, as the shaders do for painter colors.
698fn srgb_to_linear(c: f32) -> f64 {
699    let c = f64::from(c);
700    if c <= 0.04045 {
701        c / 12.92
702    } else {
703        ((c + 0.055) / 1.055).powf(2.4)
704    }
705}