From 25a76d418cabdc628a97bb21c8e338149657984d Mon Sep 17 00:00:00 2001 From: Tamely Date: Sun, 7 Jun 2026 10:19:56 -0500 Subject: [PATCH 1/2] Opaque forward driven GPU path --- .../AxiomRHI/Vulkan/VulkanDescriptors.cpp | 7 +- .../AxiomRHI/Vulkan/VulkanDescriptors.h | 2 + .../AxiomRHI/Vulkan/VulkanDevice.cpp | 1 + .../Vulkan/VulkanMaterialResources.cpp | 74 +++++++---- .../AxiomRHI/Vulkan/VulkanMaterialResources.h | 6 + AxiomInternal/AxiomRHI/Vulkan/VulkanMesh.h | 3 + .../AxiomRHI/Vulkan/VulkanRendererTypes.h | 16 +++ .../AxiomRHI/Vulkan/VulkanResourceManager.cpp | 73 ++++++++++- .../AxiomRHI/Vulkan/VulkanResourceManager.h | 3 + .../AxiomRHI/Vulkan/VulkanRhiDevice.cpp | 79 +++++++++++- .../AxiomRHI/Vulkan/VulkanRhiDevice.h | 10 ++ .../AxiomRHI/Vulkan/VulkanSceneRenderer.cpp | 121 +++++++++++++----- Content/Shaders/mesh.frag | 45 +++++-- Content/Shaders/mesh.frag.spv | Bin 5904 -> 7732 bytes Content/Shaders/mesh.vert | 24 +++- Content/Shaders/mesh.vert.spv | Bin 3052 -> 4408 bytes Tests/RenderSubmissionTests.cpp | 9 +- 17 files changed, 395 insertions(+), 78 deletions(-) diff --git a/AxiomInternal/AxiomRHI/Vulkan/VulkanDescriptors.cpp b/AxiomInternal/AxiomRHI/Vulkan/VulkanDescriptors.cpp index 74a8d95e..7c62a351 100644 --- a/AxiomInternal/AxiomRHI/Vulkan/VulkanDescriptors.cpp +++ b/AxiomInternal/AxiomRHI/Vulkan/VulkanDescriptors.cpp @@ -8,10 +8,15 @@ void DescriptorLayoutBuilder::AddBinding(uint32_t Binding, VkDescriptorType Type) { + AddBinding(Binding, Type, 1); +} + +void DescriptorLayoutBuilder::AddBinding(uint32_t Binding, VkDescriptorType Type, + uint32_t DescriptorCount) { VkDescriptorSetLayoutBinding NewBind{}; NewBind.binding = Binding; NewBind.descriptorType = Type; - NewBind.descriptorCount = 1; + NewBind.descriptorCount = DescriptorCount; Bindings.push_back(NewBind); } diff --git a/AxiomInternal/AxiomRHI/Vulkan/VulkanDescriptors.h b/AxiomInternal/AxiomRHI/Vulkan/VulkanDescriptors.h index 2acad687..990bff70 100644 --- a/AxiomInternal/AxiomRHI/Vulkan/VulkanDescriptors.h +++ b/AxiomInternal/AxiomRHI/Vulkan/VulkanDescriptors.h @@ -6,6 +6,8 @@ struct DescriptorLayoutBuilder { std::vector Bindings; void AddBinding(uint32_t Binding, VkDescriptorType Type); + void AddBinding(uint32_t Binding, VkDescriptorType Type, + uint32_t DescriptorCount); void Clear(); VkDescriptorSetLayout Build(VkDevice Device, VkShaderStageFlags ShaderStages, void *pNext = VK_NULL_HANDLE, diff --git a/AxiomInternal/AxiomRHI/Vulkan/VulkanDevice.cpp b/AxiomInternal/AxiomRHI/Vulkan/VulkanDevice.cpp index ce5958e0..ade0e451 100644 --- a/AxiomInternal/AxiomRHI/Vulkan/VulkanDevice.cpp +++ b/AxiomInternal/AxiomRHI/Vulkan/VulkanDevice.cpp @@ -23,6 +23,7 @@ void VulkanDevice::Init(VulkanContext &Context) { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES}; Features12.bufferDeviceAddress = true; Features12.descriptorIndexing = true; + Features12.shaderSampledImageArrayNonUniformIndexing = true; vkb::PhysicalDeviceSelector Selector{Context.BootstrapInstance}; Selector.set_minimum_version(1, 3) diff --git a/AxiomInternal/AxiomRHI/Vulkan/VulkanMaterialResources.cpp b/AxiomInternal/AxiomRHI/Vulkan/VulkanMaterialResources.cpp index 49ab93bb..76920686 100644 --- a/AxiomInternal/AxiomRHI/Vulkan/VulkanMaterialResources.cpp +++ b/AxiomInternal/AxiomRHI/Vulkan/VulkanMaterialResources.cpp @@ -6,12 +6,25 @@ #include namespace Axiom { +namespace { +constexpr uint32_t MaxGraphicsMaterialTextures = 1024; +} + void VulkanMaterialResources::Init(const CreateInfo &CreateInfo) { m_Device = CreateInfo.Device; m_DescriptorAllocator = CreateInfo.DescriptorAllocator; m_MaterialDescriptorSetLayout = CreateInfo.MaterialDescriptorSetLayout; m_TextureSampler = CreateInfo.TextureSampler; m_CreateTextureImage = CreateInfo.CreateTextureImage; + m_BindlessMaterialDescriptorSet = + m_DescriptorAllocator->Allocate(m_Device, m_MaterialDescriptorSetLayout); + + VkDescriptorImageInfo GraphicsTextureSamplerInfo{}; + GraphicsTextureSamplerInfo.sampler = m_TextureSampler; + const VkWriteDescriptorSet SamplerWrite = VkInit::WriteDescriptorSet( + VK_DESCRIPTOR_TYPE_SAMPLER, m_BindlessMaterialDescriptorSet, + &GraphicsTextureSamplerInfo, 2); + vkUpdateDescriptorSets(m_Device, 1, &SamplerWrite, 0, VK_NULL_HANDLE); } void VulkanMaterialResources::Shutdown() { @@ -19,7 +32,9 @@ void VulkanMaterialResources::Shutdown() { m_MaterialImageViews.clear(); m_MaterialDescriptorSets.clear(); m_FallbackTexture = {}; + m_BindlessMaterialDescriptorSet = VK_NULL_HANDLE; m_NextMaterialHandleValue = 1; + m_NextTextureIndex = 1; #if !defined(NDEBUG) m_DebugGraphicsMaterialDescriptorUpdates = 0; #endif @@ -50,6 +65,10 @@ void VulkanMaterialResources::InitFallbackTexture() { } m_FallbackTexture = m_CreateTextureImage(CheckerTexture); + for (uint32_t TextureIndex = 0; TextureIndex < MaxGraphicsMaterialTextures; + ++TextureIndex) { + WriteTextureDescriptor(TextureIndex, m_FallbackTexture.ImageView); + } } MaterialHandle @@ -111,8 +130,8 @@ VulkanMaterialResources::ResolveMaterialTextureView(const MaterialInstance *Mate return TextureImage.ImageView; } -VkDescriptorSet -VulkanMaterialResources::ResolveMaterialDescriptorSet(const MaterialInstance *Material) { +uint32_t VulkanMaterialResources::ResolveMaterialTextureIndex( + const MaterialInstance *Material) { const MaterialInstance *MaterialKey = Material; const uint64_t MaterialRevision = Material ? Material->Revision : 0; const TextureSourceData *TextureSource = @@ -131,14 +150,19 @@ VulkanMaterialResources::ResolveMaterialDescriptorSet(const MaterialInstance *Ma if (It != m_MaterialDescriptorSets.end() && It->second.Revision == MaterialRevision && It->second.TextureView == TextureView) { - return It->second.DescriptorSet; + return It->second.TextureIndex; } MaterialDescriptorCacheEntry *Entry = nullptr; if (It == m_MaterialDescriptorSets.end()) { MaterialDescriptorCacheEntry NewEntry{}; - NewEntry.DescriptorSet = m_DescriptorAllocator->Allocate( - m_Device, m_MaterialDescriptorSetLayout); + NewEntry.DescriptorSet = m_BindlessMaterialDescriptorSet; + NewEntry.TextureIndex = MaterialKey == nullptr ? 0 : m_NextTextureIndex++; + assert(NewEntry.TextureIndex < MaxGraphicsMaterialTextures && + "Vulkan graphics material texture table exhausted"); + if (NewEntry.TextureIndex >= MaxGraphicsMaterialTextures) { + NewEntry.TextureIndex = 0; + } auto [InsertedIt, Inserted] = m_MaterialDescriptorSets.emplace(MaterialKey, NewEntry); (void)Inserted; @@ -147,30 +171,34 @@ VulkanMaterialResources::ResolveMaterialDescriptorSet(const MaterialInstance *Ma Entry = &It->second; } - VkDescriptorImageInfo GraphicsTextureImageInfo{}; - GraphicsTextureImageInfo.imageView = TextureView; - GraphicsTextureImageInfo.imageLayout = - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - VkDescriptorImageInfo GraphicsTextureSamplerInfo{}; - GraphicsTextureSamplerInfo.sampler = m_TextureSampler; - - const std::array GraphicsMaterialWrites = { - VkInit::WriteDescriptorSet(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, - Entry->DescriptorSet, &GraphicsTextureImageInfo, - 1), - VkInit::WriteDescriptorSet(VK_DESCRIPTOR_TYPE_SAMPLER, - Entry->DescriptorSet, - &GraphicsTextureSamplerInfo, 2)}; - vkUpdateDescriptorSets(m_Device, - static_cast(GraphicsMaterialWrites.size()), - GraphicsMaterialWrites.data(), 0, VK_NULL_HANDLE); + WriteTextureDescriptor(Entry->TextureIndex, TextureView); Entry->TextureView = TextureView; Entry->TextureSource = TextureSource; Entry->Revision = MaterialRevision; #if !defined(NDEBUG) ++m_DebugGraphicsMaterialDescriptorUpdates; #endif - return Entry->DescriptorSet; + return Entry->TextureIndex; +} + +VkDescriptorSet +VulkanMaterialResources::ResolveMaterialDescriptorSet( + const MaterialInstance *Material) { + (void)ResolveMaterialTextureIndex(Material); + return m_BindlessMaterialDescriptorSet; +} + +void VulkanMaterialResources::WriteTextureDescriptor(uint32_t TextureIndex, + VkImageView TextureView) { + VkDescriptorImageInfo GraphicsTextureImageInfo{}; + GraphicsTextureImageInfo.imageView = TextureView; + GraphicsTextureImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + VkWriteDescriptorSet Write = VkInit::WriteDescriptorSet( + VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, m_BindlessMaterialDescriptorSet, + &GraphicsTextureImageInfo, 1); + Write.dstArrayElement = TextureIndex; + vkUpdateDescriptorSets(m_Device, 1, &Write, 0, VK_NULL_HANDLE); } #if !defined(NDEBUG) diff --git a/AxiomInternal/AxiomRHI/Vulkan/VulkanMaterialResources.h b/AxiomInternal/AxiomRHI/Vulkan/VulkanMaterialResources.h index b70837f7..dbb442cc 100644 --- a/AxiomInternal/AxiomRHI/Vulkan/VulkanMaterialResources.h +++ b/AxiomInternal/AxiomRHI/Vulkan/VulkanMaterialResources.h @@ -27,6 +27,7 @@ class VulkanMaterialResources { void UpdateMaterialHandle(MaterialHandle Handle, const MaterialInstance &Material); const MaterialInstance *ResolveMaterialHandle(MaterialHandle Handle) const; VkImageView ResolveMaterialTextureView(const MaterialInstance *Material); + uint32_t ResolveMaterialTextureIndex(const MaterialInstance *Material); VkDescriptorSet ResolveMaterialDescriptorSet(const MaterialInstance *Material); VkImageView GetFallbackTextureView() const { return m_FallbackTexture.ImageView; } #if !defined(NDEBUG) @@ -42,20 +43,25 @@ class VulkanMaterialResources { VkImageView TextureView{VK_NULL_HANDLE}; const TextureSourceData *TextureSource{nullptr}; uint64_t Revision{0}; + uint32_t TextureIndex{0}; }; + void WriteTextureDescriptor(uint32_t TextureIndex, VkImageView TextureView); + VkDevice m_Device{VK_NULL_HANDLE}; DescriptorAllocator *m_DescriptorAllocator{nullptr}; VkDescriptorSetLayout m_MaterialDescriptorSetLayout{VK_NULL_HANDLE}; VkSampler m_TextureSampler{VK_NULL_HANDLE}; std::function m_CreateTextureImage; AllocatedImage m_FallbackTexture; + VkDescriptorSet m_BindlessMaterialDescriptorSet{VK_NULL_HANDLE}; std::unordered_map, MaterialHandleHash> m_MaterialsByHandle; std::unordered_map m_MaterialImageViews; std::unordered_map m_MaterialDescriptorSets; + uint32_t m_NextTextureIndex{1}; uint32_t m_NextMaterialHandleValue{1}; #if !defined(NDEBUG) uint32_t m_DebugGraphicsMaterialDescriptorUpdates{0}; diff --git a/AxiomInternal/AxiomRHI/Vulkan/VulkanMesh.h b/AxiomInternal/AxiomRHI/Vulkan/VulkanMesh.h index f87e49da..8afe7b37 100644 --- a/AxiomInternal/AxiomRHI/Vulkan/VulkanMesh.h +++ b/AxiomInternal/AxiomRHI/Vulkan/VulkanMesh.h @@ -28,6 +28,9 @@ class VulkanMesh final : public Mesh { AllocatedBuffer VertexBuffer; AllocatedBuffer IndexBuffer; AllocatedBuffer ProjectedVertexBuffer; + VkDeviceSize PooledVertexOffset{0}; + VkDeviceSize PooledIndexOffset{0}; + int32_t PooledVertexBase{0}; VkDescriptorSet DescriptorSet{VK_NULL_HANDLE}; glm::vec3 BoundsMin{0.0f}; glm::vec3 BoundsMax{0.0f}; diff --git a/AxiomInternal/AxiomRHI/Vulkan/VulkanRendererTypes.h b/AxiomInternal/AxiomRHI/Vulkan/VulkanRendererTypes.h index 0ceb02c1..902a0277 100644 --- a/AxiomInternal/AxiomRHI/Vulkan/VulkanRendererTypes.h +++ b/AxiomInternal/AxiomRHI/Vulkan/VulkanRendererTypes.h @@ -2,6 +2,8 @@ #include "AxiomRHI/Vulkan/VulkanTypes.h" +#include + #include #include #include @@ -44,6 +46,16 @@ struct MeshGraphicsPushConstants { glm::vec4 BaseColorFactor{1.0f}; float Metallic{0.0f}; float Roughness{0.5f}; + glm::vec2 Padding{0.0f}; + glm::uvec4 DrawOptions{0xffffffffu, 0u, 0u, 0u}; +}; +static_assert(offsetof(MeshGraphicsPushConstants, DrawOptions) == 96); +static_assert(sizeof(MeshGraphicsPushConstants) == 112); + +struct MeshGraphicsObjectData { + glm::mat4 Model{1.0f}; + glm::vec4 BaseColorFactor{1.0f}; + glm::vec4 MaterialParams{0.0f}; }; struct HzbReducePushConstants { @@ -58,7 +70,11 @@ struct ProjectedMeshVertexGpu { struct MeshFrameResources { AllocatedBuffer CameraBuffer; + AllocatedBuffer OpaqueObjectBuffer; + AllocatedBuffer OpaqueIndirectBuffer; AllocatedBuffer HzbReadbackBuffer; + size_t OpaqueObjectCapacity{0}; + size_t OpaqueIndirectCapacity{0}; VkDescriptorSet DepthFrameDescriptorSet{VK_NULL_HANDLE}; VkDescriptorSet GraphicsFrameDescriptorSet{VK_NULL_HANDLE}; VkDescriptorSet ComputeFrameDescriptorSet{VK_NULL_HANDLE}; diff --git a/AxiomInternal/AxiomRHI/Vulkan/VulkanResourceManager.cpp b/AxiomInternal/AxiomRHI/Vulkan/VulkanResourceManager.cpp index ab2d4cc8..dfea33ec 100644 --- a/AxiomInternal/AxiomRHI/Vulkan/VulkanResourceManager.cpp +++ b/AxiomInternal/AxiomRHI/Vulkan/VulkanResourceManager.cpp @@ -9,6 +9,8 @@ namespace Axiom { namespace { +constexpr uint32_t MaxGraphicsMaterialTextures = 1024; + uint32_t ComputeHzbMipCount(VkExtent2D BaseExtent) { uint32_t Width = BaseExtent.width; uint32_t Height = BaseExtent.height; @@ -80,6 +82,8 @@ void VulkanResourceManager::Shutdown() { Frame.TimestampQueryPool = VK_NULL_HANDLE; } VkBufferUtil::DestroyBuffer(m_Device->Allocator, Frame.CameraBuffer); + VkBufferUtil::DestroyBuffer(m_Device->Allocator, Frame.OpaqueObjectBuffer); + VkBufferUtil::DestroyBuffer(m_Device->Allocator, Frame.OpaqueIndirectBuffer); VkBufferUtil::DestroyBuffer(m_Device->Allocator, Frame.HzbReadbackBuffer); } @@ -298,7 +302,7 @@ void VulkanResourceManager::InitDescriptors() { std::vector Sizes = { {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 4.0f}, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 6.0f}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 2.0f}, + {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 20.0f}, {VK_DESCRIPTOR_TYPE_SAMPLER, 2.0f}, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 4.0f}, {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2.0f}}; @@ -320,13 +324,15 @@ void VulkanResourceManager::InitDescriptors() { { DescriptorLayoutBuilder Builder; Builder.AddBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + Builder.AddBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); m_MeshGraphicsFrameDescriptorLayout = Builder.Build(m_Device->Device, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT); } { DescriptorLayoutBuilder Builder; - Builder.AddBinding(1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE); + Builder.AddBinding(1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + MaxGraphicsMaterialTextures); Builder.AddBinding(2, VK_DESCRIPTOR_TYPE_SAMPLER); m_MeshGraphicsMaterialDescriptorLayout = Builder.Build(m_Device->Device, @@ -439,6 +445,18 @@ void VulkanResourceManager::InitMeshFrameResources() { VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU, VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT); + Frame.OpaqueObjectBuffer = VkBufferUtil::CreateBuffer( + m_Device->Allocator, sizeof(MeshGraphicsObjectData), + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | + VMA_ALLOCATION_CREATE_MAPPED_BIT); + Frame.OpaqueIndirectBuffer = VkBufferUtil::CreateBuffer( + m_Device->Allocator, sizeof(VkDrawIndexedIndirectCommand), + VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | + VMA_ALLOCATION_CREATE_MAPPED_BIT); + Frame.OpaqueObjectCapacity = 1; + Frame.OpaqueIndirectCapacity = 1; Frame.HzbReadbackBuffer = VkBufferUtil::CreateBuffer( m_Device->Allocator, static_cast(m_HzbReadbackBufferSize), VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_TO_CPU, @@ -459,7 +477,58 @@ void VulkanResourceManager::InitMeshFrameResources() { .queryCount = TimestampQueryCount}; VK_CHECK(vkCreateQueryPool(m_Device->Device, &QueryPoolInfo, VK_NULL_HANDLE, &Frame.TimestampQueryPool)); + UpdateGraphicsFrameDescriptor(Frame); + } +} + +void VulkanResourceManager::EnsureOpaqueIndirectCapacity( + MeshFrameResources &Frame, size_t DrawCapacity) { + const size_t WantedCapacity = std::max(1, DrawCapacity); + if (Frame.OpaqueObjectCapacity >= WantedCapacity && + Frame.OpaqueIndirectCapacity >= WantedCapacity) { + return; } + + size_t NewCapacity = + std::max(Frame.OpaqueObjectCapacity, Frame.OpaqueIndirectCapacity); + NewCapacity = std::max(1, NewCapacity); + while (NewCapacity < WantedCapacity) { + NewCapacity *= 2; + } + + VkBufferUtil::DestroyBuffer(m_Device->Allocator, Frame.OpaqueObjectBuffer); + VkBufferUtil::DestroyBuffer(m_Device->Allocator, Frame.OpaqueIndirectBuffer); + Frame.OpaqueObjectBuffer = VkBufferUtil::CreateBuffer( + m_Device->Allocator, NewCapacity * sizeof(MeshGraphicsObjectData), + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | + VMA_ALLOCATION_CREATE_MAPPED_BIT); + Frame.OpaqueIndirectBuffer = VkBufferUtil::CreateBuffer( + m_Device->Allocator, NewCapacity * sizeof(VkDrawIndexedIndirectCommand), + VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | + VMA_ALLOCATION_CREATE_MAPPED_BIT); + Frame.OpaqueObjectCapacity = NewCapacity; + Frame.OpaqueIndirectCapacity = NewCapacity; + UpdateGraphicsFrameDescriptor(Frame); +} + +void VulkanResourceManager::UpdateGraphicsFrameDescriptor( + const MeshFrameResources &Frame) const { + VkDescriptorBufferInfo CameraBufferInfo = + VkInit::BufferInfo(Frame.CameraBuffer.Buffer, 0, Frame.CameraBuffer.Size); + VkDescriptorBufferInfo ObjectBufferInfo = + VkInit::BufferInfo(Frame.OpaqueObjectBuffer.Buffer, 0, + Frame.OpaqueObjectBuffer.Size); + const std::array Writes = { + VkInit::WriteDescriptorBuffer(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + Frame.GraphicsFrameDescriptorSet, + &CameraBufferInfo, 0), + VkInit::WriteDescriptorBuffer(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + Frame.GraphicsFrameDescriptorSet, + &ObjectBufferInfo, 1)}; + vkUpdateDescriptorSets(m_Device->Device, static_cast(Writes.size()), + Writes.data(), 0, VK_NULL_HANDLE); } AllocatedImage diff --git a/AxiomInternal/AxiomRHI/Vulkan/VulkanResourceManager.h b/AxiomInternal/AxiomRHI/Vulkan/VulkanResourceManager.h index d43749e1..2c86b009 100644 --- a/AxiomInternal/AxiomRHI/Vulkan/VulkanResourceManager.h +++ b/AxiomInternal/AxiomRHI/Vulkan/VulkanResourceManager.h @@ -115,6 +115,8 @@ class VulkanResourceManager { MeshFrameResources &GetMeshFrame(uint64_t FrameNumber) { return m_MeshFrames[FrameNumber % FRAME_OVERLAP]; } + void EnsureOpaqueIndirectCapacity(MeshFrameResources &Frame, + size_t DrawCapacity); std::array &GetOffscreenCaptureFrames() { return m_OffscreenCaptureFrames; @@ -129,6 +131,7 @@ class VulkanResourceManager { void InitHzbResources(); void InitDescriptors(); void InitMeshFrameResources(); + void UpdateGraphicsFrameDescriptor(const MeshFrameResources &Frame) const; AllocatedImage CreateTextureImage(const TextureSourceData &TextureData, bool TrackForShutdown); AllocatedImage CreateTextureImage(const HDRTextureSourceData &TextureData, diff --git a/AxiomInternal/AxiomRHI/Vulkan/VulkanRhiDevice.cpp b/AxiomInternal/AxiomRHI/Vulkan/VulkanRhiDevice.cpp index c1a613a9..9d29ec13 100644 --- a/AxiomInternal/AxiomRHI/Vulkan/VulkanRhiDevice.cpp +++ b/AxiomInternal/AxiomRHI/Vulkan/VulkanRhiDevice.cpp @@ -2,6 +2,7 @@ #include "Renderer/Camera.h" #include "Renderer/RenderScene.h" +#include "AxiomRHI/Vulkan/VulkanBuffer.h" #include "AxiomRHI/Vulkan/VulkanImage.h" #include "AxiomRHI/Vulkan/VulkanInitializers.h" #include "AxiomRHI/Vulkan/VulkanMesh.h" @@ -12,6 +13,7 @@ #include #include #include +#include #include #include @@ -22,6 +24,9 @@ namespace { glm::vec3 TransformPoint(const glm::mat4 &Transform, const glm::vec3 &Point) { return glm::vec3(Transform * glm::vec4(Point, 1.0f)); } + +constexpr VkDeviceSize MeshVertexPoolSize = 512ull * 1024ull * 1024ull; +constexpr VkDeviceSize MeshIndexPoolSize = 256ull * 1024ull * 1024ull; } // namespace namespace Axiom { @@ -56,7 +61,8 @@ void VulkanRhiDevice::Init(const RHIDeviceCreateInfo &CreateInfo) { std::function &&Cleanup) { m_DrawSubmissionSystem.SubmitTransferUpload( std::move(Record), std::move(Cleanup)); - }}); + }}); + InitMeshGeometryPool(); m_MaterialResources.Init( {.Device = m_Device.Device, @@ -118,6 +124,10 @@ void VulkanRhiDevice::Shutdown() { m_GraphicsQueue.reset(); m_MeshesByHandle.clear(); m_NextMeshHandleValue = 1; + VkBufferUtil::DestroyBuffer(m_Device.Allocator, m_PooledVertexBuffer); + VkBufferUtil::DestroyBuffer(m_Device.Allocator, m_PooledIndexBuffer); + m_PooledVertexHead = 0; + m_PooledIndexHead = 0; m_GpuResourceQueue->Flush(); m_GpuResourceQueue.reset(); m_PipelineLibrary.Shutdown(); @@ -221,6 +231,7 @@ VulkanRhiDevice::CreateMesh(const MeshData &MeshSource, if (Mesh == nullptr) { return nullptr; } + AllocateMeshGeometry(*Mesh, MeshSource); const MeshHandle Handle = AllocateMeshHandle(); Mesh->AssignHandle(Handle); @@ -248,6 +259,72 @@ MeshHandle VulkanRhiDevice::AllocateMeshHandle() { return Handle; } +void VulkanRhiDevice::InitMeshGeometryPool() { + const VkBufferUsageFlags VertexUsage = + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT; + const VkBufferUsageFlags IndexUsage = + VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + m_PooledVertexBuffer = VkBufferUtil::CreateBuffer( + m_Device.Allocator, static_cast(MeshVertexPoolSize), VertexUsage, + VMA_MEMORY_USAGE_CPU_TO_GPU, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | + VMA_ALLOCATION_CREATE_MAPPED_BIT); + m_PooledIndexBuffer = VkBufferUtil::CreateBuffer( + m_Device.Allocator, static_cast(MeshIndexPoolSize), IndexUsage, + VMA_MEMORY_USAGE_CPU_TO_GPU, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | + VMA_ALLOCATION_CREATE_MAPPED_BIT); + m_PooledVertexHead = 0; + m_PooledIndexHead = 0; +} + +VkDeviceSize VulkanRhiDevice::AlignPoolOffset(VkDeviceSize Offset, + VkDeviceSize Alignment) const { + return (Offset + Alignment - 1) & ~(Alignment - 1); +} + +void VulkanRhiDevice::AllocateMeshGeometry(VulkanMesh &Mesh, + const MeshData &MeshSource) { + const VkDeviceSize VertexBytes = + static_cast(MeshSource.Vertices.size() * sizeof(MeshVertex)); + const VkDeviceSize IndexBytes = + static_cast(MeshSource.Indices.size() * sizeof(uint32_t)); + + const VkDeviceSize VertexOffset = + AlignPoolOffset(m_PooledVertexHead, alignof(MeshVertex)); + const VkDeviceSize IndexOffset = + AlignPoolOffset(m_PooledIndexHead, alignof(uint32_t)); + assert(VertexOffset + VertexBytes <= m_PooledVertexBuffer.Size && + "Merged Vulkan vertex buffer pool exhausted"); + assert(IndexOffset + IndexBytes <= m_PooledIndexBuffer.Size && + "Merged Vulkan index buffer pool exhausted"); + if (VertexOffset + VertexBytes > m_PooledVertexBuffer.Size || + IndexOffset + IndexBytes > m_PooledIndexBuffer.Size) { + return; + } + + std::memcpy(static_cast(m_PooledVertexBuffer.Info.pMappedData) + + VertexOffset, + MeshSource.Vertices.data(), static_cast(VertexBytes)); + std::memcpy(static_cast(m_PooledIndexBuffer.Info.pMappedData) + + IndexOffset, + MeshSource.Indices.data(), static_cast(IndexBytes)); + vmaFlushAllocation(m_Device.Allocator, m_PooledVertexBuffer.Allocation, + VertexOffset, VertexBytes); + vmaFlushAllocation(m_Device.Allocator, m_PooledIndexBuffer.Allocation, + IndexOffset, IndexBytes); + + Mesh.PooledVertexOffset = VertexOffset; + Mesh.PooledIndexOffset = IndexOffset; + Mesh.PooledVertexBase = + static_cast(VertexOffset / static_cast(sizeof(MeshVertex))); + m_PooledVertexHead = VertexOffset + VertexBytes; + m_PooledIndexHead = IndexOffset + IndexBytes; +} + VulkanMesh *VulkanRhiDevice::ResolveMeshHandle(MeshHandle Handle) const { assert(Handle.IsValid() && "Render submission contained an invalid mesh handle"); if (!Handle.IsValid()) { diff --git a/AxiomInternal/AxiomRHI/Vulkan/VulkanRhiDevice.h b/AxiomInternal/AxiomRHI/Vulkan/VulkanRhiDevice.h index 305e5d8e..550e9ab4 100644 --- a/AxiomInternal/AxiomRHI/Vulkan/VulkanRhiDevice.h +++ b/AxiomInternal/AxiomRHI/Vulkan/VulkanRhiDevice.h @@ -54,6 +54,8 @@ class VulkanRhiDevice final : public IRHIDevice { return m_MaterialResources; } VulkanOcclusionCulling &GetOcclusionCulling() { return m_OcclusionCulling; } + VkBuffer GetPooledVertexBuffer() const { return m_PooledVertexBuffer.Buffer; } + VkBuffer GetPooledIndexBuffer() const { return m_PooledIndexBuffer.Buffer; } const std::shared_ptr &GetGpuResourceQueue() const { return m_GpuResourceQueue; } @@ -70,10 +72,14 @@ class VulkanRhiDevice final : public IRHIDevice { const MaterialInstance &Material); bool IsInitialized() const { return m_IsInitialized; } MeshHandle AllocateMeshHandle(); + void AllocateMeshGeometry(VulkanMesh &Mesh, const MeshData &MeshSource); VulkanMesh *ResolveMeshHandle(MeshHandle Handle) const; const MaterialInstance *ResolveMaterialHandle(MaterialHandle Handle) const; private: + void InitMeshGeometryPool(); + VkDeviceSize AlignPoolOffset(VkDeviceSize Offset, VkDeviceSize Alignment) const; + bool m_IsInitialized{false}; uint64_t m_FrameNumber{0}; VkExtent2D m_WindowExtent{1700, 900}; @@ -90,6 +96,10 @@ class VulkanRhiDevice final : public IRHIDevice { VulkanDrawSubmissionSystem m_DrawSubmissionSystem; VulkanMaterialResources m_MaterialResources; VulkanOcclusionCulling m_OcclusionCulling; + AllocatedBuffer m_PooledVertexBuffer; + AllocatedBuffer m_PooledIndexBuffer; + VkDeviceSize m_PooledVertexHead{0}; + VkDeviceSize m_PooledIndexHead{0}; std::unique_ptr m_GraphicsQueue; std::unique_ptr m_ComputeQueue; std::unique_ptr m_TransferQueue; diff --git a/AxiomInternal/AxiomRHI/Vulkan/VulkanSceneRenderer.cpp b/AxiomInternal/AxiomRHI/Vulkan/VulkanSceneRenderer.cpp index e815a1db..695d76de 100644 --- a/AxiomInternal/AxiomRHI/Vulkan/VulkanSceneRenderer.cpp +++ b/AxiomInternal/AxiomRHI/Vulkan/VulkanSceneRenderer.cpp @@ -570,10 +570,16 @@ void VulkanSceneRenderer::UpdateGraphicsFrameDescriptors( VkDescriptorBufferInfo CameraBufferInfo = VkInit::BufferInfo(Frame.CameraBuffer.Buffer, 0, Frame.CameraBuffer.Size); VkDescriptorBufferInfo MutableCameraBufferInfo = CameraBufferInfo; - std::array GraphicsFrameWrites = { + VkDescriptorBufferInfo ObjectBufferInfo = + VkInit::BufferInfo(Frame.OpaqueObjectBuffer.Buffer, 0, + Frame.OpaqueObjectBuffer.Size); + std::array GraphicsFrameWrites = { VkInit::WriteDescriptorBuffer(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, Frame.GraphicsFrameDescriptorSet, - &MutableCameraBufferInfo, 0)}; + &MutableCameraBufferInfo, 0), + VkInit::WriteDescriptorBuffer(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + Frame.GraphicsFrameDescriptorSet, + &ObjectBufferInfo, 1)}; vkUpdateDescriptorSets(m_Device->GetVulkanDevice().Device, static_cast(GraphicsFrameWrites.size()), GraphicsFrameWrites.data(), 0, VK_NULL_HANDLE); @@ -850,10 +856,18 @@ void VulkanSceneRenderer::RecordOpaqueForwardPass( RHIHandle(m_Device->GetPipelineLibrary().GetMeshGraphicsPipelineLayout()), 0, GraphicsFrameSets); - VkDescriptorSet BoundMaterialDescriptorSet = VK_NULL_HANDLE; -#if !defined(NDEBUG) - uint32_t MaterialDescriptorBindCount = 0; -#endif + auto &MutableFrame = + const_cast(Frame); + m_Device->GetResourceManager().EnsureOpaqueIndirectCapacity( + MutableFrame, GraphicsSubmissions.size()); + + auto *ObjectData = static_cast( + MutableFrame.OpaqueObjectBuffer.Info.pMappedData); + auto *IndirectCommands = static_cast( + MutableFrame.OpaqueIndirectBuffer.Info.pMappedData); + + VkDescriptorSet FirstMaterialDescriptorSet = VK_NULL_HANDLE; + uint32_t DrawCount = 0; for (const VisibleSubmission &Visible : GraphicsSubmissions) { const RenderMeshSubmission &Submission = GetSubmission(Visible.SubmissionIndex); VulkanMesh *Mesh = ResolveVisibleMesh(Visible); @@ -861,44 +875,79 @@ void VulkanSceneRenderer::RecordOpaqueForwardPass( continue; } - const VkDescriptorSet MaterialDescriptorSet = - m_Device->GetMaterialResources().ResolveMaterialDescriptorSet( - m_Device->ResolveMaterialHandle(Submission.MaterialHandle)); - if (MaterialDescriptorSet != BoundMaterialDescriptorSet) { - const auto MaterialSets = RHIDescriptorSets(MaterialDescriptorSet); - CommandList.BindDescriptorSet( - RHIBindPoint::Graphics, - RHIHandle(m_Device->GetPipelineLibrary().GetMeshGraphicsPipelineLayout()), - 1, MaterialSets); - BoundMaterialDescriptorSet = MaterialDescriptorSet; -#if !defined(NDEBUG) - ++MaterialDescriptorBindCount; -#endif + const MaterialInstance *Material = + m_Device->ResolveMaterialHandle(Submission.MaterialHandle); + if (FirstMaterialDescriptorSet == VK_NULL_HANDLE) { + FirstMaterialDescriptorSet = + m_Device->GetMaterialResources().ResolveMaterialDescriptorSet(Material); } - MeshGraphicsPushConstants PushConstants{}; - PushConstants.Model = Submission.Transform; - if (const MaterialInstance *Material = - m_Device->ResolveMaterialHandle(Submission.MaterialHandle); - Material != nullptr) { - PushConstants.BaseColorFactor = Material->BaseColorFactor; - PushConstants.Metallic = Material->Metallic; - PushConstants.Roughness = Material->Roughness; + + MeshGraphicsObjectData &Object = ObjectData[DrawCount]; + Object.Model = Submission.Transform; + if (Material != nullptr) { + Object.BaseColorFactor = Material->BaseColorFactor; + Object.MaterialParams = + glm::vec4(Material->Metallic, Material->Roughness, + static_cast( + m_Device->GetMaterialResources() + .ResolveMaterialTextureIndex(Material)), + 0.0f); + } else { + Object.BaseColorFactor = glm::vec4(1.0f); + Object.MaterialParams = glm::vec4(0.0f, 0.5f, 0.0f, 0.0f); } - CommandList.PushConstants( - RHIHandle(m_Device->GetPipelineLibrary().GetMeshGraphicsPipelineLayout()), - VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(MeshGraphicsPushConstants), &PushConstants); - BindMeshBuffers(CommandList, *Mesh); - CommandList.DrawIndexed(Mesh->IndexCount, 1, 0, 0, 0); + + IndirectCommands[DrawCount] = VkDrawIndexedIndirectCommand{ + .indexCount = Mesh->IndexCount, + .instanceCount = 1, + .firstIndex = + static_cast(Mesh->PooledIndexOffset / sizeof(uint32_t)), + .vertexOffset = Mesh->PooledVertexBase, + .firstInstance = DrawCount}; + ++DrawCount; } + if (DrawCount == 0) { + CommandList.EndRendering(); + return; + } + + vmaFlushAllocation(m_Device->GetVulkanDevice().Allocator, + MutableFrame.OpaqueObjectBuffer.Allocation, 0, + DrawCount * sizeof(MeshGraphicsObjectData)); + vmaFlushAllocation(m_Device->GetVulkanDevice().Allocator, + MutableFrame.OpaqueIndirectBuffer.Allocation, 0, + DrawCount * sizeof(VkDrawIndexedIndirectCommand)); + + if (FirstMaterialDescriptorSet == VK_NULL_HANDLE) { + FirstMaterialDescriptorSet = + m_Device->GetMaterialResources().ResolveMaterialDescriptorSet(nullptr); + } + const auto MaterialSets = RHIDescriptorSets(FirstMaterialDescriptorSet); + CommandList.BindDescriptorSet( + RHIBindPoint::Graphics, + RHIHandle(m_Device->GetPipelineLibrary().GetMeshGraphicsPipelineLayout()), 1, + MaterialSets); + + MeshGraphicsPushConstants PushConstants{}; + PushConstants.DrawOptions.x = 0u; + CommandList.PushConstants( + RHIHandle(m_Device->GetPipelineLibrary().GetMeshGraphicsPipelineLayout()), + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, + sizeof(MeshGraphicsPushConstants), &PushConstants); + + CommandList.BindVertexBuffer(0, RHIHandle(m_Device->GetPooledVertexBuffer()), 0); + CommandList.BindIndexBuffer(RHIHandle(m_Device->GetPooledIndexBuffer()), 0, + RHIIndexType::UInt32); + CommandList.DrawIndexedIndirect(RHIHandle(MutableFrame.OpaqueIndirectBuffer.Buffer), + 0, DrawCount, + sizeof(VkDrawIndexedIndirectCommand)); CommandList.EndRendering(); #if !defined(NDEBUG) AccessFrameStats().DebugGraphicsMaterialDescriptorUpdates = m_Device->GetMaterialResources().GetDebugGraphicsMaterialDescriptorUpdates(); - AccessFrameStats().DebugOpaqueMaterialDescriptorBinds = - MaterialDescriptorBindCount; + AccessFrameStats().DebugOpaqueMaterialDescriptorBinds = 1; #endif } @@ -982,6 +1031,8 @@ void VulkanSceneRenderer::RecordTranslucentForwardPass( PushConstants.BaseColorFactor = Material->BaseColorFactor; PushConstants.Metallic = Material->Metallic; PushConstants.Roughness = Material->Roughness; + PushConstants.DrawOptions.y = + m_Device->GetMaterialResources().ResolveMaterialTextureIndex(Material); } CommandList.PushConstants( RHIHandle(m_Device->GetPipelineLibrary().GetMeshGraphicsPipelineLayout()), diff --git a/Content/Shaders/mesh.frag b/Content/Shaders/mesh.frag index 04086785..8d36a2ff 100644 --- a/Content/Shaders/mesh.frag +++ b/Content/Shaders/mesh.frag @@ -1,18 +1,31 @@ #version 460 +#extension GL_EXT_nonuniform_qualifier : require layout(location = 0) in vec3 inNormal; layout(location = 1) in vec2 inTexCoord; layout(location = 2) in vec3 inWorldPos; +layout(location = 3) flat in uint inObjectIndex; layout(location = 0) out vec4 outColor; -layout(set = 1, binding = 1) uniform texture2D baseColorImage; +layout(set = 1, binding = 1) uniform texture2D baseColorImages[1024]; layout(set = 1, binding = 2) uniform sampler baseColorSampler; +struct MeshGraphicsObjectData { + mat4 model; + vec4 baseColorFactor; + vec4 materialParams; +}; + +layout(std430, set = 0, binding = 1) readonly buffer ObjectData { + MeshGraphicsObjectData objects[]; +} objectData; + layout(push_constant) uniform MeshGraphicsPushConstants { mat4 model; vec4 baseColorFactor; float metallic; float roughness; + uvec4 drawOptions; } pushConstants; layout(std140, set = 0, binding = 0) uniform CameraFrame { mat4 view; @@ -26,8 +39,20 @@ layout(std140, set = 0, binding = 0) uniform CameraFrame { } cameraFrame; void main() { - vec4 texColor = texture(sampler2D(baseColorImage, baseColorSampler), inTexCoord); - vec4 baseColor = texColor * pushConstants.baseColorFactor; + vec4 baseColorFactor = pushConstants.baseColorFactor; + float metallic = pushConstants.metallic; + float roughness = pushConstants.roughness; + uint textureIndex = pushConstants.drawOptions.y; + if (inObjectIndex != 0xffffffffu) { + MeshGraphicsObjectData object = objectData.objects[inObjectIndex]; + baseColorFactor = object.baseColorFactor; + metallic = object.materialParams.x; + roughness = object.materialParams.y; + textureIndex = uint(object.materialParams.z); + } + + vec4 texColor = texture(sampler2D(baseColorImages[nonuniformEXT(textureIndex)], baseColorSampler), inTexCoord); + vec4 baseColor = texColor * baseColorFactor; bool litMode = cameraFrame.renderOptions.x == 0u; bool hasLight = cameraFrame.lightColorAndEnabled.w > 0.5; @@ -39,9 +64,9 @@ void main() { // Dielectric ambient is low; metallic gets a boosted ambient to stand in for // environment reflections that would normally light a mirror-like surface. - float dielectricAmbient = mix(0.05, 0.20, pushConstants.roughness); - float metallicAmbient = mix(0.35, 0.15, pushConstants.roughness); - float ambientFloor = mix(dielectricAmbient, metallicAmbient, pushConstants.metallic); + float dielectricAmbient = mix(0.05, 0.20, roughness); + float metallicAmbient = mix(0.35, 0.15, roughness); + float ambientFloor = mix(dielectricAmbient, metallicAmbient, metallic); if (!hasLight) { outColor = vec4(baseColor.rgb * ambientFloor, baseColor.a); @@ -60,15 +85,15 @@ void main() { float NdotH = max(dot(N, H), 0.0); // Roughness -> shininess: roughness=0 gives tight highlight, roughness=1 gives almost none. - float shininess = mix(256.0, 2.0, pushConstants.roughness); - float specularStr = pow(NdotH, shininess) * (1.0 - pushConstants.roughness); + float shininess = mix(256.0, 2.0, roughness); + float specularStr = pow(NdotH, shininess) * (1.0 - roughness); // Metallic reduces diffuse (energy redirected to specular). - float diffuseScale = mix(1.0, 0.15, pushConstants.metallic); + float diffuseScale = mix(1.0, 0.15, metallic); float diffuse = max(NdotL * lightIntensity * diffuseScale, ambientFloor); // Dielectric specular is near-white (F0 ~0.04); metallic tints specular with base color. - vec3 specularColor = mix(vec3(0.04), baseColor.rgb, pushConstants.metallic); + vec3 specularColor = mix(vec3(0.04), baseColor.rgb, metallic); vec3 specular = specularColor * specularStr * lightIntensity * lightColor; outColor = vec4(baseColor.rgb * lightColor * diffuse + specular, baseColor.a); diff --git a/Content/Shaders/mesh.frag.spv b/Content/Shaders/mesh.frag.spv index 7c82c6849567c64f50379f957e20f048bf1b5d00..6bbc90709109782e6ba437e395153dc628846982 100644 GIT binary patch literal 7732 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 OM&}Wu&)=V8z<&Vo9WIps literal 5904 zcmZve`*&Pb6~}LyCVdcj$g@SUX_cbXik2dZZ4*Y38=MIGG7E6DZV2 zBSouL6a^71h*c3mt%{1^E52Vv@%^iRg1_lVCOIvaoOOoY@+_9Qt?a35$nt`B4xwsv}Gq)}@$5kdNs0r>t(E6u?j)0Km% z_!9D#@QKRwOf4;9`tlmG`t@dNG^_geB`eDQdG2M&i{Lxc*3@XTGBcG;w#H{$QzMOf zt6izLTiE)E)5kI0sHQb|KXZ$@{)M`>RVLe%+|Qpj*EpTFE45lSiMW8fbIr!=!Kr%M zGQaD)1U}O>uY0o=yq2|hGHqV}E6_(O(`mD^tqCKtFA;OxlBKuGonOq_W}1zg(#dw# z;8d(x+`9A~|35Zso6Pf%H(J^A<2)zhW*W`*M0PtT@LbM54oXSWXCsjahi&vOIV zRHZd$`tTbh-d_)oa|(<2KJZG%b6brTLdCx~ zxfyOgeGb9%noH}kuET*BRT%evuw8jvUw$vL`mRQEx?(%!d5^;P6n5`SR^Qub)~Xyt z$9^1adv0Hx+zsDVZL~XeEKcq#^p3qRIf34q%IeudkM$)d#b(mU*;=JJ(QfATKMq#S z4jhqZ`?!sZ9DUaZts|3D(FJuonPHG ztm1bmTufTQ8?qd(y$33f(2Y5xFz91< z&VxIz^&5LW+&RUaL*1Shy9oVaxHY?uONg|G12d0go)^RY7HT(-y89(|KKe`Gt041U z-lZEq2(GVlokw*qgNt8UVCv?%EXVm_xSp58#X9wM-f-&+iapmaWb@Ft%U=gEwrJmips(@Pqdo+Q zxz1s@F_CX8vcBbfJJFvxJ+>+G`+lQuj{0_^N4#%{`(2KI3;Ko)PvrjMzV8p*zhWW& z26We8y)$sEnAH;89$O-td?lcD=RS)9?O$ZNZF zzwu%BjK;cj-|4d5?{?_!so(3+jrW@!y7t3ey5H%t{b-kdyri4YZ*lmm`z&GG=$qJ2-=G&jzG3RVSLZ-yLwneD->GvUeZ=+gUAhqRP1=i( zxi5nB6W`3Kc}8OTYhMY84dXuuUk&*dt%Aa54YK&1Mb4KZ8z*i~&sl7k-tn$q0r@`o zCio7R$GiV(NZVuN@r`;-f$3wOwFPD#-+`Wp)DU-)iDcKipM_$Elq-d2u1cq6jw+)bx@KSm%i$XW?L%;F|0RQKa4ps}4!;ia zY<=5eFW+3?1FXxMCLrhbOnf7ELHd|`46Z%$?M3d+_f|0R$oDqn$mg5sJE@QPyx-cR zmbW82N9gZB)+gqBC$jVTwuaBUki}!Z8wxd}`>dg9&4 z#ySG4A&WU@VABO=4beMwWaIVMZtmzEZP7cf*P2@JHna|ELb3MQ0*^a!3%Z!(h_w&U zS+4cg0`qR@>zp?ic-;Bh&|{vB$gyv?BWp9}5L`WKdk?ZPz7tW~dy&PXw)Y{6Nsg$^ ze10di8*7~(faaj6`wnFNty`NheoMrohC7k1A?}vF@j*yDYWPrri94c(nD@hA$Dlik z-uMWzG2&6nN0Flz?bdP>E*^P4hU~tEeiyPnb4A{dBkLm`c|U<1d9|DOI9xpPd=lAp zhyE#K=edWx{W(t(eZ&X29b%t`%zGJh4|FeN?z`dIBG+e-%en7IcmBxz0J1*fk^8gA zkz0G@z7JWOXXCr~ImmY-&c?bPhQ#CD_&l12UL-mjvY_Xa5H`Wmu$ ztnur}u}1Unf%Gxo<8bW}`we8zDD-b4d+(y2Zz1a=9zIVXn9~67_ zU1V#rKlImbJ*VK>j9UVK8d?FxUHX23i95{U8yEZW1LW@g_#v41I6FEF{}Ch>^PJ)3yne}=4&c=-Gr*}V*(Um)uz9(jL>d>C>s zo#$7Oe&W_K4;Rzl{rNRi-k;wTn7E?{a(^QKZ;{LSe}`_2c+`0s*>i|GpF!47Jo5Y= zc^;Z8>ih$;e&U|RR`|1!wQYnV_a6&9zEgifH`Wo@pOM9^J+QwNm}`me*3*I&E2 O-J>D6Hos?AL;nM#j3bl) diff --git a/Content/Shaders/mesh.vert b/Content/Shaders/mesh.vert index 5f575ff8..60923809 100644 --- a/Content/Shaders/mesh.vert +++ b/Content/Shaders/mesh.vert @@ -7,6 +7,7 @@ layout(location = 2) in vec2 inTexCoord; layout(location = 0) out vec3 outNormal; layout(location = 1) out vec2 outTexCoord; layout(location = 2) out vec3 outWorldPos; +layout(location = 3) flat out uint outObjectIndex; layout(std140, set = 0, binding = 0) uniform CameraFrame { mat4 view; @@ -17,19 +18,38 @@ layout(std140, set = 0, binding = 0) uniform CameraFrame { uvec4 renderOptions; } cameraFrame; +struct MeshGraphicsObjectData { + mat4 model; + vec4 baseColorFactor; + vec4 materialParams; +}; + +layout(std430, set = 0, binding = 1) readonly buffer ObjectData { + MeshGraphicsObjectData objects[]; +} objectData; + layout(push_constant) uniform MeshGraphicsPushConstants { mat4 model; vec4 baseColorFactor; float metallic; float roughness; + uvec4 drawOptions; } pushConstants; void main() { - vec4 worldPosition = pushConstants.model * vec4(inPosition.xyz, 1.0); + uint objectBase = pushConstants.drawOptions.x; + outObjectIndex = objectBase == 0xffffffffu + ? 0xffffffffu + : objectBase + gl_InstanceIndex; + mat4 model = objectBase == 0xffffffffu + ? pushConstants.model + : objectData.objects[outObjectIndex].model; + + vec4 worldPosition = model * vec4(inPosition.xyz, 1.0); vec4 clipPosition = cameraFrame.viewProjection * worldPosition; clipPosition.z = (clipPosition.z + clipPosition.w) * 0.5; gl_Position = clipPosition; - outNormal = normalize(mat3(pushConstants.model) * inNormal.xyz); + outNormal = normalize(mat3(model) * inNormal.xyz); outTexCoord = inTexCoord; outWorldPos = worldPosition.xyz; } diff --git a/Content/Shaders/mesh.vert.spv b/Content/Shaders/mesh.vert.spv index a4bf2f5c955fc98683276041a2d0277810096be7..5f3ad4cba7c66b5092d0c21e13cc747f3f9b7e43 100644 GIT binary patch literal 4408 zcmZvf>2?%V5XU>qB*EZCR*fHg z0AItW@B#ebBls3B=Xmu0>%KK<=Wsd2t@_t;tLonBfxg~}LC@>)hP@$g*UQ!cuUCjC zY#`kiSJqah+evl$)ajF0JmdAHhS)qSiPbM^_+beHWI z?Y}BWLEd@)f{-EUqK&PflFa$-0GoZ@)55L4Ow`yUfUEXfjN>Q_&_|2p(-GHpH zVKt&^P}e@-4W<~mZYWYIsz>pHUrC}^x`9+j9*rRJ>-CUyJ2+Lw(e`Go8ML$bnLW8y zV}ED0m4rH8rw*tOp4aNi2b^9++sSHL%5t+B+@qGkv_G5mJIiVImB4ATUQEiz@~0E; zOU~Dw1)KL1KdTu%%Y3LA?8!A}y0m6L3F6SNm;Kmps6hIDT7KQ}!C2XSP;bFI(~+}0 zsP&?-ohYtX%TYU|HIlI{Y7d)xAhwrgqQqA5c>;CDv)Kt17qC{gB^D6 z4UGC)al|7@52J2yYQwht1RZx;N#pOGY<}D;eyu1@*1`vRb=mtNWXThb$faXa4jCuSRjhuRDx+44WM&wPQYIcRjdQilVrhV$5sU>_92T{L1d8 zyJ~My=P`HWxui+98Kh@R@-SH+8t=I-Y(g^2?C1m7Yz`wXSUHFFIqZ54&0L+9v{-7tK>$jdNzF!JIoW^J$c{ps%>GI7u|V|khRLMAqPX3OM*Lczy$BIm## zmJRP{RKyyUOfMnu7i76F#rYlX_ns1uOnyi8H@Si#qu;L`KjaZbVOGrsKL{JnVV01w zIqzhC_rS=D*&Y@3h^Cx;Ks>RLIp=v1i+BgM5(xutgVe^LCuT8Gq) zm&6liOgm?CK7PpK4nu!JJpG|A%WGOZ3;jFV$pig}-URb&J)l>|l>ze$J|doZMaK67 z?c56(XCpJ8VCc^|o_NR?w37>Qk!Q8{I2nKBqQkhKIq~FXJ%GO?^1R)d5&heMIU%+Y zaUr|2E8@uoJK`fVA7Ivt)tpT4`KF>LzfZN3FX!4F+!CM9=eA_68Png9KuyQwzpfo$ zuo2y~ubuqB)*W%qhInG4Cr{*x!_Wtgx4vyUc}_d_$jlxwc;{Q%(IH!3`OPCQ`nsc? z9C}5>=SLb_aO{zpRWS4qwBu`fWM&u4&d>f`>*);?(zv{P7BT)5!gmT=`Z*+GH~Cmj zJ|TXxOP>G*vC`&pSXTQ7^i zn7Kb>&v%LSw_>3?E_y{oZScgT4($IDhCMoL?2J+IoPo@|PKd@uk6eDQI&4=tknd}v zQ4zSs2g}Fj9E<QA076kc<|dIi}jUwJC7QXv9tWE zPLF*c{%cW1#5=$q85`_}wTB|^lAhDUZyctrbDwpGqhsao6Q72NINh_6* z;ltgUPfI*F#4?|Lh-GoOQ{Lych*;R|S8TAmBJ6li s=;>8WJ2>a;Xn!PnC_;DN$(F-+DZZD(_maUNmgRu&W69|M5qehiA8BuU6951J literal 3052 zcmZvc+j10D6oz*WGl_tlkBTr5IS3L_j*27`1~Lf-G7yAItDWg0ZS{0db$5jF3NLs7 z-^d5>!bk8iys^sv>)tEVPPtPnd#!(+*Iqp{(+hLXO}V{p-o0|wIpn5=IAP85?rv;t zEat^v@#d}TSnPK*r6D#4+`f{Z)&F4-B?w1FUyGJR>!P0Mx#)#xMt{?~|B!RT&FXLL z*HFF>B)>_sc+gApsEASmHiYig{Upq{yIC;Wj{13TC*SU*NnQj=kxSQ<6*inWkgQ=`c%oUT-I1PJC+nQ1@seZ$ldT zjgoP}*q#yAkE78TB}T)Y6W$4iVHT`rx@1{lg5A$i_=cO>u%qr#mi`p>t3l8YI5lJ2 z`yU;3_sjU>%;v|e@f)RCu@(IiN>rvQWFtiN*YOX% zFnbwhMfg@?Su;2XoxL9M!Re*MqvSui(b11|9gXW@#EbTsOXWvD=yu|GSJ%)Ev0of! z%ATb=#nUt!2E02Mdlx0UP>HdJvU?G}?WAcoC^7ajN_L?VV^3xG%CDj8Dj&N=oom`y z+cRdiCJ&SKA-RJS81v(p9diI{*D&IOQJ>k-HyE{am6IJ~r#RGOdq6$#9IMCVdOd~_ zlVg5U;_H6MlYU_MwMFJf&2>NINk8lBUFlctx%KsX4P!5f|7Q&&HrQVx`iHk3Og=A+ z!#r${n4j&Ty^CS=0mf|@HG)x>VenwoMK1PhJp0*lw~&d0o_DLu>=ZJw(es8T9}xT*9``pl)T6D=+DTKS`EXmiZxsQGV^Jx?3Xpxa#Et;yL7Hm$4-_@)6zGQU~#n*-J3$KBk-8^hG@QlOpHdEtBu8xE7)g z_AT9DRUh-^`;yDZdd80Y=XF=JtMVf+c+Bem)XUgk@I3L*lj}-Ne^op=sMGX|;_LN) zC0S?L^tU9??`g%mts7r3-hsWdc<3PNxT~9(=$RWbzh7YJ?|B|ya%XjV(C#$n31;eX3*%WY_M?hxa)w0!PO7xQN}_R783`;W2OywNb}2#iZ9$ zB5XK@eIy*bQi zt(VJ^@w1qpiN^+(Index % 40u) * 0.12f - 2.34f; - const float Y = static_cast(Index / 40u) * 0.10f - 1.20f; + const float X = static_cast(Index % 100u) * 0.05f - 2.5f; + const float Y = static_cast(Index / 100u) * 0.05f - 1.25f; Axiom::RenderCommand::Submit({ .MeshHandle = MeshHandle, .DebugDataId = Axiom::RegisterRenderMeshSubmissionDebugData( From c4b54d01398aa18e45c0bb7b1e402a4f27551a24 Mon Sep 17 00:00:00 2001 From: Tamely Date: Sun, 7 Jun 2026 10:24:52 -0500 Subject: [PATCH 2/2] Create keys to sort on --- Axiom/Renderer/RenderScene.h | 1 + .../AxiomRHI/Vulkan/VulkanSceneRenderer.cpp | 41 +++++++++++++------ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/Axiom/Renderer/RenderScene.h b/Axiom/Renderer/RenderScene.h index f6d8a979..b5cd88d3 100644 --- a/Axiom/Renderer/RenderScene.h +++ b/Axiom/Renderer/RenderScene.h @@ -41,6 +41,7 @@ struct VisibleSubmission { uint32_t SubmissionIndex{0}; MeshHandle MeshHandle{}; float SortDepth{0.0f}; + uint64_t SortKey{0}; }; struct VisibleSubmissionList { diff --git a/AxiomInternal/AxiomRHI/Vulkan/VulkanSceneRenderer.cpp b/AxiomInternal/AxiomRHI/Vulkan/VulkanSceneRenderer.cpp index 695d76de..074533dc 100644 --- a/AxiomInternal/AxiomRHI/Vulkan/VulkanSceneRenderer.cpp +++ b/AxiomInternal/AxiomRHI/Vulkan/VulkanSceneRenderer.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -36,6 +37,25 @@ std::array RHIDescriptorSets(VkDescriptorSet Set) { VkCommandBuffer GetVulkanCommandBuffer(const IRHICommandList &CommandList) { return reinterpret_cast(CommandList.GetNativeCommandBuffer()); } + +uint64_t PackOpaqueSortKey(MaterialHandle Material, MeshHandle Mesh) { + return (static_cast(Material.Value) << 32) | + static_cast(Mesh.Value); +} + +uint64_t PackTranslucentSortKey(float SortDepth, uint32_t SubmissionIndex) { + if (!std::isfinite(SortDepth) || SortDepth <= 0.0f) { + return (static_cast(std::numeric_limits::max()) << 32) | + SubmissionIndex; + } + + uint32_t QuantizedDepth = 0; + static_assert(sizeof(QuantizedDepth) == sizeof(SortDepth)); + std::memcpy(&QuantizedDepth, &SortDepth, sizeof(QuantizedDepth)); + const uint32_t BackToFrontDepth = + std::numeric_limits::max() - QuantizedDepth; + return (static_cast(BackToFrontDepth) << 32) | SubmissionIndex; +} } // namespace void VulkanSceneRenderer::Init(IRHIDevice &Device, @@ -291,8 +311,12 @@ void VulkanSceneRenderer::PrepareSceneFrame(RenderScene &Scene) { Submission.RenderPath == MeshRenderPath::Compute) { VisibleSubmissions.Compute.push_back(Visible); } else if (Submission.Translucent) { + Visible.SortKey = + PackTranslucentSortKey(Candidate.SortDepth, Candidate.SubmissionIndex); VisibleSubmissions.TranslucentGraphics.push_back(Visible); } else { + Visible.SortKey = + PackOpaqueSortKey(Submission.MaterialHandle, Candidate.MeshHandle); VisibleSubmissions.OpaqueGraphics.push_back(Visible); } @@ -302,17 +326,8 @@ void VulkanSceneRenderer::PrepareSceneFrame(RenderScene &Scene) { std::sort(VisibleSubmissions.OpaqueGraphics.begin(), VisibleSubmissions.OpaqueGraphics.end(), - [this](const VisibleSubmission &Left, const VisibleSubmission &Right) { - const MaterialInstance *LeftMaterial = - m_Device->ResolveMaterialHandle( - GetSubmission(Left.SubmissionIndex).MaterialHandle); - const MaterialInstance *RightMaterial = - m_Device->ResolveMaterialHandle( - GetSubmission(Right.SubmissionIndex).MaterialHandle); - if (LeftMaterial != RightMaterial) { - return LeftMaterial < RightMaterial; - } - return Left.SubmissionIndex < Right.SubmissionIndex; + [](const VisibleSubmission &Left, const VisibleSubmission &Right) { + return Left.SortKey < Right.SortKey; }); PrepareGraphicsMaterialDescriptors(); @@ -636,7 +651,7 @@ void VulkanSceneRenderer::PrepareGraphicsMaterialDescriptors() { std::sort(SortedTranslucentSubmissions.begin(), SortedTranslucentSubmissions.end(), [](const VisibleSubmission &Left, const VisibleSubmission &Right) { - return Left.SortDepth > Right.SortDepth; + return Left.SortKey < Right.SortKey; }); for (const VisibleSubmission &Visible : SortedTranslucentSubmissions) { TranslucentMaterials.insert(m_Device->ResolveMaterialHandle( @@ -961,7 +976,7 @@ void VulkanSceneRenderer::RecordTranslucentForwardPass( std::sort(GraphicsSubmissions.begin(), GraphicsSubmissions.end(), [](const VisibleSubmission &Left, const VisibleSubmission &Right) { - return Left.SortDepth > Right.SortDepth; + return Left.SortKey < Right.SortKey; }); const VkExtent2D DrawExtent = GetDrawExtent2D();