Skip to content

Commit dec042b

Browse files
committed
examples: Implement triple-buffering
This removes a per-frame stall and demonstrates how users should utilize Vulkan to keep the GPU fed with rendering work, as long as that work (and corresponding presentation requests) complete before we're getting 3 frames ahead. Note that this does not implement proper frame throttling, and may render lots of discarded frames. It is an alternative solution to solving `VUID-vkAcquireNextImageKHR-semaphore-01779` by also using a unique `Semaphore` that we know is no longer waited on via previously waiting on the relevant `Fence`; although similar in nature to the previous solution.
1 parent 9dab58e commit dec042b

File tree

3 files changed

+68
-42
lines changed

3 files changed

+68
-42
lines changed

ash-examples/src/bin/texture.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -687,13 +687,19 @@ fn main() -> Result<(), Box<dyn Error>> {
687687

688688
let graphic_pipeline = graphics_pipelines[0];
689689

690-
let _ = base.render_loop(|| {
690+
let _ = base.render_loop(|frame_index| {
691+
let present_complete_semaphore =
692+
base.present_complete_semaphores[frame_index % MAX_FRAME_LATENCY];
693+
let draw_commands_reuse_fence =
694+
base.draw_commands_reuse_fences[frame_index % MAX_FRAME_LATENCY];
695+
let draw_command_buffer = base.draw_command_buffers[frame_index % MAX_FRAME_LATENCY];
696+
691697
let (present_index, _) = base
692698
.swapchain_loader
693699
.acquire_next_image(
694700
base.swapchain,
695701
u64::MAX,
696-
base.present_complete_semaphore,
702+
present_complete_semaphore,
697703
vk::Fence::null(),
698704
)
699705
.unwrap();
@@ -722,11 +728,11 @@ fn main() -> Result<(), Box<dyn Error>> {
722728

723729
record_submit_commandbuffer(
724730
&base.device,
725-
base.draw_command_buffer,
726-
base.draw_commands_reuse_fence,
731+
draw_command_buffer,
732+
draw_commands_reuse_fence,
727733
base.present_queue,
728734
&[vk::PipelineStageFlags::BOTTOM_OF_PIPE],
729-
&[base.present_complete_semaphore],
735+
&[present_complete_semaphore],
730736
&[rendering_complete_semaphore],
731737
|device, draw_command_buffer| {
732738
device.cmd_begin_render_pass(

ash-examples/src/bin/triangle.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -349,13 +349,19 @@ fn main() -> Result<(), Box<dyn Error>> {
349349

350350
let graphic_pipeline = graphics_pipelines[0];
351351

352-
let _ = base.render_loop(|| {
352+
let _ = base.render_loop(|frame_index| {
353+
let present_complete_semaphore =
354+
base.present_complete_semaphores[frame_index % MAX_FRAME_LATENCY];
355+
let draw_commands_reuse_fence =
356+
base.draw_commands_reuse_fences[frame_index % MAX_FRAME_LATENCY];
357+
let draw_command_buffer = base.draw_command_buffers[frame_index % MAX_FRAME_LATENCY];
358+
353359
let (present_index, _) = base
354360
.swapchain_loader
355361
.acquire_next_image(
356362
base.swapchain,
357363
u64::MAX,
358-
base.present_complete_semaphore,
364+
present_complete_semaphore,
359365
vk::Fence::null(),
360366
)
361367
.unwrap();
@@ -384,11 +390,11 @@ fn main() -> Result<(), Box<dyn Error>> {
384390

385391
record_submit_commandbuffer(
386392
&base.device,
387-
base.draw_command_buffer,
388-
base.draw_commands_reuse_fence,
393+
draw_command_buffer,
394+
draw_commands_reuse_fence,
389395
base.present_queue,
390396
&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT],
391-
&[base.present_complete_semaphore],
397+
&[present_complete_semaphore],
392398
&[rendering_complete_semaphore],
393399
|device, draw_command_buffer| {
394400
device.cmd_begin_render_pass(
@@ -428,11 +434,11 @@ fn main() -> Result<(), Box<dyn Error>> {
428434
device.cmd_end_render_pass(draw_command_buffer);
429435
},
430436
);
431-
let wait_semaphors = [rendering_complete_semaphore];
437+
let wait_semaphores = [rendering_complete_semaphore];
432438
let swapchains = [base.swapchain];
433439
let image_indices = [present_index];
434440
let present_info = vk::PresentInfoKHR::default()
435-
.wait_semaphores(&wait_semaphors) // &base.rendering_complete_semaphore)
441+
.wait_semaphores(&wait_semaphores) // &base.rendering_complete_semaphore)
436442
.swapchains(&swapchains)
437443
.image_indices(&image_indices);
438444

ash-examples/src/lib.rs

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ use winit::{
2525
window::WindowBuilder,
2626
};
2727

28+
// The maximum number of frames we allow to be in flight at any given time
29+
pub const MAX_FRAME_LATENCY: usize = 3;
30+
2831
// Simple offset_of macro akin to C++ offsetof
2932
#[macro_export]
3033
macro_rules! offset_of {
@@ -133,6 +136,7 @@ pub struct ExampleBase {
133136
pub debug_utils_loader: debug_utils::Instance,
134137
pub window: winit::window::Window,
135138
pub event_loop: RefCell<EventLoop<()>>,
139+
pub frame_index: RefCell<usize>,
136140
pub debug_call_back: vk::DebugUtilsMessengerEXT,
137141

138142
pub pdevice: vk::PhysicalDevice,
@@ -149,22 +153,22 @@ pub struct ExampleBase {
149153
pub present_image_views: Vec<vk::ImageView>,
150154

151155
pub pool: vk::CommandPool,
152-
pub draw_command_buffer: vk::CommandBuffer,
156+
pub draw_command_buffers: [vk::CommandBuffer; MAX_FRAME_LATENCY],
153157
pub setup_command_buffer: vk::CommandBuffer,
154158
pub app_setup_command_buffer: vk::CommandBuffer,
155159

156160
pub depth_image: vk::Image,
157161
pub depth_image_view: vk::ImageView,
158162
pub depth_image_memory: vk::DeviceMemory,
159163

160-
pub present_complete_semaphore: vk::Semaphore,
164+
pub present_complete_semaphores: [vk::Semaphore; MAX_FRAME_LATENCY],
161165
pub rendering_complete_semaphores: Vec<vk::Semaphore>,
162166

163-
pub draw_commands_reuse_fence: vk::Fence,
167+
pub draw_commands_reuse_fences: [vk::Fence; MAX_FRAME_LATENCY],
164168
}
165169

166170
impl ExampleBase {
167-
pub fn render_loop<F: Fn()>(&self, f: F) -> Result<(), impl Error> {
171+
pub fn render_loop<F: Fn(usize)>(&self, f: F) -> Result<(), impl Error> {
168172
self.event_loop.borrow_mut().run_on_demand(|event, elwp| {
169173
elwp.set_control_flow(ControlFlow::Poll);
170174
match event {
@@ -185,19 +189,22 @@ impl ExampleBase {
185189
elwp.exit();
186190
}
187191
Event::AboutToWait => {
192+
let mut frame_index = self.frame_index.borrow_mut();
193+
194+
// The fence from 3 frames ago, that will also be signaled this frame
195+
let draw_commands_reuse_fence =
196+
self.draw_commands_reuse_fences[*frame_index % MAX_FRAME_LATENCY];
188197
unsafe {
189-
self.device.wait_for_fences(
190-
&[self.draw_commands_reuse_fence],
191-
true,
192-
u64::MAX,
193-
)
198+
self.device
199+
.wait_for_fences(&[draw_commands_reuse_fence], true, u64::MAX)
194200
}
195201
.expect("Wait for fence failed.");
196202

197-
unsafe { self.device.reset_fences(&[self.draw_commands_reuse_fence]) }
203+
unsafe { self.device.reset_fences(&[draw_commands_reuse_fence]) }
198204
.expect("Reset fences failed.");
199205

200-
f()
206+
f(*frame_index);
207+
*frame_index += 1;
201208
}
202209
_ => (),
203210
}
@@ -404,7 +411,7 @@ impl ExampleBase {
404411
let pool = device.create_command_pool(&pool_create_info, None).unwrap();
405412

406413
let command_buffer_allocate_info = vk::CommandBufferAllocateInfo::default()
407-
.command_buffer_count(3)
414+
.command_buffer_count(5)
408415
.command_pool(pool)
409416
.level(vk::CommandBufferLevel::PRIMARY);
410417

@@ -413,7 +420,7 @@ impl ExampleBase {
413420
.unwrap();
414421
let setup_command_buffer = command_buffers[0];
415422
let app_setup_command_buffer = command_buffers[1];
416-
let draw_command_buffer = command_buffers[2];
423+
let draw_command_buffers = command_buffers[2..5].try_into().unwrap();
417424

418425
let present_images = swapchain_loader.get_swapchain_images(swapchain).unwrap();
419426
let present_image_views: Vec<vk::ImageView> = present_images
@@ -472,13 +479,6 @@ impl ExampleBase {
472479
.bind_image_memory(depth_image, depth_image_memory, 0)
473480
.expect("Unable to bind depth image memory");
474481

475-
let fence_create_info =
476-
vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED);
477-
478-
let draw_commands_reuse_fence = device
479-
.create_fence(&fence_create_info, None)
480-
.expect("Create fence failed.");
481-
482482
record_submit_commandbuffer(
483483
&device,
484484
setup_command_buffer,
@@ -532,9 +532,11 @@ impl ExampleBase {
532532

533533
let semaphore_create_info = vk::SemaphoreCreateInfo::default();
534534

535-
let present_complete_semaphore = device
536-
.create_semaphore(&semaphore_create_info, None)
537-
.unwrap();
535+
let present_complete_semaphores = std::array::from_fn(|_| {
536+
device
537+
.create_semaphore(&semaphore_create_info, None)
538+
.unwrap()
539+
});
538540
let rendering_complete_semaphores = (0..present_images.len())
539541
.map(|_| {
540542
device
@@ -543,8 +545,18 @@ impl ExampleBase {
543545
})
544546
.collect();
545547

548+
let fence_create_info =
549+
vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED);
550+
551+
let draw_commands_reuse_fences = std::array::from_fn(|_| {
552+
device
553+
.create_fence(&fence_create_info, None)
554+
.expect("Create fence failed.")
555+
});
556+
546557
Ok(Self {
547558
event_loop: RefCell::new(event_loop),
559+
frame_index: RefCell::new(0),
548560
entry,
549561
instance,
550562
device,
@@ -561,14 +573,14 @@ impl ExampleBase {
561573
present_images,
562574
present_image_views,
563575
pool,
564-
draw_command_buffer,
576+
draw_command_buffers,
565577
setup_command_buffer,
566578
app_setup_command_buffer,
567579
depth_image,
568580
depth_image_view,
569-
present_complete_semaphore,
581+
present_complete_semaphores,
570582
rendering_complete_semaphores,
571-
draw_commands_reuse_fence,
583+
draw_commands_reuse_fences,
572584
surface,
573585
debug_call_back,
574586
debug_utils_loader,
@@ -582,13 +594,15 @@ impl Drop for ExampleBase {
582594
fn drop(&mut self) {
583595
unsafe {
584596
self.device.device_wait_idle().unwrap();
585-
self.device
586-
.destroy_semaphore(self.present_complete_semaphore, None);
597+
for &semaphore in &self.present_complete_semaphores {
598+
self.device.destroy_semaphore(semaphore, None);
599+
}
587600
for &semaphore in &self.rendering_complete_semaphores {
588601
self.device.destroy_semaphore(semaphore, None);
589602
}
590-
self.device
591-
.destroy_fence(self.draw_commands_reuse_fence, None);
603+
for &fence in &self.draw_commands_reuse_fences {
604+
self.device.destroy_fence(fence, None);
605+
}
592606
self.device.free_memory(self.depth_image_memory, None);
593607
self.device.destroy_image_view(self.depth_image_view, None);
594608
self.device.destroy_image(self.depth_image, None);

0 commit comments

Comments
 (0)