Недавно я создал свой старый проект Vulkan, написанный на Java с помощью LWJGL. Пока я тестировал его, чтобы начать модифицировать, я заметил следующую ошибку проверки, генерируемую для каждого кадра:
[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
Я не мог вспомнить, чтобы эта ошибка присутствовала в моем старом проекте, и это странно, потому что я не вносил никаких изменений в сам движок. Я только что перенес проект с Windows на Linux. Кроме того, проблема НЕ приводит к сбою программы, которая работает нормально. Просто консоль заливает, а это признак того, что что-то не так...
Это код в классе SwapChain
:
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;
}
Создание семафора происходит в конструкторе:
semaphores = new SyncSemaphores[nImages];
for(int i = 0; i < nImages; i++){
semaphores[i] = new SyncSemaphores(device);
}
Это функции отправки в очередь:
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);
}
}
И в классе Queue:
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);
}
}
А это функция рендеринга, называемая каждым кадром:
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);
}
}
Я уже пробовал отключать код рендеринга промежуточного приложения, но он остался прежним.
У меня два вопроса:
Во-первых, уровень проверки со временем развивался и улучшался, особенно в области проверки синхронизации. Неудивительно, что старый код, выполняемый на новом уровне проверки, может генерировать отчеты о новых ошибках.
Кроме того, генерация ошибки не должна зависеть от платформы. Поведение может быть разным: на одних происходит сбой, а на других работает.
Что касается кода, я не совсем уверен, что полностью его придерживаюсь, но похоже, что вы слишком много думаете об индексе изображения, возвращаемом AcquireNextImage. Индексы могут быть возвращены в любом порядке и не всегда будут равны или синхронизированы с переменной номера кадра. Это, вероятно, приводит к выборке семафора для текущего кадра, что приводит к ошибке.
Я бы предложил сохранить индекс изображения, возвращаемый AcquireNextImage, в ваших покадровых данных, а не назначать индекс изображения кадру в той же функции. И используйте этот индекс в текущей функции, чтобы получить изображение. Короче говоря, вы путаете индексы, используемые для получения семафоров.
Добавление WaitIdle подходит для отладки и изучения проблемы, но оно не решает проблему, на которую я указал: ваши индексы кадров/изображений путаются. Некоторые образцы ждут определенного ограничения, прежде чем получить изображение, и я не вижу, чтобы вы ждали здесь каких-либо ограничений, и я не решаюсь предложить это до тех пор, пока проблема с индексом не будет решена. См. это руководство (также раздел «Кадры в полете») и этот репозиторий для получения дополнительной помощи.
Я временно обошел проблему presentQueue.waitIdle()
перед получением нового изображения из SwapChain
, но он блокирует текущий поток, не позволяя ему отображать следующий кадр до тех пор, пока не будет представлен текущий кадр. Это может вызвать проблемы с производительностью.
Итак, после некоторых исследований я решил проблему, просто подождав текущего забора. Функция render
становится:
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);
}
}
Спасибо @KarlShultz за предложение. Этот урок может быть полезен.
Это, конечно, была проблема в моем коде, но проблема оказалась в том, что я не ждал завершения задач очереди для каждого кадра. Мне пришлось вызвать vkDeviceWaitIdle, прежде чем получить другое изображение.