Skip to main content

alpha2_render/
target.rs

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