multithreading – Vulkan C++ multi-threading crashes when trying to submit commands on main thread

In short there is something happening in drawFrame() and my thread function that can’t both happen at the same time.

I’m trying to get multi threading working in my voxel engine, my engine is very similar to the official Khronos tutorial code for Vulkan C++. There is a drawFrame() function which is the only rendering related function I want to have in the main thread. At first, the validation layers told me that I was submitting to the same queue from multiple threads, so I made another queue for drawFrame() and now it just goes to a Windows blue screen and restarts my computer… It works fine if drawFrame() is also in the other thread, so I know it’s not a problem with the queue or anything. So obviously something that is being used in the other thread function is being used in the drawFrame() function, but I can’t figure out what it is and I am tired of restarting my computer lol any help would be greatly appreciated. This is drawFrame():

void drawFrame() {

    vkWaitForFences(device, 1, &inFlightFences(currentFrame), VK_TRUE, UINT64_MAX);

    uint32_t imageIndex;
    VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores(currentFrame), VK_NULL_HANDLE, &imageIndex);

    if (result == VK_ERROR_OUT_OF_DATE_KHR) {
        recreateSwapChain();
        return;
    }
    else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
        throw std::runtime_error("failed to acquire swap chain image!");
    }

    updateTransformBuffers(imageIndex);

    if (imagesInFlight(imageIndex) != VK_NULL_HANDLE) {
        vkWaitForFences(device, 1, &imagesInFlight(imageIndex), VK_TRUE, UINT64_MAX);
    }
    imagesInFlight(imageIndex) = inFlightFences(currentFrame);


    VkSubmitInfo submitInfo{};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;

    VkSemaphore waitSemaphores() = { imageAvailableSemaphores(currentFrame) };
    VkPipelineStageFlags waitStages() = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
    submitInfo.waitSemaphoreCount = 1;
    submitInfo.pWaitSemaphores = waitSemaphores;
    submitInfo.pWaitDstStageMask = waitStages;

    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &graphicsCommandBuffer(imageIndex);

    VkSemaphore signalSemaphores() = { renderFinishedSemaphores(currentFrame) };
    submitInfo.signalSemaphoreCount = 1;
    submitInfo.pSignalSemaphores = signalSemaphores;

    vkResetFences(device, 1, &inFlightFences(currentFrame));

    if (vkQueueSubmit(graphicsQueue2, 1, &submitInfo, inFlightFences(currentFrame)) != VK_SUCCESS) {
        throw std::runtime_error("failed to submit draw command buffer!");
    }


    VkPresentInfoKHR presentInfo{};
    presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;

    presentInfo.waitSemaphoreCount = 1;
    presentInfo.pWaitSemaphores = signalSemaphores;

    VkSwapchainKHR swapChains() = { swapChain };
    presentInfo.swapchainCount = 1;
    presentInfo.pSwapchains = swapChains;

    presentInfo.pImageIndices = &imageIndex;

    result = vkQueuePresentKHR(presentQueue, &presentInfo);

    if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) {
        framebufferResized = false;
        recreateSwapChain();
    }
    else if (result != VK_SUCCESS) {
        throw std::runtime_error("failed to present swap chain image!");
    }

    currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
}

In the thread function, the only Vulkan stuff I am doing is copying buffers and mapping them and changing them and whatnot like this:

        uint32_t bufferSize = sizeof(Vertex) * model.verts.size();
        void* data;
        vkMapMemory(device, unifiedBufferMesh.vertexStagingBufferMemory, 0, bufferSize, 0, &data);
        memcpy(data, model.verts.data(), (size_t)bufferSize);
        vkUnmapMemory(device, unifiedBufferMesh.vertexStagingBufferMemory);

        bufferSize = sizeof(uint32_t) * model.inds.size();
        vkMapMemory(device, unifiedBufferMesh.indexStagingBufferMemory, 0, bufferSize, 0, &data);
        memcpy(data, model.inds.data(), (size_t)bufferSize);
        vkUnmapMemory(device, unifiedBufferMesh.indexStagingBufferMemory);

        VkCommandBuffer commandBuffer = beginSingleTimeCommands();
        vkCmdCopyBuffer(commandBuffer, unifiedBufferMesh.vertexStagingBuffer, unifiedBufferMesh.vertexBuffer, 1, &unifiedBufferMesh.unifiedBufferChunks(indexInUnifiedBufferChunks).vertexRegion);
        vkCmdCopyBuffer(commandBuffer, unifiedBufferMesh.indexStagingBuffer, unifiedBufferMesh.indexBuffer, 1, &unifiedBufferMesh.unifiedBufferChunks(indexInUnifiedBufferChunks).indexRegion);
        endSingleTimeCommands(commandBuffer);

The begin/endSingleTimeCommand() functions:

VkCommandBuffer beginSingleTimeCommands() {
    VkCommandBufferAllocateInfo allocInfo{};
    allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    allocInfo.commandPool = graphicsCommandPool;
    allocInfo.commandBufferCount = 1;

    VkCommandBuffer commandBuffer;
    vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);

    VkCommandBufferBeginInfo beginInfo{};
    beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

    vkBeginCommandBuffer(commandBuffer, &beginInfo);

    return commandBuffer;
}
void endSingleTimeCommands(VkCommandBuffer commandBuffer) {
    vkEndCommandBuffer(commandBuffer);

    VkSubmitInfo submitInfo{};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &commandBuffer;

    vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
    vkQueueWaitIdle(graphicsQueue);

    vkFreeCommandBuffers(device, graphicsCommandPool, 1, &commandBuffer);
}

And I’ve tried binding on either thread but on the main thread I’ve got a check for when the other thread is done so it doesn’t try binding to something that is being written to. Everything also works if drawFrame() is in that check too, calling it only when we know the other thread is done. But I need to be able to drawFrame() and copy those buffers at the same time.