Skip to content

Instantly share code, notes, and snippets.

@Adanos020
Last active September 12, 2019 21:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Adanos020/9d1569776d5486216e048228ecaa3525 to your computer and use it in GitHub Desktop.
Save Adanos020/9d1569776d5486216e048228ecaa3525 to your computer and use it in GitHub Desktop.
My VkSpinningChalet
#pragma once
#include <vulkan/vulkan.hpp>
#include <iostream>
#ifndef NDEBUG
# define VK_VALIDATION_LAYERS_ENABLED
#endif
inline static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageTypes,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData)
{
if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT)
{
std::cerr << "Err: " << pCallbackData->pMessage << std::endl;
}
else
{
std::cout << "Log: " << pCallbackData->pMessage << std::endl;
}
return VK_FALSE;
}
inline static vk::Result createDebugUtilsMessengerExt(
vk::Instance instance,
const vk::DebugUtilsMessengerCreateInfoEXT& pCreateInfo,
vk::Optional<const vk::AllocationCallbacks> pAllocator,
VkDebugUtilsMessengerEXT* pDebugMessenger)
{
if (auto func = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(
instance.getProcAddr("vkCreateDebugUtilsMessengerEXT")))
{
const VkDebugUtilsMessengerCreateInfoEXT& pCI = pCreateInfo;
if (!pAllocator)
{
return vk::Result{ func(instance, &pCI, nullptr, pDebugMessenger) };
}
const VkAllocationCallbacks& pA = *pAllocator;
return vk::Result{ func(instance, &pCI, &pA, pDebugMessenger) };
}
return vk::Result::eErrorExtensionNotPresent;
}
inline static void destroyDebugUtilsMessengerExt(
vk::Instance instance,
vk::DebugUtilsMessengerEXT& debugMessenger,
vk::Optional<const vk::AllocationCallbacks> pAllocator)
{
if (auto func = reinterpret_cast<PFN_vkDestroyDebugUtilsMessengerEXT>(
instance.getProcAddr("vkDestroyDebugUtilsMessengerEXT")))
{
if (pAllocator)
{
const VkAllocationCallbacks& pA = *pAllocator;
func(instance, debugMessenger, &pA);
}
else
{
func(instance, debugMessenger, nullptr);
}
}
}
#include <debug.hpp>
#include <shaders.hpp>
#include <vertex.hpp>
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#define GLM_FORCE_RADIANS
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#define STB_IMAGE_IMPLEMENTATION
#include <stb/stb_image.h>
#define TINYOBJLOADER_IMPLEMENTATION
#include <tiny_obj_loader.h>
#include <vulkan/vulkan.hpp>
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <unordered_map>
#include <unordered_set>
class App
{
public:
~App()
{
this->cleanup();
}
void run()
{
this->initWindow();
this->initVulkan();
this->mainLoop();
}
private: // Types
struct QueueFamilyIndices
{
std::optional<uint32_t> graphicsFamily;
std::optional<uint32_t> presentFamily;
bool isComplete() const
{
return this->graphicsFamily && this->presentFamily;
}
};
struct SwapChainSupportDetails
{
vk::SurfaceCapabilitiesKHR capabilities;
std::vector<vk::SurfaceFormatKHR> formats;
std::vector<vk::PresentModeKHR> presentModes;
};
private: // Main functions
void initWindow()
{
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
this->window = glfwCreateWindow(windowExtent.width, windowExtent.height, "Vulkan Tutorial", nullptr, nullptr);
glfwSetWindowUserPointer(this->window, this);
glfwSetFramebufferSizeCallback(this->window, framebufferResizeCallback);
}
void initVulkan()
{
this->createInstance();
this->setupDebugMessenger();
this->createSurface();
this->pickPhysicalDevice();
this->createLogicalDevice();
this->createSwapChain();
this->createImageViews();
this->createRenderPass();
this->createDescriptorSetLayout();
this->createGraphicsPipeline();
this->createCommandPool();
this->createColorResources();
this->createDepthResources();
this->createFramebuffers();
this->createTextureImage();
this->createTextureImageView();
this->createTextureSampler();
this->loadModel();
this->createVertexBuffer();
this->createIndexBuffer();
this->createUniformBuffers();
this->createDescriptorPool();
this->createDescriptorSets();
this->createCommandBuffers();
this->createSyncObjects();
}
void mainLoop()
{
while (!glfwWindowShouldClose(this->window))
{
glfwPollEvents();
this->drawFrame();
}
this->device.waitIdle();
}
void cleanup()
{
this->device.waitIdle();
this->destroyBuffer(this->indexBuffer, this->indexBufferMemory);
this->destroyBuffer(this->vertexBuffer, this->vertexBufferMemory);
this->cleanupSwapChain();
this->device.destroySampler(this->textureSampler);
this->destroyImage(this->textureImage, this->textureImageMemory, this->textureImageView);
this->device.destroyDescriptorSetLayout(this->descriptorSetLayout);
for (size_t i = 0; i < maxFramesInFlight; ++i)
{
this->device.destroySemaphore(this->renderFinishedSemaphores[i]);
this->device.destroySemaphore(this->imageAvailableSemaphores[i]);
this->device.destroyFence(this->inFlightFences[i]);
}
this->device.destroyCommandPool(this->commandPool);
this->device.destroy();
#ifdef VK_VALIDATION_LAYERS_ENABLED
destroyDebugUtilsMessengerExt(this->instance, this->debugMessenger, nullptr);
#endif
this->instance.destroySurfaceKHR(this->renderSurface);
this->instance.destroy();
glfwDestroyWindow(this->window);
glfwTerminate();
}
private: // Init, cleanup, and loop stages
void createInstance()
{
if (!this->checkValidationLayerSupport())
{
throw std::runtime_error("Requested validation layers are not available.");
}
const auto appInfo = vk::ApplicationInfo{}
.setPApplicationName("Vulkan Tutorial")
.setApplicationVersion(VK_MAKE_VERSION(1, 0, 0))
.setPEngineName("No Engine")
.setEngineVersion(VK_MAKE_VERSION(1, 0, 0))
.setApiVersion(VK_API_VERSION_1_0);
#ifdef VK_VALIDATION_LAYERS_ENABLED
vk::DebugUtilsMessengerCreateInfoEXT debugCreateInfo;
populateDebugMessengerCreateInfo(debugCreateInfo);
#endif
const std::vector<const char*> extensions = this->getRequiredExtensions();
this->instance = vk::createInstance(vk::InstanceCreateInfo{}
.setPApplicationInfo(&appInfo)
#ifdef VK_VALIDATION_LAYERS_ENABLED
.setPNext(&debugCreateInfo)
.setPpEnabledLayerNames(this->validationLayers.data())
.setEnabledLayerCount(this->validationLayers.size())
#else
.setEnabledLayerCount(0)
#endif
.setEnabledExtensionCount(extensions.size())
.setPpEnabledExtensionNames(extensions.data()));
}
void setupDebugMessenger()
{
#ifdef VK_VALIDATION_LAYERS_ENABLED
vk::DebugUtilsMessengerCreateInfoEXT createInfo;
populateDebugMessengerCreateInfo(createInfo);
VkDebugUtilsMessengerEXT messenger;
if (createDebugUtilsMessengerExt(this->instance, createInfo, nullptr, &messenger) != vk::Result::eSuccess)
{
throw std::runtime_error("Failed to set up debug messenger.");
}
this->debugMessenger = messenger;
#endif
}
void createSurface()
{
VkSurfaceKHR cSurface = this->renderSurface;
if (glfwCreateWindowSurface(this->instance, this->window, nullptr, &cSurface) != VK_SUCCESS)
{
throw std::runtime_error("Failed to create window surface.");
}
this->renderSurface = cSurface;
}
void pickPhysicalDevice()
{
std::vector<vk::PhysicalDevice> devices = this->instance.enumeratePhysicalDevices();
if (devices.empty())
{
throw std::runtime_error("Failed to find GPUs with Vulkan support.");
}
for (const vk::PhysicalDevice& device : devices)
{
if (isDeviceSuitable(device))
{
this->physicalDevice = device;
this->msaaSamples = this->getMaxSupportedSampleCount();
break;
}
}
if (!this->physicalDevice)
{
throw std::runtime_error("Failed to find a suitable GPU.");
}
}
void createLogicalDevice()
{
const QueueFamilyIndices indices = this->findQueueFamilies(this->physicalDevice);
std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos;
const std::unordered_set<uint32_t> uniqueQueueFamilies = {
*indices.graphicsFamily,
*indices.presentFamily,
};
const float queuePriority = 1.f;
for (const uint32_t queueFamily : uniqueQueueFamilies)
{
queueCreateInfos.push_back(vk::DeviceQueueCreateInfo{}
.setQueueFamilyIndex(queueFamily)
.setQueueCount(1)
.setPQueuePriorities(&queuePriority));
}
const auto deviceFeatures = vk::PhysicalDeviceFeatures{}
.setSamplerAnisotropy(VK_TRUE)
.setSampleRateShading(VK_TRUE);
this->device = this->physicalDevice.createDevice(vk::DeviceCreateInfo{}
#ifdef VK_VALIDATION_LAYERS_ENABLED
.setEnabledLayerCount(validationLayers.size())
.setPpEnabledLayerNames(validationLayers.data())
#else
.setEnabledLayerCount(0)
#endif
.setQueueCreateInfoCount(queueCreateInfos.size())
.setPQueueCreateInfos(queueCreateInfos.data())
.setPEnabledFeatures(&deviceFeatures)
.setEnabledExtensionCount(this->deviceExtensions.size())
.setPpEnabledExtensionNames(this->deviceExtensions.data()));
this->graphicsQueue = this->device.getQueue(*indices.graphicsFamily, 0);
this->presentQueue = this->device.getQueue(*indices.presentFamily, 0);
}
void createSwapChain()
{
const SwapChainSupportDetails swapChainSupport = this->querySwapChainSupport(this->physicalDevice);
const vk::SurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
const vk::PresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
const vk::Extent2D extent = this->chooseSwapExtent(swapChainSupport.capabilities);
const uint32_t imageCount = swapChainSupport.capabilities.maxImageCount == 0
? swapChainSupport.capabilities.minImageCount + 1
: std::clamp(swapChainSupport.capabilities.minImageCount + 1, 0u, swapChainSupport.capabilities.maxImageCount);
auto swapchainInfo = vk::SwapchainCreateInfoKHR{}
.setSurface(this->renderSurface)
.setMinImageCount(imageCount)
.setImageFormat(surfaceFormat.format)
.setImageColorSpace(surfaceFormat.colorSpace)
.setImageExtent(extent)
.setImageArrayLayers(1)
.setImageUsage(vk::ImageUsageFlagBits::eColorAttachment)
.setPreTransform(swapChainSupport.capabilities.currentTransform)
.setCompositeAlpha(vk::CompositeAlphaFlagBitsKHR::eOpaque)
.setPresentMode(presentMode)
.setClipped(VK_TRUE)
.setOldSwapchain(nullptr);
const QueueFamilyIndices indices = this->findQueueFamilies(this->physicalDevice);
const std::vector<uint32_t> queueFamilyIndices = { *indices.graphicsFamily, *indices.presentFamily };
if (indices.graphicsFamily != indices.presentFamily)
{
swapchainInfo
.setImageSharingMode(vk::SharingMode::eConcurrent)
.setQueueFamilyIndexCount(queueFamilyIndices.size())
.setPQueueFamilyIndices(queueFamilyIndices.data());
}
else
{
swapchainInfo.setImageSharingMode(vk::SharingMode::eExclusive);
}
this->swapChain = device.createSwapchainKHR(swapchainInfo);
this->swapChainImages = device.getSwapchainImagesKHR(this->swapChain);
this->swapChainImageFormat = surfaceFormat.format;
this->swapChainExtent = extent;
}
void createImageViews()
{
this->swapChainImageViews.resize(this->swapChainImages.size());
for (size_t i = 0; i < this->swapChainImages.size(); ++i)
{
this->swapChainImageViews[i] = this->createImageView(this->swapChainImages[i], this->swapChainImageFormat,
vk::ImageAspectFlagBits::eColor, 1);
}
}
void createRenderPass()
{
const auto colorAttachment = vk::AttachmentDescription{}
.setFormat(this->swapChainImageFormat)
.setSamples(this->msaaSamples)
.setLoadOp(vk::AttachmentLoadOp::eClear)
.setStoreOp(vk::AttachmentStoreOp::eStore)
.setStencilLoadOp(vk::AttachmentLoadOp::eDontCare)
.setStencilStoreOp(vk::AttachmentStoreOp::eDontCare)
.setInitialLayout(vk::ImageLayout::eUndefined)
.setFinalLayout(vk::ImageLayout::eColorAttachmentOptimal);
const auto depthAttachment = vk::AttachmentDescription{}
.setFormat(this->findDepthFormat())
.setSamples(this->msaaSamples)
.setLoadOp(vk::AttachmentLoadOp::eClear)
.setStoreOp(vk::AttachmentStoreOp::eDontCare)
.setStencilLoadOp(vk::AttachmentLoadOp::eDontCare)
.setStencilStoreOp(vk::AttachmentStoreOp::eDontCare)
.setInitialLayout(vk::ImageLayout::eUndefined)
.setFinalLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal);
const auto colorAttachmentResolve = vk::AttachmentDescription{}
.setFormat(this->swapChainImageFormat)
.setSamples(vk::SampleCountFlagBits::e1)
.setLoadOp(vk::AttachmentLoadOp::eDontCare)
.setStoreOp(vk::AttachmentStoreOp::eStore)
.setStencilLoadOp(vk::AttachmentLoadOp::eDontCare)
.setStencilStoreOp(vk::AttachmentStoreOp::eDontCare)
.setInitialLayout(vk::ImageLayout::eUndefined)
.setFinalLayout(vk::ImageLayout::ePresentSrcKHR);
const auto colorAttachmentRef = vk::AttachmentReference{}
.setAttachment(0)
.setLayout(vk::ImageLayout::eColorAttachmentOptimal);
const auto depthAttachmentRef = vk::AttachmentReference{}
.setAttachment(1)
.setLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal);
const auto colorAttachmentResolveRef = vk::AttachmentReference{}
.setAttachment(2)
.setLayout(vk::ImageLayout::eColorAttachmentOptimal);
const auto subpass = vk::SubpassDescription{}
.setPipelineBindPoint(vk::PipelineBindPoint::eGraphics)
.setColorAttachmentCount(1)
.setPColorAttachments(&colorAttachmentRef)
.setPDepthStencilAttachment(&depthAttachmentRef)
.setPResolveAttachments(&colorAttachmentResolveRef);
const auto dependency = vk::SubpassDependency{}
.setSrcSubpass(VK_SUBPASS_EXTERNAL)
.setSrcStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput)
.setSrcAccessMask(vk::AccessFlagBits{ 0 })
.setDstSubpass(0)
.setDstStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput)
.setDstAccessMask(vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite);
const std::vector<vk::AttachmentDescription> attachments = {
colorAttachment,
depthAttachment,
colorAttachmentResolve,
};
this->renderPass = this->device.createRenderPass(vk::RenderPassCreateInfo{}
.setAttachmentCount(attachments.size())
.setPAttachments(attachments.data())
.setSubpassCount(1)
.setPSubpasses(&subpass)
.setDependencyCount(1)
.setPDependencies(&dependency));
}
void createDescriptorSetLayout()
{
const auto uboLayoutBinding = vk::DescriptorSetLayoutBinding{}
.setBinding(0)
.setDescriptorType(vk::DescriptorType::eUniformBuffer)
.setDescriptorCount(1)
.setStageFlags(vk::ShaderStageFlagBits::eVertex);
const auto samplerLayoutBinding = vk::DescriptorSetLayoutBinding{}
.setBinding(1)
.setDescriptorCount(1)
.setDescriptorType(vk::DescriptorType::eCombinedImageSampler)
.setPImmutableSamplers(nullptr)
.setStageFlags(vk::ShaderStageFlagBits::eFragment);
const std::vector<vk::DescriptorSetLayoutBinding> bindings = {
uboLayoutBinding,
samplerLayoutBinding,
};
this->descriptorSetLayout = this->device.createDescriptorSetLayout(vk::DescriptorSetLayoutCreateInfo{}
.setBindingCount(bindings.size())
.setPBindings(bindings.data()));
}
void createGraphicsPipeline()
{
const std::vector<char> vertexShaderCode = readFile("shaders/vert.spv");
const std::vector<char> fragmentShaderCode = readFile("shaders/frag.spv");
vk::ShaderModule vertexShaderModule = this->createShaderModule(vertexShaderCode);
vk::ShaderModule fragmentShaderModule = this->createShaderModule(fragmentShaderCode);
const auto vertexShaderStageInfo = vk::PipelineShaderStageCreateInfo{}
.setStage(vk::ShaderStageFlagBits::eVertex)
.setModule(vertexShaderModule)
.setPName("main");
const auto fragmentShaderStageInfo = vk::PipelineShaderStageCreateInfo{}
.setStage(vk::ShaderStageFlagBits::eFragment)
.setModule(fragmentShaderModule)
.setPName("main");
const std::vector<vk::PipelineShaderStageCreateInfo> shaderStages = {
vertexShaderStageInfo,
fragmentShaderStageInfo,
};
const auto bindingDescription = Vertex::getBindingDescription();
const auto attributeDescriptions = Vertex::getAttributeDescriptions();
const auto vertexInputInfo = vk::PipelineVertexInputStateCreateInfo{}
.setVertexBindingDescriptionCount(1)
.setPVertexBindingDescriptions(&bindingDescription)
.setVertexAttributeDescriptionCount(attributeDescriptions.size())
.setPVertexAttributeDescriptions(attributeDescriptions.data());
const auto inputAssembly = vk::PipelineInputAssemblyStateCreateInfo{}
.setTopology(vk::PrimitiveTopology::eTriangleList)
.setPrimitiveRestartEnable(VK_FALSE);
const auto viewport = vk::Viewport{}
.setX(0.f)
.setY(0.f)
.setWidth(static_cast<float>(swapChainExtent.width))
.setHeight(static_cast<float>(swapChainExtent.height))
.setMinDepth(0.f)
.setMaxDepth(1.f);
const vk::Rect2D scissor = { {0, 0}, this->swapChainExtent };
const auto viewportState = vk::PipelineViewportStateCreateInfo{}
.setViewportCount(1)
.setPViewports(&viewport)
.setScissorCount(1)
.setPScissors(&scissor);
const auto rasterizer = vk::PipelineRasterizationStateCreateInfo{}
.setDepthClampEnable(VK_FALSE)
.setRasterizerDiscardEnable(VK_FALSE)
.setPolygonMode(vk::PolygonMode::eFill)
.setLineWidth(1.f)
.setCullMode(vk::CullModeFlagBits::eBack)
.setFrontFace(vk::FrontFace::eCounterClockwise)
.setDepthBiasEnable(VK_FALSE);
const auto multisampling = vk::PipelineMultisampleStateCreateInfo{}
.setSampleShadingEnable(VK_FALSE)
.setRasterizationSamples(this->msaaSamples)
.setSampleShadingEnable(VK_TRUE)
.setMinSampleShading(0.2f);
using ColorComponent = vk::ColorComponentFlagBits;
const auto colorBlendAttachment = vk::PipelineColorBlendAttachmentState{}
.setColorWriteMask(ColorComponent::eR | ColorComponent::eG | ColorComponent::eB | ColorComponent::eA)
.setBlendEnable(VK_TRUE)
.setSrcColorBlendFactor(vk::BlendFactor::eSrcAlpha)
.setDstColorBlendFactor(vk::BlendFactor::eOneMinusSrcAlpha)
.setColorBlendOp(vk::BlendOp::eAdd)
.setSrcAlphaBlendFactor(vk::BlendFactor::eOne)
.setDstAlphaBlendFactor(vk::BlendFactor::eZero)
.setAlphaBlendOp(vk::BlendOp::eAdd);
const auto colorBlending = vk::PipelineColorBlendStateCreateInfo{}
.setLogicOpEnable(VK_FALSE)
.setLogicOp(vk::LogicOp::eCopy)
.setAttachmentCount(1)
.setPAttachments(&colorBlendAttachment);
const std::vector<vk::DynamicState> dynamicStates = {
vk::DynamicState::eViewport,
vk::DynamicState::eLineWidth,
};
const auto dynamicState = vk::PipelineDynamicStateCreateInfo{}
.setDynamicStateCount(dynamicStates.size())
.setPDynamicStates(dynamicStates.data());
this->pipelineLayout = this->device.createPipelineLayout(vk::PipelineLayoutCreateInfo{}
.setSetLayoutCount(1)
.setPSetLayouts(&this->descriptorSetLayout));
const auto depthStencil = vk::PipelineDepthStencilStateCreateInfo{}
.setDepthTestEnable(VK_TRUE)
.setDepthWriteEnable(VK_TRUE)
.setDepthCompareOp(vk::CompareOp::eLess)
.setDepthBoundsTestEnable(VK_FALSE)
.setStencilTestEnable(VK_FALSE)
.setMinDepthBounds(0.f)
.setMaxDepthBounds(1.f);
this->graphicsPipeline = this->device.createGraphicsPipeline(nullptr, vk::GraphicsPipelineCreateInfo{}
.setStageCount(shaderStages.size())
.setPStages(shaderStages.data())
.setPVertexInputState(&vertexInputInfo)
.setPInputAssemblyState(&inputAssembly)
.setPViewportState(&viewportState)
.setPRasterizationState(&rasterizer)
.setPMultisampleState(&multisampling)
.setPColorBlendState(&colorBlending)
.setPDepthStencilState(&depthStencil)
.setLayout(this->pipelineLayout)
.setRenderPass(this->renderPass)
.setSubpass(0));
this->device.destroyShaderModule(vertexShaderModule);
this->device.destroyShaderModule(fragmentShaderModule);
}
void createFramebuffers()
{
this->swapChainFramebuffers.resize(this->swapChainImageViews.size());
for (size_t i = 0; i < swapChainImageViews.size(); ++i)
{
const std::vector<vk::ImageView> attachments = {
this->colorImageView,
this->depthImageView,
this->swapChainImageViews[i],
};
this->swapChainFramebuffers[i] = this->device.createFramebuffer(vk::FramebufferCreateInfo{}
.setRenderPass(this->renderPass)
.setAttachmentCount(attachments.size())
.setPAttachments(attachments.data())
.setWidth(this->swapChainExtent.width)
.setHeight(this->swapChainExtent.height)
.setLayers(1));
}
}
void createCommandPool()
{
const QueueFamilyIndices queueFamilyIndices = this->findQueueFamilies(this->physicalDevice);
this->commandPool = this->device.createCommandPool(vk::CommandPoolCreateInfo{}.setQueueFamilyIndex(*queueFamilyIndices.graphicsFamily));
}
void createColorResources()
{
const vk::Format colorFormat = this->swapChainImageFormat;
this->createImage(
{ this->swapChainExtent.width, this->swapChainExtent.height, 1 },
1,
this->msaaSamples,
colorFormat,
vk::ImageTiling::eOptimal,
vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment,
vk::MemoryPropertyFlagBits::eDeviceLocal,
this->colorImage,
this->colorImageMemory);
this->colorImageView = this->createImageView(this->colorImage, colorFormat, vk::ImageAspectFlagBits::eColor, 1);
this->transitionImageLayout(this->colorImage, colorFormat, vk::ImageLayout::eUndefined, vk::ImageLayout::eColorAttachmentOptimal, 1);
}
void createDepthResources()
{
const vk::Format depthFormat = this->findDepthFormat();
this->createImage(
{ swapChainExtent.width, swapChainExtent.height, 1 },
1,
this->msaaSamples,
depthFormat,
vk::ImageTiling::eOptimal,
vk::ImageUsageFlagBits::eDepthStencilAttachment,
vk::MemoryPropertyFlagBits::eDeviceLocal,
this->depthImage,
this->depthImageMemory);
this->depthImageView = this->createImageView(this->depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth, 1);
this->transitionImageLayout(this->depthImage, depthFormat, vk::ImageLayout::eUndefined,
vk::ImageLayout::eDepthStencilAttachmentOptimal, 1);
}
void createTextureImage()
{
int32_t textureWidth;
int32_t textureHeight;
int32_t textureChannels;
stbi_uc* pixels = stbi_load(texturePath, &textureWidth, &textureHeight, &textureChannels, STBI_rgb_alpha);
const vk::DeviceSize imageSize = 4ull * textureWidth * textureHeight;
this->mipLevels = static_cast<uint32_t>(std::floor(std::log2(std::max(textureWidth, textureHeight)))) + 1;
if (!pixels)
{
throw std::runtime_error("Failed to load texture image.");
}
vk::Buffer stagingBuffer;
vk::DeviceMemory stagingBufferMemory;
this->createBuffer(imageSize, stagingBufferUsage, stagingBufferMemoryProperties, stagingBuffer, stagingBufferMemory);
void* data = this->device.mapMemory(stagingBufferMemory, 0, imageSize);
std::memcpy(data, pixels, imageSize);
this->device.unmapMemory(stagingBufferMemory);
stbi_image_free(pixels);
const auto imageExtent = vk::Extent3D{}
.setWidth(textureWidth)
.setHeight(textureHeight)
.setDepth(1);
this->createImage(
imageExtent,
this->mipLevels,
vk::SampleCountFlagBits::e1,
vk::Format::eR8G8B8A8Unorm,
vk::ImageTiling::eOptimal,
vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled,
vk::MemoryPropertyFlagBits::eDeviceLocal,
this->textureImage,
this->textureImageMemory);
this->transitionImageLayout(
this->textureImage,
vk::Format::eR8G8B8A8Unorm,
vk::ImageLayout::eUndefined,
vk::ImageLayout::eTransferDstOptimal,
this->mipLevels);
this->copyBufferToImage(stagingBuffer, this->textureImage, imageExtent);
this->destroyBuffer(stagingBuffer, stagingBufferMemory);
this->generateMipmaps(this->textureImage, vk::Format::eR8G8B8A8Unorm, { imageExtent.width, imageExtent.height }, this->mipLevels);
}
void createTextureImageView()
{
this->textureImageView = this->createImageView(this->textureImage, vk::Format::eR8G8B8A8Unorm, vk::ImageAspectFlagBits::eColor, this->mipLevels);
}
void createTextureSampler()
{
this->textureSampler = this->device.createSampler(vk::SamplerCreateInfo{}
.setMagFilter(vk::Filter::eLinear)
.setMinFilter(vk::Filter::eLinear)
.setAddressModeU(vk::SamplerAddressMode::eRepeat)
.setAddressModeV(vk::SamplerAddressMode::eRepeat)
.setAddressModeW(vk::SamplerAddressMode::eRepeat)
.setAnisotropyEnable(VK_TRUE)
.setMaxAnisotropy(16)
.setBorderColor(vk::BorderColor::eIntOpaqueBlack)
.setUnnormalizedCoordinates(VK_FALSE)
.setCompareEnable(VK_FALSE)
.setCompareOp(vk::CompareOp::eAlways)
.setMipmapMode(vk::SamplerMipmapMode::eLinear)
.setMipLodBias(0.f)
.setMinLod(0.f)
.setMaxLod(static_cast<float>(this->mipLevels)));
}
void loadModel()
{
tinyobj::attrib_t attributes;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string warning, error;
if (!tinyobj::LoadObj(&attributes, &shapes, &materials, &warning, &error, modelPath))
{
throw std::runtime_error(warning + error);
}
std::unordered_map<Vertex, uint32_t> uniqueVertices;
for (const tinyobj::shape_t& shape : shapes)
{
for (const tinyobj::index_t& index : shape.mesh.indices)
{
const Vertex vertex = {
{ // position
attributes.vertices[3 * index.vertex_index + 0],
attributes.vertices[3 * index.vertex_index + 1],
attributes.vertices[3 * index.vertex_index + 2],
},
{ 1.f, 1.f, 1.f }, // color
{ // texCoord
attributes.texcoords[2 * index.texcoord_index + 0],
1.f - attributes.texcoords[2 * index.texcoord_index + 1],
}
};
if (uniqueVertices.count(vertex) == 0)
{
uniqueVertices[vertex] = static_cast<uint32_t>(vertices.size());
this->vertices.push_back(vertex);
}
this->indices.push_back(uniqueVertices[vertex]);
}
}
std::cout << "Vertex count: " << this->vertices.size() << std::endl;
}
void createVertexBuffer()
{
const vk::DeviceSize bufferSize = sizeof(this->vertices[0]) * this->vertices.size();
vk::Buffer stagingBuffer;
vk::DeviceMemory stagingBufferMemory;
this->createBuffer(bufferSize, stagingBufferUsage, stagingBufferMemoryProperties, stagingBuffer, stagingBufferMemory);
void* data = this->device.mapMemory(stagingBufferMemory, 0, bufferSize);
std::memcpy(data, this->vertices.data(), bufferSize);
this->device.unmapMemory(stagingBufferMemory);
const vk::BufferUsageFlags vertexBufferUsage = vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer;
const vk::MemoryPropertyFlags vertexBufferMemoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal;
this->createBuffer(bufferSize, vertexBufferUsage, vertexBufferMemoryProperties, this->vertexBuffer, this->vertexBufferMemory);
this->copyBuffer(stagingBuffer, this->vertexBuffer, bufferSize);
this->destroyBuffer(stagingBuffer, stagingBufferMemory);
}
void createIndexBuffer()
{
const vk::DeviceSize bufferSize = this->indices.size() * sizeof(this->indices[0]);
vk::Buffer stagingBuffer;
vk::DeviceMemory stagingBufferMemory;
this->createBuffer(bufferSize, stagingBufferUsage, stagingBufferMemoryProperties,
stagingBuffer, stagingBufferMemory);
void* data = this->device.mapMemory(stagingBufferMemory, 0, bufferSize);
std::memcpy(data, this->indices.data(), bufferSize);
this->device.unmapMemory(stagingBufferMemory);
const vk::BufferUsageFlags indexBufferUsage =
vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer;
const vk::MemoryPropertyFlags indexBufferMemoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal;
this->createBuffer(bufferSize, indexBufferUsage, indexBufferMemoryProperties,
this->indexBuffer, this->indexBufferMemory);
this->copyBuffer(stagingBuffer, this->indexBuffer, bufferSize);
this->destroyBuffer(stagingBuffer, stagingBufferMemory);
}
void createUniformBuffers()
{
const vk::DeviceSize bufferSize = sizeof(UniformBufferObject);
this->uniformBuffers.resize(this->swapChainImages.size());
this->uniformBuffersMemory.resize(this->swapChainImages.size());
for (size_t i = 0; i < this->swapChainImages.size(); ++i)
{
this->createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer,
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent,
this->uniformBuffers[i], this->uniformBuffersMemory[i]);
}
}
void createDescriptorPool()
{
const std::vector<vk::DescriptorPoolSize> poolSizes = {
vk::DescriptorPoolSize{}
.setType(vk::DescriptorType::eUniformBuffer)
.setDescriptorCount(this->swapChainImages.size()),
vk::DescriptorPoolSize{}
.setType(vk::DescriptorType::eCombinedImageSampler)
.setDescriptorCount(this->swapChainImages.size()),
};
this->descriptorPool = this->device.createDescriptorPool(vk::DescriptorPoolCreateInfo{}
.setPoolSizeCount(poolSizes.size())
.setPPoolSizes(poolSizes.data())
.setMaxSets(this->swapChainImages.size()));
}
void createDescriptorSets()
{
const std::vector<vk::DescriptorSetLayout> layouts(this->swapChainImages.size(), this->descriptorSetLayout);
this->descriptorSets = this->device.allocateDescriptorSets(vk::DescriptorSetAllocateInfo{}
.setDescriptorPool(this->descriptorPool)
.setDescriptorSetCount(this->swapChainImages.size())
.setPSetLayouts(layouts.data()));
for (size_t i = 0; i < this->swapChainImages.size(); ++i)
{
const auto bufferInfo = vk::DescriptorBufferInfo{}
.setBuffer(this->uniformBuffers[i])
.setOffset(0)
.setRange(sizeof(UniformBufferObject));
const auto imageInfo = vk::DescriptorImageInfo{}
.setImageLayout(vk::ImageLayout::eShaderReadOnlyOptimal)
.setImageView(this->textureImageView)
.setSampler(this->textureSampler);
const std::vector<vk::WriteDescriptorSet> descriptorWrites = {
vk::WriteDescriptorSet{}
.setDstSet(this->descriptorSets[i])
.setDstBinding(0)
.setDstArrayElement(0)
.setDescriptorType(vk::DescriptorType::eUniformBuffer)
.setDescriptorCount(1)
.setPBufferInfo(&bufferInfo),
vk::WriteDescriptorSet{}
.setDstSet(this->descriptorSets[i])
.setDstBinding(1)
.setDstArrayElement(0)
.setDescriptorType(vk::DescriptorType::eCombinedImageSampler)
.setDescriptorCount(1)
.setPImageInfo(&imageInfo),
};
this->device.updateDescriptorSets(descriptorWrites, {});
}
}
void createCommandBuffers()
{
this->commandBuffers = this->device.allocateCommandBuffers(vk::CommandBufferAllocateInfo{}
.setCommandPool(this->commandPool)
.setLevel(vk::CommandBufferLevel::ePrimary)
.setCommandBufferCount(this->swapChainFramebuffers.size()));
for (size_t i = 0; i < this->commandBuffers.size(); ++i)
{
vk::CommandBuffer& commandBuffer = this->commandBuffers[i];
commandBuffer.begin(vk::CommandBufferBeginInfo{});
const std::vector<vk::ClearValue> clearValues = {
{ vk::ClearColorValue{}.setFloat32({0.f, 0.f, 0.f, 1.f}) },
{ vk::ClearDepthStencilValue{}.setDepth(1.f).setStencil(0) },
};
commandBuffer.beginRenderPass(vk::RenderPassBeginInfo{}
.setRenderPass(this->renderPass)
.setFramebuffer(this->swapChainFramebuffers[i])
.setRenderArea({ {0, 0}, this->swapChainExtent })
.setClearValueCount(clearValues.size())
.setPClearValues(clearValues.data()), vk::SubpassContents::eInline);
commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, this->graphicsPipeline);
const std::vector<vk::Buffer> vertexBuffers = {
this->vertexBuffer,
};
const std::vector<vk::DeviceSize> offsets = {
0,
};
commandBuffer.bindVertexBuffers(0, vertexBuffers, offsets);
commandBuffer.bindIndexBuffer(this->indexBuffer, 0, vk::IndexType::eUint32);
commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, this->pipelineLayout,
0, 1, &this->descriptorSets[i], 0, nullptr);
commandBuffer.drawIndexed(this->indices.size(), 1, 0, 0, 0);
commandBuffer.endRenderPass();
commandBuffer.end();
}
}
void createSyncObjects()
{
this->imageAvailableSemaphores.resize(maxFramesInFlight);
this->renderFinishedSemaphores.resize(maxFramesInFlight);
this->inFlightFences.resize(maxFramesInFlight);
const auto semaphoreInfo = vk::SemaphoreCreateInfo{};
const auto fenceInfo = vk::FenceCreateInfo{}.setFlags(vk::FenceCreateFlagBits::eSignaled);
for (size_t i = 0; i < maxFramesInFlight; ++i)
{
this->imageAvailableSemaphores[i] = this->device.createSemaphore(semaphoreInfo);
this->renderFinishedSemaphores[i] = this->device.createSemaphore(semaphoreInfo);
this->inFlightFences[i] = this->device.createFence(fenceInfo);
}
}
void cleanupSwapChain()
{
this->destroyImage(this->colorImage, this->colorImageMemory, this->colorImageView);
this->destroyImage(this->depthImage, this->depthImageMemory, this->depthImageView);
for (vk::Framebuffer& framebuffer : this->swapChainFramebuffers)
{
this->device.destroyFramebuffer(framebuffer);
}
this->device.freeCommandBuffers(this->commandPool, this->commandBuffers);
this->device.destroyPipeline(this->graphicsPipeline);
this->device.destroyPipelineLayout(this->pipelineLayout);
this->device.destroyRenderPass(this->renderPass);
for (vk::ImageView& imageView : this->swapChainImageViews)
{
this->device.destroyImageView(imageView);
}
this->device.destroySwapchainKHR(this->swapChain);
for (size_t i = 0; i < this->swapChainImages.size(); ++i)
{
this->destroyBuffer(this->uniformBuffers[i], this->uniformBuffersMemory[i]);
}
this->device.destroyDescriptorPool(this->descriptorPool);
}
void drawFrame()
{
this->device.waitForFences(this->inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
this->device.resetFences(this->inFlightFences[currentFrame]);
auto [result, imageIndex] = this->device.acquireNextImageKHR(this->swapChain, UINT64_MAX,
this->imageAvailableSemaphores[this->currentFrame], nullptr);
if (result == vk::Result::eErrorOutOfDateKHR)
{
recreateSwapChain();
return;
}
this->updateUniformBuffer(imageIndex);
const std::vector<vk::Semaphore> waitSemaphores = {
this->imageAvailableSemaphores[this->currentFrame],
};
const std::vector<vk::Semaphore> signalSemaphores = {
this->renderFinishedSemaphores[this->currentFrame],
};
const std::vector<vk::PipelineStageFlags> waitStages = {
vk::PipelineStageFlagBits::eColorAttachmentOutput,
};
this->device.resetFences(this->inFlightFences[currentFrame]);
this->graphicsQueue.submit(vk::SubmitInfo{}
.setWaitSemaphoreCount(waitSemaphores.size())
.setPWaitSemaphores(waitSemaphores.data())
.setSignalSemaphoreCount(signalSemaphores.size())
.setPSignalSemaphores(signalSemaphores.data())
.setPWaitDstStageMask(waitStages.data())
.setCommandBufferCount(1)
.setPCommandBuffers(&this->commandBuffers[imageIndex]), this->inFlightFences[this->currentFrame]);
try
{
const std::vector<vk::SwapchainKHR> swapChains = {
this->swapChain,
};
const auto presentInfo = vk::PresentInfoKHR{}
.setWaitSemaphoreCount(signalSemaphores.size())
.setPWaitSemaphores(signalSemaphores.data())
.setSwapchainCount(swapChains.size())
.setPSwapchains(swapChains.data())
.setPImageIndices(&imageIndex);
if (this->presentQueue.presentKHR(presentInfo) == vk::Result::eSuboptimalKHR || framebufferResized)
{
framebufferResized = false;
recreateSwapChain();
}
}
catch (vk::OutOfDateKHRError e)
{
framebufferResized = false;
recreateSwapChain();
}
this->currentFrame = (this->currentFrame + 1) % maxFramesInFlight;
}
private: // Helper functions
static void framebufferResizeCallback(GLFWwindow* window, int width, int height)
{
auto app = reinterpret_cast<App*>(glfwGetWindowUserPointer(window));
app->framebufferResized = true;
}
bool checkValidationLayerSupport()
{
#ifdef VK_VALIDATION_LAYERS_ENABLED
const std::vector<vk::LayerProperties> availableLayers = vk::enumerateInstanceLayerProperties();
return std::all_of(this->validationLayers.begin(), this->validationLayers.end(),
[&](const std::string& name)
{
const auto result = std::find_if(availableLayers.begin(), availableLayers.end(),
[&](const vk::LayerProperties& properties)
{
return name == properties.layerName;
});
return result != availableLayers.end();
});
#else
return true;
#endif
}
std::vector<const char*> getRequiredExtensions()
{
uint32_t glfwExtensionCount;
const char* const* glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
std::vector<const char*> extensions = { glfwExtensions, glfwExtensions + glfwExtensionCount };
#ifdef VK_VALIDATION_LAYERS_ENABLED
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
#endif
const std::vector<vk::ExtensionProperties> availableExtensions = vk::enumerateInstanceExtensionProperties();
const bool allSupported = std::all_of(extensions.begin(), extensions.end(),
[&](const std::string& name)
{
const auto result = std::find_if(availableExtensions.begin(), availableExtensions.end(),
[&](const vk::ExtensionProperties& properties)
{
return name == properties.extensionName;
});
return result != availableExtensions.end();
});
if (!allSupported)
{
throw std::runtime_error("Requested extensions are not supported.");
}
return extensions;
}
static void populateDebugMessengerCreateInfo(vk::DebugUtilsMessengerCreateInfoEXT& createInfo)
{
using MsgSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT;
using MsgType = vk::DebugUtilsMessageTypeFlagBitsEXT;
createInfo
.setMessageSeverity(MsgSeverity::eVerbose | MsgSeverity::eWarning | MsgSeverity::eError)
.setMessageType(MsgType::eGeneral | MsgType::eValidation | MsgType::ePerformance)
.setPfnUserCallback(debugCallback);
}
bool isDeviceSuitable(const vk::PhysicalDevice& device) const
{
if (checkDeviceExtensionSupport(device))
{
const SwapChainSupportDetails swapChainSupport = this->querySwapChainSupport(device);
const vk::PhysicalDeviceFeatures featureSupport = device.getFeatures();
return this->findQueueFamilies(device).isComplete()
&& !swapChainSupport.formats.empty()
&& !swapChainSupport.presentModes.empty()
&& featureSupport.samplerAnisotropy;
}
return false;
}
vk::SampleCountFlagBits getMaxSupportedSampleCount() const
{
vk::PhysicalDeviceProperties properties = this->physicalDevice.getProperties();
const auto counts = static_cast<vk::SampleCountFlags>(std::min(
static_cast<uint32_t>(properties.limits.framebufferColorSampleCounts),
static_cast<uint32_t>(properties.limits.framebufferDepthSampleCounts)));
if (counts & vk::SampleCountFlagBits::e64) { return vk::SampleCountFlagBits::e64; }
if (counts & vk::SampleCountFlagBits::e32) { return vk::SampleCountFlagBits::e32; }
if (counts & vk::SampleCountFlagBits::e16) { return vk::SampleCountFlagBits::e16; }
if (counts & vk::SampleCountFlagBits::e8) { return vk::SampleCountFlagBits::e8; }
if (counts & vk::SampleCountFlagBits::e4) { return vk::SampleCountFlagBits::e4; }
if (counts & vk::SampleCountFlagBits::e2) { return vk::SampleCountFlagBits::e2; }
return vk::SampleCountFlagBits::e1;
}
bool checkDeviceExtensionSupport(const vk::PhysicalDevice& device) const
{
const std::vector<vk::ExtensionProperties> availableExtensions = device.enumerateDeviceExtensionProperties();
std::unordered_set<std::string> missingExtensions{ this->deviceExtensions.begin(), this->deviceExtensions.end() };
for (const vk::ExtensionProperties& extension : availableExtensions)
{
missingExtensions.erase(extension.extensionName);
}
return missingExtensions.empty();
}
SwapChainSupportDetails querySwapChainSupport(const vk::PhysicalDevice& device) const
{
return {
device.getSurfaceCapabilitiesKHR(this->renderSurface),
device.getSurfaceFormatsKHR(this->renderSurface),
device.getSurfacePresentModesKHR(this->renderSurface)
};
}
QueueFamilyIndices findQueueFamilies(const vk::PhysicalDevice& device) const
{
QueueFamilyIndices indices;
const std::vector<vk::QueueFamilyProperties> queueFamilies = device.getQueueFamilyProperties();
int32_t i = 0;
for (const vk::QueueFamilyProperties& queueFamily : queueFamilies)
{
if (queueFamily.queueCount > 0)
{
if (queueFamily.queueFlags & vk::QueueFlagBits::eGraphics)
{
indices.graphicsFamily = i;
}
if (device.getSurfaceSupportKHR(i, this->renderSurface))
{
indices.presentFamily = i;
}
}
if (indices.isComplete())
{
break;
}
++i;
}
return indices;
}
static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<vk::SurfaceFormatKHR>& availableFormats)
{
const auto chosenFormat = std::find_if(availableFormats.begin(), availableFormats.end(),
[](const vk::SurfaceFormatKHR& format)
{
return format.format == vk::Format::eB8G8R8A8Unorm
&& format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear;
});
return chosenFormat != availableFormats.end() ? *chosenFormat : availableFormats[0];
}
static vk::PresentModeKHR chooseSwapPresentMode(const std::vector<vk::PresentModeKHR>& availablePresentModes)
{
const auto chosenMode = std::find_if(availablePresentModes.begin(), availablePresentModes.end(),
[](const vk::PresentModeKHR& mode)
{
return mode == vk::PresentModeKHR::eMailbox;
});
return chosenMode != availablePresentModes.end() ? *chosenMode : vk::PresentModeKHR::eFifo;
}
vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) const
{
if (capabilities.currentExtent.width != UINT32_MAX)
{
return capabilities.currentExtent;
}
int32_t width;
int32_t height;
glfwGetFramebufferSize(this->window, &width, &height);
const vk::Extent2D actualExtent = {
static_cast<uint32_t>(width),
static_cast<uint32_t>(height),
};
return {
std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width),
std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height)
};
}
vk::ShaderModule createShaderModule(const std::vector<char>& code)
{
return this->device.createShaderModule(vk::ShaderModuleCreateInfo{}
.setCodeSize(code.size())
.setPCode(reinterpret_cast<const uint32_t*>(code.data())));
}
vk::CommandBuffer beginSingleTimeCommands()
{
vk::CommandBuffer commandBuffer = this->device.allocateCommandBuffers(vk::CommandBufferAllocateInfo{}
.setLevel(vk::CommandBufferLevel::ePrimary)
.setCommandPool(this->commandPool)
.setCommandBufferCount(1)).front();
commandBuffer.begin(vk::CommandBufferBeginInfo{}.setFlags(vk::CommandBufferUsageFlagBits::eOneTimeSubmit));
return commandBuffer;
}
void endSingleTimeCommands(vk::CommandBuffer& commandBuffer)
{
commandBuffer.end();
this->graphicsQueue.submit(vk::SubmitInfo{}
.setCommandBufferCount(1)
.setPCommandBuffers(&commandBuffer), nullptr);
this->graphicsQueue.waitIdle();
this->device.freeCommandBuffers(this->commandPool, commandBuffer);
}
void createImage(
const vk::Extent3D size,
const uint32_t mipLevels,
const vk::SampleCountFlagBits numSamples,
const vk::Format format,
const vk::ImageTiling tiling,
const vk::ImageUsageFlags usage,
const vk::MemoryPropertyFlags properties,
vk::Image& image,
vk::DeviceMemory& imageMemory
) {
image = this->device.createImage(vk::ImageCreateInfo{}
.setImageType(vk::ImageType::e2D)
.setExtent(size)
.setMipLevels(mipLevels)
.setArrayLayers(1)
.setFormat(format)
.setTiling(tiling)
.setInitialLayout(vk::ImageLayout::eUndefined)
.setUsage(usage)
.setSharingMode(vk::SharingMode::eExclusive)
.setSamples(numSamples));
const vk::MemoryRequirements memoryRequirements = this->device.getImageMemoryRequirements(image);
imageMemory = this->device.allocateMemory(vk::MemoryAllocateInfo{}
.setAllocationSize(memoryRequirements.size)
.setMemoryTypeIndex(this->findMemoryType(memoryRequirements.memoryTypeBits, properties)));
this->device.bindImageMemory(image, imageMemory, 0);
}
vk::ImageView createImageView(
const vk::Image& image,
const vk::Format format,
const vk::ImageAspectFlags aspectFlags,
const uint32_t mipLevels
) {
return this->device.createImageView(vk::ImageViewCreateInfo{}
.setImage(image)
.setViewType(vk::ImageViewType::e2D)
.setFormat(format)
.setSubresourceRange(vk::ImageSubresourceRange{}
.setAspectMask(aspectFlags)
.setBaseMipLevel(0)
.setLevelCount(mipLevels)
.setBaseArrayLayer(0)
.setLayerCount(1)));
}
void transitionImageLayout(const vk::Image& image, const vk::Format format, const vk::ImageLayout oldLayout,
const vk::ImageLayout newLayout, const uint32_t mipLevels)
{
vk::CommandBuffer commandBuffer = this->beginSingleTimeCommands();
vk::PipelineStageFlags srcStage;
vk::PipelineStageFlags dstStage;
auto barrier = vk::ImageMemoryBarrier{}
.setOldLayout(oldLayout)
.setNewLayout(newLayout)
.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setImage(image)
.setSubresourceRange(vk::ImageSubresourceRange{}
.setAspectMask(vk::ImageAspectFlagBits::eColor)
.setBaseMipLevel(0)
.setLevelCount(mipLevels)
.setBaseArrayLayer(0)
.setLayerCount(1));
if (newLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal)
{
barrier.subresourceRange.setAspectMask(vk::ImageAspectFlagBits::eDepth);
if (this->hasStencilComponent(format))
{
barrier.subresourceRange.aspectMask |= vk::ImageAspectFlagBits::eStencil;
}
}
if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal)
{
barrier.setDstAccessMask(vk::AccessFlagBits::eTransferWrite);
srcStage = vk::PipelineStageFlagBits::eTopOfPipe;
dstStage = vk::PipelineStageFlagBits::eTransfer;
}
else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal)
{
barrier
.setSrcAccessMask(vk::AccessFlagBits::eTransferWrite)
.setDstAccessMask(vk::AccessFlagBits::eShaderRead);
srcStage = vk::PipelineStageFlagBits::eTransfer;
dstStage = vk::PipelineStageFlagBits::eFragmentShader;
}
else if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal)
{
barrier.setDstAccessMask(vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite);
srcStage = vk::PipelineStageFlagBits::eTopOfPipe;
dstStage = vk::PipelineStageFlagBits::eEarlyFragmentTests;
}
else if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eColorAttachmentOptimal)
{
barrier.setDstAccessMask(vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite);
srcStage = vk::PipelineStageFlagBits::eTopOfPipe;
dstStage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
}
else
{
throw std::runtime_error("Unsupported layout transition.");
}
commandBuffer.pipelineBarrier(srcStage, dstStage, vk::DependencyFlags{}, {}, {}, barrier);
this->endSingleTimeCommands(commandBuffer);
}
void destroyImage(vk::Image& image, vk::DeviceMemory& imageMemory, vk::ImageView& imageView)
{
this->device.destroyImageView(imageView);
this->device.destroyImage(image);
this->device.freeMemory(imageMemory);
}
void generateMipmaps(vk::Image& image, const vk::Format imageFormat, const vk::Extent2D size, const uint32_t mipLevels)
{
const vk::FormatProperties formatProperties = this->physicalDevice.getFormatProperties(imageFormat);
if (!(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear))
{
throw std::runtime_error("Texture image format does not support linear blitting.");
}
vk::CommandBuffer commandBuffer = this->beginSingleTimeCommands();
auto barrier = vk::ImageMemoryBarrier{}
.setImage(image)
.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
.setSubresourceRange(vk::ImageSubresourceRange{}
.setAspectMask(vk::ImageAspectFlagBits::eColor)
.setBaseArrayLayer(0)
.setLayerCount(1)
.setLevelCount(1));
int32_t mipWidth = size.width;
int32_t mipHeight = size.height;
for (uint32_t i = 1; i < mipLevels; ++i)
{
barrier.subresourceRange.setBaseMipLevel(i - 1);
barrier
.setOldLayout(vk::ImageLayout::eTransferDstOptimal)
.setNewLayout(vk::ImageLayout::eTransferSrcOptimal)
.setSrcAccessMask(vk::AccessFlagBits::eTransferWrite)
.setDstAccessMask(vk::AccessFlagBits::eTransferRead);
commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, barrier);
const auto blit = vk::ImageBlit{}
.setSrcOffsets({
vk::Offset3D{0, 0, 0},
vk::Offset3D{mipWidth, mipHeight, 1}
})
.setSrcSubresource(vk::ImageSubresourceLayers{}
.setAspectMask(vk::ImageAspectFlagBits::eColor)
.setMipLevel(i - 1)
.setBaseArrayLayer(0)
.setLayerCount(1))
.setDstOffsets({
vk::Offset3D{0, 0, 0},
vk::Offset3D{mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1}
})
.setDstSubresource(vk::ImageSubresourceLayers{}
.setAspectMask(vk::ImageAspectFlagBits::eColor)
.setMipLevel(i)
.setBaseArrayLayer(0)
.setLayerCount(1));
commandBuffer.blitImage(
image, vk::ImageLayout::eTransferSrcOptimal,
image, vk::ImageLayout::eTransferDstOptimal,
blit, vk::Filter::eLinear);
barrier
.setOldLayout(vk::ImageLayout::eTransferSrcOptimal)
.setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal)
.setSrcAccessMask(vk::AccessFlagBits::eTransferRead)
.setDstAccessMask(vk::AccessFlagBits::eShaderRead);
commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier);
if (mipWidth > 1)
{
mipWidth /= 2;
}
if (mipHeight > 1)
{
mipHeight /= 2;
}
}
barrier.subresourceRange.setBaseMipLevel(mipLevels - 1);
barrier
.setOldLayout(vk::ImageLayout::eTransferDstOptimal)
.setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal)
.setSrcAccessMask(vk::AccessFlagBits::eTransferWrite)
.setDstAccessMask(vk::AccessFlagBits::eShaderRead);
commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, barrier);
this->endSingleTimeCommands(commandBuffer);
}
void copyBufferToImage(const vk::Buffer& buffer, vk::Image& image, const vk::Extent3D size)
{
vk::CommandBuffer commandBuffer = this->beginSingleTimeCommands();
commandBuffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, vk::BufferImageCopy{}
.setBufferOffset(0)
.setBufferRowLength(0)
.setBufferImageHeight(0)
.setImageSubresource(vk::ImageSubresourceLayers{}
.setAspectMask(vk::ImageAspectFlagBits::eColor)
.setMipLevel(0)
.setBaseArrayLayer(0)
.setLayerCount(1))
.setImageOffset({ 0, 0, 0 })
.setImageExtent(size));
this->endSingleTimeCommands(commandBuffer);
}
void createBuffer(const vk::DeviceSize size, const vk::BufferUsageFlags usage, const vk::MemoryPropertyFlags properties,
vk::Buffer& buffer, vk::DeviceMemory& memory) const
{
const auto bufferInfo = vk::BufferCreateInfo{}
.setSize(size)
.setUsage(usage)
.setSharingMode(vk::SharingMode::eExclusive);
buffer = this->device.createBuffer(bufferInfo);
const vk::MemoryRequirements memoryRequirements = this->device.getBufferMemoryRequirements(buffer);
memory = this->device.allocateMemory(vk::MemoryAllocateInfo{}
.setAllocationSize(memoryRequirements.size)
.setMemoryTypeIndex(findMemoryType(memoryRequirements.memoryTypeBits, properties)));
this->device.bindBufferMemory(buffer, memory, 0);
}
void copyBuffer(const vk::Buffer& source, vk::Buffer& destination, const vk::DeviceSize size)
{
vk::CommandBuffer copyCommandBuffer = this->beginSingleTimeCommands();
copyCommandBuffer.copyBuffer(source, destination, vk::BufferCopy{}.setSize(size));
this->endSingleTimeCommands(copyCommandBuffer);
}
void destroyBuffer(vk::Buffer& buffer, vk::DeviceMemory& bufferMemory)
{
this->device.destroyBuffer(buffer);
this->device.freeMemory(bufferMemory);
}
vk::Format findSupportedFormat(const std::vector<vk::Format>& candidates, const vk::ImageTiling tiling,
const vk::FormatFeatureFlags features) const
{
for (const vk::Format format : candidates)
{
const vk::FormatProperties properties = this->physicalDevice.getFormatProperties(format);
if ((tiling == vk::ImageTiling::eLinear && (properties.linearTilingFeatures & features) == features) ||
(tiling == vk::ImageTiling::eOptimal && (properties.optimalTilingFeatures & features) == features))
{
return format;
}
}
throw std::runtime_error("Failed to find supported format.");
}
bool hasStencilComponent(const vk::Format format)
{
return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint;
}
vk::Format findDepthFormat() const
{
return this->findSupportedFormat(
{ vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint },
vk::ImageTiling::eOptimal, vk::FormatFeatureFlagBits::eDepthStencilAttachment);
}
uint32_t findMemoryType(const uint32_t typeFilter, const vk::MemoryPropertyFlags properties) const
{
const vk::PhysicalDeviceMemoryProperties memoryProperties = this->physicalDevice.getMemoryProperties();
for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; ++i)
{
if ((typeFilter & (1 << i)) && (memoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
{
return i;
}
}
throw std::runtime_error("Failed to find suitable memory type.");
}
void updateUniformBuffer(uint32_t currentImage)
{
static const auto startTime = std::chrono::high_resolution_clock::now();
const auto currentTime = std::chrono::high_resolution_clock::now();
const float deltaSeconds = std::chrono::duration<float, std::chrono::seconds::period>(currentTime - startTime).count();
auto ubo = UniformBufferObject{}
.setModel(glm::rotate(glm::mat4(1.f), deltaSeconds * glm::radians(90.f), glm::vec3(0.f, 0.f, 1.f)))
.setView(glm::lookAt(glm::vec3(2.f, 2.f, 2.f), glm::vec3(0.f, 0.f, 0.f), glm::vec3(0.f, 0.f, 1.f)))
.setProjection(glm::perspective(
glm::radians(45.f), static_cast<float>(this->swapChainExtent.width) / this->swapChainExtent.height, 0.1f, 10.f));
ubo.projection[1][1] *= -1;
void* data = this->device.mapMemory(this->uniformBuffersMemory[currentImage], 0, sizeof(ubo));
std::memcpy(data, &ubo, sizeof(ubo));
this->device.unmapMemory(this->uniformBuffersMemory[currentImage]);
}
void recreateSwapChain()
{
int width = 0;
int height = 0;
while (!width || !height)
{
glfwGetFramebufferSize(this->window, &width, &height);
glfwWaitEvents();
}
this->device.waitIdle();
this->cleanupSwapChain();
this->createSwapChain();
this->createImageViews();
this->createRenderPass();
this->createGraphicsPipeline();
this->createColorResources();
this->createDepthResources();
this->createFramebuffers();
this->createUniformBuffers();
this->createDescriptorPool();
this->createDescriptorSets();
this->createCommandBuffers();
}
private:
inline static const vk::Extent2D windowExtent = { 800, 600 };
#ifdef VK_VALIDATION_LAYERS_ENABLED
inline static const std::vector<const char*> validationLayers = {
"VK_LAYER_KHRONOS_validation",
};
#endif
inline static const std::vector<const char*> deviceExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
};
GLFWwindow* window;
vk::Instance instance;
vk::DebugUtilsMessengerEXT debugMessenger;
vk::SurfaceKHR renderSurface;
vk::PhysicalDevice physicalDevice;
vk::Device device;
vk::SampleCountFlagBits msaaSamples = vk::SampleCountFlagBits::e1;
vk::Image colorImage;
vk::DeviceMemory colorImageMemory;
vk::ImageView colorImageView;
vk::Queue graphicsQueue;
vk::Queue presentQueue;
vk::SwapchainKHR swapChain;
std::vector<vk::Image> swapChainImages;
vk::Format swapChainImageFormat;
vk::Extent2D swapChainExtent;
std::vector<vk::ImageView> swapChainImageViews;
std::vector<vk::Framebuffer> swapChainFramebuffers;
vk::RenderPass renderPass;
vk::DescriptorSetLayout descriptorSetLayout;
vk::PipelineLayout pipelineLayout;
vk::Pipeline graphicsPipeline;
vk::CommandPool commandPool;
std::vector<vk::CommandBuffer> commandBuffers;
inline static constexpr const char* texturePath = "textures/chalet.jpg";
inline static constexpr const char* modelPath = "models/chalet.obj";
uint32_t mipLevels;
vk::Image textureImage;
vk::DeviceMemory textureImageMemory;
vk::ImageView textureImageView;
vk::Sampler textureSampler;
std::vector<vk::Semaphore> imageAvailableSemaphores;
std::vector<vk::Semaphore> renderFinishedSemaphores;
std::vector<vk::Fence> inFlightFences;
inline static constexpr int32_t maxFramesInFlight = 2;
size_t currentFrame = 0;
bool framebufferResized = false;
inline static const vk::BufferUsageFlags stagingBufferUsage = vk::BufferUsageFlagBits::eTransferSrc;
inline static const vk::MemoryPropertyFlags stagingBufferMemoryProperties =
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent;
std::vector<Vertex> vertices;
std::vector<uint32_t> indices;
vk::Buffer vertexBuffer;
vk::DeviceMemory vertexBufferMemory;
vk::Buffer indexBuffer;
vk::DeviceMemory indexBufferMemory;
std::vector<vk::Buffer> uniformBuffers;
std::vector<vk::DeviceMemory> uniformBuffersMemory;
vk::DescriptorPool descriptorPool;
std::vector<vk::DescriptorSet> descriptorSets;
vk::Image depthImage;
vk::DeviceMemory depthImageMemory;
vk::ImageView depthImageView;
};
int main()
{
App app;
try
{
app.run();
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
catch (const vk::Error& e)
{
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec3 fragColor;
layout(location = 1) in vec2 fragTexCoord;
layout(location = 0) out vec4 outColor;
layout(binding = 1) uniform sampler2D texSampler;
void main()
{
outColor = vec4(fragColor * texture(texSampler, fragTexCoord).rgb, 1.0);
}
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec2 inTexCoord;
layout(location = 0) out vec3 fragColor;
layout(location = 1) out vec2 fragTexCoord;
layout(binding = 0) uniform UniformBufferObject
{
mat4 model;
mat4 view;
mat4 projection;
} ubo;
void main()
{
gl_Position = ubo.projection * ubo.view * ubo.model * vec4(inPosition, 1.0);
fragColor = inColor;
fragTexCoord = inTexCoord;
}
#pragma once
#include <glm/glm.hpp>
#include <fstream>
#include <vector>
// Types
struct UniformBufferObject
{
alignas(16) glm::mat4 model;
alignas(16) glm::mat4 view;
alignas(16) glm::mat4 projection;
UniformBufferObject& setModel(const glm::mat4& model)
{
this->model = model;
return *this;
}
UniformBufferObject& setView(const glm::mat4& view)
{
this->view = view;
return *this;
}
UniformBufferObject& setProjection(const glm::mat4& projection)
{
this->projection = projection;
return *this;
}
};
// Functions
inline 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 " + filename);
}
const size_t fileSize = static_cast<size_t>(file.tellg());
std::vector<char> buffer(fileSize);
file.seekg(0);
file.read(buffer.data(), fileSize);
file.close();
return buffer;
}
#pragma once
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/gtx/hash.hpp>
#include <vulkan/vulkan.hpp>
#include <array>
struct Vertex
{
glm::vec3 position = {0.f, 0.f, 0.f};
glm::vec3 color = {1.f, 1.f, 1.f};
glm::vec2 texCoord = {0.f, 0.f};
bool operator==(const Vertex& other) const
{
return this->position == other.position
&& this->color == other.color
&& this->texCoord == other.texCoord;
}
static vk::VertexInputBindingDescription getBindingDescription()
{
return vk::VertexInputBindingDescription{}
.setBinding(0)
.setStride(sizeof(Vertex))
.setInputRate(vk::VertexInputRate::eVertex);
}
static std::array<vk::VertexInputAttributeDescription, 3> getAttributeDescriptions()
{
return {
vk::VertexInputAttributeDescription{}
.setBinding(0)
.setLocation(0)
.setFormat(vk::Format::eR32G32B32Sfloat)
.setOffset(offsetof(Vertex, position)),
vk::VertexInputAttributeDescription{}
.setBinding(0)
.setLocation(1)
.setFormat(vk::Format::eR32G32B32Sfloat)
.setOffset(offsetof(Vertex, color)),
vk::VertexInputAttributeDescription{}
.setBinding(0)
.setLocation(2)
.setFormat(vk::Format::eR32G32Sfloat)
.setOffset(offsetof(Vertex, texCoord)),
};
}
};
namespace std
{
template<> struct hash<Vertex>
{
size_t operator()(const Vertex& vertex) const
{
return ((hash<glm::vec3>()(vertex.position) ^
(hash<glm::vec3>()(vertex.color) << 1)) >> 1) ^
(hash<glm::vec2>()(vertex.texCoord) << 1);
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment