受保护的内存

受保护的内存将设备内存划分为“受保护的设备内存”和“未受保护的设备内存”。

一般来说,大多数操作系统不允许一个应用程序访问另一个应用程序的 GPU 内存,除非显式共享(例如,通过外部内存)。受保护内存的一个常见示例是包含 DRM 内容,进程可能被允许修改(例如,用于图像过滤,或合成播放控件和隐藏字幕),但不应该能够提取到未受保护的内存中。数据以加密形式传入,并保持加密状态,直到到达显示屏上的像素为止。

Vulkan 规范详细解释了“受保护的设备内存”强制执行的内容。以下是正确启用使用受保护内存的受保护提交所需内容的细分。

检查支持

受保护的内存是在 Vulkan 1.1 中添加的,之前没有扩展。这意味着任何 Vulkan 1.0 设备都无法支持受保护的内存。要检查支持,应用程序必须查询并启用 `VkPhysicalDeviceProtectedMemoryFeatures::protectedMemory` 字段。

受保护的队列

受保护的队列可以读取受保护和未受保护的内存,但只能写入受保护的内存。如果一个队列可以写入未受保护的内存,那么它也不能从受保护的内存中读取。

通常,为了防止侧信道攻击,性能计数器和其他计时测量系统对于受保护的队列是禁用或精度较低的。

使用 `vkGetPhysicalDeviceQueueFamilyProperties` 获取每个队列的 `VkQueueFlags`,应用程序可以找到一个具有 `VK_QUEUE_PROTECTED_BIT` 标志的队列族。这**并不**意味着来自该族的队列始终受保护,而是意味着这些队列**可以**成为受保护的队列。

要告诉驱动程序使 `VkQueue` 受保护,需要在 `vkCreateDevice` 期间的 `VkDeviceQueueCreateInfo` 中使用 `VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT`。

以下伪代码说明应用程序如何从同一个队列族请求创建 2 个受保护的 `VkQueue` 对象

VkDeviceQueueCreateInfo queueCreateInfo[1];
queueCreateInfo[0].flags             = VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT;
queueCreateInfo[0].queueFamilyIndex  = queueFamilyFound;
queueCreateInfo[0].queueCount        = 2; // assuming 2 queues are in the queue family

VkDeviceCreateInfo deviceCreateInfo   = {};
deviceCreateInfo.pQueueCreateInfos    = queueCreateInfo;
deviceCreateInfo.queueCreateInfoCount = 1;
vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &deviceHandle);

也可以将一个队列族中的队列拆分,使一些受保护,而另一些不受保护。以下伪代码说明应用程序如何从同一个队列族请求创建 1 个受保护的 `VkQueue` 和 1 个未受保护的 `VkQueue` 对象

VkDeviceQueueCreateInfo queueCreateInfo[2];
queueCreateInfo[0].flags             = VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT;
queueCreateInfo[0].queueFamilyIndex  = queueFamilyFound;
queueCreateInfo[0].queueCount        = 1;

queueCreateInfo[1].flags             = 0; // unprotected because the protected flag is not set
queueCreateInfo[1].queueFamilyIndex  = queueFamilyFound;
queueCreateInfo[1].queueCount        = 1;

VkDeviceCreateInfo deviceCreateInfo   = {};
deviceCreateInfo.pQueueCreateInfos    = queueCreateInfo;
deviceCreateInfo.queueCreateInfoCount = 2;
vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &deviceHandle);

现在,应用程序必须使用 `vkGetDeviceQueue2` 来传递 `VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT` 标志,而不是使用 `vkGetDeviceQueue` 来获取 `VkQueue` 句柄。

VkDeviceQueueInfo2 info = {};
info.queueFamilyIndex = queueFamilyFound;
info.queueIndex       = 0;
info.flags            = VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT;
vkGetDeviceQueue2(deviceHandle, &info, &protectedQueue);

受保护的资源

当创建 `VkImage` 或 `VkBuffer` 以使其受保护时,只需分别设置 `VK_IMAGE_CREATE_PROTECTED_BIT` 和 `VK_BUFFER_CREATE_PROTECTED_BIT` 即可。

当将内存绑定到受保护的资源时,必须已从具有 `VK_MEMORY_PROPERTY_PROTECTED_BIT` 位的 `VkMemoryType` 分配了 `VkDeviceMemory`。

受保护的交换链

当创建交换链时,使用 `VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR` 位来创建受保护的交换链。

从使用受保护交换链的 `vkGetSwapchainImagesKHR` 中的所有 `VkImage` 都与使用 `VK_IMAGE_CREATE_PROTECTED_BIT` 创建的图像相同。

有时,是否可以使用设置的 `VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR` 标志创建交换链是未知的。在可能未知的情况下,会公开VK_KHR_surface_protected_capabilities扩展。

受保护的命令缓冲区

使用受保护的 `VkQueue`,应用程序还可以在创建 `VkCommandPool` 时使用 `VK_COMMAND_POOL_CREATE_PROTECTED_BIT`。

VkCommandPoolCreateInfo info = {};
info.flags            = VK_COMMAND_POOL_CREATE_PROTECTED_BIT;
info.queueFamilyIndex = queueFamilyFound; // protected queue
vkCreateCommandPool(deviceHandle, &info, nullptr, &protectedCommandPool);

从受保护的命令池分配的所有命令缓冲区都将成为“受保护的命令缓冲区”。

VkCommandBufferAllocateInfo info = {};
info.commandPool = protectedCommandPool;
vkAllocateCommandBuffers(deviceHandle, &info, &protectedCommandBuffers);

提交受保护的工作

当提交工作以进行保护时,提交的所有 `VkCommandBuffer` 也必须是受保护的。

VkProtectedSubmitInfo protectedSubmitInfo = {};
protectedSubmitInfo.protectedSubmit       = true;

VkSubmitInfo submitInfo                  = {};
submitInfo.pNext                         = &protectedSubmitInfo;
submitInfo.pCommandBuffers               = protectedCommandBuffers;

vkQueueSubmit(protectedQueue, 1, &submitInfo, fence));
VkSubmitInfo2KHR submitInfo = {}
submitInfo.flags = VK_SUBMIT_PROTECTED_BIT_KHR;

vkQueueSubmit2KHR(protectedQueue, 1, submitInfo, fence);