Program Listing for File SkyBox.cpp

Program Listing for File SkyBox.cpp#

Return to documentation for file (Src/GraphicsEngineVulkan/scene/sky_box/SkyBox.cpp)

module;
#include <memory>
#include <array>
#include <filesystem>
#include <sstream>
#include <string>
#include <vector>
#include <vulkan/vulkan.hpp>
#include <stb_image.h>
#include <spdlog/spdlog.h>
#include <glm/glm.hpp>
#include "shared/scene/Vertex.hpp"
#include "shared/scene/ObjMaterial.hpp"

#include "common/Utilities.hpp"

module kataglyphis.vulkan.sky_box;

import kataglyphis.vulkan.vertex;
import kataglyphis.vulkan.texture;
import kataglyphis.vulkan.mesh;
import kataglyphis.vulkan.file;
import kataglyphis.vulkan.shader_helper;
import kataglyphis.vulkan.buffer;
import kataglyphis.vulkan.command_buffer_manager;

namespace Kataglyphis {

SkyBox::SkyBox() = default;

void SkyBox::init(std::shared_ptr<VulkanDevice>in_device, vk::CommandPool commandPool)
{
    this->device = in_device;

    createMesh(commandPool);
    loadCubeMap(commandPool);
}

void SkyBox::loadCubeMap(vk::CommandPool commandPool)
{
    std::stringstream skybox_base_dir;
    std::filesystem::path const cwd = std::filesystem::current_path();
    skybox_base_dir << cwd.string();
    skybox_base_dir << "/Resources/Textures/Skybox/DOOM2016/";

    std::array<std::string, 6> skybox_textures = {
        "DOOM16RT.png", "DOOM16LF.png", "DOOM16UP.png", "DOOM16DN.png", "DOOM16FT.png", "DOOM16BK.png"
    };

    cubeMapTexture = std::make_unique<Texture>();

    int width = 0, height = 0, bit_depth = 0;
    std::vector<unsigned char*> face_data(6);
    vk::DeviceSize layerSize = 0;
    vk::DeviceSize imageSize = 0;

    for (size_t i = 0; i < 6; i++) {
        std::string path = skybox_base_dir.str() + skybox_textures[i];
        face_data[i] = stbi_load(path.c_str(), &width, &height, &bit_depth, 4);
        if (!face_data[i]) {
            spdlog::error("Failed to load skybox texture: {}", path);
            for (size_t j = 0; j < i; j++) { stbi_image_free(face_data[j]); }
            return;
        }
        layerSize = static_cast<vk::DeviceSize>(width) * static_cast<vk::DeviceSize>(height) * 4;
        imageSize += layerSize;
    }

    spdlog::info("SkyBox: All 6 textures loaded, width={}, height={}, totalImageSize={}", width, height, imageSize);

    cubeMapTexture->createImage(device, static_cast<uint32_t>(width), static_cast<uint32_t>(height), 1, vk::Format::eR8G8B8A8Unorm, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal, 6, vk::ImageCreateFlagBits::eCubeCompatible);

    VulkanBuffer stagingBuffer;
    stagingBuffer.create(device, imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);

    void* mappedData = device->getLogicalDevice().mapMemory(stagingBuffer.getBufferMemory(), 0, imageSize).value;
    for (size_t i = 0; i < 6; i++) {
        void* layerOffset = static_cast<char*>(mappedData) + i * layerSize;
        std::memcpy(layerOffset, face_data[i], static_cast<size_t>(layerSize));
    }
    device->getLogicalDevice().unmapMemory(stagingBuffer.getBufferMemory());

    for (size_t i = 0; i < 6; i++) { stbi_image_free(face_data[i]); }

    vk::CommandBuffer commandBuffer = Kataglyphis::VulkanRendererInternals::CommandBufferManager::beginCommandBuffer(device->getLogicalDevice(), commandPool);

    vk::ImageMemoryBarrier barrier{};
    barrier.srcQueueFamilyIndex = vk::QueueFamilyIgnored;
    barrier.dstQueueFamilyIndex = vk::QueueFamilyIgnored;
    barrier.image = cubeMapTexture->getImage();
    barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
    barrier.subresourceRange.baseMipLevel = 0;
    barrier.subresourceRange.levelCount = 1;
    barrier.subresourceRange.baseArrayLayer = 0;
    barrier.subresourceRange.layerCount = 6;

    barrier.oldLayout = vk::ImageLayout::eUndefined;
    barrier.newLayout = vk::ImageLayout::eTransferDstOptimal;
    barrier.srcAccessMask = vk::AccessFlags{};
    barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;

    commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags{}, {}, {}, barrier);

    vk::BufferImageCopy region{};
    region.bufferOffset = 0;
    region.bufferRowLength = static_cast<uint32_t>(width);
    region.bufferImageHeight = static_cast<uint32_t>(height);
    region.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
    region.imageSubresource.mipLevel = 0;
    region.imageSubresource.baseArrayLayer = 0;
    region.imageSubresource.layerCount = 6;
    region.imageOffset = vk::Offset3D{0, 0, 0};
    region.imageExtent = vk::Extent3D{static_cast<uint32_t>(width), static_cast<uint32_t>(height), 1};

    commandBuffer.copyBufferToImage(stagingBuffer.getBuffer(), cubeMapTexture->getImage(), vk::ImageLayout::eTransferDstOptimal, 1, &region);

    barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
    barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
    barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
    barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;

    commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, vk::DependencyFlags{}, {}, {}, barrier);

    Kataglyphis::VulkanRendererInternals::CommandBufferManager::endAndSubmitCommandBuffer(device->getLogicalDevice(), commandPool, device->getGraphicsQueue(), commandBuffer);

    stagingBuffer.cleanUp();

    cubeMapTexture->createImageView(device, vk::Format::eR8G8B8A8Unorm, vk::ImageAspectFlagBits::eColor, 1, vk::ImageViewType::eCube, 6);
    cubeMapTexture->createTextureSampler(device);

    createDescriptorSetForCubeMap();
}

void SkyBox::createDescriptorSetForCubeMap()
{
    vk::DescriptorSetLayoutBinding cubemapBinding{};
    cubemapBinding.binding = 1;
    cubemapBinding.descriptorCount = 1;
    cubemapBinding.descriptorType = vk::DescriptorType::eCombinedImageSampler;
    cubemapBinding.stageFlags = vk::ShaderStageFlagBits::eFragment;

    vk::DescriptorSetLayoutCreateInfo layoutInfo{};
    layoutInfo.bindingCount = 1;
    layoutInfo.pBindings = &cubemapBinding;

    auto layoutResult = device->getLogicalDevice().createDescriptorSetLayout(layoutInfo);
    ASSERT_VULKAN(VkResult(layoutResult.result), "Failed to create skybox descriptor set layout!");
    descriptorSetLayout = layoutResult.value;

    vk::DescriptorPoolSize poolSize{};
    poolSize.type = vk::DescriptorType::eCombinedImageSampler;
    poolSize.descriptorCount = 1;

    vk::DescriptorPoolCreateInfo poolInfo{};
    poolInfo.poolSizeCount = 1;
    poolInfo.pPoolSizes = &poolSize;
    poolInfo.maxSets = 1;

    auto poolResult = device->getLogicalDevice().createDescriptorPool(poolInfo);
    ASSERT_VULKAN(VkResult(poolResult.result), "Failed to create skybox descriptor pool!");
    descriptorPool = poolResult.value;

    vk::DescriptorSetAllocateInfo allocInfo{};
    allocInfo.descriptorPool = descriptorPool;
    allocInfo.descriptorSetCount = 1;
    allocInfo.pSetLayouts = &descriptorSetLayout;

    auto allocResult = device->getLogicalDevice().allocateDescriptorSets(allocInfo);
    ASSERT_VULKAN(VkResult(allocResult.result), "Failed to allocate skybox descriptor set!");
    descriptorSet = allocResult.value[0];

    vk::DescriptorImageInfo imageInfo{};
    imageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
    imageInfo.imageView = cubeMapTexture->getImageView();
    imageInfo.sampler = cubeMapTexture->getSampler();

    vk::WriteDescriptorSet write{};
    write.dstSet = descriptorSet;
    write.dstBinding = 1;
    write.dstArrayElement = 0;
    write.descriptorType = vk::DescriptorType::eCombinedImageSampler;
    write.descriptorCount = 1;
    write.pImageInfo = &imageInfo;

    device->getLogicalDevice().updateDescriptorSets(1, &write, 0, nullptr);
}

void SkyBox::createRenderPass(vk::Format format, vk::Format depthFormat)
{
    vk::AttachmentDescription colorAttachment{};
    colorAttachment.format = format;
    colorAttachment.samples = vk::SampleCountFlagBits::e1;
    colorAttachment.loadOp = vk::AttachmentLoadOp::eClear;
    colorAttachment.storeOp = vk::AttachmentStoreOp::eStore;
    colorAttachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
    colorAttachment.stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
    colorAttachment.initialLayout = vk::ImageLayout::eUndefined;
    colorAttachment.finalLayout = vk::ImageLayout::eColorAttachmentOptimal;

    vk::AttachmentDescription depthAttachment{};
    depthAttachment.format = depthFormat;
    depthAttachment.samples = vk::SampleCountFlagBits::e1;
    depthAttachment.loadOp = vk::AttachmentLoadOp::eClear;
    depthAttachment.storeOp = vk::AttachmentStoreOp::eStore;
    depthAttachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
    depthAttachment.stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
    depthAttachment.initialLayout = vk::ImageLayout::eUndefined;
    depthAttachment.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal;

    std::array attachments = {colorAttachment, depthAttachment};

    vk::AttachmentReference colorRef{};
    colorRef.attachment = 0;
    colorRef.layout = vk::ImageLayout::eColorAttachmentOptimal;

    vk::AttachmentReference depthRef{};
    depthRef.attachment = 1;
    depthRef.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;

    vk::SubpassDescription subpass{};
    subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
    subpass.colorAttachmentCount = 1;
    subpass.pColorAttachments = &colorRef;
    subpass.pDepthStencilAttachment = &depthRef;

    std::array<vk::SubpassDependency, 2> dependencies{};

    dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
    dependencies[0].dstSubpass = 0;
    dependencies[0].srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
    dependencies[0].dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
    dependencies[0].srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
    dependencies[0].dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eColorAttachmentRead;
    dependencies[0].dependencyFlags = vk::DependencyFlagBits::eByRegion;

    dependencies[1].srcSubpass = 0;
    dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
    dependencies[1].srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
    dependencies[1].dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
    dependencies[1].srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
    dependencies[1].dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead;
    dependencies[1].dependencyFlags = vk::DependencyFlagBits::eByRegion;

    vk::RenderPassCreateInfo renderPassInfo{};
    renderPassInfo.attachmentCount = 2;
    renderPassInfo.pAttachments = attachments.data();
    renderPassInfo.subpassCount = 1;
    renderPassInfo.pSubpasses = &subpass;
    renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
    renderPassInfo.pDependencies = dependencies.data();

    auto result = device->getLogicalDevice().createRenderPass(renderPassInfo);
    ASSERT_VULKAN(VkResult(result.result), "Failed to create skybox render pass!");
    renderPass = result.value;
}

void SkyBox::createFramebuffers(size_t count, const std::vector<vk::ImageView>& imageViews, const std::vector<vk::ImageView>& depthViews, uint32_t width, uint32_t height)
{
    framebufferWidth = width;
    framebufferHeight = height;
    framebuffers.resize(count);
    for (size_t i = 0; i < count; i++) {
        std::array attachments = {imageViews[i], depthViews[i]};
        vk::FramebufferCreateInfo fbInfo{};
        fbInfo.renderPass = renderPass;
        fbInfo.attachmentCount = 2;
        fbInfo.pAttachments = attachments.data();
        fbInfo.width = width;
        fbInfo.height = height;
        fbInfo.layers = 1;
        auto fbResult = device->getLogicalDevice().createFramebuffer(fbInfo);
        ASSERT_VULKAN(VkResult(fbResult.result), "Failed to create skybox framebuffer!");
        framebuffers[i] = fbResult.value;
    }
}

void SkyBox::createGraphicsPipeline(vk::DescriptorSetLayout sharedLayout)
{
    std::stringstream skybox_shader_dir;
    std::filesystem::path const cwd = std::filesystem::current_path();
    skybox_shader_dir << cwd.string() << RELATIVE_RESOURCE_PATH << "Shaders/skybox/";

    ShaderHelper shaderHelper;
    shaderHelper.compileShader(skybox_shader_dir.str(), "SkyBox.vert");
    shaderHelper.compileShader(skybox_shader_dir.str(), "SkyBox.frag");

    File vertexFile(shaderHelper.getShaderSpvDir(skybox_shader_dir.str(), "SkyBox.vert"));
    File fragmentFile(shaderHelper.getShaderSpvDir(skybox_shader_dir.str(), "SkyBox.frag"));
    std::vector<char> const vertexShaderCode = vertexFile.readCharSequence();
    std::vector<char> const fragmentShaderCode = fragmentFile.readCharSequence();

    vk::ShaderModule vertexShaderModule = shaderHelper.createShaderModule(device, vertexShaderCode);
    vk::ShaderModule fragmentShaderModule = shaderHelper.createShaderModule(device, fragmentShaderCode);

    vk::PipelineShaderStageCreateInfo vertStageInfo{};
    vertStageInfo.stage = vk::ShaderStageFlagBits::eVertex;
    vertStageInfo.module = vertexShaderModule;
    vertStageInfo.pName = "main";

    vk::PipelineShaderStageCreateInfo fragStageInfo{};
    fragStageInfo.stage = vk::ShaderStageFlagBits::eFragment;
    fragStageInfo.module = fragmentShaderModule;
    fragStageInfo.pName = "main";

    std::array skyStages = {vertStageInfo, fragStageInfo};

    vk::VertexInputBindingDescription bindingDescription{};
    bindingDescription.binding = 0;
    bindingDescription.stride = sizeof(Vertex);
    bindingDescription.inputRate = vk::VertexInputRate::eVertex;

    std::array<vk::VertexInputAttributeDescription, 4> attributeDescriptions = vertex::getVertexInputAttributeDesc();

    vk::PipelineVertexInputStateCreateInfo vertexInputInfo{};
    vertexInputInfo.vertexBindingDescriptionCount = 1;
    vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
    vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
    vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();

    vk::PipelineInputAssemblyStateCreateInfo inputAssembly{};
    inputAssembly.topology = vk::PrimitiveTopology::eTriangleList;
    inputAssembly.primitiveRestartEnable = VK_FALSE;

    vk::PipelineDepthStencilStateCreateInfo depthStencil{};
    depthStencil.depthTestEnable = VK_FALSE;
    depthStencil.depthWriteEnable = VK_FALSE;
    depthStencil.depthCompareOp = vk::CompareOp::eAlways;
    depthStencil.depthBoundsTestEnable = VK_FALSE;
    depthStencil.stencilTestEnable = VK_FALSE;

    vk::PipelineColorBlendAttachmentState colorBlendAttachment{};
    colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
    colorBlendAttachment.blendEnable = VK_FALSE;

    vk::PipelineColorBlendStateCreateInfo colorBlending{};
    colorBlending.logicOpEnable = VK_FALSE;
    colorBlending.attachmentCount = 1;
    colorBlending.pAttachments = &colorBlendAttachment;

    std::vector<vk::DynamicState> dynamicStates = {vk::DynamicState::eViewport, vk::DynamicState::eScissor};
    vk::PipelineDynamicStateCreateInfo dynamicState{};
    dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
    dynamicState.pDynamicStates = dynamicStates.data();

    vk::PipelineRasterizationStateCreateInfo rasterizer{};
    rasterizer.depthClampEnable = VK_FALSE;
    rasterizer.rasterizerDiscardEnable = VK_FALSE;
    rasterizer.polygonMode = vk::PolygonMode::eFill;
    rasterizer.lineWidth = 1.0f;
    rasterizer.cullMode = vk::CullModeFlagBits::eNone;
    rasterizer.depthBiasEnable = VK_FALSE;

    vk::PipelineMultisampleStateCreateInfo multisampling{};
    multisampling.sampleShadingEnable = VK_FALSE;
    multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1;

    vk::PipelineViewportStateCreateInfo viewportState{};
    viewportState.viewportCount = 1;
    viewportState.scissorCount = 1;

    std::array<vk::DescriptorSetLayout, 2> combinedLayouts = {sharedLayout, descriptorSetLayout};
    vk::PipelineLayoutCreateInfo pipelineLayoutInfo{};
    pipelineLayoutInfo.setLayoutCount = 2;
    pipelineLayoutInfo.pSetLayouts = combinedLayouts.data();

    vk::PushConstantRange pushConstantRange{};
    pushConstantRange.stageFlags = vk::ShaderStageFlagBits::eFragment;
    pushConstantRange.offset = 0;
    pushConstantRange.size = sizeof(uint32_t);
    pipelineLayoutInfo.pushConstantRangeCount = 1;
    pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;

    auto layoutRes = device->getLogicalDevice().createPipelineLayout(pipelineLayoutInfo);
    ASSERT_VULKAN(VkResult(layoutRes.result), "Failed to create skybox pipeline layout!");
    pipelineLayout = layoutRes.value;

    vk::GraphicsPipelineCreateInfo pipelineInfo{};
    pipelineInfo.stageCount = static_cast<uint32_t>(skyStages.size());
    pipelineInfo.pStages = skyStages.data();
    pipelineInfo.pVertexInputState = &vertexInputInfo;
    pipelineInfo.pInputAssemblyState = &inputAssembly;
    pipelineInfo.pViewportState = &viewportState;
    pipelineInfo.pDynamicState = &dynamicState;
    pipelineInfo.pRasterizationState = &rasterizer;
    pipelineInfo.pMultisampleState = &multisampling;
    pipelineInfo.pDepthStencilState = &depthStencil;
    pipelineInfo.pColorBlendState = &colorBlending;
    pipelineInfo.layout = pipelineLayout;
    pipelineInfo.renderPass = renderPass;
    pipelineInfo.subpass = 0;

    auto pipelineRes = device->getLogicalDevice().createGraphicsPipeline(nullptr, pipelineInfo);
    ASSERT_VULKAN(VkResult(pipelineRes.result), "Failed to create skybox graphics pipeline!");
    graphicsPipeline = pipelineRes.value;

    device->getLogicalDevice().destroyShaderModule(vertexShaderModule);
    device->getLogicalDevice().destroyShaderModule(fragmentShaderModule);
}

void SkyBox::createMesh(vk::CommandPool commandPool)
{
    // Fullscreen quad indices
    std::vector<unsigned int> indices = {
        0, 1, 2,
        2, 1, 3
    };

    // Fullscreen quad vertices with UVs
    std::vector<Vertex> vertices = {
        Vertex(glm::vec3(-1.0F, -1.0F, 0.0F), glm::vec3(0), glm::vec3(0), glm::vec2(0.0F, 0.0F)),
        Vertex(glm::vec3( 1.0F, -1.0F, 0.0F), glm::vec3(0), glm::vec3(0), glm::vec2(1.0F, 0.0F)),
        Vertex(glm::vec3(-1.0F,  1.0F, 0.0F), glm::vec3(0), glm::vec3(0), glm::vec2(0.0F, 1.0F)),
        Vertex(glm::vec3( 1.0F,  1.0F, 0.0F), glm::vec3(0), glm::vec3(0), glm::vec2(1.0F, 1.0F))
    };

    skyMesh = std::make_unique<Kataglyphis::Mesh>();
    std::vector<unsigned int> materialIndex = {0};
    std::vector<ObjMaterial> materials = {ObjMaterial{}};
    skyMesh = std::make_unique<Mesh>(device, device->getGraphicsQueue(), commandPool, vertices, indices, materialIndex, materials);
}

void SkyBox::recordCommands(vk::CommandBuffer &commandBuffer, uint32_t image_index, const std::vector<vk::DescriptorSet> &descriptorSets, bool skyboxEnabled)
{
    if (image_index >= framebuffers.size() || framebuffers.empty()) {
        spdlog::error("SkyBox: framebuffer not created or index out of range!");
        return;
    }

    spdlog::debug("SkyBox: enabled={}, fbSize={}, indexCount={}", skyboxEnabled, framebuffers.size(), skyMesh->getIndexCount());

    vk::RenderPassBeginInfo renderPassInfo{};
    renderPassInfo.renderPass = renderPass;
    renderPassInfo.framebuffer = framebuffers[image_index];
    renderPassInfo.renderArea.offset = vk::Offset2D{0, 0};
    renderPassInfo.renderArea.extent = vk::Extent2D{framebufferWidth, framebufferHeight};

    std::array clearValues = {
        vk::ClearValue{std::array<float, 4>{0.0f, 0.0f, 0.0f, 1.0f}},
        vk::ClearValue{vk::ClearDepthStencilValue{1.0f, 0}}
    };
    renderPassInfo.clearValueCount = 2;
    renderPassInfo.pClearValues = clearValues.data();

    commandBuffer.beginRenderPass(renderPassInfo, vk::SubpassContents::eInline);

    vk::Viewport viewport{};
    viewport.x = 0.0f;
    viewport.y = 0.0f;
    viewport.width = static_cast<float>(framebufferWidth);
    viewport.height = static_cast<float>(framebufferHeight);
    viewport.minDepth = 0.0f;
    viewport.maxDepth = 1.0f;
    commandBuffer.setViewport(0, 1, &viewport);

    vk::Rect2D scissor{};
    scissor.offset = vk::Offset2D{ 0, 0 };
    scissor.extent = vk::Extent2D{ framebufferWidth, framebufferHeight };
    commandBuffer.setScissor(0, 1, &scissor);

    commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, graphicsPipeline);

    std::vector<vk::DescriptorSet> skyboxDescriptorSets = {descriptorSets[0], descriptorSet};
    commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, skyboxDescriptorSets, nullptr);

    uint32_t skyboxEnabledVal = skyboxEnabled ? 1u : 0u;
    commandBuffer.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eFragment, 0, sizeof(uint32_t), &skyboxEnabledVal);

    std::vector<vk::Buffer> const vertex_buffers = { skyMesh->getVertexBuffer() };
    vk::DeviceSize offsets[] = { 0 };
    commandBuffer.bindVertexBuffers(0, vertex_buffers, offsets);
    commandBuffer.bindIndexBuffer(skyMesh->getIndexBuffer(), 0, vk::IndexType::eUint32);

    commandBuffer.drawIndexed(skyMesh->getIndexCount(), 1, 0, 0, 0);
    commandBuffer.endRenderPass();
}

void SkyBox::cleanUp()
{
    if (device) {
        for (auto fb : framebuffers) {
            device->getLogicalDevice().destroyFramebuffer(fb);
        }
        framebuffers.clear();
        if (graphicsPipeline) { device->getLogicalDevice().destroyPipeline(graphicsPipeline); }
        if (pipelineLayout) { device->getLogicalDevice().destroyPipelineLayout(pipelineLayout); }
        if (descriptorSetLayout) { device->getLogicalDevice().destroyDescriptorSetLayout(descriptorSetLayout); }
        if (descriptorPool) { device->getLogicalDevice().destroyDescriptorPool(descriptorPool); }
        if (renderPass) { device->getLogicalDevice().destroyRenderPass(renderPass); }
    }
    if (skyMesh) {
        skyMesh->cleanUp();
    }
    if (cubeMapTexture) {
        cubeMapTexture->cleanUp();
    }
}

SkyBox::~SkyBox() = default;

void SkyBox::destroyFramebuffers()
{
    for (auto fb : framebuffers) { device->getLogicalDevice().destroyFramebuffer(fb); }
    framebuffers.clear();
}

void SkyBox::recreateFrameResources(size_t count, const std::vector<vk::ImageView>& imageViews, const std::vector<vk::ImageView>& depthViews, uint32_t width, uint32_t height)
{
    createFramebuffers(count, imageViews, depthViews, width, height);
}

}