Skip to main content

game_toolkit_gfx/
custom.rs

1//! Game-recorded passes interleaved with the painter's layers ([`Painter::custom`]).
2//!
3//! [`Painter::custom`]: crate::Painter::custom
4
5use crate::target::{self, Targets};
6
7/// A pass queued with [`Painter::custom`](crate::Painter::custom), run when its layer draws.
8pub(crate) type CustomFn = Box<dyn FnOnce(&mut CustomPass<'_>)>;
9
10/// Queued custom passes, in call order per layer.
11#[derive(Default)]
12pub(crate) struct CustomPasses {
13    pending: Vec<(i16, CustomFn)>,
14}
15
16impl CustomPasses {
17    pub fn push(&mut self, layer: i16, f: CustomFn) {
18        self.pending.push((layer, f));
19    }
20
21    pub fn collect_layers(&self, out: &mut std::collections::BTreeSet<i16>) {
22        out.extend(self.pending.iter().map(|(layer, _)| *layer));
23    }
24
25    /// Take this layer's passes, in the order they were queued.
26    pub fn take_layer(&mut self, layer: i16) -> Vec<CustomFn> {
27        self.pending
28            .extract_if(.., |(l, _)| *l == layer)
29            .map(|(_, f)| f)
30            .collect()
31    }
32
33    pub fn clear(&mut self) {
34        self.pending.clear();
35    }
36}
37
38/// What a [`Painter::custom`](crate::Painter::custom) closure gets: the frame's encoder and
39/// the painter's render target at that point in the frame (the canvas, the window, or the
40/// MSAA texture resolving into either).
41///
42/// With indexed colour on ([`Self::is_indexed`]) the target holds palette indices: return
43/// `index_out(i)` from `INDEX_WGSL` (feature `indexed`) instead of a colour.
44///
45/// The easiest way to draw is [`Self::begin_render_pass`], which attaches the target the same
46/// way the painter does (load, store, MSAA resolve, depth). Build the pipeline for it with
47/// [`Self::format`], [`Self::multisample`] and [`Self::depth_stencil`], so it matches the
48/// attachments whatever `AppConfig::msaa_samples` and `depth_format` are.
49pub struct CustomPass<'a> {
50    pub device: &'a wgpu::Device,
51    pub queue: &'a wgpu::Queue,
52    pub encoder: &'a mut wgpu::CommandEncoder,
53    pub(crate) targets: Targets<'a>,
54    pub(crate) format: wgpu::TextureFormat,
55    pub(crate) sample_count: u32,
56    pub(crate) depth_format: Option<wgpu::TextureFormat>,
57    pub(crate) size: [u32; 2],
58    pub(crate) indexed: bool,
59}
60
61impl<'a> CustomPass<'a> {
62    /// A render pass onto the painter's target that keeps what is already there (the clear
63    /// colour and lower layers), with the MSAA resolve and depth attachment the painter uses.
64    pub fn begin_render_pass(&mut self, label: &str) -> wgpu::RenderPass<'_> {
65        self.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
66            label: Some(label),
67            color_attachments: &[Some(self.targets.color_attachment(wgpu::LoadOp::Load))],
68            depth_stencil_attachment: self.targets.depth_attachment(wgpu::LoadOp::Load),
69            occlusion_query_set: None,
70            timestamp_writes: None,
71            multiview_mask: None,
72        })
73    }
74
75    /// The colour view drawn into: the MSAA texture when multisampling, else the canvas (or
76    /// the window without one). For passes set up by hand; see [`Self::resolve_target`].
77    pub fn view(&self) -> &wgpu::TextureView {
78        self.targets.color
79    }
80
81    /// `Some(view)` when multisampling: the MSAA colour resolves into it.
82    pub fn resolve_target(&self) -> Option<&wgpu::TextureView> {
83        self.targets.resolve
84    }
85
86    /// The depth view, when `AppConfig::depth_format` is set.
87    pub fn depth_view(&self) -> Option<&wgpu::TextureView> {
88        self.targets.depth
89    }
90
91    /// The target's colour format (always sRGB; see `Graphics::surface_format`).
92    pub fn format(&self) -> wgpu::TextureFormat {
93        self.format
94    }
95
96    /// Multisample state matching the target, for the pipeline.
97    pub fn multisample(&self) -> wgpu::MultisampleState {
98        target::multisample(self.sample_count)
99    }
100
101    /// Depth-stencil state matching the target, for the pipeline: `None` without a depth
102    /// buffer, else one that neither tests nor writes depth (like the painter's 2D layers).
103    pub fn depth_stencil(&self) -> Option<wgpu::DepthStencilState> {
104        self.depth_format.map(target::no_write_depth)
105    }
106
107    /// Whether the target holds palette indices (indexed colour is on) rather than colours.
108    pub fn is_indexed(&self) -> bool {
109        self.indexed
110    }
111
112    /// Size of the target in pixels: the virtual resolution, else the window's size.
113    pub fn size(&self) -> [u32; 2] {
114        self.size
115    }
116}