// ============================================= // Aster: main.cpp // Copyright (c) 2020-2024 Anish Bhobe // ============================================= #include "aster/core/context.h" #include "aster/core/device.h" #include "aster/core/image.h" #include "aster/core/physical_device.h" #include "aster/core/pipeline.h" #include "aster/core/swapchain.h" #include "aster/core/window.h" #include "helpers.h" #include "render_resource_manager.h" #include "asset_loader.h" #include "camera.h" #include "core_components.h" #include "light_manager.h" #include "ecs_adapter.h" #include "frame.h" #include "ibl_helpers.h" #include "pipeline_utils.h" constexpr u32 MAX_FRAMES_IN_FLIGHT = 3; constexpr auto PIPELINE_CACHE_FILE = "PipelineCacheData.bin"; constexpr auto MODEL_FILE = "model/AlphaBlendModeTest.glb"; constexpr auto MODEL_FILE2 = "model/Box.glb"; constexpr auto BACKDROP_FILE = "image/photo_studio_loft_hall_4k.hdr"; constexpr u32 INIT_WIDTH = 640; constexpr u32 INIT_HEIGHT = 480; int main(int, char *[]) { MIN_LOG_LEVEL(Logger::LogType::eInfo); Window window = {"Scene Render [WIP] (Aster)", {INIT_WIDTH, INIT_HEIGHT}}; Context context = {"Scene Render [WIP]", VERSION}; Surface surface = {&context, &window, "Primary Surface"}; PhysicalDevices physicalDevices = {&surface, &context}; PhysicalDevice deviceToUse = FindSuitableDevice(physicalDevices); vk::Extent2D internalResolution = {1920, 1080}; internalResolution.width = (internalResolution.height * INIT_WIDTH) / INIT_HEIGHT; CameraController cameraController = {vec3{0.0f, 1.0f, 4.0f}, vec3{0.0f, 0.0f, 0.0f}, 70_deg, static_cast(internalResolution.width) / static_cast(internalResolution.height)}; INFO("Using {} as the primary device.", deviceToUse.m_DeviceProperties.deviceName.data()); Features enabledDeviceFeatures = { .m_Vulkan10Features = { .multiDrawIndirect = true, .samplerAnisotropy = true, .shaderInt64 = true, }, .m_Vulkan11Features = { .shaderDrawParameters = true, }, .m_Vulkan12Features = { .descriptorIndexing = true, .shaderSampledImageArrayNonUniformIndexing = true, .shaderStorageBufferArrayNonUniformIndexing = true, .shaderStorageImageArrayNonUniformIndexing = true, .descriptorBindingUniformBufferUpdateAfterBind = true, // Not related to Bindless .descriptorBindingSampledImageUpdateAfterBind = true, .descriptorBindingStorageImageUpdateAfterBind = true, .descriptorBindingStorageBufferUpdateAfterBind = true, .descriptorBindingPartiallyBound = true, .runtimeDescriptorArray = true, .bufferDeviceAddress = true, .bufferDeviceAddressCaptureReplay = true, }, .m_Vulkan13Features = { .synchronization2 = true, .dynamicRendering = true, }, }; auto attachmentFormat = vk::Format::eR8G8B8A8Srgb; auto pipelineCacheData = ReadFileBytes(PIPELINE_CACHE_FILE, false); QueueAllocation queueAllocation = FindAppropriateQueueAllocation(&deviceToUse); Device device = {&context, &deviceToUse, &enabledDeviceFeatures, {queueAllocation}, pipelineCacheData, "Primary Device"}; vk::Queue graphicsQueue = device.GetQueue(queueAllocation.m_Family, 0); Swapchain swapchain = {&surface, &device, window.GetSize(), "Primary Chain"}; RenderResourceManager resourceManager = {&device, 1024}; EcsRegistry registry; AssetLoader assetLoader = {&resourceManager, ®istry, graphicsQueue, queueAllocation.m_Family, queueAllocation.m_Family}; LightManager lightManager = LightManager{&resourceManager}; Texture environmentHdri; assetLoader.LoadHdrImage(&environmentHdri, BACKDROP_FILE); auto envHdriHandle = resourceManager.CommitTexture(&environmentHdri); auto environment = CreateEnvironment(&assetLoader, graphicsQueue, 512, envHdriHandle, "Cube Env"); resourceManager.Release(envHdriHandle); lightManager.SetEnvironment(&environment); eastl::vector models; models.emplace_back(assetLoader.LoadModelToGpu(MODEL_FILE, "Main Model")); // registry.get(model.m_RootEntity).m_Position = vec3(2 * i, 0, 2 * j); UniformBuffer ubo; constexpr usize uboLightManagerOffset = sizeof cameraController.m_Camera; constexpr usize uboTotalSize = uboLightManagerOffset + sizeof lightManager.m_MetaInfo; ubo.Init(&device, uboTotalSize, "Desc1 UBO"); ubo.Write(&device, 0, sizeof cameraController.m_Camera, &cameraController.m_Camera); ubo.Write(&device, uboLightManagerOffset, sizeof lightManager.m_MetaInfo, &lightManager.m_MetaInfo); Pipeline pipeline = CreatePipeline(&device, attachmentFormat, &resourceManager); Pipeline backgroundPipeline = CreateBackgroundPipeline(&device, attachmentFormat, &resourceManager); vk::DescriptorPool descriptorPool; vk::DescriptorSet perFrameDescriptor; { vk::DescriptorSetLayout descriptorSetLayout = pipeline.m_SetLayouts[1]; eastl::array poolSizes = { vk::DescriptorPoolSize{ .type = vk::DescriptorType::eUniformBuffer, .descriptorCount = 3, }, }; vk::DescriptorPoolCreateInfo descriptorPoolCreateInfo = { .maxSets = 1, .poolSizeCount = static_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, &perFrameDescriptor)); } vk::DescriptorBufferInfo camLightBufferInfo = { .buffer = ubo.m_Buffer, .offset = 0, .range = uboTotalSize, }; eastl::array writeDescriptors = { vk::WriteDescriptorSet{ .dstSet = perFrameDescriptor, .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &camLightBufferInfo, }, }; device.m_Device.updateDescriptorSets(static_cast(writeDescriptors.size()), writeDescriptors.data(), 0, nullptr); // Persistent variables vk::Viewport viewport = { .x = 0, .y = static_cast(internalResolution.height), .width = static_cast(internalResolution.width), .height = -static_cast(internalResolution.height), .minDepth = 0.0, .maxDepth = 1.0, }; vk::Rect2D scissor = { .offset = {0, 0}, .extent = internalResolution, }; vk::ImageSubresourceRange subresourceRange = { .aspectMask = vk::ImageAspectFlagBits::eColor, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }; vk::ImageMemoryBarrier2 preRenderBarrier = { .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, .srcAccessMask = vk::AccessFlagBits2::eNone, .dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, .dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, .oldLayout = vk::ImageLayout::eUndefined, .newLayout = vk::ImageLayout::eColorAttachmentOptimal, .srcQueueFamilyIndex = queueAllocation.m_Family, .dstQueueFamilyIndex = queueAllocation.m_Family, .subresourceRange = subresourceRange, }; vk::DependencyInfo preRenderDependencies = { .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &preRenderBarrier, }; vk::ImageMemoryBarrier2 renderToBlitBarrier = { .srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, .srcAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, .dstStageMask = vk::PipelineStageFlagBits2::eAllTransfer, .dstAccessMask = vk::AccessFlagBits2::eTransferRead, .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, .newLayout = vk::ImageLayout::eTransferSrcOptimal, .srcQueueFamilyIndex = queueAllocation.m_Family, .dstQueueFamilyIndex = queueAllocation.m_Family, .subresourceRange = subresourceRange, }; vk::ImageMemoryBarrier2 acquireToTransferDstBarrier = { .srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe, .srcAccessMask = vk::AccessFlagBits2::eNone, .dstStageMask = vk::PipelineStageFlagBits2::eAllTransfer, .dstAccessMask = vk::AccessFlagBits2::eTransferWrite, .oldLayout = vk::ImageLayout::eUndefined, .newLayout = vk::ImageLayout::eTransferDstOptimal, .srcQueueFamilyIndex = queueAllocation.m_Family, .dstQueueFamilyIndex = queueAllocation.m_Family, .subresourceRange = subresourceRange, }; eastl::array postRenderBarriers = { renderToBlitBarrier, acquireToTransferDstBarrier, }; vk::DependencyInfo postRenderDependencies = { .imageMemoryBarrierCount = static_cast(postRenderBarriers.size()), .pImageMemoryBarriers = postRenderBarriers.data(), }; vk::ImageMemoryBarrier2 transferDstToGuiRenderBarrier = { .srcStageMask = vk::PipelineStageFlagBits2::eAllTransfer, .srcAccessMask = vk::AccessFlagBits2::eTransferWrite, .dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, .dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, .oldLayout = vk::ImageLayout::eTransferDstOptimal, .newLayout = vk::ImageLayout::eColorAttachmentOptimal, .srcQueueFamilyIndex = queueAllocation.m_Family, .dstQueueFamilyIndex = queueAllocation.m_Family, .subresourceRange = subresourceRange, }; vk::DependencyInfo preGuiDependencies = { .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &transferDstToGuiRenderBarrier, }; vk::ImageMemoryBarrier2 prePresentBarrier = { .srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, .srcAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, .dstStageMask = vk::PipelineStageFlagBits2::eBottomOfPipe, .dstAccessMask = vk::AccessFlagBits2::eNone, .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, .newLayout = vk::ImageLayout::ePresentSrcKHR, .srcQueueFamilyIndex = queueAllocation.m_Family, .dstQueueFamilyIndex = queueAllocation.m_Family, .subresourceRange = subresourceRange, }; vk::DependencyInfo prePresentDependencies = { .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &prePresentBarrier, }; FrameManager frameManager = {&device, queueAllocation.m_Family, MAX_FRAMES_IN_FLIGHT}; eastl::fixed_vector depthImages(frameManager.m_FramesInFlight); eastl::fixed_vector attachmentImages(frameManager.m_FramesInFlight); { auto depthIter = depthImages.begin(); auto attachmentIter = attachmentImages.begin(); for (u32 index = 0; index < frameManager.m_FramesInFlight; ++index) { auto name = fmt::format("Depth Frame{}", index); depthIter->Init(&device, internalResolution, name.c_str()); name = fmt::format("Attachment0 Frame{}", index); attachmentIter->Init(&device, internalResolution, attachmentFormat, name.c_str()); ++depthIter; ++attachmentIter; } } struct NodeData { mat4 m_Transform; uptr m_VertexPositionPtr; uptr m_VertexDataPtr; uptr m_MaterialPtr; u8 m_Padding_[8]; }; eastl::vector nodeData; eastl::vector nodeDrawInfo; eastl::fixed_vector nodeBuffers; eastl::fixed_vector nodeIndirectBuffers; eastl::fixed_vector perFrameNodeBufferPtr; for (u32 i = 0; i < frameManager.m_FramesInFlight; ++i) { auto *buffer = &nodeBuffers.push_back(); buffer->Init(&device, sizeof(NodeData) * 100'000, true, true, "Node Buffer"); auto *indirect = &nodeIndirectBuffers.push_back(); indirect->Init(&device, sizeof(vk::DrawIndexedIndirectCommand) * 100'000, true); perFrameNodeBufferPtr.push_back(buffer->GetDeviceAddress(&device)); } swapchain.RegisterResizeCallback( [&cameraController, &internalResolution, &viewport, &scissor](vk::Extent2D extent) { cameraController.SetAspectRatio(static_cast(extent.width) / static_cast(extent.height)); internalResolution.width = static_cast(static_cast(internalResolution.height) * cameraController.m_AspectRatio); viewport.y = static_cast(internalResolution.height); viewport.width = static_cast(internalResolution.width); viewport.height = -static_cast(internalResolution.height); scissor.extent = internalResolution; }); auto sortByParentHier = [®istry](Entity a, Entity b) { const auto parent = registry.try_get>(b); return parent && parent->m_ParentEntity == a; }; registry.sort>(sortByParentHier); Time::Init(); auto rootNodeUpdateView = registry.view(Without>{}); auto rootModel = registry.group(Get{}); auto nodeWithParentsUpdateView = registry.view, CGlobalTransform>(); nodeWithParentsUpdateView.use>(); auto renderableObjectsGroup = registry.group(); lightManager.Update(); ubo.Write(&device, uboLightManagerOffset, sizeof lightManager.m_MetaInfo, &lightManager.m_MetaInfo); resourceManager.Update(); while (window.Poll()) { Time::Update(); // u32 index = 0; // for (auto [entity, dynTrans] : rootModel.each()) //{ // dynTrans.m_Rotation = // glm::rotate(dynTrans.m_Rotation, static_cast(30_deg * (++index) * Time::m_Delta), vec3{0.0f, 1.0f, // 0.0f}); // } Frame *currentFrame = frameManager.GetNextFrame(&swapchain, &surface, window.GetSize()); u32 imageIndex = currentFrame->m_ImageIdx; vk::Image currentSwapchainImage = swapchain.m_Images[imageIndex]; vk::ImageView currentSwapchainImageView = swapchain.m_ImageViews[imageIndex]; vk::CommandBuffer cmd = currentFrame->m_CommandBuffer; DepthImage *currentDepthImage = &depthImages[currentFrame->m_FrameIdx]; AttachmentImage *currentAttachment = &attachmentImages[currentFrame->m_FrameIdx]; // Resize outdated attachments if (currentAttachment->m_Extent.width != internalResolution.width || currentAttachment->m_Extent.height != internalResolution.height) { auto name = fmt::format("Depth Frame{}", currentFrame->m_FrameIdx); currentDepthImage->Destroy(&device); currentDepthImage->Init(&device, internalResolution, name.c_str()); name = fmt::format("Attachment0 Frame{}", currentFrame->m_FrameIdx); currentAttachment->Destroy(&device); currentAttachment->Init(&device, internalResolution, attachmentFormat, name.c_str()); } vk::ImageView currentDepthImageView = currentDepthImage->m_View; vk::Image currentImage = currentAttachment->m_Image; vk::ImageView currentImageView = currentAttachment->m_View; // Ready the barrier structs preRenderBarrier.image = currentImage; postRenderBarriers[0].image = currentImage; postRenderBarriers[1].image = currentSwapchainImage; transferDstToGuiRenderBarrier.image = currentSwapchainImage; prePresentBarrier.image = currentSwapchainImage; // Write Camera ubo.Write(&device, 0, sizeof cameraController.m_Camera, &cameraController.m_Camera); // Update all root dynamic object transforms. for (auto [entity, dynTransform, globalTransform] : rootNodeUpdateView.each()) { auto scale = glm::scale(mat4{1.0f}, dynTransform.m_Scale); auto rotation = glm::toMat4(dynTransform.m_Rotation); auto translation = glm::translate(mat4{1.0f}, dynTransform.m_Position); globalTransform.m_Transform = translation * rotation * scale; } // Has been sorted and ordered by parent. // Update all dynamic object transforms. for (auto [entity, dynTransform, parent, globalTransform] : nodeWithParentsUpdateView.each()) { auto scale = glm::scale(mat4{1.0f}, dynTransform.m_Scale); auto rotation = glm::toMat4(dynTransform.m_Rotation); auto translation = glm::translate(mat4{1.0f}, dynTransform.m_Position); globalTransform.m_Transform = registry.get(parent.m_ParentEntity).m_Transform * translation * rotation * scale; } u32 objectCount = static_cast(renderableObjectsGroup.size()); nodeData.clear(); nodeDrawInfo.clear(); nodeData.reserve(objectCount); nodeDrawInfo.reserve(objectCount); // Write all objects into the node data to be pushed. for (auto [entity, globalTransform, mesh, material] : renderableObjectsGroup.each()) { nodeData.push_back({ .m_Transform = globalTransform.m_Transform, .m_VertexPositionPtr = mesh.m_VertexPositionPtr, .m_VertexDataPtr = mesh.m_VertexDataPtr, .m_MaterialPtr = material.m_MaterialPtr, }); nodeDrawInfo.push_back({ .indexCount = mesh.m_IndexCount, .instanceCount = 1, .firstIndex = mesh.m_FirstIndex, .vertexOffset = 0, .firstInstance = 0, }); } nodeBuffers[currentFrame->m_FrameIdx].Write(&device, 0, objectCount * sizeof(NodeData), nodeData.data()); nodeIndirectBuffers[currentFrame->m_FrameIdx].Write(&device, 0, objectCount * sizeof nodeDrawInfo[0], nodeDrawInfo.data()); vk::CommandBufferBeginInfo beginInfo = {.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; AbortIfFailed(cmd.begin(&beginInfo)); cmd.pipelineBarrier2(&preRenderDependencies); // 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 = ToExtent2D(currentAttachment->m_Extent)}, .layerCount = 1, .colorAttachmentCount = static_cast(attachmentInfos.size()), .pColorAttachments = attachmentInfos.data(), .pDepthAttachment = &depthAttachment, }; cmd.beginRendering(&renderingInfo); cmd.setViewport(0, 1, &viewport); cmd.setScissor(0, 1, &scissor); cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline.m_Layout, 0, 1, &resourceManager.m_DescriptorSet, 0, nullptr); cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline.m_Layout, 1, 1, &perFrameDescriptor, 0, nullptr); cmd.bindIndexBuffer(resourceManager.GetIndexBuffer(), 0, vk::IndexType::eUint32); cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.m_Pipeline); auto nodeBufferAddr = perFrameNodeBufferPtr[currentFrame->m_FrameIdx]; cmd.pushConstants(pipeline.m_Layout, vk::ShaderStageFlagBits::eAll, 0, sizeof nodeBufferAddr, &nodeBufferAddr); cmd.drawIndexedIndirect(nodeIndirectBuffers[currentFrame->m_FrameIdx].m_Buffer, 0, objectCount, sizeof nodeDrawInfo[0]); cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, backgroundPipeline.m_Pipeline); cmd.draw(3, 1, 0, 0); cmd.endRendering(); cmd.pipelineBarrier2(&postRenderDependencies); vk::ImageBlit blitRegion = { .srcSubresource = { .aspectMask = vk::ImageAspectFlagBits::eColor, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1, }, .srcOffsets = std::array{ vk::Offset3D{0, 0, 0}, ToOffset3D(currentAttachment->m_Extent), }, .dstSubresource = { .aspectMask = vk::ImageAspectFlagBits::eColor, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1, }, .dstOffsets = std::array{ vk::Offset3D{0, 0, 0}, vk::Offset3D{static_cast(swapchain.m_Extent.width), static_cast(swapchain.m_Extent.height), 1}, }, }; cmd.blitImage(currentImage, postRenderBarriers[0].newLayout, currentSwapchainImage, postRenderBarriers[1].newLayout, 1, &blitRegion, vk::Filter::eLinear); cmd.pipelineBarrier2(&preGuiDependencies); cmd.pipelineBarrier2(&prePresentDependencies); 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(graphicsQueue.submit(1, &submitInfo, currentFrame->m_FrameAvailableFence)); currentFrame->Present(graphicsQueue, &swapchain, &surface, window.GetSize()); } device.WaitIdle(); for (auto buffer : nodeIndirectBuffers) { buffer.Destroy(&device); } for (auto buffer : nodeBuffers) { buffer.Destroy(&device); } for (auto depthImage : depthImages) { depthImage.Destroy(&device); } for (auto attachmentImage : attachmentImages) { attachmentImage.Destroy(&device); } ubo.Destroy(&device); device.m_Device.destroy(descriptorPool, nullptr); for (auto &model : models) { model.Destroy(&resourceManager, ®istry); } }