Protected Memory

Protected memory divides device memory into “protected device memory” and “unprotected device memory”.

In general, most OS don’t allow one application to access another application’s GPU memory unless explicitly shared (e.g. via external memory). A common example of protected memory is for containing DRM content, which a process might be allowed to modify (e.g. for image filtering, or compositing playback controls and closed captions) but shouldn' be able to extract into unprotected memory. The data comes in encrypted and remains encrypted until it reaches the pixels on the display.

The Vulkan Spec explains in detail what “protected device memory” enforces. The following is a breakdown of what is required in order to properly enable a protected submission using protected memory.

Checking for support

Protected memory was added in Vulkan 1.1 and there was no extension prior. This means any Vulkan 1.0 device will not be capable of supporting protected memory. To check for support, an application must query and enable the VkPhysicalDeviceProtectedMemoryFeatures::protectedMemory field.

Protected queues

A protected queue can read both protected and unprotected memory, but can only write to protected memory. If a queue can write to unprotected memory, then it can’t also read from protected memory.

Often performance counters and other timing measurement systems are disabled or less accurate for protected queues to prevent side-channel attacks.

Using vkGetPhysicalDeviceQueueFamilyProperties to get the VkQueueFlags of each queue, an application can find a queue family with VK_QUEUE_PROTECTED_BIT flag exposed. This does not mean the queues from the family are always protected, but rather the queues can be a protected queue.

To tell the driver to make the VkQueue protected, the VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT is needed in VkDeviceQueueCreateInfo during vkCreateDevice.

The following pseudo code is how an application could request for 2 protected VkQueue objects to be created from the same queue family:

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);

It is also possible to split the queues in a queue family so some are protected and some are not. The following pseudo code is how an application could request for 1 protected VkQueue and 1 unprotected VkQueue objects to be created from the same queue family:

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);

Now instead of using vkGetDeviceQueue an application has to use vkGetDeviceQueue2 in order to pass the VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT flag when getting the VkQueue handle.

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

Protected resources

When creating a VkImage or VkBuffer to make them protected is as simple as setting VK_IMAGE_CREATE_PROTECTED_BIT and VK_BUFFER_CREATE_PROTECTED_BIT respectively.

When binding memory to the protected resource, the VkDeviceMemory must have been allocated from a VkMemoryType with the VK_MEMORY_PROPERTY_PROTECTED_BIT bit.

Protected swapchain

When creating a swapchain the VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR bit is used to make a protected swapchain.

All VkImage from vkGetSwapchainImagesKHR using a protected swapchain are the same as if the image was created with VK_IMAGE_CREATE_PROTECTED_BIT.

Sometimes it is unknown whether swapchains can be created with the VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR flag set. The VK_KHR_surface_protected_capabilities extension is exposed on platforms where this might be unknown.

Protected command buffer

Using the protected VkQueue, an application can also use VK_COMMAND_POOL_CREATE_PROTECTED_BIT when creating a VkCommandPool

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

All command buffers allocated from the protected command pool become “protected command buffers”

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

Submitting protected work

When submitting work to be protected, all the VkCommandBuffer submitted must also be protected.

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);