// ============================================= // Aster: box.cpp // Copyright (c) 2020-2024 Anish Bhobe // ============================================= #include "buffer.h" #include "constants.h" #include "context.h" #include "device.h" #include "physical_device.h" #include "window.h" #include "global.h" #include "pipeline.h" #include "swapchain.h" #include "helpers.h" #include constexpr u32 MAX_FRAMES_IN_FLIGHT = 3; constexpr auto VERTEX_SHADER_FILE = "shader/box.vert.glsl.spv"; constexpr auto FRAGMENT_SHADER_FILE = "shader/box.frag.glsl.spv"; #define AbortIfFailed(RESULT) \ do \ { \ vk::Result _checkResultValue_; \ ERROR_IF(Failed(_checkResultValue_ = Cast(RESULT)), "Cause: {}", _checkResultValue_) \ THEN_ABORT(_checkResultValue_); \ } while (false) #define AbortIfFailedMV(RESULT, MSG, EXTRA) \ do \ { \ vk::Result _checkResultValue_; \ ERROR_IF(Failed(_checkResultValue_ = Cast(RESULT)), MSG " Cause: {}", EXTRA, _checkResultValue_) \ THEN_ABORT(_checkResultValue_); \ } while (false) #define AbortIfFailedM(RESULT, MSG) \ do \ { \ auto _checkResultValue_ = Cast(RESULT); \ ERROR_IF(Failed(_checkResultValue_), MSG " Cause: {}", _checkResultValue_) THEN_ABORT(_checkResultValue_); \ } while (false) vk::ShaderModule CreateShader(const Device *device, cstr shaderFile); Pipeline CreatePipeline(const Device *device, const Swapchain *swapchain); struct Vertex { vec3 m_Position; constexpr static vk::VertexInputBindingDescription GetBinding(const u32 binding) { return {.binding = binding, .stride = sizeof(Vertex), .inputRate = vk::VertexInputRate::eVertex}; } constexpr static eastl::array GetAttributes(const u32 binding) { return { vk::VertexInputAttributeDescription{ .location = 0, .binding = binding, .format = vk::Format::eR32G32B32Sfloat, .offset = offsetof(Vertex, m_Position), }, }; } }; struct Camera { mat4 m_Model; mat4 m_View; mat4 m_Perspective; }; struct Frame { const Device *m_Device; vk::CommandPool m_Pool; vk::CommandBuffer m_CommandBuffer; vk::Fence m_FrameAvailableFence; vk::Semaphore m_ImageAcquireSem; vk::Semaphore m_RenderFinishSem; Frame(const Device *device, u32 queueFamilyIndex, u32 frameCount); ~Frame(); }; int main(int, char **) { MIN_LOG_LEVEL(Logger::LogType::eInfo); Context context = {"Box", VERSION}; Window window = {"Box (Aster)", &context, {640, 480}}; PhysicalDevices physicalDevices = {&window, &context}; PhysicalDevice deviceToUse = FindSuitableDevice(physicalDevices); INFO("Using {} as the primary device.", deviceToUse.m_DeviceProperties.deviceName.data()); Features enabledDeviceFeatures = {.m_Vulkan13Features = {.dynamicRendering = true}}; QueueAllocation queueAllocation = FindAppropriateQueueAllocation(&deviceToUse); Device device = {&context, &deviceToUse, &enabledDeviceFeatures, {queueAllocation}, "Primary Device"}; vk::Queue commandQueue = device.GetQueue(queueAllocation.m_Family, 0); Swapchain swapchain = {&window, &device, "Primary Chain"}; Pipeline pipeline = CreatePipeline(&device, &swapchain); Camera camera = { .m_Model = {1.0f}, .m_View = glm::lookAt(vec3(0.0f, 2.0f, 2.0f), vec3(0.0f), vec3(0.0f, 1.0f, 0.0f)), .m_Perspective = glm::perspective( 70_deg, Cast(swapchain.m_Extent.width) / Cast(swapchain.m_Extent.height), 0.1f, 100.0f), }; vk::DescriptorPool descriptorPool; vk::DescriptorSet descriptorSet; { vk::DescriptorSetLayout descriptorSetLayout = pipeline.m_SetLayouts.front(); vk::DescriptorPoolSize poolSize = { .type = vk::DescriptorType::eUniformBuffer, .descriptorCount = 1, }; vk::DescriptorPoolCreateInfo descriptorPoolCreateInfo = { .maxSets = 1, .poolSizeCount = 1, .pPoolSizes = &poolSize}; AbortIfFailed(device.m_Device.createDescriptorPool(&descriptorPoolCreateInfo, nullptr, &descriptorPool)); vk::DescriptorSetAllocateInfo descriptorSetAllocateInfo = { .descriptorPool = descriptorPool, .descriptorSetCount = 1, .pSetLayouts = &descriptorSetLayout, }; AbortIfFailed(device.m_Device.allocateDescriptorSets(&descriptorSetAllocateInfo, &descriptorSet)); } vk::CommandPool copyPool; vk::CommandBuffer copyBuffer; { vk::CommandPoolCreateInfo poolCreateInfo = { .flags = vk::CommandPoolCreateFlagBits::eTransient, .queueFamilyIndex = queueAllocation.m_Family, }; AbortIfFailedM(device.m_Device.createCommandPool(&poolCreateInfo, nullptr, ©Pool), "Copy command pool creation failed."); vk::CommandBufferAllocateInfo bufferAllocateInfo = { .commandPool = copyPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1, }; AbortIfFailedM(device.m_Device.allocateCommandBuffers(&bufferAllocateInfo, ©Buffer), "Copy command buffer allocation failed."); } // eastl::array vertices{}; eastl::array vertices = { vec3(-0.5f, -0.5f, -0.5f), vec3(0.5f, -0.5f, -0.5f), vec3(0.5f, 0.5f, -0.5f), vec3(0.5f, 0.5f, -0.5f), vec3(-0.5f, 0.5f, -0.5f), vec3(-0.5f, -0.5f, -0.5f), vec3(-0.5f, -0.5f, 0.5f), vec3(0.5f, -0.5f, 0.5f), vec3(0.5f, 0.5f, 0.5f), vec3(0.5f, 0.5f, 0.5f), vec3(-0.5f, 0.5f, 0.5f), vec3(-0.5f, -0.5f, 0.5f), vec3(-0.5f, 0.5f, 0.5f), vec3(-0.5f, 0.5f, -0.5f), vec3(-0.5f, -0.5f, -0.5f), vec3(-0.5f, -0.5f, -0.5f), vec3(-0.5f, -0.5f, 0.5f), vec3(-0.5f, 0.5f, 0.5f), vec3(0.5f, 0.5f, 0.5f), vec3(0.5f, 0.5f, -0.5f), vec3(0.5f, -0.5f, -0.5f), vec3(0.5f, -0.5f, -0.5f), vec3(0.5f, -0.5f, 0.5f), vec3(0.5f, 0.5f, 0.5f), vec3(-0.5f, -0.5f, -0.5f), vec3(0.5f, -0.5f, -0.5f), vec3(0.5f, -0.5f, 0.5f), vec3(0.5f, -0.5f, 0.5f), vec3(-0.5f, -0.5f, 0.5f), vec3(-0.5f, -0.5f, -0.5f), vec3(-0.5f, 0.5f, -0.5f), vec3(0.5f, 0.5f, -0.5f), vec3(0.5f, 0.5f, 0.5f), vec3(0.5f, 0.5f, 0.5f), vec3(-0.5f, 0.5f, 0.5f), vec3(-0.5f, 0.5f, -0.5f), }; /* * 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top right 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom right -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom left -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f */ VertexBuffer vbo; vbo.Init(&device, vertices.size() * sizeof vertices[0], "VBO"); { StagingBuffer staging; staging.Init(&device, vertices.size() * sizeof vertices[0], "Staging"); staging.Write(&device, 0, vertices.size() * sizeof vertices[0], vertices.data()); vk::Fence fence; vk::FenceCreateInfo fenceCreateInfo = {}; AbortIfFailed(device.m_Device.createFence(&fenceCreateInfo, nullptr, &fence)); vk::CommandBufferBeginInfo beginInfo = {.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; AbortIfFailed(copyBuffer.begin(&beginInfo)); vk::BufferCopy bufferCopy = {.srcOffset = 0, .dstOffset = 0, .size = staging.GetSize()}; copyBuffer.copyBuffer(staging.m_Buffer, vbo.m_Buffer, 1, &bufferCopy); AbortIfFailed(copyBuffer.end()); vk::SubmitInfo submitInfo = { .commandBufferCount = 1, .pCommandBuffers = ©Buffer, }; AbortIfFailed(commandQueue.submit(1, &submitInfo, fence)); INFO("Submit copy"); AbortIfFailed(device.m_Device.waitForFences(1, &fence, true, MaxValue)); INFO("Fence wait"); AbortIfFailedM(device.m_Device.resetCommandPool(copyPool, {}), "Couldn't reset command pool."); device.m_Device.destroy(fence, nullptr); staging.Destroy(&device); } UniformBuffer ubo; ubo.Init(&device, sizeof camera, "Camera UBO"); ubo.Write(&device, 0, sizeof camera, &camera); vk::DescriptorBufferInfo descriptorBufferInfo = { .buffer = ubo.m_Buffer, .offset = 0, .range = ubo.GetSize(), }; vk::WriteDescriptorSet writeDescriptors = { .dstSet = descriptorSet, .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &descriptorBufferInfo, }; device.m_Device.updateDescriptorSets(1, &writeDescriptors, 0, nullptr); // Persistent variables vk::Viewport viewport = { .x = 0, .y = Cast(swapchain.m_Extent.height), .width = Cast(swapchain.m_Extent.width), .height = -Cast(swapchain.m_Extent.height), .minDepth = 0.0, .maxDepth = 1.0, }; vk::Rect2D scissor = { .offset = {0, 0}, .extent = swapchain.m_Extent, }; vk::ImageSubresourceRange subresourceRange = { .aspectMask = vk::ImageAspectFlagBits::eColor, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }; vk::ImageMemoryBarrier topOfThePipeBarrier = { .oldLayout = vk::ImageLayout::eUndefined, .newLayout = vk::ImageLayout::eColorAttachmentOptimal, .srcQueueFamilyIndex = queueAllocation.m_Family, .dstQueueFamilyIndex = queueAllocation.m_Family, .subresourceRange = subresourceRange, }; vk::ImageMemoryBarrier renderToPresentBarrier = { .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, .newLayout = vk::ImageLayout::ePresentSrcKHR, .srcQueueFamilyIndex = queueAllocation.m_Family, .dstQueueFamilyIndex = queueAllocation.m_Family, .subresourceRange = subresourceRange, }; // Frames eastl::fixed_vector frames; for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { frames.emplace_back(&device, queueAllocation.m_Family, i); } Time::Init(); INFO("Starting loop"); u32 frameIndex = 0; while (window.Poll()) { Frame *currentFrame = &frames[frameIndex]; Time::Update(); camera.m_Model *= rotate(mat4{1.0f}, Cast(45.0_deg * Time::m_Delta), vec3(0.0f, 1.0f, 0.0f)); ubo.Write(&device, 0, sizeof camera, &camera); AbortIfFailedMV(device.m_Device.waitForFences(1, ¤tFrame->m_FrameAvailableFence, true, MaxValue), "Waiting for fence {} failed.", frameIndex); u32 imageIndex; auto result = device.m_Device.acquireNextImageKHR(swapchain.m_Swapchain, MaxValue, currentFrame->m_ImageAcquireSem, nullptr, &imageIndex); if (Failed(result)) { switch (result) { case vk::Result::eErrorOutOfDateKHR: case vk::Result::eSuboptimalKHR: INFO("Recreating Swapchain. Cause: {}", result); swapchain.Create(&window); viewport.y = Cast(swapchain.m_Extent.height); viewport.width = Cast(swapchain.m_Extent.width); viewport.height = -Cast(swapchain.m_Extent.height); scissor.extent = swapchain.m_Extent; continue; // Image acquire has failed. We move to the next frame. default: AbortIfFailedMV(result, "Waiting for swapchain image {} failed.", frameIndex); } } // Reset fences here. In case swapchain was out of date, we leave the fences signalled. AbortIfFailedMV(device.m_Device.resetFences(1, ¤tFrame->m_FrameAvailableFence), "Fence {} reset failed.", frameIndex); AbortIfFailedMV(device.m_Device.resetCommandPool(currentFrame->m_Pool, {}), "Command pool {} reset failed.", frameIndex); vk::ImageView currentImageView = swapchain.m_ImageViews[imageIndex]; vk::Image currentImage = swapchain.m_Images[imageIndex]; vk::CommandBuffer cmd = currentFrame->m_CommandBuffer; topOfThePipeBarrier.image = currentImage; renderToPresentBarrier.image = currentImage; vk::CommandBufferBeginInfo beginInfo = {.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; AbortIfFailed(cmd.begin(&beginInfo)); cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eColorAttachmentOutput, {}, 0, nullptr, 0, nullptr, 1, &topOfThePipeBarrier); // Render vk::RenderingAttachmentInfo attachmentInfo = { .imageView = currentImageView, .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, .resolveMode = vk::ResolveModeFlagBits::eNone, .loadOp = vk::AttachmentLoadOp::eClear, .storeOp = vk::AttachmentStoreOp::eStore, .clearValue = vk::ClearColorValue{0.0f, 0.0f, 0.0f, 1.0f}, }; vk::RenderingInfo renderingInfo = { .renderArea = {.extent = swapchain.m_Extent}, .layerCount = 1, .colorAttachmentCount = 1, .pColorAttachments = &attachmentInfo, }; cmd.beginRendering(&renderingInfo); cmd.setViewport(0, 1, &viewport); cmd.setScissor(0, 1, &scissor); cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.m_Pipeline); cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline.m_Layout, 0, 1, &descriptorSet, 0, nullptr); usize offsets = 0; cmd.bindVertexBuffers(0, 1, &vbo.m_Buffer, &offsets); cmd.draw(Cast(vertices.size()), 1, 0, 0); cmd.endRendering(); cmd.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eBottomOfPipe, {}, 0, nullptr, 0, nullptr, 1, &renderToPresentBarrier); AbortIfFailed(cmd.end()); vk::PipelineStageFlags waitDstStage = vk::PipelineStageFlagBits::eColorAttachmentOutput; vk::SubmitInfo submitInfo = { .waitSemaphoreCount = 1, .pWaitSemaphores = ¤tFrame->m_ImageAcquireSem, .pWaitDstStageMask = &waitDstStage, .commandBufferCount = 1, .pCommandBuffers = &cmd, .signalSemaphoreCount = 1, .pSignalSemaphores = ¤tFrame->m_RenderFinishSem, }; AbortIfFailed(commandQueue.submit(1, &submitInfo, currentFrame->m_FrameAvailableFence)); vk::PresentInfoKHR presentInfo = { .waitSemaphoreCount = 1, .pWaitSemaphores = ¤tFrame->m_RenderFinishSem, .swapchainCount = 1, .pSwapchains = &swapchain.m_Swapchain, .pImageIndices = &imageIndex, .pResults = nullptr, }; result = commandQueue.presentKHR(&presentInfo); if (Failed(result)) { switch (result) { case vk::Result::eErrorOutOfDateKHR: case vk::Result::eSuboptimalKHR: INFO("Recreating Swapchain. Cause: {}", result); swapchain.Create(&window); viewport.y = Cast(swapchain.m_Extent.height); viewport.width = Cast(swapchain.m_Extent.width); viewport.height = -Cast(swapchain.m_Extent.height); scissor.extent = swapchain.m_Extent; break; // Present failed. We redo the frame. default: AbortIfFailedM(result, "Swapchain Present failed."); } } frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; } AbortIfFailed(device.m_Device.waitIdle()); ubo.Destroy(&device); device.m_Device.destroy(descriptorPool, nullptr); device.m_Device.destroy(copyPool, nullptr); vbo.Destroy(&device); return 0; } Frame::Frame(const Device *device, const u32 queueFamilyIndex, const u32 frameCount) { m_Device = device; const vk::CommandPoolCreateInfo commandPoolCreateInfo = { .flags = vk::CommandPoolCreateFlagBits::eTransient, .queueFamilyIndex = queueFamilyIndex, }; AbortIfFailedMV(device->m_Device.createCommandPool(&commandPoolCreateInfo, nullptr, &m_Pool), "Could not command pool for frame {}", frameCount); constexpr vk::FenceCreateInfo fenceCreateInfo = {.flags = vk::FenceCreateFlagBits::eSignaled}; AbortIfFailedMV(device->m_Device.createFence(&fenceCreateInfo, nullptr, &m_FrameAvailableFence), "Could not create a fence for frame {}", frameCount); constexpr vk::SemaphoreCreateInfo semaphoreCreateInfo = {}; AbortIfFailedMV(device->m_Device.createSemaphore(&semaphoreCreateInfo, nullptr, &m_ImageAcquireSem), "Could not create IA semaphore for frame {}.", frameCount); AbortIfFailedMV(device->m_Device.createSemaphore(&semaphoreCreateInfo, nullptr, &m_RenderFinishSem), "Could not create RF semaphore for frame {}.", frameCount); const vk::CommandBufferAllocateInfo allocateInfo = { .commandPool = m_Pool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1, }; AbortIfFailed(m_Device->m_Device.allocateCommandBuffers(&allocateInfo, &m_CommandBuffer)); DEBUG("Frame {} created successfully.", frameCount); } Pipeline CreatePipeline(const Device *device, const Swapchain *swapchain) { // Pipeline Setup auto vertexShaderModule = CreateShader(device, VERTEX_SHADER_FILE); auto fragmentShaderModule = CreateShader(device, FRAGMENT_SHADER_FILE); eastl::array shaderStages = {{ { .stage = vk::ShaderStageFlagBits::eVertex, .module = vertexShaderModule, .pName = "main", }, { .stage = vk::ShaderStageFlagBits::eFragment, .module = fragmentShaderModule, .pName = "main", }, }}; vk::DescriptorSetLayoutBinding descriptorSetLayoutBinding = { .binding = 0, .descriptorType = vk::DescriptorType::eUniformBuffer, .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, }; vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = { .bindingCount = 1, .pBindings = &descriptorSetLayoutBinding, }; vk::DescriptorSetLayout descriptorSetLayout; AbortIfFailed( device->m_Device.createDescriptorSetLayout(&descriptorSetLayoutCreateInfo, nullptr, &descriptorSetLayout)); vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo = { .setLayoutCount = 1, .pSetLayouts = &descriptorSetLayout, .pushConstantRangeCount = 0, .pPushConstantRanges = nullptr, }; vk::PipelineLayout pipelineLayout; AbortIfFailed(device->m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); device->SetName(pipelineLayout, "Box Layout"); vk::VertexInputBindingDescription inputBindingDescription = Vertex::GetBinding(0); auto inputAttributeDescription = Vertex::GetAttributes(0); vk::PipelineVertexInputStateCreateInfo vertexInputStateCreateInfo = { .vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &inputBindingDescription, .vertexAttributeDescriptionCount = Cast(inputAttributeDescription.size()), .pVertexAttributeDescriptions = inputAttributeDescription.data(), }; vk::PipelineInputAssemblyStateCreateInfo inputAssemblyStateCreateInfo = { .topology = vk::PrimitiveTopology::eTriangleList, .primitiveRestartEnable = false, }; vk::PipelineViewportStateCreateInfo viewportStateCreateInfo = { .viewportCount = 1, .scissorCount = 1, }; vk::PipelineRasterizationStateCreateInfo rasterizationStateCreateInfo = { .depthClampEnable = false, .rasterizerDiscardEnable = false, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eNone, .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = false, .lineWidth = 1.0, }; vk::PipelineMultisampleStateCreateInfo multisampleStateCreateInfo = { .rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = false, }; vk::PipelineDepthStencilStateCreateInfo depthStencilStateCreateInfo = { .depthTestEnable = false, .depthWriteEnable = false, }; vk::PipelineColorBlendAttachmentState colorBlendAttachmentState = { .blendEnable = false, .srcColorBlendFactor = vk::BlendFactor::eSrcColor, .dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcColor, .colorBlendOp = vk::BlendOp::eAdd, .srcAlphaBlendFactor = vk::BlendFactor::eSrcAlpha, .dstAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha, .alphaBlendOp = vk::BlendOp::eAdd, .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA, }; vk::PipelineColorBlendStateCreateInfo colorBlendStateCreateInfo = { .logicOpEnable = false, .attachmentCount = 1, .pAttachments = &colorBlendAttachmentState, }; eastl::array dynamicStates = { vk::DynamicState::eScissor, vk::DynamicState::eViewport, }; vk::PipelineDynamicStateCreateInfo dynamicStateCreateInfo = { .dynamicStateCount = Cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data(), }; vk::PipelineRenderingCreateInfo renderingCreateInfo = { .viewMask = 0, .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapchain->m_Format, }; vk::GraphicsPipelineCreateInfo pipelineCreateInfo = { .pNext = &renderingCreateInfo, .stageCount = Cast(shaderStages.size()), .pStages = shaderStages.data(), .pVertexInputState = &vertexInputStateCreateInfo, .pInputAssemblyState = &inputAssemblyStateCreateInfo, .pViewportState = &viewportStateCreateInfo, .pRasterizationState = &rasterizationStateCreateInfo, .pMultisampleState = &multisampleStateCreateInfo, .pDepthStencilState = &depthStencilStateCreateInfo, .pColorBlendState = &colorBlendStateCreateInfo, .pDynamicState = &dynamicStateCreateInfo, .layout = pipelineLayout, }; vk::Pipeline pipeline; AbortIfFailed(device->m_Device.createGraphicsPipelines(nullptr, 1, &pipelineCreateInfo, nullptr, &pipeline)); device->SetName(pipeline, "Box Pipeline"); device->m_Device.destroy(vertexShaderModule, nullptr); device->m_Device.destroy(fragmentShaderModule, nullptr); return {device, pipelineLayout, pipeline, {descriptorSetLayout}}; } vk::ShaderModule CreateShader(const Device *device, cstr shaderFile) { eastl::vector shaderCode = ReadFile(shaderFile); const vk::ShaderModuleCreateInfo shaderModuleCreateInfo = { .codeSize = shaderCode.size() * sizeof(u32), .pCode = shaderCode.data(), }; vk::ShaderModule shaderModule; AbortIfFailedMV(device->m_Device.createShaderModule(&shaderModuleCreateInfo, nullptr, &shaderModule), "Shader {} could not be created.", shaderFile); return shaderModule; } Frame::~Frame() { m_Device->m_Device.destroy(m_RenderFinishSem, nullptr); m_Device->m_Device.destroy(m_ImageAcquireSem, nullptr); m_Device->m_Device.destroy(m_FrameAvailableFence, nullptr); m_Device->m_Device.destroy(m_Pool, nullptr); DEBUG("Destoryed Frame"); }