《Vulkan Tutorial》2. Drawing A Triangle
校验层(Validation Layer)
Vulkan API 的设计理念是将驱动程序开销降到最低,所以默认情况下 API 中的错误检查非常有限
由于 Vulkan 要求非常明确地说明您正在做的每件事,因此很容易犯很多小错误,例如使用新的 GPU 功能并忘记在逻辑设备创建时请求它
Vulkan 为此引入了验证层,验证层是可选组件,可挂接到 Vulkan 函数调用中以应用其他操作,其中的常见操作有:
- 根据标准来检查参数
- 跟踪对象的创建和销毁以查找资源泄漏
- 通过跟踪调用来源的线程来检查线程安全性
- 将每个调用及其参数记录到标准输出
- 跟踪 Vulkan 调用以进行分析和重放
Vulkan 中以前有两种不同类型的验证层:实例校验层和设备校验层
- 实例校验层只检查和全局 Vulkan 对象相关的调用
- 设备校验层只检查和特定 GPU 相关的调用,现在已经不推荐使用
LunarG 的 Vulkan SDK 允许我们通过 VK_LAYER_KHRONOS_validation
来隐式地开启所有可用的校验层
消息回调(Message Callback)
仅仅启用校验层并没有任何用处,我们不能得到任何有用的调试信息
为了获得调试信息,我们需要使用 VK_EXT_debug_utils
扩展,设置回调函数来接受调试信息
可以设置 vk::InstanceCreateInfo
的 pNext
为 vk::DebugUtilsMessengerCreateInfoEXT
指针,这样就能调试 instance
的创建和销毁了
图形管线基础
图形管线是一系列将我们提交的顶点和纹理转换为渲染目标上的像素的操作。它的简化过程如下:
- input assembler 获取顶点数据
- vertex shader 对每个顶点进行模型空间到屏幕空间的变换
- tessellation shader 根据一定的规则对几何图形进行细分,从而提高网格质量
- geometry shader 可以以图元为单位处理几何图形,它可以剔除图元、输出图元,与 tessellation shader 相似,但现在已经不推荐使用
- rasterization 阶段将图元分成片元,这一阶段会执行深度测试
- fragment shader 对每一个未丢弃的片元进行处理,确定哪些片元写入帧缓冲
- color blending 阶段对写入帧缓冲的同一像素位置的不同片段进行混合
绿色标识的是固定功能阶段,固定功能阶段允许通过参数对处理过程进行一定程度的配置
橙色标识的是可编程阶段,允许将自己的代码加载到显卡
着色器模块
Vulkan 使用的着色器代码格式是一种叫做 SPIR-V 的字节码
Khronos 发布了一个独立于厂商的可以将 GLSL 代码转换为 SPIR-V 字节码的编译器,可以在程序运行时调用它,动态生成 SPIR-V 字节码
vertex shader 对每个顶点进行处理,输出包括顶点最终的裁剪坐标(就是标准视空间)
代码
#include <vulkan/vulkan.hpp>
#include <GLFW/glfw3.h>
#include <iostream>
#include <set>
class VulkanError final : public std::runtime_error {
public:
explicit VulkanError(const vk::Result& result) : runtime_error(to_string(result).c_str()) {
}
};
class HelloTriangleApplication {
public:
void run() {
initWindow();
initVulkan();
mainLoop();
cleanup();
}
private:
const int WIDTH = 800;
const int HEIGHT = 600;
GLFWwindow* window = nullptr;
vk::Instance instance;
vk::detail::DispatchLoaderDynamic dispatch;
vk::PhysicalDevice physicalDevice;
vk::Device device;
vk::DebugUtilsMessengerEXT messenger;
const std::vector<const char*> validationLayers = {
"VK_LAYER_KHRONOS_validation"
};
#ifdef NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif
static void checkResult(const vk::Result& result) {
if (result != vk::Result::eSuccess) {
throw VulkanError(result);
}
}
void initWindow() {
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
}
static void checkAllExtensionsSupport(const std::vector<const char*>& extensions) {
uint32_t extensionCount;
checkResult(vk::enumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr));
std::vector<vk::ExtensionProperties> availableExtensions(extensionCount);
checkResult(vk::enumerateInstanceExtensionProperties(nullptr, &extensionCount, availableExtensions.data()));
std::set<std::string> availableExtensionsSet;
for (const auto& extension : availableExtensions) {
availableExtensionsSet.insert(extension.extensionName);
}
for (const char* extension : extensions) {
if (!availableExtensionsSet.contains(extension)) {
throw std::runtime_error("Required extension not supported!");
}
}
}
std::vector<const char*> getRequiredExtensions() const {
uint32_t glfwExtensionCount;
const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
if (enableValidationLayers) {
extensions.push_back(vk::EXTDebugUtilsExtensionName);
}
return extensions;
}
void checkValidationLayerSupport() const {
uint32_t layerCount;
checkResult(vk::enumerateInstanceLayerProperties(&layerCount, nullptr));
std::vector<vk::LayerProperties> availableLayers(layerCount);
checkResult(vk::enumerateInstanceLayerProperties(&layerCount, availableLayers.data()));
std::set<std::string> availableLayersSet;
for (const auto& layer : availableLayers) {
availableLayersSet.insert(layer.layerName);
}
for (const char* layer : validationLayers) {
if (!availableLayersSet.contains(layer)) {
throw std::runtime_error("Validation layers requested, but not available!");
}
}
}
void createInstance() {
vk::ApplicationInfo appInfo{};
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;
const std::vector extensions = getRequiredExtensions();
checkAllExtensionsSupport(extensions);
vk::InstanceCreateInfo createInfo{};
createInfo.pApplicationInfo = &appInfo;
createInfo.enabledExtensionCount = extensions.size();
createInfo.ppEnabledExtensionNames = extensions.data();
if (enableValidationLayers) {
checkValidationLayerSupport();
vk::DebugUtilsMessengerCreateInfoEXT debugCreateInfo{};
populateDebugMessengerCreateInfo(debugCreateInfo);
createInfo.pNext = &debugCreateInfo;
createInfo.enabledLayerCount = validationLayers.size();
createInfo.ppEnabledLayerNames = validationLayers.data();
} else {
createInfo.enabledLayerCount = 0;
}
checkResult(vk::createInstance(&createInfo, nullptr, &instance));
dispatch = vk::detail::DispatchLoaderDynamic(instance, vkGetInstanceProcAddr);
}
static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(
vk::DebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
vk::DebugUtilsMessageTypeFlagsEXT messageType,
const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData
) {
std::cout << "validation layer: " << pCallbackData->pMessage << std::endl;
return vk::False;
}
static void populateDebugMessengerCreateInfo(vk::DebugUtilsMessengerCreateInfoEXT& createInfo) {
// 指定调试信息的级别
// Verbose 表示详细信息
// Warning 表示警告信息
// Error 表示错误信息
createInfo.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose |
vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning |
vk::DebugUtilsMessageSeverityFlagBitsEXT::eError;
// 指定调试信息的类型
// General 表示一般信息,如应用程序启动、关闭等
// Validation 表示验证层的警告和错误信息
// Performance 表示性能相关的信息
createInfo.messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral |
vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation |
vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance;
// 指定回调函数
createInfo.pfnUserCallback = debugCallback;
createInfo.pUserData = nullptr;
}
void setupDebugMessenger() {
if (!enableValidationLayers) {
return;
}
vk::DebugUtilsMessengerCreateInfoEXT createInfo{};
populateDebugMessengerCreateInfo(createInfo);
// 创建调试回调
messenger = instance.createDebugUtilsMessengerEXT(createInfo, nullptr, dispatch);
}
uint32_t findQueueFamilies(const vk::PhysicalDevice& device) const {
std::vector<vk::QueueFamilyProperties> queueFamilies = device.getQueueFamilyProperties();
return std::ranges::count_if(queueFamilies, [](const vk::QueueFamilyProperties& queueFamily) -> bool {
if (queueFamily.queueFlags & vk::QueueFlagBits::eGraphics) {
return true;
}
return false;
});
}
bool isDeviceSuitable(const vk::PhysicalDevice& device) const {
const vk::PhysicalDeviceProperties deviceProperties = device.getProperties();
const vk::PhysicalDeviceFeatures deviceFeatures = device.getFeatures();
return deviceProperties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu &&
deviceFeatures.geometryShader &&
findQueueFamilies(device) > 0;
}
void pickPhysicalDevice() {
uint32_t deviceCount;
checkResult(instance.enumeratePhysicalDevices(&deviceCount, nullptr));
if (deviceCount == 0) {
throw std::runtime_error("failed to find GPUs with Vulkan Support!");
}
std::vector<vk::PhysicalDevice> devices(deviceCount);
checkResult(instance.enumeratePhysicalDevices(&deviceCount, devices.data()));
for (const auto& device : devices) {
if (isDeviceSuitable(device)) {
physicalDevice = device;
break;
}
}
if (!physicalDevice) {
throw std::runtime_error("failed to find a suitable Vulkan Support!");
}
}
void createLogicalDevice() {
}
void initVulkan() {
createInstance();
setupDebugMessenger();
pickPhysicalDevice();
createLogicalDevice();
}
void mainLoop() const {
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
}
}
void cleanup() const {
if (enableValidationLayers) {
instance.destroyDebugUtilsMessengerEXT(messenger, nullptr, dispatch);
}
instance.destroy();
glfwDestroyWindow(window);
glfwTerminate();
}
};
int main() {
try {
HelloTriangleApplication app;
app.run();
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}