nannou/frame/
mod.rs

1//! Items related to the **Frame** type, describing a single frame of graphics for a single window.
2
3use crate::color::IntoLinSrgba;
4use crate::wgpu;
5use std::ops;
6use std::path::PathBuf;
7use std::sync::Mutex;
8use std::time::Duration;
9
10pub mod raw;
11
12pub use self::raw::RawFrame;
13
14/// A **Frame** to which the user can draw graphics before it is presented to the display.
15///
16/// **Frame**s are delivered to the user for drawing via the user's **view** function.
17///
18/// See the **RawFrame** docs for more details on how the implementation works under the hood. The
19/// **Frame** type differs in that rather than drawing directly to the swapchain image the user may
20/// draw to an intermediary linear sRGBA image. There are several advantages of drawing to an
21/// intermediary image.
22pub struct Frame<'swap_chain> {
23    raw_frame: RawFrame<'swap_chain>,
24    render_data: &'swap_chain RenderData,
25    capture_data: &'swap_chain CaptureData,
26}
27
28/// Data specific to the intermediary textures.
29#[derive(Debug)]
30pub struct RenderData {
31    intermediary_lin_srgba: IntermediaryLinSrgba,
32    msaa_samples: u32,
33    size: [u32; 2],
34    // For writing the intermediary linear sRGBA texture to the swap chain texture.
35    texture_reshaper: wgpu::TextureReshaper,
36}
37
38/// Data related to the capturing of a frame.
39#[derive(Debug)]
40pub(crate) struct CaptureData {
41    // If `Some`, indicates a path to which the current frame should be written.
42    pub(crate) next_frame_path: Mutex<Option<PathBuf>>,
43    // The `TextureCapturer` used to capture the frame.
44    pub(crate) texture_capturer: wgpu::TextureCapturer,
45}
46
47/// Intermediary textures used as a target before resolving multisampling and writing to the
48/// swapchain texture.
49#[derive(Debug)]
50pub(crate) struct IntermediaryLinSrgba {
51    msaa_texture: Option<(wgpu::Texture, wgpu::TextureView)>,
52    texture: wgpu::Texture,
53    texture_view: wgpu::TextureView,
54}
55
56impl<'swap_chain> ops::Deref for Frame<'swap_chain> {
57    type Target = RawFrame<'swap_chain>;
58    fn deref(&self) -> &Self::Target {
59        &self.raw_frame
60    }
61}
62
63impl<'swap_chain> Frame<'swap_chain> {
64    /// The default number of multisample anti-aliasing samples used if the window with which the
65    /// `Frame` is associated supports it.
66    pub const DEFAULT_MSAA_SAMPLES: u32 = 4;
67    /// The texture format used by the intermediary linear sRGBA image.
68    ///
69    /// We use a high bit depth format in order to retain as much information as possible when
70    /// converting from the linear representation to the swapchain format (normally a non-linear
71    /// representation).
72    // TODO: Kvark recommends trying `Rgb10A2Unorm`.
73    pub const TEXTURE_FORMAT: wgpu::TextureFormat =
74        wgpu::RenderPipelineBuilder::DEFAULT_COLOR_FORMAT;
75
76    // Initialise a new empty frame ready for "drawing".
77    pub(crate) fn new_empty(
78        raw_frame: RawFrame<'swap_chain>,
79        render_data: &'swap_chain RenderData,
80        capture_data: &'swap_chain CaptureData,
81    ) -> Self {
82        Frame {
83            raw_frame,
84            render_data,
85            capture_data,
86        }
87    }
88
89    // The private implementation of `submit`, allowing it to be called during `drop` if submission
90    // has not yet occurred.
91    fn submit_inner(&mut self) {
92        let Frame {
93            ref capture_data,
94            ref render_data,
95            ref mut raw_frame,
96        } = *self;
97
98        // Resolve the MSAA if necessary.
99        if let Some((_, ref msaa_texture_view)) = render_data.intermediary_lin_srgba.msaa_texture {
100            let mut encoder = raw_frame.command_encoder();
101            wgpu::resolve_texture(
102                msaa_texture_view,
103                &render_data.intermediary_lin_srgba.texture_view,
104                &mut *encoder,
105            );
106        }
107
108        // Check to see if the user specified capturing the frame.
109        let mut snapshot_capture = None;
110        if let Ok(mut guard) = capture_data.next_frame_path.lock() {
111            if let Some(path) = guard.take() {
112                let device = raw_frame.device_queue_pair().device();
113                let mut encoder = raw_frame.command_encoder();
114                let snapshot = capture_data.texture_capturer.capture(
115                    device,
116                    &mut *encoder,
117                    &render_data.intermediary_lin_srgba.texture,
118                );
119                snapshot_capture = Some((path, snapshot));
120            }
121        }
122
123        // Convert the linear sRGBA image to the swapchain image.
124        //
125        // To do so, we sample the linear sRGBA image and draw it to the swapchain image using
126        // two triangles and a fragment shader.
127        {
128            let mut encoder = raw_frame.command_encoder();
129            render_data
130                .texture_reshaper
131                .encode_render_pass(raw_frame.swap_chain_texture(), &mut *encoder);
132        }
133
134        // Submit all commands on the device queue.
135        raw_frame.submit_inner();
136
137        // If the user did specify capturing the frame, submit the asynchronous read.
138        if let Some((path, snapshot)) = snapshot_capture {
139            let result = snapshot.read(move |result| match result {
140                // TODO: Log errors, don't print to stderr.
141                Err(e) => eprintln!("failed to async read captured frame: {:?}", e),
142                Ok(image) => {
143                    let image = image.to_owned();
144                    if let Err(e) = image.save(&path) {
145                        // TODO: Log errors, don't print to stderr.
146                        eprintln!(
147                            "failed to save captured frame to \"{}\": {}",
148                            path.display(),
149                            e
150                        );
151                    }
152                }
153            });
154            if let Err(wgpu::TextureCapturerAwaitWorkerTimeout(_)) = result {
155                // TODO: Log errors, don't print to stderr.
156                eprintln!("timed out while waiting for a worker thread to capture the frame");
157            }
158        }
159    }
160
161    /// The texture to which all graphics should be drawn this frame.
162    ///
163    /// This is **not** the swapchain texture, but rather an intermediary linear sRGBA image. This
164    /// intermediary image is used in order to:
165    ///
166    /// - Ensure consistent MSAA resolve behaviour across platforms.
167    /// - Avoid the need for multiple implicit conversions to and from linear sRGBA for each
168    /// graphics pipeline render pass that is used.
169    /// - Allow for the user's rendered image to persist between frames.
170    ///
171    /// The exact format of the texture is equal to `Frame::TEXTURE_FORMAT`.
172    ///
173    /// If the number of MSAA samples specified is greater than `1` (which it is by default if
174    /// supported by the platform), this will be a multisampled texture. After the **view**
175    /// function returns, this texture will be resolved to a non-multisampled linear sRGBA texture.
176    /// After the texture has been resolved if necessary, it will then be used as a shader input
177    /// within a graphics pipeline used to draw the swapchain texture.
178    pub fn texture(&self) -> &wgpu::Texture {
179        self.render_data
180            .intermediary_lin_srgba
181            .msaa_texture
182            .as_ref()
183            .map(|(tex, _)| tex)
184            .unwrap_or(&self.render_data.intermediary_lin_srgba.texture)
185    }
186
187    /// A full view into the frame's texture.
188    ///
189    /// See `texture` for details.
190    pub fn texture_view(&self) -> &wgpu::TextureView {
191        self.render_data.texture_view()
192    }
193
194    /// Returns the resolve target texture in the case that MSAA is enabled.
195    pub fn resolve_target(&self) -> Option<&wgpu::TextureView> {
196        if self.render_data.msaa_samples <= 1 {
197            None
198        } else {
199            Some(&self.render_data.intermediary_lin_srgba.texture_view)
200        }
201    }
202
203    /// The color format of the `Frame`'s intermediary linear sRGBA texture (equal to
204    /// `Frame::TEXTURE_FORMAT`).
205    pub fn texture_format(&self) -> wgpu::TextureFormat {
206        Self::TEXTURE_FORMAT
207    }
208
209    /// The number of MSAA samples of the `Frame`'s intermediary linear sRGBA texture.
210    pub fn texture_msaa_samples(&self) -> u32 {
211        self.render_data.msaa_samples
212    }
213
214    /// The size of the frame's texture in pixels.
215    pub fn texture_size(&self) -> [u32; 2] {
216        self.render_data.size
217    }
218
219    /// Short-hand for constructing a `wgpu::RenderPassColorAttachment` for use within a
220    /// render pass that targets this frame's texture. The returned descriptor's `attachment` will
221    /// the same `wgpu::TextureView` returned by the `Frame::texture` method.
222    ///
223    /// Note that this method will not perform any resolving. In the case that `msaa_samples` is
224    /// greater than `1`, a render pass will be automatically added after the `view` completes and
225    /// before the texture is drawn to the swapchain.
226    pub fn color_attachment_descriptor(&self) -> wgpu::RenderPassColorAttachment {
227        let load = wgpu::LoadOp::Load;
228        let store = true;
229        let attachment = match self.render_data.intermediary_lin_srgba.msaa_texture {
230            None => &self.render_data.intermediary_lin_srgba.texture_view,
231            Some((_, ref msaa_texture_view)) => msaa_texture_view,
232        };
233        let resolve_target = None;
234        wgpu::RenderPassColorAttachment {
235            view: attachment,
236            resolve_target,
237            ops: wgpu::Operations { load, store },
238        }
239    }
240
241    /// Clear the texture with the given color.
242    pub fn clear<C>(&self, color: C)
243    where
244        C: IntoLinSrgba<f32>,
245    {
246        let lin_srgba = color.into_lin_srgba();
247        let (r, g, b, a) = lin_srgba.into_components();
248        let (r, g, b, a) = (r as f64, g as f64, b as f64, a as f64);
249        let color = wgpu::Color { r, g, b, a };
250
251        self.raw_frame.clear(self.texture_view(), color);
252    }
253
254    /// Submit the frame to the GPU!
255    ///
256    /// Note that you do not need to call this manually as submission will occur automatically when
257    /// the **Frame** is dropped.
258    ///
259    /// Before submission, the frame does the following:
260    ///
261    /// - If the frame's intermediary linear sRGBA texture is multisampled, resolve it.
262    /// - Write the intermediary linear sRGBA image to the swap chain texture.
263    ///
264    /// It can sometimes be useful to submit the **Frame** before `view` completes in order to read
265    /// the frame's texture back to the CPU (e.g. for screen shots, recordings, etc).
266    pub fn submit(mut self) {
267        self.submit_inner();
268    }
269}
270
271impl CaptureData {
272    pub(crate) fn new(max_jobs: u32, timeout: Option<Duration>) -> Self {
273        CaptureData {
274            next_frame_path: Default::default(),
275            texture_capturer: wgpu::TextureCapturer::new(Some(max_jobs), timeout),
276        }
277    }
278}
279
280impl RenderData {
281    /// Initialise the render data.
282    ///
283    /// Creates an `wgpu::TextureView` with the given parameters.
284    ///
285    /// If `msaa_samples` is greater than 1 a `multisampled` texture will also be created. Otherwise the
286    /// a regular non-multisampled image will be created.
287    pub(crate) fn new(
288        device: &wgpu::Device,
289        swap_chain_dims: [u32; 2],
290        swap_chain_format: wgpu::TextureFormat,
291        msaa_samples: u32,
292    ) -> Self {
293        let intermediary_lin_srgba =
294            create_intermediary_lin_srgba(device, swap_chain_dims, msaa_samples);
295        let src_sample_count = 1;
296        let swap_chain_sample_count = 1;
297        let texture_reshaper = wgpu::TextureReshaper::new(
298            device,
299            &intermediary_lin_srgba.texture_view,
300            src_sample_count,
301            intermediary_lin_srgba.texture_view.sample_type(),
302            swap_chain_sample_count,
303            swap_chain_format,
304        );
305        RenderData {
306            intermediary_lin_srgba,
307            texture_reshaper,
308            size: swap_chain_dims,
309            msaa_samples,
310        }
311    }
312
313    /// A full view into the associated texture.
314    ///
315    /// See `texture` for details.
316    pub(crate) fn texture_view(&self) -> &wgpu::TextureView {
317        self.intermediary_lin_srgba
318            .msaa_texture
319            .as_ref()
320            .map(|(_, view)| view)
321            .unwrap_or(&self.intermediary_lin_srgba.texture_view)
322    }
323}
324
325impl<'swap_chain> Drop for Frame<'swap_chain> {
326    fn drop(&mut self) {
327        if !self.raw_frame.is_submitted() {
328            self.submit_inner();
329        }
330    }
331}
332
333fn create_lin_srgba_msaa_texture(
334    device: &wgpu::Device,
335    swap_chain_dims: [u32; 2],
336    msaa_samples: u32,
337) -> wgpu::Texture {
338    wgpu::TextureBuilder::new()
339        .size(swap_chain_dims)
340        .sample_count(msaa_samples)
341        .usage(wgpu::TextureUsages::RENDER_ATTACHMENT)
342        .format(Frame::TEXTURE_FORMAT)
343        .build(device)
344}
345
346fn create_lin_srgba_texture(device: &wgpu::Device, swap_chain_dims: [u32; 2]) -> wgpu::Texture {
347    wgpu::TextureBuilder::new()
348        .size(swap_chain_dims)
349        .format(Frame::TEXTURE_FORMAT)
350        .usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
351        .build(device)
352}
353
354fn create_intermediary_lin_srgba(
355    device: &wgpu::Device,
356    swap_chain_dims: [u32; 2],
357    msaa_samples: u32,
358) -> IntermediaryLinSrgba {
359    let msaa_texture = match msaa_samples {
360        0 | 1 => None,
361        _ => {
362            let texture = create_lin_srgba_msaa_texture(device, swap_chain_dims, msaa_samples);
363            let texture_view = texture.view().build();
364            Some((texture, texture_view))
365        }
366    };
367    let texture = create_lin_srgba_texture(device, swap_chain_dims);
368    let texture_view = texture.view().build();
369    IntermediaryLinSrgba {
370        msaa_texture,
371        texture,
372        texture_view,
373    }
374}