目录

  • 程序资源创建流程
    • window 相关(glfw)
    • 创建Vulkan实例
    • shader
    • compile shader
  • Code

程序资源创建流程

window 相关(glfw)

  - initWindow()glfwInit();glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);//glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);glfwSetWindowUserPointer(window, this);glfwSetFramebufferSizeCallback(window, framebufferResizeCallback);- mainLoop()while (!glfwWindowShouldClose(window)){glfwPollEvents();}- cleanup()glfwDestroyWindow(window);glfwTerminate();

创建Vulkan实例

  - initVulkan().createInstance().createSurface().setupDebugMessenger().pickPhysicalDevice().createLogicalDevice().createSwapChain().createImageViews().createRenderPass().createGraphicsPipeline() //shader.createFramebuffers().createCommandPool().createCommandBuffers().createSemaphores()   - mainLoop()while (!glfwWindowShouldClose(window)){glfwPollEvents();drawFrame();}- cleanup()cleanupSwapChain();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);}vkDestroyDevice(device, nullptr);if (enableValidationLayers) {DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);}vkDestroySurfaceKHR(instance, surface, nullptr);vkDestroyInstance(instance, nullptr);glfwDestroyWindow(window);

shader

shader.vert

 #version 450layout(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 : enablelayout(location = 0) in vec3 fragColor;layout(location = 0) out vec4 outColor;void main() {outColor = vec4(fragColor, 1.0);}

compile shader

compile.bat

C:\VulkanSDK\1.2.176.1\Bin32\glslc.exe shader.vert -o shaders/vert.spv
C:\VulkanSDK\1.2.176.1\Bin32\glslc.exe shader.frag -o shaders/frag.spv
pause

Code

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>#include <iostream>
#include <stdexcept>
#include <cstdlib>
#include <vector>
#include <map>
#include <optional>
#include <set>
#include <fstream>
//[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 Application {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 destroyedstd::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)){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 enablecreateInfo.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]GLFWauto 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_validationfor (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 非最优的方式使用VulkanVkDebugUtilsMessageTypeFlagsEXT 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 surfaceif (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, constVkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, constVkAllocationCallbacks* 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]创建代理函数 销毁CreateDebugUtilsMessengerEXTvoid DestroyDebugUtilsMessengerEXT(VkInstance instance,VkDebugUtilsMessengerEXT debugMessenger, constVkAllocationCallbacks* 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]选择适合该程序的GPUfor (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 allif (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 supportbool 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;//orQueueFamilyIndices indices = findQueueFamilies(device);//return indices.graphicsFamily.has_value();//orreturn indices.isComplete() && extensionsSupported && swapChainAdequate;}SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {SwapChainSupportDetails details;//[5]basic surface capabilities 基本性能vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);//[5]the supported surface formatsuint32_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 modesuint32_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 familyQueueFamilyIndices 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; // trueuint32_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]寻找一个队列族,它能够链接windowVkBool32 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 featuresVkPhysicalDeviceFeatures 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 familiesstd::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]将队列信息加入驱动infocreateInfo.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]因为我们只从这个族中创建一个队列,所以我们只使用索引0vkGetDeviceQueue(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_EXCLUSIVEQueueFamilyIndices 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; // OptionalcreateInfo.pQueueFamilyIndices = nullptr; // Optional}//[7]指定对交换链中的图像应用某种变换createInfo.preTransform = swapChainSupport.capabilities.currentTransform;//[7]alpha channel should be used for blendingcreateInfo.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_KHRVkPresentModeKHR 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 3DcreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;createInfo.format = swapChainImageFormat;//[8]components字段允许旋转颜色通道。 createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; //defaultcreateInfo.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 datacolorAttachment.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]引用AttachmentVkAttachmentReference colorAttachmentRef = {};//[9]attachment参数通过attachment描述数组中的索引指定要引用的attachmentcolorAttachmentRef.attachment = 0;//[9]布局指定了我们希望attachment在使用此引用的subpass中具有哪种布局。colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;//[9]Vulkan may also support compute subpasses in the futureVkSubpassDescription 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("shaders/vert.spv");auto fragShaderCode = readFile("shaders/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]viewportVkViewport 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; // Optionalmultisampling.pSampleMask = nullptr; // Optionalmultisampling.alphaToCoverageEnable = VK_FALSE; // Optionalmultisampling.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_TRUEcolorBlendAttachment.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; // OptionalcolorBlending.attachmentCount = 1;colorBlending.pAttachments = &colorBlendAttachment;colorBlending.blendConstants[0] = 0.0f; // OptionalcolorBlending.blendConstants[1] = 0.0f; // OptionalcolorBlending.blendConstants[2] = 0.0f; // OptionalcolorBlending.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; // OptionalpipelineLayoutInfo.pSetLayouts = nullptr; // OptionalpipelineLayoutInfo.pushConstantRangeCount = 0; // OptionalpipelineLayoutInfo.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; // OptionalpipelineInfo.pColorBlendState = &colorBlending;pipelineInfo.pDynamicState = nullptr; // OptionalpipelineInfo.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; // OptionalpipelineInfo.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执行。//这意味着在管道创建完成后,就可以立即销毁ShaderModulevkDestroyShaderModule(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 framebuffersfor (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; //不使用flagif (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 bufferfor (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; // Optionalif (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) {throw std::runtime_error("failed to begin recording command buffer!");}//[13]Starting a render passVkRenderPassBeginInfo renderPassInfo = {};//[13]the render pass itself and the attachments to bindrenderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;renderPassInfo.renderPass = renderPass;renderPassInfo.framebuffer = swapChainFramebuffers[i];//[13]the size of the render arearenderPassInfo.renderArea.offset = { 0, 0 }; //It should match the size of the attachments for best performancerenderPassInfo.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 endedif (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 finishedfenceInfo.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 chainvoid 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 frameimagesInFlight[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 之前先 cleanupvoid 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 rebuiltvoid 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<Application*>(glfwGetWindowUserPointer(window));app->framebufferResized = true;}//[16]--------------------------------------------------------------------------------------------------------
};int main() {Application app;try {app.run();}catch (const std::exception& e) {std::cerr << e.what() << std::endl;return EXIT_FAILURE;}return EXIT_FAILURE;
}

Vulkan学习(八): Hello Triangle 重构相关推荐

  1. 从零开始编写深度学习库(四)Eigen::Tensor学习使用及代码重构

    从零开始编写深度学习库(四)Eigen::Tensor学习使用及代码重构 博客:http://blog.csdn.net/hjimce 微博:黄锦池-hjimce   qq:1393852684 一. ...

  2. 由学习《软件设计重构》所想到的代码review(二)

    前言 对于一个程序员来讲如何来最直接的来衡量他的技术能力和产出呢?我想最直观的作法是看他的代码编写能力,就拿我经常接触的一些程序员来看,他们买了很多技术重构类书籍,但是看完后代码编写能力并没有显著提高 ...

  3. 方差 标准差_方差与标准差——杭州市初中数学核心组寒假微课学习八年级第38课...

    国家正值非常时期,开学已经推迟,为响应"在推迟开学时段,指导各地各校充分利用'互联网+'的模式共享优质教育资源,开展远程教育教学活动和学生课业辅导,努力实现我市广大中小学校学'停课不停学', ...

  4. VULKAN学习资料

    VULKAN学习资料 1,中文开发教程:https://www.cnblogs.com/heitao/p/7193853.html posted on 2019-03-31 03:14 时空观察者9号 ...

  5. VULKAN学习资料收集

    VULKAN学习资料收集 https://github.com/vinjn/awesome-vulkan 张静初 https://zhuanlan.zhihu.com/p/24798656 知乎 ht ...

  6. VULKAN学习笔记-inter教学四篇

    VULKAN学习笔记-inter教学四篇 --交换链相关函数:实例层 vkCreateWin32SurfaceKHR vkDestroySurfaceKHR vkGetPhysicalDeviceSu ...

  7. OpenCV与图像处理学习八——图像边缘提取(Canny检测代码)

    OpenCV与图像处理学习八--图像边缘提取(Canny检测代码) 一.图像梯度 1.1 梯度 1.2 图像梯度 二.梯度图与梯度算子 2.1模板卷积 2.2 梯度图 2.3 梯度算子 2.3.1 R ...

  8. PyTorch框架学习八——PyTorch数据读取机制(简述)

    PyTorch框架学习八--PyTorch数据读取机制(简述) 一.数据 二.DataLoader与Dataset 1.torch.utils.data.DataLoader 2.torch.util ...

  9. JMS学习八(ActiveMQ消息持久化)

    JMS学习八(ActiveMQ消息持久化) ActiveMQ的消息持久化机制有JDBC,AMQ,KahaDB和LevelDB,还有一种内存存储的方式,由于内存不属于持久化范畴,而且如果使用内存队列,可 ...

  10. 推荐系统遇上深度学习(八十七)-[阿里]基于搜索的用户终身行为序列建模

    本文介绍的论文是<Search-based User Interest Modeling with Lifelong Sequential Behavior Data for Click-Thr ...

最新文章

  1. ECMAScript 6 Features 中文版
  2. 西门子逻辑运算指令_西门子S7-300PLC逻辑运算指令
  3. 执行maven clean package 时报OutOfMemoryError的解决办法
  4. 马尔科夫链和马尔科夫链蒙特卡洛方法
  5. python 扫描枪_python 之serial、pyusb 使用开发
  6. 我是真的傻,她被超市安保罚了100元,我居然给她50元
  7. 防暴力破解一些安全机制
  8. 利用WinRar压缩和解压缩文件
  9. 最近在整理和准备发布
  10. 模板模式 php,PHP设计模式5-模板模式
  11. 就地升级Lync Server 到Skype for Business Server
  12. 在centos中如何用yum安装最新的yum源
  13. 学习几个Excel表格职场实战技巧
  14. 【sketchup 2021】草图大师的辅助建模工具1【量角器与文字、尺寸标注与三维字、实体工具】
  15. python输入一个包含若干自然数的列表_Python练习题
  16. C# 学习笔记04-15
  17. 【raspberry pi】树莓派3测评
  18. Permute mac版 v3.7.2 文件格式转换器
  19. 【DSP学习笔记】定点DSP小数乘加计算
  20. 【电子产品】ThinkPad S5 Intel R Dual Band Wireless-AC 3165 网卡驱动莫名

热门文章

  1. 奥塔在线:VisualStudio中使用Git的详细配置说明
  2. 第一篇:基于小米手机的,解锁教程教学
  3. 磁场强度单位和磁感应强度单位转换
  4. 自媒体人都在用这 5个素材网站
  5. 技术学习:Python(11)|操作PDF
  6. ARduino接KY-040电位编码器
  7. 花开花落花非花、缘起缘灭缘随缘
  8. JavaScript sort 方法 默认排序顺序为按字母升序-数组常用方法
  9. 利用esp8266接入小爱同学,实现智能台灯的改造物联网初识
  10. 破解某助手刺探功能---第二篇smali代码实现