1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
//! The lower-level "raw" frame type allowing to draw directly to the window's swap chain image.

use crate::geom;
use crate::wgpu;
use crate::window;
use std::cell::{RefCell, RefMut};
use std::sync::Arc;

/// Allows the user to draw a single **RawFrame** to the surface of a window.
///
/// The application's **view** function is called each time the application is ready to retrieve a
/// new image that will be displayed to a window. The **RawFrame** type can be thought of as the
/// canvas to which you draw this image.
///
/// ## Under the hood - WGPU
///
/// **RawFrame** provides access to the **wgpu::TextureViewHandle** associated with the swap
/// chain's current target texture for a single window.
///
/// In the case that your **view** function is shared between multiple windows, can determine which
/// window the **RawFrame** is associated with via the **RawFrame::window_id** method.
///
/// The user can draw to the swap chain texture by building a list of commands via a
/// `wgpu::CommandEncoder` and submitting them to the `wgpu::Queue` associated with the
/// `wgpu::Device` that was used to create the swap chain. It is important that the queue
/// matches the device. In an effort to reduce the chance for errors to occur, **RawFrame**
/// provides access to a `wgpu::CommandEncoder` whose commands are guaranteed to be submitted to
/// the correct `wgpu::Queue` at the end of the **view** function.
pub struct RawFrame<'swap_chain> {
    command_encoder: Option<RefCell<wgpu::CommandEncoder>>,
    window_id: window::Id,
    nth: u64,
    swap_chain_texture: &'swap_chain wgpu::TextureViewHandle,
    device_queue_pair: Arc<wgpu::DeviceQueuePair>,
    texture_format: wgpu::TextureFormat,
    window_rect: geom::Rect,
}

impl<'swap_chain> RawFrame<'swap_chain> {
    // Initialise a new empty frame ready for "drawing".
    pub(crate) fn new_empty(
        device_queue_pair: Arc<wgpu::DeviceQueuePair>,
        window_id: window::Id,
        nth: u64,
        swap_chain_texture: &'swap_chain wgpu::TextureViewHandle,
        texture_format: wgpu::TextureFormat,
        window_rect: geom::Rect,
    ) -> Self {
        let ce_desc = wgpu::CommandEncoderDescriptor {
            label: Some("nannou_raw_frame"),
        };
        let command_encoder = device_queue_pair.device().create_command_encoder(&ce_desc);
        let command_encoder = Some(RefCell::new(command_encoder));
        let frame = RawFrame {
            command_encoder,
            window_id,
            nth,
            swap_chain_texture,
            device_queue_pair,
            texture_format,
            window_rect,
        };
        frame
    }

    // Submit the encoded commands to the queue of the device that was used to create the swap
    // chain texture.
    pub(crate) fn submit_inner(&mut self) {
        let command_encoder = self
            .command_encoder
            .take()
            .expect("the command encoder should always be `Some` at the time of submission")
            .into_inner();
        let command_buffer = command_encoder.finish();
        let queue = self.device_queue_pair.queue();
        queue.submit(std::iter::once(command_buffer));
    }

    // Allow the `Frame` to check if the raw frame has already been submitted on drop.
    pub(crate) fn is_submitted(&self) -> bool {
        self.command_encoder.is_none()
    }

    /// Access the command encoder in order to encode commands that will be submitted to the swap
    /// chain queue at the end of the call to **view**.
    pub fn command_encoder(&self) -> RefMut<wgpu::CommandEncoder> {
        match self.command_encoder {
            Some(ref ce) => ce.borrow_mut(),
            None => unreachable!("`RawFrame`'s command_encoder was `None`"),
        }
    }

    /// The `Id` of the window whose wgpu surface is associated with this frame.
    pub fn window_id(&self) -> window::Id {
        self.window_id
    }

    /// A **Rect** representing the full surface of the frame.
    ///
    /// The returned **Rect** is equivalent to the result of calling **Window::rect** on the window
    /// associated with this **Frame**.
    pub fn rect(&self) -> geom::Rect {
        self.window_rect
    }

    /// The `nth` frame for the associated window since the application started.
    ///
    /// E.g. the first frame yielded will return `0`, the second will return `1`, and so on.
    pub fn nth(&self) -> u64 {
        self.nth
    }

    /// The swap chain texture that will be the target for drawing this frame.
    pub fn swap_chain_texture(&self) -> &wgpu::TextureViewHandle {
        &self.swap_chain_texture
    }

    /// The texture format of the frame's swap chain texture.
    pub fn texture_format(&self) -> wgpu::TextureFormat {
        self.texture_format
    }

    /// The device and queue on which the swap chain was created and which will be used to submit
    /// the **RawFrame**'s encoded commands.
    ///
    /// This refers to the same **DeviceQueuePair** as held by the window associated with this
    /// frame.
    pub fn device_queue_pair(&self) -> &Arc<wgpu::DeviceQueuePair> {
        &self.device_queue_pair
    }

    /// Submit the frame to the GPU!
    ///
    /// Specifically, this submits the encoded commands to the queue of the device that was used to
    /// create the swap chain texture.
    ///
    /// Note: You do not need to call this manually as submission will occur automatically when
    /// the **Frame** is dropped.
    ///
    /// Note: Be careful that you do not currently possess a lock to either the frame's command
    /// encoder *or* the queue of the window associated with this frame or this method will lock
    /// and block forever.
    pub fn submit(mut self) {
        self.submit_inner();
    }

    /// Clear the texture with the given color.
    pub fn clear(&self, texture_view: &wgpu::TextureView, color: wgpu::Color) {
        wgpu::clear_texture(texture_view, color, &mut *self.command_encoder())
    }
}

impl<'swap_chain> Drop for RawFrame<'swap_chain> {
    fn drop(&mut self) {
        // Submit the commands if the user hasn't done so already.
        if !self.is_submitted() {
            self.submit_inner();
        }
    }
}