vulkan-test/src/main.cpp

437 lines
15 KiB
C++
Raw Normal View History

2024-09-30 00:57:13 -04:00
#include <fmt/format.h>
2024-09-28 18:13:24 -04:00
#include <iostream>
2024-09-30 00:57:13 -04:00
#include <set>
2024-09-25 23:03:56 -04:00
2024-09-30 21:21:38 -04:00
#define VK_ENABLE_BETA_EXTENSIONS
2024-09-29 23:02:04 -04:00
#define VULKAN_HPP_NO_CONSTRUCTORS
#include <vulkan/vulkan.hpp>
2024-09-28 19:38:13 -04:00
#include "util/types.h"
#include "vkfw.hpp"
2024-09-25 23:03:56 -04:00
2024-09-30 00:31:08 -04:00
namespace vk {
using DebugUtilsMessengerUnique = vk::UniqueHandle<vk::DebugUtilsMessengerEXT, vk::DispatchLoaderDynamic>;
}
constexpr i32 WIDTH = 800;
constexpr i32 HEIGHT = 600;
2024-09-25 23:03:56 -04:00
2024-09-29 23:02:04 -04:00
constexpr std::array<const char*, 1> validationLayers = { "VK_LAYER_KHRONOS_validation" };
2024-09-25 23:03:56 -04:00
#ifdef __APPLE__
constexpr std::array<const char*, 2> deviceExtensions = { vk::KHRSwapchainExtensionName,
vk::KHRPortabilitySubsetExtensionName };
#else
constexpr std::array<const char*, 1> deviceExtensions = { vk::KHRSwapchainExtensionName };
#endif
2024-09-26 19:56:19 -04:00
#ifdef NDEBUG
2024-09-29 23:02:04 -04:00
constexpr bool enableValidationLayers = false;
2024-09-25 23:03:56 -04:00
#else
2024-09-29 23:02:04 -04:00
constexpr bool enableValidationLayers = true;
2024-09-25 23:03:56 -04:00
#endif
2024-09-29 23:11:12 -04:00
class VulkanApp {
2024-09-25 23:03:56 -04:00
public:
2024-09-30 00:57:13 -04:00
fn run() -> void {
2024-09-25 23:03:56 -04:00
initWindow();
initVulkan();
mainLoop();
}
private:
2024-09-30 16:46:17 -04:00
vkfw::UniqueInstance mGLFWInstance;
vkfw::UniqueWindow mWindow;
2024-09-28 18:13:24 -04:00
2024-09-29 23:02:04 -04:00
vk::UniqueInstance mInstance;
2024-09-25 23:03:56 -04:00
2024-09-30 00:31:08 -04:00
vk::DebugUtilsMessengerUnique mDebugMessenger;
vk::DispatchLoaderDynamic mLoader;
2024-10-01 17:06:14 -04:00
vk::UniqueSurfaceKHR mSurface;
2024-09-30 00:31:08 -04:00
vk::PhysicalDevice mPhysicalDevice;
2024-09-30 00:57:13 -04:00
vk::UniqueDevice mDevice;
vk::Queue mGraphicsQueue;
vk::Queue mPresentQueue;
2024-10-01 17:06:14 -04:00
vk::UniqueSwapchainKHR mSwapChain;
std::vector<vk::Image> mSwapChainImages;
vk::Format mSwapChainImageFormat;
vk::Extent2D mSwapChainExtent;
std::vector<vk::UniqueImageView> mSwapChainImageViews;
2024-10-01 16:57:40 -04:00
2024-09-30 00:31:08 -04:00
struct QueueFamilyIndices {
std::optional<u32> graphics_family;
2024-09-30 00:57:13 -04:00
std::optional<u32> present_family;
2024-09-30 00:31:08 -04:00
2024-09-30 00:57:13 -04:00
fn isComplete() -> bool { return graphics_family.has_value() && present_family.has_value(); }
2024-09-30 00:31:08 -04:00
};
2024-09-28 21:55:26 -04:00
struct SwapChainSupportDetails {
vk::SurfaceCapabilitiesKHR capabilities;
std::vector<vk::SurfaceFormatKHR> formats;
std::vector<vk::PresentModeKHR> present_modes;
};
2024-09-30 00:57:13 -04:00
fn initWindow() -> void {
mGLFWInstance = vkfw::initUnique();
2024-09-25 23:03:56 -04:00
vkfw::WindowHints hints;
2024-09-25 23:03:56 -04:00
hints.clientAPI = vkfw::ClientAPI::eNone;
2024-09-30 21:34:25 -04:00
hints.resizable = vkfw::eFalse;
2024-09-30 16:46:17 -04:00
mWindow = vkfw::createWindowUnique(WIDTH, HEIGHT, "Vulkan", hints);
2024-09-25 23:03:56 -04:00
}
2024-09-30 00:57:13 -04:00
fn initVulkan() -> void {
2024-09-28 18:13:24 -04:00
createInstance();
setupDebugMessenger();
2024-09-30 00:57:13 -04:00
createSurface();
2024-09-30 00:31:08 -04:00
pickPhysicalDevice();
2024-09-30 00:57:13 -04:00
createLogicalDevice();
2024-10-01 16:57:40 -04:00
createSwapChain();
2024-10-01 17:06:14 -04:00
createImageViews();
2024-09-29 23:02:04 -04:00
}
2024-09-28 14:54:39 -04:00
2024-09-30 00:57:13 -04:00
fn mainLoop() -> void {
2024-09-30 16:46:17 -04:00
while (!mWindow->shouldClose()) { vkfw::waitEvents(); }
2024-09-28 14:54:39 -04:00
}
2024-09-25 23:03:56 -04:00
fn createInstance() -> void {
if (enableValidationLayers && !checkValidationLayerSupport())
2024-09-30 21:21:38 -04:00
throw std::runtime_error("Validation layers requested, but not available!");
2024-09-25 23:03:56 -04:00
2024-09-29 20:12:56 -04:00
vk::ApplicationInfo appInfo { .pApplicationName = "Hello Triangle",
.applicationVersion = 1,
.pEngineName = "No Engine",
.engineVersion = 1,
2024-09-30 21:21:38 -04:00
.apiVersion = vk::ApiVersion10 };
2024-09-25 23:03:56 -04:00
2024-09-29 20:12:56 -04:00
// Retrieve extensions using custom function
2024-09-28 18:13:24 -04:00
std::vector<const char*> extensions = getRequiredExtensions();
2024-09-26 19:56:19 -04:00
#ifdef __APPLE__
2024-09-30 00:31:08 -04:00
// Enable the portability extension and set flags
2024-10-01 14:15:39 -04:00
extensions.emplace_back(vk::KHRPortabilityEnumerationExtensionName);
// Technically deprecated but vulkan complains if I don't include it for macOS
// So instead of using the vk::KHRPortabilitySubsetExtensionName, I just use
// the direct string.
extensions.emplace_back("VK_KHR_get_physical_device_properties2");
2024-09-30 21:21:38 -04:00
#endif
2024-09-30 00:31:08 -04:00
2024-09-29 23:05:15 -04:00
vk::InstanceCreateInfo createInfo {
2024-10-01 16:57:40 -04:00
#ifdef __APPLE__
.flags = vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR,
#endif
2024-09-30 00:31:08 -04:00
.pApplicationInfo = &appInfo,
.enabledLayerCount = enableValidationLayers ? static_cast<u32>(validationLayers.size()) : 0,
2024-09-30 00:31:08 -04:00
.ppEnabledLayerNames = enableValidationLayers ? validationLayers.data() : nullptr,
.enabledExtensionCount = static_cast<u32>(extensions.size()),
2024-09-30 00:31:08 -04:00
.ppEnabledExtensionNames = extensions.data()
2024-09-29 23:05:15 -04:00
};
2024-09-26 17:18:45 -04:00
2024-09-30 00:31:08 -04:00
#ifndef NDEBUG
2024-09-29 23:02:04 -04:00
fmt::println("Available extensions:");
for (const char* extension : extensions) fmt::println("\t{}", extension);
2024-09-30 00:31:08 -04:00
#endif
2024-09-25 23:03:56 -04:00
2024-09-29 20:12:56 -04:00
try {
2024-09-29 23:02:04 -04:00
mInstance = vk::createInstanceUnique(createInfo);
mLoader = vk::DispatchLoaderDynamic(mInstance.get(), vkGetInstanceProcAddr);
2024-09-29 20:12:56 -04:00
} catch (const vk::SystemError& err) {
throw std::runtime_error("Failed to create instance: " + std::string(err.what()));
}
2024-09-28 14:54:39 -04:00
}
2024-09-30 00:57:13 -04:00
fn setupDebugMessenger() -> void {
2024-09-30 00:31:08 -04:00
if (!enableValidationLayers)
return;
2024-09-28 18:13:24 -04:00
2024-09-30 00:31:08 -04:00
vk::DebugUtilsMessengerCreateInfoEXT messengerCreateInfo {
.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose |
vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning |
vk::DebugUtilsMessageSeverityFlagBitsEXT::eError,
.messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral |
vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation |
vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance,
.pfnUserCallback = debugCallback,
};
2024-09-28 18:13:24 -04:00
2024-09-30 00:31:08 -04:00
mDebugMessenger = mInstance->createDebugUtilsMessengerEXTUnique(messengerCreateInfo, nullptr, mLoader);
}
2024-09-28 18:13:24 -04:00
2024-09-30 16:46:17 -04:00
fn createSurface() -> void { mSurface = vkfw::createWindowSurfaceUnique(mInstance.get(), mWindow.get()); }
2024-09-30 00:57:13 -04:00
2024-09-30 00:31:08 -04:00
fn pickPhysicalDevice() -> void {
std::vector<vk::PhysicalDevice> devices = mInstance->enumeratePhysicalDevices();
if (devices.empty())
throw std::runtime_error("Failed to find GPUs with Vulkan support!");
#ifndef NDEBUG
fmt::println("Available devices:");
#endif
for (const auto& device : devices) {
#ifndef NDEBUG
vk::PhysicalDeviceProperties properties = device.getProperties();
2024-09-30 19:49:44 -04:00
fmt::println("\t{}", properties.deviceName.data());
2024-09-30 00:31:08 -04:00
#endif
if (isDeviceSuitable(device)) {
mPhysicalDevice = device;
break;
}
2024-09-28 18:13:24 -04:00
}
2024-09-30 00:31:08 -04:00
if (!mPhysicalDevice)
throw std::runtime_error("Failed to find a suitable GPU!");
}
2024-09-30 00:57:13 -04:00
fn createLogicalDevice() -> void {
QueueFamilyIndices indices = findQueueFamilies(mPhysicalDevice);
std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos;
std::set<u32> uniqueQueueFamilies = { indices.graphics_family.value(), indices.present_family.value() };
f32 queuePriority = 1.0F;
for (u32 queueFamily : uniqueQueueFamilies) {
vk::DeviceQueueCreateInfo queueCreateInfo { .queueFamilyIndex = queueFamily,
.queueCount = 1,
.pQueuePriorities = &queuePriority };
2024-10-01 14:15:39 -04:00
queueCreateInfos.emplace_back(queueCreateInfo);
2024-09-30 00:57:13 -04:00
}
vk::PhysicalDeviceFeatures deviceFeatures;
vk::DeviceCreateInfo createInfo { .queueCreateInfoCount = static_cast<u32>(queueCreateInfos.size()),
.pQueueCreateInfos = queueCreateInfos.data(),
.enabledExtensionCount = static_cast<u32>(deviceExtensions.size()),
.ppEnabledExtensionNames = deviceExtensions.data(),
2024-09-30 00:57:13 -04:00
.pEnabledFeatures = &deviceFeatures };
mDevice = mPhysicalDevice.createDeviceUnique(createInfo);
mGraphicsQueue = mDevice->getQueue(indices.graphics_family.value(), 0);
2024-09-30 15:25:22 -04:00
mPresentQueue = mDevice->getQueue(indices.present_family.value(), 0);
2024-09-30 00:57:13 -04:00
}
2024-10-01 16:57:40 -04:00
fn createSwapChain() -> void {
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(mPhysicalDevice);
vk::SurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
vk::PresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.present_modes);
vk::Extent2D extent = chooseSwapExtent(swapChainSupport.capabilities);
u32 imageCount = swapChainSupport.capabilities.minImageCount + 1;
if (swapChainSupport.capabilities.maxImageCount > 0 &&
imageCount > swapChainSupport.capabilities.maxImageCount)
imageCount = swapChainSupport.capabilities.maxImageCount;
QueueFamilyIndices indices = findQueueFamilies(mPhysicalDevice);
std::array<u32, 2> queueFamilyIndices = { indices.graphics_family.value(),
indices.present_family.value() };
vk::SwapchainCreateInfoKHR createInfo {
.surface = mSurface.get(),
.minImageCount = imageCount,
.imageFormat = surfaceFormat.format,
.imageColorSpace = surfaceFormat.colorSpace,
.imageExtent = extent,
.imageArrayLayers = 1,
.imageUsage = vk::ImageUsageFlagBits::eColorAttachment,
.imageSharingMode = indices.graphics_family != indices.present_family ? vk::SharingMode::eConcurrent
: vk::SharingMode::eExclusive,
.queueFamilyIndexCount = static_cast<u32>(indices.graphics_family != indices.present_family ? 2 : 0),
.pQueueFamilyIndices =
indices.graphics_family != indices.present_family ? queueFamilyIndices.data() : nullptr,
.preTransform = swapChainSupport.capabilities.currentTransform,
.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque,
.presentMode = presentMode,
.clipped = VK_TRUE,
.oldSwapchain = nullptr,
};
mSwapChain = mDevice->createSwapchainKHRUnique(createInfo);
mSwapChainImages = mDevice->getSwapchainImagesKHR(mSwapChain.get());
mSwapChainImageFormat = surfaceFormat.format;
mSwapChainExtent = extent;
}
2024-10-01 17:06:14 -04:00
fn createImageViews() -> void {
mSwapChainImageViews.resize(mSwapChainImages.size());
for (u32 i = 0; i < mSwapChainImages.size(); i++) {
vk::ImageViewCreateInfo createInfo {
.image = mSwapChainImages[i],
.viewType = vk::ImageViewType::e2D,
.format = mSwapChainImageFormat,
.components = { .r = vk::ComponentSwizzle::eIdentity,
.g = vk::ComponentSwizzle::eIdentity,
.b = vk::ComponentSwizzle::eIdentity,
.a = vk::ComponentSwizzle::eIdentity },
.subresourceRange = { .aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1 },
};
mSwapChainImageViews[i] = mDevice->createImageViewUnique(createInfo);
}
}
2024-10-01 16:57:40 -04:00
static fn chooseSwapSurfaceFormat(const std::vector<vk::SurfaceFormatKHR>& availableFormats
) -> vk::SurfaceFormatKHR {
for (const auto& availableFormat : availableFormats)
if (availableFormat.format == vk::Format::eB8G8R8A8Srgb &&
availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear)
return availableFormat;
return availableFormats[0];
}
static fn chooseSwapPresentMode(const std::vector<vk::PresentModeKHR>& availablePresentModes
) -> vk::PresentModeKHR {
for (const auto& availablePresentMode : availablePresentModes)
if (availablePresentMode == vk::PresentModeKHR::eMailbox)
return availablePresentMode;
return vk::PresentModeKHR::eFifo;
}
fn chooseSwapExtent(const vk::SurfaceCapabilitiesKHR capabilities) -> vk::Extent2D {
if (capabilities.currentExtent.width != UINT32_MAX)
return capabilities.currentExtent;
u32 width = 0, height = 0;
std::tie(width, height) = mWindow->getFramebufferSize();
vk::Extent2D actualExtent = { width, 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;
}
2024-10-01 17:06:14 -04:00
fn querySwapChainSupport(vk::PhysicalDevice device) -> SwapChainSupportDetails {
SwapChainSupportDetails details;
details.capabilities = device.getSurfaceCapabilitiesKHR(mSurface.get());
details.formats = device.getSurfaceFormatsKHR(mSurface.get());
details.present_modes = device.getSurfacePresentModesKHR(mSurface.get());
return details;
}
fn isDeviceSuitable(vk::PhysicalDevice device) -> bool {
QueueFamilyIndices indices = findQueueFamilies(device);
bool extensionsSupported = checkDeviceExtensionSupport(device);
bool swapChainAdequate = false;
if (extensionsSupported) {
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.present_modes.empty();
}
return indices.isComplete() && extensionsSupported && swapChainAdequate;
}
static fn checkDeviceExtensionSupport(vk::PhysicalDevice device) -> bool {
std::vector<vk::ExtensionProperties> availableExtensions = device.enumerateDeviceExtensionProperties();
std::set<string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());
for (const auto& extension : availableExtensions) requiredExtensions.erase(extension.extensionName);
return requiredExtensions.empty();
}
fn findQueueFamilies(vk::PhysicalDevice device) -> QueueFamilyIndices {
QueueFamilyIndices indices;
std::vector<vk::QueueFamilyProperties> queueFamilies = device.getQueueFamilyProperties();
for (u32 i = 0; i < queueFamilies.size(); i++) {
if (queueFamilies[i].queueFlags & vk::QueueFlagBits::eGraphics)
indices.graphics_family = i;
vk::Bool32 presentSupport = device.getSurfaceSupportKHR(i, mSurface.get());
if (presentSupport)
indices.present_family = i;
if (indices.isComplete())
break;
}
return indices;
}
2024-09-28 18:13:24 -04:00
static fn getRequiredExtensions() -> std::vector<const char*> {
2024-09-30 16:46:17 -04:00
std::span<const char*> extensionsSpan = vkfw::getRequiredInstanceExtensions();
2024-09-28 18:13:24 -04:00
2024-09-30 16:46:17 -04:00
std::vector extensions(extensionsSpan.begin(), extensionsSpan.end());
2024-09-28 18:13:24 -04:00
2024-09-28 14:54:39 -04:00
if (enableValidationLayers)
2024-10-01 14:15:39 -04:00
extensions.emplace_back(vk::EXTDebugUtilsExtensionName);
2024-09-28 14:54:39 -04:00
2024-09-28 18:13:24 -04:00
return extensions;
}
2024-09-30 00:31:08 -04:00
static fn checkValidationLayerSupport() -> bool {
std::vector<vk::LayerProperties> availableLayers = vk::enumerateInstanceLayerProperties();
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;
}
2024-09-28 18:13:24 -04:00
static VKAPI_ATTR fn VKAPI_CALL debugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT /*messageSeverity*/,
VkDebugUtilsMessageTypeFlagsEXT /*messageType*/,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* /*pUserData*/
2024-09-30 00:57:13 -04:00
) -> vk::Bool32 {
2024-09-29 23:02:04 -04:00
fmt::println("Validation layer: {}", pCallbackData->pMessage);
2024-09-28 18:13:24 -04:00
2024-09-30 21:34:25 -04:00
return vk::False;
2024-09-25 23:03:56 -04:00
}
};
2024-09-28 21:55:26 -04:00
fn main() -> i32 {
2024-09-29 23:11:12 -04:00
VulkanApp app;
2024-09-25 23:03:56 -04:00
try {
app.run();
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}