diff --git a/samples/02_box/box.cpp b/samples/02_box/box.cpp index 9e870d3..a4148a7 100644 --- a/samples/02_box/box.cpp +++ b/samples/02_box/box.cpp @@ -420,13 +420,13 @@ main(int, char **) .extent = swapchain.m_Extent, }; - auto ResizeViewportScissor = [&viewport, &scissor](vk::Extent2D extent) { + auto resizeViewportScissor = [&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(ResizeViewportScissor); + swapchain.RegisterResizeCallback(resizeViewportScissor); vk::ImageSubresourceRange subresourceRange = { .aspectMask = vk::ImageAspectFlagBits::eColor, @@ -479,7 +479,7 @@ main(int, char **) index++; } - auto RecreateDepthBuffers = [&device, &depthImages, &depthViews, depthSubresourceRange](vk::Extent2D extent) { + auto recreateDepthBuffers = [&device, &depthImages, &depthViews, depthSubresourceRange](vk::Extent2D extent) { for (auto &depthView : depthViews) { device.m_Device.destroy(depthView, nullptr); @@ -500,7 +500,7 @@ main(int, char **) ++index; } }; - swapchain.RegisterResizeCallback(RecreateDepthBuffers); + swapchain.RegisterResizeCallback(recreateDepthBuffers); Time::Init(); diff --git a/samples/03_model_render/CMakeLists.txt b/samples/03_model_render/CMakeLists.txt new file mode 100644 index 0000000..a6c718a --- /dev/null +++ b/samples/03_model_render/CMakeLists.txt @@ -0,0 +1,21 @@ +# CMakeList.txt ; CMake project for box + +cmake_minimum_required(VERSION 3.13) + +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fsanitize=address") +find_path(TINYGLTF_INCLUDE_DIRS "tiny_gltf.h") + +add_executable(model_render model_render.cpp + pipeline_utils.cpp + pipeline_utils.h + render_resource_manager.cpp + render_resource_manager.h) +add_shader(model_render shader/model.vert.glsl) +add_shader(model_render shader/model.frag.glsl) + +target_link_libraries(model_render PRIVATE aster_core) +target_link_libraries(model_render PRIVATE util_helper) + +target_include_directories(model_render PRIVATE ${TINYGLTF_INCLUDE_DIRS}) + +add_resource_dir(model_render model) \ No newline at end of file diff --git a/samples/03_model_render/model/Box.glb b/samples/03_model_render/model/Box.glb new file mode 100644 index 0000000..5445f06 --- /dev/null +++ b/samples/03_model_render/model/Box.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed52f7192b8311d700ac0ce80644e3852cd01537e4d62241b9acba023da3d54e +size 1664 diff --git a/samples/03_model_render/model_render.cpp b/samples/03_model_render/model_render.cpp new file mode 100644 index 0000000..4c7979c --- /dev/null +++ b/samples/03_model_render/model_render.cpp @@ -0,0 +1,624 @@ +// ============================================= +// 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; +} \ No newline at end of file diff --git a/samples/03_model_render/pipeline_utils.cpp b/samples/03_model_render/pipeline_utils.cpp new file mode 100644 index 0000000..aff8543 --- /dev/null +++ b/samples/03_model_render/pipeline_utils.cpp @@ -0,0 +1,185 @@ +// ============================================= +// Aster: pipeline_utils.cpp +// Copyright (c) 2020-2024 Anish Bhobe +// ============================================= + +#include "pipeline_utils.h" + +#include "device.h" +#include "helpers.h" +#include "render_resource_manager.h" +#include "swapchain.h" + +#include + +Pipeline +CreatePipeline(const Device *device, const Swapchain *swapchain, const RenderResourceManager *resourceManager) +{ + // 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", + }, + }}; + + eastl::vector descriptorSetLayouts; + + descriptorSetLayouts.push_back(resourceManager->m_SetLayout); + + { + eastl::array descriptorSetLayoutBindings = { + vk::DescriptorSetLayoutBinding{ + .binding = 0, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eVertex, + }, + vk::DescriptorSetLayoutBinding{ + .binding = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment, + }, + }; + vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = { + .bindingCount = Cast(descriptorSetLayoutBindings.size()), + .pBindings = descriptorSetLayoutBindings.data(), + }; + vk::DescriptorSetLayout descriptorSetLayout; + AbortIfFailed( + device->m_Device.createDescriptorSetLayout(&descriptorSetLayoutCreateInfo, nullptr, &descriptorSetLayout)); + descriptorSetLayouts.push_back(descriptorSetLayout); + } + + vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo = { + .setLayoutCount = Cast(descriptorSetLayouts.size()), + .pSetLayouts = descriptorSetLayouts.data(), + .pushConstantRangeCount = 0, + .pPushConstantRanges = nullptr, + }; + vk::PipelineLayout pipelineLayout; + AbortIfFailed(device->m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); + device->SetName(pipelineLayout, "Box Layout"); + + descriptorSetLayouts[0] = nullptr; // Not owned. + + 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 = true, + .depthWriteEnable = true, + .depthCompareOp = vk::CompareOp::eLess, + }; + 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, + .depthAttachmentFormat = vk::Format::eD32Sfloat, + }; + + 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, std::move(descriptorSetLayouts)}; +} + +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; +} diff --git a/samples/03_model_render/pipeline_utils.h b/samples/03_model_render/pipeline_utils.h new file mode 100644 index 0000000..2ece8fe --- /dev/null +++ b/samples/03_model_render/pipeline_utils.h @@ -0,0 +1,51 @@ +// ============================================= +// Aster: pipeline_utils.h +// Copyright (c) 2020-2024 Anish Bhobe +// ============================================= + +#pragma once + +#include "pipeline.h" + +#include + +struct RenderResourceManager; +struct Swapchain; +struct Device; + +constexpr auto VERTEX_SHADER_FILE = "shader/model.vert.glsl.spv"; +constexpr auto FRAGMENT_SHADER_FILE = "shader/model.frag.glsl.spv"; + +struct Vertex +{ + vec3 m_Position; + vec2 m_UV0; + + 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), + }, + vk::VertexInputAttributeDescription{ + .location = 1, + .binding = binding, + .format = vk::Format::eR32G32Sfloat, + .offset = offsetof(Vertex, m_UV0), + }, + }; + } +}; + +vk::ShaderModule CreateShader(const Device *device, cstr shaderFile); +Pipeline CreatePipeline(const Device *device, const Swapchain *swapchain, const RenderResourceManager *resourceManager); diff --git a/samples/03_model_render/render_resource_manager.cpp b/samples/03_model_render/render_resource_manager.cpp new file mode 100644 index 0000000..6d2e4e7 --- /dev/null +++ b/samples/03_model_render/render_resource_manager.cpp @@ -0,0 +1,87 @@ +// ============================================= +// Aster: pipeline_utils.h +// Copyright (c) 2020-2024 Anish Bhobe +// ============================================= + +#include "render_resource_manager.h" + +#include "buffer.h" +#include "device.h" +#include "helpers.h" +#include "image.h" + +RenderResourceManager::RenderResourceManager(const Device *device) + : m_Device(device) +{ + vk::PhysicalDeviceProperties properties; + m_Device->m_PhysicalDevice.getProperties(&properties); + + auto maxBufferCount = properties.limits.maxDescriptorSetStorageBuffers - 1024; + auto maxTextureCount = properties.limits.maxDescriptorSetSampledImages - 1024; + + INFO("Max Buffer Count: {}", maxBufferCount); + INFO("Max Texture Count: {}", maxTextureCount); + + m_Buffers.resize(maxBufferCount); + m_Textures.resize(maxTextureCount); + + eastl::array poolSizes = { + vk::DescriptorPoolSize{.type = vk::DescriptorType::eStorageBuffer, .descriptorCount = maxBufferCount}, + vk::DescriptorPoolSize{.type = vk::DescriptorType::eSampledImage, .descriptorCount = maxTextureCount}, + }; + vk::DescriptorPoolCreateInfo poolCreateInfo = { + .flags = vk::DescriptorPoolCreateFlagBits::eUpdateAfterBind, + .maxSets = 4, + .poolSizeCount = Cast(poolSizes.size()), + .pPoolSizes = poolSizes.data(), + }; + AbortIfFailed(device->m_Device.createDescriptorPool(&poolCreateInfo, nullptr, &m_DescriptorPool)); + + eastl::array descriptorLayoutBindings = { + vk::DescriptorSetLayoutBinding{ + .binding = 0, + .descriptorType = vk::DescriptorType::eStorageBuffer, + .descriptorCount = Cast(m_Buffers.size()), + .stageFlags = vk::ShaderStageFlagBits::eAll, + }, + vk::DescriptorSetLayoutBinding{ + .binding = 1, + .descriptorType = vk::DescriptorType::eSampledImage, + .descriptorCount = Cast(m_Textures.size()), + .stageFlags = vk::ShaderStageFlagBits::eAll, + }, + }; + const vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = { + .flags = vk::DescriptorSetLayoutCreateFlagBits::eUpdateAfterBindPool, + .bindingCount = Cast(descriptorLayoutBindings.size()), + .pBindings = descriptorLayoutBindings.data(), + }; + AbortIfFailed(device->m_Device.createDescriptorSetLayout(&descriptorSetLayoutCreateInfo, nullptr, &m_SetLayout)); + + // One descriptor is enough. Updating it at any time is safe. (Update until submit, data held when pending) + // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_descriptor_indexing.html + // https://github.com/KhronosGroup/Vulkan-Guide/blob/main/chapters/extensions/VK_EXT_descriptor_indexing.adoc + const vk::DescriptorSetAllocateInfo descriptorSetAllocateInfo = { + .descriptorPool = m_DescriptorPool, + .descriptorSetCount = 1, + .pSetLayouts = &m_SetLayout, + }; + AbortIfFailed(device->m_Device.allocateDescriptorSets(&descriptorSetAllocateInfo, &m_DescriptorSet)); +} + +RenderResourceManager::~RenderResourceManager() +{ + for (auto &buffer : m_Buffers) + { + buffer.Destroy(m_Device); + } + for (auto &image : m_Textures) + { + image.Destroy(m_Device); + } + + m_Device->m_Device.destroy(m_TextureUpdate, nullptr); + m_Device->m_Device.destroy(m_BufferUpdate, nullptr); + m_Device->m_Device.destroy(m_DescriptorPool, nullptr); + m_Device->m_Device.destroy(m_SetLayout, nullptr); +} \ No newline at end of file diff --git a/samples/03_model_render/render_resource_manager.h b/samples/03_model_render/render_resource_manager.h new file mode 100644 index 0000000..fe74305 --- /dev/null +++ b/samples/03_model_render/render_resource_manager.h @@ -0,0 +1,55 @@ +// ============================================= +// Aster: pipeline_utils.h +// Copyright (c) 2020-2024 Anish Bhobe +// ============================================= + +#pragma once + +#include "global.h" + +#include +#include + +struct Device; +struct Texture; +struct UniformBuffer; + +struct Handle +{ + u16 m_Index; +}; + +struct BufferHandle : Handle +{ +}; + +struct TextureHandle : Handle +{ +}; + +struct RenderResourceManager +{ + const Device *m_Device; + + vk::DescriptorPool m_DescriptorPool; + vk::DescriptorSetLayout m_SetLayout; + vk::DescriptorSet m_DescriptorSet; + + vk::DescriptorUpdateTemplate m_BufferUpdate; + vk::DescriptorUpdateTemplate m_TextureUpdate; + + eastl::vector m_Buffers; + eastl::vector m_Textures; + // TODO: eastl::vector m_Textures; + + // UniformBuffer *Allocate(); + // UniformBuffer *Fetch(UniformHandle handle); + // void Commit(UniformHandle handle); + // Texture *Allocate(); + // Texture *Fetch(TextureHandle handle); + // void Commit(TextureHandle handle); + + // Ctor/Dtor + explicit RenderResourceManager(const Device *device); + ~RenderResourceManager(); +}; \ No newline at end of file diff --git a/samples/03_model_render/shader/model.frag.glsl b/samples/03_model_render/shader/model.frag.glsl new file mode 100644 index 0000000..5573ea1 --- /dev/null +++ b/samples/03_model_render/shader/model.frag.glsl @@ -0,0 +1,11 @@ +#version 450 +#pragma shader_stage(fragment) + +layout (location = 0) in vec2 inUV; +layout (location = 0) out vec4 outColor; + +layout(set = 1, binding = 1) uniform sampler2D tex; + +void main() { + outColor = vec4(texture(tex, inUV).rgb, 1.0f); +} \ No newline at end of file diff --git a/samples/03_model_render/shader/model.vert.glsl b/samples/03_model_render/shader/model.vert.glsl new file mode 100644 index 0000000..96720f9 --- /dev/null +++ b/samples/03_model_render/shader/model.vert.glsl @@ -0,0 +1,22 @@ +#version 450 +#pragma shader_stage(vertex) + +layout(location = 0) in vec4 position; +layout(location = 1) in vec2 uv0; + +layout(location = 0) out vec2 outUV; + +//layout(std140, set=0, binding=0) readonly buffer buffers; +layout(set = 0, binding = 1) uniform texture2D textures[]; + +layout(set = 1, binding = 0) uniform Camera { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +void main() { + outUV = uv0; + gl_Position = ubo.proj * ubo.view * ubo.model * vec4(position.xyz, 1.0f); + // outColor = vec3(0.5f, 0.3f, 0.1f); +} \ No newline at end of file diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 0b584b7..a157b3a 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -5,3 +5,4 @@ cmake_minimum_required(VERSION 3.13) add_subdirectory("00_util") add_subdirectory("01_triangle") add_subdirectory("02_box") +add_subdirectory("03_model_render") diff --git a/vcpkg.json b/vcpkg.json index c0ed20c..2db59ac 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,10 +1,11 @@ { "dependencies": [ + "eastl", "fmt", "glfw3", "glm", "scottt-debugbreak", - "vulkan-memory-allocator", - "eastl" + "tinygltf", + "vulkan-memory-allocator" ] }