// ============================================= // Aster: model_render.cpp // Copyright (c) 2020-2024 Anish Bhobe // ============================================= #define TINYGLTF_NOEXCEPTION #define JSON_NOEXCEPTION #define TINYGLTF_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_WRITE_IMPLEMENTATION #include "buffer.h" #include "constants.h" #include "context.h" #include "device.h" #include "global.h" #include "image.h" #include "physical_device.h" #include "pipeline.h" #include "swapchain.h" #include "window.h" #include "frame.h" #include "helpers.h" #include "pipeline_utils.h" #include "render_resource_manager.h" #include #include #include constexpr u32 MAX_FRAMES_IN_FLIGHT = 3; constexpr auto MODEL_FILE = "model/Box.glb"; struct ImageFile { u8 *m_Data = nullptr; u32 m_Width = 512; u32 m_Height = 512; u32 m_NumChannels = 4; bool Load(vec4 color); [[nodiscard]] usize GetSize() const; ~ImageFile(); }; struct Camera { mat4 m_Model; mat4 m_View; mat4 m_Perspective; }; constexpr auto GLTF_ASCII_FILE_EXTENSION = ".gltf"; constexpr auto GLTF_BINARY_FILE_EXTENSION = ".glb"; void LoadModel(cstr path) { namespace fs = std::filesystem; tinygltf::Model model; tinygltf::TinyGLTF loader; const auto fsPath = fs::absolute(path); const auto ext = fsPath.extension(); if (ext.c_str() == GLTF_ASCII_FILE_EXTENSION) { std::string err; std::string warn; if (loader.LoadASCIIFromFile(&model, &err, &warn, fsPath.generic_string())) { ERROR_IF(!err.empty(), "{}", err) ELSE_IF_WARN(!warn.empty(), "{}", warn); } } if (ext.c_str() == GLTF_BINARY_FILE_EXTENSION) { std::string err; std::string warn; if (loader.LoadBinaryFromFile(&model, &err, &warn, fsPath.generic_string())) { ERROR_IF(!err.empty(), "{}", err) ELSE_IF_WARN(!warn.empty(), "{}", warn); } } } 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_Vulkan10Features = {.samplerAnisotropy = true}, .m_Vulkan12Features = {.descriptorIndexing = true}, .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"}; RenderResourceManager resourceManager(&device); Pipeline pipeline = CreatePipeline(&device, &swapchain, &resourceManager); 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[1]; eastl::array poolSizes = { vk::DescriptorPoolSize{ .type = vk::DescriptorType::eUniformBuffer, .descriptorCount = 1, }, vk::DescriptorPoolSize{ .type = vk::DescriptorType::eCombinedImageSampler, .descriptorCount = 1, }, }; vk::DescriptorPoolCreateInfo descriptorPoolCreateInfo = { .maxSets = 1, .poolSizeCount = Cast(poolSizes.size()), .pPoolSizes = poolSizes.data()}; 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 = { Vertex{.m_Position = vec3(0.5f, 0.5f, -0.5f), .m_UV0 = vec2(1.0f, 1.0f)}, Vertex{.m_Position = vec3(0.5f, -0.5f, -0.5f), .m_UV0 = vec2(1.0f, 0.0f)}, Vertex{.m_Position = vec3(-0.5f, -0.5f, -0.5f), .m_UV0 = vec2(0.0f, 0.0f)}, Vertex{.m_Position = vec3(-0.5f, -0.5f, -0.5f), .m_UV0 = vec2(0.0f, 0.0f)}, Vertex{.m_Position = vec3(-0.5f, 0.5f, -0.5f), .m_UV0 = vec2(0.0f, 1.0f)}, Vertex{.m_Position = vec3(0.5f, 0.5f, -0.5f), .m_UV0 = vec2(1.0f, 1.0f)}, Vertex{.m_Position = vec3(-0.5f, -0.5f, 0.5f), .m_UV0 = vec2(0.0f, 0.0f)}, Vertex{.m_Position = vec3(0.5f, -0.5f, 0.5f), .m_UV0 = vec2(1.0f, 0.0f)}, Vertex{.m_Position = vec3(0.5f, 0.5f, 0.5f), .m_UV0 = vec2(1.0f, 1.0f)}, Vertex{.m_Position = vec3(0.5f, 0.5f, 0.5f), .m_UV0 = vec2(1.0f, 1.0f)}, Vertex{.m_Position = vec3(-0.5f, 0.5f, 0.5f), .m_UV0 = vec2(0.0f, 1.0f)}, Vertex{.m_Position = vec3(-0.5f, -0.5f, 0.5f), .m_UV0 = vec2(0.0f, 0.0f)}, Vertex{.m_Position = vec3(-0.5f, 0.5f, 0.5f), .m_UV0 = vec2(1.0f, 1.0f)}, Vertex{.m_Position = vec3(-0.5f, 0.5f, -0.5f), .m_UV0 = vec2(1.0f, 0.0f)}, Vertex{.m_Position = vec3(-0.5f, -0.5f, -0.5f), .m_UV0 = vec2(0.0f, 0.0f)}, Vertex{.m_Position = vec3(-0.5f, -0.5f, -0.5f), .m_UV0 = vec2(0.0f, 0.0f)}, Vertex{.m_Position = vec3(-0.5f, -0.5f, 0.5f), .m_UV0 = vec2(0.0f, 1.0f)}, Vertex{.m_Position = vec3(-0.5f, 0.5f, 0.5f), .m_UV0 = vec2(1.0f, 1.0f)}, Vertex{.m_Position = vec3(0.5f, -0.5f, -0.5f), .m_UV0 = vec2(0.0f, 0.0f)}, Vertex{.m_Position = vec3(0.5f, 0.5f, -0.5f), .m_UV0 = vec2(1.0f, 0.0f)}, Vertex{.m_Position = vec3(0.5f, 0.5f, 0.5f), .m_UV0 = vec2(1.0f, 1.0f)}, Vertex{.m_Position = vec3(0.5f, 0.5f, 0.5f), .m_UV0 = vec2(1.0f, 1.0f)}, Vertex{.m_Position = vec3(0.5f, -0.5f, 0.5f), .m_UV0 = vec2(0.0f, 1.0f)}, Vertex{.m_Position = vec3(0.5f, -0.5f, -0.5f), .m_UV0 = vec2(0.0f, 0.0f)}, Vertex{.m_Position = vec3(-0.5f, -0.5f, -0.5f), .m_UV0 = vec2(0.0f, 0.0f)}, Vertex{.m_Position = vec3(0.5f, -0.5f, -0.5f), .m_UV0 = vec2(1.0f, 0.0f)}, Vertex{.m_Position = vec3(0.5f, -0.5f, 0.5f), .m_UV0 = vec2(1.0f, 1.0f)}, Vertex{.m_Position = vec3(0.5f, -0.5f, 0.5f), .m_UV0 = vec2(1.0f, 1.0f)}, Vertex{.m_Position = vec3(-0.5f, -0.5f, 0.5f), .m_UV0 = vec2(0.0f, 1.0f)}, Vertex{.m_Position = vec3(-0.5f, -0.5f, -0.5f), .m_UV0 = vec2(0.0f, 0.0f)}, Vertex{.m_Position = vec3(0.5f, 0.5f, 0.5f), .m_UV0 = vec2(1.0f, 1.0f)}, Vertex{.m_Position = vec3(0.5f, 0.5f, -0.5f), .m_UV0 = vec2(1.0f, 0.0f)}, Vertex{.m_Position = vec3(-0.5f, 0.5f, -0.5f), .m_UV0 = vec2(0.0f, 0.0f)}, Vertex{.m_Position = vec3(-0.5f, 0.5f, -0.5f), .m_UV0 = vec2(0.0f, 0.0f)}, Vertex{.m_Position = vec3(-0.5f, 0.5f, 0.5f), .m_UV0 = vec2(0.0f, 1.0f)}, Vertex{.m_Position = vec3(0.5f, 0.5f, 0.5f), .m_UV0 = vec2(1.0f, 1.0f)}, }; ImageFile imageFile; bool loaded = imageFile.Load({0.7f, 0.4f, 0.1f, 1.0f}); assert(loaded); INFO("Image {}x{} : {} channels", imageFile.m_Width, imageFile.m_Height, imageFile.m_NumChannels); VertexBuffer vbo; Texture crate; vbo.Init(&device, vertices.size() * sizeof vertices[0], "VBO"); crate.Init(&device, {imageFile.m_Width, imageFile.m_Height}, false, "Crate Texture"); { StagingBuffer vertexStaging, imageStaging; vertexStaging.Init(&device, vertices.size() * sizeof vertices[0], "Vertex Staging"); vertexStaging.Write(&device, 0, vertices.size() * sizeof vertices[0], vertices.data()); imageStaging.Init(&device, imageFile.GetSize(), "Image Staging"); INFO("fine {}", imageFile.GetSize()); imageStaging.Write(&device, 0, imageFile.GetSize(), imageFile.m_Data); vk::ImageMemoryBarrier imageReadyToWrite = { .oldLayout = vk::ImageLayout::eUndefined, .newLayout = vk::ImageLayout::eTransferDstOptimal, .srcQueueFamilyIndex = queueAllocation.m_Family, .dstQueueFamilyIndex = queueAllocation.m_Family, .image = crate.m_Image, .subresourceRange = { .aspectMask = vk::ImageAspectFlagBits::eColor, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }, }; vk::ImageMemoryBarrier imageReadyToRead = { .oldLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eShaderReadOnlyOptimal, .srcQueueFamilyIndex = queueAllocation.m_Family, .dstQueueFamilyIndex = queueAllocation.m_Family, .image = crate.m_Image, .subresourceRange = { .aspectMask = vk::ImageAspectFlagBits::eColor, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }, }; 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)); copyBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eHost, vk::PipelineStageFlagBits::eTransfer, {}, 0, nullptr, 0, nullptr, 1, &imageReadyToWrite); vk::BufferCopy bufferCopy = {.srcOffset = 0, .dstOffset = 0, .size = vertexStaging.GetSize()}; copyBuffer.copyBuffer(vertexStaging.m_Buffer, vbo.m_Buffer, 1, &bufferCopy); vk::BufferImageCopy imageCopy = { .bufferOffset = 0, .bufferRowLength = imageFile.m_Width, .bufferImageHeight = imageFile.m_Height, .imageSubresource = { .aspectMask = vk::ImageAspectFlagBits::eColor, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1, }, .imageOffset = {}, .imageExtent = {imageFile.m_Width, imageFile.m_Height, 1}, }; copyBuffer.copyBufferToImage(imageStaging.m_Buffer, crate.m_Image, vk::ImageLayout::eTransferDstOptimal, 1, &imageCopy); copyBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, 0, nullptr, 0, nullptr, 1, &imageReadyToRead); 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); vertexStaging.Destroy(&device); imageStaging.Destroy(&device); } vk::ImageView imageView; vk::Sampler sampler; { vk::ImageViewCreateInfo imageViewCreateInfo = { .image = crate.m_Image, .viewType = vk::ImageViewType::e2D, .format = vk::Format::eR8G8B8A8Srgb, .components = vk::ComponentMapping{ .r = vk::ComponentSwizzle::eIdentity, .g = vk::ComponentSwizzle::eIdentity, .b = vk::ComponentSwizzle::eIdentity, .a = vk::ComponentSwizzle::eIdentity, }, .subresourceRange = { .aspectMask = vk::ImageAspectFlagBits::eColor, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }, }; AbortIfFailed(device.m_Device.createImageView(&imageViewCreateInfo, nullptr, &imageView)); vk::SamplerCreateInfo samplerCreateInfo = { .magFilter = vk::Filter::eLinear, .minFilter = vk::Filter::eLinear, .mipmapMode = vk::SamplerMipmapMode::eLinear, .addressModeU = vk::SamplerAddressMode::eRepeat, .addressModeV = vk::SamplerAddressMode::eRepeat, .addressModeW = vk::SamplerAddressMode::eRepeat, .mipLodBias = 0.2, .anisotropyEnable = true, .maxAnisotropy = 1.0f, .compareEnable = false, .minLod = 0, .maxLod = 4, .unnormalizedCoordinates = false, }; AbortIfFailed(device.m_Device.createSampler(&samplerCreateInfo, nullptr, &sampler)); } 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::DescriptorImageInfo descriptorImageInfo = { .sampler = sampler, .imageView = imageView, .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal, }; eastl::array writeDescriptors = { vk::WriteDescriptorSet{ .dstSet = descriptorSet, .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &descriptorBufferInfo, }, vk::WriteDescriptorSet{ .dstSet = descriptorSet, .dstBinding = 1, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eCombinedImageSampler, .pImageInfo = &descriptorImageInfo, }, }; device.m_Device.updateDescriptorSets(Cast(writeDescriptors.size()), writeDescriptors.data(), 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, }; auto fnResizeViewportScissor = [&viewport, &scissor](vk::Extent2D extent) { viewport.y = Cast(extent.height); viewport.width = Cast(extent.width); viewport.height = -Cast(extent.height); scissor.extent = extent; }; swapchain.RegisterResizeCallback(fnResizeViewportScissor); 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, }; FrameManager frameManager = {&device, queueAllocation.m_Family, MAX_FRAMES_IN_FLIGHT}; eastl::fixed_vector depthImages(frameManager.m_FramesInFlight); eastl::fixed_vector depthViews(frameManager.m_FramesInFlight); vk::ImageSubresourceRange depthSubresourceRange = { .aspectMask = vk::ImageAspectFlagBits::eDepth, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }; u32 index = 0; for (auto &depthImage : depthImages) { auto name = fmt::format("Depth image {}", index); depthImage.Init(&device, swapchain.m_Extent, name.c_str()); vk::ImageViewCreateInfo imageViewCreateInfo = { .image = depthImage.m_Image, .viewType = vk::ImageViewType::e2D, .format = vk::Format::eD32Sfloat, .components = vk::ComponentMapping{.r = vk::ComponentSwizzle::eIdentity}, .subresourceRange = depthSubresourceRange, }; AbortIfFailed(device.m_Device.createImageView(&imageViewCreateInfo, nullptr, &depthViews[index])); index++; } auto fnRecreateDepthBuffers = [&device, &depthImages, &depthViews, depthSubresourceRange](vk::Extent2D extent) { for (const auto &depthView : depthViews) { device.m_Device.destroy(depthView, nullptr); } u32 index = 0; for (auto &depthImage : depthImages) { depthImage.Destroy(&device); depthImage.Init(&device, extent, "Depth"); vk::ImageViewCreateInfo imageViewCreateInfo = { .image = depthImage.m_Image, .viewType = vk::ImageViewType::e2D, .format = vk::Format::eD32Sfloat, .components = vk::ComponentMapping{.r = vk::ComponentSwizzle::eIdentity}, .subresourceRange = depthSubresourceRange, }; AbortIfFailed(device.m_Device.createImageView(&imageViewCreateInfo, nullptr, &depthViews[index])); ++index; } }; swapchain.RegisterResizeCallback(fnRecreateDepthBuffers); Time::Init(); INFO("Starting loop"); while (window.Poll()) { 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); Frame *currentFrame = frameManager.GetNextFrame(&swapchain, &window); u32 imageIndex = currentFrame->m_ImageIdx; vk::ImageView currentImageView = swapchain.m_ImageViews[imageIndex]; vk::Image currentImage = swapchain.m_Images[imageIndex]; vk::CommandBuffer cmd = currentFrame->m_CommandBuffer; vk::ImageView currentDepthImageView = depthViews[currentFrame->m_FrameIdx]; 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 eastl::array attachmentInfos = { vk::RenderingAttachmentInfo{ .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::RenderingAttachmentInfo depthAttachment = { .imageView = currentDepthImageView, .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, .resolveMode = vk::ResolveModeFlagBits::eNone, .loadOp = vk::AttachmentLoadOp::eClear, .storeOp = vk::AttachmentStoreOp::eDontCare, .clearValue = vk::ClearDepthStencilValue{.depth = 1.0f, .stencil = 0}, }; vk::RenderingInfo renderingInfo = { .renderArea = {.extent = swapchain.m_Extent}, .layerCount = 1, .colorAttachmentCount = Cast(attachmentInfos.size()), .pColorAttachments = attachmentInfos.data(), .pDepthAttachment = &depthAttachment, }; 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, 1, 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)); currentFrame->Present(commandQueue, &swapchain, &window); } AbortIfFailed(device.m_Device.waitIdle()); for (auto &depthView : depthViews) { device.m_Device.destroy(depthView, nullptr); } for (auto &depthImage : depthImages) { depthImage.Destroy(&device); } device.m_Device.destroy(sampler, nullptr); device.m_Device.destroy(imageView, nullptr); ubo.Destroy(&device); device.m_Device.destroy(descriptorPool, nullptr); device.m_Device.destroy(copyPool, nullptr); crate.Destroy(&device); vbo.Destroy(&device); return 0; } bool ImageFile::Load(vec4 color) { constexpr usize size = 512llu * 512llu * 4llu; m_Data = new u8[size]; vec4 color255 = 255.999f * color; glm::vec<4, u8> color8 = color255; for (usize i = 0; i < size; i += 4) { memcpy(m_Data + i, &color8, sizeof color8); } return true; } usize ImageFile::GetSize() const { return m_Width * m_Height * m_NumChannels; } ImageFile::~ImageFile() { delete[] m_Data; m_Data = nullptr; }