Vulkan
参考书
教程
Vulkan::0.0::开始于VulKanSDK(Getting Started with the Vulkan SDK)
Vulkan Programming Guide::Chapter1::Overview of VulKan(纵观VulKan)
学习摘要
vkGetPhysicalDeviceFeatures —— 负责查询物理设备的特性
VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFeatures( VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures* pFeatures);
- VkPhysicalDeviceFeatures
typedef struct VkPhysicalDeviceFeatures { VkBool32 robustBufferAccess; VkBool32 fullDrawIndexUint32; VkBool32 imageCubeArray; VkBool32 independentBlend; VkBool32 geometryShader; VkBool32 tessellationShader; VkBool32 sampleRateShading; VkBool32 dualSrcBlend; VkBool32 logicOp; VkBool32 multiDrawIndirect; VkBool32 drawIndirectFirstInstance; VkBool32 depthClamp; VkBool32 depthBiasClamp; VkBool32 fillModeNonSolid; VkBool32 depthBounds; VkBool32 wideLines; VkBool32 largePoints; VkBool32 alphaToOne; VkBool32 multiViewport; VkBool32 samplerAnisotropy; VkBool32 textureCompressionETC2; VkBool32 textureCompressionASTC_LDR; VkBool32 textureCompressionBC; VkBool32 occlusionQueryPrecise; VkBool32 pipelineStatisticsQuery; VkBool32 vertexPipelineStoresAndAtomics; VkBool32 fragmentStoresAndAtomics; VkBool32 shaderTessellationAndGeometryPointSize; VkBool32 shaderImageGatherExtended; VkBool32 shaderStorageImageExtendedFormats; VkBool32 shaderStorageImageMultisample; VkBool32 shaderStorageImageReadWithoutFormat; VkBool32 shaderStorageImageWriteWithoutFormat; VkBool32 shaderUniformBufferArrayDynamicIndexing; VkBool32 shaderSampledImageArrayDynamicIndexing; VkBool32 shaderStorageBufferArrayDynamicIndexing; VkBool32 shaderStorageImageArrayDynamicIndexing; VkBool32 shaderClipDistance; VkBool32 shaderCullDistance; VkBool32 shaderFloat64; VkBool32 shaderInt64; VkBool32 shaderInt16; VkBool32 shaderResourceResidency; VkBool32 shaderResourceMinLod; VkBool32 sparseBinding; VkBool32 sparseResidencyBuffer; VkBool32 sparseResidencyImage2D; VkBool32 sparseResidencyImage3D; VkBool32 sparseResidency2Samples; VkBool32 sparseResidency4Samples; VkBool32 sparseResidency8Samples; VkBool32 sparseResidency16Samples; VkBool32 sparseResidencyAliased; VkBool32 variableMultisampleRate; VkBool32 inheritedQueries; } VkPhysicalDeviceFeatures;
vkCreateDevice —— 创建逻辑设备
VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice( VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice);
示例:
VkResult result; float queuePriorities[1] = { 0.0 }; VkDeviceQueueCreateInfo queueInfo = {}; queueInfo.queueFamilyIndex = graphicsQueueIndex; queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueInfo.pNext = NULL; queueInfo.queueCount = 1; queueInfo.pQueuePriorities = queuePriorities; vkGetPhysicalDeviceFeatures(*gpu, &deviceFeatures); VkPhysicalDeviceFeatures setEnabledFeatures = {VK_FALSE}; setEnabledFeatures.samplerAnisotropy = deviceFeatures.samplerAnisotropy; VkDeviceCreateInfo deviceInfo = {}; deviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; deviceInfo.pNext = NULL; deviceInfo.queueCreateInfoCount = 1; deviceInfo.pQueueCreateInfos = &queueInfo; deviceInfo.enabledLayerCount = 0; deviceInfo.ppEnabledLayerNames = NULL; // Device layers are deprecated deviceInfo.enabledExtensionCount = (uint32_t)extensions.size(); deviceInfo.ppEnabledExtensionNames = extensions.size() ? extensions.data() : NULL; deviceInfo.pEnabledFeatures = &setEnabledFeatures; result = vkCreateDevice(*gpu, &deviceInfo, NULL, &device);
- VkDeviceCreateInfo
typedef struct VkDeviceCreateInfo { VkStructureType sType; const void* pNext; VkDeviceCreateFlags flags; uint32_t queueCreateInfoCount; const VkDeviceQueueCreateInfo* pQueueCreateInfos; uint32_t enabledLayerCount; const char* const* ppEnabledLayerNames; uint32_t enabledExtensionCount; const char* const* ppEnabledExtensionNames; const VkPhysicalDeviceFeatures* pEnabledFeatures; } VkDeviceCreateInfo;
- VkDeviceCreateFlags
typedef enum VkQueueFlagBits { VK_QUEUE_GRAPHICS_BIT = 0x00000001, VK_QUEUE_COMPUTE_BIT = 0x00000002, VK_QUEUE_TRANSFER_BIT = 0x00000004, VK_QUEUE_SPARSE_BINDING_BIT = 0x00000008, VK_QUEUE_PROTECTED_BIT = 0x00000010, #ifdef VK_ENABLE_BETA_EXTENSIONS VK_QUEUE_VIDEO_DECODE_BIT_KHR = 0x00000020, #endif #ifdef VK_ENABLE_BETA_EXTENSIONS VK_QUEUE_VIDEO_ENCODE_BIT_KHR = 0x00000040, #endif VK_QUEUE_OPTICAL_FLOW_BIT_NV = 0x00000100, VK_QUEUE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VkQueueFlagBits; typedef VkFlags VkQueueFlags; typedef VkFlags VkDeviceCreateFlags;
- VkDeviceQueueCreateInfo
typedef struct VkDeviceQueueCreateInfo { VkStructureType sType; const void* pNext; VkDeviceQueueCreateFlags flags; uint32_t queueFamilyIndex; uint32_t queueCount; const float* pQueuePriorities; } VkDeviceQueueCreateInfo;
指令缓存
指令缓存中的指令类型
指令缓存中记录了一条或者更多条指令。这些指令主要可以划分为以下三种类型:
行为(action):这一类指令会执行诸如绘制、分发、清屏、复制、查询/时间戳,以及子通道的开始/结束等操作。
状态管理(state management):这一类指令包括描述符集合,绑定流水线、缓存,用于设置动态状态、推送常量,以及设置渲染通道/子通道的状态。
同步(synchronization):这一类指令用来执行各种同步:流水线屏障、设置事件、等待事件,以及渲染通道/子通道的依赖。
Vulkan 提供了硬件队列的一个逻辑视图,每个逻辑视图都会紧密地关联到一个硬件队列。一个 Vulkan 硬件队列可以被多个不同的逻辑队列分别对应,而每个逻辑队列是通过不同的队列参数创建而成的。例如,要显示一幅渲染后的交换链图像,我们可能需要使用指令缓存将它提交给一个图形队列,而后者也是可以用作渲染展示的。
执行顺序
指令缓存可以提交到一个队列或者多个队列中:
单队列提交(single queue submission):多个指令缓存提交到一个队列的过程是可以叠加执行的。在单队列提交过程中,指令缓存必须按照具体操作的顺序排列,按照指令顺序和 API 顺序执行。本书中只涉及函数 vkQueueSubmit 的指令提交,并不涉及稀疏内存绑定的指令缓存的问题(通过vkQueueBindSparse)。
多队列提交(multiple queue submission):提交给多个队列的指令缓存可能会按照任何一种顺序执行,除非我们已经通过信号量、屏障等同步机制,显式地定义了排序的约束条件。
vkCreateCommandPool —— 创建指令池
VKAPI_ATTR VkResult VKAPI_CALL vkCreateCommandPool( VkDevice device, const VkCommandPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkCommandPool* pCommandPool);
- VkCommandPoolCreateInfo
typedef struct VkCommandPoolCreateInfo { VkStructureType sType; // 可以设置为扩展结构体的指针或者NULL const void* pNext; // 枚举量,表示指令池和分配指令缓存的方式 VkCommandPoolCreateFlags flags; // 队列族索引号 uint32_t queueFamilyIndex; } VkCommandPoolCreateInfo;
- 示例
void VulkanRenderer::createCommandPool() { VulkanDevice* deviceObj = application->deviceObj; /* Depends on intializeSwapChainExtension() */ VkResult res; VkCommandPoolCreateInfo cmdPoolInfo = {}; cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; cmdPoolInfo.pNext = NULL; cmdPoolInfo.queueFamilyIndex = deviceObj->graphicsQueueWithPresentIndex; cmdPoolInfo.flags = 0; res = vkCreateCommandPool(deviceObj->device, &cmdPoolInfo, NULL, &cmdPool); assert(res == VK_SUCCESS); }
vkAllocateCommandBuffers —— 指令缓存的分配
VKAPI_ATTR VkResult VKAPI_CALL vkAllocateCommandBuffers( VkDevice device, const VkCommandBufferAllocateInfo* pAllocateInfo, VkCommandBuffer* pCommandBuffers);
- VkCommandBufferAllocateInfo
typedef struct VkCommandBufferAllocateInfo { VkStructureType sType; const void* pNext; // 指令池句柄 VkCommandPool commandPool; // 位枚举量,表示指令缓存是主级别还是次级别 VkCommandBufferLevel level; uint32_t commandBufferCount; } VkCommandBufferAllocateInfo;
- VkCommandBufferLevel
typedef enum VkCommandBufferLevel { VK_COMMAND_BUFFER_LEVEL_PRIMARY = 0, VK_COMMAND_BUFFER_LEVEL_SECONDARY = 1, VK_COMMAND_BUFFER_LEVEL_MAX_ENUM = 0x7FFFFFFF } VkCommandBufferLevel;
- 示例
VkCommandBufferAllocateInfo cmdInfo = {}; cmdInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; cmdInfo.pNext = NULL; cmdInfo.commandPool = cmdPool; cmdInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmdInfo.commandBufferCount = (uint32_t) sizeof(cmdBuf) / sizeof(VkCommandBuffer);; result = vkAllocateCommandBuffers(*device, &cmdInfo, cmdBuf);
vkResetCommandBuffer —— 重新设置指令缓存
VKAPI_ATTR VkResult VKAPI_CALL vkResetCommandBuffer( VkCommandBuffer commandBuffer, VkCommandBufferResetFlags flags);
vkFreeCommandBuffer —— 释放指令缓存
VKAPI_ATTR void VKAPI_CALL vkFreeCommandBuffers( VkDevice device, VkCommandPool commandPool, uint32_t commandBufferCount, const VkCommandBuffer* pCommandBuffers);
vkBeginCommandBuffer —— 设置指令记录的起始位置
VKAPI_ATTR VkResult VKAPI_CALL vkBeginCommandBuffer( VkCommandBuffer commandBuffer, const VkCommandBufferBeginInfo* pBeginInfo);
这个函数有两个参数。第一个参数是指令缓存的句柄,我们将记录指令到这个缓存当中。第二个参数是一个 VkCommandBufferBeginInfo 结构体,它定义了一些额外的参数,可以设置指令缓存记录过程开始时的不同状态。
- VkCommandBufferBeginInfo
typedef struct VkCommandBufferBeginInfo { VkStructureType sType; const void* pNext; VkCommandBufferUsageFlags flags; const VkCommandBufferInheritanceInfo* pInheritanceInfo; } VkCommandBufferBeginInfo;
- 示例
VkCommandBufferInheritanceInfo cmdBufInheritInfo = {}; cmdBufInheritInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO; cmdBufInheritInfo.pNext = NULL; cmdBufInheritInfo.renderPass = VK_NULL_HANDLE; cmdBufInheritInfo.subpass = 0; cmdBufInheritInfo.framebuffer = VK_NULL_HANDLE; cmdBufInheritInfo.occlusionQueryEnable = VK_FALSE; cmdBufInheritInfo.queryFlags = 0; cmdBufInheritInfo.pipelineStatistics = 0; VkCommandBufferBeginInfo cmdBufInfo = {}; cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; cmdBufInfo.pNext = NULL; cmdBufInfo.flags = 0; cmdBufInfo.pInheritanceInfo = &cmdBufInheritInfo; result = vkBeginCommandBuffer(cmdBuf, &cmdBufInfo);
vkEndCommandBuffer —— 设置指令记录的结束位置
VKAPI_ATTR VkResult VKAPI_CALL vkEndCommandBuffer( VkCommandBuffer commandBuffer);
- 示例
void CommandBufferMgr::endCommandBuffer(VkCommandBuffer commandBuffer) { VkResult result; result = vkEndCommandBuffer(commandBuffer); assert(result == VK_SUCCESS); }
vkQueueSubmit —— 队列提交
将指令记录到指令缓存(VkCommandBuffer)之后,就可以将它提交给一个队列了。函数 vkQueueSubmit() 可以帮助我们将工作内容提交给对应的队列。
VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit( VkQueue queue, uint32_t submitCount, const VkSubmitInfo* pSubmits, VkFence fence);
- VkSubmitInfo
typedef struct VkSubmitInfo { VkStructureType sType; const void* pNext; uint32_t waitSemaphoreCount; const VkSemaphore* pWaitSemaphores; const VkPipelineStageFlags* pWaitDstStageMask; uint32_t commandBufferCount; const VkCommandBuffer* pCommandBuffers; uint32_t signalSemaphoreCount; const VkSemaphore* pSignalSemaphores; } VkSubmitInfo;
- 示例
void CommandBufferMgr::submitCommandBuffer(const VkQueue& queue, const VkCommandBuffer* commandBuffer, const VkFence& fence) { VkResult result; // Otherwise, create the submit information with specified buffer commands VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.pNext = NULL; submitInfo.waitSemaphoreCount = 0; submitInfo.pWaitSemaphores = NULL; submitInfo.pWaitDstStageMask = NULL; submitInfo.commandBufferCount = (uint32_t)sizeof(commandBuffer)/sizeof(VkCommandBuffer); submitInfo.pCommandBuffers = commandBuffer; submitInfo.signalSemaphoreCount = 0; submitInfo.pSignalSemaphores = NULL; result = vkQueueSubmit(queue, 1, &submitInfo, fence); assert(!result); result = vkQueueWaitIdle(queue); assert(!result); }
vkQueueWaitIdle —— 队列等待
当应用程序将指令缓存提交到队列中以后,它会等待队列完成提交工作并且准备好接收下一个批次的内容。等待队列完成的API函数为 vkQueueWaitIdle 。
VKAPI_ATTR VkResult VKAPI_CALL vkQueueWaitIdle( VkQueue queue);
VkImage 、VkBuffer
VkImage 相关
完整的图像资源创建工作流如下图:
vkCreateImage —— 创建图像资源
VKAPI_ATTR VkResult VKAPI_CALL vkCreateImage( VkDevice device, const VkImageCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImage* pImage);
- VkImageCreateInfo
typedef struct VkImageCreateInfo { VkStructureType sType; const void* pNext; VkImageCreateFlags flags; VkImageType imageType; // 设置图像的 1D/2D/3D 维度 VkFormat format; // 图像格式 VkExtent3D extent; // 图像在 mipmap 基础级别的每个维度的元素数量 uint32_t mipLevels; // 图像 mipmap 采样的级数 uint32_t arrayLayers; // 图像数组的层数 VkSampleCountFlagBits samples; // 图像中的子元素采样数 VkImageTiling tiling; // 图像在内存中进行重复贴图的方式 VkImageUsageFlags usage; // 图像内在用途 VkSharingMode sharingMode; // 图像在多个队列族之间的共享模式 uint32_t queueFamilyIndexCount; // 数组 pQueueFamilyIndices 的元素个数 const uint32_t* pQueueFamilyIndices; // 准备访问图像的队列族的数组 VkImageLayout initialLayout; // 图像所有子资源的布局 } VkImageCreateInfo;
- VkImageCreateFlagBits
typedef enum VkImageCreateFlagBits { VK_IMAGE_CREATE_SPARSE_BINDING_BIT = 0x00000001, VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT = 0x00000002, VK_IMAGE_CREATE_SPARSE_ALIASED_BIT = 0x00000004, VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT = 0x00000008, VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT = 0x00000010, ... VK_IMAGE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VkImageCreateFlagBits; typedef VkFlags VkImageCreateFlags;
- VkImageType
typedef enum VkImageType { VK_IMAGE_TYPE_1D = 0, VK_IMAGE_TYPE_2D = 1, VK_IMAGE_TYPE_3D = 2, VK_IMAGE_TYPE_MAX_ENUM = 0x7FFFFFFF } VkImageType;
- VkFormat
typedef enum VkFormat { VK_FORMAT_UNDEFINED = 0, VK_FORMAT_R4G4_UNORM_PACK8 = 1, VK_FORMAT_R4G4B4A4_UNORM_PACK16 = 2, VK_FORMAT_B4G4R4A4_UNORM_PACK16 = 3, VK_FORMAT_R5G6B5_UNORM_PACK16 = 4, VK_FORMAT_B5G6R5_UNORM_PACK16 = 5, ... VK_FORMAT_MAX_ENUM = 0x7FFFFFFF } VkFormat;
- VkExtent3D
typedef struct VkExtent3D { uint32_t width; uint32_t height; uint32_t depth; } VkExtent3D;
- VkSampleCountFlagBits
typedef enum VkSampleCountFlagBits { VK_SAMPLE_COUNT_1_BIT = 0x00000001, VK_SAMPLE_COUNT_2_BIT = 0x00000002, VK_SAMPLE_COUNT_4_BIT = 0x00000004, VK_SAMPLE_COUNT_8_BIT = 0x00000008, VK_SAMPLE_COUNT_16_BIT = 0x00000010, VK_SAMPLE_COUNT_32_BIT = 0x00000020, VK_SAMPLE_COUNT_64_BIT = 0x00000040, VK_SAMPLE_COUNT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VkSampleCountFlagBits; typedef VkFlags VkSampleCountFlags;
- VkImageTiling
typedef enum VkImageTiling { VK_IMAGE_TILING_OPTIMAL = 0, VK_IMAGE_TILING_LINEAR = 1, VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT = 1000158000, VK_IMAGE_TILING_MAX_ENUM = 0x7FFFFFFF } VkImageTiling;
- VkImageUsageFlagBits
typedef enum VkImageUsageFlagBits { VK_IMAGE_USAGE_TRANSFER_SRC_BIT = 0x00000001, VK_IMAGE_USAGE_TRANSFER_DST_BIT = 0x00000002, VK_IMAGE_USAGE_SAMPLED_BIT = 0x00000004, VK_IMAGE_USAGE_STORAGE_BIT = 0x00000008, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT = 0x00000010, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT = 0x00000020, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT = 0x00000040, ... VK_IMAGE_USAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF } VkImageUsageFlagBits; typedef VkFlags VkImageUsageFlags;
- VkSharingMode
typedef enum VkSharingMode { VK_SHARING_MODE_EXCLUSIVE = 0, VK_SHARING_MODE_CONCURRENT = 1, VK_SHARING_MODE_MAX_ENUM = 0x7FFFFFFF } VkSharingMode;
- VkImageLayout
typedef enum VkImageLayout { VK_IMAGE_LAYOUT_UNDEFINED = 0, VK_IMAGE_LAYOUT_GENERAL = 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL = 2, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL = 3, ... VK_IMAGE_LAYOUT_MAX_ENUM = 0x7FFFFFFF } VkImageLayout;
示例
VkImageCreateInfo imageCreateInfo = {}; imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageCreateInfo.pNext = NULL; imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; imageCreateInfo.format = format; imageCreateInfo.mipLevels = texture->mipMapLevels; imageCreateInfo.arrayLayers = 1; imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageCreateInfo.extent = { texture->textureWidth, texture->textureHeight, 1 }; imageCreateInfo.usage = imageUsageFlags; // Set image object with VK_IMAGE_USAGE_TRANSFER_DST_BIT if // not set already. This allows to copy the source VkBuffer // object (with VK_IMAGE_USAGE_TRANSFER_DST_BIT) contents // into this image object memory(destination). if (!(imageCreateInfo.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT)){ imageCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; } error = vkCreateImage(deviceObj->device, &imageCreateInfo, nullptr, &texture->image);
Vulkan 围炉夜话2:https://developer.aliyun.com/article/1598109