I recently forked an old Vulkan project of mine, written in Java with LWJGL. While I was testing it to start modifying it I noticed the following validation error, generated on each frame:
[Renderer] DEBUG com.f03.juleng.engine.rendering.Renderer -- Acquiring next image. Frame: 6
[Renderer] ERROR com.f03.juleng.engine.rendering.VulkanInstance -- VkDebugUtilsCallback: Validation Error: [ VUID-vkAcquireNextImageKHR-semaphore-01779 ] Object 0: handle = 0xab64de0000000020, type = VK_OBJECT_TYPE_SEMAPHORE; | MessageID = 0x5717e75b | vkAcquireNextImageKHR(): Semaphore must not have any pending operations. The Vulkan spec states: If semaphore is not VK_NULL_HANDLE it must not have any uncompleted signal or wait operations pending (https://vulkan.lunarg.com/doc/view/1.3.283.0/linux/1.3-extensions/vkspec.html#VUID-vkAcquireNextImageKHR-semaphore-01779)
[Renderer] DEBUG com.f03.juleng.engine.rendering.Renderer -- Submitting present queue. Frame: 6
[Renderer] DEBUG com.f03.juleng.engine.rendering.Renderer -- Presenting next image. Frame: 6
I could not remember this error being present in my old project, and that's strange because I didn't make any changes to the engine itself. I just migrated the project from Windows to Linux. Also, the problem does NOT crash the program, which runs normally. It's just that it floods the console, which is a sign that something is wrong...
This is the code in the SwapChain
class:
boolean acquire() {
boolean outOfDate = false;
try (MemoryStack stack = MemoryStack.stackPush()) {
IntBuffer ip = stack.mallocInt(1);
// NEXT LINE IS WHAT CAUSES THE ERROR
int result = KHRSwapchain.vkAcquireNextImageKHR(
device.getLogicalDevice(),
swapChain, ~0L,
semaphores[frame].acquisitionSemaphore().getSemaphore(),
MemoryUtil.NULL,
ip);
if (result == KHRSwapchain.VK_ERROR_OUT_OF_DATE_KHR) {
outOfDate = true;
} else if (result == KHRSwapchain.VK_SUBOPTIMAL_KHR) {
// TODO
} else if (result != VK_SUCCESS) {
throw new RuntimeException("Failed to acquire image: " + result);
}
frame = ip.get(0);
}
return !outOfDate;
}
boolean present(Queue queue){
boolean resize = false;
try (MemoryStack stack = MemoryStack.stackPush()) {
VkPresentInfoKHR present = VkPresentInfoKHR.calloc(stack)
.sType(KHRSwapchain.VK_STRUCTURE_TYPE_PRESENT_INFO_KHR)
.pWaitSemaphores(stack.longs(semaphores[frame].completeSemaphore().getSemaphore()))
.swapchainCount(1)
.pSwapchains(stack.longs(swapChain))
.pImageIndices(stack.ints(frame));
int result = KHRSwapchain.vkQueuePresentKHR(queue.getQueue(), present);
if (result == KHRSwapchain.VK_ERROR_OUT_OF_DATE_KHR) {
resize = true;
} else if (result == KHRSwapchain.VK_SUBOPTIMAL_KHR) {
// TODO
} else if (result != VK_SUCCESS) {
throw new RuntimeException("Failed to present KHR: " + result);
}
}
frame = (frame + 1) % imageViews.length;
return resize;
}
The semaphore creation happens in the constructor:
semaphores = new SyncSemaphores[nImages];
for(int i = 0; i < nImages; i++){
semaphores[i] = new SyncSemaphores(device);
}
These are the queue submit functions:
void submit(Queue queue) {
try (MemoryStack stack = MemoryStack.stackPush()) {
int idx = swapChain.currentFrame();
CommandBuffer commandBuffer = commandBuffers[idx];
Fence currentFence = fences[idx];
SwapChain.SyncSemaphores syncSemaphores = swapChain.getSyncSemaphores()[idx];
queue.submit(stack.pointers(commandBuffer.getCommandBuffer()),
stack.longs(syncSemaphores.acquisitionSemaphore().getSemaphore()),
stack.ints(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT),
stack.longs(syncSemaphores.completeSemaphore().getSemaphore()),
currentFence);
}
}
And in the Queue class:
public void submit(PointerBuffer commandBuffers, LongBuffer waitSemaphores, IntBuffer dstStageMasks,
LongBuffer signalSemaphores, Fence fence) {
try (MemoryStack stack = MemoryStack.stackPush()) {
VkSubmitInfo submitInfo = VkSubmitInfo.calloc(stack)
.sType(VK_STRUCTURE_TYPE_SUBMIT_INFO)
.pCommandBuffers(commandBuffers)
.pSignalSemaphores(signalSemaphores);
if (waitSemaphores != null) {
submitInfo.waitSemaphoreCount(waitSemaphores.capacity())
.pWaitSemaphores(waitSemaphores)
.pWaitDstStageMask(dstStageMasks);
} else {
submitInfo.waitSemaphoreCount(0);
}
long fenceHandle = fence != null ? fence.getFence(): VK_NULL_HANDLE;
vkCheck(vkQueueSubmit(queue, submitInfo, fenceHandle),
"Failed to submit command to queue", logger);
}
}
And this is the rendering function, called every frame:
private void render(){
lock.lock();
try {
while(paused.get()){
pause.awaitUninterruptibly();
}
} finally {
lock.unlock();
}
logger.debug("Acquiring next image. Frame: " + swapChain.currentFrame());
if(!swapChain.acquire() || recreateSwapChain.get()){
resize(); // SwapChain out of date. Recreate.
swapChain.acquire();
recreateSwapChain.set(false);
}
updateApplicationRendering();
for(IRenderer subRenderer : subRenderers.values()){
subRenderer.render();
}
logger.debug("Submitting present queue. Frame: " + swapChain.currentFrame());
sys.submit(presentQueue);
logger.debug("Presenting next image. Frame: " + swapChain.currentFrame());
if(swapChain.present(graphicsQueue)){
recreateSwapChain.set(true);
}
}
I already tried disabling the intermediate application rendering code, but it remained the same.
My questions are two:
I had temporarily bypassed the problem presentQueue.waitIdle()
before acquiring a new image from the SwapChain
, but it blocks the current thread preventing it from rendering the next frame until the current frame is presented. This is likely to cause performance issues.
So after some research I solved the problem simply by waiting on the current fence. The render
function becomes:
private void render(){
lock.lock();
try {
while(paused.get()){
pause.awaitUninterruptibly();
}
} finally {
lock.unlock();
}
//presentQueue.waitIdle(); // Temporary fix.
// This solves the problem. Waits for the fence at the
// current frame index.
sys.waitForFence();
sys.resetFence();
int imageIndex;
if((imageIndex = swapChain.acquire()) < 0 || recreateSwapChain.get()){
resize(); // SwapChain out of date. Recreate.
swapChain.acquire();
recreateSwapChain.set(false);
}
updateApplicationRendering();
for(IRenderer subRenderer : subRenderers.values()){
subRenderer.render();
}
sys.submit(presentQueue);
if(swapChain.present(graphicsQueue, imageIndex)){
recreateSwapChain.set(true);
}
}
Thanks to @KarlShultz for the suggestion. This tutorial may be useful.