lambda_platform/gfx/
command.rs

1use std::{
2  borrow::Borrow,
3  collections::HashMap,
4  ops::Range,
5  rc::Rc,
6};
7
8use gfx_hal::{
9  command::ClearValue,
10  device::Device,
11  pool::CommandPool as _,
12  prelude::CommandBuffer as GfxCommandBuffer,
13};
14
15use super::{
16  pipeline::RenderPipeline,
17  viewport::ViewPort,
18};
19
20/// Command Pool Flag used to define optimizations/properties of the command
21/// pool prior to being built.
22pub enum CommandPoolFeatures {
23  /// Optimizes the command pool for buffers that are expected to have short
24  /// lifetimes (I.E. command buffers that continuously need to render data to
25  /// the screen)
26  ShortLivedBuffers,
27  /// Allows for buffers to be reset individually & manually by the owner of the
28  /// command pool.
29  ResetBuffersIndividually,
30  /// Enable no features on the CommandPool.
31  None,
32  /// Enable all features for a given CommandPool.
33  All,
34}
35
36/// Features that can be used to optimize command buffers for specific
37/// usages/scenarios
38pub enum CommandBufferFeatures {
39  /// Enable this feature when you would like the command buffer to reset it's
40  /// contents every time its submitted for Rendering.
41  ResetEverySubmission,
42  /// Enable this feature if the command buffer lives within the lifetime of a
43  /// render pass.
44  TiedToRenderPass,
45  /// Enable this feature if the command buffer allows for silumtaneous
46  /// recording
47  SimultaneousRecording,
48  /// Enables no features.
49  None,
50  /// Enables all features.
51  All,
52}
53
54/// This enum is used for specifying the type of command buffer to allocate on
55/// the command pool.
56pub enum CommandBufferLevel {
57  /// Use for allocating a top level primary command buffer on the
58  /// command pool. A command buffer at this level can then be used to create
59  /// other primaries.
60  Primary,
61  /// Used
62  Secondary,
63}
64
65/// Enumeration for issuing commands to a CommandBuffer allocated on the GPU.
66/// The enumerations are evaluated upon being issued to an active command buffer
67/// and correspond to lower level function calls.
68pub enum Command<RenderBackend: gfx_hal::Backend> {
69  /// Begins recording commands to the GPU. A primary command buffer can only
70  /// issue this command once.
71  BeginRecording,
72  SetViewports {
73    start_at: u32,
74    viewports: Vec<ViewPort>,
75  },
76  SetScissors {
77    start_at: u32,
78    viewports: Vec<ViewPort>,
79  },
80  BeginRenderPass {
81    render_pass: Rc<super::render_pass::RenderPass<RenderBackend>>,
82    surface: Rc<super::surface::Surface<RenderBackend>>,
83    frame_buffer: Rc<super::framebuffer::Framebuffer<RenderBackend>>,
84    viewport: ViewPort,
85  },
86  /// Ends a currently active render pass.
87  EndRenderPass,
88  AttachGraphicsPipeline {
89    pipeline: Rc<RenderPipeline<RenderBackend>>,
90  },
91  Draw {
92    vertices: Range<u32>,
93  },
94  PushConstants {
95    pipeline: Rc<RenderPipeline<RenderBackend>>,
96    stage: super::pipeline::PipelineStage,
97    offset: u32,
98    bytes: Vec<u32>,
99  },
100  BindVertexBuffer {
101    buffer: Rc<super::buffer::Buffer<RenderBackend>>,
102  },
103  EndRecording,
104}
105
106/// Representation of a command buffer allocated on the GPU. The lifetime of
107/// the command is constrained to the lifetime of the command pool that built
108/// it to ensure that it cannot be used while
109pub struct CommandBuffer<'command_pool, RenderBackend: gfx_hal::Backend> {
110  command_buffer: &'command_pool mut RenderBackend::CommandBuffer,
111  flags: gfx_hal::command::CommandBufferFlags,
112}
113
114impl<'command_pool, RenderBackend: gfx_hal::Backend>
115  CommandBuffer<'command_pool, RenderBackend>
116{
117  /// Validates and issues a command directly to the buffer on the GPU.
118  /// If using a newly created Primary CommandBuffer the first and last commands
119  /// that should be issued are:
120  /// Command<RenderBackend>::BeginRecording
121  /// Command<RenderBackend>::EndRecording
122  /// Once the command buffer has stopped recording, it can be submitted to the
123  /// GPU to start performing work.
124  pub fn issue_command(&mut self, command: Command<RenderBackend>) {
125    use gfx_hal::command::CommandBuffer as _;
126    unsafe {
127      match command {
128        Command::BeginRecording => {
129          self.command_buffer.begin_primary(self.flags)
130        }
131        Command::SetViewports {
132          start_at,
133          viewports,
134        } => self.command_buffer.set_viewports(
135          start_at,
136          viewports
137            .into_iter()
138            .map(|viewport| viewport.internal_viewport()),
139        ),
140        Command::SetScissors {
141          start_at,
142          viewports,
143        } => self.command_buffer.set_scissors(
144          start_at,
145          viewports
146            .into_iter()
147            .map(|viewport| viewport.internal_viewport().rect),
148        ),
149
150        Command::BeginRenderPass {
151          render_pass,
152          frame_buffer,
153          surface,
154          viewport,
155        } => self.command_buffer.begin_render_pass(
156          render_pass.internal_render_pass(),
157          frame_buffer.internal_frame_buffer(),
158          viewport.internal_viewport().rect,
159          vec![gfx_hal::command::RenderAttachmentInfo::<RenderBackend> {
160            image_view: surface
161              .internal_surface_image()
162              .expect("No internal surface set when beginning the render pass.")
163              .borrow(),
164            clear_value: ClearValue {
165              color: gfx_hal::command::ClearColor {
166                float32: [0.0, 0.0, 0.0, 1.0],
167              },
168            },
169          }]
170          .into_iter(),
171          gfx_hal::command::SubpassContents::Inline,
172        ),
173        Command::AttachGraphicsPipeline { pipeline } => self
174          .command_buffer
175          .bind_graphics_pipeline(pipeline.internal_pipeline()),
176        Command::EndRenderPass => self.command_buffer.end_render_pass(),
177        Command::PushConstants {
178          pipeline,
179          stage,
180          offset,
181          bytes,
182        } => self.command_buffer.push_graphics_constants(
183          pipeline.internal_pipeline_layout(),
184          stage,
185          offset,
186          bytes.as_slice(),
187        ),
188        Command::Draw { vertices } => {
189          self.command_buffer.draw(vertices.clone(), 0..1)
190        }
191        Command::BindVertexBuffer { buffer } => {
192          self.command_buffer.bind_vertex_buffers(
193            0,
194            vec![(buffer.internal_buffer(), gfx_hal::buffer::SubRange::WHOLE)]
195              .into_iter(),
196          )
197        }
198        Command::EndRecording => self.command_buffer.finish(),
199      }
200    }
201  }
202
203  /// Functions exactly like issue_command except over multiple commands at
204  /// once. Command execution is based on the order of commands inside the
205  /// vector.
206  pub fn issue_commands(&mut self, commands: Vec<Command<RenderBackend>>) {
207    for command in commands {
208      self.issue_command(command);
209    }
210  }
211
212  pub fn reset(&mut self) {
213    unsafe {
214      self.command_buffer.reset(true);
215    }
216  }
217}
218
219/// Builder for creating a Command buffer that can issue commands directly to
220/// the GPU.
221pub struct CommandBufferBuilder {
222  flags: gfx_hal::command::CommandBufferFlags,
223  level: CommandBufferLevel,
224}
225
226impl CommandBufferBuilder {
227  pub fn new(level: CommandBufferLevel) -> Self {
228    let flags = gfx_hal::command::CommandBufferFlags::empty();
229    return CommandBufferBuilder { flags, level };
230  }
231
232  pub fn with_feature(mut self, feature: CommandBufferFeatures) -> Self {
233    let flags = match feature {
234      CommandBufferFeatures::ResetEverySubmission => {
235        gfx_hal::command::CommandBufferFlags::ONE_TIME_SUBMIT
236      }
237      CommandBufferFeatures::TiedToRenderPass => {
238        gfx_hal::command::CommandBufferFlags::RENDER_PASS_CONTINUE
239      }
240      CommandBufferFeatures::SimultaneousRecording => {
241        gfx_hal::command::CommandBufferFlags::SIMULTANEOUS_USE
242      }
243      CommandBufferFeatures::None => {
244        gfx_hal::command::CommandBufferFlags::empty()
245      }
246      CommandBufferFeatures::All => gfx_hal::command::CommandBufferFlags::all(),
247    };
248
249    self.flags.insert(flags);
250    return self;
251  }
252
253  /// Build the command buffer and tie it to the lifetime of the command pool
254  /// that gets created.
255  pub fn build<'command_pool, RenderBackend: gfx_hal::Backend>(
256    self,
257    command_pool: &'command_pool mut CommandPool<RenderBackend>,
258    name: &str,
259  ) -> CommandBuffer<'command_pool, RenderBackend> {
260    let command_buffer =
261      command_pool.fetch_or_allocate_command_buffer(name, self.level);
262
263    let flags = self.flags;
264
265    return CommandBuffer {
266      command_buffer,
267      flags,
268    };
269  }
270}
271
272pub struct CommandPoolBuilder {
273  command_pool_flags: gfx_hal::pool::CommandPoolCreateFlags,
274}
275
276pub mod internal {
277  pub fn command_buffer_for<
278    'render_context,
279    RenderBackend: gfx_hal::Backend,
280  >(
281    command_buffer: &'render_context super::CommandBuffer<
282      'render_context,
283      RenderBackend,
284    >,
285  ) -> &'render_context RenderBackend::CommandBuffer {
286    return command_buffer.command_buffer;
287  }
288}
289
290impl CommandPoolBuilder {
291  pub fn new() -> Self {
292    return Self {
293      command_pool_flags: gfx_hal::pool::CommandPoolCreateFlags::empty(),
294    };
295  }
296
297  /// Attach command pool create flags to the command pool builder.
298  pub fn with_features(mut self, flag: CommandPoolFeatures) -> Self {
299    let flags = match flag {
300      CommandPoolFeatures::ShortLivedBuffers => {
301        gfx_hal::pool::CommandPoolCreateFlags::TRANSIENT
302      }
303      CommandPoolFeatures::ResetBuffersIndividually => {
304        gfx_hal::pool::CommandPoolCreateFlags::RESET_INDIVIDUAL
305      }
306      CommandPoolFeatures::None => {
307        gfx_hal::pool::CommandPoolCreateFlags::empty()
308      }
309      CommandPoolFeatures::All => gfx_hal::pool::CommandPoolCreateFlags::all(),
310    };
311
312    self.command_pool_flags.insert(flags);
313    return self;
314  }
315
316  /// Builds a command pool.
317  pub fn build<B: gfx_hal::Backend>(
318    self,
319    gpu: &super::gpu::Gpu<B>,
320  ) -> CommandPool<B> {
321    let command_pool = unsafe {
322      gpu
323        .internal_logical_device()
324        .create_command_pool(
325          gpu.internal_queue_family(),
326          self.command_pool_flags,
327        )
328        .expect("")
329    };
330
331    return CommandPool {
332      command_pool,
333      command_buffers: HashMap::new(),
334    };
335  }
336}
337
338pub struct CommandPool<RenderBackend: gfx_hal::Backend> {
339  command_pool: RenderBackend::CommandPool,
340  command_buffers: HashMap<String, RenderBackend::CommandBuffer>,
341}
342
343impl<RenderBackend: gfx_hal::Backend> CommandPool<RenderBackend> {
344  /// Allocate a command buffer for lambda.
345  fn fetch_or_allocate_command_buffer(
346    &mut self,
347    name: &str,
348    level: CommandBufferLevel,
349  ) -> &mut RenderBackend::CommandBuffer {
350    if self.command_buffers.contains_key(name) {
351      return self.command_buffers.get_mut(name).unwrap();
352    }
353
354    let buffer = unsafe {
355      self
356        .command_pool
357        .allocate_one(gfx_hal::command::Level::Primary)
358    };
359
360    self.command_buffers.insert(name.to_string(), buffer);
361    return self.command_buffers.get_mut(name).unwrap();
362  }
363
364  /// Deallocate a command buffer
365  // TODO(vmarcella): This function should return a result based on the status
366  // of the deallocation.
367  pub fn deallocate_command_buffer(&mut self, name: &str) {
368    if self.command_buffers.contains_key(name) == false {
369      return;
370    }
371
372    let buffer = self
373      .command_buffers
374      .remove(&name.to_string())
375      .expect(format!("Command Buffer {} doesn't exist", name).as_str());
376
377    unsafe { self.command_pool.free(vec![buffer].into_iter()) }
378  }
379
380  /// Buffers can be looked up with the same name that they're given when
381  /// calling `allocate_command_buffer`
382  pub fn get_mutable_command_buffer(
383    &mut self,
384    name: &str,
385  ) -> Option<&mut RenderBackend::CommandBuffer> {
386    return self.command_buffers.get_mut(name);
387  }
388
389  /// Retrieves a command buffer that has been allocated by this command pool.
390  /// This function is most likely not
391  #[inline]
392  pub fn get_command_buffer(
393    &self,
394    name: &str,
395  ) -> Option<&RenderBackend::CommandBuffer> {
396    return self.command_buffers.get(name);
397  }
398
399  /// Resets the command pool and all of the command buffers.
400  #[inline]
401  pub fn reset_pool(&mut self, release_resources: bool) {
402    unsafe {
403      self.command_pool.reset(release_resources);
404    }
405  }
406
407  /// Moves the command pool into itself and destroys any command pool and
408  /// buffer resources allocated on the GPU.
409  #[inline]
410  pub fn destroy(mut self, gpu: &super::gpu::Gpu<RenderBackend>) {
411    unsafe {
412      self.command_pool.reset(true);
413      gpu
414        .internal_logical_device()
415        .destroy_command_pool(self.command_pool);
416    }
417  }
418}