Ошибка проверки Vulkan: «Семафор не должен иметь ожидающих операций». из vkAcquireNextImageKHR

Недавно я создал свой старый проект 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);
        }
    }

Я уже пробовал отключать код рендеринга промежуточного приложения, но он остался прежним.

У меня два вопроса:

  • Вероятно, проблема связана с неправильной синхронизацией, но как?
  • Почему эта проблема кажется специфичной для платформы? К сожалению, я больше не могу тестировать программу в Windows, но, насколько я помню, у меня никогда не возникало этой проблемы.
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
86
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Во-первых, уровень проверки со временем развивался и улучшался, особенно в области проверки синхронизации. Неудивительно, что старый код, выполняемый на новом уровне проверки, может генерировать отчеты о новых ошибках.

Кроме того, генерация ошибки не должна зависеть от платформы. Поведение может быть разным: на одних происходит сбой, а на других работает.

Что касается кода, я не совсем уверен, что полностью его придерживаюсь, но похоже, что вы слишком много думаете об индексе изображения, возвращаемом AcquireNextImage. Индексы могут быть возвращены в любом порядке и не всегда будут равны или синхронизированы с переменной номера кадра. Это, вероятно, приводит к выборке семафора для текущего кадра, что приводит к ошибке.

Я бы предложил сохранить индекс изображения, возвращаемый AcquireNextImage, в ваших покадровых данных, а не назначать индекс изображения кадру в той же функции. И используйте этот индекс в текущей функции, чтобы получить изображение. Короче говоря, вы путаете индексы, используемые для получения семафоров.

Это, конечно, была проблема в моем коде, но проблема оказалась в том, что я не ждал завершения задач очереди для каждого кадра. Мне пришлось вызвать vkDeviceWaitIdle, прежде чем получить другое изображение.

Francesco Sollazzi 19.07.2024 18:07

Добавление WaitIdle подходит для отладки и изучения проблемы, но оно не решает проблему, на которую я указал: ваши индексы кадров/изображений путаются. Некоторые образцы ждут определенного ограничения, прежде чем получить изображение, и я не вижу, чтобы вы ждали здесь каких-либо ограничений, и я не решаюсь предложить это до тех пор, пока проблема с индексом не будет решена. См. это руководство (также раздел «Кадры в полете») и этот репозиторий для получения дополнительной помощи.

Karl Schultz 19.07.2024 19:42
Ответ принят как подходящий

Я временно обошел проблему 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 за предложение. Этот урок может быть полезен.

Другие вопросы по теме