diff --git a/27_depth_buffering.cpp b/27_depth_buffering.cpp new file mode 100644 index 0000000..1e4facd --- /dev/null +++ b/27_depth_buffering.cpp @@ -0,0 +1,1224 @@ +#include +#include +#include +#include +#if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) +#include "vulkan/vulkan.hpp" +#include +#include +#else +import vulkan_hpp; +#endif + +#define STB_IMAGE_IMPLEMENTATION +#include +#define GLFW_INCLUDE_VULKAN +#include +// The perspective projection matrix generated by GLM will use the OpenGL depth range of -1.0 to 1.0 +// by default. We need to configure it to use the Vulkan range of 0.0 to 1.0 using the +// GLM_FORCE_DEPTH_ZERO_TO_ONE definition. +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int32_t MAX_FRAMES_IN_FLIGHT = 2; + +struct Vertex { + glm::vec3 position; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() { + return { + .binding = 0, + .stride = sizeof(Vertex), + .inputRate = vk::VertexInputRate::eVertex, + }; + } + + static std::array getAttributeDescriptions() { + return { + vk::VertexInputAttributeDescription{ + .location = 0, + .binding = 0, + .format = vk::Format::eR32G32B32Sfloat, + .offset = offsetof(Vertex, position), + }, + vk::VertexInputAttributeDescription{ + .location = 1, + .binding = 0, + .format = vk::Format::eR32G32B32Sfloat, + .offset = offsetof(Vertex, color), + }, + vk::VertexInputAttributeDescription{ + .location = 2, + .binding = 0, + .format = vk::Format::eR32G32Sfloat, + .offset = offsetof(Vertex, texCoord), + } + }; + } +}; + +struct UniformBufferObject { + alignas(16) glm::mat4 model; + alignas(16) glm::mat4 view; + alignas(16) glm::mat4 proj; +}; + +const std::vector vertices = { + {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, + {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{ 0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{ 0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0, + 4, 5, 6, 6, 7, 4 +}; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +#ifdef NDEBUG +constexpr bool enableValidationLayers = false; +#else +constexpr bool enableValidationLayers = true; +#endif + +static std::vector 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!"); + } + + std::vector buffer(file.tellg()); + file.seekg(0, std::ios::beg); + file.read(buffer.data(), static_cast(buffer.size())); + file.close(); + + return buffer; +} + +class HelloTriangleApplication { + public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + private: + void initWindow() { + glfwInit(); + // Don't create an OpenGL context + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + } + static void framebufferResizeCallback(GLFWwindow *window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + void initVulkan() { + createInstance(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + } + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + + device.waitIdle(); + } + void drawFrame() { + // Wait till fence is signalled + while (vk::Result::eTimeout == device.waitForFences(*drawFences[frameIndex], vk::True, UINT64_MAX)) {} + + vk::Result result; + uint32_t imageIndex; + try { + std::tie(result, imageIndex) = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); + if (result == vk::Result::eErrorOutOfDateKHR) { + recreateSwapChain(); + return; + } + if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { + throw std::runtime_error("failed to acquire swap chain image!"); + } + } catch (const vk::SystemError &e) { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { + recreateSwapChain(); + return; + } else { + throw; + } + } + + device.resetFences(*drawFences[frameIndex]); + + updateUniformBuffer(frameIndex); + + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); + const vk::SubmitInfo submitInfo = { + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex], + }; + graphicsQueue.submit(submitInfo, *drawFences[frameIndex]); + + try { + // Presentation + vk::PresentInfoKHR presentInfoKHR = { + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex, + }; + result = graphicsQueue.presentKHR(presentInfoKHR); + if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else if (result != vk::Result::eSuccess) { + throw std::runtime_error("failed to present swap chain image!"); + } + } catch (const vk::SystemError &e) { + if (e.code().value() == static_cast(vk::Result::eErrorOutOfDateKHR)) { + recreateSwapChain(); + return; + } else { + throw; + } + } + + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; + } + void cleanup() { + cleanupSwapChain(); + glfwDestroyWindow(window); + glfwTerminate(); + } + void createInstance() { + constexpr vk::ApplicationInfo appInfo { + .pApplicationName = "Hello Triangle", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "No Engine", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = vk::ApiVersion14, + }; + + // Get the required layers + std::vector requiredLayers; + if (enableValidationLayers) { + requiredLayers.assign(validationLayers.begin(), validationLayers.end()); + } + + // Check if the required layers are supported by the Vulkan implementation. + auto layerProperties = context.enumerateInstanceLayerProperties(); + if (std::ranges::any_of(requiredLayers, [&layerProperties](auto const& requiredLayer) { + return std::ranges::none_of(layerProperties, + [requiredLayer](auto const& layerProperty) + { return strcmp(layerProperty.layerName, requiredLayer) == 0; }); + })) + { + throw std::runtime_error("One or more required layers are not supported!"); + } + + // Get the required instance extensions from GLFW. + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + // Check if the required GLFW extensions are supported by the Vulkan implementation. + auto extensionProperties = context.enumerateInstanceExtensionProperties(); + for (uint32_t i = 0; i < glfwExtensionCount; ++i) + { + if (std::ranges::none_of(extensionProperties, + [glfwExtension = glfwExtensions[i]](auto const& extensionProperty) + { return strcmp(extensionProperty.extensionName, glfwExtension) == 0; })) + { + throw std::runtime_error("Required GLFW extension not supported: " + std::string(glfwExtensions[i])); + } + } + vk::InstanceCreateInfo createInfo { + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = glfwExtensionCount, + .ppEnabledExtensionNames = glfwExtensions, + }; + + instance = vk::raii::Instance(context, createInfo); + } + void createSurface() { + VkSurfaceKHR _surface; + if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != 0) { + throw std::runtime_error("failed to create window surface!"); + } + + surface = vk::raii::SurfaceKHR(instance, _surface); + } + void pickPhysicalDevice() { + std::vector deviceExtensions = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRCreateRenderpass2ExtensionName, + }; + + auto devices = instance.enumeratePhysicalDevices(); + if (devices.empty()) { + throw std::runtime_error("failed to find GPUs with Vulkan support!"); + } + + for (const auto &device : devices) { + auto deviceProperties = device.getProperties(); + auto deviceFeatures = device.getFeatures(); + auto queueFamilies = device.getQueueFamilyProperties(); + auto extensions = device.enumerateDeviceExtensionProperties(); + bool isSuitable = deviceProperties.apiVersion >= VK_API_VERSION_1_3; + bool extensionFound = true; + + const vk::QueueFamilyProperties *qf = nullptr; + for (const auto &qfp : queueFamilies) { + if ((qfp.queueFlags & vk::QueueFlagBits::eGraphics) != static_cast(0)) { + qf = &qfp; + break; + } + } + + isSuitable = isSuitable && (qf != nullptr); + + for (const auto &extension : deviceExtensions) { + auto extensionIter = std::ranges::find_if(extensions, [extension](auto const & ext) {return strcmp(ext.extensionName, extension) == 0;}); + extensionFound = extensionFound && extensionIter != extensions.end(); + } + + isSuitable = isSuitable && extensionFound; + + if (isSuitable) { + physicalDevice = device; + return; + } + + throw std::runtime_error("failed to find a suitable GPU"); + } + } + void createLogicalDevice() { + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + graphicsQueueIndex = findQueueFamilies(physicalDevice); + float queuePriority = 0.5f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo { + .queueFamilyIndex = graphicsQueueIndex, + .queueCount = 1, + .pQueuePriorities = &queuePriority, + }; + + // Create a chain of feature structures + vk::StructureChain featureChain = { + {.features = {.samplerAnisotropy = true}}, // vk::PhysicalDeviceFeatures2 + {.synchronization2 = true, + .dynamicRendering = true}, // Enable dynamic rendering and synchronization2 from Vulkan 1.3 + {.shaderDrawParameters = true}, // Enable shader draw parameters from Vulkan 1.2 + {.extendedDynamicState = true} // Enable extended dynamic state from the extension + }; + + std::vector deviceExtensions = { + vk::KHRSwapchainExtensionName, + vk::KHRSpirv14ExtensionName, + vk::KHRSynchronization2ExtensionName, + vk::KHRCreateRenderpass2ExtensionName + }; + + vk::DeviceCreateInfo deviceCreateInfo { + .pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(deviceExtensions.size()), + .ppEnabledExtensionNames = deviceExtensions.data(), + }; + + device = vk::raii::Device(physicalDevice, deviceCreateInfo); + graphicsQueue = vk::raii::Queue(device, graphicsQueueIndex, 0); + } + void createSwapChain() { + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(surface)); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + minImageCount = (surfaceCapabilities.maxImageCount > 0 && + minImageCount > surfaceCapabilities.maxImageCount) ? + surfaceCapabilities.maxImageCount : + minImageCount; + + vk::SwapchainCreateInfoKHR swapChainCreateInfo { + .flags = vk::SwapchainCreateFlagsKHR(), + .surface = surface, + .minImageCount = minImageCount, + .imageFormat = swapChainSurfaceFormat.format, + .imageColorSpace = swapChainSurfaceFormat.colorSpace, + .imageExtent = swapChainExtent, + .imageArrayLayers = 1, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, + .imageSharingMode = vk::SharingMode::eExclusive, + .preTransform = surfaceCapabilities.currentTransform, + .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, + .presentMode = chooseSwapPresentMode(physicalDevice.getSurfacePresentModesKHR(surface)), + .clipped = true, + .oldSwapchain = nullptr, + }; + + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + swapChainImageFormat = swapChainSurfaceFormat.format; + } + void createImageViews() { + swapChainImageViews.clear(); + + vk::ImageViewCreateInfo imageViewCreateInfo{ + .viewType = vk::ImageViewType::e2D, + .format = swapChainImageFormat, + .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } + }; + + for (auto image : swapChainImages) { + imageViewCreateInfo.image = image; + swapChainImageViews.emplace_back(vk::raii::ImageView(device, imageViewCreateInfo)); + } + } + void createDescriptorSetLayout() { + std::array bindings = { + vk::DescriptorSetLayoutBinding{0, vk::DescriptorType::eUniformBuffer, + 1, vk::ShaderStageFlagBits::eVertex, nullptr}, + vk::DescriptorSetLayoutBinding{1, vk::DescriptorType::eCombinedImageSampler, + 1, vk::ShaderStageFlagBits::eFragment, nullptr}, + }; + vk::DescriptorSetLayoutCreateInfo layoutInfo{ + .bindingCount = bindings.size(), + .pBindings = bindings.data(), + }; + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + } + void createGraphicsPipeline() { + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/27_shader_depth.spv")); + + vk::PipelineShaderStageCreateInfo vertShaderStageInfo = { + .stage = vk::ShaderStageFlagBits::eVertex, + .module = shaderModule, + .pName = "vertMain", + }; + vk::PipelineShaderStageCreateInfo fragShaderStageInfo = { + .stage = vk::ShaderStageFlagBits::eFragment, + .module = shaderModule, + .pName = "fragMain", + }; + + vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + // Vertex input + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescriptions = Vertex::getAttributeDescriptions(); + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = attributeDescriptions.size(), + .pVertexAttributeDescriptions = attributeDescriptions.data(), + }; + + // Input assembly + vk::PipelineInputAssemblyStateCreateInfo inputAssembly = { + .topology = vk::PrimitiveTopology::eTriangleList, + }; + + // Dynamic state + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor, + }; + vk::PipelineDynamicStateCreateInfo dynamicState = { + .dynamicStateCount = static_cast(dynamicStates.size()), + .pDynamicStates = dynamicStates.data(), + }; + // No need to specify viewport and scissor because they will be specified dynamically + vk::PipelineViewportStateCreateInfo viewportState = { + .viewportCount = 1, + .scissorCount = 1, + }; + + // Rasterisation + vk::PipelineRasterizationStateCreateInfo rasterizer = { + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False, + .depthBiasSlopeFactor = 1.0f, + .lineWidth = 1.0f + }; + + // Multisampling + vk::PipelineMultisampleStateCreateInfo multisampling = { + .rasterizationSamples = vk::SampleCountFlagBits::e1, + .sampleShadingEnable = vk::False, + }; + + // Color blending + vk::PipelineColorBlendAttachmentState colorBlendAttachment = { + .blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | + vk::ColorComponentFlagBits::eG | + vk::ColorComponentFlagBits::eB | + vk::ColorComponentFlagBits::eA, + }; + vk::PipelineColorBlendStateCreateInfo colorBlending = { + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment, + }; + + // Depth/Stencil testing + vk::PipelineDepthStencilStateCreateInfo depthStencil = { + .depthTestEnable = vk::True, + .depthWriteEnable = vk::True, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False, + }; + + // Pipeline layout + vk::PipelineLayoutCreateInfo pipelineLayoutInfo = { + .setLayoutCount = 1, + .pSetLayouts = &*descriptorSetLayout, + .pushConstantRangeCount = 0, + }; + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + + vk::Format depthFormat = findDepthFormat(); + + // Dynamic rendering pipeline + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo = { + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &swapChainImageFormat, + .depthAttachmentFormat = depthFormat, + }; + vk::GraphicsPipelineCreateInfo pipelineInfo = { + .pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr, + }; + + // Create pipeline + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + } + void createCommandPool() { + vk::CommandPoolCreateInfo poolInfo = { + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = graphicsQueueIndex, + }; + commandPool = vk::raii:: CommandPool(device, poolInfo); + } + void createDepthResources() { + vk::Format depthFormat = findDepthFormat(); + createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, + depthImage, depthImageMemory); + depthImageView = createImageView(depthImage, depthFormat, vk::ImageAspectFlagBits::eDepth); + } + void createTextureImage() { + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + if (!pixels) { + throw std::runtime_error("failed to load texture image!"); + } + + vk::DeviceSize imageSize = texWidth * texHeight * 4; + + vk::raii::Buffer stagingBuffer({}); + vk::raii::DeviceMemory stagingBufferMemory({}); + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostVisible, + stagingBuffer, stagingBufferMemory); + void *data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + + stbi_image_free(pixels); + + createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); + + // Transition layout to copy data + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + + // Transition layout to enable sampling + transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + } + void createTextureImageView() { + textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor); + } + void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, + vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, + vk::raii::Image& image, vk::raii::DeviceMemory& imageMemory) { + vk::ImageCreateInfo imageInfo { + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + }; + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo { + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) + }; + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(*imageMemory, 0); + } + vk::raii::ImageView createImageView(vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags) { + vk::ImageViewCreateInfo imageViewCreateInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = { aspectFlags, 0, 1, 0, 1 } + }; + return vk::raii::ImageView(device, imageViewCreateInfo); + } + void copyBufferToImage(const vk::raii::Buffer& buffer, vk::raii::Image& image, uint32_t width, uint32_t height) { + vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); + vk::BufferImageCopy region { + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 }, + .imageOffset = { 0, 0, 0 }, + .imageExtent = { width, height, 1 } + }; + commandBuffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(commandBuffer); + } + void createTextureSampler() { + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo = { + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways, + .borderColor = vk::BorderColor::eIntOpaqueBlack, + .unnormalizedCoordinates = vk::False, + }; + textureImageSampler = vk::raii::Sampler(device, samplerInfo); + } + void createVertexBuffer() { + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingBufferMemory = nullptr; + + // Create staging buffer visible to host (CPU) + createBuffer( + bufferSize, + vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, + stagingBuffer, + stagingBufferMemory + ); + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, vertices.data(), (size_t)bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer( + bufferSize, + vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, + vk::MemoryPropertyFlagBits::eDeviceLocal, + vertexBuffer, + vertexBufferMemory + ); + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + } + void createIndexBuffer() { + vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingBufferMemory = nullptr; + + // Create staging buffer visible to host (CPU) + createBuffer( + bufferSize, + vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, + stagingBuffer, + stagingBufferMemory + ); + void *data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), (size_t)bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer( + bufferSize, + vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eTransferDst, + vk::MemoryPropertyFlagBits::eDeviceLocal, + indexBuffer, + indexBufferMemory + ); + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + } + void createUniformBuffers() { + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + unifromBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer({}); + vk::raii::DeviceMemory bufferMem({}); + createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, + buffer, bufferMem); + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + unifromBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + } + void createDescriptorPool() { + std::array poolSizes = { + vk::DescriptorPoolSize{vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT}, + vk::DescriptorPoolSize{vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT}, + }; + vk::DescriptorPoolCreateInfo poolInfo = { + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = poolSizes.size(), + .pPoolSizes = poolSizes.data(), + }; + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + } + void createDescriptorSets() { + std::vector layouts(MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout); + vk::DescriptorSetAllocateInfo allocInfo { + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data(), + }; + descriptorSets.clear(); + descriptorSets = device.allocateDescriptorSets(allocInfo); + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { + vk::DescriptorBufferInfo bufferInfo { + .buffer = uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject), + }; + vk::DescriptorImageInfo imageInfo { + .sampler = textureImageSampler, + .imageView = textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + }; + + std::array descriptorWrites = { + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo, + }, + vk::WriteDescriptorSet{ + .dstSet = descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo, + } + }; + + device.updateDescriptorSets(descriptorWrites, {}); + } + } + void createCommandBuffers() { + commandBuffers.clear(); + vk::CommandBufferAllocateInfo allocInfo = { + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = MAX_FRAMES_IN_FLIGHT, + }; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + } + void createSyncObjects() { + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && drawFences.empty()); + + for (size_t i = 0; i < swapChainImages.size(); ++i) { + renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); + } + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { + presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); + drawFences.emplace_back(device, vk::FenceCreateInfo{ .flags = vk::FenceCreateFlagBits::eSignaled }); + } + } + void updateUniformBuffer(uint32_t currentImage) { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo{}; + ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), + static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), + 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(unifromBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + void recordCommandBuffer(uint32_t imageIndex) { + auto &commandBuffer = commandBuffers[frameIndex]; + + // Begin recording the command buffer + commandBuffer.begin({}); + + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transitionRenderingImageLayout( + swapChainImages[imageIndex], + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, // srcAccessMask (no need to wait for previous operations) + vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // dstStage + vk::ImageAspectFlagBits::eColor // aspectFlags + ); + transitionRenderingImageLayout( + *depthImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthAttachmentOptimal, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, // srcAccessMask + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, // dstAccessMask + vk::PipelineStageFlagBits2::eEarlyFragmentTests | + vk::PipelineStageFlagBits2::eLateFragmentTests, // srcStage + vk::PipelineStageFlagBits2::eEarlyFragmentTests | + vk::PipelineStageFlagBits2::eLateFragmentTests, // dstStage + vk::ImageAspectFlagBits::eDepth // aspectFlags + ); + + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); + vk::RenderingAttachmentInfo colorAttachmentInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor, + }; + vk::RenderingAttachmentInfo depthAttachmentInfo = { + .imageView = depthImageView, + .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .clearValue = clearDepth, + }; + vk::RenderingInfo renderingInfo = { + .renderArea = { .offset = { 0, 0 }, .extent = swapChainExtent }, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachmentInfo, + .pDepthAttachment = &depthAttachmentInfo, + }; + + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, graphicsPipeline); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + // Viewport and scissor are dynamic so we need to set them + vk::Viewport viewport = { + .x = 0.0f, + .y = 0.0f, + .width = static_cast(swapChainExtent.width), + .height = static_cast(swapChainExtent.height), + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + commandBuffer.setViewport(0, viewport); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); + + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, + *descriptorSets[frameIndex], nullptr); + + // Issue the draw command + commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); + + commandBuffer.endRendering(); + + // After rendering, transition the swapchain image to PRESENT_SRC + transitionRenderingImageLayout( + swapChainImages[imageIndex], + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask + {}, // dstAccessMask + vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage + vk::PipelineStageFlagBits2::eBottomOfPipe, // dstStage + vk::ImageAspectFlagBits::eColor // aspectFlags + ); + + // Finish recording the command buffer + commandBuffer.end(); + } + void createBuffer( + vk::DeviceSize size, + vk::BufferUsageFlags usage, + vk::MemoryPropertyFlags properties, + vk::raii::Buffer &buffer, + vk::raii::DeviceMemory &bufferMemory + ) { + vk::BufferCreateInfo bufferInfo = { + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive, + }; + buffer = vk::raii::Buffer(device, bufferInfo); + + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo memAllocateInfo = { + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) + }; + bufferMemory = vk::raii::DeviceMemory(device, memAllocateInfo); + buffer.bindMemory(bufferMemory, 0); + } + void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) { + vk::raii::CommandBuffer commandCopyBuffer = beginSingleTimeCommands(); + commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy(0, 0, size)); + endSingleTimeCommands(commandCopyBuffer); + } + vk::raii::CommandBuffer beginSingleTimeCommands() { + vk::CommandBufferAllocateInfo allocInfo { + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1 + }; + vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + + vk::CommandBufferBeginInfo beginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }; + commandBuffer.begin(beginInfo); + + return commandBuffer; + } + bool hasStencilComponent(vk::Format format) { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + vk::Format findDepthFormat() { + return findSupportedFormat( + {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, + vk::ImageTiling::eOptimal, vk::FormatFeatureFlagBits::eDepthStencilAttachment + ); + } + vk::Format findSupportedFormat(const std::vector& candidates, vk::ImageTiling tiling, + vk::FormatFeatureFlags features) { + for (const auto format : candidates) { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) { + return format; + } + if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw std::runtime_error("failed to find supported format!"); + } + void endSingleTimeCommands(vk::raii::CommandBuffer& commandBuffer) { + commandBuffer.end(); + + vk::SubmitInfo submitInfo{ .commandBufferCount = 1, .pCommandBuffers = &*commandBuffer }; + graphicsQueue.submit(submitInfo, nullptr); + graphicsQueue.waitIdle(); + } + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties)) { + return i; + } + } + + throw std::runtime_error("failed to find suitable memory type!"); + } + void transitionImageLayout(const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { + vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); + + vk::ImageMemoryBarrier barrier{ + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = image, + .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1} + }; + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } + else + { + throw std::invalid_argument("unsupported layout transition!"); + } + + commandBuffer.pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + endSingleTimeCommands(commandBuffer); + } + void transitionRenderingImageLayout( + vk::Image image, + vk::ImageLayout oldLayout, + vk::ImageLayout newLayout, + vk::AccessFlags2 srcAccessMask, + vk::AccessFlags2 dstAccessMask, + vk::PipelineStageFlags2 srcStageMask, + vk::PipelineStageFlags2 dstStageMask, + vk::ImageAspectFlags aspectFlags + ) { + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = srcStageMask, + .srcAccessMask = srcAccessMask, + .dstStageMask = dstStageMask, + .dstAccessMask = dstAccessMask, + .oldLayout = oldLayout, + .newLayout = newLayout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange = { + .aspectMask = aspectFlags, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1 + } + }; + vk::DependencyInfo dependencyInfo = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier + }; + commandBuffers[frameIndex].pipelineBarrier2(dependencyInfo); + } + void cleanupSwapChain() { + swapChainImageViews.clear(); + swapChain = nullptr; + } + void recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + createDepthResources(); + } + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector &code) const { + vk::ShaderModuleCreateInfo createInfo { + .codeSize = code.size() * sizeof(char), + .pCode = reinterpret_cast(code.data()), + }; + + return vk::raii::ShaderModule{device, createInfo}; + } + vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == vk::Format::eB8G8R8A8Srgb && availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) { + return availableFormat; + } + } + + return availableFormats[0]; + } + vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == vk::PresentModeKHR::eMailbox) { + return availablePresentMode; + } + } + + return vk::PresentModeKHR::eFifo; + } + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + return { + std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), + std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height), + }; + } + uint32_t findQueueFamilies(vk::raii::PhysicalDevice physicalDevice) { + // find the index of the first queue family that supports graphics + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // get the first index into queueFamilyProperties which both supports graphics and present + uint32_t queueIndex = ~0; + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); ++qfpIndex) { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && + physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) { + queueIndex = qfpIndex; + break; + } + } + + if (queueIndex == ~0) { + throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); + } + + return queueIndex; + } + + uint32_t frameIndex = 0; + bool framebufferResized = false; + GLFWwindow *window; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + uint32_t graphicsQueueIndex; + vk::raii::Device device = nullptr; + vk::raii::Queue graphicsQueue = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::SwapchainKHR swapChain = nullptr; + vk::SurfaceFormatKHR swapChainSurfaceFormat; + vk::Extent2D swapChainExtent; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + std::vector swapChainImages; + std::vector swapChainImageViews; + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + vk::raii::CommandPool commandPool = nullptr; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureImageSampler = nullptr; + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector unifromBuffersMapped; + std::vector commandBuffers; + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; + std::vector drawFences; +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception &e) { + std::cout << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bcc230..02093b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -252,11 +252,11 @@ add_chapter (26_texture_mapping TEXTURES images/texture.jpg LIBS glm::glm) -# add_chapter (27_depth_buffering -# SHADER 27_shader_depth -# TEXTURES images/texture.jpg -# LIBS glm::glm) -# +add_chapter (27_depth_buffering + SHADER 27_shader_depth + TEXTURES images/texture.jpg + LIBS glm::glm) + # add_chapter (28_model_loading # SHADER 27_shader_depth # MODELS viking_room.obj diff --git a/shaders/27_shader_depth.slang b/shaders/27_shader_depth.slang new file mode 100644 index 0000000..03b4d92 --- /dev/null +++ b/shaders/27_shader_depth.slang @@ -0,0 +1,34 @@ +struct VSInput { + float3 inPosition; + float3 inColor; + float2 inTexCoord; +}; + +struct UniformBuffer { + float4x4 model; + float4x4 view; + float4x4 proj; +}; +ConstantBuffer ubo; + +struct VSOutput { + float4 pos : SV_Position; + float3 color; + float2 fragTexCoord; +}; + +[shader("vertex")] +VSOutput vertMain(VSInput input) { + VSOutput output; + output.color = input.inColor; + output.pos = mul(ubo.proj, mul(ubo.view, mul(ubo.model, float4(input.inPosition, 1.0)))); + output.fragTexCoord = input.inTexCoord; + return output; +} + +Sampler2D texture; + +[shader("fragment")] +float4 fragMain(VSOutput vertIn) : SV_Target { + return texture.Sample(vertIn.fragTexCoord); +}