Android vulkan示例

585 阅读6分钟

vkCreateInstance

vkCreateInstance 是 Vulkan 应用程序的 ​基石函数,负责初始化全局环境、配置调试工具与平台适配扩展。其设计体现了 Vulkan 的显式控制理念——开发者需主动声明需求,而非依赖驱动隐式处理。正确使用该函数是构建高性能、跨平台图形应用的第一步。

vkCreateAndroidSurfaceKHR

  1. 获取 ANativeWindow
    通过 JNI 从 Android 应用层传递 Surface 对象,并转换为 ANativeWindow
// Java 层通过 JNI 传递 Surface
extern "C" JNIEXPORT void JNICALL Java_com_example_app_setSurface(JNIEnv* env, jobject obj, jobject surface) {
    ANativeWindow* window = ANativeWindow_fromSurface(env, surface); // 转换为 ANativeWindow
}
  1. 创建 VkSurfaceKHR
    初始化 VkAndroidSurfaceCreateInfoKHR 结构体并调用函数:
VkAndroidSurfaceCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
createInfo.window = window; // 传入 ANativeWindow 指针

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

成功时返回 VK_SUCCESS,失败则抛出错误(如 VK_ERROR_NATIVE_WINDOW_IN_USE_KHR

vkGetPhysicalDeviceQueueFamilyProperties

核心功能

  1. 获取队列族信息
    该函数返回物理设备支持的队列族列表,每个队列族包含以下属性:

    • ​**queueFlags**:标识队列支持的操作类型(图形、计算、传输等),通过 VkQueueFlagBits 枚举表示(如 VK_QUEUE_GRAPHICS_BIT 表示图形渲染支持)
    • ​**queueCount**:该队列族中可用的队列数量。例如,一个图形队列族可能包含多个独立的图形队列。
    • ​**minImageTransferGranularity**:定义图像传输操作的最小粒度,影响内存复制和布局转换的效率 
  2. 多阶段调用模式
    与 Vulkan 的典型设计一致,需分两步调用:

    • 首次调用:传入 pQueueFamilyProperties 为 nullptr,获取队列族数量 queueFamilyCount 
    • 二次调用:分配足够内存后,传入 VkQueueFamilyProperties 数组,填充具体属性数据
    uint32_t queueFamilyCount;
    vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr);
    std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
    vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilies.data());
    

应用场景

  1. 选择图形/计算队列
    遍历队列族属性,筛选支持所需功能的队列族索引。例如,查找支持图形操作的队列族
int graphicsQueueFamilyIndex = -1;
for (uint32_t i = 0; i < queueFamilyCount; i++) {
    if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
        graphicsQueueFamilyIndex = i;
        break;
    }
}
  1. 窗口呈现队列兼容性检查
    在支持窗口系统的队列族中(如 VK_KHR_surface 扩展),需通过 vkGetPhysicalDeviceSurfaceSupportKHR 验证是否支持呈现(Present)操作
  2. 创建逻辑设备 在 VkDeviceCreateInfo 中指定队列族的索引和优先级,例如为图形队列分配最高优先级
VkDeviceQueueCreateInfo queueCreateInfo{};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = graphicsQueueFamilyIndex;
queueCreateInfo.queueCount = 1;
float queuePriority = 1.0f;  // 最高优先级
queueCreateInfo.pQueuePriorities = &queuePriority;

注意事项

  1. 队列族功能隔离 同一物理设备的队列族可能功能重叠(如多个队列族支持图形操作),但 Vulkan 禁止两队列族的功能集完全相同。
  2. 队列数量限制 每个队列族的 queueCount 受物理设备限制。例如,某些设备可能仅支持一个图形队列。
  3. 多线程优化 若队列族支持多队列(queueCount > 1),可通过多线程并行提交命令缓冲,提升 CPU 利用率。
  4. 跨平台兼容性 移动端设备(如 Android)通常仅需图形队列,而桌面端可能需分离图形、计算和传输队列以优化性能。

vkCreateDevice

核心功能

  1. 抽象物理设备
    逻辑设备是物理设备(如 GPU)的代理,通过它提交渲染命令、管理资源(如缓冲区、纹理),而无需直接操作硬件。例如,一个物理设备可能支持图形、计算和传输队列,但逻辑设备可以选择只启用图形队列
  2. 队列分配与管理
    在创建逻辑设备时需指定 ​队列族(Queue Family)​ 及其优先级,例如为图形队列分配最高优先级(1.0f),以优化 GPU 调度效率
  3. 扩展与特性配置 支持启用设备级扩展(如交换链扩展 VK_KHR_swapchain)和硬件特性(如几何着色器、多视口渲染)。

参数说明

函数原型如下:

VkResult vkCreateDevice(
    VkPhysicalDevice physicalDevice,
    const VkDeviceCreateInfo* pCreateInfo,
    const VkAllocationCallbacks* pAllocator,
    VkDevice* pDevice
);
参数说明
physicalDevice目标物理设备句柄(通过 vkEnumeratePhysicalDevices 获取)
pCreateInfo指向 VkDeviceCreateInfo 结构体的指针,包含队列配置、扩展和特性信息
pAllocator自定义内存分配器(通常设为 nullptr 使用默认分配器)
pDevice输出参数,返回创建的逻辑设备句柄

vkGetDeviceQueue

vkGetDeviceQueue 是 Vulkan 中用于 ​从逻辑设备(VkDevice)获取队列(VkQueue)句柄 的核心函数。队列是 Vulkan 提交渲染或计算任务的通道,其作用与使用场景如下:

一、核心作用

  • 获取队列句柄
    逻辑设备创建时已通过 vkCreateDevice 分配了队列资源,但需通过此函数获取实际的 VkQueue 句柄以提交命令缓冲(VkCommandBuffer)。
  • 多队列族支持
    若逻辑设备配置了多个队列族(如图形、计算、传输),需分别获取各自的队列句柄。

二、函数原型与参数解析

void vkGetDeviceQueue(
    VkDevice device,              // 已创建的逻辑设备
    uint32_t queueFamilyIndex,    // 队列族索引(由 vkGetPhysicalDeviceQueueFamilyProperties 确定)
    uint32_t queueIndex,          // 队列索引(通常从 0 开始)
    VkQueue* pQueue               // 输出参数,返回队列句柄
);
参数说明
device已创建的逻辑设备句柄
queueFamilyIndex队列族索引(如图形队列族、计算队列族)
queueIndex队列在该队列族中的索引(例如:若队列族支持 2 个队列,可为 0 或 1)
pQueue输出参数,返回获取的队列句柄

三、典型场景示例

多队列并行

若队列族支持多个队列(如 queueCount = 2),可获取两个队列并行提交命令:

VkQueue graphicsQueue0, graphicsQueue1;
vkGetDeviceQueue(device, graphicsQueueFamilyIndex, 0, &graphicsQueue0);
vkGetDeviceQueue(device, graphicsQueueFamilyIndex, 1, &graphicsQueue1);

vkCreateSwapchainKHR

vkCreateSwapchainKHR 是 Vulkan 中用于 ​创建交换链(Swapchain)​ 的核心函数,负责管理渲染帧的缓冲区并与窗口系统(如 Android Surface)交互。以下是其功能详解及在 Android 设备上的实践指南:

一、核心功能

交换链是 Vulkan 实现 ​双缓冲(Double Buffering)或三缓冲(Triple Buffering)​ 的关键组件,主要作用包括:

  1. 图像队列管理:维护一组图像(VkImage),用于交替渲染和呈现。
  2. 帧同步:协调 GPU 渲染与屏幕刷新周期,避免撕裂(Tearing)或卡顿。
  3. 平台适配:对接不同窗口系统(通过 VkSurfaceKHR),如 Android 的 ANativeWindow

二、函数原型与参数

VkResult vkCreateSwapchainKHR(
    VkDevice device,                        // 逻辑设备
    const VkSwapchainCreateInfoKHR* pCreateInfo, // 交换链配置信息
    const VkAllocationCallbacks* pAllocator,     // 内存分配器(通常为 nullptr)
    VkSwapchainKHR* pSwapchain                  // 输出交换链句柄
);

三、VkSwapchainCreateInfoKHR 关键参数解析

typedef struct VkSwapchainCreateInfoKHR {
    VkStructureType sType;                   // 必须为 VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR
    VkSurfaceKHR surface;                    // 关联的窗口表面(VkSurfaceKHR)
    uint32_t minImageCount;                  // 交换链最小图像数量(通常为 2 或 3)
    VkFormat imageFormat;                    // 图像格式(如 VK_FORMAT_R8G8B8A8_SRGB)
    VkColorSpaceKHR imageColorSpace;         // 颜色空间(通常 VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
    VkExtent2D imageExtent;                  // 图像分辨率(需匹配窗口大小)
    uint32_t imageArrayLayers;              // 图像层数(2D 渲染设为 1)
    VkImageUsageFlags imageUsage;            // 图像用途(如 VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
    VkSharingMode imageSharingMode;          // 共享模式(VK_SHARING_MODE_EXCLUSIVE 或 CONCURRENT)
    uint32_t queueFamilyIndexCount;          // 共享队列族数量(仅 CONCURRENT 模式需设置)
    const uint32_t* pQueueFamilyIndices;      // 共享队列族索引数组
    VkSurfaceTransformFlagBitsKHR preTransform; // 表面变换(如旋转、翻转)
    VkCompositeAlphaFlagBitsKHR compositeAlpha; // Alpha 通道合成模式
    VkPresentModeKHR presentMode;            // 呈现模式(FIFO、MAILBOX 等)
    VkBool32 clipped;                        // 是否裁剪不可见区域(建议设为 VK_TRUE)
    VkSwapchainKHR oldSwapchain;              // 旧交换链(用于窗口大小变化时重建)
} VkSwapchainCreateInfoKHR;