Vulkan是Khronos Group组织发布的跨平台图像渲染引擎,而Khronos Group是由Intel、Nvidia等公司共同创立,致力于创建开放标准的应用程序API。大名鼎鼎的OpenGL、OpenGL ES、WebGL、Vulkan都是来自Khronos组织。而Vulkan号称为“下一版本的OpenGL”,旨在提供更低的CPU开销和更多GPU控制。Android API 24以后支持vulkan,iOS在WWDC2014也推出Metal图像渲染。短期内全面取代OpenGL应该不现实,但从长远来看,它取代OpenGL应该是大势所趋的。笔者也是新接触Vulkan,从初学者的角度来看,它的程序确实比较繁琐,一个简单的Triangle程序竟然要1000多行的代码,但万丈高楼平地起,像学习OpenGL一样,任何复杂的工程都必须先从简单的三角形开始,这里我把Vulkan的三角形先写下来,给后来者一个参考。
1、配置Vulkan的环境,这里就不细说了,网上有很多教程,这里推荐一篇吧,地址是:https://blog.csdn.net/allflowerup/article/details/89766179,总结下来就是将vulkan下载下来并安装好,再将glfw和glm两个库文件放置到VS的环境设置目录里,后者(glfw和glm)的设置和OpenGL的开发环境搭建一模一样。顺便说下:
GLFW是配合 OpenGL 使用的轻量级工具程序库,缩写自 Graphics Library Framework(图形库框架)。GLFW 的主要功能是创建并管理窗口和 OpenGL 上下文,同时还提供了处理手柄、键盘、鼠标输入的功能。
GLM(OpenGL Mathematics )是基于OpenGL着色语言(GLSL)规范的图形软件的头文件C ++数学库。GLM提供的类和函数使用与GLSL相同的命名约定和功能设计和实现,因此任何知道GLSL的人都可以在C ++中使用GLM。
2、我是用Visual Studio2019,先在VS里创建一个空的项目为HelloTriangle,然后为它添加一个HelloTriangle.cpp的源文件,代码如下:
#define GLFW_INCLUDE_VULKAN #include <GLFW/glfw3.h> #include <iostream> #include <stdexcept> #include <cstdlib> #include <vector> #include <map> #include <optional> #include <set> #include <fstream> void processInput(GLFWwindow* window); //[2]验证层Debug时开启 #ifdef NDEBUG const bool enableValidationLayers = false; #else const bool enableValidationLayers = true; #endif //[2]所有有用的标准验证都捆绑到SDK的一个层中,称为VK_LAYER_KHRONOS_validation层。 const std::vector<const char*> validationLayers = { "VK_LAYER_KHRONOS_validation" }; //[5] struct QueueFamilyIndices { std::optional<uint32_t> graphicsFamily; std::optional<uint32_t> presentFamily; //[8]为了方便起见,我们还将向结构本身添加一个泛型检查 bool isComplete() { return graphicsFamily.has_value() && presentFamily.has_value(); } }; //[5] struct SwapChainSupportDetails { VkSurfaceCapabilitiesKHR capabilities; std::vector<VkSurfaceFormatKHR> formats; std::vector<VkPresentModeKHR> presentModes; }; //[5] const std::vector<const char*> deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; //[7] const int WIDTH = 800; //[7] const int HEIGHT = 600; //[10]ate: Start reading at the end of the file //[10]binary: Read the file as binary file (avoid text transformations) static std::vector<char> readFile(const std::string& filename) { std::ifstream file(filename, std::ios::ate | std::ios::binary); if (!file.is_open()) { throw std::runtime_error("failed to open file!"); } //[10]ate 的优势是,可以获取文件的大小 size_t fileSize = (size_t)file.tellg(); std::vector<char> buffer(fileSize); //[10]指针跳到头 file.seekg(0); file.read(buffer.data(), fileSize); file.close(); return buffer; } //[14] const int MAX_FRAMES_IN_FLIGHT = 2; class HelloTriangle { public: void run() { //[1] initWindow(); initVulkan(); mainLoop(); cleanup(); } public: //[1] GLFWwindow* window; //[2] VkInstance instance; //[3] VkSurfaceKHR surface; //[4] VkDebugUtilsMessengerEXT debugMessenger; //[5]当vkinInstance被销毁时,该对象将被隐式销毁,因此不需要在cleanup函数中执行销毁。 VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; //[6] VkDevice device; //[6] VkQueue graphicsQueue; //[6] VkQueue presentQueue; //[7] VkSwapchainKHR swapChain; //[7] std::vector<VkImage> swapChainImages; //[7] VkFormat swapChainImageFormat; //[7] VkExtent2D swapChainExtent; //[8] std::vector<VkImageView> swapChainImageViews; //[9] VkRenderPass renderPass; //[10] VkPipelineLayout pipelineLayout; //[10] VkPipeline graphicsPipeline; //[11] std::vector<VkFramebuffer> swapChainFramebuffers; //[12] VkCommandPool commandPool; //[13] Command buffers will be automatically freed when their command pool is destroyed std::vector<VkCommandBuffer> commandBuffers; //[14] std::vector<VkSemaphore> imageAvailableSemaphores; //[14] std::vector<VkSemaphore> renderFinishedSemaphores; //[14] std::vector<VkFence> inFlightFences; //[14]-[15]是否需要释放? std::vector<VkFence> imagesInFlight; //[15] size_t currentFrame = 0; //[16] bool framebufferResized = false; //[1] void initWindow() { glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); //glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); //[16-1]储存当前对象指针 glfwSetWindowUserPointer(window, this); //[1] 检测窗体实际大小,我们可以使用 GLFW 框架中的 glfwSetFramebufferSizeCallback 函数来设置回调: glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); } void initVulkan() { //[2] createInstance(); //[3] createSurface(); //[4] setupDebugMessenger(); //[5] pickPhysicalDevice(); //[6] createLogicalDevice(); //[7] createSwapChain(); //[8] createImageViews(); //[9] createRenderPass(); //[10] createGraphicsPipeline(); //[11] createFramebuffers(); //[12] createCommandPool(); //[13] createCommandBuffers(); //[14] createSemaphores(); } void mainLoop() { //[1] while (!glfwWindowShouldClose(window)) { processInput(window); glfwPollEvents(); //[15] drawFrame(); } //[16]可以用作执行同步的基本的方法. vkDeviceWaitIdle(device); } void cleanup() { //[17] cleanupSwapChain(); //[14] for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); vkDestroyFence(device, inFlightFences[i], nullptr); } //17将这些释放资源的函数移动到cleanupSwapChain中 //[12] //vkDestroyCommandPool(device, commandPool, nullptr); //[11] //for (auto framebuffer : swapChainFramebuffers) { // vkDestroyFramebuffer(device, framebuffer, nullptr); //} //[10] //vkDestroyPipeline(device, graphicsPipeline, nullptr); //[10] //vkDestroyPipelineLayout(device, pipelineLayout, nullptr); //[9] //vkDestroyRenderPass(device, renderPass, nullptr); //[8] //for (auto imageView : swapChainImageViews) { // vkDestroyImageView(device, imageView, nullptr); //} //[7] //vkDestroySwapchainKHR(device, swapChain, nullptr); //[6] vkDestroyDevice(device, nullptr); //[4] if (enableValidationLayers) { DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); } //[3] vkDestroySurfaceKHR(instance, surface, nullptr); //[2] vkDestroyInstance(instance, nullptr); //[1] glfwDestroyWindow(window); glfwTerminate(); } //[2]-------------------------------------------------------------------------------------------------------- void createInstance() { //[2]创建实例时检测是否启用验证层 if (enableValidationLayers && !checkValidationLayerSupport()) { throw std::runtime_error("validation layers requested, but not available!"); } //[2]well-known graphics engine VkApplicationInfo appInfo = {}; //[2]结构体必须指明类型,pNext指向拓展信息 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.pEngineName = "No Engine"; appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; //[2]Vulkan驱动程序使用哪些全局扩展和验证,后续后详细说明 VkInstanceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; //[2]指定全局扩展 uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); createInfo.enabledExtensionCount = glfwExtensionCount; createInfo.ppEnabledExtensionNames = glfwExtensions; //[2]the global validation layers to enable createInfo.enabledLayerCount = 0; //后续有说明 //[2]验证层信息 //[2]如果检查成功,那么vkCreateInstance不会返回VK_ERROR_LAYER_NOT_PRESENT错误 if (enableValidationLayers) { createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } //[2]GLFW auto extensions = getRequiredExtensions(); createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); //[2]重用 //[2]通过该方式创建一个额外的调试信息,它将在vkCreateInstance和vkDestroyInstance期间自动创建和销毁 VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo; if (enableValidationLayers) { createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); populateDebugMessengerCreateInfo(debugCreateInfo); createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; } else { createInfo.enabledLayerCount = 0; createInfo.pNext = nullptr; } //[2]or /*if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug messenger!"); }*/ //[2] VK_SUCCESS or Error Code //[2]VkResult result = vkCreateInstance(&createInfo, nullptr, &instance); //[2]or //[2]创建实例 if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); /* * //[2]验证层说明,Vulkan每次调用都会进行相应的验证,通过返回值判定函数是否执行成功 VkResult vkCreateInstance( const VkInstanceCreateInfo * pCreateInfo, const VkAllocationCallbacks * pAllocator, VkInstance * instance) { if (pCreateInfo == nullptr || instance == nullptr) { log("Null pointer passed to required parameter!"); return VK_ERROR_INITIALIZATION_FAILED; } return real_vkCreateInstance(pCreateInfo, pAllocator, instance); } */ } //[2]the number of extensions //[2]支持扩展的数量 uint32_t extensionCount = 0; vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); //[2]an array of VkExtensionProperties to store details of the extensions. //[2]an array to hold the extension details //[2]支持的扩展详细信息 std::vector<VkExtensionProperties> extensionsProperties(extensionCount); vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensionsProperties.data()); //[2]query the extension details //[2]Each VkExtensionProperties struct contains the name and version of an extension. //[2]查询扩展的详细信息 std::cout << "available extensions:" << std::endl; for (const auto& extension : extensionsProperties) { std::cout << "\t" << extension.extensionName << std::endl; } } //[2]list all of the available layers //[2]列出所有验证层的信息 bool checkValidationLayerSupport() { uint32_t layerCount; vkEnumerateInstanceLayerProperties(&layerCount, nullptr); std::vector<VkLayerProperties> availableLayers(layerCount); vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); //[2]查询是否存在验证层信息 layerName = VK_LAYER_KHRONOS_validation for (const char* layerName : validationLayers) { bool layerFound = false; for (const auto& layerProperties : availableLayers) { if (strcmp(layerName, layerProperties.layerName) == 0) { layerFound = true; break; } } if (!layerFound) { return false; } } return true; } //[2]we have to set up a debug messenger with a callback using the VK_EXT_debug_utils extension. //[2]我们必须使用VK_EXT_debug_utils扩展,设置一个带有回调的debug messenger。 std::vector<const char*> getRequiredExtensions() { //[5]指定GLFW扩展,但是debug messenger 扩展是有条件添加的 uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { //[2]在这里使用VK_EXT_DEBUG_UTILS_EXTENSION_NAME宏,它等于字符串“VK_EXT_debug_utils”。 //[2]使用此宏可以避免输入错误 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; } //[2]仔细阅读扩展文档,就会发现有一种方法可以专门为这两个函数调用创建单独的 debug utils messenger. //[2]它要求您只需在VkInstanceCreateInfo的pNext扩展字段中 //[2]传递一个指向VkDebugUtilsMessengerCreateInfoEXT结构的指针。 //[2]首先将messenger创建信息的填充提取到单独的函数中: void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; createInfo.pfnUserCallback = debugCallback; } //[2]Add a new static member function called debugCallback with //[2] the PFN_vkDebugUtilsMessengerCallbackEXT prototype. //[2]使用PFN_vkDebugUtilsMessengerCallbackEXT属性添加一个静态函数 //[2]The VKAPI_ATTR and VKAPI_CALL ensure that the function has the //[2] right signature for Vulkan to call it. //[2]使用VKAPI_ATTR和VKAPI_CALL 确保函数具有正确的签名,以便Vulkan调用它 static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( //[2]VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT 诊断信息 //[2]VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT 信息性消息,如资源的创建 //[2]关于行为的消息,其不一定是错误,但很可能是应用程序中的BUG //[2]VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT //[2]关于无效且可能导致崩溃的行为的消息 //[2]VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT //[2]可以使用比较操作来检查消息是否与某个严重性级别相等或更差,例如: //[2]if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { //[2] // Message is important enough to show //[2]} VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, //[2]VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT 发生了一些与规范或性能无关的事件 //[2]VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT 发生了违反规范或一些可能显示的错误 //[2]VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT 非最优的方式使用Vulkan VkDebugUtilsMessageTypeFlagsEXT messageType, //[2]消息本身的详细信息, 包括其重要成员: //[2]pMessage 以null结尾的调试消息字符串 //[2]pObjects 与消息相关的Vulkan对象句柄数组 //[2]objectCount 数组中的对象数 //[2]pUserData 包含回调指定的指针,允许将自己设置的数据传递给它。 const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } //[2]-------------------------------------------------------------------------------------------------------- //[3]-------------------------------------------------------------------------------------------------------- void createSurface() { //Windows的创建方法 //VkWin32SurfaceCreateInfoKHR createInfo = {}; //createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; //createInfo.hwnd = glfwGetWin32Window(window); //createInfo.hinstance = GetModuleHandle(nullptr); //if (vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) { // throw std::runtime_error("failed to create window surface!"); //} //Linux的创建方法与上面类似 vkCreateXcbSurfaceKHR //[10]使用GLFWWindow surface if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } //[3]-------------------------------------------------------------------------------------------------------- //[4]-------------------------------------------------------------------------------------------------------- void setupDebugMessenger() { if (!enableValidationLayers) return; VkDebugUtilsMessengerCreateInfoEXT createInfo = {}; populateDebugMessengerCreateInfo(createInfo); //[4] messenger 创建信息的填充提取到单独的函数中 if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug messenger!"); } //[4]or // createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; //[4]指定希望调用回调严重性类型 //createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | // VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | // VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; //[4]滤回调通知的消息类型 //createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | // VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | // VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; //[4]指定指向回调函数的指针 //createInfo.pfnUserCallback = debugCallback; //[4]返回的回调函数 //createInfo.pUserData = nullptr; } //[4]创建代理函数 VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { auto func = (PFN_vkCreateDebugUtilsMessengerEXT) //[4]如果无法加载,函数将返回nullptr。 vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } //[4]创建代理函数 销毁CreateDebugUtilsMessengerEXT void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { func(instance, debugMessenger, pAllocator); } } //[4]-------------------------------------------------------------------------------------------------------- //[5]-------------------------------------------------------------------------------------------------------- void pickPhysicalDevice() { //[5]查询GPU数量 uint32_t deviceCount = 0; vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); if (deviceCount == 0) { throw std::runtime_error("failed to find GPUs with Vulkan support!"); } //[5]获取驱动信息 std::vector<VkPhysicalDevice> devices(deviceCount); vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); //[5]选择适合该程序的GPU for (const auto& device : devices) { if (isDeviceSuitable(device)) { physicalDevice = device; break; } } if (physicalDevice == VK_NULL_HANDLE) { throw std::runtime_error("failed to find a suitable GPU!"); } //or //[5]使用有序Map,通过分数自动对显卡排序 std::multimap<int, VkPhysicalDevice> candidates; for (const auto& device : devices) { int score = rateDeviceSuitability(device); candidates.insert(std::make_pair(score, device)); } //[5]Check if the best candidate is suitable at all if (candidates.rbegin()->first > 0) { physicalDevice = candidates.rbegin()->second; } else { throw std::runtime_error("failed to find a suitable GPU!"); } } //[5]GPU是否适合该程序的 bool isDeviceSuitable(VkPhysicalDevice device) { //[5]查询显卡属性,包括:名称,支持Vulkan的版本号 //VkPhysicalDeviceProperties deviceProperties; //vkGetPhysicalDeviceProperties(device, &deviceProperties); //[5]扩展支持 bool extensionsSupported = checkDeviceExtensionSupport(device); //[5]swap chain support bool swapChainAdequate = false; if (extensionsSupported) { SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); } //[5]查询显卡特性,包括:纹理压缩,64位浮点述。多视口渲染(VR) //VkPhysicalDeviceFeatures deviceFeatures; //vkGetPhysicalDeviceFeatures(device, &deviceFeatures); //[5]是否为专业显卡(a dedicated graphics card )(独显),是否支持几何着色器 //return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && deviceFeatures.geometryShader; //or QueueFamilyIndices indices = findQueueFamilies(device); //return indices.graphicsFamily.has_value(); //or return indices.isComplete() && extensionsSupported && swapChainAdequate; } SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { SwapChainSupportDetails details; //[5]basic surface capabilities 基本性能 vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); //[5]the supported surface formats uint32_t formatCount; vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); if (formatCount != 0) { details.formats.resize(formatCount); vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); } //[5]the supported presentation modes uint32_t presentModeCount; vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); if (presentModeCount != 0) { details.presentModes.resize(presentModeCount); vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); } return details; } QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { //[5]Logic to find graphics queue family QueueFamilyIndices indices; //[5]Logic to find queue family indices to populate struct with //[5]C++ 17引入了optional数据结构来区分存在或不存在的值的情况。 //[5]std::optional<uint32_t> graphicsFamily; //[5]std::cout << std::boolalpha << graphicsFamily.has_value() <<std::endl; // false //[5]graphicsFamily = 0; //[5]std::cout << std::boolalpha << graphicsFamily.has_value() << std::endl; // true uint32_t queueFamilyCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount); vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); //我们需要找到至少一个支持VK_QUEUE_GRAPHICS_BIT的族。 int i = 0; for (const auto& queueFamily : queueFamilies) { //[5]寻找一个队列族,它能够链接window VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); if (presentSupport) { indices.presentFamily = i; } if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; if (indices.isComplete()) break; } i++; } return indices; } //[5] bool checkDeviceExtensionSupport(VkPhysicalDevice device) { uint32_t extensionCount; vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); std::vector<VkExtensionProperties> availableExtensions(extensionCount); vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); for (const auto& extension : availableExtensions) { requiredExtensions.erase(extension.extensionName); } return requiredExtensions.empty(); } int rateDeviceSuitability(VkPhysicalDevice device) { //[5]查询显卡属性,包括:名称,支持Vulkan的版本号 VkPhysicalDeviceProperties deviceProperties; vkGetPhysicalDeviceProperties(device, &deviceProperties); int score = 0; //[5]离散GPU具有显著的性能优势 if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { score += 1000; } //[5]支持纹理的最大值,影响图形质量 score += deviceProperties.limits.maxImageDimension2D; //[5]查询显卡特性,包括:纹理压缩,64位浮点述。多视口渲染(VR) VkPhysicalDeviceFeatures deviceFeatures; vkGetPhysicalDeviceFeatures(device, &deviceFeatures); //[5]不支持几何着色器 if (!deviceFeatures.geometryShader) { return 0; } return score; } //[5]-------------------------------------------------------------------------------------------------------- //[6]-------------------------------------------------------------------------------------------------------- void createLogicalDevice() { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); VkDeviceQueueCreateInfo queueCreateInfo = {}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value(); queueCreateInfo.queueCount = 1; //[6]Vulkan使用0.0到1.0之间的浮点数为队列分配优先级, 来进行缓冲区执行的调度。即使只有一个队列,这也是必需的: float queuePriority = 1.0f; queueCreateInfo.pQueuePriorities = &queuePriority; //[6]device features VkPhysicalDeviceFeatures deviceFeatures = {}; VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; createInfo.pQueueCreateInfos = &queueCreateInfo; createInfo.queueCreateInfoCount = 1; createInfo.pEnabledFeatures = &deviceFeatures; //[6]VK_KHR_swapchain 将该设备的渲染图像显示到windows //[6]之前版本Vulkan实现对实例和设备特定的验证层进行了区分,但现在情况不再如此。 //[6]这意味着VkDeviceCreateInfo的enabledLayerCount和ppEnabledLayerNames字段被最新的实现忽略。不过,还是应该将它们设置为与较旧的实现兼容: createInfo.enabledExtensionCount = 0; if (enableValidationLayers) { createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } //[6]create a queue from both families std::vector<VkDeviceQueueCreateInfo> queueCreateInfos; std::set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; 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); } //[6]将队列信息加入驱动info createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); //[6]开启扩展支持 createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); //[6]创建驱动 if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } //[6]获取驱动队列 //[6]因为我们只从这个族中创建一个队列,所以我们只使用索引0 vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); //[6]显示队列 vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } //[6]-------------------------------------------------------------------------------------------------------- //[7]-------------------------------------------------------------------------------------------------------- void createSwapChain() { SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); //[7]然而,简单地坚持这个最小值意味着我们有时可能需要等待驱动程序完成内部操作, //[7]然后才能获取另一个要渲染的图像。因此,建议请求至少比最小值多一个图像: //[7]uint32_t imageCount = swapChainSupport.capabilities.minImageCount; uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { imageCount = swapChainSupport.capabilities.maxImageCount; } VkSwapchainCreateInfoKHR createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; createInfo.minImageCount = imageCount; createInfo.imageFormat = surfaceFormat.format; createInfo.imageColorSpace = surfaceFormat.colorSpace; createInfo.imageExtent = extent; //[7]imageArrayLayers指定每个图像包含的层的数量。除非您正在开发3D应用程序,否则该值始终为1。 createInfo.imageArrayLayers = 1; createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; //[7]两种方法可以处理从多个队列访问的图像: //[7]VK_SHARING_MODE_CONCURRENT //[7]VK_SHARING_MODE_EXCLUSIVE QueueFamilyIndices indices = findQueueFamilies(physicalDevice); uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; createInfo.queueFamilyIndexCount = 2; createInfo.pQueueFamilyIndices = queueFamilyIndices; } else { createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; createInfo.queueFamilyIndexCount = 0; // Optional createInfo.pQueueFamilyIndices = nullptr; // Optional } //[7]指定对交换链中的图像应用某种变换 createInfo.preTransform = swapChainSupport.capabilities.currentTransform; //[7]alpha channel should be used for blending createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; createInfo.presentMode = presentMode; createInfo.clipped = VK_TRUE; //[7]窗口重置是取缓存区图像方式 createInfo.oldSwapchain = VK_NULL_HANDLE; if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); swapChainImageFormat = surfaceFormat.format; swapChainExtent = extent; } //[7] 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; } } //[12]如果查询失败返回第一个 return availableFormats[0]; } //[7]VK_PRESENT_MODE_IMMEDIATE_KHR //[7]VK_PRESENT_MODE_FIFO_KHR //[7]VK_PRESENT_MODE_FIFO_RELAXED_KHR //[7]VK_PRESENT_MODE_MAILBOX_KHR VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) { //[7]三级缓存更好,如果有就开启 for (const auto& availablePresentMode : availablePresentModes) { if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { return availablePresentMode; } } return VK_PRESENT_MODE_FIFO_KHR; } //[7]在minImageExtent和maxImageExtent内选择与窗口最匹配的分辨率 VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != UINT32_MAX) { return capabilities.currentExtent; } else { //[16]为了正确地处理窗口大小调整,还需要查询帧缓冲区的当前窗口大小,以确保交swap chain(new)中图像大小正确。 int width, height; glfwGetFramebufferSize(window, &width, &height); VkExtent2D actualExtent = { static_cast<uint32_t>(width), static_cast<uint32_t>(height) }; //VkExtent2D actualExtent = { WIDTH, HEIGHT }; actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); return actualExtent; } } //[7]-------------------------------------------------------------------------------------------------------- //[8]-------------------------------------------------------------------------------------------------------- void createImageViews() { swapChainImageViews.resize(swapChainImages.size()); for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; //[8]选择视图类型 1D 2D 3D createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; createInfo.format = swapChainImageFormat; //[8]components字段允许旋转颜色通道。 createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; //default createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; //[8]描述图像的用途以及应访问图像的哪个部分。 createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; createInfo.subresourceRange.baseMipLevel = 0; createInfo.subresourceRange.levelCount = 1; createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create image views!"); } } } //[8]-------------------------------------------------------------------------------------------------------- //[9]-------------------------------------------------------------------------------------------------------- void createRenderPass() { VkAttachmentDescription colorAttachment = {}; //[9]colorAttachment的format应与swapChain图像的格式匹配 colorAttachment.format = swapChainImageFormat; //[9]没有使用多重采样(multisampling),将使用1个样本。 colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; //[9]clear the framebuffer to black before drawing a new frame //[9]VK_ATTACHMENT_LOAD_OP_LOAD 保留Attachment的现有内容 //[9]VK_ATTACHMENT_LOAD_OP_CLEAR 开始时将值初始化为常数 //[9]VK_ATTACHMENT_LOAD_OP_DONT_CARE 现有内容未定义; 忽略 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; //[9]color and depth data //[9]VK_ATTACHMENT_STORE_OP_STORE 渲染的内容将存储在内存中,以后可以读取 //[9]VK_ATTACHMENT_STORE_OP_DONT_CARE 渲染操作后,帧缓冲区的内容将是未定义的 colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; //[9]stencil data colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; //[9]Textures and framebuffers //[9]指定在渲染开始之前图像将具有的布局。 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; //[9]指定在渲染完成时自动过渡到的布局。 //[9]VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL used as color attachment //[9]VK_IMAGE_LAYOUT_PRESENT_SRC_KHR Images the swap chain 中要显示的图像 //[9]VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL 用作存储器复制操作目的图像 colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; //[9]引用Attachment VkAttachmentReference colorAttachmentRef = {}; //[9]attachment参数通过attachment描述数组中的索引指定要引用的attachment colorAttachmentRef.attachment = 0; //[9]布局指定了我们希望attachment在使用此引用的subpass中具有哪种布局。 colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; //[9]Vulkan may also support compute subpasses in the future VkSubpassDescription subpass = {}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; //[9]引用colorAttachment //[9]数组中attachment的索引是直接从fragment shader 引用的(location = 0)out vec4 outColor指令! //[9]subpass可以引用以下其他类型的attachment //[9]pInputAttachments read from a shader //[9]pResolveAttachments used for multisampling attachments //[9]pDepthStencilAttachment depth and stencil data //[9]pPreserveAttachments not used by this subpass but for which the data must be preserved(保存) subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; //[9] VkRenderPassCreateInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } //[9]-------------------------------------------------------------------------------------------------------- //[10]-------------------------------------------------------------------------------------------------------- void createGraphicsPipeline() { //[10] auto vertShaderCode = readFile("vert.spv"); auto fragShaderCode = readFile("frag.spv"); //[10] VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); //[10]可以将多个着色器组合到一个ShaderModule中,并使用不同的entry points来区分它们的行为。 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"; //[10]指定着色器常量的值。 //[10]单个着色器模块,在管道中创建常量,给予不同的值来配置其行为。 //[10]比在渲染时使用变量配置着色器更为有效, 编译器可以进行优化,例如消除依赖于这些值的if语句 //[10]如果没有这样的常量,则可以将成员设置为nullptr,初始化会自动执行该操作。 //[10]pSpecializationInfo //[10] 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"; //[10] VkPipelineShaderStageCreateInfo shaderStages[] = { vertShaderStageInfo, fragShaderStageInfo }; //[10] VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputInfo.vertexBindingDescriptionCount = 0; //[10]描述上述用于加载顶点数据的详细信息 vertexInputInfo.pVertexBindingDescriptions = nullptr; vertexInputInfo.vertexAttributeDescriptionCount = 0; //[10]描述上述用于加载顶点数据的详细信息 vertexInputInfo.pVertexAttributeDescriptions = nullptr; //[10] VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; inputAssembly.primitiveRestartEnable = VK_FALSE; //[10]viewport VkViewport viewport = {}; viewport.x = 0.0f; viewport.y = 0.0f; viewport.width = (float)swapChainExtent.width; viewport.height = (float)swapChainExtent.height; //[10]minDepth和maxDepth 在0.0f到1.0f之间 viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; //[10]裁剪矩形定义哪些区域像素被存储 VkRect2D scissor = {}; scissor.offset = { 0, 0 }; scissor.extent = swapChainExtent; //[10]viewport 和scissor可以有多个 VkPipelineViewportStateCreateInfo viewportState = {}; viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewportState.viewportCount = 1; viewportState.pViewports = &viewport; viewportState.scissorCount = 1; viewportState.pScissors = &scissor; VkPipelineRasterizationStateCreateInfo rasterizer = {}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; //[10]保留近平面和远平面之外的片元 rasterizer.depthClampEnable = VK_FALSE; //[10]true 几何图形不会通过光栅化阶段。 禁用对帧缓冲区的任何输出 rasterizer.rasterizerDiscardEnable = VK_FALSE; //[10]VK_POLYGON_MODE_FILL 用片段填充多边形区域 //[10]VK_POLYGON_MODE_LINE 多边形边缘绘制为线 //[10]VK_POLYGON_MODE_POINT 多边形顶点绘制为点 rasterizer.polygonMode = VK_POLYGON_MODE_FILL; //[10]支持的最大线宽取决于硬件,任何比1.0f粗的线都需要启用wideLines。 rasterizer.lineWidth = 1.0f; //[10]确定要使用的面部剔除类型。 rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; //[10]指定面片视为正面的顶点顺序,可以是顺时针或逆时针 rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; //[10]Rasterization可以通过添加常量或基于片段的斜率对深度值进行偏置来更改深度值。 //[10]多用于阴影贴图,如不需要将depthBiasEnable设置为VK_FALSE。 rasterizer.depthBiasEnable = VK_FALSE; rasterizer.depthBiasConstantFactor = 0.0f; rasterizer.depthBiasClamp = 0.0f; rasterizer.depthBiasSlopeFactor = 0.0f; //[10] 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 //[10]specification详见说明文档 //[10]每个附加的帧缓冲区的混合规则 VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; //[6]片段着色器的颜色直接输出 colorBlendAttachment.blendEnable = VK_FALSE; colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; /* if (blendEnable) { finalColor.rgb = (srcColorBlendFactor * newColor.rgb) < colorBlendOp > (dstColorBlendFactor * oldColor.rgb); finalColor.a = (srcAlphaBlendFactor * newColor.a) < alphaBlendOp > (dstAlphaBlendFactor * oldColor.a); } else { finalColor = newColor; } finalColor = finalColor & colorWriteMask; */ //[10]透明混合 /* finalColor.rgb = newAlpha * newColor + (1 - newAlpha) * oldColor; finalColor.a = newAlpha.a; */ //[10]VK_TRUE colorBlendAttachment.blendEnable = VK_TRUE; colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; //[10]全局颜色混合设置。 VkPipelineColorBlendStateCreateInfo colorBlending = {}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; //[10]bitwise combination 请注意,这将自动禁用第一种方法 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 //[10] VkDynamicState dynamicStates[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_LINE_WIDTH }; //[10] VkPipelineDynamicStateCreateInfo dynamicState = {}; dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamicState.dynamicStateCount = 2; dynamicState.pDynamicStates = dynamicStates; //[10] 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 //[10] if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } //[10] VkGraphicsPipelineCreateInfo pipelineInfo = {}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; pipelineInfo.pVertexInputState = &vertexInputInfo; pipelineInfo.pInputAssemblyState = &inputAssembly; pipelineInfo.pViewportState = &viewportState; pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pDepthStencilState = nullptr; // Optional pipelineInfo.pColorBlendState = &colorBlending; pipelineInfo.pDynamicState = nullptr; // Optional pipelineInfo.layout = pipelineLayout; //[10]引用将使用图形管线的renderPass和subpass索引。 //[10]也可以使用其他渲染管线,但是它们必须与renderPass兼容 pipelineInfo.renderPass = renderPass; pipelineInfo.subpass = 0; //[10]Vulkan允许通过从现有管线中派生来创建新的图形管线。 //[10]管线派生的思想是,当管线具有与现有管线共有的许多功能时,建立管线的成本较低, //[10]并且可以更快地完成同一父管线之间的切换。 //[10]可以使用basePipelineHandle指定现有管线的句柄,也可以使用basePipelineIndex引用索引创建的另一个管线。 //[10]现在只有一个管线,因此我们只需指定一个空句柄和一个无效索引。 //[10]仅当在VkGraphicsPipelineCreateInfo的flags字段中还指定了VK_PIPELINE_CREATE_DERIVATIVE_BIT标志时,才使用这些值。 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional pipelineInfo.basePipelineIndex = -1; // Optional //[10]第二个参数VK_NULL_HANDLE引用了一个可选的VkPipelineCache对象。 //[10]管线Cache可用于存储和重用管线创建相关的数据 //[10]可以加快管线的创建速度,后有详解 if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } //[10] //在创建图形管线之前,不会将SPIR-V字节码编译并链接到机器代码以供GPU执行。 //这意味着在管道创建完成后,就可以立即销毁ShaderModule vkDestroyShaderModule(device, fragShaderModule, nullptr); vkDestroyShaderModule(device, vertShaderModule, nullptr); } 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; } //[10]-------------------------------------------------------------------------------------------------------- //[11]-------------------------------------------------------------------------------------------------------- void createFramebuffers() { //[11]调整容器大小以容纳所有帧缓冲区 swapChainFramebuffers.resize(swapChainImageViews.size()); //[11] create framebuffers for (size_t i = 0; i < swapChainImageViews.size(); i++) { 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!"); } } } //[11]-------------------------------------------------------------------------------------------------------- //[12]-------------------------------------------------------------------------------------------------------- void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); VkCommandPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); //[12]There are two possible flags for command pools //[12]VK_COMMAND_POOL_CREATE_TRANSIENT_BIT 提示CommandPool经常用新命令重新记录(可能会改变内存分配行为) //[12]VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT 允许单独重新记录命CommandPool,否则都必须一起重置 poolInfo.flags = 0; //不使用flag if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create command pool!"); } } //[13]-------------------------------------------------------------------------------------------------------- void createCommandBuffers() { commandBuffers.resize(swapChainFramebuffers.size()); VkCommandBufferAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; //[13]指定primary 或 secondary command buffers. //[13]VK_COMMAND_BUFFER_LEVEL_PRIMARY 可以提交到队列执行,但不能从其他命令缓冲区调用。 //[13]VK_COMMAND_BUFFER_LEVEL_SECONDARY 不能直接提交,但可以从主命令缓冲区调用 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { throw std::runtime_error("failed to allocate command buffers!"); } //[13]recording a command buffer for (size_t i = 0; i < commandBuffers.size(); i++) { VkCommandBufferBeginInfo beginInfo = {}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; //[13]指定指定我们将如何使用command buffers. //[13]VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT 在执行一次后立即rerecord。 //[13]VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT 这是一个辅助command buffers, 将在单个渲染通道中使用 //[13] VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT command buffer 可以在已经挂起执行时重新提交。 beginInfo.flags = 0; // Optional //[13]仅与secondary command buffers 相关。 它指定从primarycommand buffers 继承哪些状态。 beginInfo.pInheritanceInfo = nullptr; // Optional if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { throw std::runtime_error("failed to begin recording command buffer!"); } //[13]Starting a render pass VkRenderPassBeginInfo renderPassInfo = {}; //[13]the render pass itself and the attachments to bind renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassInfo.renderPass = renderPass; renderPassInfo.framebuffer = swapChainFramebuffers[i]; //[13]the size of the render area renderPassInfo.renderArea.offset = { 0, 0 }; //It should match the size of the attachments for best performance renderPassInfo.renderArea.extent = swapChainExtent; VkClearValue clearColor = { 0.0f, 0.0f, 0.0f, 1.0f }; renderPassInfo.clearValueCount = 1; renderPassInfo.pClearValues = &clearColor; //[13]最后一个参数:how the drawing commands within the render pass will be provided //[13]VK_SUBPASS_CONTENTS_INLINE render pass commands 将嵌入 primary command buffer, 并且不会执行 secondary command buffers //[13]VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERSrender pass commands 将从 secondary command buffers 执行 vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); //[13]Basic drawing commands //[13]第二个参数指定管线对象是图形管线还是计算管线。 vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); //[13]vertexCount 即使没有顶点缓冲区,从技术上讲,仍然有3个顶点要绘制 //[13]instanceCount 用于实例化渲染,如果不进行实例化使用1 //[13]firstVertex 顶点缓冲区的偏移量,定义gl_VertexIndex的最小值 //[13]firstInstance 用作实例渲染的偏移量,定义gl_InstanceIndex的最小值 vkCmdDraw(commandBuffers[i], 3, 1, 0, 0); //[13]The render pass can now be ended if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to record command buffer!"); } } } //[13]-------------------------------------------------------------------------------------------------------- //[14]-------------------------------------------------------------------------------------------------------- void createSemaphores() { //[14] imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); //[14] inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); //[15] imagesInFlight.resize(swapChainImages.size(), VK_NULL_HANDLE); //[14] Vulkan API 会扩展 flags 和 pNext 参数。 VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; //[14] VkFenceCreateInfo fenceInfo = {}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; //[14]if we had rendered an initial frame that finished fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; //[14] for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { //[14] if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || //[14] vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create semaphores for a frame!"); } } } //[14]-------------------------------------------------------------------------------------------------------- //[15]-------------------------------------------------------------------------------------------------------- //[15]异步执行的 //[15]acquire an image from the swap chain void drawFrame() { //[15]wait for the frame to be finished //[15]vkWaitForFences函数接受一个fences数组,并等待其中任何一个或所有fences在返回之前发出信号。 //[15]这里传递的VK_TRUE表示要等待所有的fences,但是对於单个fences来说,这显然无关紧要。 //[15]就像vkAcquireNextImageKHR一样,这个函数也需要超时。与信号量不同,我们需要通过vkresetfines调用将fence重置为unsignaled状态。 //vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); //or uint32_t imageIndex; //[15]第三个参数指定可获得图像的超时时间(以纳秒为单位)。 //[15]接下来的两个参数指定了同步对象,这些对象将在引擎使用完图像时发出信号。 可以指定semaphore、fence或都指定 //[15]用于输出可用swap chain中图像的索引。 //vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); //or //[15] //vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); //or //[16] VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); //[16]VK_ERROR_OUT_OF_DATE_KHR: Swap chain 与 surface 不兼容,无法再用于渲染。 通常发生在窗口调整大小之后 //[16]VK_SUBOPTIMAL_KHR Swap: chain仍可用于成功呈现到surface,但surface属性不再完全匹配 if (result == VK_ERROR_OUT_OF_DATE_KHR) { //[18]如果SwapChain在尝试获取图像时已过时,则无法再显示给它。因此,我们应该立即重新创建SwapChain,并在下一个drawFrame调用中重试。 recreateSwapChain(); return; } //[16]如果SwapChain是次优的,可以调用recreateSwapChain,但还是选择继续,因为我们已经获得了一个图像。VK_SUCCESS和VK_SUBOPTIMAL_KHR都被认为是“成功” 。 else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { throw std::runtime_error("failed to acquire swap chain image!"); } //[15]Check if a previous frame is using this image (i.e. there is its fence to wait on) if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) { vkWaitForFences(device, 1, &imagesInFlight[imageIndex], VK_TRUE, UINT64_MAX); } //[15] Mark the image as now being in use by this frame imagesInFlight[imageIndex] = inFlightFences[currentFrame]; VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; //VkSemaphore waitSemaphores[] = { imageAvailableSemaphore }; //or //[15] VkSemaphore waitSemaphores[] = { imageAvailableSemaphores[currentFrame] }; VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; //[15]前三个参数指定在执行开始之前等待哪些Semaphore,以及在管线的哪个阶段等待。 submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; submitInfo.pWaitDstStageMask = waitStages; //[15]指定实际提交执行的commandBuffer。 submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; //[15]指定在commandBuffer完成执行后发送信号的Semaphore。 //VkSemaphore signalSemaphores[] = { renderFinishedSemaphore}; //or //[15] VkSemaphore signalSemaphores[] = { renderFinishedSemaphores[currentFrame] }; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; //[15] vkResetFences(device, 1, &inFlightFences[currentFrame]); //[15]最后一个参数引用了可选的 fenc, 当 command buffer 执行完成时,它将发出信号。 //[15]我们使用信号量进行同步,所以我们传递VK_NULL_HANDLE。 //if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { // throw std::runtime_error("failed to submit draw command buffer!"); //} //or //[15] if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } VkPresentInfoKHR presentInfo = {}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; //[15]在呈现(Present)之前等待哪些信号量 presentInfo.waitSemaphoreCount = 1; presentInfo.pWaitSemaphores = signalSemaphores; VkSwapchainKHR swapChains[] = { swapChain }; presentInfo.swapchainCount = 1; //[15]为每个swapChain指定呈现的图像和图像索引 presentInfo.pSwapchains = swapChains; presentInfo.pImageIndices = &imageIndex; //[15]check for every individual swap chain if presentation was successful. (array of VkResult) presentInfo.pResults = nullptr; // Optional //[15]submits the request to present an image to the swap chain. //vkQueuePresentKHR(presentQueue, &presentInfo); //or //[16]vkQueuePresentKHR 函数返回具有相同vkAcquireNextImageKHR返回值含义相同的值。 //[16]在这种情况下,如果SwapChain不是最优的,我们也会重新创建它,因为我们想要最好的结果。 result = vkQueuePresentKHR(presentQueue, &presentInfo); //添加 framebufferResized 确保semaphores处于一致状态,否则可能永远不会正确等待发出信号的semaphores。 if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { framebufferResized = false; recreateSwapChain(); } else if (result != VK_SUCCESS) { throw std::runtime_error("failed to present swap chain image!"); } //[15] currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; //[15] vkQueueWaitIdle(presentQueue); } //[15]-------------------------------------------------------------------------------------------------------- //[16]-------------------------------------------------------------------------------------------------------- //[16]recreate 之前先 cleanup void cleanupSwapChain() { for (size_t i = 0; i < swapChainFramebuffers.size(); i++) { vkDestroyFramebuffer(device, swapChainFramebuffers[i], nullptr); } //[16]可以重新创建Command池,但相当浪费的。相反,使用vkfreecandbuffers函数清理现有CommandBuffer。就可以重用现有的池来分配新的CommandBuffer。 vkFreeCommandBuffers(device, commandPool, static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data()); vkDestroyPipeline(device, graphicsPipeline, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyRenderPass(device, renderPass, nullptr); for (size_t i = 0; i < swapChainImageViews.size(); i++) { vkDestroyImageView(device, swapChainImageViews[i], nullptr); } vkDestroySwapchainKHR(device, swapChain, nullptr); } //[16] recreate SwapChain, pipeline must rebuilt void recreateSwapChain() { //[16]处理最小化 int width = 0, height = 0; glfwGetFramebufferSize(window, &width, &height); while (width == 0 || height == 0) { glfwGetFramebufferSize(window, &width, &height); glfwWaitEvents(); } //[16]等待资源完成使用。 vkDeviceWaitIdle(device); cleanupSwapChain(); createSwapChain(); createImageViews(); createRenderPass(); createGraphicsPipeline(); createFramebuffers(); createCommandBuffers(); } //[16] static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { //[16-2]当前对象指针赋值 auto app = reinterpret_cast<HelloTriangle*>(glfwGetWindowUserPointer(window)); app->framebufferResized = true; } //[16]-------------------------------------------------------------------------------------------------------- }; int main() { HelloTriangle app; try { app.run(); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_FAILURE; } //处理所有的输入,查询glfw看是否有相关的键被按下或松开,并作出相应的响应处理 void processInput(GLFWwindow* window) { if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS || glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); }
3、在项目中再建两个着色器文件,分别为:顶点着色器的shader.vert和片元着色器的shader.frag
shader.vert
#version 450 layout(location = 0) out vec3 fragColor; vec2 positions[3] = vec2[]( vec2(0.0, -0.5), vec2(0.5, 0.5), vec2(-0.5, 0.5) ); vec3 colors[3] = vec3[]( vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0) ); void main() { gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); fragColor = colors[gl_VertexIndex]; }
shader.frag
#version 450 #extension GL_ARB_separate_shader_objects : enable layout(location = 0) in vec3 fragColor; layout(location = 0) out vec4 outColor; void main() { outColor = vec4(fragColor, 1.0); }
4、将shader.vert和shader.frag分别编译成vert.spv和frag.spv,笔者自己的环境是在F盘,如下:
F:\Study\Vulkan\HelloTriangle1>glslc.exe shader.vert -o vert.spv F:\Study\Vulkan\HelloTriangle1>glslc.exe shader.frag -o frag.spv F:\Study\Vulkan\HelloTriangle1>dir 驱动器 F 中的卷是 新加卷 卷的序列号是 86A1-6462 F:\Study\Vulkan\HelloTriangle1 的目录 2022/03/19 17:13 <DIR> . 2022/03/19 17:13 <DIR> .. 2022/03/19 17:13 608 frag.spv 2022/03/19 17:13 61,696 HelloTriangle.cpp 2022/03/19 17:08 30,563 HelloTriangle1.vcxproj 2022/03/19 17:08 1,204 HelloTriangle1.vcxproj.filters 2022/03/15 18:08 168 HelloTriangle1.vcxproj.user 2022/03/19 17:08 218 shader.frag 2022/03/19 17:07 391 shader.vert 2022/03/19 17:13 1,504 vert.spv 2022/03/15 18:12 <DIR> x64 9 个文件 96,352 字节 3 个目录 66,113,679,360 可用字节 F:\Study\Vulkan\HelloTriangle1>
这里对spriv进行简单描述,它是Standard, Portable Intermediate Representation - V (SPIR-V)的缩写,是一种用于GPU通用计算和图形学的中间语言,在OpenGL4.6版本后才支持SPIR-V,由Khronos开发设计,最初是为OpenCL规范准备的,和Vulkan的标准差不多同时提出,也还在不断发展完善中,详细介绍请移步:https://blog.csdn.net/aoxuestudy/article/details/112350292
5、按VS上的F5执行,结果如下: