lambda_platform/gfx/
gpu.rs

1use gfx_hal::{
2  adapter::Adapter,
3  prelude::{
4    PhysicalDevice,
5    QueueFamily,
6  },
7  queue::{
8    Queue,
9    QueueGroup,
10  },
11};
12#[cfg(test)]
13use mockall::automock;
14
15use super::{
16  command::CommandBuffer,
17  fence::{
18    RenderSemaphore,
19    RenderSubmissionFence,
20  },
21  surface,
22};
23
24/// GpuBuilder for constructing a GPU
25pub struct GpuBuilder {
26  render_queue_type: RenderQueueType,
27}
28
29impl GpuBuilder {
30  /// Create a new GpuBuilder to configure and build a GPU to use for rendering.
31  pub fn new() -> Self {
32    return Self {
33      render_queue_type: RenderQueueType::Graphical,
34    };
35  }
36
37  /// Set the type of queue to use for rendering. The GPU defaults to graphical.
38  pub fn with_render_queue_type(mut self, queue_type: RenderQueueType) -> Self {
39    self.render_queue_type = queue_type;
40    return self;
41  }
42
43  /// If passing in a surface, the gpu will be built using the queue that best
44  /// supports both the render queue & surface.
45  pub fn build<RenderBackend: gfx_hal::Backend>(
46    self,
47    instance: &mut super::Instance<RenderBackend>,
48    surface: Option<&surface::Surface<RenderBackend>>,
49  ) -> Result<Gpu<RenderBackend>, String> {
50    match (surface, self.render_queue_type) {
51      (Some(surface), RenderQueueType::Graphical) => {
52        let adapter = instance.first_adapter();
53
54        let queue_family = adapter
55          .queue_families
56          .iter()
57          .find(|family| {
58            return surface.can_support_queue_family(family)
59              && family.queue_type().supports_graphics();
60          })
61          .expect("No compatible queue family found.")
62          .id();
63
64        return Ok(Gpu::new(adapter, queue_family));
65      }
66      (Some(_surface), RenderQueueType::Compute) => {
67        todo!("Support a Compute based GPU.")
68      }
69      (_, _) => return Err("Failed to build GPU.".to_string()),
70    }
71  }
72}
73
74/// Commands oriented around creating resources on & for the GPU.
75pub struct Gpu<B: gfx_hal::Backend> {
76  adapter: gfx_hal::adapter::Adapter<B>,
77  gpu: gfx_hal::adapter::Gpu<B>,
78  queue_group: QueueGroup<B>,
79}
80
81/// The render queue types that the GPU can use for
82#[derive(Clone, Copy, Debug, PartialEq)]
83pub enum RenderQueueType {
84  Compute,
85  Graphical,
86  GraphicalCompute,
87  Transfer,
88}
89
90impl<RenderBackend: gfx_hal::Backend> Gpu<RenderBackend> {
91  /// Instantiates a new GPU given an adapter that is implemented by the GPUs
92  /// current rendering backend B. A new GPU does not come with a command pool
93  /// unless specified.
94  pub(super) fn new(
95    adapter: Adapter<RenderBackend>,
96    queue_family: gfx_hal::queue::QueueFamilyId,
97  ) -> Self {
98    let queue_family = adapter
99      .queue_families
100      .iter()
101      .find(|family| family.id() == queue_family)
102      .expect("Failed to find the queue family requested for the GPU.");
103
104    let mut gpu = unsafe {
105      adapter
106        .physical_device
107        .open(&[(queue_family, &[1.0])], gfx_hal::Features::empty())
108        .expect("Failed to open the device.")
109    };
110
111    let queue_group = gpu.queue_groups.pop().unwrap();
112
113    return Self {
114      adapter,
115      gpu,
116      queue_group,
117    };
118  }
119
120  /// Submits a command buffer to the GPU.
121  pub fn submit_command_buffer<'render_context>(
122    &mut self,
123    command_buffer: &mut CommandBuffer<RenderBackend>,
124    signal_semaphores: Vec<&RenderSemaphore<RenderBackend>>,
125    fence: &mut RenderSubmissionFence<RenderBackend>,
126  ) {
127    let commands =
128      vec![super::command::internal::command_buffer_for(command_buffer)]
129        .into_iter();
130    unsafe {
131      self
132        .queue_group
133        .queues
134        .first_mut()
135        .expect("Couldn't find the primary queue to submit commands to. ")
136        .submit(
137          commands,
138          vec![].into_iter(),
139          // TODO(vmarcella): This was needed to allow the push constants to
140          // properly render to the screen. Look into a better way to do this.
141          signal_semaphores.into_iter().map(|semaphore| {
142            return semaphore.internal_semaphore();
143          }),
144          Some(fence.internal_fence_mut()),
145        );
146    }
147  }
148
149  /// Render to the surface and return the result from the GPU.
150  pub fn render_to_surface(
151    &mut self,
152    surface: &mut surface::Surface<RenderBackend>,
153    semaphore: &mut RenderSemaphore<RenderBackend>,
154  ) -> Result<(), &str> {
155    let (render_surface, render_image) = surface.internal_surface_and_image();
156
157    let result = unsafe {
158      self.queue_group.queues[0].present(
159        render_surface,
160        render_image,
161        Some(semaphore.internal_semaphore_mut()),
162      )
163    };
164
165    if result.is_err() {
166      logging::error!(
167        "Failed to present to the surface: {:?}",
168        result.err().unwrap()
169      );
170      surface.remove_swapchain(self);
171      return Err(
172        "Rendering failed. Swapchain for the surface needs to be reconfigured.",
173      );
174    }
175
176    return Ok(());
177  }
178}
179
180impl<RenderBackend: gfx_hal::Backend> Gpu<RenderBackend> {
181  pub(super) fn internal_logical_device(&self) -> &RenderBackend::Device {
182    return &self.gpu.device;
183  }
184
185  pub(super) fn internal_physical_device(
186    &self,
187  ) -> &RenderBackend::PhysicalDevice {
188    return &self.adapter.physical_device;
189  }
190
191  pub(super) fn internal_queue_family(&self) -> gfx_hal::queue::QueueFamilyId {
192    return self.queue_group.family;
193  }
194}
195
196#[cfg(test)]
197mod tests {
198  #[test]
199  fn test_gpu_builder_default_state() {
200    use super::{
201      GpuBuilder,
202      RenderQueueType,
203    };
204
205    let builder = GpuBuilder::new();
206
207    assert_eq!(builder.render_queue_type, RenderQueueType::Graphical);
208  }
209
210  #[test]
211  fn test_gpu_builder_with_render_queue_type() {
212    use super::{
213      GpuBuilder,
214      RenderQueueType,
215    };
216
217    let builder =
218      GpuBuilder::new().with_render_queue_type(RenderQueueType::Compute);
219
220    assert_eq!(builder.render_queue_type, RenderQueueType::Compute);
221  }
222
223  #[test]
224  fn test_gpu_builder_build() {}
225}