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執行,結果如下: