[Vulkan 学习之路] 10 - 掌握 SPIR-V:编写你的第一个着色器 (Shader Modules)

欢迎来到第十篇!两位数里程碑!

在 OpenGL 时代,我们习惯了在 C++ 代码里写一串 GLSL 字符串,然后在运行时交给驱动去编译。这种做法虽然方便,但有几个大问题:

  1. 各家驱动编译结果不一致:N卡能跑的 Shader,A卡可能就报错。

  2. 启动慢:每次运行程序都要重新编译 Shader,浪费时间。

Vulkan 为了解决这些问题,引入了强制标准:SPIR-V

什么是 SPIR-V?

SPIR-V 是一种中间语言(类似于 Java 的字节码或 .NET 的 MSIL)。它是一种二进制格式,显卡驱动一定能看懂它。

我们的工作流程变成了这样:

  1. 用人类语言(GLSL 或 HLSL)编写着色器。

  2. 在开发阶段,用编译器把它们编译成.spv文件(SPIR-V 二进制文件)。

  3. 在 C++ 代码中,读取这些.spv文件。

  4. 把二进制数据打包成 Vulkan 的VkShaderModule对象,喂给管线。

这确保了无论在什么显卡上,你的 Shader 行为都是一致的,而且加载速度极快。

编写 GLSL 着色器

为了画出那个三角形,我们需要两个最基础的着色器。

请在你的 Visual Studio 项目文件夹中(和main.cpp在一起),新建两个文本文件:shader.vertshader.frag

顶点着色器 (shader.vert)

顶点着色器处理每个传入的顶点。它有它的属性,比如 模型空间位置,颜色,法线和纹理坐标作为输入。输出为 剪辑坐标中的最终位置和需要传递的属性 在片段着色器上,像颜色和纹理坐标。这些值将 然后通过光栅化器在碎片上进行插值,以产生平滑梯度。它的任务是告诉显卡三角形的三个顶点在哪。

对于第一个三角形我们不会应用任何变换,我们只是 将三个顶点的位置直接指定为规范化设备 创建以下形状的坐标:

通常情况下,我们会通过“顶点缓冲区”把位置传进来。但为了让第一个教程尽可能简单,Vulkan 教程采用了一种“作弊”的方法:直接把坐标硬编码在 Shader 里

#version 450 // 使用 GLSL 4.50 版本 // 输出变量:传递给下一个阶段(片元着色器)的颜色 // layout(location = 0) 指定了它在输出接口中的索引槽位 layout(location = 0) out vec3 fragColor; // 硬编码的三角形顶点坐标数组 (2D坐标,中心是0,0) 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_VertexIndex 是一个内置变量,告诉我们当前处理的是第几个顶点 // 输出位置:Vulkan 只需要我们将最终坐标赋值给内置变量 gl_Position // 我们把 2D 坐标补全为 4D 坐标 (x, y, z, w),z=0表示在屏幕平面上,w=1是标准做法 gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); // 输出颜色:把对应顶点的颜色传给后面 fragColor = colors[gl_VertexIndex]; }

片元着色器 (shader.frag)

它的任务是计算三角形内部每个像素的颜色。光栅化器会自动插值顶点颜色,我们只需要把插值后的结果输出即可。

#version 450 // 输入变量:从顶点着色器传过来的颜色 // 必须和顶点着色器的输出变量类型一致,且 location 对应 layout(location = 0) in vec3 fragColor; // 输出变量:最终显示在屏幕上的颜色(RGBA) // location = 0 对应我们 SwapChain 里的第0个颜色附件 layout(location = 0) out vec4 outColor; void main() { // 直接把接收到的 RGB 颜色输出,并加上 Alpha=1.0 (不透明) outColor = vec4(fragColor, 1.0); }

编译为 SPIR-V (Windows 之道)

现在我们需要把这俩文件编译成显卡认识的.spv文件。

Vulkan SDK 自带了一个编译器,名叫glslc.exe。它通常位于C:\VulkanSDK\<你的版本号>\Bin\目录下。

为了方便,我们在项目目录下创建一个批处理文件compile.bat(Windows用户专属福利):

@echo off :: 请将下面的路径替换为你实际安装的 Vulkan SDK 路径 set GLSLC="C:/VulkanSDK/1.x.xx.x/Bin/glslc.exe" echo Compiling vertex shader... %GLSLC% shader.vert -o vert.spv if %errorlevel% neq 0 pause & exit /b %errorlevel% echo Compiling fragment shader... %GLSLC% shader.frag -o frag.spv if %errorlevel% neq 0 pause & exit /b %errorlevel% echo Compilation successful! pause

双击运行这个compile.bat。如果一切顺利,你的目录下会多出vert.spvfrag.spv两个文件。这就是我们要的二进制原料。

注意:以后每次修改了 GLSL 文件,记得都要重新运行一下这个脚本!


回到 C++:加载二进制文件

现在我们需要在 C++ 里读取这俩文件。这是标准的文件 I/O 操作,我们写一个辅助函数来完成:

请在HelloVulkanApp类定义之前添加这个通用函数,并引入必要的头文件:

#include <fstream> #include <vector> // ... 其他 include ... // 辅助函数:读取整个二进制文件到一个 vector<char> 中 static std::vector<char> readFile(const std::string& filename) { // ate: 打开时定位到文件末尾 (at the end),方便获取文件大小 // binary: 以二进制方式读取 std::ifstream file(filename, std::ios::ate | std::ios::binary); if (!file.is_open()) { throw std::runtime_error("failed to open file: " + filename); } // 利用 ate 获取文件大小 size_t fileSize = (size_t)file.tellg(); std::vector<char> buffer(fileSize); // 回到文件开头,开始读取 file.seekg(0); file.read(buffer.data(), fileSize); file.close(); return buffer; } // HelloVulkanApp 类定义开始...

创建 Shader Modules

读取到二进制数据后,我们需要把它包装成 Vulkan 的对象:VkShaderModule

HelloVulkanAppprivate区域添加一个辅助函数:

VkShaderModule createShaderModule(const std::vector<char>& code) { VkShaderModuleCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); // 关键点:Vulkan 需要 uint32_t* 类型的指针,但我们读出来的是 char*。 // 我们需要用 reinterpret_cast 进行强制转换。 // 只要我们的 vector 数据是按照默认对齐的(std::vector通常满足),这样做是安全的。 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; }

临时整合 (先睹为快)

注意:Shader Modules 是“用完即弃”的。

我们只需要在创建图形管线的那一刻用到它们。一旦管线创建完成,这些 Shader Module 就可以(而且应该)立即销毁以释放内存。

因此,我们不会VkShaderModule存储为类的成员变量。我们将在这个漫长的管线创建函数中局部地创建和销毁它们。

现在,我们先创建一个空的createGraphicsPipeline函数,并在initVulkan中调用它,看看能不能成功加载文件。

void initVulkan() { // ... 之前的步骤 ... createImageViews(); createGraphicsPipeline(); // <--- 新增 } // ... 在类中添加这个函数 ... void createGraphicsPipeline() { auto vertShaderCode = readFile("vert.spv"); auto fragShaderCode = readFile("frag.spv"); VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); std::cout << "成功加载 Shader! Vertex大小: " << vertShaderCode.size() << ", Fragment大小: " << fragShaderCode.size() << std::endl; // 管线创建完成后,必须销毁它们 // (当然,目前我们还没创建管线,但先写上清理代码是个好习惯) vkDestroyShaderModule(device, fragShaderModule, nullptr); vkDestroyShaderModule(device, vertShaderModule, nullptr); }

完整代码:

#define GLFW_INCLUDE_VULKAN #include <GLFW/glfw3.h> #include <iostream> #include <fstream> #include <stdexcept> #include <algorithm> #include <vector> #include <cstring> #include <cstdlib> #include <cstdint> #include <limits> #include <optional> #include <set> const uint32_t WIDTH = 800; const uint32_t HEIGHT = 600; const std::vector<const char*> validationLayers = { "VK_LAYER_KHRONOS_validation" }; const std::vector<const char*> deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; #ifdef NDEBUG const bool enableValidationLayers = false; #else const bool enableValidationLayers = true; #endif VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { func(instance, debugMessenger, pAllocator); } } struct QueueFamilyIndices { std::optional<uint32_t> graphicsFamily; std::optional<uint32_t> presentFamily; 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(); initVulkan(); mainLoop(); cleanup(); } private: GLFWwindow* window; VkInstance instance; VkDebugUtilsMessengerEXT debugMessenger; VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; VkSwapchainKHR swapChain; std::vector<VkImage> swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; std::vector<VkImageView> swapChainImageViews; void initWindow() { glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); } void initVulkan() { createInstance(); setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); createSwapChain(); createImageViews(); createGraphicsPipeline(); } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } } void cleanup() { for (auto imageView : swapChainImageViews) { vkDestroyImageView(device, imageView, nullptr); } vkDestroySwapchainKHR(device, swapChain, nullptr); vkDestroyDevice(device, nullptr); if (enableValidationLayers) { DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); } vkDestroySurfaceKHR(instance, surface, nullptr); vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); glfwTerminate(); } void createInstance() { if (enableValidationLayers && !checkValidationLayerSupport()) { throw std::runtime_error("validation layers requested, but not available!"); } VkApplicationInfo appInfo{}; 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; VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); 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; } if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } 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 setupDebugMessenger() { if (!enableValidationLayers) return; VkDebugUtilsMessengerCreateInfoEXT createInfo; populateDebugMessengerCreateInfo(createInfo); if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug messenger!"); } } void createSurface() { if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } void pickPhysicalDevice() { uint32_t deviceCount = 0; vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); if (deviceCount == 0) { throw std::runtime_error("failed to find GPUs with Vulkan support!"); } std::vector<VkPhysicalDevice> devices(deviceCount); vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); for (const auto& device : devices) { if (isDeviceSuitable(device)) { physicalDevice = device; break; } } if (physicalDevice == VK_NULL_HANDLE) { throw std::runtime_error("failed to find a suitable GPU!"); } } void createLogicalDevice() { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector<VkDeviceQueueCreateInfo> queueCreateInfos; std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; for (uint32_t queueFamily : uniqueQueueFamilies) { VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; queueCreateInfo.pQueuePriorities = &queuePriority; queueCreateInfos.push_back(queueCreateInfo); } VkPhysicalDeviceFeatures deviceFeatures{}; VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); createInfo.pEnabledFeatures = &deviceFeatures; createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createSwapChain() { SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); 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; createInfo.imageArrayLayers = 1; createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; createInfo.queueFamilyIndexCount = 2; createInfo.pQueueFamilyIndices = queueFamilyIndices; } else { createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; } createInfo.preTransform = swapChainSupport.capabilities.currentTransform; createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; createInfo.presentMode = presentMode; createInfo.clipped = VK_TRUE; 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]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; createInfo.format = swapChainImageFormat; createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; 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!"); } } } void createGraphicsPipeline() { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; vertShaderStageInfo.module = vertShaderModule; vertShaderStageInfo.pName = "main"; VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; fragShaderStageInfo.pName = "main"; VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; vkDestroyShaderModule(device, fragShaderModule, nullptr); vkDestroyShaderModule(device, vertShaderModule, nullptr); } 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; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) { for (const auto& availableFormat : availableFormats) { if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } return availableFormats[0]; } VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) { for (const auto& availablePresentMode : availablePresentModes) { if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { return availablePresentMode; } } return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) { return capabilities.currentExtent; } else { int width, height; glfwGetFramebufferSize(window, &width, &height); VkExtent2D actualExtent = { static_cast<uint32_t>(width), static_cast<uint32_t>(height) }; actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; } } SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { SwapChainSupportDetails details; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); uint32_t formatCount; vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); if (formatCount != 0) { details.formats.resize(formatCount); vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); } uint32_t presentModeCount; vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); if (presentModeCount != 0) { details.presentModes.resize(presentModeCount); vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); } return details; } bool isDeviceSuitable(VkPhysicalDevice device) { QueueFamilyIndices indices = findQueueFamilies(device); bool extensionsSupported = checkDeviceExtensionSupport(device); bool swapChainAdequate = false; if (extensionsSupported) { SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); } return indices.isComplete() && extensionsSupported && swapChainAdequate; } 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(); } QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { QueueFamilyIndices indices; uint32_t queueFamilyCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount); vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); int i = 0; for (const auto& queueFamily : queueFamilies) { if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); if (presentSupport) { indices.presentFamily = i; } if (indices.isComplete()) { break; } i++; } return indices; } std::vector<const char*> getRequiredExtensions() { uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; } bool checkValidationLayerSupport() { uint32_t layerCount; vkEnumerateInstanceLayerProperties(&layerCount, nullptr); std::vector<VkLayerProperties> availableLayers(layerCount); vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); for (const char* layerName : validationLayers) { bool layerFound = false; for (const auto& layerProperties : availableLayers) { if (strcmp(layerName, layerProperties.layerName) == 0) { layerFound = true; break; } } if (!layerFound) { return false; } } return true; } 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!"); } size_t fileSize = (size_t) file.tellg(); std::vector<char> buffer(fileSize); file.seekg(0); file.read(buffer.data(), fileSize); file.close(); return buffer; } static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } }; int main() { HelloTriangleApplication app; try { app.run(); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }

运行测试

确保你已经用compile.bat生成了.spv文件,并且它们和你的.exe文件(或者 Visual Studio 的项目工作目录)在同一个地方。

运行程序。如果控制台输出了文件大小,并且没有报错,恭喜你!你已经成功迈出了控制 GPU 的第一步。

下一步预告

现在手里拿着两个编译好的 Shader Module,我们就像拿着两块乐高积木。接下来的任务,就是配置一大堆其他的积木(光栅化状态、深度模板状态、混合状态等),然后把它们拼在一起,组成一个完整的Graphics Pipeline

下一篇,我们将开始填写那个巨大无比的VkGraphicsPipelineCreateInfo结构体中的第一部分:Shader Stage Creation (着色器阶段创建)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1162164.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Dango-Translator终极指南:三步实现本地化翻译自由

Dango-Translator终极指南&#xff1a;三步实现本地化翻译自由 【免费下载链接】Dango-Translator 团子翻译器 —— 个人兴趣制作的一款基于OCR技术的翻译器 项目地址: https://gitcode.com/GitHub_Trending/da/Dango-Translator 还在为翻译软件的云端依赖而烦恼吗&…

WuWa-Mod模组安装与使用完全指南

WuWa-Mod模组安装与使用完全指南 【免费下载链接】wuwa-mod Wuthering Waves pak mods 项目地址: https://gitcode.com/GitHub_Trending/wu/wuwa-mod 想要彻底改变《鸣潮》游戏体验吗&#xff1f;WuWa-Mod模组为你提供了15种强大的游戏功能增强&#xff0c;从无限体力到…

WuWa-Mod模组完整配置手册:3分钟开启游戏增强之旅

WuWa-Mod模组完整配置手册&#xff1a;3分钟开启游戏增强之旅 【免费下载链接】wuwa-mod Wuthering Waves pak mods 项目地址: https://gitcode.com/GitHub_Trending/wu/wuwa-mod 想要彻底改变游戏体验吗&#xff1f;WuWa-Mod模组为你提供了全面的游戏功能增强方案&…

Open Interpreter部署指南:高可用性配置方案

Open Interpreter部署指南&#xff1a;高可用性配置方案 1. 引言 随着大语言模型&#xff08;LLM&#xff09;在代码生成与自动化任务中的广泛应用&#xff0c;本地化、安全可控的AI编程助手需求日益增长。Open Interpreter 作为一款开源的本地代码解释器框架&#xff0c;凭借…

NarratoAI终极使用指南:5分钟快速上手智能视频解说

NarratoAI终极使用指南&#xff1a;5分钟快速上手智能视频解说 【免费下载链接】NarratoAI 利用AI大模型&#xff0c;一键解说并剪辑视频&#xff1b; Using AI models to automatically provide commentary and edit videos with a single click. 项目地址: https://gitcode…

OpenCode效果展示:代码生成与重构真实案例

OpenCode效果展示&#xff1a;代码生成与重构真实案例 1. 引言&#xff1a;AI编程助手的现实挑战与OpenCode的定位 在现代软件开发中&#xff0c;开发者面临着日益复杂的项目结构、多样化的技术栈以及紧迫的交付周期。传统的编码方式已难以满足高效开发的需求&#xff0c;而A…

Z-Image-Turbo + Python脚本:自动化生成不是梦

Z-Image-Turbo Python脚本&#xff1a;自动化生成不是梦 在AI图像生成领域&#xff0c;高效、稳定且开箱即用的部署方案是提升开发与教学效率的关键。Z-Image-Turbo作为阿里达摩院推出的高性能文生图模型&#xff0c;凭借其基于DiT架构的9步极速推理能力&#xff0c;支持1024…

Engine-Sim 终极入门指南:零基础搭建虚拟发动机实验室

Engine-Sim 终极入门指南&#xff1a;零基础搭建虚拟发动机实验室 【免费下载链接】engine-sim Combustion engine simulator that generates realistic audio. 项目地址: https://gitcode.com/gh_mirrors/en/engine-sim 想要亲身体验V12发动机的澎湃声浪&#xff0c;却…

FST ITN-ZH长文本处理:复杂中文文本标准化解决方案

FST ITN-ZH长文本处理&#xff1a;复杂中文文本标准化解决方案 1. 简介与背景 在自然语言处理&#xff08;NLP&#xff09;的实际应用中&#xff0c;语音识别系统输出的原始文本通常包含大量非标准表达形式。例如&#xff0c;“二零零八年八月八日”或“早上八点半”这类口语…

BongoCat终极指南:三步打造你的专属桌面萌宠

BongoCat终极指南&#xff1a;三步打造你的专属桌面萌宠 【免费下载链接】BongoCat 让呆萌可爱的 Bongo Cat 陪伴你的键盘敲击与鼠标操作&#xff0c;每一次输入都充满趣味与活力&#xff01; 项目地址: https://gitcode.com/gh_mirrors/bong/BongoCat 还在为枯燥的电脑…

Whisper语音识别案例:语音博客内容索引

Whisper语音识别案例&#xff1a;语音博客内容索引 1. 引言 随着多语言内容创作的快速增长&#xff0c;如何高效地对音频内容进行索引、检索和再利用成为技术团队面临的重要挑战。传统的语音识别方案往往受限于语言支持范围、准确率和部署复杂度&#xff0c;难以满足全球化内…

南京信息工程大学LaTeX论文模板:从格式焦虑到排版自由的蜕变之路 [特殊字符]

南京信息工程大学LaTeX论文模板&#xff1a;从格式焦虑到排版自由的蜕变之路 &#x1f393; 【免费下载链接】NUIST_Bachelor_Thesis_LaTeX_Template 南京信息工程大学本科生毕业论文 LaTeX 模板 项目地址: https://gitcode.com/gh_mirrors/nu/NUIST_Bachelor_Thesis_LaTeX_T…

RS485测试从零实现:基于STM32的简易通信程序

从零构建RS485通信测试系统&#xff1a;STM32实战全解析在工业现场&#xff0c;你是否遇到过这样的场景&#xff1f;设备明明通电了&#xff0c;但PLC读不到传感器数据&#xff1b;调试串口助手时&#xff0c;收到的总是乱码或空包&#xff1b;换了一根线就好了——可下次又出问…

DeepSeek-R1-Distill-Qwen-1.5B工业应用:设备故障诊断系统搭建

DeepSeek-R1-Distill-Qwen-1.5B工业应用&#xff1a;设备故障诊断系统搭建 1. 引言 1.1 工业场景中的智能诊断需求 在现代制造业与重工业领域&#xff0c;设备运行的稳定性直接关系到生产效率、安全性和维护成本。传统的设备故障诊断依赖人工经验或基于规则的专家系统&#…

浏览器下载管理器终极指南:3步掌握高效下载管理技巧

浏览器下载管理器终极指南&#xff1a;3步掌握高效下载管理技巧 【免费下载链接】download-manager 谷歌浏览器下载管理器插件【A chrome extension for managing download】 项目地址: https://gitcode.com/gh_mirrors/dow/download-manager 还在为浏览器下载列表杂乱无…

Realtek RTL8125 2.5GbE网卡驱动完全安装指南

Realtek RTL8125 2.5GbE网卡驱动完全安装指南 【免费下载链接】realtek-r8125-dkms A DKMS package for easy use of Realtek r8125 driver, which supports 2.5 GbE. 项目地址: https://gitcode.com/gh_mirrors/re/realtek-r8125-dkms 还在为Linux系统无法识别2.5GbE高…

Keil5汉化系统学习:新手入门全流程

Keil5汉化实战指南&#xff1a;从零开始&#xff0c;轻松搞定中文界面 你是不是刚打开Keil5&#xff0c;面对满屏英文菜单一头雾水&#xff1f; “Project”、“Target”、“Download”这些词看着眼熟&#xff0c;但点进去却不知道哪个是新建工程、哪个是下载程序&#xff1f…

多场景适配:Image-to-Video参数预设模板分享

多场景适配&#xff1a;Image-to-Video参数预设模板分享 1. 简介与背景 随着生成式AI技术的快速发展&#xff0c;图像到视频&#xff08;Image-to-Video, I2V&#xff09;转换已成为内容创作、影视制作和交互设计中的关键工具。基于I2VGen-XL等先进扩散模型构建的Image-to-Vi…

开箱即用!BGE-M3镜像让文本检索部署零门槛

开箱即用&#xff01;BGE-M3镜像让文本检索部署零门槛 1. 引言&#xff1a;为什么需要BGE-M3&#xff1f; 在现代信息检索系统中&#xff0c;如何高效、准确地从海量文本中找到最相关的内容&#xff0c;是搜索、推荐、问答等应用的核心挑战。传统方法往往依赖单一的检索模式—…

OpenArk深度揭秘:Windows系统安全检测与防护实战指南

OpenArk深度揭秘&#xff1a;Windows系统安全检测与防护实战指南 【免费下载链接】OpenArk The Next Generation of Anti-Rookit(ARK) tool for Windows. 项目地址: https://gitcode.com/GitHub_Trending/op/OpenArk 你的Windows系统真的安全吗&#xff1f;&#x1f50d…