当前位置: 首页 > news >正文

Vulkan demo入门教程三:逻辑设备、队列与交换链

Vulkan 嵌入式开发实战:逻辑设备、队列与交换链 (Swapchain)

系列回顾

  • [第一步] 我们创建了VkInstance并加载了扩展。
  • [第二步] 我们绕过了窗口系统,直接通过VK_KHR_display枚举了物理设备、选择了 HDMI 接口并创建了直连Surface

本章目标
有了“画布”(Surface),我们需要“画笔”和“颜料”。本文将完成以下核心任务:

  1. 选择队列族 (Queue Families):找到能画图 (Graphics) 和能上屏 (Present) 的硬件队列。
  2. 创建逻辑设备 (Logical Device):从物理 GPU 中切分出一部分资源供程序使用。
  3. 创建交换链 (Swapchain):管理前后端缓冲,实现双缓冲/三缓冲机制,防止画面撕裂。

本文主要是基于Direct Display (VK_KHR_display)扩展,详解在嵌入式 Linux环境下,如何从零构建 Vulkan 渲染环境。与桌面窗口系统不同,直连模式要求开发者显式管理物理显示属性(分辨率、刷新率),任何参数不匹配都将导致初始化失败。

第一步:初始化队列族 (Queue Families)

Vulkan 的硬件架构是异构的。GPU 内部包含不同的硬件单元(引擎),分别处理图形渲染、计算、视频解码和显示输出。我们需要查询物理设备支持的队列族,并筛选出满足需求的队列。

  • Graphics Queue: 执行绘图命令(Draw Calls)。
  • Present Queue: 将渲染完成的图像提交至显示控制器。

直连模式关键点
即使没有窗口系统,vkGetPhysicalDeviceSurfaceSupportKHR依然有效。它用于验证某个队列族是否支持向特定的VkDisplayModeKHR(Surface) 进行呈现。若队列不支持 Present,后续的vkQueuePresentKHR必败。

#include<vector>#include<cstdio>#include<vulkan/vulkan.h>// 假设这些是类成员变量VkPhysicalDevice mPhysicalDevice;VkSurfaceKHR mSurface;// 在直连模式下,Surface 对应一个 Display ModeboolmOnScreen=true;std::vector<VkQueueFamilyProperties>mQueueFamilies;std::vector<uint32_t>mGraphicsQueueFamilies;std::vector<uint32_t>mPresentQueueFamilies;uint32_tmGraphicsQueueFamily=UINT32_MAX;uint32_tmPresentQueueFamily=UINT32_MAX;intVkEngine::initQueueFamilies(){uint32_tqueueFamilyCount=0;// 1. 获取队列族数量vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice,&queueFamilyCount,nullptr);if(queueFamilyCount==0){printf("错误: 未找到任何队列族!\n");return-1;}mQueueFamilies.resize(queueFamilyCount);// 2. 获取队列族详细信息vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice,&queueFamilyCount,mQueueFamilies.data());printf("物理设备支持 %u 个队列族.\n",queueFamilyCount);// 3. 筛选支持 Graphics 的队列uint32_ti=0;for(constauto&queueFamily:mQueueFamilies){if(queueFamily.queueFlags&VK_QUEUE_GRAPHICS_BIT){mGraphicsQueueFamilies.push_back(i);}else{printf("队列族 %u 不支持图形渲染 (Graphics).\n",i);}i++;}if(mGraphicsQueueFamilies.empty()){printf("致命错误: 没有找到任何支持图形渲染的队列族!\n");return-1;}// 策略:优先选择第一个支持 Graphics 的队列族mGraphicsQueueFamily=mGraphicsQueueFamilies[0];printf("选定图形队列族索引: %u\n",mGraphicsQueueFamily);// 4. 筛选支持 Present 的队列 (仅在屏幕渲染模式下需要)if(mOnScreen){for(i=0;i<mQueueFamilies.size();i++){VkBool32 presentSupport=VK_FALSE;// 核心检查:验证该队列族是否支持向当前 Surface (Display Mode) 呈现vkGetPhysicalDeviceSurfaceSupportKHR(mPhysicalDevice,i,mSurface,&presentSupport);if(presentSupport==VK_TRUE){mPresentQueueFamilies.push_back(i);}else{printf("队列族 %u 不支持呈现 (Present) 到当前 Surface.\n",i);}}if(mPresentQueueFamilies.empty()){printf("致命错误: 没有找到任何支持呈现的队列族!\n");return-1;}// 策略:选择第一个支持 Present 的队列族// 优化:若 Graphics 和 Present 索引相同,可复用同一队列,减少同步开销mPresentQueueFamily=mPresentQueueFamilies[0];printf("选定呈现队列族索引: %u\n",mPresentQueueFamily);}return0;}

第二步:创建逻辑设备 (Logical Device)

物理设备代表真实硬件,应用程序通过逻辑设备 (VkDevice)与之交互。创建逻辑设备时,必须显式声明:

  1. 使用的队列族及其优先级。
  2. 启用的设备特性 (Features)
  3. 启用的设备扩展 (Extensions),例如VK_KHR_swapchain
VkDevice mDevice;VkQueue mGraphicsQueue;VkQueue mPresentQueue;std::vector<VkExtensionProperties>mDeviceExtensionProperties;// 需提前查询获取intVkEngine::createLogicalDevice(){VkDeviceCreateInfo createInfo{};createInfo.sType=VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;// (1) 配置队列创建信息std::vector<VkDeviceQueueCreateInfo>queueCreateInfos;floatqueuePriority=1.0f;// 优先级范围 0.0 ~ 1.0// (1-1) 配置图形队列VkDeviceQueueCreateInfo graphicsQueueCreateInfo{};graphicsQueueCreateInfo.sType=VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;graphicsQueueCreateInfo.queueFamilyIndex=mGraphicsQueueFamily;graphicsQueueCreateInfo.queueCount=1;graphicsQueueCreateInfo.pQueuePriorities=&queuePriority;queueCreateInfos.push_back(graphicsQueueCreateInfo);// (1-2) 配置呈现队列// 注意:若图形队列与呈现队列索引相同,Vulkan 驱动会自动去重,但显式分开写更清晰if(mGraphicsQueueFamily!=mPresentQueueFamily){VkDeviceQueueCreateInfo presentQueueCreateInfo{};presentQueueCreateInfo.sType=VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;presentQueueCreateInfo.queueFamilyIndex=mPresentQueueFamily;presentQueueCreateInfo.queueCount=1;presentQueueCreateInfo.pQueuePriorities=&queuePriority;queueCreateInfos.push_back(presentQueueCreateInfo);}createInfo.queueCreateInfoCount=static_cast<uint32_t>(queueCreateInfos.size());createInfo.pQueueCreateInfos=queueCreateInfos.data();// (2) 设置设备特性 (Features)// 默认全关闭。如需多重采样、几何着色器等,需在此处显式开启VkPhysicalDeviceFeatures deviceFeatures{};createInfo.pEnabledFeatures=&deviceFeatures;// (3) 启用所需的设备扩展std::vector<constchar*>extensions;if(mOnScreen){extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);}// (3-1) 运行时验证扩展支持 (防御性编程)for(constauto&extension:extensions){boolcheckSupport=false;for(constauto&phydevExt:mDeviceExtensionProperties){if(strcmp(phydevExt.extensionName,extension)==0){checkSupport=true;break;}}if(!checkSupport){printf("错误: 物理设备不支持必需的扩展:%s\n",extension);return-1;}}createInfo.enabledExtensionCount=static_cast<uint32_t>(extensions.size());createInfo.ppEnabledExtensionNames=extensions.data();// 已废弃的验证层字段 (现代 Vulkan 推荐在 Instance 层处理)createInfo.enabledLayerCount=0;// (4) 创建逻辑设备if(vkCreateDevice(mPhysicalDevice,&createInfo,nullptr,&mDevice)!=VK_SUCCESS){printf("致命错误: 无法创建逻辑设备!\n");return-1;}printf("逻辑设备创建成功.\n");// (5) 从逻辑设备获取队列句柄// 即使两个队列来自同一个家族,获取到的 VkQueue 句柄也是独立的逻辑对象vkGetDeviceQueue(mDevice,mGraphicsQueueFamily,0,&mGraphicsQueue);vkGetDeviceQueue(mDevice,mPresentQueueFamily,0,&mPresentQueue);printf("图形队列和呈现队列获取成功.\n");return0;}

第三步:创建交换链 (Swapchain)

交换链是图像缓冲区的集合。我们在其中一个缓冲区渲染,然后将其“呈现”到屏幕,并切换到下一个缓冲区。
直连模式的核心约束imageExtent(分辨率) 必须严格匹配选定的VkDisplayModeKHRvisibleRegion,不可随意设定。

VkSwapchainKHR mSwapChain;VkFormat mImageFormat;VkExtent2D mExtent;std::vector<VkImage>mImages;std::vector<VkImageView>mImageViews;VkSurfaceFormatKHR mSurfaceFormat;// 假设 mSurfaceFormats, mSurfacePresentModes, mSurfaceCapabilities, mSelectDisplayModeProperty 已在前序步骤获取intVkEngine::createSwapchain(){VkSwapchainCreateInfoKHR createInfo{};createInfo.sType=VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;createInfo.surface=mSurface;// (1) 选择图像格式// 策略:首选 B8G8R8A8 SRGB,若不支持则回退到第一个可用格式VkSurfaceFormatKHR chosenFormat;boolformatFound=false;VkSurfaceFormatKHR preferredFormat;preferredFormat.format=VK_FORMAT_B8G8R8A8_SRGB;preferredFormat.colorSpace=VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;for(constauto&availableFormat:mSurfaceFormats){if(availableFormat.format==preferredFormat.format&&availableFormat.colorSpace==preferredFormat.colorSpace){chosenFormat=availableFormat;formatFound=true;break;}}if(!formatFound){printf("警告: 首选格式不可用,使用默认可用格式.\n");chosenFormat=mSurfaceFormats[0];}mImageFormat=chosenFormat.format;createInfo.imageFormat=chosenFormat.format;createInfo.imageColorSpace=chosenFormat.colorSpace;printf("选定 Swapchain 格式: %d, 颜色空间: %d\n",chosenFormat.format,chosenFormat.colorSpace);// (2) 选择呈现模式 (Present Mode)// FIFO (垂直同步) 是最广泛支持且无撕裂的模式VkPresentModeKHR chosenPresentMode=VK_PRESENT_MODE_FIFO_KHR;boolmodeFound=false;for(constauto&availableMode:mSurfacePresentModes){if(availableMode==chosenPresentMode){modeFound=true;break;}}if(!modeFound){// 理论上 FIFO 是必须支持的,若缺失则表明驱动实现有问题printf("致命错误: 不支持 VK_PRESENT_MODE_FIFO_KHR!\n");return-1;}createInfo.presentMode=chosenPresentMode;// (3) 设置分辨率 (Extent) - 直连模式关键// 必须使用 DisplayMode 定义的分辨率,不能像窗口模式那样自由调整VkExtent2D extent=mSelectDisplayModeProperty.parameters.visibleRegion;// 双重检查:确保分辨率在 Surface 能力范围内if(extent.width<mSurfaceCapabilities.minImageExtent.width||extent.width>mSurfaceCapabilities.maxImageExtent.width||extent.height<mSurfaceCapabilities.minImageExtent.height||extent.height>mSurfaceCapabilities.maxImageExtent.height){printf("错误: 选定的分辨率超出 Surface 支持范围!\n");return-1;}mExtent=extent;createInfo.imageExtent=extent;printf("设定 Swapchain 分辨率: %ux%u\n",extent.width,extent.height);// (4) 设置图像数量 (缓冲区长度的深度)// 通常设为 3 (三重缓冲),需遵守 min/max 限制uint32_timageCount=3;if(mSurfaceCapabilities.maxImageCount>0&&imageCount>mSurfaceCapabilities.maxImageCount){imageCount=mSurfaceCapabilities.maxImageCount;}if(imageCount<mSurfaceCapabilities.minImageCount){imageCount=mSurfaceCapabilities.minImageCount;}createInfo.minImageCount=imageCount;printf("设定 Swapchain 图像数量: %u\n",imageCount);// (5) 其他配置createInfo.imageArrayLayers=1;// 单图层,除非是 VR 应用createInfo.imageUsage=VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;// 作为颜色附件渲染// (6) 队列共享模式uint32_tqueueFamilyIndices[]={mGraphicsQueueFamily,mPresentQueueFamily};if(mGraphicsQueueFamily!=mPresentQueueFamily){// 队列不同:必须设为 CONCURRENT,避免所有权转移开销createInfo.imageSharingMode=VK_SHARING_MODE_CONCURRENT;createInfo.queueFamilyIndexCount=2;createInfo.pQueueFamilyIndices=queueFamilyIndices;}else{// 队列相同:Exclusive 模式性能略优createInfo.imageSharingMode=VK_SHARING_MODE_EXCLUSIVE;}// (7) 变换 (Transform)// 直连屏幕通常不需要旋转,使用 IDENTITYcreateInfo.preTransform=VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;if(!(mSurfaceCapabilities.supportedTransforms&createInfo.preTransform)){// 若不支持 Identity,回退到当前变换createInfo.preTransform=mSurfaceCapabilities.currentTransform;}// (8) Alpha 混合与裁剪createInfo.compositeAlpha=VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;// 不透明createInfo.clipped=VK_TRUE;// 忽略被遮挡像素的性能优化createInfo.oldSwapchain=VK_NULL_HANDLE;// (9) 创建 Swapchainif(vkCreateSwapchainKHR(mDevice,&createInfo,nullptr,&mSwapChain)!=VK_SUCCESS){printf("致命错误: 无法创建 Swapchain!\n");return-1;}printf("Swapchain 创建成功!\n");// (10) 获取 Swapchain 中的图像句柄// 注意:此处仅获取 VkImage 句柄,后续需手动创建 VkImageViewuint32_trealImageCount;vkGetSwapchainImagesKHR(mDevice,mSwapChain,&realImageCount,nullptr);mImages.resize(realImageCount);vkGetSwapchainImagesKHR(mDevice,mSwapChain,&realImageCount,mImages.data());printf("成功获取 %u 个 Swapchain 图像.\n",realImageCount);return0;}

第四步:资源清理 (Cleanup)

Vulkan 要求显式管理所有资源。当程序退出或需要重建交换链(如显示模式切换)时,必须按依赖关系的反向顺序销毁资源。

// 假设成员变量std::vector<VkFramebuffer>mFramebuffers;VkPipeline mGraphicsPipeline;VkPipelineLayout mPipelineLayout;VkRenderPass mRenderPass;// 同步对象 (semaphores, fences) 假设已有 cleanSyncObjects() 函数voidcleanSyncObjects();voidVkEngine::cleanupSwapChain(){// 1. 等待设备空闲,确保没有正在使用的资源vkDeviceWaitIdle(mDevice);// 2. 清理同步对象cleanSyncObjects();// 3. 销毁帧缓冲区 (依赖 ImageView)for(autoframebuffer:mFramebuffers){vkDestroyFramebuffer(mDevice,framebuffer,nullptr);}mFramebuffers.clear();// 4. 销毁管线相关 (依赖 RenderPass 和 PipelineLayout)vkDestroyPipeline(mDevice,mGraphicsPipeline,nullptr);vkDestroyPipelineLayout(mDevice,mPipelineLayout,nullptr);vkDestroyRenderPass(mDevice,mRenderPass,nullptr);// 5. 销毁图像视图 (ImageView)// 注意:不要销毁 mImages 本身,它们由 Swapchain 统一管理for(autoimageView:mImageViews){vkDestroyImageView(mDevice,imageView,nullptr);}mImageViews.clear();mImages.clear();// 仅清空句柄列表// 6. 销毁 Swapchainif(mSwapChain!=VK_NULL_HANDLE){vkDestroySwapchainKHR(mDevice,mSwapChain,nullptr);mSwapChain=VK_NULL_HANDLE;}// 注意:mSurface 通常在销毁 Instance 前或整个引擎析构时统一销毁// 若是因分辨率变化重建 Swapchain,Surface 应保持不变}

关键技术点解析

1. 队列族的复用与共享模式

  • 同队列族(Graphics==Present):
    • imageSharingMode设为VK_SHARING_MODE_EXCLUSIVE
    • 优势:无需额外的所有权转移操作,性能最优。
  • 异队列族(Graphics!=Present):
    • imageSharingMode必须设为VK_SHARING_MODE_CONCURRENT
    • 必须在pQueueFamilyIndices中传入两个队列索引。
    • 风险:若设为EXCLUSIVE,在vkQueuePresent时会因图像所有权未从 Graphics 队列转移到 Present 队列而报错。

2. 直连模式的分辨率铁律

在桌面窗口系统中,currentExtent可由用户动态调整。但在嵌入式直连模式下:

  • imageExtent必须等于VkDisplayModePropertiesKHR::parameters.visibleRegion
  • 任何偏差(即使是一个像素)都可能导致vkCreateSwapchainKHR返回VK_ERROR_INITIALIZATION_FAILED或导致黑屏。

3. 格式回退策略

嵌入式 GPU 的格式支持差异巨大。

  • 稳健策略:优先尝试VK_FORMAT_B8G8R8A8_SRGB
  • 兜底方案:若首选不可用,直接使用mSurfaceFormats[0]
  • 强行指定 unsupported 格式是导致初始化失败的常见原因。

下一步预告

至此,我们已完成 Vulkan 初始化的核心基础设施:

  • ✅ 逻辑设备 (VkDevice)
  • ✅ 队列句柄 (VkQueue)
  • ✅ 交换链 (VkSwapchain) 及底层图像资源 (VkImage)

接下来的内容将进入渲染管线构建阶段:

  1. ImageView 创建:如何将VkImage包装为着色器可读的视图。
  2. Render Pass 定义:描述附件的加载/存储操作及子依赖关系。
  3. Graphics Pipeline 构建:整合 Shader、顶点输入、光栅化状态等,打造不可变的渲染状态对象。
http://www.jsqmd.com/news/491666/

相关文章:

  • AI绘画重塑游戏美术设计全流程
  • 前架构师转行AI风水师:给机房看罗盘——软件测试从业者的专业启示
  • TypeScript+React 全栈生态实战:从架构选型到工程落地,告别开发踩坑
  • Stable Diffusion原理解析与实战
  • 毕业季求生指南:如何用百考通AI,一站式搞定论文全流程?
  • 2026 ChatGPT技术深度拆解:架构演进与国内镜像站实测
  • 揭秘谷歌Nano图像生成核心技术
  • 大厂朋友AI转型屡屡碰壁?揭秘AI产品经理正确入门路径,避开这些坑!
  • CHATGPT-5.4技术深度拆解:计算机操作、工具搜索与百万级上下文的架构革命
  • 服务器防御怎么选择更合适?
  • ChatGPT-4o颠覆数学建模与AI绘画
  • MATLAB模拟ADS-B数据解码与信号处理整体流程
  • 沈阳示剑网络是怎么做GEO优化的?
  • 2026年工业研磨泵厂家推荐:均质研磨泵/液体肥研磨泵/化工研磨泵专业供应 - 品牌推荐官
  • [特殊字符] | OpenClaw威胁模型:MAESTRO框架分析
  • 全文 - OpenPattern project: a comprehensivemodular routing platform
  • 《电力变压器绝缘油高含气量故障分析及真空处理标准操作规范》
  • 如何集成单点登录和设计子系统的权限管理三
  • 抽水试验水位监测设备厂家哪家好? - WHSENSORS
  • sqli-labs-Less-48
  • 2026年企业如何选对HR系统?
  • Kali Linux命令行新手入门:从0到1轻松上手
  • MES / ERP后台原型设计思路:一套数字工厂系统实战案例拆解
  • 几款免费工具深度实测,论文AI率一键从65%降至14%
  • 快速运行matlab仿真方法
  • OpenClaw Windows 安装指南
  • 技术干货|AS05-26S12和LS05-26B12R3性能及兼容性对比,工业电路适配指南
  • FusionBrain团队发明了让AI更聪明地“多思考几种解法“的新方法
  • 人工智能基础知识笔记三十七:Agent AI威胁建模----主流框架与工具全景比较
  • BUCK小信号模型的传递函数控制框图