Ahao's Technical Blog Ahao's Technical Blog
首页
  • 001.基础篇
  • 002.玩转AOSP篇
  • 003.学穿Binder篇
  • 004.基础组件篇
  • 005.系统启动过程分析
  • 006.Hal开发入门与实践
  • 007.显示系统
  • 008.核心系统服务
  • 009.输入系统
  • 010.开发工具
  • Vulkan
  • OpenGL
  • Skia
  • 001.语法相关
  • 002.Android相关
  • 003.Linux相关
  • 004.环境配置
  • 001.Java
  • 002.Kotlin
  • 003.C/C++
  • 004.脚本类
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

阿豪讲Framework

不积跬步无以至千里
首页
  • 001.基础篇
  • 002.玩转AOSP篇
  • 003.学穿Binder篇
  • 004.基础组件篇
  • 005.系统启动过程分析
  • 006.Hal开发入门与实践
  • 007.显示系统
  • 008.核心系统服务
  • 009.输入系统
  • 010.开发工具
  • Vulkan
  • OpenGL
  • Skia
  • 001.语法相关
  • 002.Android相关
  • 003.Linux相关
  • 004.环境配置
  • 001.Java
  • 002.Kotlin
  • 003.C/C++
  • 004.脚本类
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Vulkan

    • 001.Vulkan 整体架构
    • 002.Vulkan绘制一个三角形1——概述
    • 003.Vulkan绘制一个三角形2——初始化过程分析1
      • 1. 初始化 (Initialization)阶段总览
      • 2. VkInstance
        • 2.1 extension
        • 2.2 layer
      • 3. VkPhysicalDevice
      • 4. VkDevice、VkQueue、Queue Family
        • Queue Family
        • VkDevice
        • VkQueue
        • 三者的联系
      • VkSurfaceKHR
    • 004.Vulkan绘制一个三角形3——初始化过程分析2
  • OpenGL

  • Skia

  • Graphic
  • Vulkan
ahao
2026-02-03
目录

003.Vulkan绘制一个三角形2——初始化过程分析1

# Vulkan 绘制一个三角形2 —— 初始化过程分析1

# 1. 初始化 (Initialization)阶段总览

这是最繁琐的阶段,通常只在程序启动时执行一次。目的是建立与 GPU 的连接并准备好绘图所需的"基础设施"。一般的流程如下:

  1. 创建 Instance (VkInstance),VkInstance 是应用程序与 Vulkan 库(Loader)之间的连接桥梁,可以把它理解为 Vulkan 的“上下文”或“入口点”
  2. 选择物理设备 (VkPhysicalDevice),查询系统中有几张显卡,选择最适合的一张(比如选独显而不是集显)。
  3. 创建逻辑设备 (VkDevice) 和队列 (VkQueue),建立与选定显卡的逻辑连接,并获取命令队列(Graphics Queue, Present Queue)。
  4. 创建窗口表面 (VkSurfaceKHR) 和交换链 (VkSwapchainKHR),连接操作系统窗口,创建一组用于显示的图像缓冲区(Images)。通常采用双缓冲或三缓冲模式。
  5. 创建图像视图 (VkImageView) 和帧缓冲 (VkFramebuffer),告诉 Vulkan 如何看待和使用交换链里的那些图像。

代码中我定义了一个结构体和对应的全局变量,用于保存我们需要初始化的部分对象:

// VulkanMain.cpp
struct VulkanDeviceInfo {
  bool initialized_;

  VkInstance instance_;
  VkPhysicalDevice gpuDevice_;
  VkDevice device_;
  uint32_t queueFamilyIndex_;

  VkSurfaceKHR surface_;
  VkQueue queue_;
};
VulkanDeviceInfo device;
1
2
3
4
5
6
7
8
9
10
11
12
13

# 2. VkInstance

在 Vulkan 图形 API 中,VkInstance 是一个非常基础且至关重要的对象。简单来说,它是你的应用程序与 Vulkan 库(Loader)之间的连接桥梁。你可以把它理解为 Vulkan 的"上下文"或"入口点"。没有它,你无法进行任何后续的 Vulkan 操作。

示例程序中相关的代码如下:

void CreateVulkanDevice(ANativeWindow *platformWindow,
                        VkApplicationInfo *appInfo) {
    
    // extension
    std::vector<const char *> instance_extensions;
    std::vector<const char *> device_extensions;

    instance_extensions.push_back("VK_KHR_surface");
    instance_extensions.push_back("VK_KHR_android_surface");
    device_extensions.push_back("VK_KHR_swapchain");

    // **********************************************************
    // Create the Vulkan instance
    VkInstanceCreateInfo instanceCreateInfo{
            .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
            .pNext = nullptr,
            .pApplicationInfo = appInfo,
            .enabledLayerCount = 0,          // Layer
            .ppEnabledLayerNames = nullptr,
            .enabledExtensionCount =         // Extension
            static_cast<uint32_t>(instance_extensions.size()),
            .ppEnabledExtensionNames = instance_extensions.data(),
    };
    CALL_VK(vkCreateInstance(&instanceCreateInfo, nullptr, &device.instance_));

    // ......
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

示例程序处于学习目的,做了一些简化,这里给一个相对完整一点的示例:

#include <vulkan/vulkan.h>
#include <iostream>
#include <vector>

int main() {
    // 1. 定义 VkInstance 句柄
    VkInstance instance;

    // 2. 填充应用信息 (VkApplicationInfo)
    // 这部分信息主要用于驱动程序的优化和识别,虽然是可选的,但强烈建议填写。
    VkApplicationInfo appInfo{};
    appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; // 必须指定结构体类型
    appInfo.pNext = nullptr;                            // 通常为 nullptr
    appInfo.pApplicationName = "Hello Vulkan";          // 你的应用名称
    appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
    appInfo.pEngineName = "No Engine";                  // 引擎名称
    appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
    appInfo.apiVersion = VK_API_VERSION_1_3;            // 你想使用的 Vulkan 版本 (1.0, 1.1, 1.2, 1.3)

    // 3. 准备扩展 (Extensions) 和 验证层 (Layers)
    // 如果你要创建窗口,必须包含 'VK_KHR_surface' 等扩展。
    // 这里为了演示简单,暂时留空,或者只开启验证层。
  
    // 常见的扩展
    const std::vector<const char*> extensions = {
       "VK_KHR_surface", "VK_KHR_android_surface" 
    };


    // 常见的验证层名称
    const std::vector<const char*> validationLayers = {
        "VK_LAYER_KHRONOS_validation"
    };
    
    // 4. 填充实例创建信息 (VkInstanceCreateInfo)
    // 这是最重要的结构体,告诉 Vulkan 驱动你要怎么创建实例。
    VkInstanceCreateInfo createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    createInfo.pNext = nullptr;
    createInfo.pApplicationInfo = &appInfo; // 指向上面定义的应用信息

    // 设置启用的扩展 (这里设为0,实际开发中需要根据需求设置)
    createInfo.enabledExtensionCount = extensions.size(); 
    createInfo.ppEnabledExtensionNames = extensions.data();

    // 设置 Layer
    createInfo.enabledLayerCount = 0;
    createInfo.ppEnabledLayerNames = nullptr;
    
    // 如果要开启验证层 Layer,代码如下:
    // createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
    // createInfo.ppEnabledLayerNames = validationLayers.data();

    // 5. 调用创建函数
    // 参数:创建信息指针,分配器回调(通常为nullptr),接收句柄的指针
    VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);

    // 6. 检查结果
    if (result == VK_SUCCESS) {
        std::cout << "VkInstance created successfully!" << std::endl;
    } else {
        std::cerr << "Failed to create VkInstance!" << std::endl;
        return -1;
    }

    // ... 在这里进行其他的 Vulkan 操作 ...

    // 7. 清理资源
    // 程序结束前必须销毁实例
    vkDestroyInstance(instance, nullptr);

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

初始化过程涉及到两个新的概念 extension 和 layer

# 2.1 extension

Extension 是对 Vulkan 核心规范的补充。Vulkan 为了保持跨平台(Windows, Linux, Android)和跨硬件(NVIDIA, AMD, Intel, 移动端 GPU),核心 API 只定义了大家都有的最基础功能。任何“特殊的”、“平台相关的”或“新出的”功能,都通过 Extension 提供。

20260117135053

扩展分为两类:

  • Instance Extensions (实例扩展),与具体的 GPU 无关,通常与操作系统/平台相关。典型例子:
    • VK_KHR_surface:通用的表面扩展,用于渲染到屏幕。
    • VK_KHR_win32_surface / VK_KHR_android_surface:特定平台的窗口系统接口。
    • VK_EXT_debug_utils:用于自定义调试信息的打印回调。
  • Device Extensions (设备扩展),与具体的 GPU 硬件能力相关。典型例子:
    • VK_KHR_swapchain:最常用! 用于管理交换链(让渲染的图像显示到屏幕上)。核心 Vulkan 甚至不知道什么是“屏幕”,它只管渲染到内存,是这个扩展让它能和显示器交互。
    • VK_KHR_ray_tracing_pipeline:光线追踪功能(只有支持光追的显卡才有)。
    • VK_NV_mesh_shader:NVIDIA 独有的网格着色器技术。

# 2.2 layer

Layer 是插入在你的应用程序和 Vulkan 驱动程序中间的一段代码。当你调用一个 Vulkan 函数(比如 vkCreateDevice)时,这个调用会像穿过“洋葱”一样,先穿过一层层的 Layer,最后才到达真正的驱动程序。

Layer 的核心目的是观察或修改 Vulkan 的行为,最常见的用途包括:

  • 验证 (Validation):这是最重要的用途!因为 Vulkan 驱动不检查错误,你需要开启 VK_LAYER_KHRONOS_validation 层。它会检查你是否传了空指针、枚举值是否越界、是否忘记销毁资源等,并在控制台报错。
  • 调试与分析 (Debugging & Profiling):有些 Layer 可以帮助显示帧率、显存占用,或者像 RenderDoc 这样的工具通过 Layer 来截帧分析。
  • 功能增强:比如 Steam 的 Overlay(游戏内覆盖界面)就是通过一个 Layer 绘制在游戏画面之上的。

# 3. VkPhysicalDevice

VkPhysicalDevice 是 Vulkan 中对物理显卡硬件的抽象。它是硬件的句柄:它代表了你电脑上实际插入的显卡(例如 NVIDIA GeForce RTX 3060, AMD Radeon RX 6700, 或 Intel UHD Graphics)。它是“只读”的:你不能修改物理设备的属性(比如你不能通过代码把显存从 8GB 变成 16GB)。你只能查询它的属性、特性、限制和队列能力。

不用销毁:这是它最特别的地方。VkPhysicalDevice 属于 VkInstance 的一部分。当 VkInstance 销毁时,它会自动失效。不存在 vkDestroyPhysicalDevice 这个函数。

与 VkInstance 的“创建(Create)”不同,获取 VkPhysicalDevice 的过程称为“枚举(Enumerate)”。逻辑通常是这样的:查询所有显卡的列表,遍历列表,检查每一张显卡是否满足你的游戏/应用需求(比如是否支持几何着色器,是否支持特定的纹理格式)。选择一张最好的(通常选择独立显卡而不是集成显卡),保存它的句柄。

示例程序中相关代码如下:

    // Find one GPU to use:
    // On Android, every GPU device is equal -- supporting
    // graphics/compute/present
    // for this sample, we use the very first GPU device found on the system
    uint32_t gpuCount = 0;
    // 查询 GPU 的个数
    CALL_VK(vkEnumeratePhysicalDevices(device.instance_, &gpuCount, nullptr));
    VkPhysicalDevice tmpGpus[gpuCount];
    // 获取 VkPhysicalDevice 对象
    CALL_VK(vkEnumeratePhysicalDevices(device.instance_, &gpuCount, tmpGpus));
    // 手机一般只有一个 GPU
    device.gpuDevice_ = tmpGpus[0];  // Pick up the first GPU Device
1
2
3
4
5
6
7
8
9
10
11
12

给出一个相对完整的 VkPhysicalDevice 初始化的示例:

#include <vulkan/vulkan.h>
#include <vector>
#include <iostream>

// 这是一个辅助函数,用于检查设备是否适合我们的需求
// 在实际项目中,这里会检查更多的东西(如是否支持特定的扩展、队列族等)
bool isDeviceSuitable(VkPhysicalDevice device) {
    VkPhysicalDeviceProperties deviceProperties;
    VkPhysicalDeviceFeatures deviceFeatures;

    // 获取属性(名字、类型、ID等)
    vkGetPhysicalDeviceProperties(device, &deviceProperties);
    // 获取特性(是否支持几何着色器、曲面细分等)
    vkGetPhysicalDeviceFeatures(device, &deviceFeatures);

    std::cout << "Found Device: " << deviceProperties.deviceName << std::endl;

    // 简单的筛选逻辑:
    // 1. 必须是独立显卡 (VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
    // 2. 必须支持几何着色器 (geometryShader)
    // 注意:这只是为了演示,实际开发中兼容性更好做法是给设备打分,而不是直接淘汰集成显卡
    return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
           deviceFeatures.geometryShader;
}

// 主逻辑:选择物理设备
VkPhysicalDevice pickPhysicalDevice(VkInstance instance) {
    VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;

    // 很多查询类的 Vulkan API 都设计为调用两次,先获取数量再获取具体对象
    // 1. 获取设备数量
    uint32_t deviceCount = 0;
    vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);

    if (deviceCount == 0) {
        std::cerr << "Failed to find GPUs with Vulkan support!" << std::endl;
        return VK_NULL_HANDLE;
    }

    // 2. 获取设备列表
    std::vector<VkPhysicalDevice> devices(deviceCount);
    vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());

    // 3. 遍历并选择最合适的一个
    for (const auto& device : devices) {
        if (isDeviceSuitable(device)) {
            physicalDevice = device;
            break; // 找到满足条件的就停止,或者你可以写个打分机制选分最高的
        }
    }

    if (physicalDevice == VK_NULL_HANDLE) {
        std::cerr << "Failed to find a suitable GPU!" << std::endl;
    } else {
        std::cout << "Selected a physical device!" << std::endl;
    }

    return physicalDevice;
}

int main() {
    // 假设 instance 已经创建好了...
    VkInstance instance = ...; 

    // 调用选择函数
   VkPhysicalDevice physicalDevice = pickPhysicalDevice(instance);

    // ... 后续使用 physicalDevice 创建逻辑设备 (VkDevice) ...

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

# 4. VkDevice、VkQueue、Queue Family

新手最难理解的三个概念!

# Queue Family

在 Vulkan 硬件(VkPhysicalDevice)中,GPU 的执行引擎(GPU 内部专门负责处理特定类型指令的硬件单元集合)被划分成了不同的组,这就是 Queue Family。

每一个 Queue Family 都有两个核心属性:

  • Flags(能力标签):这个族里的队列能干什么活?
  • Queue Count(数量):这个族里有几条可用的队列?

常见的队列能力(VkQueueFlagBits):

  • VK_QUEUE_GRAPHICS_BIT (图形),支持所有图形渲染命令(画三角形、光栅化等),通常也隐式支持 Compute 和 Transfer。
  • VK_QUEUE_COMPUTE_BIT (计算),支持计算着色器(Compute Shader)。用于物理模拟、AI 推理等。
  • VK_QUEUE_TRANSFER_BIT (传输),支持数据拷贝命令(比如把 CPU 内存的数据上传到 GPU 显存)。这种队列通常代表了 GPU 上的 DMA(直接内存访问) 引擎,拷贝速度极快且不占用渲染资源。
  • VK_QUEUE_SPARSE_BINDING_BIT (稀疏绑定),用于高级的显存管理(如巨型纹理),初学者暂时用不到。

20260117154806

NVIDIA 显卡:通常有一个“全能”的主队列族(Graphics + Compute + Transfer),里面有 16 条队列。还有一个专门的“拷贝”队列族(Transfer only)。

AMD 显卡:通常分得很细。有一个图形族,还有一个专门的“异步计算”族(Async Compute),利用这个族可以在渲染画面的同时,并行处理物理运算,从而提高帧率。

这就是 Vulkan 高性能的来源之一: 它允许你把“搬运数据”的任务丢给专门的 Transfer 队列,把“物理计算”丢给 Compute 队列,让 Graphics 队列专心画画,三者并行(Asynchronous)工作。

# VkDevice

如果说 VkPhysicalDevice 是你买回来的“全能机器人”(硬件实体),那么 VkDevice 就是你为这个机器人安装的“专用操作系统”(逻辑接口)。

它的核心作用有三点:

  • 资源管理:几乎所有的 Vulkan 资源(纹理、缓冲、管线、着色器)都是通过 VkDevice 创建的。
  • 功能锁定:在创建 VkDevice 时,你必须明确声明你要启用哪些特性(Features)(比如“我要用几何着色器”)。如果你没声明,就算硬件支持,你也用不了。
  • 队列获取:你必须在创建时通过 queueFamilyIndex 声明你需要用到哪些队列(Queues)。

关键点:VkDevice 是你与 GPU 交互的主要句柄。程序结束时,必须使用 vkDestroyDevice 销毁它。

# VkQueue

VkQueue 是用于向硬件提交命令的抽象机制,如果说 VkDevice 是你与 GPU 签订的雇佣合同,那么 VkQueue 就是你向 GPU 下达指令的传送带。

在 Vulkan 中,VkQueue 不需要显式的“创建(Create)”函数,当你调用 vkCreateDevice 时,队列就已经在驱动内部被隐式创建好了,我们直接“获取(Get)”这个已经存在的队列的句柄即可。

# 三者的联系

我们可以用一句话概括它们的关系:“Queue Family 是显卡提供的能力分类,VkDevice 是你根据这些分类组建的团队,而 VkQueue 是团队里具体干活的工人。”

三者的关系是从抽象到具体,从硬件到软件的过程:

Queue Family (队列族):属于 物理层 (VkPhysicalDevice)。

  • 性质:这是硬件属性,你无法改变。
  • 定义:它定义了“这里有一组能干图形渲染的硬件”或“这里有一组能干计算的硬件”。

VkDevice (逻辑设备):属于 逻辑层。

  • 性质:这是你创建的管理对象。
  • 动作:在创建它时,你必须引用 Queue Family 的索引,声明“我要从这个族里申请几个队列”。

VkQueue (队列):属于 执行层。

  • 性质:这是最终的操作句柄。
  • 来源:它是由 VkDevice 管理的。你通过 Device 拿到它,向它提交命令。

让我们通过一个完整的生命周期来看看它们是如何串联起来的:

第一步:查看能力 (Queue Family)

  • 主体:VkPhysicalDevice
  • 动作:你查询显卡,发现它有 3 个 Queue Family。
Family 0: 支持图形 (Graphics),有 16 个队列。
Family 1: 支持计算 (Compute),有 8 个队列。
Family 2: 支持传输 (Transfer),有 2 个队列。
1
2
3
  • 关系:Queue Family 存在于物理设备上,等待被挑选。

第二步:签订合同 (VkDevice Creation)

  • 主体:VkDevice
  • 动作:你创建逻辑设备,在 VkDeviceCreateInfo 结构体中,你填入 VkDeviceQueueCreateInfo,你写道:“我要从 Family 0 (图形族) 中申请 1 个 队列。”
  • 关系:VkDevice 锁定并初始化了来自特定 Queue Family 的资源。

第三步:指派任务 (VkQueue)

  • 主体:VkQueue
  • 动作:设备创建成功后,你调用 vkGetDeviceQueue。你对 Device 说:“把刚才从 Family 0 申请的第 0 号队列的句柄给我。”你拿到了 VkQueue 句柄。

示例程序中相关代码如下:

    // Find a GFX queue family
    uint32_t queueFamilyCount;
    vkGetPhysicalDeviceQueueFamilyProperties(device.gpuDevice_, &queueFamilyCount,
                                             nullptr);
    assert(queueFamilyCount);
    std::vector<VkQueueFamilyProperties> queueFamilyProperties(queueFamilyCount);
    vkGetPhysicalDeviceQueueFamilyProperties(device.gpuDevice_, &queueFamilyCount,
                                             queueFamilyProperties.data());

    uint32_t queueFamilyIndex;
    for (queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount;
         queueFamilyIndex++) {
        if (queueFamilyProperties[queueFamilyIndex].queueFlags &
            VK_QUEUE_GRAPHICS_BIT) {
            break;
        }
    }
    assert(queueFamilyIndex < queueFamilyCount);
    device.queueFamilyIndex_ = queueFamilyIndex;

    // Create a logical device (vulkan device)
    float priorities[] = {
            1.0f,
    };
    VkDeviceQueueCreateInfo queueCreateInfo{
            .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
            .pNext = nullptr,
            .flags = 0,
            .queueFamilyIndex = queueFamilyIndex,
            .queueCount = 1,
            .pQueuePriorities = priorities,
    };

    VkDeviceCreateInfo deviceCreateInfo{
            .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
            .pNext = nullptr,
            .queueCreateInfoCount = 1,
            .pQueueCreateInfos = &queueCreateInfo,
            .enabledLayerCount = 0,
            .ppEnabledLayerNames = nullptr,
            .enabledExtensionCount = static_cast<uint32_t>(device_extensions.size()),
            .ppEnabledExtensionNames = device_extensions.data(),
            .pEnabledFeatures = nullptr,
    };

    CALL_VK(vkCreateDevice(device.gpuDevice_, &deviceCreateInfo, nullptr,
                           &device.device_));
    vkGetDeviceQueue(device.device_, device.queueFamilyIndex_, 0, &device.queue_);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

给个相对完整的示例程序:

#include <vulkan/vulkan.h>
#include <vector>
#include <iostream>
#include <stdexcept>
#include <optional>

// 一个辅助结构,用来存放我们要找的队列族索引
struct QueueFamilyIndices {
    std::optional<uint32_t> graphicsFamily;

    bool isComplete() {
        return graphicsFamily.has_value();
    }
};

// =================================================================
// 步骤 1: 辅助函数 - 查找显卡支持图形命令的队列族 (Queue Family)
// =================================================================
QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
    QueueFamilyIndices indices;

    // 1. 获取队列族数量
    uint32_t queueFamilyCount = 0;
    vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);

    // 2. 获取队列族属性列表
    std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
    vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());

    // 3. 遍历列表,寻找支持 GRAPHICS_BIT 的族
    int i = 0;
    for (const auto& queueFamily : queueFamilies) {
        // 检查是否支持图形操作
        if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
            indices.graphicsFamily = i;
            std::cout << "[Info] Found Graphics Queue Family at index: " << i << std::endl;
        }

        // 如果找到了我们需要的族,就可以提前退出了
        if (indices.isComplete()) {
            break;
        }
        i++;
    }

    return indices;
}

int main() {
    // -------------------------------------------------------------
    // 0. 前置准备 (假设 Instance 和 PhysicalDevice 已创建)
    // -------------------------------------------------------------
    // 为了代码可运行,这里简略写一下 instance 和 physicalDevice 的获取,
    // 实际项目中这部分代码会很长。
    VkInstance instance; 
    VkApplicationInfo appInfo{VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr, "App", 1, nullptr, 0, VK_API_VERSION_1_0};
    VkInstanceCreateInfo instInfo{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, nullptr, 0, &appInfo, 0, nullptr, 0, nullptr};
    vkCreateInstance(&instInfo, nullptr, &instance);

    uint32_t deviceCount = 0;
    vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
    if (deviceCount == 0) return -1;
    std::vector<VkPhysicalDevice> devices(deviceCount);
    vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
    VkPhysicalDevice physicalDevice = devices[0]; // 直接选第一个显卡用于演示
    // -------------------------------------------------------------
    
    
    try {
        // =========================================================
        // 步骤 1: 确定我们要用哪个队列族
        // =========================================================
        QueueFamilyIndices indices = findQueueFamilies(physicalDevice);

        if (!indices.isComplete()) {
            throw std::runtime_error("failed to find a graphics queue family!");
        }

        // =========================================================
        // 步骤 2: 填写 VkDeviceQueueCreateInfo (申请队列)
        // =========================================================
        
        // 队列优先级 (0.0 ~ 1.0),即使只有一个队列也必须设置
        float queuePriority = 1.0f;

        VkDeviceQueueCreateInfo queueCreateInfo{};
        queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
        
        // 【关键点 A】指定我们要从哪个族申请队列
        queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value();
        
        // 【关键点 B】指定我们要申请几个队列 (通常 1 个就够了)
        queueCreateInfo.queueCount = 1;
        
        // 【关键点 C】指定优先级
        queueCreateInfo.pQueuePriorities = &queuePriority;

        // =========================================================
        // 步骤 3: 填写 VkDeviceCreateInfo (配置逻辑设备)
        // =========================================================
        
        // 指定我们需要启用的设备特性 (这里暂时留空,表示什么高级特性都不开)
        VkPhysicalDeviceFeatures deviceFeatures{};

        VkDeviceCreateInfo createInfo{};
        createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;

        // 链接刚才定义的队列创建信息
        createInfo.pQueueCreateInfos = &queueCreateInfo;
        createInfo.queueCreateInfoCount = 1;

        // 链接设备特性
        createInfo.pEnabledFeatures = &deviceFeatures;

        // (可选) 链接设备扩展,例如 Swapchain
        // const std::vector<const char*> deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
        // createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
        // createInfo.ppEnabledExtensionNames = deviceExtensions.data();
        createInfo.enabledExtensionCount = 0; // 演示用,暂不开启扩展

        // =========================================================
        // 步骤 4: 创建逻辑设备 (VkDevice)
        // =========================================================
        
        VkDevice device; // 逻辑设备句柄
        if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
            throw std::runtime_error("failed to create logical device!");
        }
        std::cout << "[Success] Logical Device created!" << std::endl;

        // =========================================================
        // 步骤 5: 获取队列句柄 (VkQueue)
        // =========================================================
        
        VkQueue graphicsQueue; // 队列句柄
        
        // 【关键点 D】从设备中取出队列
        // 参数: 逻辑设备, 队列族索引, 队列索引(第几个), 存放句柄的指针
        vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
        
        std::cout << "[Success] Graphics Queue handle retrieved!" << std::endl;

        // --- 此时,device 和 graphicsQueue 均已准备好,可以开始渲染循环了 ---

        // =========================================================
        // 步骤 6: 清理资源
        // =========================================================
        
        // 销毁逻辑设备 (队列会随之自动销毁,不需要手动 destroy queue)
        vkDestroyDevice(device, nullptr);
        
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return -1;
    }

    // 清理 Instance (演示代码收尾)
    vkDestroyInstance(instance, nullptr);
    
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161

# VkSurfaceKHR

Vulkan 本身是一个跨平台的 API,它本身并不懂什么是"Windows 窗口"、"安卓屏幕"或者"Linux X11 窗口"。它只管计算。为了让 Vulkan 能把画好的图显示到屏幕上,我们需要一个中间层,这就是 WSI (Window System Integration,窗口系统集成)。

VkSurfaceKHR 是一个抽象层。它代表了操作系统原生的窗口(比如 Windows 上的 HWND 或 Linux 上的 Window)。

后缀 KHR:代表这是 Khronos Group 定义的标准扩展(Extension),因为它不是 Vulkan 核心(Core)的一部分(因为有些 Vulkan 程序只做后台计算,不需要屏幕)。

比喻:

  • Vulkan 是画家。
  • 操作系统窗口是画框。
  • VkSurfaceKHR 就是画布。没有画布,画家就没法把画放进画框里。

示例程序中相关代码:

    VkAndroidSurfaceCreateInfoKHR createInfo{
            .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR,
            .pNext = nullptr,
            .flags = 0,
            .window = platformWindow};

    CALL_VK(vkCreateAndroidSurfaceKHR(device.instance_, &createInfo, nullptr,
                                      &device.surface_));
1
2
3
4
5
6
7
8

这里再给一个相对完整的示例:

// 【关键点 1】必须定义这个宏,才能使用 Android 特有的 Vulkan 结构体
#define VK_USE_PLATFORM_ANDROID_KHR
#include <vulkan/vulkan.h>

// Android NDK 头文件,用于处理窗口
#include <android/native_window.h> 
#include <android/log.h>
#include <vector>
#include <stdexcept>

// 为了方便打印日志
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "VulkanApp", __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "VulkanApp", __VA_ARGS__))

// 假设这个函数是在你的 Android Native Activity 或 JNI 函数中被调用的
// 参数 window: 从 Java 层传入的 Surface,或者 android_app_glue 提供的 window
void initVulkanAndroid(ANativeWindow* window) {
    
    // =============================================================
    // 步骤 1: 创建 Instance (必须包含 Android Surface 扩展)
    // =============================================================
    
    // 必须开启的两个扩展:
    // 1. 通用 Surface 扩展
    // 2. Android 专用 Surface 扩展
    std::vector<const char*> instanceExtensions = {
        VK_KHR_SURFACE_EXTENSION_NAME,
        VK_KHR_ANDROID_SURFACE_EXTENSION_NAME
    };

    VkInstanceCreateInfo instanceCreateInfo{};
    instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    instanceCreateInfo.enabledExtensionCount = static_cast<uint32_t>(instanceExtensions.size());
    instanceCreateInfo.ppEnabledExtensionNames = instanceExtensions.data();
    
    // (此处省略了 AppInfo 和 Validation Layers 的设置,实际开发中建议加上)

    VkInstance instance;
    if (vkCreateInstance(&instanceCreateInfo, nullptr, &instance) != VK_SUCCESS) {
        LOGE("Failed to create Vulkan Instance!");
        return;
    }
    LOGI("Vulkan Instance created successfully.");

    // =============================================================
    // 步骤 2: 创建 Android Surface
    // =============================================================

    VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo{};
    surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
    surfaceCreateInfo.pNext = nullptr;
    surfaceCreateInfo.flags = 0; // 预留字段,必须为 0
    
    // 【关键点 2】传入 Android 原生窗口句柄
    surfaceCreateInfo.window = window;

    VkSurfaceKHR surface;
    VkResult result = vkCreateAndroidSurfaceKHR(instance, &surfaceCreateInfo, nullptr, &surface);

    if (result != VK_SUCCESS) {
        LOGE("Failed to create Android Surface! Error code: %d", result);
        vkDestroyInstance(instance, nullptr);
        return;
    }

    LOGI("Android Surface created successfully!");

    // =============================================================
    // 步骤 3: 后续操作 (检查支持、创建 Device 等)
    // =============================================================
    
    // ... 此时你可以使用 vkGetPhysicalDeviceSurfaceSupportKHR 检查支持情况 ...
    
    // =============================================================
    // 清理
    // =============================================================
    
    // 销毁顺序:先 Surface,后 Instance
    vkDestroySurfaceKHR(instance, surface, nullptr);
    vkDestroyInstance(instance, nullptr);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
002.Vulkan绘制一个三角形1——概述
004.Vulkan绘制一个三角形3——初始化过程分析2

← 002.Vulkan绘制一个三角形1——概述 004.Vulkan绘制一个三角形3——初始化过程分析2→

最近更新
01
004.Vulkan绘制一个三角形3——初始化过程分析2
02-03
02
002.Vulkan绘制一个三角形1——概述
02-03
03
窗口显示过程全流程分析1
02-02
更多文章>
Theme by Vdoing | Copyright © 2020-2026 AHao Framework | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式