使用C++ vulkan-hpp绑定创建Vulkan交换链时触发访问违例错误
问题描述
我正在使用C++ vulkan-hpp绑定学习Vulkan开发,在跟随Vulkan教程学习、参考RAII Samples理解基础逻辑、搭建可渲染三角形的简单应用过程中,创建交换链时遇到如下错误:
0xC0000005: Access violation executing location 0x0000000000000000
调试器显示错误触发于SwapchainKHR类的构造函数执行阶段,具体位于vulkan_raii头文件的第9337行。
同类错误通常由创建交换链所需的扩展缺失导致,但我在代码中已确认添加了对应扩展,且使用这些扩展完成了逻辑设备的初始化,开启验证层也未能捕获到该错误。以下是可复现该问题的完整示例代码:
#include <iostream> #define GLFW_INCLUDE_VULKAN #include <GLFW/glfw3.h> #include <vulkan/vulkan.hpp> #include <vulkan/vulkan_raii.hpp> vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<vk::SurfaceFormatKHR> &formatsList) { for (vk::SurfaceFormatKHR const &availableFormat : formatsList) { if (availableFormat.format == vk::Format::eB8G8R8A8Srgb && availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) return availableFormat; } return formatsList.front(); } vk::Extent2D chooseSwapExtent(uint32_t width, uint32_t height, const vk::SurfaceCapabilitiesKHR &surfaceCapabilities) { vk::Extent2D swapchainExtent; if (surfaceCapabilities.currentExtent.width == std::numeric_limits<uint32_t>::max()) { // If the surface size is undefined, the size is set to the size of the images requested. swapchainExtent.width = std::clamp(width, surfaceCapabilities.minImageExtent.width, surfaceCapabilities.maxImageExtent.width); swapchainExtent.height = std::clamp(height, surfaceCapabilities.minImageExtent.height, surfaceCapabilities.maxImageExtent.height); } else { // If the surface size is defined, the swap chain size must match swapchainExtent = surfaceCapabilities.currentExtent; } return swapchainExtent; } void findGraphicsAndPresentQueueFamilyIndices(std::vector<vk::QueueFamilyProperties> const &queueFamilyProperties, uint32_t &graphicsIndex, uint32_t &presentIndex, const vk::raii::PhysicalDevice &gpu, const vk::raii::SurfaceKHR &surface) { auto graphicsQueueFamilyProperty = std::find_if(queueFamilyProperties.begin(), queueFamilyProperties.end(), [](vk::QueueFamilyProperties const &qfp) { return qfp.queueFlags & vk::QueueFlagBits::eGraphics; }); assert(graphicsQueueFamilyProperty != queueFamilyProperties.end()); uint32_t graphicsQueueFamilyIndex = static_cast<uint32_t>(std::distance(queueFamilyProperties.begin(), graphicsQueueFamilyProperty)); graphicsIndex = graphicsQueueFamilyIndex; // get present family index uint32_t presentQueueFamilyIndex = gpu.getSurfaceSupportKHR(graphicsIndex, *surface) ? graphicsIndex : static_cast<uint32_t>(queueFamilyProperties.size()); if (presentQueueFamilyIndex == queueFamilyProperties.size()) { // the graphicsQueueFamilyIndex doesn't support present -> look for an other family index that supports both // graphics and present for (size_t i = 0; i < queueFamilyProperties.size(); i++) { if ((queueFamilyProperties[i].queueFlags & vk::QueueFlagBits::eGraphics) && gpu.getSurfaceSupportKHR(static_cast<uint32_t>(i), *surface)) { graphicsQueueFamilyIndex = static_cast<uint32_t>(i); presentQueueFamilyIndex = graphicsIndex; break; } } } if (presentQueueFamilyIndex == queueFamilyProperties.size()) { // there's nothing like a single family index that supports both graphics and present -> look for an other // family index that supports present for (size_t i = 0; i < queueFamilyProperties.size(); i++) { if (gpu.getSurfaceSupportKHR(static_cast<uint32_t>(i), *surface)) { presentQueueFamilyIndex = static_cast<uint32_t>(i); break; } } } if ((graphicsQueueFamilyIndex == queueFamilyProperties.size()) || (presentQueueFamilyIndex == queueFamilyProperties.size())) { throw std::runtime_error("Could not find a queue for graphics or present -> terminating"); } graphicsIndex = graphicsQueueFamilyIndex; presentIndex = presentQueueFamilyIndex; } int main() { glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); auto window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr); vk::raii::Context raiContext; // get all extensions required by glfw uint32_t glfwExtensionCount = 0; const char **glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); std::vector<char const *> instanceExtensionNames(glfwExtensions, glfwExtensions + glfwExtensionCount); instanceExtensionNames.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); std::vector<char const *> deviceExtensionNames; deviceExtensionNames.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); // create instance vk::ApplicationInfo appInfo("VulkanTest", 1, "Vulkan", VK_API_VERSION_1_1); vk::InstanceCreateInfo instanceCreateInfo({}, &appInfo, {}, instanceExtensionNames); auto instance = vk::raii::Instance(raiContext, instanceCreateInfo); // create window surface VkSurfaceKHR _surface; if (glfwCreateWindowSurface(static_cast<VkInstance>(*instance), window, nullptr, &_surface) != VK_SUCCESS) throw std::runtime_error("Failed to create window surface!"); vk::raii::SurfaceKHR surface (instance, _surface); // select physical device auto gpu = vk::raii::PhysicalDevice(std::move(instance.enumeratePhysicalDevices().front())); // get graphics and present index auto queueFamilies = gpu.getQueueFamilyProperties(); uint32_t graphicsIndex = -1; uint32_t presentIndex = -1; findGraphicsAndPresentQueueFamilyIndices(queueFamilies, graphicsIndex, presentIndex, gpu, surface); if(graphicsIndex < 0 || presentIndex < 0) throw std::runtime_error("Cannot find a valid graphics or present queue"); // create logical device float queuePriority = 0.0f; auto deviceQueueCreateInfo = vk::DeviceQueueCreateInfo(vk::DeviceQueueCreateFlags(), static_cast<uint32_t>(graphicsIndex), 1, &queuePriority); vk::DeviceCreateInfo deviceCreateInfo(vk::DeviceCreateFlags(), deviceQueueCreateInfo, {}, deviceExtensionNames, nullptr); auto device = gpu.createDevice(vk::DeviceCreateInfo(vk::DeviceCreateFlags(), deviceQueueCreateInfo)); // create swap chain std::vector<vk::SurfaceFormatKHR> formats = gpu.getSurfaceFormatsKHR(*surface); assert(!formats.empty()); int windowWidth, windowHeight; glfwGetFramebufferSize(window, &windowWidth, &windowHeight); vk::SurfaceCapabilitiesKHR surfaceCapabilities = gpu.getSurfaceCapabilitiesKHR(*surface); vk::Extent2D swapChainExtent = chooseSwapExtent(windowWidth, windowHeight, surfaceCapabilities); vk::SurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(formats); vk::PresentModeKHR presentMode = vk::PresentModeKHR::eFifo; uint32_t imageCount = surfaceCapabilities.minImageCount + 1; if (surfaceCapabilities.maxImageCount > 0 && imageCount > surfaceCapabilities.maxImageCount) { imageCount = surfaceCapabilities.maxImageCount; } // not using constructor for clarity vk::SwapchainCreateInfoKHR swapChainCreateInfo; swapChainCreateInfo.setFlags(vk::SwapchainCreateFlagsKHR()); swapChainCreateInfo.setSurface(*surface); swapChainCreateInfo.setMinImageCount(imageCount); swapChainCreateInfo.setImageFormat(surfaceFormat.format); swapChainCreateInfo.setImageColorSpace(surfaceFormat.colorSpace); swapChainCreateInfo.setImageExtent(swapChainExtent); swapChainCreateInfo.setImageArrayLayers(1); swapChainCreateInfo.setImageUsage(vk::ImageUsageFlagBits::eColorAttachment); swapChainCreateInfo.setImageSharingMode(vk::SharingMode::eExclusive); swapChainCreateInfo.setPreTransform(surfaceCapabilities.currentTransform); swapChainCreateInfo.setCompositeAlpha(vk::CompositeAlphaFlagBitsKHR::eOpaque); swapChainCreateInfo.setPresentMode(presentMode); swapChainCreateInfo.setClipped(true); vk::raii::SwapchainKHR swapChain( device, swapChainCreateInfo ); while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } }
错误原因
崩溃的核心原因是逻辑设备创建时未启用交换链扩展:
代码中虽然正确定义了包含VK_KHR_SWAPCHAIN_EXTENSION_NAME的deviceCreateInfo,但实际调用gpu.createDevice时传入的是临时构造的vk::DeviceCreateInfo对象,这个临时对象没有传入设备扩展列表,导致创建出的逻辑设备根本没有加载交换链相关函数,vkCreateSwapchainKHR函数指针为空,调用时直接跳转到0地址触发访问违例,这也是验证层无法捕获错误的原因——崩溃发生在进入Vulkan驱动校验逻辑之前。
代码中另外存在两个隐藏问题:
- 用
uint32_t类型存储队列索引时初始化为-1,后续判断graphicsIndex < 0永远不会成立,因为无符号整数不可能小于0,索引校验逻辑完全失效。 - 交换链创建时固定使用
vk::SharingMode::eExclusive模式,当图形队列和呈现队列不属于同一个队列族时,没有传入对应的队列索引列表,后续会出现呈现错误。
修复方法
- 修改逻辑设备创建代码,直接使用提前配置好扩展的
deviceCreateInfo,替换原有临时构造的创建信息:// 原错误代码:auto device = gpu.createDevice(vk::DeviceCreateInfo(vk::DeviceCreateFlags(), deviceQueueCreateInfo)); auto device = gpu.createDevice(deviceCreateInfo); - 修正队列索引的初始化和校验逻辑,将索引起始值设为
std::numeric_limits<uint32_t>::max(),通过判断索引是否等于该值确认队列是否查找成功。 - 补充交换链的队列共享模式配置:如果图形队列索引和呈现队列索引不一致,将
imageSharingMode改为vk::SharingMode::eConcurrent,并传入两个队列族的索引。
内容的提问来源于stack exchange,提问作者Resident-Rock6028
相关产品推荐
相关产品推荐

