Skip to content

Commit 96becc8

Browse files
authored
examples: Prevent invalid semaphore reuse across frames (#1009)
* examples: Have a unique semaphore per swapchain image This resolves `VUID-vkQueueSubmit-pSignalSemaphores-00067` as explained at https://docs.vulkan.org/guide/latest/swapchain_semaphore_reuse.html A unique semaphore should be used per swapchain image, because the presentation engine may still reference and use it before the image index it was used with is returned out of `acquire_next_image()` again. Alternatives include waiting for the `Fence` from presentation in `VkSwapchainPresentFenceInfoKHR`, added by the `VK_EXT_swapchain_maintenance1`/`VK_KHR_swapchain_maintenance1` extension but this is less performant than just using a unique semaphore per swapchain image. * examples: Wait earlier for command-buffer fence to prevent semaphore reuse This resolves `VUID-vkAcquireNextImageKHR-semaphore-01779` where `acquire_next_image()` is asked to signal a semaphore that may still be waited on by the currently-running submit. Since the next submit is reusing the existing command buffer and resources as well it must inevitably wait on a fence from the previous frame/submit, pulling out that fence to wait on it is the most practical approach despite teaching bad/slow practices. Note that this fence was also used to "reuse" the setup command buffer in the `texture` sample; this was instead generalized to use another command buffer without waiting for and signaling any fence. * 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 b6a9b53 commit 96becc8

File tree

3 files changed

+107
-68
lines changed

3 files changed

+107
-68
lines changed

ash-examples/src/bin/texture.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,8 @@ fn main() -> Result<(), Box<dyn Error>> {
352352

353353
record_submit_commandbuffer(
354354
&base.device,
355-
base.setup_command_buffer,
356-
base.setup_commands_reuse_fence,
355+
base.app_setup_command_buffer,
356+
vk::Fence::null(),
357357
base.present_queue,
358358
&[],
359359
&[],
@@ -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();
@@ -711,6 +717,9 @@ fn main() -> Result<(), Box<dyn Error>> {
711717
},
712718
];
713719

720+
let rendering_complete_semaphore =
721+
base.rendering_complete_semaphores[present_index as usize];
722+
714723
let render_pass_begin_info = vk::RenderPassBeginInfo::default()
715724
.render_pass(renderpass)
716725
.framebuffer(framebuffers[present_index as usize])
@@ -719,12 +728,12 @@ fn main() -> Result<(), Box<dyn Error>> {
719728

720729
record_submit_commandbuffer(
721730
&base.device,
722-
base.draw_command_buffer,
723-
base.draw_commands_reuse_fence,
731+
draw_command_buffer,
732+
draw_commands_reuse_fence,
724733
base.present_queue,
725734
&[vk::PipelineStageFlags::BOTTOM_OF_PIPE],
726-
&[base.present_complete_semaphore],
727-
&[base.rendering_complete_semaphore],
735+
&[present_complete_semaphore],
736+
&[rendering_complete_semaphore],
728737
|device, draw_command_buffer| {
729738
device.cmd_begin_render_pass(
730739
draw_command_buffer,
@@ -773,7 +782,7 @@ fn main() -> Result<(), Box<dyn Error>> {
773782
);
774783
let present_info = vk::PresentInfoKHR {
775784
wait_semaphore_count: 1,
776-
p_wait_semaphores: &base.rendering_complete_semaphore,
785+
p_wait_semaphores: &rendering_complete_semaphore,
777786
swapchain_count: 1,
778787
p_swapchains: &base.swapchain,
779788
p_image_indices: &present_index,

ash-examples/src/bin/triangle.rs

Lines changed: 17 additions & 8 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();
@@ -373,6 +379,9 @@ fn main() -> Result<(), Box<dyn Error>> {
373379
},
374380
];
375381

382+
let rendering_complete_semaphore =
383+
base.rendering_complete_semaphores[present_index as usize];
384+
376385
let render_pass_begin_info = vk::RenderPassBeginInfo::default()
377386
.render_pass(renderpass)
378387
.framebuffer(framebuffers[present_index as usize])
@@ -381,12 +390,12 @@ fn main() -> Result<(), Box<dyn Error>> {
381390

382391
record_submit_commandbuffer(
383392
&base.device,
384-
base.draw_command_buffer,
385-
base.draw_commands_reuse_fence,
393+
draw_command_buffer,
394+
draw_commands_reuse_fence,
386395
base.present_queue,
387396
&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT],
388-
&[base.present_complete_semaphore],
389-
&[base.rendering_complete_semaphore],
397+
&[present_complete_semaphore],
398+
&[rendering_complete_semaphore],
390399
|device, draw_command_buffer| {
391400
device.cmd_begin_render_pass(
392401
draw_command_buffer,
@@ -425,11 +434,11 @@ fn main() -> Result<(), Box<dyn Error>> {
425434
device.cmd_end_render_pass(draw_command_buffer);
426435
},
427436
);
428-
let wait_semaphors = [base.rendering_complete_semaphore];
437+
let wait_semaphores = [rendering_complete_semaphore];
429438
let swapchains = [base.swapchain];
430439
let image_indices = [present_index];
431440
let present_info = vk::PresentInfoKHR::default()
432-
.wait_semaphores(&wait_semaphors) // &base.rendering_complete_semaphore)
441+
.wait_semaphores(&wait_semaphores)
433442
.swapchains(&swapchains)
434443
.image_indices(&image_indices);
435444

ash-examples/src/lib.rs

Lines changed: 72 additions & 51 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 {
@@ -36,9 +39,7 @@ macro_rules! offset_of {
3639
}
3740
}};
3841
}
39-
/// Helper function for submitting command buffers. Immediately waits for the fence before the command buffer
40-
/// is executed. That way we can delay the waiting for the fences by 1 frame which is good for performance.
41-
/// Make sure to create the fence in a signaled state on the first use.
42+
4243
#[allow(clippy::too_many_arguments)]
4344
pub fn record_submit_commandbuffer<F: FnOnce(&Device, vk::CommandBuffer)>(
4445
device: &Device,
@@ -51,14 +52,6 @@ pub fn record_submit_commandbuffer<F: FnOnce(&Device, vk::CommandBuffer)>(
5152
f: F,
5253
) {
5354
unsafe {
54-
device
55-
.wait_for_fences(&[command_buffer_reuse_fence], true, u64::MAX)
56-
.expect("Wait for fence failed.");
57-
58-
device
59-
.reset_fences(&[command_buffer_reuse_fence])
60-
.expect("Reset fences failed.");
61-
6255
device
6356
.reset_command_buffer(
6457
command_buffer,
@@ -143,6 +136,7 @@ pub struct ExampleBase {
143136
pub debug_utils_loader: debug_utils::Instance,
144137
pub window: winit::window::Window,
145138
pub event_loop: RefCell<EventLoop<()>>,
139+
pub frame_index: RefCell<usize>,
146140
pub debug_call_back: vk::DebugUtilsMessengerEXT,
147141

148142
pub pdevice: vk::PhysicalDevice,
@@ -159,22 +153,22 @@ pub struct ExampleBase {
159153
pub present_image_views: Vec<vk::ImageView>,
160154

161155
pub pool: vk::CommandPool,
162-
pub draw_command_buffer: vk::CommandBuffer,
156+
pub draw_command_buffers: [vk::CommandBuffer; MAX_FRAME_LATENCY],
163157
pub setup_command_buffer: vk::CommandBuffer,
158+
pub app_setup_command_buffer: vk::CommandBuffer,
164159

165160
pub depth_image: vk::Image,
166161
pub depth_image_view: vk::ImageView,
167162
pub depth_image_memory: vk::DeviceMemory,
168163

169-
pub present_complete_semaphore: vk::Semaphore,
170-
pub rendering_complete_semaphore: vk::Semaphore,
164+
pub present_complete_semaphores: [vk::Semaphore; MAX_FRAME_LATENCY],
165+
pub rendering_complete_semaphores: Vec<vk::Semaphore>,
171166

172-
pub draw_commands_reuse_fence: vk::Fence,
173-
pub setup_commands_reuse_fence: vk::Fence,
167+
pub draw_commands_reuse_fences: [vk::Fence; MAX_FRAME_LATENCY],
174168
}
175169

176170
impl ExampleBase {
177-
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> {
178172
self.event_loop.borrow_mut().run_on_demand(|event, elwp| {
179173
elwp.set_control_flow(ControlFlow::Poll);
180174
match event {
@@ -194,7 +188,24 @@ impl ExampleBase {
194188
} => {
195189
elwp.exit();
196190
}
197-
Event::AboutToWait => f(),
191+
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];
197+
unsafe {
198+
self.device
199+
.wait_for_fences(&[draw_commands_reuse_fence], true, u64::MAX)
200+
}
201+
.expect("Wait for fence failed.");
202+
203+
unsafe { self.device.reset_fences(&[draw_commands_reuse_fence]) }
204+
.expect("Reset fences failed.");
205+
206+
f(*frame_index);
207+
*frame_index += 1;
208+
}
198209
_ => (),
199210
}
200211
})
@@ -400,15 +411,18 @@ impl ExampleBase {
400411
let pool = device.create_command_pool(&pool_create_info, None).unwrap();
401412

402413
let command_buffer_allocate_info = vk::CommandBufferAllocateInfo::default()
403-
.command_buffer_count(2)
414+
.command_buffer_count(2 + MAX_FRAME_LATENCY as u32)
404415
.command_pool(pool)
405416
.level(vk::CommandBufferLevel::PRIMARY);
406417

407418
let command_buffers = device
408419
.allocate_command_buffers(&command_buffer_allocate_info)
409420
.unwrap();
410421
let setup_command_buffer = command_buffers[0];
411-
let draw_command_buffer = command_buffers[1];
422+
let app_setup_command_buffer = command_buffers[1];
423+
let draw_command_buffers = command_buffers[2..][..MAX_FRAME_LATENCY]
424+
.try_into()
425+
.unwrap();
412426

413427
let present_images = swapchain_loader.get_swapchain_images(swapchain).unwrap();
414428
let present_image_views: Vec<vk::ImageView> = present_images
@@ -467,20 +481,10 @@ impl ExampleBase {
467481
.bind_image_memory(depth_image, depth_image_memory, 0)
468482
.expect("Unable to bind depth image memory");
469483

470-
let fence_create_info =
471-
vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED);
472-
473-
let draw_commands_reuse_fence = device
474-
.create_fence(&fence_create_info, None)
475-
.expect("Create fence failed.");
476-
let setup_commands_reuse_fence = device
477-
.create_fence(&fence_create_info, None)
478-
.expect("Create fence failed.");
479-
480484
record_submit_commandbuffer(
481485
&device,
482486
setup_command_buffer,
483-
setup_commands_reuse_fence,
487+
vk::Fence::null(),
484488
present_queue,
485489
&[],
486490
&[],
@@ -530,15 +534,31 @@ impl ExampleBase {
530534

531535
let semaphore_create_info = vk::SemaphoreCreateInfo::default();
532536

533-
let present_complete_semaphore = device
534-
.create_semaphore(&semaphore_create_info, None)
535-
.unwrap();
536-
let rendering_complete_semaphore = device
537-
.create_semaphore(&semaphore_create_info, None)
538-
.unwrap();
537+
let present_complete_semaphores = std::array::from_fn(|_| {
538+
device
539+
.create_semaphore(&semaphore_create_info, None)
540+
.unwrap()
541+
});
542+
let rendering_complete_semaphores = (0..present_images.len())
543+
.map(|_| {
544+
device
545+
.create_semaphore(&semaphore_create_info, None)
546+
.unwrap()
547+
})
548+
.collect();
549+
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+
});
539558

540559
Ok(Self {
541560
event_loop: RefCell::new(event_loop),
561+
frame_index: RefCell::new(0),
542562
entry,
543563
instance,
544564
device,
@@ -555,14 +575,14 @@ impl ExampleBase {
555575
present_images,
556576
present_image_views,
557577
pool,
558-
draw_command_buffer,
578+
draw_command_buffers,
559579
setup_command_buffer,
580+
app_setup_command_buffer,
560581
depth_image,
561582
depth_image_view,
562-
present_complete_semaphore,
563-
rendering_complete_semaphore,
564-
draw_commands_reuse_fence,
565-
setup_commands_reuse_fence,
583+
present_complete_semaphores,
584+
rendering_complete_semaphores,
585+
draw_commands_reuse_fences,
566586
surface,
567587
debug_call_back,
568588
debug_utils_loader,
@@ -576,18 +596,19 @@ impl Drop for ExampleBase {
576596
fn drop(&mut self) {
577597
unsafe {
578598
self.device.device_wait_idle().unwrap();
579-
self.device
580-
.destroy_semaphore(self.present_complete_semaphore, None);
581-
self.device
582-
.destroy_semaphore(self.rendering_complete_semaphore, None);
583-
self.device
584-
.destroy_fence(self.draw_commands_reuse_fence, None);
585-
self.device
586-
.destroy_fence(self.setup_commands_reuse_fence, None);
599+
for &semaphore in &self.present_complete_semaphores {
600+
self.device.destroy_semaphore(semaphore, None);
601+
}
602+
for &semaphore in &self.rendering_complete_semaphores {
603+
self.device.destroy_semaphore(semaphore, None);
604+
}
605+
for &fence in &self.draw_commands_reuse_fences {
606+
self.device.destroy_fence(fence, None);
607+
}
587608
self.device.free_memory(self.depth_image_memory, None);
588609
self.device.destroy_image_view(self.depth_image_view, None);
589610
self.device.destroy_image(self.depth_image, None);
590-
for &image_view in self.present_image_views.iter() {
611+
for &image_view in &self.present_image_views {
591612
self.device.destroy_image_view(image_view, None);
592613
}
593614
self.device.destroy_command_pool(self.pool, None);

0 commit comments

Comments
 (0)