目录

  • Vulkan程序结构
  • initWindow
  • initVulkan
    • createInstance
    • createSurface
    • setupDebugMessenger
    • pickPhysicalDevice
    • createLogicalDevice
    • createSwapChain
    • createSwapChain
  • mainLoop
  • cleanup
  • Code

Vulkan程序结构

      initWindow()  //初始化windowinitVulkan()  //初始化VulkanmainLoop()    //渲染循环cleanup()     //释放资源

initWindow

   创建窗体,一般选择创建窗口图形库进行创建。如(GLFW)

     glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);

initVulkan

   包括:

     createInstance();      //创建Vulkan实例createSurface();       //创建SurfacesetupDebugMessenger(); //启动调试信息pickPhysicalDevice();  //选择显卡createLogicalDevice(); //创建逻辑设备createSwapChain();     //创建SwapChaincreateImageViews();    //创建视图

createInstance

     createInstance()-- checkValidationLayerSupport()  //创建实例时检测是否启用验证层-vkEnumerateInstanceLayerProperties() //查询验证层属性信息-- vkCreateInstance()  //创建实例,包括相关配置信息-VkApplicationInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO.pApplicationName = "Hello Triangle".applicationVersion = VK_MAKE_VERSION(1, 0, 0).pEngineName = "No Engine".engineVersion = VK_MAKE_VERSION(1, 0, 0).apiVersion = VK_API_VERSION_1_0-VkInstanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO.pApplicationName = "Hello Triangle".pApplicationInfo = &VkApplicationInfo//指定全局扩展.enabledExtensionCount = glfwGetRequiredInstanceExtensions   //参数.ppEnabledExtensionNames = glfwGetRequiredInstanceExtensions //返回值//验证层信息.enabledLayerCount = tatic_cast<uint32_t>(validationLayers.size()) //or 0.ppEnabledLayerNames = validationLayers.data()                     //or not//额外的调试信息.VkDebugUtilsMessengerCreateInfoEXT-sType-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;-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;-pfnUserCallback = debugCallback.pNext = &VkDebugUtilsMessengerCreateInfoEXT//扩展信息.enabledExtensionCount = static_cast<uint32_t>(extensions.size()).ppEnabledExtensionNames = extensions.data().getRequiredExtensions()                  //返回值是extensions//参数值是glfwExtensionCount,返回值是glfwExtensions.glfwGetRequiredInstanceExtensions() //指定GLFW扩展,debug messenger 扩展是有条件添加的--vkEnumerateInstanceExtensionProperties()  //支持扩展的数量--vkEnumerateInstanceExtensionProperties()  //支持的扩展详细信息

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--使用GLFWWindow surfaceif (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) {throw std::runtime_error("failed to create window surface!");}

setupDebugMessenger

     --VkDebugUtilsMessengerCreateInfoEXT-sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT-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;-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;-pfnUserCallback = debugCallback-debugCallback.VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,.VkDebugUtilsMessageTypeFlagsEXT messageType,.const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData)-CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger)

pickPhysicalDevice

     --vkEnumeratePhysicalDevices();--rateDeviceSuitability() //使用有序Map,通过分数自动对显卡排序-VkPhysicalDeviceProperties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU.limits = maxImageDimension2D-vkGetPhysicalDeviceProperties()-VkPhysicalDeviceFeatures.geometryShader-vkGetPhysicalDeviceFeatures()

createLogicalDevice

     --findQueueFamilies();-vkGetPhysicalDeviceQueueFamilyProperties().std::vector<VkQueueFamilyProperties>  queueFamilies.vkGetPhysicalDeviceSurfaceSupportKHR()int i = 0;for (const auto& queueFamily : queueFamilies) {//寻找一个队列,它能够链接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++;}--VkDeviceQueueCreateInfo-sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO-queueFamilyIndex = indices.graphicsFamily.value() -queueCount = 1-queuePriority = 1.0f; //队列分配优先级-pQueuePriorities =  &queuePriority--std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;-VkDeviceQueueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO.queueFamilyIndex = queueFamily.queueCount = 1.pQueuePriorities = &queuePriority--VkPhysicalDeviceFeatures-sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO-pQueueCreateInfos = &queueCreateInfo-queueCreateInfoCount = 1-pEnabledFeatures = &deviceFeatures-enabledLayerCount = static_cast<uint32_t>(validationLayers.size())-ppEnabledLayerNames = validationLayers.data()-queueCreateInfoCount =static_cast<uint32_t>(queueCreateInfos.size())-pQueueCreateInfos = queueCreateInfos.data()-enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size())-ppEnabledExtensionNames = deviceExtensions.data()--vkCreateDevice(physicalDevice, &createInfo, nullptr, &device)//获取驱动队列 & ]显示队列--vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue)--vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue)

createSwapChain

     --querySwapChainSupport()-vkGetPhysicalDeviceSurfaceCapabilitiesKHR().SwapChainSupportDetails-vkGetPhysicalDeviceSurfaceFormatsKHR()-vkGetPhysicalDeviceSurfacePresentModesKHR()--chooseSwapSurfaceFormat()for (const auto& availableFormat : availableFormats) {if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {return availableFormat;}}--chooseSwapPresentMode()for (const auto& availablePresentMode : availablePresentModes) {if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {return availablePresentMode;}}--chooseSwapExtent()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;--VkSwapchainCreateInfoKHR-sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR-surface = surface-minImageCount = imageCount-imageFormat = surfaceFormat.format-imageColorSpace = surfaceFormat.colorSpace-imageExtent = extent-imageArrayLayers = 1-imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT-imageSharingMode = VK_SHARING_MODE_CONCURRENT-queueFamilyIndexCount = 2-pQueueFamilyIndices = queueFamilyIndices-preTransform = swapChainSupport.capabilities.currentTransform;-compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR-presentMode = presentMode-clipped = VK_TRUE-oldSwapchain = VK_NULL_HANDLE--vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain)--vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr)--vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());

createSwapChain

     --VkImageViewCreateInfo-sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO-image = swapChainImages[i]-viewType = VK_IMAGE_VIEW_TYPE_2D-format = swapChainImageFormat-components.r = VK_COMPONENT_SWIZZLE_IDENTITY-components.g = VK_COMPONENT_SWIZZLE_IDENTITY-components.b = VK_COMPONENT_SWIZZLE_IDENTITY-components.a = VK_COMPONENT_SWIZZLE_IDENTITY-subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT-subresourceRange.baseMipLevel = 0-subresourceRange.levelCount = 1-subresourceRange.baseArrayLayer = 0-subresourceRange.layerCount = 1--vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i])

mainLoop

     while (!glfwWindowShouldClose(window)) {glfwPollEvents();}

cleanup

     --vkDestroyImageView(device, imageView, nullptr)--vkDestroySwapchainKHR(device, swapChain, nullptr)--DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr)auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");if (func != nullptr) {func(instance, debugMessenger, pAllocator);}--vkDestroyDevice(device, nullptr)--vkDestroySurfaceKHR(instance, surface, nullptr)--vkDestroyInstance(instance, nullptr)--glfwDestroyWindow(window)--glfwTerminate()

Code

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>#include <vulkan/vulkan.h>#include <iostream>
#include <GLFW/glfw3.h>#include <vulkan/vulkan.h>#include <iostream>  //[1]
#include <stdexcept> //[1]异常处理函数
#include <functional>//[1]提供 EXIT_SUCCESS and EXIT_FAILURE 宏指令
#include <cstdlib>
#include <vector>
#include <map>
#include <optional>
#include <set>
#include <cstdint> // Necessary for UINT32_MAXconst int WIDTH = 800;
const int HEIGHT = 600;//[4]所有有用的标准验证都捆绑到SDK的一个层中,称为VK_LAYER_KHRONOS_validation层。
const std::vector<const char*> validationLayers = {"VK_LAYER_KHRONOS_validation"};
//[12]检查是否支持SWAPCHAIN
const std::vector<const char*> deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
//[4]验证层Debug时开启
#ifdef NDEBUGconst bool enableValidationLayers = false;
#elseconst bool enableValidationLayers = true;
#endif
struct QueueFamilyIndices {std::optional<uint32_t> graphicsFamily;std::optional<uint32_t> presentFamily;//[8]为了方便起见,我们还将向结构本身添加一个泛型检查bool isComplete() {return graphicsFamily.has_value() && presentFamily.has_value();}
};struct SwapChainSupportDetails {VkSurfaceCapabilitiesKHR capabilities;std::vector<VkSurfaceFormatKHR> formats;std::vector<VkPresentModeKHR> presentModes;
};class HelloTriangleApplication
{public:void run() {initWindow(); //[2]initVulkan(); //[1]初始化Vulakn相关mainLoop();   //[1]渲染循环(start rendering frames) cleanup();    //[1]释放资源}
private:void initWindow() {glfwInit();   //[2]初始化GLFW库//[2]由于glfw原本是用来创建OpenGL Context的,因此需要使用GLFW_NO_API(不进行OpenGL Context创建)glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); //[2]关闭重置窗口//[2]创建窗体window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);}void initVulkan() {//[3]创建Vulkan实例createInstance();//[10]createSurface();//[6]setupDebugMessenger();//[7]pickPhysicalDevice();//[8]createLogicalDevice();//[12]createSwapChain();//[13]createImageViews();}void mainLoop(){//[2]窗口关门,终止渲染while (!glfwWindowShouldClose(window)) {glfwPollEvents();}}void cleanup() {//[13]for (auto imageView : swapChainImageViews) {vkDestroyImageView(device, imageView, nullptr);}vkDestroySwapchainKHR(device, swapChain, nullptr);//[6]销毁代理函数if (enableValidationLayers) {DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);}//[9]销毁逻辑驱动vkDestroyDevice(device, nullptr);//[10]销毁vkDestroySurfaceKHR(instance, surface, nullptr);//[3]销毁实例vkDestroyInstance(instance, nullptr);//[2]释放资源glfwDestroyWindow(window);glfwTerminate();}void createInstance() {//[4]创建实例时检测是否启用验证层if (enableValidationLayers && !checkValidationLayerSupport()) {throw std::runtime_error("validation layers requested, but not available!");}//[3]well-known graphics engine VkApplicationInfo appInfo = {};//[3]结构体必须指明类型,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;//[3]Vulkan驱动程序使用哪些全局扩展和验证,后续后详细说明 VkInstanceCreateInfo createInfo = {};createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;createInfo.pApplicationInfo = &appInfo;//[3]指定全局扩展uint32_t glfwExtensionCount = 0;const char** glfwExtensions;glfwExtensions =glfwGetRequiredInstanceExtensions(&glfwExtensionCount);createInfo.enabledExtensionCount = glfwExtensionCount;createInfo.ppEnabledExtensionNames = glfwExtensions;//[3]the global validation layers to enablecreateInfo.enabledLayerCount = 0; //后续有说明//[5]验证层信息//[5]如果检查成功,那么vkCreateInstance不会返回VK_ERROR_LAYER_NOT_PRESENT错误if (enableValidationLayers) {createInfo.enabledLayerCount =static_cast<uint32_t>(validationLayers.size());createInfo.ppEnabledLayerNames = validationLayers.data();}else {createInfo.enabledLayerCount = 0;}//[5]GLFWauto extensions = getRequiredExtensions();createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());createInfo.ppEnabledExtensionNames = extensions.data();//[6]重用//[6]通过该方式创建一个额外的调试信息,它将在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;}//[6]or/*if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {throw std::runtime_error("failed to set up debug messenger!");}*///[3]   VK_SUCCESS or Error Code//[3]VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);//[3]or//[3]创建实例if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS){throw std::runtime_error("failed to create instance!");/** //[4]验证层说明,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);}*/}//[3]the number of extensions//[3]支持扩展的数量uint32_t extensionCount = 0;vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);//[3]an array of VkExtensionProperties to store details of the extensions.//[3]an array to hold the extension details//[3]支持的扩展详细信息std::vector<VkExtensionProperties> extensionsProperties(extensionCount);vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensionsProperties.data());//[3]query the extension details//[3]Each VkExtensionProperties struct contains the name and version of an extension.//[3]查询扩展的详细信息std::cout << "available extensions:" << std::endl;for (const auto& extension : extensionsProperties) {std::cout << "\t" << extension.extensionName << std::endl;}}//[4]list all of the available layers//[4]列出所有验证层的信息bool checkValidationLayerSupport() {uint32_t layerCount;vkEnumerateInstanceLayerProperties(&layerCount, nullptr);std::vector<VkLayerProperties> availableLayers(layerCount);vkEnumerateInstanceLayerProperties(&layerCount,availableLayers.data());//[4]查询是否存在验证层信息 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;}//[5]we have to set up a debug messenger with a callback using the VK_EXT_debug_utils extension.//[5]我们必须使用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) {//[5]在这里使用VK_EXT_DEBUG_UTILS_EXTENSION_NAME宏,它等于字符串“VK_EXT_debug_utils”。//[5]使用此宏可以避免输入错误extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);}return extensions;}//[5]Add a new static member function called debugCallback with //[5]   the PFN_vkDebugUtilsMessengerCallbackEXT prototype.//[5]使用PFN_vkDebugUtilsMessengerCallbackEXT属性添加一个静态函数//[5]The VKAPI_ATTR and VKAPI_CALL ensure that the function has the//[5]    right signature for Vulkan to call it.//[5]使用VKAPI_ATTR和VKAPI_CALL 确保函数具有正确的签名,以便Vulkan调用它static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(//[5]VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT 诊断信息//[5]VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT 信息性消息,如资源的创建//[5]关于行为的消息,其不一定是错误,但很可能是应用程序中的BUG//[5]VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT//[5]关于无效且可能导致崩溃的行为的消息//[5]VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT//[5]可以使用比较操作来检查消息是否与某个严重性级别相等或更差,例如://[5]if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {//[5] // Message is important enough to show//[5]}VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,//[6]VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT 发生了一些与规范或性能无关的事件//[6]VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT 发生了违反规范或一些可能显示的错误//[6]VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT 非最优的方式使用VulkanVkDebugUtilsMessageTypeFlagsEXT messageType,//[6]消息本身的详细信息, 包括其重要成员://[6]pMessage 以null结尾的调试消息字符串//[6]pObjects 与消息相关的Vulkan对象句柄数组//[6]objectCount 数组中的对象数//[6]pUserData 包含回调指定的指针,允许将自己设置的数据传递给它。const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,void* pUserData) {std::cerr << "validation layer: " << pCallbackData->pMessage <<std::endl;return VK_FALSE;}void setupDebugMessenger() {if (!enableValidationLayers) return;VkDebugUtilsMessengerCreateInfoEXT createInfo = {};populateDebugMessengerCreateInfo(createInfo);//[6] messenger创建信息的填充提取到单独的函数中if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr,&debugMessenger) != VK_SUCCESS) {throw std::runtime_error("failed to set up debug messenger!");}//[6]or//  createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;//[6]指定希望调用回调严重性类型//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;//[6]滤回调通知的消息类型//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;//[6]指定指向回调函数的指针//createInfo.pfnUserCallback = debugCallback;//[6]返回的回调函数//createInfo.pUserData = nullptr; }//[6]创建代理函数VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, constVkDebugUtilsMessengerCreateInfoEXT * pCreateInfo, constVkAllocationCallbacks * pAllocator, VkDebugUtilsMessengerEXT *pDebugMessenger) {auto func = (PFN_vkCreateDebugUtilsMessengerEXT)//[6]如果无法加载,函数将返回nullptr。vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");if (func != nullptr) {return func(instance, pCreateInfo, pAllocator, pDebugMessenger);}else {return VK_ERROR_EXTENSION_NOT_PRESENT;}}//[6]创建代理函数 销毁CreateDebugUtilsMessengerEXTvoid DestroyDebugUtilsMessengerEXT(VkInstance instance,VkDebugUtilsMessengerEXT debugMessenger, constVkAllocationCallbacks* pAllocator) {auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");if (func != nullptr) {func(instance, debugMessenger, pAllocator);}}//[6]仔细阅读扩展文档,就会发现有一种方法可以专门为这两个函数调用创建单独的debug utils messenger。//[6]它要求您只需在VkInstanceCreateInfo的pNext扩展字段中//[6]传递一个指向VkDebugUtilsMessengerCreateInfoEXT结构的指针。//[6]首先将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;}void pickPhysicalDevice() {//[7]查询GPU数量uint32_t deviceCount = 0;vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);if (deviceCount == 0) {throw std::runtime_error("failed to find GPUs with Vulkan support!");}//[7]获取驱动信息std::vector<VkPhysicalDevice> devices(deviceCount);vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());//[7]选择适合该程序的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//[7]使用有序Map,通过分数自动对显卡排序std::multimap<int, VkPhysicalDevice> candidates;for (const auto& device : devices) {int score = rateDeviceSuitability(device);candidates.insert(std::make_pair(score, device));}// 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!");    }}//[7]GPU是否适合该程序的bool isDeviceSuitable(VkPhysicalDevice device) {//[7]查询显卡属性,包括:名称,支持Vulkan的版本号//VkPhysicalDeviceProperties deviceProperties;//vkGetPhysicalDeviceProperties(device, &deviceProperties);//[11]扩展支持bool extensionsSupported = checkDeviceExtensionSupport(device);//[12]swap chain supportbool swapChainAdequate = false;if (extensionsSupported) {SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();}//[7]查询显卡特性,包括:纹理压缩,64位浮点述。多视口渲染(VR)//VkPhysicalDeviceFeatures deviceFeatures;//vkGetPhysicalDeviceFeatures(device, &deviceFeatures);//[7]是否为专业显卡(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;}int rateDeviceSuitability(VkPhysicalDevice device) {//[7]查询显卡属性,包括:名称,支持Vulkan的版本号VkPhysicalDeviceProperties deviceProperties;vkGetPhysicalDeviceProperties(device, &deviceProperties);int score = 0;//离散GPU具有显著的性能优势if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {score += 1000;}//支持纹理的最大值,影响图形质量score += deviceProperties.limits.maxImageDimension2D;//[7]查询显卡特性,包括:纹理压缩,64位浮点述。多视口渲染(VR)VkPhysicalDeviceFeatures deviceFeatures;vkGetPhysicalDeviceFeatures(device, &deviceFeatures);// 不支持几何着色器if (!deviceFeatures.geometryShader) {return 0;}return score;}//[8]QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {//[8]Logic to find graphics queue familyQueueFamilyIndices indices;//[8]Logic to find queue family indices to populate struct with//[8]C++ 17引入了optional数据结构来区分存在或不存在的值的情况。//[8]std::optional<uint32_t> graphicsFamily;//[8]std::cout << std::boolalpha << graphicsFamily.has_value() <<std::endl; // false//[8]graphicsFamily = 0;//[8]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) {//[10]寻找一个队列族,它能够链接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;}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;//[9]Vulkan使用0.0到1.0之间的浮点数为队列分配优先级, 来进行缓冲区执行的调度。即使只有一个队列,这也是必需的:float queuePriority = 1.0f;queueCreateInfo.pQueuePriorities = &queuePriority;//[9]device featuresVkPhysicalDeviceFeatures deviceFeatures = {};VkDeviceCreateInfo createInfo = {};createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;createInfo.pQueueCreateInfos = &queueCreateInfo;createInfo.queueCreateInfoCount = 1;createInfo.pEnabledFeatures = &deviceFeatures;//[9]VK_KHR_swapchain 将该设备的渲染图像显示到windows//[9]之前版本Vulkan实现对实例和设备特定的验证层进行了区分,但现在情况不再如此。//[9]这意味着VkDeviceCreateInfo的enabledLayerCount和ppEnabledLayerNames字段被最新的实现忽略。不过,还是应该将它们设置为与较旧的实现兼容:createInfo.enabledExtensionCount = 0;if (enableValidationLayers) {createInfo.enabledLayerCount =  static_cast<uint32_t>(validationLayers.size());createInfo.ppEnabledLayerNames = validationLayers.data();}else {createInfo.enabledLayerCount = 0;}//[10]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);}//[10]将队列信息加入驱动infocreateInfo.queueCreateInfoCount =static_cast<uint32_t>(queueCreateInfos.size());createInfo.pQueueCreateInfos = queueCreateInfos.data();//[11]开启扩展支持createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());createInfo.ppEnabledExtensionNames = deviceExtensions.data();//[9]创建驱动if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {throw std::runtime_error("failed to create logical device!");}//[9]获取驱动队列//[9]因为我们只从这个族中创建一个队列,所以我们只使用索引0vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);//[10]显示队列vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);}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!");}}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();}//[12]SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {SwapChainSupportDetails details;//[12]basic surface capabilities 基本性能vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);//[12]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());}//[12]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;}//[12]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];}//[12]VK_PRESENT_MODE_IMMEDIATE_KHR//[12]VK_PRESENT_MODE_FIFO_KHR//[12]VK_PRESENT_MODE_FIFO_RELAXED_KHR//[12]VK_PRESENT_MODE_MAILBOX_KHRVkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>&availablePresentModes) {//[12]三级缓存更好,如果有就开启for (const auto& availablePresentMode : availablePresentModes) {if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {return availablePresentMode;}}return VK_PRESENT_MODE_FIFO_KHR;}//[12]在minImageExtent和maxImageExtent内选择与窗口最匹配的分辨率VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {if (capabilities.currentExtent.width != UINT32_MAX) {return capabilities.currentExtent;}else {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;}}void createSwapChain() {SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice);VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);//[12]然而,简单地坚持这个最小值意味着我们有时可能需要等待驱动程序完成内部操作,//[12]然后才能获取另一个要渲染的图像。因此,建议请求至少比最小值多一个图像://[12]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;//[12]imageArrayLayers指定每个图像包含的层的数量。除非您正在开发3D应用程序,否则该值始终为1。createInfo.imageArrayLayers = 1;createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;//[12]两种方法可以处理从多个队列访问的图像://[12]VK_SHARING_MODE_CONCURRENT//[12]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}//[12]指定对交换链中的图像应用某种变换createInfo.preTransform = swapChainSupport.capabilities.currentTransform;//[12]alpha channel should be used for blendingcreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;createInfo.presentMode = presentMode;createInfo.clipped = VK_TRUE;//[12]窗口重置是取缓存区图像方式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;}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];//[13]选择视图类型 1D 2D 3DcreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;createInfo.format = swapChainImageFormat;//[13]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;//[13]描述图像的用途以及应访问图像的哪个部分。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!");}}}
private:GLFWwindow* window = nullptr;VkInstance instance = nullptr;VkDebugUtilsMessengerEXT debugMessenger;//[7]当vkinInstance被销毁时,该对象将被隐式销毁,因此不需要在cleanup函数中执行销毁。VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;VkDevice device;//[9]当设备被销毁时,设备队列会被隐式清理,因此我们不需要在清理中执行任何操作。VkQueue graphicsQueue;//[10]Window surface creationVkSurfaceKHR surface;//[11]连接队列VkQueue presentQueue;VkSwapchainKHR swapChain;//[12]don’t need to add any cleanup code.std::vector<VkImage> swapChainImages;//[12]store the format and extent we’ve chosen for the swap chain images in member variablesVkFormat swapChainImageFormat;VkExtent2D swapChainExtent;//[13]std::vector<VkImageView> swapChainImageViews;
};int main() {HelloTriangleApplication app;//[1]捕获异常try {app.run();}catch (const std::exception& e) {std::cerr << e.what() << std::endl;return EXIT_FAILURE;}return EXIT_SUCCESS;
}

Vulkan学习(三):小结相关推荐

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

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

  2. 集成学习原理小结(转载)

    集成学习(ensemble learning)可以说是现在非常火爆的机器学习方法了.它本身不是一个单独的机器学习算法,而是通过构建并结合多个机器学习器来完成学习任务.也就是我们常说的"博采众 ...

  3. OpenGL入门学习[三]

    OpenGL入门学习[三] http://xiaxveliang.blog.163.com/blog/static/2970803420126246501930/ OpenGL入门学习[十一] 我们在 ...

  4. vulkan学习_使用vulkan kompute在gpu中进行机器学习和数据处理

    vulkan学习 Machine learning, together with many other advanced data processing paradigms, fits incredi ...

  5. 统计学习三要素 模型+策略+算法

    统计学习方法都是由模型. 策略和算法构成的. 即统计学习方法由三要素构成, 可以简单地表示为:方法=模型+策略+算法 模型 统计学习首要考虑的问题是学习什么样的模型. 在监督学习过程中, 模型就是所要 ...

  6. 深度学习三巨头也成了大眼萌,这个一键转换动画电影形象的网站竟因「太火」而下线...

    机器之心报道 作者:魔王.杜伟 想不想在动画电影中拥有自己的角色?这个网站一键满足你的需求,不过竟因流量太大成本过高而下线. 近期热映的电影<花木兰>总是让人回想起 1998 年上映的同名 ...

  7. 2020届 AAAI Fellow名单新鲜出炉!!!深度学习三巨头终于齐聚

    点击上方"深度学习技术前沿",选择"星标"公众号 资源干货,第一时间送达 AAAI 是国际人工智能领域最权威的学术组织,Fellow 是该学会给予会员的最高荣誉 ...

  8. HTTP学习三:HTTPS

    HTTP学习三:HTTPS 1 HTTP安全问题 HTTP1.0/1.1在网络中是明文传输的,因此会被黑客进行攻击. 1.1 窃取数据 因为HTTP1.0/1.1是明文的,黑客很容易获得用户的重要数据 ...

  9. python爬虫正则表达式实例-python爬虫学习三:python正则表达式

    python爬虫学习三:python正则表达式 1.正则表达式基础 a.正则表达式的大致匹配过程: 1.依次拿出表达式和文本中的字符比较 2.如果每一个字符都能匹配,则匹配成功:一旦有匹配不成功的字符 ...

  10. TweenMax动画库学习(三)

    目录               TweenMax动画库学习(一)            TweenMax动画库学习(二)            TweenMax动画库学习(三)           ...

最新文章

  1. 成功解决PackagesNotFoundError: The following packages are not available from current channels: tensorflo
  2. 文件传输服务器多目录,node ftp 模块 如何把本地多个文件夹或者文件上传到服务器...
  3. 快乐学算法之:字典树Trie
  4. 细节决定成败—关于.net的.dll.refresh文件
  5. 【ZOJ - 2972】Hurdles of 110m (dp)
  6. esxi6.0开启网络UI管理界面
  7. 【异常】Unable to instantiate SparkSession with Hive support because Hive classes a
  8. 刺客信条起源计算机内存不足,刺客信条起源需要什么配置能玩?最低/推荐配置需求介绍...
  9. android ram压力测试,android用memtester内存压力测试
  10. 解决同一办公环境局域网下无法添加打印机的情况
  11. linux内核的reciprocal_value结构体
  12. idea xml高亮问题
  13. 尚硅谷Web前端ES6教程,涵盖ES6-ES11
  14. python从字符串中提取数字
  15. Complete Internet Repair(电脑网络修复工具)官方中文版V6.0.3.5003 | 富有成效的电脑网络修复大师 | 电脑网络修复怎么修复?
  16. citrixreceiver云桌面系统_Citrix Receiver(虚拟桌面软件)V5.0 正式版
  17. enfuzion与lsf构建渲染集群_集群渲染系统构建及优化
  18. shell执行curl_Linux curl命令详解
  19. 语句摘抄——第16周
  20. RabbitMQ系列【3】安装RabbitMQ

热门文章

  1. CodeForces - 767C Garland(附带易错数据)
  2. linux命令 trtest,Linux tr命令的使用方法
  3. 沈阳市中考计算机考试时间,2021辽宁沈阳中考考试时间、科目分值及时间轴
  4. win32 窗口 绘制矩形
  5. Google-APAC2015-Password Attacker
  6. 期货与期权套期保值的对比研究
  7. 创建Web站点的欢迎页面
  8. dat图片 电脑端微信_写了一个电脑版微信的dat图片转换器
  9. J2EE进阶之tomcat服务器搭建,HTTP协议 八
  10. java深克隆 浅克隆_通过Java中深克隆与浅克隆来理解克隆