816 lines
28 KiB
C++
816 lines
28 KiB
C++
#include <fmt/format.h>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <set>
|
|
|
|
#define VK_ENABLE_BETA_EXTENSIONS
|
|
#define VULKAN_HPP_NO_CONSTRUCTORS
|
|
#include <vulkan/vulkan.hpp>
|
|
|
|
#include "util/types.h"
|
|
|
|
#define VKFW_NO_STD_FUNCTION_CALLBACKS
|
|
#include "vkfw.hpp"
|
|
|
|
namespace vk {
|
|
using DebugUtilsMessengerUnique = vk::UniqueHandle<vk::DebugUtilsMessengerEXT, vk::DispatchLoaderDynamic>;
|
|
}
|
|
|
|
constexpr i32 WIDTH = 800;
|
|
constexpr i32 HEIGHT = 600;
|
|
|
|
constexpr i32 MAX_FRAMES_IN_FLIGHT = 2;
|
|
|
|
constexpr std::array<const char*, 1> validationLayers = { "VK_LAYER_KHRONOS_validation" };
|
|
|
|
#ifdef __APPLE__
|
|
constexpr std::array<const char*, 2> deviceExtensions = { vk::KHRSwapchainExtensionName,
|
|
vk::KHRPortabilitySubsetExtensionName };
|
|
#else
|
|
constexpr std::array<const char*, 1> deviceExtensions = { vk::KHRSwapchainExtensionName };
|
|
#endif
|
|
|
|
#ifdef NDEBUG
|
|
constexpr bool enableValidationLayers = false;
|
|
#else
|
|
constexpr bool enableValidationLayers = true;
|
|
#endif
|
|
|
|
class VulkanApp {
|
|
public:
|
|
fn run() -> void {
|
|
initWindow();
|
|
initVulkan();
|
|
mainLoop();
|
|
}
|
|
|
|
private:
|
|
vkfw::UniqueInstance mGLFWInstance;
|
|
vkfw::UniqueWindow mWindow;
|
|
|
|
vk::UniqueInstance mInstance;
|
|
|
|
vk::DebugUtilsMessengerUnique mDebugMessenger;
|
|
vk::DispatchLoaderDynamic mLoader;
|
|
vk::UniqueSurfaceKHR mSurface;
|
|
|
|
vk::PhysicalDevice mPhysicalDevice;
|
|
vk::UniqueDevice mDevice;
|
|
|
|
vk::Queue mGraphicsQueue;
|
|
vk::Queue mPresentQueue;
|
|
|
|
vk::UniqueSwapchainKHR mSwapChain;
|
|
std::vector<vk::Image> mSwapChainImages;
|
|
vk::Format mSwapChainImageFormat;
|
|
vk::Extent2D mSwapChainExtent;
|
|
std::vector<vk::UniqueImageView> mSwapChainImageViews;
|
|
std::vector<vk::UniqueFramebuffer> mSwapChainFramebuffers;
|
|
|
|
vk::UniqueRenderPass mRenderPass;
|
|
vk::UniquePipelineLayout mPipelineLayout;
|
|
vk::UniquePipeline mGraphicsPipeline;
|
|
|
|
vk::UniqueCommandPool mCommandPool;
|
|
std::vector<vk::UniqueCommandBuffer> mCommandBuffers;
|
|
|
|
std::vector<vk::UniqueSemaphore> mImageAvailableSemaphores;
|
|
std::vector<vk::UniqueSemaphore> mRenderFinishedSemaphores;
|
|
std::vector<vk::UniqueFence> mInFlightFences;
|
|
|
|
bool mFramebufferResized = false;
|
|
u32 mCurrentFrame = 0;
|
|
|
|
struct QueueFamilyIndices {
|
|
std::optional<u32> graphics_family;
|
|
std::optional<u32> present_family;
|
|
|
|
fn isComplete() -> bool { return graphics_family.has_value() && present_family.has_value(); }
|
|
};
|
|
|
|
struct SwapChainSupportDetails {
|
|
vk::SurfaceCapabilitiesKHR capabilities;
|
|
std::vector<vk::SurfaceFormatKHR> formats;
|
|
std::vector<vk::PresentModeKHR> present_modes;
|
|
};
|
|
|
|
static fn readFile(const std::string& filename) -> std::vector<char> {
|
|
std::ifstream file(filename, std::ios::ate | std::ios::binary);
|
|
|
|
if (!file.is_open())
|
|
throw std::runtime_error("Failed to open file! " + filename);
|
|
|
|
usize fileSize = static_cast<usize>(file.tellg());
|
|
std::vector<char> buffer(fileSize);
|
|
|
|
file.seekg(0);
|
|
file.read(buffer.data(), static_cast<std::streamsize>(fileSize));
|
|
|
|
file.close();
|
|
|
|
return buffer;
|
|
}
|
|
|
|
fn initWindow() -> void {
|
|
mGLFWInstance = vkfw::initUnique();
|
|
|
|
vkfw::WindowHints hints;
|
|
|
|
hints.clientAPI = vkfw::ClientAPI::eNone;
|
|
// hints.resizable = false;
|
|
|
|
mWindow = vkfw::createWindowUnique(WIDTH, HEIGHT, "Vulkan", hints);
|
|
mWindow->setUserPointer(this);
|
|
mWindow->setFramebufferSizeCallback(framebufferResizeCallback);
|
|
}
|
|
|
|
static fn framebufferResizeCallback(GLFWwindow* window, int /*width*/, int /*height*/) -> void {
|
|
auto* app = std::bit_cast<VulkanApp*>(glfwGetWindowUserPointer(window));
|
|
|
|
app->mFramebufferResized = true;
|
|
}
|
|
|
|
fn initVulkan() -> void {
|
|
createInstance();
|
|
setupDebugMessenger();
|
|
createSurface();
|
|
pickPhysicalDevice();
|
|
createLogicalDevice();
|
|
createSwapChain();
|
|
createImageViews();
|
|
createRenderPass();
|
|
createGraphicsPipeline();
|
|
createFramebuffers();
|
|
createCommandPool();
|
|
createCommandBuffers();
|
|
createSyncObjects();
|
|
}
|
|
|
|
fn mainLoop() -> void {
|
|
while (!mWindow->shouldClose()) {
|
|
vkfw::waitEvents();
|
|
drawFrame();
|
|
}
|
|
|
|
mDevice->waitIdle();
|
|
}
|
|
|
|
fn cleanupSwapChain() -> void {
|
|
for (vk::UniqueHandle<vk::Framebuffer, vk::DispatchLoaderStatic>& mSwapChainFramebuffer :
|
|
mSwapChainFramebuffers) {
|
|
mSwapChainFramebuffer.reset();
|
|
}
|
|
for (vk::UniqueHandle<vk::ImageView, vk::DispatchLoaderStatic>& mSwapChainImageView :
|
|
mSwapChainImageViews) {
|
|
mSwapChainImageView.reset();
|
|
}
|
|
|
|
mSwapChain.reset();
|
|
}
|
|
|
|
fn recreateSwapChain() -> void {
|
|
u32 width = 0, height = 0;
|
|
std::tie(width, height) = mWindow->getFramebufferSize();
|
|
|
|
while (width == 0 || height == 0) {
|
|
std::tie(width, height) = mWindow->getFramebufferSize();
|
|
vkfw::waitEvents();
|
|
}
|
|
|
|
mDevice->waitIdle();
|
|
|
|
cleanupSwapChain();
|
|
|
|
createSwapChain();
|
|
createImageViews();
|
|
createFramebuffers();
|
|
}
|
|
|
|
fn createInstance() -> void {
|
|
if (enableValidationLayers && !checkValidationLayerSupport())
|
|
throw std::runtime_error("Validation layers requested, but not available!");
|
|
|
|
vk::ApplicationInfo appInfo { .pApplicationName = "Hello Triangle",
|
|
.applicationVersion = 1,
|
|
.pEngineName = "No Engine",
|
|
.engineVersion = 1,
|
|
.apiVersion = vk::ApiVersion10 };
|
|
|
|
// Retrieve extensions using custom function
|
|
std::vector<const char*> extensions = getRequiredExtensions();
|
|
|
|
#ifdef __APPLE__
|
|
// Enable the portability extension and set flags
|
|
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");
|
|
#endif
|
|
|
|
vk::InstanceCreateInfo createInfo {
|
|
#ifdef __APPLE__
|
|
.flags = vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR,
|
|
#endif
|
|
.pApplicationInfo = &appInfo,
|
|
.enabledLayerCount = enableValidationLayers ? static_cast<u32>(validationLayers.size()) : 0,
|
|
.ppEnabledLayerNames = enableValidationLayers ? validationLayers.data() : nullptr,
|
|
.enabledExtensionCount = static_cast<u32>(extensions.size()),
|
|
.ppEnabledExtensionNames = extensions.data()
|
|
};
|
|
|
|
#ifndef NDEBUG
|
|
fmt::println("Available extensions:");
|
|
|
|
for (const char* extension : extensions) fmt::println("\t{}", extension);
|
|
#endif
|
|
|
|
try {
|
|
mInstance = vk::createInstanceUnique(createInfo);
|
|
mLoader = vk::DispatchLoaderDynamic(mInstance.get(), vkGetInstanceProcAddr);
|
|
} catch (const vk::SystemError& err) {
|
|
throw std::runtime_error("Failed to create instance: " + std::string(err.what()));
|
|
}
|
|
}
|
|
|
|
fn setupDebugMessenger() -> void {
|
|
if (!enableValidationLayers)
|
|
return;
|
|
|
|
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,
|
|
};
|
|
|
|
mDebugMessenger = mInstance->createDebugUtilsMessengerEXTUnique(messengerCreateInfo, nullptr, mLoader);
|
|
}
|
|
|
|
fn createSurface() -> void { mSurface = vkfw::createWindowSurfaceUnique(mInstance.get(), mWindow.get()); }
|
|
|
|
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();
|
|
fmt::println("\t{}", properties.deviceName.data());
|
|
#endif
|
|
|
|
if (isDeviceSuitable(device)) {
|
|
mPhysicalDevice = device;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!mPhysicalDevice)
|
|
throw std::runtime_error("Failed to find a suitable GPU!");
|
|
}
|
|
|
|
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 };
|
|
|
|
queueCreateInfos.emplace_back(queueCreateInfo);
|
|
}
|
|
|
|
vk::PhysicalDeviceFeatures deviceFeatures;
|
|
|
|
vk::DeviceCreateInfo createInfo { .queueCreateInfoCount = static_cast<u32>(queueCreateInfos.size()),
|
|
.pQueueCreateInfos = queueCreateInfos.data(),
|
|
.enabledExtensionCount = static_cast<u32>(deviceExtensions.size()),
|
|
.ppEnabledExtensionNames = deviceExtensions.data(),
|
|
.pEnabledFeatures = &deviceFeatures };
|
|
|
|
mDevice = mPhysicalDevice.createDeviceUnique(createInfo);
|
|
|
|
mGraphicsQueue = mDevice->getQueue(indices.graphics_family.value(), 0);
|
|
mPresentQueue = mDevice->getQueue(indices.present_family.value(), 0);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
fn createRenderPass() -> void {
|
|
vk::AttachmentDescription colorAttachment {
|
|
.format = mSwapChainImageFormat,
|
|
.samples = vk::SampleCountFlagBits::e1,
|
|
.loadOp = vk::AttachmentLoadOp::eClear,
|
|
.storeOp = vk::AttachmentStoreOp::eStore,
|
|
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
|
|
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare,
|
|
.initialLayout = vk::ImageLayout::eUndefined,
|
|
.finalLayout = vk::ImageLayout::ePresentSrcKHR,
|
|
};
|
|
|
|
vk::AttachmentReference colorAttachmentRef {
|
|
.attachment = 0,
|
|
.layout = vk::ImageLayout::eColorAttachmentOptimal,
|
|
};
|
|
|
|
vk::SubpassDescription subpass {
|
|
.pipelineBindPoint = vk::PipelineBindPoint::eGraphics,
|
|
.colorAttachmentCount = 1,
|
|
.pColorAttachments = &colorAttachmentRef,
|
|
};
|
|
|
|
vk::RenderPassCreateInfo renderPassInfo {
|
|
.attachmentCount = 1,
|
|
.pAttachments = &colorAttachment,
|
|
.subpassCount = 1,
|
|
.pSubpasses = &subpass,
|
|
};
|
|
|
|
mRenderPass = mDevice->createRenderPassUnique(renderPassInfo);
|
|
}
|
|
|
|
fn createGraphicsPipeline() -> void {
|
|
std::vector<char> vertShaderCode = readFile("src/shaders/vert.spv");
|
|
std::vector<char> fragShaderCode = readFile("src/shaders/frag.spv");
|
|
|
|
vk::UniqueShaderModule vertShaderModule = createShaderModule(vertShaderCode);
|
|
vk::UniqueShaderModule fragShaderModule = createShaderModule(fragShaderCode);
|
|
|
|
vk::PipelineShaderStageCreateInfo vertShaderStageInfo {
|
|
.stage = vk::ShaderStageFlagBits::eVertex,
|
|
.module = vertShaderModule.get(),
|
|
.pName = "main",
|
|
};
|
|
|
|
vk::PipelineShaderStageCreateInfo fragShaderStageInfo {
|
|
.stage = vk::ShaderStageFlagBits::eFragment,
|
|
.module = fragShaderModule.get(),
|
|
.pName = "main",
|
|
};
|
|
|
|
std::array<vk::PipelineShaderStageCreateInfo, 2> shaderStages = { vertShaderStageInfo,
|
|
fragShaderStageInfo };
|
|
|
|
vk::PipelineVertexInputStateCreateInfo vertexInputInfo {
|
|
.vertexBindingDescriptionCount = 0,
|
|
.pVertexBindingDescriptions = nullptr,
|
|
.vertexAttributeDescriptionCount = 0,
|
|
.pVertexAttributeDescriptions = nullptr,
|
|
};
|
|
|
|
vk::PipelineInputAssemblyStateCreateInfo inputAssembly {
|
|
.topology = vk::PrimitiveTopology::eTriangleList,
|
|
.primitiveRestartEnable = vk::False,
|
|
};
|
|
|
|
vk::PipelineViewportStateCreateInfo viewportState {
|
|
.viewportCount = 1,
|
|
.scissorCount = 1,
|
|
};
|
|
|
|
vk::PipelineRasterizationStateCreateInfo rasterizer {
|
|
.depthClampEnable = vk::False,
|
|
.rasterizerDiscardEnable = vk::False,
|
|
.polygonMode = vk::PolygonMode::eFill,
|
|
.cullMode = vk::CullModeFlagBits::eBack,
|
|
.frontFace = vk::FrontFace::eClockwise,
|
|
.depthBiasEnable = vk::False,
|
|
.lineWidth = 1.0F,
|
|
};
|
|
|
|
vk::PipelineMultisampleStateCreateInfo multisampling {
|
|
.rasterizationSamples = vk::SampleCountFlagBits::e1,
|
|
.sampleShadingEnable = vk::False,
|
|
};
|
|
|
|
vk::PipelineColorBlendAttachmentState colorBlendAttachment {
|
|
.blendEnable = vk::False,
|
|
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
|
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA,
|
|
};
|
|
|
|
vk::PipelineColorBlendStateCreateInfo colorBlending {
|
|
.logicOpEnable = vk::False,
|
|
.logicOp = vk::LogicOp::eCopy,
|
|
.attachmentCount = 1,
|
|
.pAttachments = &colorBlendAttachment,
|
|
.blendConstants = std::array<float, 4> { 0.0F, 0.0F, 0.0F, 0.0F },
|
|
};
|
|
std::vector<vk::DynamicState> dynamicStates = { vk::DynamicState::eViewport, vk::DynamicState::eScissor };
|
|
|
|
vk::PipelineDynamicStateCreateInfo dynamicState {
|
|
.dynamicStateCount = static_cast<u32>(dynamicStates.size()),
|
|
.pDynamicStates = dynamicStates.data(),
|
|
};
|
|
|
|
vk::PipelineLayoutCreateInfo pipelineLayoutInfo {
|
|
.setLayoutCount = 0,
|
|
.pushConstantRangeCount = 0,
|
|
};
|
|
|
|
mPipelineLayout = mDevice->createPipelineLayoutUnique(pipelineLayoutInfo);
|
|
|
|
vk::GraphicsPipelineCreateInfo pipelineInfo {
|
|
.stageCount = static_cast<u32>(shaderStages.size()),
|
|
.pStages = shaderStages.data(),
|
|
.pVertexInputState = &vertexInputInfo,
|
|
.pInputAssemblyState = &inputAssembly,
|
|
.pViewportState = &viewportState,
|
|
.pRasterizationState = &rasterizer,
|
|
.pMultisampleState = &multisampling,
|
|
.pColorBlendState = &colorBlending,
|
|
.pDynamicState = &dynamicState,
|
|
.layout = mPipelineLayout.get(),
|
|
.renderPass = mRenderPass.get(),
|
|
.subpass = 0,
|
|
};
|
|
|
|
mGraphicsPipeline = mDevice->createGraphicsPipelineUnique(nullptr, pipelineInfo).value;
|
|
}
|
|
|
|
fn createFramebuffers() -> void {
|
|
mSwapChainFramebuffers.resize(mSwapChainImageViews.size());
|
|
|
|
for (usize i = 0; i < mSwapChainImageViews.size(); i++) {
|
|
vk::FramebufferCreateInfo framebufferInfo {
|
|
.renderPass = mRenderPass.get(),
|
|
.attachmentCount = 1,
|
|
.pAttachments = &mSwapChainImageViews[i].get(),
|
|
.width = mSwapChainExtent.width,
|
|
.height = mSwapChainExtent.height,
|
|
.layers = 1,
|
|
};
|
|
|
|
mSwapChainFramebuffers[i] = mDevice->createFramebufferUnique(framebufferInfo);
|
|
}
|
|
}
|
|
|
|
fn createCommandPool() -> void {
|
|
QueueFamilyIndices queueFamilyIndices = findQueueFamilies(mPhysicalDevice);
|
|
|
|
vk::CommandPoolCreateInfo poolInfo {
|
|
.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
|
|
.queueFamilyIndex = queueFamilyIndices.graphics_family.value(),
|
|
};
|
|
|
|
mCommandPool = mDevice->createCommandPoolUnique(poolInfo);
|
|
}
|
|
|
|
fn createCommandBuffers() -> void {
|
|
mCommandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
|
|
|
|
vk::CommandBufferAllocateInfo allocInfo { .commandPool = mCommandPool.get(),
|
|
.level = vk::CommandBufferLevel::ePrimary,
|
|
.commandBufferCount =
|
|
static_cast<u32>(mCommandBuffers.size()) };
|
|
|
|
mCommandBuffers = mDevice->allocateCommandBuffersUnique(allocInfo);
|
|
}
|
|
|
|
fn recordCommandBuffer(vk::CommandBuffer commandBuffer, u32 imageIndex) -> void {
|
|
vk::CommandBufferBeginInfo beginInfo {};
|
|
|
|
commandBuffer.begin(beginInfo);
|
|
|
|
vk::ClearValue clearColor { .color = { .float32 = std::array<float, 4> { 0.0F, 0.0F, 0.0F, 1.0F } } };
|
|
|
|
vk::RenderPassBeginInfo renderPassInfo {
|
|
.renderPass = mRenderPass.get(),
|
|
.framebuffer = mSwapChainFramebuffers[imageIndex].get(),
|
|
.renderArea = { .offset = { .x = 0, .y = 0 }, .extent = mSwapChainExtent },
|
|
.clearValueCount = 1,
|
|
.pClearValues = &clearColor,
|
|
};
|
|
|
|
commandBuffer.beginRenderPass(renderPassInfo, vk::SubpassContents::eInline);
|
|
commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, mGraphicsPipeline.get());
|
|
|
|
vk::Viewport viewport {
|
|
.x = 0.0F,
|
|
.y = 0.0F,
|
|
.width = static_cast<f32>(mSwapChainExtent.width),
|
|
.height = static_cast<f32>(mSwapChainExtent.height),
|
|
.minDepth = 0.0F,
|
|
.maxDepth = 1.0F,
|
|
};
|
|
vk::Rect2D scissor {
|
|
.offset = { 0, 0 },
|
|
.extent = mSwapChainExtent,
|
|
};
|
|
|
|
commandBuffer.setViewport(0, viewport);
|
|
commandBuffer.setScissor(0, scissor);
|
|
|
|
commandBuffer.draw(3, 1, 0, 0);
|
|
|
|
commandBuffer.endRenderPass();
|
|
commandBuffer.end();
|
|
}
|
|
|
|
fn createSyncObjects() -> void {
|
|
mImageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
|
|
mRenderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
|
|
mInFlightFences.resize(MAX_FRAMES_IN_FLIGHT);
|
|
|
|
vk::SemaphoreCreateInfo semaphoreInfo {};
|
|
vk::FenceCreateInfo fenceInfo { .flags = vk::FenceCreateFlagBits::eSignaled };
|
|
|
|
for (usize idx = 0; idx < MAX_FRAMES_IN_FLIGHT; idx++) {
|
|
mImageAvailableSemaphores[idx] = mDevice->createSemaphoreUnique(semaphoreInfo);
|
|
mRenderFinishedSemaphores[idx] = mDevice->createSemaphoreUnique(semaphoreInfo);
|
|
mInFlightFences[idx] = mDevice->createFenceUnique(fenceInfo);
|
|
}
|
|
}
|
|
|
|
fn drawFrame() -> void {
|
|
vk::Result result =
|
|
mDevice->waitForFences(mInFlightFences[mCurrentFrame].get(), vk::Bool32(vk::True), UINT64_MAX);
|
|
|
|
if (result != vk::Result::eSuccess)
|
|
throw std::runtime_error("Failed to wait for fences!");
|
|
|
|
vk::ResultValue<uint32_t> imageIndex = mDevice->acquireNextImageKHR(
|
|
mSwapChain.get(), UINT64_MAX, mImageAvailableSemaphores[mCurrentFrame].get(), nullptr
|
|
);
|
|
|
|
if (imageIndex.result == vk::Result::eErrorOutOfDateKHR) {
|
|
recreateSwapChain();
|
|
return;
|
|
}
|
|
|
|
if (imageIndex.result != vk::Result::eSuccess && imageIndex.result != vk::Result::eSuboptimalKHR)
|
|
throw std::runtime_error("Failed to acquire swap chain image!");
|
|
|
|
mDevice->resetFences(mInFlightFences[mCurrentFrame].get());
|
|
|
|
mCommandBuffers[mCurrentFrame]->reset(vk::CommandBufferResetFlagBits::eReleaseResources);
|
|
recordCommandBuffer(mCommandBuffers[mCurrentFrame].get(), imageIndex.value);
|
|
|
|
std::array<vk::PipelineStageFlags, 1> waitStages = { vk::PipelineStageFlagBits::eColorAttachmentOutput };
|
|
|
|
vk::SubmitInfo submitInfo {
|
|
.waitSemaphoreCount = 1,
|
|
.pWaitSemaphores = &mImageAvailableSemaphores[mCurrentFrame].get(),
|
|
.pWaitDstStageMask = waitStages.data(),
|
|
.commandBufferCount = 1,
|
|
.pCommandBuffers = &mCommandBuffers[mCurrentFrame].get(),
|
|
.signalSemaphoreCount = 1,
|
|
.pSignalSemaphores = &mRenderFinishedSemaphores[mCurrentFrame].get(),
|
|
};
|
|
|
|
mGraphicsQueue.submit(submitInfo, mInFlightFences[mCurrentFrame].get());
|
|
|
|
vk::PresentInfoKHR presentInfo {
|
|
.waitSemaphoreCount = 1,
|
|
.pWaitSemaphores = &mRenderFinishedSemaphores[mCurrentFrame].get(),
|
|
.swapchainCount = 1,
|
|
.pSwapchains = &mSwapChain.get(),
|
|
.pImageIndices = &imageIndex.value,
|
|
};
|
|
|
|
vk::Result presentResult = mPresentQueue.presentKHR(presentInfo);
|
|
|
|
if (presentResult == vk::Result::eErrorOutOfDateKHR || presentResult == vk::Result::eSuboptimalKHR || mFramebufferResized) {
|
|
mFramebufferResized = false;
|
|
recreateSwapChain();
|
|
} else if (presentResult != vk::Result::eSuccess){
|
|
std::cout << presentResult << '\n';
|
|
throw std::runtime_error("Failed to present swap chain image!");
|
|
}
|
|
|
|
mCurrentFrame = (mCurrentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
|
|
}
|
|
|
|
fn createShaderModule(const std::vector<char>& code) -> vk::UniqueShaderModule {
|
|
vk::ShaderModuleCreateInfo createInfo { .codeSize = code.size(),
|
|
.pCode = std::bit_cast<const u32*>(code.data()) };
|
|
|
|
return mDevice->createShaderModuleUnique(createInfo);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static fn getRequiredExtensions() -> std::vector<const char*> {
|
|
std::span<const char*> extensionsSpan = vkfw::getRequiredInstanceExtensions();
|
|
|
|
std::vector extensions(extensionsSpan.begin(), extensionsSpan.end());
|
|
|
|
if (enableValidationLayers)
|
|
extensions.emplace_back(vk::EXTDebugUtilsExtensionName);
|
|
|
|
return extensions;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static VKAPI_ATTR fn VKAPI_CALL debugCallback(
|
|
VkDebugUtilsMessageSeverityFlagBitsEXT /*messageSeverity*/,
|
|
VkDebugUtilsMessageTypeFlagsEXT /*messageType*/,
|
|
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
|
void* /*pUserData*/
|
|
) -> vk::Bool32 {
|
|
fmt::println("Validation layer: {}", pCallbackData->pMessage);
|
|
|
|
return vk::False;
|
|
}
|
|
};
|
|
|
|
fn main() -> i32 {
|
|
VulkanApp app;
|
|
|
|
try {
|
|
app.run();
|
|
} catch (const std::exception& e) {
|
|
fmt::println("{}", e.what());
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|