lambda_platform/gfx/
surface.rs

1/// ColorFormat for the surface.
2pub use gfx_hal::format::Format as ColorFormat;
3use gfx_hal::{
4  window::{
5    PresentationSurface,
6    Surface as _,
7  },
8  Backend,
9};
10#[cfg(test)]
11use mockall::automock;
12
13use super::{
14  gpu::Gpu,
15  Instance,
16};
17
18/// The API to use for building surfaces from a graphical instance.
19#[derive(Debug, Clone)]
20pub struct SurfaceBuilder {
21  name: Option<String>,
22}
23
24#[cfg_attr(test, automock)]
25impl SurfaceBuilder {
26  pub fn new() -> Self {
27    return Self { name: None };
28  }
29
30  /// Set the name of the surface.
31  pub fn with_name(mut self, name: &str) -> Self {
32    self.name = Some(name.to_string());
33    return self;
34  }
35
36  /// Build a surface using a graphical instance & active window
37  pub fn build<RenderBackend: gfx_hal::Backend>(
38    self,
39    instance: &super::Instance<RenderBackend>,
40    window: &crate::winit::WindowHandle,
41  ) -> Surface<RenderBackend> {
42    let gfx_hal_surface = instance.create_surface(window);
43    let name = match self.name {
44      Some(name) => name,
45      None => "RenderSurface".to_string(),
46    };
47
48    return Surface {
49      name,
50      extent: None,
51      gfx_hal_surface,
52      swapchain_is_valid: true,
53      image: None,
54      frame_buffer_attachment: None,
55    };
56  }
57}
58
59/// Defines a surface that can be rendered on to.
60#[derive(Debug)]
61pub struct Surface<RenderBackend: gfx_hal::Backend> {
62  name: String,
63  gfx_hal_surface: RenderBackend::Surface,
64  extent: Option<gfx_hal::window::Extent2D>,
65  swapchain_is_valid: bool,
66  // TODO(vmarcella): the Image type is very large
67  image: Option<
68    <RenderBackend::Surface as gfx_hal::window::PresentationSurface<
69      RenderBackend,
70    >>::SwapchainImage,
71  >,
72  frame_buffer_attachment: Option<gfx_hal::image::FramebufferAttachment>,
73}
74
75#[derive(Debug, Clone)]
76pub struct Swapchain {
77  config: gfx_hal::window::SwapchainConfig,
78  format: gfx_hal::format::Format,
79}
80
81#[cfg_attr(test, automock)]
82impl<RenderBackend: gfx_hal::Backend> Surface<RenderBackend> {
83  /// Apply a swapchain to the current surface. This is required whenever a
84  /// swapchain has been invalidated (I.E. by window resizing)
85  pub fn apply_swapchain<'surface>(
86    &mut self,
87    gpu: &Gpu<RenderBackend>,
88    swapchain: Swapchain,
89    timeout_in_nanoseconds: u64,
90  ) -> Result<(), &'surface str> {
91    let device = gpu.internal_logical_device();
92    self.extent = Some(swapchain.config.extent);
93
94    unsafe {
95      self
96        .gfx_hal_surface
97        .configure_swapchain(device, swapchain.config.clone())
98        .expect("Failed to configure the swapchain");
99
100      self.frame_buffer_attachment =
101        Some(swapchain.config.framebuffer_attachment());
102
103      let image =
104        match self.gfx_hal_surface.acquire_image(timeout_in_nanoseconds) {
105          Ok((image, _)) => Some(image),
106          Err(_) => {
107            self.swapchain_is_valid = false;
108            None
109          }
110        };
111
112      match image {
113        Some(image) => {
114          self.image = Some(image);
115          return Ok(());
116        }
117        None => {
118          return Err("Failed to apply the swapchain.");
119        }
120      }
121    }
122  }
123
124  pub fn needs_swapchain(&self) -> bool {
125    return self.swapchain_is_valid;
126  }
127
128  /// Remove the swapchain configuration that this surface used on this given
129  /// GPU.
130  pub fn remove_swapchain(&mut self, gpu: &Gpu<RenderBackend>) {
131    logging::debug!("Removing the swapchain configuration from: {}", self.name);
132    unsafe {
133      self
134        .gfx_hal_surface
135        .unconfigure_swapchain(gpu.internal_logical_device());
136    }
137  }
138
139  /// Destroy the current surface and it's underlying resources.
140  pub fn destroy(self, instance: &Instance<RenderBackend>) {
141    logging::debug!("Destroying the surface: {}", self.name);
142
143    instance.destroy_surface(self.gfx_hal_surface);
144  }
145
146  /// Get the size of the surface's extent. Will only return a size if a
147  /// swapchain has been applied to the surface to render with.
148  pub fn size(&self) -> Option<(u32, u32)> {
149    return match self.extent {
150      Some(extent) => Some((extent.width, extent.height)),
151      None => None,
152    };
153  }
154}
155
156// ------------------------------ SWAPCHAIN BUILDER ----------------------------
157
158pub struct SwapchainBuilder {
159  size: (u32, u32),
160}
161
162impl SwapchainBuilder {
163  pub fn new() -> Self {
164    return Self { size: (480, 360) };
165  }
166
167  /// Set the size of the swapchain for the surface image.
168  pub fn with_size(mut self, width: u32, height: u32) -> Self {
169    self.size = (width, height);
170    return self;
171  }
172
173  pub fn build<RenderBackend: Backend>(
174    self,
175    gpu: &Gpu<RenderBackend>,
176    surface: &Surface<RenderBackend>,
177  ) -> Swapchain {
178    let physical_device = gpu.internal_physical_device();
179    let caps = surface.gfx_hal_surface.capabilities(physical_device);
180    let format = surface.get_first_supported_format(physical_device);
181    let (width, height) = self.size;
182
183    let mut swapchain_config = gfx_hal::window::SwapchainConfig::from_caps(
184      &caps,
185      format,
186      gfx_hal::window::Extent2D { width, height },
187    );
188
189    // TODO(vmarcella) Profile the performance on MacOS to see if this slows
190    // down frame times.
191    if caps.image_count.contains(&3) {
192      swapchain_config.image_count = 3;
193    }
194
195    return Swapchain {
196      config: swapchain_config,
197      format,
198    };
199  }
200}
201
202#[cfg(test)]
203mod tests {
204  use super::*;
205  use crate::gfx::MockInstanceBuilder;
206
207  #[test]
208  fn test_surface_builder() {
209    let surface_builder = SurfaceBuilder::new();
210    assert_eq!(surface_builder.name, None);
211
212    let surface_builder = SurfaceBuilder::new().with_name("TestSurface");
213    assert_eq!(surface_builder.name, Some("TestSurface".to_string()));
214  }
215
216  #[test]
217  fn test_swapchain_builder() {
218    let swapchain_builder = SwapchainBuilder::new();
219    assert_eq!(swapchain_builder.size, (480, 360));
220
221    let swapchain_builder = SwapchainBuilder::new().with_size(1920, 1080);
222    assert_eq!(swapchain_builder.size, (1920, 1080));
223  }
224
225  #[test]
226  fn test_surface_builder_e2e() {
227    //let instance = MockInstanceBuilder::new().build("TestInstance");
228    let surface_builder = SurfaceBuilder::new().with_name("TestSurface");
229    //let surface = surface_builder.build(&instance);
230
231    //assert_eq!(surface.name, "TestSurface".to_string());
232    //assert_eq!(surface.swapchain_is_valid, false);
233    //assert_eq!(surface.image, None);
234    //assert_eq!(surface.frame_buffer_attachment, None);
235  }
236}
237
238impl<RenderBackend: Backend> Surface<RenderBackend> {
239  /// Checks the queue family if the current Surface can support the GPU.
240  pub(super) fn can_support_queue_family(
241    &self,
242    queue_family: &RenderBackend::QueueFamily,
243  ) -> bool {
244    return self.gfx_hal_surface.supports_queue_family(queue_family);
245  }
246
247  pub(super) fn get_supported_formats(
248    &self,
249    physical_device: &RenderBackend::PhysicalDevice,
250  ) -> Vec<gfx_hal::format::Format> {
251    return self
252      .gfx_hal_surface
253      .supported_formats(physical_device)
254      .unwrap_or(vec![]);
255  }
256
257  pub(super) fn get_first_supported_format(
258    &self,
259    physical_device: &RenderBackend::PhysicalDevice,
260  ) -> gfx_hal::format::Format {
261    return self
262      .get_supported_formats(physical_device)
263      .get(0)
264      .unwrap_or(&gfx_hal::format::Format::Rgba8Srgb)
265      .clone();
266  }
267
268  pub(super) fn internal_surface_image(
269    &self,
270  ) -> Option<&<RenderBackend::Surface as PresentationSurface<RenderBackend>>::SwapchainImage>{
271    return self.image.as_ref();
272  }
273
274  pub(super) fn internal_frame_buffer_attachment(
275    &self,
276  ) -> Option<gfx_hal::image::FramebufferAttachment> {
277    return self.frame_buffer_attachment.clone();
278  }
279
280  pub(super) fn internal_surface_and_image(
281    &mut self,
282  ) -> (
283    &mut RenderBackend::Surface,
284    <RenderBackend::Surface as PresentationSurface<RenderBackend>>::SwapchainImage,
285  ){
286    return (
287      &mut self.gfx_hal_surface,
288      self.image.take().expect("Surface image is not present"),
289    );
290  }
291}