Vulkan 学习笔记

VKInstance、VKPhysicalDevice、VKDevice、VKQueue、VKSwapChain、VKRenderPass、VKFrameBuffer、VKCommandPool、VKCommandBuffer、

一、基础环境配置

1、VKInstance

        实例是 Vulkan 与 应用app 建立交互的桥梁,通过instance 我们定义了我们需要 Vulkan 使用的全局扩展和验证层。

1.1.1 设置需要的扩展        

1.1.2 设置验证层。

        默认情况 Vulkan 的运行检测是很有限的,相当于默认vulkan认为你是个不会犯错的老手,所以很多检测都是没有监控的。如果我们开发需要vulkan能帮我们做一些检查和校验需要我们在创建 instance的时候去配置验证层。验证层常规的检测操作如下:

VK_LAYER_RENDERDOC_Capture //debugging capture layer for renderdoc
VK_LAYER_LUNARG_api_dump   //LunarG API dump layer
VK_LAYER_LUNARG_gfxreconstruct //GFXReconstruct Capture Layer Version 0.9.16
VK_LAYER_KHRONOS_synchronization2  //Khronos Synchronization2 layer
VK_LAYER_KHRONOS_validation   //Khronos Validation Layer
VK_LAYER_LUNARG_monitor //Execution Monitoring Layer
VK_LAYER_LUNARG_screenshot  //LunarG image capture layer
VK_LAYER_KHRONOS_profiles //Khronos Profiles layer
  • 根据规范检查参数值以检测误用
  • 跟踪对象的创建和销毁以查找资源泄漏
  • 通过跟踪调用源自的线程来检查线程安全
  • 将每个调用及其参数记录到标准输出
  • 跟踪 Vulkan 调用分析和重播

2、VkPhysicalDevice

       初始化 VKInstance 我们可以使用任意显卡,但是 Vulkan 支持我们根据我们想要的特性选择最适合我们的显卡。

        通过如下代码我们可以获取当前可选择的显卡物理设备

uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);

std::vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());

然后我们可以通过获取设备的属性信息和显卡支持的特性来选择最适合我们的设备

VkPhysicalDeviceProperties deviceProperties;
vkGetPhysicalDeviceProperties(device, &deviceProperties);

VkPhysicalDeviceFeatures deviceFeatures;
vkGetPhysicalDeviceFeatures(device, &deviceFeatures);

3、 QueueFamily

        Vulkan 中每个操作,都需要通过命令的形式提交到命令队列中,而命令队列又分为不同的种类,每种队列支持一个子集的命令。Vulkan 总共有 Graphic、Compute、Transfer 、SparseBinding 四种队列

//通过此代码可以获取 设备支持的队列,和队列的属性信息
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);

std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());

4、VKDevice 逻辑设备

        选择了物理设备后,VKDevice 逻辑设备就是用来和 物理设备交互的设备。创建逻辑设备的时候我们可以指定我们想要的特性,和需要创建的队列。原则上我们的逻辑设备是可以同时有多个的。

VkDeviceQueueCreateInfo queueCreateInfo{};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = 物理设备上查询到的queue的index
queueCreateInfo.queueCount = 队列的数量; 每个队列都有可创建的数量限制,可以在物理设备查询得到
queueCreateInfo.pQueuePriorities = 队列的优先级; //取值 0 ~ 1

//需要的物理设备特性
VkPhysicalDeviceFeatures deviceFeatures{};

VkDeviceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
createInfo.pQueueCreateInfos = &queueCreateInfo;  //队列创建信息,这里可以是一个数组
createInfo.queueCreateInfoCount = 1; //队列信息数组长度
createInfo.pEnabledFeatures = &deviceFeatures; // 需要的设备特性
createInfo.enabledExtensionCount = 0; //使用的扩展数量
createInfo.enabledLayerCount = 1; //使用的验证层 数量
createInfo.ppEnabledLayerNames = 使用的验证层的名字数组

//创建设备
if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
    throw std::runtime_error("failed to create logical device!");
}
//销毁设备
 vkDestroyDevice(device, nullptr);

//创建好逻辑设备的时候队列也一起创建好了,只要取获取即可
VkQueue graphicsQueue;
vkGetDeviceQueue(device, 队列index, 0, &graphicsQueue);

二、表面呈现

1、VKSurfaceKHR 窗口表面

        KHR 缩写的意思是扩展的意思,由于Vulkan 是平台无关的API 所以他是没有处理窗口相关的业务,需要窗口系统提供扩展功能来支持,所以在选择物理设备的时候我们需要查询设备是否支持是否只是演示。

VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);

       VKSurfaceKHR 在创建了 VKInstance 后就需要创建,因为需要的扩展 VK_KHR_surface 会影响我们的物理设备的选择。当然如果我们只做离屏渲染则不需要考虑 VKSurfaceKHR了。

//需要在 include glfw 库前定义宏开启 khr的内容
#define VK_USE_PLATFORM_WIN32_KHR
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h>


VkWin32SurfaceCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
createInfo.hwnd = glfwGetWin32Window(window); //返回 glfw 窗口的  HWND 句柄
createInfo.hinstance = GetModuleHandle(nullptr); //返回当前进程的句柄

//创建 SurfaceKHR
VkSurfaceKHR surface;
if (vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) {
    throw std::runtime_error("failed to create window surface!");
}

//在 glfw 中提供了快捷的接口可以实现上述复杂的过程
if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) {
        throw std::runtime_error("failed to create window surface!");
}

 在创建了 SurfaceKHR 后,我们还需要在创建逻辑设备的时候将我们的演示队列也一起创建出来

QueueFamilyIndices indices = findQueueFamilies(physicalDevice);

std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()};

float queuePriority = 1.0f;
for (uint32_t queueFamily : uniqueQueueFamilies) {
    VkDeviceQueueCreateInfo queueCreateInfo{};
    queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    queueCreateInfo.queueFamilyIndex = queueFamily;
    queueCreateInfo.queueCount = 1;
    queueCreateInfo.pQueuePriorities = &queuePriority;
    queueCreateInfos.push_back(queueCreateInfo);
}

createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
createInfo.pQueueCreateInfos = queueCreateInfos.data();

vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);

2、SwapChain 交换链

        Vulkan 没有OpenGL 默认帧缓冲的概念,因此需要一个设施将渲染命令渲染到一个缓冲区,然后再把这个缓冲区的图形显示在屏幕上,而这个设施就是 SwapChain 交换链。

        并不是所有的显卡都支持图像呈现在屏幕上,所以我们使用前得先查询下设备是否支持此扩展(VK_KHR_swapchain )。所以我们再选择物理设备的时候还要补充检查我们需要的扩展的支持情况来控制选择适合我们的物理设备。

//获取设备支持的扩展信息
uint32_t extensionCount;
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);

std::vector<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());

        选择了支持扩展的物理设备后,我们再创建逻辑设备的时候也要记得把我们需要的扩展信息传递进去。

createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
createInfo.ppEnabledExtensionNames = deviceExtensions.data();

         我们还需要查询交换链的支持情况,因为可能存在不兼容的情况。我们需要检查的内容如下

  • 基本表面功能(交换链中图像的最小/最大数量,图像的最小/最大宽度和高度)
  • 表面格式(像素格式、色彩空间)
  • 可用的演示模式
struct SwapChainSupportDetails {
    VkSurfaceCapabilitiesKHR capabilities;
    std::vector<VkSurfaceFormatKHR> formats;
    std::vector<VkPresentModeKHR> presentModes;
};

SwapChainSupportDetails details;
//获取支持的表面能力
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);

//获取支持的表面格式
uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
if (formatCount != 0) {
    details.formats.resize(formatCount);
    vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
}

//获取支持的呈现模式
uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);

if (presentModeCount != 0) {
    details.presentModes.resize(presentModeCount);
    vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
}

  在获取了交换链的能力后,我们还需要从支持的模式中选择对我们来说最优的模式来使用

VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
    for (const auto& availableFormat : availableFormats) {
        if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
            return availableFormat;
        }
    }

    return availableFormats[0];
}

VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
    for (const auto& availablePresentMode : availablePresentModes) {
        if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
            return availablePresentMode;
        }
    }

    return VK_PRESENT_MODE_FIFO_KHR;
}

VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
    if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
        return capabilities.currentExtent;
    } else {
        int width, height;
        glfwGetFramebufferSize(window, &width, &height);

        VkExtent2D actualExtent = {
            static_cast<uint32_t>(width),
            static_cast<uint32_t>(height)
        };

        actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
        actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);

        return actualExtent;
    }
}

三、渲染管线

1、着色器模块

        Vulkan 中使用的是 spir-v 格式的二进制的shader, GLSL 可以用vulkan提供的 glslc.exe工具将其编译长 spv格式。

VkShaderModule createShaderModule(const std::vector<char>& code) {
    VkShaderModuleCreateInfo createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
    createInfo.codeSize = code.size();
    createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
    
    VkShaderModule shaderModule;
    if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
        throw std::runtime_error("failed to create shader module!");
    }
    return shaderModule;
}

auto vertShaderCode = readFile("shaders/vert.spv");
auto fragShaderCode = readFile("shaders/frag.spv");

VkShaderModule vertShaderModule = createShaderModule(vertShaderCode);
VkShaderModule fragShaderModule = createShaderModule(fragShaderCode);

VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertShaderStageInfo.module = vertShaderModule;
vertShaderStageInfo.pName = "main";

VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragShaderStageInfo.module = fragShaderModule;
fragShaderStageInfo.pName = "main";

VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};

vkDestroyShaderModule(device, fragShaderModule, nullptr);
vkDestroyShaderModule(device, vertShaderModule, nullptr);

2、固定管线状态

        在OpenGL中大部分的固定管道状态都是有默认状态的。但是在Vulkan中,你必须主动设置大多数的管道状态。因为他将被烘焙到一个不可变的管道状态对象中(VKPipline)。

 2.1 动态状态

        管道状态大部分是在创建好管道后不能在动态修改的了。但是有些状态我们可能还是需要在创建后动态修改的。这时候我们可以在创建管道的时候设置我们需要保留的动态状态。这样这些动态状态我们可以在绘制的时候再单独重新指定具体的值

//指定了 viewport 和 scissor 是动态状态
std::vector<VkDynamicState> dynamicStates = {
    VK_DYNAMIC_STATE_VIEWPORT,
    VK_DYNAMIC_STATE_SCISSOR
};

VkPipelineDynamicStateCreateInfo dynamicState{};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
dynamicState.pDynamicStates = dynamicStates.data();

//在 record commandbuffer 的时候bindstate后动态指定 viewport 和 scissor
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);

VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float)swapChainExtent.width;
viewport.height = (float)swapChainExtent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);

VkRect2D scissor{};
scissor.offset = { 0, 0 };
scissor.extent = swapChainExtent;
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);

我们可以选择的动态状态有如下:


typedef enum VkDynamicState {
    VK_DYNAMIC_STATE_VIEWPORT = 0,
    VK_DYNAMIC_STATE_SCISSOR = 1,
    VK_DYNAMIC_STATE_LINE_WIDTH = 2,
    VK_DYNAMIC_STATE_DEPTH_BIAS = 3,
    VK_DYNAMIC_STATE_BLEND_CONSTANTS = 4,
    VK_DYNAMIC_STATE_DEPTH_BOUNDS = 5,
    VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK = 6,
    VK_DYNAMIC_STATE_STENCIL_WRITE_MASK = 7,
    VK_DYNAMIC_STATE_STENCIL_REFERENCE = 8,
    VK_DYNAMIC_STATE_CULL_MODE = 1000267000,
    VK_DYNAMIC_STATE_FRONT_FACE = 1000267001,
    VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY = 1000267002,
    VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT = 1000267003,
    VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT = 1000267004,
    VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE = 1000267005,
    VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE = 1000267006,
    VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE = 1000267007,
    VK_DYNAMIC_STATE_DEPTH_COMPARE_OP = 1000267008,
    VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE = 1000267009,
    VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE = 1000267010,
    VK_DYNAMIC_STATE_STENCIL_OP = 1000267011,
    VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE = 1000377001,
    VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE = 1000377002,
    VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE = 1000377004,
    VK_DYNAMIC_STATE_VIEWPORT_W_SCALING_NV = 1000087000,
    VK_DYNAMIC_STATE_DISCARD_RECTANGLE_EXT = 1000099000,
    VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_EXT = 1000143000,
    VK_DYNAMIC_STATE_RAY_TRACING_PIPELINE_STACK_SIZE_KHR = 1000347000,
    VK_DYNAMIC_STATE_VIEWPORT_SHADING_RATE_PALETTE_NV = 1000164004,
    VK_DYNAMIC_STATE_VIEWPORT_COARSE_SAMPLE_ORDER_NV = 1000164006,
    VK_DYNAMIC_STATE_EXCLUSIVE_SCISSOR_NV = 1000205001,
    VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR = 1000226000,
    VK_DYNAMIC_STATE_LINE_STIPPLE_EXT = 1000259000,
    VK_DYNAMIC_STATE_VERTEX_INPUT_EXT = 1000352000,
    VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT = 1000377000,
    VK_DYNAMIC_STATE_LOGIC_OP_EXT = 1000377003,
    VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT = 1000381000,
    VK_DYNAMIC_STATE_TESSELLATION_DOMAIN_ORIGIN_EXT = 1000455002,
    VK_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT = 1000455003,
    VK_DYNAMIC_STATE_POLYGON_MODE_EXT = 1000455004,
    VK_DYNAMIC_STATE_RASTERIZATION_SAMPLES_EXT = 1000455005,
    VK_DYNAMIC_STATE_SAMPLE_MASK_EXT = 1000455006,
    VK_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT = 1000455007,
    VK_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT = 1000455008,
    VK_DYNAMIC_STATE_LOGIC_OP_ENABLE_EXT = 1000455009,
    VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT = 1000455010,
    VK_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT = 1000455011,
    VK_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT = 1000455012,
    VK_DYNAMIC_STATE_RASTERIZATION_STREAM_EXT = 1000455013,
    VK_DYNAMIC_STATE_CONSERVATIVE_RASTERIZATION_MODE_EXT = 1000455014,
    VK_DYNAMIC_STATE_EXTRA_PRIMITIVE_OVERESTIMATION_SIZE_EXT = 1000455015,
    VK_DYNAMIC_STATE_DEPTH_CLIP_ENABLE_EXT = 1000455016,
    VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_ENABLE_EXT = 1000455017,
    VK_DYNAMIC_STATE_COLOR_BLEND_ADVANCED_EXT = 1000455018,
    VK_DYNAMIC_STATE_PROVOKING_VERTEX_MODE_EXT = 1000455019,
    VK_DYNAMIC_STATE_LINE_RASTERIZATION_MODE_EXT = 1000455020,
    VK_DYNAMIC_STATE_LINE_STIPPLE_ENABLE_EXT = 1000455021,
    VK_DYNAMIC_STATE_DEPTH_CLIP_NEGATIVE_ONE_TO_ONE_EXT = 1000455022,
    VK_DYNAMIC_STATE_VIEWPORT_W_SCALING_ENABLE_NV = 1000455023,
    VK_DYNAMIC_STATE_VIEWPORT_SWIZZLE_NV = 1000455024,
    VK_DYNAMIC_STATE_COVERAGE_TO_COLOR_ENABLE_NV = 1000455025,
    VK_DYNAMIC_STATE_COVERAGE_TO_COLOR_LOCATION_NV = 1000455026,
    VK_DYNAMIC_STATE_COVERAGE_MODULATION_MODE_NV = 1000455027,
    VK_DYNAMIC_STATE_COVERAGE_MODULATION_TABLE_ENABLE_NV = 1000455028,
    VK_DYNAMIC_STATE_COVERAGE_MODULATION_TABLE_NV = 1000455029,
    VK_DYNAMIC_STATE_SHADING_RATE_IMAGE_ENABLE_NV = 1000455030,
    VK_DYNAMIC_STATE_REPRESENTATIVE_FRAGMENT_TEST_ENABLE_NV = 1000455031,
    VK_DYNAMIC_STATE_COVERAGE_REDUCTION_MODE_NV = 1000455032,
    VK_DYNAMIC_STATE_CULL_MODE_EXT = VK_DYNAMIC_STATE_CULL_MODE,
    VK_DYNAMIC_STATE_FRONT_FACE_EXT = VK_DYNAMIC_STATE_FRONT_FACE,
    VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT = VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY,
    VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT_EXT = VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT,
    VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT_EXT = VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT,
    VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT = VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE,
    VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT = VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE,
    VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT = VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE,
    VK_DYNAMIC_STATE_DEPTH_COMPARE_OP_EXT = VK_DYNAMIC_STATE_DEPTH_COMPARE_OP,
    VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE_EXT = VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE,
    VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT = VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE,
    VK_DYNAMIC_STATE_STENCIL_OP_EXT = VK_DYNAMIC_STATE_STENCIL_OP,
    VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE_EXT = VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE,
    VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE_EXT = VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE,
    VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE_EXT = VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE,
    VK_DYNAMIC_STATE_MAX_ENUM = 0x7FFFFFFF
} VkDynamicState;

 2.2 顶点输入

        描述了将传递给顶点着色器的顶点数据的格式,

struct Vertex {
	glm::vec2 pos;
	glm::vec3 color;
};

//描述了所有顶点数据的格式,该如何分割每个顶点数据,并且指定 是否是 instance 渲染
VkVertexInputBindingDescription bindingDescription{};
bindingDescription.binding = 0;
bindingDescription.stride = sizeof(Vertex);
/*
typedef enum VkVertexInputRate {
    VK_VERTEX_INPUT_RATE_VERTEX = 0,  // 普通顶点渲染
    VK_VERTEX_INPUT_RATE_INSTANCE = 1,  // instancing 渲染
    VK_VERTEX_INPUT_RATE_MAX_ENUM = 0x7FFFFFFF
} VkVertexInputRate;
*/
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;


//描述了每个单独的顶点数据的格式,每个顶点有哪些成员
std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};
attributeDescriptions[0].binding = 0;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[0].offset = offsetof(Vertex, pos);

attributeDescriptions[1].binding = 0;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
attributeDescriptions[1].offset = offsetof(Vertex, color);


VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;

vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size();
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();

2.3 输入组件 

        描述两件事,1、描述传输的顶点按什么规律使用来绘制什么样的图形 2、是否启用  primitiveRestartEnable

VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
/*
typedef enum VkPrimitiveTopology {
    VK_PRIMITIVE_TOPOLOGY_POINT_LIST = 0, //来自顶点的点
    VK_PRIMITIVE_TOPOLOGY_LINE_LIST = 1, //从每 2 个顶点开始的线,不重复使用
    VK_PRIMITIVE_TOPOLOGY_LINE_STRIP = 2, //每行的结束顶点用作下一行的起始顶点
    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST = 3, // 三角形每 3 个顶点不重复使用
    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP = 4, //每个三角形的第二个和第三个顶点用作下一个三角形的前两个顶点
    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN = 5,
    VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY = 6,
    VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY = 7,
    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY = 8,
    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY = 9,
    VK_PRIMITIVE_TOPOLOGY_PATCH_LIST = 10,
    VK_PRIMITIVE_TOPOLOGY_MAX_ENUM = 0x7FFFFFFF
} VkPrimitiveTopology;
*/
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;

2.4 ViewPort、Scissor

        ViewPort 描述渲染输出将被渲染到的缓冲区区域,一般取值为(0, 0)~ (width, height),同时也能设置帧缓冲区中深度值的范围 一般为(0~1),而 Scissor 指定了要裁剪的区域,超出裁剪区域的输出都会被光栅化器丢弃不渲染。

VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float) swapChainExtent.width;
viewport.height = (float) swapChainExtent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;

VkRect2D scissor{};
scissor.offset = {0, 0};
scissor.extent = swapChainExtent;

2.5 光栅器

        光栅器,将顶点着色器生成的顶点到细分着色器、几何着色器最终生成的几何图形转换成一个一个的着色片段,它还执行,深度测试、面部剔除,裁剪测试,并且可以配置为输出填充数个多边形或仅填充边缘的片段(线框渲染)。

VkPipelineRasterizationStateCreateInfo rasterizer{};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
//如果 VK_TRUE 则 不在远平面和近平面之间的片段 不会丢弃,而是去 clamp 值(<0 则为0 >1 则为1),在阴影贴图的时候是很有用的
rasterizer.depthClampEnable = VK_FALSE;
// 控制图元是否在光栅化阶段前被立即丢弃
rasterizer.rasterizerDiscardEnable = VK_FALSE;
//图形的填充模式
/*
typedef enum VkPolygonMode {
    VK_POLYGON_MODE_FILL = 0,  //填充
    VK_POLYGON_MODE_LINE = 1, //画线
    VK_POLYGON_MODE_POINT = 2, //画点
    VK_POLYGON_MODE_FILL_RECTANGLE_NV = 1000153000, //填充矩形
    VK_POLYGON_MODE_MAX_ENUM = 0x7FFFFFFF
} VkPolygonMode;
*/
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
//光栅化器可以通过添加常量值或根据片段的斜率对其进行偏置来改变深度值。这有时用于阴影映射,但我们不会使用它。只需设置depthBiasEnable为VK_FALSE.
rasterizer.depthBiasEnable = VK_FALSE;
rasterizer.depthBiasConstantFactor = 0.0f; // Optional
rasterizer.depthBiasClamp = 0.0f; // Optional
rasterizer.depthBiasSlopeFactor = 0.0f; // Optional

2.6 多重采样

VkPipelineMultisampleStateCreateInfo multisampling{};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
multisampling.minSampleShading = 1.0f; // Optional
multisampling.pSampleMask = nullptr; // Optional
multisampling.alphaToCoverageEnable = VK_FALSE; // Optional
multisampling.alphaToOneEnable = VK_FALSE; // Optional

2.7 深度和模板测试

VkPipelineDepthStencilStateCreateInfo depthStencil{};
depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depthStencil.depthTestEnable = VK_TRUE;
depthStencil.depthWriteEnable = VK_TRUE;
depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
//depthBoundsTestEnable 于可选的深度边界测试。基本上,这允许您只保留落在指定深度范围(minDepthBounds-maxDepthBounds )内的片段
depthStencil.depthBoundsTestEnable = VK_FALSE;
depthStencil.minDepthBounds = 0.0f; // Optional
depthStencil.maxDepthBounds = 1.0f; // Optional

depthStencil.stencilTestEnable = VK_FALSE;
depthStencil.front = {}; // Optional
depthStencil.back = {}; // Optional

2.8 颜色混合

        用于控制片段着色器返回的颜色和帧缓冲中已有颜色的混合,有两种方法实现

        1、混合旧值和新值得到最终颜色

        2、使用按位运算组合旧值和新值 

       有两种类型的结构体控制颜色混合,一个是 VkPipelineColorBlendAttachmentState  、另外一个是 VkPipelineColorBlendStateCreateInfo  

//第一种控制方式(颜色混合)
VkPipelineColorBlendAttachmentState colorBlendAttachment{};
//颜色混合影响的 颜色位
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
//如果 false 那片段的输出的颜色不加修改的通过,直接变成最终颜色,否则执行颜色混合得到最终颜色
colorBlendAttachment.blendEnable = VK_FALSE;
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // Optional
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; // Optional
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // Optional
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optional

//第二种控制方式,如果要启用(按位组合)
//如果要启用此方法需要 logicOpEnable = VK_TRUE; 启用此方法会自动关闭第一个方法。
// colorBlendAttachment.colorWriteMask  将会用来控制这个方法中会受影响的位

VkPipelineColorBlendStateCreateInfo colorBlending{};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_FALSE;
colorBlending.logicOp = VK_LOGIC_OP_COPY; // Optional
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
colorBlending.blendConstants[0] = 0.0f; // Optional
colorBlending.blendConstants[1] = 0.0f; // Optional
colorBlending.blendConstants[2] = 0.0f; // Optional
colorBlending.blendConstants[3] = 0.0f; // Optional

2.9 PipelineLayout

        在着色器中我们会使用uniform值,这些值是动态的着色器中的全局变量,PipeLayout 用于描述这些 uniform 的结构信息 .

VkPipelineLayout pipelineLayout;
//通过 VKDescriptSet 对象描述具体的结构
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 0; // Optional
pipelineLayoutInfo.pSetLayouts = nullptr; // Optional
pipelineLayoutInfo.pushConstantRangeCount = 0; // Optional
pipelineLayoutInfo.pPushConstantRanges = nullptr; // Optional

if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
    throw std::runtime_error("failed to create pipeline layout!");
}

2.10、VKRenderPass

        在完成 Pipeline 前,我们还需要告诉 Vulkan 渲染过程中有哪些渲染 附件和如果处理这些附件。

       需要特别注意的是RenderPass 中的子通道,主要用于描述渲染过程中的附件的布局切换和子通道之间的执行依赖。

        子通道的依赖通过 VkSubpassDependency 来控制。

VkSubpassDependency dependency{};
//指定 每个子通道依赖的通道索引,其中 VK_SUBPASS_EXTERNAL 是特殊值代表渲染通道之前或之后的隐式子通道。需要注意的是 dstSubPass 始终要 > srcSubpass 防止出现循环 (触发子通道是 VK_SUBPASS_EXTERNAL) 
dependency.srcSubpass = VK_SUBPASS_EXTERNAL; //当前设置的 dstSubpass 依赖的通道
dependency.dstSubpass = 0; // 设置的通道index

//指定要等待的操作,和操作发生的阶段
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; //设置需要附件颜色输出完成的时候

dependency.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; //设置在我们访问颜色附件中颜色的时候 需要等待  srcStageMask 设置的阶段完成(这里是颜色附件输出完成)
//颜色附件描述
VkAttachmentDescription colorAttachment{};
//颜色格式
colorAttachment.format = swapChainImageFormat;
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; //采样次数
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; 
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
//颜色附件出事的布局格式,和使用完需要转换的布局格式
// 布局格式影响到我们使用附件时候的访问速度,所以合适的布局格式很重要
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

// 附件参考
VkAttachmentReference colorAttachmentRef{};
colorAttachmentRef.attachment = 0;
//使用部件是希望的部件布局
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

//每个RenderPass 可能存在多个子通道,用于描述每个附件之间的关联依赖关系
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
subpass.pInputAttachments = 从着色器读取的附件
subpass.pResolveAttachments = 用于多重采样颜色附件的附件
subpass.pDepthStencilAttachment = 深度和模板数据的附件
subpass.pPreserveAttachments = 此子通道未使用但必须保留其数据的附件


VkSubpassDependency dependency{};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.dstSubpass = 0;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.srcAccessMask = 0;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;


VkRenderPass renderPass;
VkPipelineLayout pipelineLayout;
kRenderPassCreateInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = 1;
renderPassInfo.pAttachments = &colorAttachment;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
//子通道依赖
renderPassInfo.dependencyCount = 1;
renderPassInfo.pDependencies = &dependency;

if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
    throw std::runtime_error("failed to create render pass!");
}

四、绘制

        我们一帧图片的程序流程如下:

  • 等待上一帧完成
  • 从交换链获取图像
  • 记录将场景绘制到该图像上的命令缓冲区
  • 提交记录的命令缓冲区
  • 呈现交换链图像

4.1 FrameBuffer

VkImageView attachments[] = {
    swapChainImageViews[i]
};

VkFramebufferCreateInfo framebufferInfo{};
framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferInfo.renderPass = renderPass;
framebufferInfo.attachmentCount = 1;
framebufferInfo.pAttachments = attachments;
framebufferInfo.width = swapChainExtent.width;
framebufferInfo.height = swapChainExtent.height;
framebufferInfo.layers = 1;

if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
    throw std::runtime_error("failed to create framebuffer!");
}

4.2 CommandPool、CommandBuffer

        Vulkan 中绘图和内存传输等操作,都不是直接用函数直接完成的。需要通过 CommandBuffer记录命令操作,最后统一提交命令执行的。而且这些命令的提交可以多线程提交。

        CommandBuffer 是不能直接创建的。必须依赖于 CommandPool,所以我们必须先创建CommandPool 

QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice);

VkCommandPool commandPool;
VkCommandPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
/*
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT:提示命令缓冲区经常用新命令重新记录(可能会改变内存分配行为)
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT: 允许单独重新记录命令缓冲区,如果没有这个标志,它们都必须一起重置
*/
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); //指定指令类型

if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {
    throw std::runtime_error("failed to create command pool!");
}

有了 CommondPool 后我们就可以创建 CommandBuffer了

VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = commandPool;
/*
VK_COMMAND_BUFFER_LEVEL_PRIMARY: 可以提交到队列执行,但不能从其他命令缓冲区调用。
VK_COMMAND_BUFFER_LEVEL_SECONDARY: 不能直接提交,但可以从主命令缓冲区调用。

secondary 命令一般都是在多线程提交,然后主线程调用提交使用
*/
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = 1;

if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) {
    throw std::runtime_error("failed to allocate command buffers!");
}

 4.3 命令的录制过程

//在命令录制前要先重置 commandbuffer
vkResetCommandBuffer(commandBuffer, 0)


//第一步
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
/*
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT:命令缓冲区执行一次后会立即重新记录。
VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT:这是一个辅助命令缓冲区,将完全在单个渲染过程中。
VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT:命令缓冲区可以在它也已经挂起执行时重新提交。
*/
beginInfo.flags = 0; // Optional
beginInfo.pInheritanceInfo = nullptr; // Optional

if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
    throw std::runtime_error("failed to begin recording command buffer!");
}

//第二步
VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = renderPass;
renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
renderPassInfo.renderArea.offset = {0, 0};
renderPassInfo.renderArea.extent = swapChainExtent;
VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;
/*
VK_SUBPASS_CONTENTS_INLINE:渲染过程命令将嵌入主命令缓冲区本身,不会执行辅助命令缓冲区。
VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS:渲染过程命令将从辅助命令缓冲区执行。
*/
vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

//第三步
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);

// pipline 中的动态状态需要动态重新设置
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = static_cast<float>(swapChainExtent.width);
viewport.height = static_cast<float>(swapChainExtent.height);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);

VkRect2D scissor{};
scissor.offset = {0, 0};
scissor.extent = swapChainExtent;
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);

//第四步
/*
vertexCount:即使我们没有顶点缓冲区,技术上我们仍然有 3 个顶点要绘制。
instanceCount:用于实例化渲染,1如果您不这样做,请使用。
firstVertex: 用作顶点缓冲区的偏移量,定义 的最低值gl_VertexIndex。
firstInstance:用作实例渲染的偏移量,定义 的最低值gl_InstanceIndex。
*/
vkCmdDraw(commandBuffer, 3, 1, 0, 0);

//第五步
vkCmdEndRenderPass(commandBuffer);

//第六步命令结束
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
    throw std::runtime_error("failed to record command buffer!");
}

4.4 命令同步操作

        Vulkan 在GPU 上的执行同步时显示的,操作的顺序由我们自己通过同步元素来控制,通过同步元素我们可以控制GPU执行我们命令的顺序。如下这些操作就需要明确执行的顺序

  • 从交换链获取图像
  • 执行在获取的图像上绘制的命令
  • 将该图像呈现到屏幕上进行展示,然后将其返回到交换链

我们可以使用的同步元素有

1、信号量  VKSemaphore 

        信号量用于在队列操作之间添加顺序。队列操作是指我们提交给队列的工作,可以是在命令缓冲区中,也可以是从我们稍后将看到的函数中。队列的示例是图形队列和演示队列。信号量用于在同一队列内和不同队列之间对工作进行排序。

        信号量是未发出信号或发出信号的。它以未发出信号的方式开始生活。我们使用信号量对队列操作进行排序的方法是在一个队列操作中提供与“信号”信号量相同的信号量,在另一个队列操作中提供相同的信号量作为“等待”信号量。例如,假设我们有信号量 S 和我们想要按顺序执行的队列操作 A 和 B。我们告诉 Vulkan 的是,操作 A 将在完成执行时“发出信号”信号量 S,而操作 B 将在信号量 S 开始执行之前“等待”它。当操作 A 完成时,信号量 S 将发出信号,而操作 B 直到 S 发出信号后才会开始。操作 B 开始执行后,信号量 S 自动重置为未发出信号,允许再次使用。伪代码如下:

VkCommandBuffer A, B = ... // record command buffers
VkSemaphore S = ... // create a semaphore

// enqueue A, signal S when done - starts executing immediately
vkQueueSubmit(work: A, signal: S, wait: None)

// enqueue B, wait on S to start
vkQueueSubmit(work: B, signal: None, wait: S)
VkSemaphoreCreateInfo semaphoreInfo{};
 semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;

VKSemaphore imageAvailableSemaphore;
vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore);

2、VKFence 

        Fence 主要用于 CPU 和 GPU 的同步操作,用于CPU层知道GPU何时完成某件事情。

VkCommandBuffer A = ... // record command buffer with the transfer
VkFence F = ... // create the fence

// enqueue A, start work immediately, signal F when done
vkQueueSubmit(work: A, fence: F)

vkWaitForFence(F) // blocks execution until A has finished executing

save_screenshot_to_disk() // can't run until the transfer has finished
VKFence inFlightFence; 

VkFenceCreateInfo fenceInfo{};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;  //设置创建好的 fence 立马发出信号,主要用于解决第一帧的时候,没有人渲染,就没有信号发射的问题。
vkCreateFence(device, &fenceInfo, nullptr, &inFlightFence) 

4.5  从 SwapChain 获取图像

uint32_t imageIndex;
/*
device 逻辑设备
swapchain  交换链
time  超时时间,UINT64_MAX 代表禁用超时机制
imageAvailableSemaphore 呈现完成时发射的信号量,代表呈现完成可以继续绘制下一帧了
fence,  呈现完成发射的 fence 通知 cpu 可以做的操作
imageIndex 代表输出的当前交换链中已经恢复使用的 ImageView
*/
vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);

4.6 提交命令

VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;

VkSemaphore waitSemaphores[] = {imageAvailableSemaphore};
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;

submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;

VkSemaphore signalSemaphores[] = {renderFinishedSemaphore};
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;

if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence) != VK_SUCCESS) {
    throw std::runtime_error("failed to submit draw command buffer!");
}

 4.7 最终图像呈现

        在渲染命令提交后,最后就是要提交交换链,并将结果显示在屏幕上。

VkPresentInfoKHR presentInfo{};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;

presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;

VkSwapchainKHR swapChains[] = {swapChain};
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;

presentInfo.pResults = nullptr; // Optional

vkQueuePresentKHR(presentQueue, &presentInfo);

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值