diff --git a/Axiom/Renderer/RenderScene.h b/Axiom/Renderer/RenderScene.h index f6d8a97..b5cd88d 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/VulkanDescriptors.cpp b/AxiomInternal/AxiomRHI/Vulkan/VulkanDescriptors.cpp index 74a8d95..7c62a35 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 2acad68..990bff7 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 ce5958e..ade0e45 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 49ab93b..7692068 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 b70837f..dbb442c 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 f87e49d..8afe7b3 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 0ceb02c..902a027 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 ab2d4cc..dfea33e 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 d43749e..2c86b00 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 c1a613a..9d29ec1 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 305e5d8..550e9ab 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 e815a1d..074533d 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(); @@ -570,10 +585,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); @@ -630,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( @@ -850,10 +871,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 +890,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 } @@ -912,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(); @@ -982,6 +1046,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 0408678..8d36a2f 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 7c82c68..6bbc907 100644 Binary files a/Content/Shaders/mesh.frag.spv and b/Content/Shaders/mesh.frag.spv differ diff --git a/Content/Shaders/mesh.vert b/Content/Shaders/mesh.vert index 5f575ff..6092380 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 a4bf2f5..5f3ad4c 100644 Binary files a/Content/Shaders/mesh.vert.spv and b/Content/Shaders/mesh.vert.spv differ diff --git a/Tests/RenderSubmissionTests.cpp b/Tests/RenderSubmissionTests.cpp index 7ab0358..8b91c92 100644 --- a/Tests/RenderSubmissionTests.cpp +++ b/Tests/RenderSubmissionTests.cpp @@ -165,10 +165,11 @@ TEST(RenderSubmissionTests, EditorSceneRendererAdapterDropsDeletedObjectsFromCac EXPECT_NE(Recreated[0].MeshHandle, First[0].MeshHandle); } -TEST(RenderSubmissionTests, VulkanRendererRendersAllThousandSubmittedMeshesOffscreen) { +TEST(RenderSubmissionTests, + VulkanRendererRendersAllFiveThousandSubmittedMeshesOffscreen) { constexpr uint32_t Width = 1280; constexpr uint32_t Height = 720; - constexpr size_t MeshCount = 1000; + constexpr size_t MeshCount = 5000; EnsureLoggingInitialized(); if (!Axiom::CanInitializeHeadlessVulkan()) { @@ -194,8 +195,8 @@ TEST(RenderSubmissionTests, VulkanRendererRendersAllThousandSubmittedMeshesOffsc Renderer.BeginFrame(); Axiom::RenderCommand::SetCamera(Camera); for (size_t Index = 0; Index < MeshCount; ++Index) { - const float X = static_cast(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(