Skip to main content

alpha2_render/
target.rs

1use crate::{RenderError, Shared};
2
3/// A GPU texture produced by the renderer. Image passes store premultiplied
4/// sRGB RGBA; raw solid clears retain the supplied component values.
5///
6/// Read it back with [`crate::Renderer::read_rgba8`] or the renderer's other
7/// readbacks, or draw it as a source through [`SourceView`].
8#[derive(Clone, Debug)]
9pub struct RenderedTexture {
10    pub(crate) size: RenderSize,
11    pub(crate) format: wgpu::TextureFormat,
12    pub(crate) texture: wgpu::Texture,
13    view: wgpu::TextureView,
14    _recycle: Option<Shared<RecycleGuard>>,
15}
16
17#[derive(Debug)]
18struct RecycleGuard {
19    texture: wgpu::Texture,
20    view: wgpu::TextureView,
21    token: Option<crate::pool::RecycleToken>,
22}
23
24impl Drop for RecycleGuard {
25    fn drop(&mut self) {
26        if let Some(token) = self.token.take() {
27            token.recycle(self.texture.clone(), self.view.clone());
28        }
29    }
30}
31
32impl RenderedTexture {
33    #[cfg(test)]
34    pub(crate) fn owned(
35        size: RenderSize,
36        format: wgpu::TextureFormat,
37        texture: wgpu::Texture,
38    ) -> Self {
39        let view = crate::pool::default_view(&texture);
40        Self {
41            size,
42            format,
43            texture,
44            view,
45            _recycle: None,
46        }
47    }
48
49    /// Texture dimensions in pixels.
50    #[must_use]
51    pub const fn size(&self) -> RenderSize {
52        self.size
53    }
54
55    /// The GPU texture format.
56    #[cfg_attr(not(feature = "wgpu-interop"), doc(hidden))]
57    #[must_use]
58    pub const fn format(&self) -> wgpu::TextureFormat {
59        self.format
60    }
61
62    /// The texture this result owns.
63    #[cfg_attr(not(feature = "wgpu-interop"), doc(hidden))]
64    #[must_use]
65    pub const fn texture(&self) -> &wgpu::Texture {
66        &self.texture
67    }
68
69    /// Returns the texture's default view, made once with the texture: a
70    /// pooled texture keeps it across every result it holds.
71    #[cfg_attr(not(feature = "wgpu-interop"), doc(hidden))]
72    #[must_use]
73    pub const fn view(&self) -> &wgpu::TextureView {
74        &self.view
75    }
76
77    pub(crate) fn pooled(
78        size: RenderSize,
79        format: wgpu::TextureFormat,
80        lease: crate::pool::TextureLease,
81    ) -> Self {
82        let (texture, view, recycle) = lease.into_parts();
83        Self {
84            size,
85            format,
86            texture: texture.clone(),
87            view: view.clone(),
88            _recycle: Some(Shared::new(RecycleGuard {
89                texture,
90                view,
91                token: Some(recycle),
92            })),
93        }
94    }
95}
96
97/// A picture the renderer draws from as a source, borrowed from whoever owns
98/// it: on the GPU a [`RenderedTexture`], an [`crate::UploadTexture`] (what
99/// [`crate::Renderer::upload_rgba8`] returns), or, through the `wgpu`
100/// interop, any texture view of the renderer's device; on the CPU a
101/// [`crate::CpuSource`].
102///
103/// A source stores premultiplied, sRGB-encoded RGBA (a GPU texture in a
104/// non-sRGB format). The owner keeps it alive and unchanged for as long as
105/// the renderer may read it: until the frame drawn from it is submitted, or
106/// on the CPU, rendered. A renderer given a source of the other backend
107/// fails with [`RenderError::InvalidSource`].
108#[derive(Clone, Copy, Debug)]
109pub struct SourceView<'a> {
110    kind: SourceKind<'a>,
111}
112
113/// Which backend's picture a [`SourceView`] borrows.
114#[derive(Clone, Copy, Debug)]
115pub(crate) enum SourceKind<'a> {
116    Gpu(&'a wgpu::TextureView),
117    Cpu(&'a crate::CpuSource),
118}
119
120impl<'a> SourceView<'a> {
121    /// A source drawn from `view`, a view of a texture on the renderer's
122    /// device.
123    #[cfg_attr(not(feature = "wgpu-interop"), doc(hidden))]
124    #[must_use]
125    pub const fn from_wgpu(view: &'a wgpu::TextureView) -> Self {
126        Self {
127            kind: SourceKind::Gpu(view),
128        }
129    }
130
131    /// A source the CPU renderer draws from.
132    #[must_use]
133    pub const fn cpu(source: &'a crate::CpuSource) -> Self {
134        Self {
135            kind: SourceKind::Cpu(source),
136        }
137    }
138
139    /// The texture view the GPU renderer samples; `None` for a CPU source.
140    #[cfg_attr(not(feature = "wgpu-interop"), doc(hidden))]
141    #[must_use]
142    pub const fn wgpu_view(self) -> Option<&'a wgpu::TextureView> {
143        match self.kind {
144            SourceKind::Gpu(view) => Some(view),
145            SourceKind::Cpu(_) => None,
146        }
147    }
148
149    /// The CPU source; `None` for a GPU texture.
150    #[must_use]
151    pub const fn cpu_source(self) -> Option<&'a crate::CpuSource> {
152        match self.kind {
153            SourceKind::Cpu(source) => Some(source),
154            SourceKind::Gpu(_) => None,
155        }
156    }
157}
158
159impl<'a> From<&'a RenderedTexture> for SourceView<'a> {
160    fn from(texture: &'a RenderedTexture) -> Self {
161        Self::from_wgpu(&texture.view)
162    }
163}
164
165impl<'a> From<&'a crate::UploadTexture> for SourceView<'a> {
166    fn from(texture: &'a crate::UploadTexture) -> Self {
167        Self::from_wgpu(texture.view())
168    }
169}
170
171impl<'a> From<&'a crate::CpuSource> for SourceView<'a> {
172    fn from(source: &'a crate::CpuSource) -> Self {
173        Self::cpu(source)
174    }
175}
176
177/// A non-zero render-target extent.
178#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
179pub struct RenderSize {
180    /// Width in pixels.
181    pub width: u32,
182    /// Height in pixels.
183    pub height: u32,
184}
185
186impl RenderSize {
187    /// Largest render-target edge accepted by [`RenderSize::new`].
188    pub const MAX_DIMENSION: u32 = alpha2_media::MAX_FRAME_DIMENSION;
189    /// Largest render-target area accepted by [`RenderSize::new`].
190    pub const MAX_PIXELS: u64 = alpha2_media::MAX_FRAME_PIXELS;
191
192    /// Creates a non-zero render size.
193    ///
194    /// # Errors
195    ///
196    /// Returns [`RenderError::ZeroDimension`] for a zero width or height.
197    pub const fn new(width: u32, height: u32) -> Result<Self, RenderError> {
198        if width == 0 || height == 0 {
199            Err(RenderError::ZeroDimension)
200        } else if width > Self::MAX_DIMENSION
201            || height > Self::MAX_DIMENSION
202            || (width as u64) * (height as u64) > Self::MAX_PIXELS
203        {
204            Err(RenderError::DimensionTooLarge)
205        } else {
206            Ok(Self { width, height })
207        }
208    }
209}