Skip to content

Commit 8452dff

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 8452dff

File tree

3 files changed

+70
-42
lines changed

3 files changed

+70
-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)
436442
.swapchains(&swapchains)
437443
.image_indices(&image_indices);
438444

ash-examples/src/lib.rs

Lines changed: 46 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(2 + MAX_FRAME_LATENCY as u32)
408415
.command_pool(pool)
409416
.level(vk::CommandBufferLevel::PRIMARY);
410417

@@ -413,7 +420,9 @@ 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..][..MAX_FRAME_LATENCY]
424+
.try_into()
425+
.unwrap();
417426

418427
let present_images = swapchain_loader.get_swapchain_images(swapchain).unwrap();
419428
let present_image_views: Vec<vk::ImageView> = present_images
@@ -472,13 +481,6 @@ impl ExampleBase {
472481
.bind_image_memory(depth_image, depth_image_memory, 0)
473482
.expect("Unable to bind depth image memory");
474483

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-
482484
record_submit_commandbuffer(
483485
&device,
484486
setup_command_buffer,
@@ -532,9 +534,11 @@ impl ExampleBase {
532534

533535
let semaphore_create_info = vk::SemaphoreCreateInfo::default();
534536

535-
let present_complete_semaphore = device
536-
.create_semaphore(&semaphore_create_info, None)
537-
.unwrap();
537+
let present_complete_semaphores = std::array::from_fn(|_| {
538+
device
539+
.create_semaphore(&semaphore_create_info, None)
540+
.unwrap()
541+
});
538542
let rendering_complete_semaphores = (0..present_images.len())
539543
.map(|_| {
540544
device
@@ -543,8 +547,18 @@ impl ExampleBase {
543547
})
544548
.collect();
545549

550+
let fence_create_info =
551+
vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED);
552+
553+
let draw_commands_reuse_fences = std::array::from_fn(|_| {
554+
device
555+
.create_fence(&fence_create_info, None)
556+
.expect("Create fence failed.")
557+
});
558+
546559
Ok(Self {
547560
event_loop: RefCell::new(event_loop),
561+
frame_index: RefCell::new(0),
548562
entry,
549563
instance,
550564
device,
@@ -561,14 +575,14 @@ impl ExampleBase {
561575
present_images,
562576
present_image_views,
563577
pool,
564-
draw_command_buffer,
578+
draw_command_buffers,
565579
setup_command_buffer,
566580
app_setup_command_buffer,
567581
depth_image,
568582
depth_image_view,
569-
present_complete_semaphore,
583+
present_complete_semaphores,
570584
rendering_complete_semaphores,
571-
draw_commands_reuse_fence,
585+
draw_commands_reuse_fences,
572586
surface,
573587
debug_call_back,
574588
debug_utils_loader,
@@ -582,13 +596,15 @@ impl Drop for ExampleBase {
582596
fn drop(&mut self) {
583597
unsafe {
584598
self.device.device_wait_idle().unwrap();
585-
self.device
586-
.destroy_semaphore(self.present_complete_semaphore, None);
599+
for &semaphore in &self.present_complete_semaphores {
600+
self.device.destroy_semaphore(semaphore, None);
601+
}
587602
for &semaphore in &self.rendering_complete_semaphores {
588603
self.device.destroy_semaphore(semaphore, None);
589604
}
590-
self.device
591-
.destroy_fence(self.draw_commands_reuse_fence, None);
605+
for &fence in &self.draw_commands_reuse_fences {
606+
self.device.destroy_fence(fence, None);
607+
}
592608
self.device.free_memory(self.depth_image_memory, None);
593609
self.device.destroy_image_view(self.depth_image_view, None);
594610
self.device.destroy_image(self.depth_image, None);

0 commit comments

Comments
 (0)