Last active
September 12, 2019 21:43
-
-
Save Adanos020/9d1569776d5486216e048228ecaa3525 to your computer and use it in GitHub Desktop.
My VkSpinningChalet
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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