diff --git a/Hydrogent/interface/HnMaterial.hpp b/Hydrogent/interface/HnMaterial.hpp index 4cfca7d0..5886d78a 100644 --- a/Hydrogent/interface/HnMaterial.hpp +++ b/Hydrogent/interface/HnMaterial.hpp @@ -88,6 +88,15 @@ class HnMaterial final : public pxr::HdMaterial m_PrimitiveAttribsVar->SetBufferOffset(PrimitiveAttribsOffset); return m_SRB; } + void SetJointsBufferOffset(Uint32 Offset) const + { + if (m_JointTransformsVar == nullptr) + { + UNEXPECTED("Joint transforms variable is not initialized, which indicates that skinning is not enabled in the renderer."); + return; + } + m_JointTransformsVar->SetBufferOffset(Offset); + } const GLTF::Material& GetMaterialData() const { return m_MaterialData; } @@ -147,6 +156,7 @@ class HnMaterial final : public pxr::HdMaterial RefCntAutoPtr m_SRB; IShaderResourceVariable* m_PrimitiveAttribsVar = nullptr; // cbPrimitiveAttribs + IShaderResourceVariable* m_JointTransformsVar = nullptr; // cbJointTransforms GLTF::Material m_MaterialData; diff --git a/Hydrogent/interface/HnRenderDelegate.hpp b/Hydrogent/interface/HnRenderDelegate.hpp index 57157820..dbd91f65 100644 --- a/Hydrogent/interface/HnRenderDelegate.hpp +++ b/Hydrogent/interface/HnRenderDelegate.hpp @@ -192,7 +192,7 @@ class HnRenderDelegate final : public pxr::HdRenderDelegate /// The maximum number of joints. /// /// If set to 0, skinning will be disabled. - Uint32 MaxJointCount = 64; + Uint32 MaxJointCount = 128; }; static std::unique_ptr Create(const CreateInfo& CI); diff --git a/Hydrogent/interface/HnRenderPass.hpp b/Hydrogent/interface/HnRenderPass.hpp index ea188f76..b09c2d5a 100644 --- a/Hydrogent/interface/HnRenderPass.hpp +++ b/Hydrogent/interface/HnRenderPass.hpp @@ -212,8 +212,9 @@ class HnRenderPass final : public pxr::HdRenderPass struct PendingDrawItem { const DrawListItem& ListItem; - const Uint32 BufferOffset; - Uint32 DrawCount = 1; + const Uint32 AttribsBufferOffset; + Uint32 JointsBufferOffset = ~0u; + Uint32 DrawCount = 1; }; // Draw list items to be rendered in the current batch. @@ -221,9 +222,12 @@ class HnRenderPass final : public pxr::HdRenderPass // Rendering order of the draw list items sorted by the PSO. std::vector m_RenderOrder; - // Scratch space prepare data for the primitive attributes buffer. + // Scratch space to prepare data for the primitive attributes buffer. std::vector m_PrimitiveAttribsData; + // Scratch space to prepare data for the joints buffer. + std::vector m_JointsData; + // Scratch space for the MultiDraw/MultiDrawIndexed command items. std::vector m_ScratchSpace; diff --git a/Hydrogent/src/HnMaterial.cpp b/Hydrogent/src/HnMaterial.cpp index 5d05cc48..c7251754 100644 --- a/Hydrogent/src/HnMaterial.cpp +++ b/Hydrogent/src/HnMaterial.cpp @@ -728,6 +728,7 @@ void HnMaterial::UpdateSRB(HnRenderDelegate& RendererDelegate) { m_SRB.Release(); m_PrimitiveAttribsVar = nullptr; + m_JointTransformsVar = nullptr; m_PBRPrimitiveAttribsBufferRange = 0; m_AtlasVersion = AtlasVersion; } @@ -1009,6 +1010,8 @@ void HnMaterial::UpdateSRB(HnRenderDelegate& RendererDelegate) const Uint32 PBRPrimitiveAttribsSize = UsdRenderer.GetPBRPrimitiveAttribsSize(PSOFlags); const Uint32 PrimitiveArraySize = std::max(UsdRenderer.GetSettings().PrimitiveArraySize, 1u); SRBCache->UpdatePrimitiveAttribsBufferRange(m_SRB, PBRPrimitiveAttribsSize * PrimitiveArraySize); + m_JointTransformsVar = m_SRB->GetVariableByName(SHADER_TYPE_VERTEX, "cbJointTransforms"); + VERIFY_EXPR(m_JointTransformsVar != nullptr || RendererSettings.MaxJointCount == 0); } else { diff --git a/Hydrogent/src/HnRenderDelegate.cpp b/Hydrogent/src/HnRenderDelegate.cpp index 7324c8af..be7a99fa 100644 --- a/Hydrogent/src/HnRenderDelegate.cpp +++ b/Hydrogent/src/HnRenderDelegate.cpp @@ -100,10 +100,25 @@ static RefCntAutoPtr CreatePrimitiveAttribsCB(IRenderDevice* pDevice) } // Allocate a large buffer to batch primitive draw calls RefCntAutoPtr PrimitiveAttribsCB; - CreateUniformBuffer(pDevice, Size, "PBR frame attribs CB", &PrimitiveAttribsCB, Usage); + CreateUniformBuffer(pDevice, Size, "PBR primitive attribs", &PrimitiveAttribsCB, Usage); return PrimitiveAttribsCB; } +static RefCntAutoPtr CreateJointsCB(IRenderDevice* pDevice) +{ + Uint64 Size = 65536; + USAGE Usage = USAGE_DYNAMIC; + if (pDevice->GetDeviceInfo().IsGLDevice()) + { + // On OpenGL, use USAGE_DEFAULT buffer and update it + // with UpdateBuffer() method. + Usage = USAGE_DEFAULT; + } + RefCntAutoPtr JointsCB; + CreateUniformBuffer(pDevice, Size, "Joint transforms", &JointsCB, Usage); + return JointsCB; +} + static std::shared_ptr CreateUSDRenderer(const HnRenderDelegate::CreateInfo& RenderDelegateCI, IBuffer* pPrimitiveAttribsCB, IObject* MaterialSRBCache) @@ -209,6 +224,9 @@ static std::shared_ptr CreateUSDRenderer(const HnRenderDelegate::C USDRendererCI.pPrimitiveAttribsCB = pPrimitiveAttribsCB; + RefCntAutoPtr pJointsCB = CreateJointsCB(RenderDelegateCI.pDevice); + USDRendererCI.pJointsBuffer = pJointsCB; + return std::make_shared(RenderDelegateCI.pDevice, RenderDelegateCI.pRenderStateCache, RenderDelegateCI.pContext, USDRendererCI); } diff --git a/Hydrogent/src/HnRenderPass.cpp b/Hydrogent/src/HnRenderPass.cpp index a26aa7a1..e28c2596 100644 --- a/Hydrogent/src/HnRenderPass.cpp +++ b/Hydrogent/src/HnRenderPass.cpp @@ -385,40 +385,74 @@ HnRenderPass::EXECUTE_RESULT HnRenderPass::Execute(HnRenderPassState& RPState, c IBuffer* const pPrimitiveAttribsCB = State.RenderDelegate.GetPrimitiveAttribsCB(); VERIFY_EXPR(pPrimitiveAttribsCB != nullptr); - IBuffer* const pJointsCB = State.USDRenderer.GetJointsBuffer(); - const Uint32 MaxJointCount = State.USDRenderer.GetSettings().MaxJointCount; + IBuffer* const pJointsCB = State.USDRenderer.GetJointsBuffer(); + const Uint32 MaxJointCount = State.USDRenderer.GetSettings().MaxJointCount; + const Uint32 JointsDataRange = State.USDRenderer.GetJointsBufferSize(); + VERIFY_EXPR(pJointsCB != nullptr || JointsDataRange == 0); const BufferDesc& AttribsBuffDesc = pPrimitiveAttribsCB->GetDesc(); + const BufferDesc& JointsBuffDesc = pJointsCB != nullptr ? pJointsCB->GetDesc() : BufferDesc{}; m_PendingDrawItems.clear(); m_PendingDrawItems.reserve(m_DrawList.size()); - void* pMappedBufferData = nullptr; - Uint32 CurrOffset = 0; - Uint32 NumPendingSkinnedItems = 0; + void* pMappedPrimitiveData = nullptr; + Uint32 AttribsBufferOffset = 0; + + void* pMappedJointsData = nullptr; + Uint32 JointsBufferOffset = 0; + Uint32 CurrJointsDataSize = 0; + size_t XformsHash = 0; + Uint32 JointCount = 0; if (AttribsBuffDesc.Usage != USAGE_DYNAMIC) { m_PrimitiveAttribsData.resize(static_cast(AttribsBuffDesc.Size)); } + if (JointsBuffDesc.Usage != USAGE_DYNAMIC) + { + m_JointsData.resize(static_cast(JointsBuffDesc.Size)); + } auto FlushPendingDraws = [&]() { - VERIFY_EXPR(CurrOffset > 0); - if (AttribsBuffDesc.Usage == USAGE_DYNAMIC) - { - VERIFY_EXPR(pMappedBufferData != nullptr); - State.pCtx->UnmapBuffer(pPrimitiveAttribsCB, MAP_WRITE); - pMappedBufferData = nullptr; - } - else + auto UnmapOrUpdateBuffer = [pCtx = State.pCtx](IBuffer* pBuffer, + const BufferDesc& BuffDesc, + void*& pMappedData, + const void* pStagingData, + size_t DataSize) { + if (BuffDesc.Usage == USAGE_DYNAMIC) + { + VERIFY_EXPR(pMappedData != nullptr); + pCtx->UnmapBuffer(pBuffer, MAP_WRITE); + pMappedData = nullptr; + } + else + { + pCtx->UpdateBuffer(pBuffer, 0, DataSize, pStagingData, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); + StateTransitionDesc Barrier{pBuffer, RESOURCE_STATE_UNKNOWN, RESOURCE_STATE_CONSTANT_BUFFER, STATE_TRANSITION_FLAG_UPDATE_STATE}; + pCtx->TransitionResourceStates(1, &Barrier); + } + }; + + VERIFY_EXPR(AttribsBufferOffset > 0); + UnmapOrUpdateBuffer(pPrimitiveAttribsCB, AttribsBuffDesc, pMappedPrimitiveData, + m_PrimitiveAttribsData.data(), m_PrimitiveAttribsData.size()); + AttribsBufferOffset = 0; + + if (CurrJointsDataSize > 0) { - State.pCtx->UpdateBuffer(pPrimitiveAttribsCB, 0, m_PrimitiveAttribsData.size(), m_PrimitiveAttribsData.data(), RESOURCE_STATE_TRANSITION_MODE_TRANSITION); - StateTransitionDesc Barrier{pPrimitiveAttribsCB, RESOURCE_STATE_UNKNOWN, RESOURCE_STATE_CONSTANT_BUFFER, STATE_TRANSITION_FLAG_UPDATE_STATE}; - State.pCtx->TransitionResourceStates(1, &Barrier); + VERIFY_EXPR(JointsBuffDesc.Usage == USAGE_DYNAMIC || CurrJointsDataSize <= m_JointsData.size()); + UnmapOrUpdateBuffer(pJointsCB, JointsBuffDesc, pMappedJointsData, + m_JointsData.data(), CurrJointsDataSize); } + JointsBufferOffset = 0; + CurrJointsDataSize = 0; + // Reset the hash to force updating the joint transforms for the next draw item. + // NB: we can't reuse the transforms at the existing offset because they may be + // overwritten by the next draw item. + XformsHash = 0; + RenderPendingDrawItems(State); VERIFY_EXPR(m_PendingDrawItems.empty()); - CurrOffset = 0; - NumPendingSkinnedItems = 0; }; const Uint32 PrimitiveArraySize = !m_UseFallbackPSO ? @@ -429,9 +463,6 @@ HnRenderPass::EXECUTE_RESULT HnRenderPass::Execute(HnRenderPassState& RPState, c entt::registry& Registry = State.RenderDelegate.GetEcsRegistry(); - size_t XformsHash = 0; - Uint32 JointCount = 0; - // Note: accessing components through a view is faster than accessing them through the registry. auto MeshAttribsView = Registry.viewXformsHash != XformsHash) { - // Flush pending draws if there are skinned items that use different joint transforms - if (NumPendingSkinnedItems > 0) - { - FlushPendingDraws(); - MultiDrawCount = 0; - } - - JointCount = std::min(static_cast(pSkinningData->Xforms->size()), MaxJointCount); + // Restart batch when the joint transforms change + MultiDrawCount = 0; - // Write new joint transforms + if (CurrJointsDataSize + JointsDataRange > JointsBuffDesc.Size) { - MapHelper JointsData{State.pCtx, pJointsCB, MAP_WRITE, MAP_FLAG_DISCARD}; - - const pxr::VtMatrix4fArray* PrevXforms = ListItem.PrevXforms != nullptr ? ListItem.PrevXforms : pSkinningData->Xforms; - USD_Renderer::WriteSkinningDataAttribs WriteSkinningAttribs{ListItem.PSOFlags, JointCount}; - WriteSkinningAttribs.PreTransform = reinterpret_cast(pSkinningData->GeomBindXform.Data()); - WriteSkinningAttribs.JointMatrices = reinterpret_cast(pSkinningData->Xforms->data()); - WriteSkinningAttribs.PrevPreTransform = reinterpret_cast(pSkinningData->GeomBindXform.Data()); - WriteSkinningAttribs.PrevJointMatrices = reinterpret_cast(PrevXforms->data()); - State.USDRenderer.WriteSkinningData(JointsData, WriteSkinningAttribs); + // There is not enough space for the new joint transforms. + // Flush pending draws to start filling the joints buffer from the beginning. + AttribsBufferOffset = AttribsBuffDesc.Size; } - - ListItem.PrevXforms = pSkinningData->Xforms; - XformsHash = pSkinningData->XformsHash; } if (MultiDrawCount > 0) @@ -504,7 +520,7 @@ HnRenderPass::EXECUTE_RESULT HnRenderPass::Execute(HnRenderPassState& RPState, c FirstMultiDrawItem.ListItem.NumVertexBuffers == ListItem.NumVertexBuffers && FirstMultiDrawItem.ListItem.VertexBuffers == ListItem.VertexBuffers && FirstMultiDrawItem.ListItem.Material.GetSRB() == ListItem.Material.GetSRB()); - VERIFY_EXPR(CurrOffset + ListItem.ShaderAttribsDataSize <= AttribsBuffDesc.Size); + VERIFY_EXPR(AttribsBufferOffset + ListItem.ShaderAttribsDataSize <= AttribsBuffDesc.Size); ++FirstMultiDrawItem.DrawCount; } @@ -517,38 +533,92 @@ HnRenderPass::EXECUTE_RESULT HnRenderPass::Execute(HnRenderPassState& RPState, c if (MultiDrawCount == 0) { // Align the offset by the constant buffer offset alignment - CurrOffset = AlignUp(CurrOffset, State.ConstantBufferOffsetAlignment); + AttribsBufferOffset = AlignUp(AttribsBufferOffset, State.ConstantBufferOffsetAlignment); // Note that the actual attribs size may be smaller than the range, but we need // to check for the entire range to avoid errors because this range is set in // the shader variable in the SRB. - if (CurrOffset + ListItem.ShaderAttribsBufferRange > AttribsBuffDesc.Size) + if (AttribsBufferOffset + ListItem.ShaderAttribsBufferRange > AttribsBuffDesc.Size) { // The buffer is full. Render the pending items and start filling the buffer from the beginning. FlushPendingDraws(); } } - void* pCurrPrimitive = nullptr; - if (AttribsBuffDesc.Usage == USAGE_DYNAMIC) - { - if (pMappedBufferData == nullptr) + auto GetBufferDataPtr = [pCtx = State.pCtx](IBuffer* pBuffer, + const BufferDesc& BuffDesc, + void*& pMappedData, + Uint32 Offset, + std::vector& StagingData, + Uint32 DataRange) -> void* { + if (BuffDesc.Usage == USAGE_DYNAMIC) { - State.pCtx->MapBuffer(pPrimitiveAttribsCB, MAP_WRITE, MAP_FLAG_DISCARD, pMappedBufferData); - if (pMappedBufferData == nullptr) + if (pMappedData == nullptr) { - UNEXPECTED("Failed to map the primitive attributes buffer"); - break; + pCtx->MapBuffer(pBuffer, MAP_WRITE, MAP_FLAG_DISCARD, pMappedData); + if (pMappedData == nullptr) + { + UNEXPECTED("Failed to map the buffer"); + return nullptr; + } } + return reinterpret_cast(pMappedData) + Offset; } - pCurrPrimitive = reinterpret_cast(pMappedBufferData) + CurrOffset; - } - else + else + { + VERIFY_EXPR(Offset + DataRange <= StagingData.size()); + return &StagingData[Offset]; + } + }; + + if (pSkinningData) { - VERIFY_EXPR(CurrOffset + ListItem.ShaderAttribsDataSize <= m_PrimitiveAttribsData.size()); - pCurrPrimitive = &m_PrimitiveAttribsData[CurrOffset]; + if (pSkinningData->XformsHash != XformsHash) + { + VERIFY(CurrJointsDataSize + JointsDataRange <= JointsBuffDesc.Size, + "There must be enough space for the new joint transforms as we flush the pending draw if there is not enough space."); + + JointsBufferOffset = CurrJointsDataSize; + JointCount = std::min(static_cast(pSkinningData->Xforms->size()), MaxJointCount); + + void* pJointsData = GetBufferDataPtr(pJointsCB, JointsBuffDesc, pMappedJointsData, JointsBufferOffset, m_JointsData, JointsDataRange); + if (pJointsData == nullptr) + break; + + // Write new joint transforms + const pxr::VtMatrix4fArray* PrevXforms = ListItem.PrevXforms != nullptr ? ListItem.PrevXforms : pSkinningData->Xforms; + USD_Renderer::WriteSkinningDataAttribs WriteSkinningAttribs{ListItem.PSOFlags, JointCount}; + WriteSkinningAttribs.PreTransform = reinterpret_cast(pSkinningData->GeomBindXform.Data()); + WriteSkinningAttribs.JointMatrices = reinterpret_cast(pSkinningData->Xforms->data()); + WriteSkinningAttribs.PrevPreTransform = reinterpret_cast(pSkinningData->GeomBindXform.Data()); + WriteSkinningAttribs.PrevJointMatrices = reinterpret_cast(PrevXforms->data()); + + void* pDataEnd = State.USDRenderer.WriteSkinningData(pJointsData, WriteSkinningAttribs); + const Uint32 JointsDataSize = static_cast(reinterpret_cast(pDataEnd) - reinterpret_cast(pJointsData)); + VERIFY_EXPR(JointsDataSize == State.USDRenderer.GetJointsDataSize(JointCount, ListItem.PSOFlags)); + CurrJointsDataSize = AlignUp(JointsBufferOffset + JointsDataSize, State.ConstantBufferOffsetAlignment); + + XformsHash = pSkinningData->XformsHash; + + VERIFY(MultiDrawCount == 0, "The batch must be reset when the joint transforms change."); + } + +#ifdef DILIGENT_DEBUG + if (MultiDrawCount > 0) + { + auto& FirstMultiDrawItem = m_PendingDrawItems[m_PendingDrawItems.size() - MultiDrawCount]; + VERIFY(FirstMultiDrawItem.JointsBufferOffset == JointsBufferOffset, + "All items in the batch must have the same joints buffer offset since we reset the batch when the joint transforms change."); + } +#endif + + ListItem.PrevXforms = pSkinningData->Xforms; } + void* pCurrPrimitive = GetBufferDataPtr(pPrimitiveAttribsCB, AttribsBuffDesc, pMappedPrimitiveData, AttribsBufferOffset, m_PrimitiveAttribsData, ListItem.ShaderAttribsDataSize); + if (pCurrPrimitive == nullptr) + break; + // Write current primitive attributes HLSL::PBRMaterialBasicAttribs* pDstMaterialBasicAttribs = nullptr; @@ -576,14 +646,12 @@ HnRenderPass::EXECUTE_RESULT HnRenderPass::Execute(HnRenderPassState& RPState, c ListItem.PrevTransform = Transform; - m_PendingDrawItems.push_back(PendingDrawItem{ListItem, CurrOffset}); + m_PendingDrawItems.push_back(PendingDrawItem{ListItem, AttribsBufferOffset, pSkinningData != nullptr ? JointsBufferOffset : ~0u}); - CurrOffset += ListItem.ShaderAttribsDataSize; + AttribsBufferOffset += ListItem.ShaderAttribsDataSize; ++MultiDrawCount; - if (pSkinningData != nullptr) - ++NumPendingSkinnedItems; } - if (CurrOffset != 0) + if (AttribsBufferOffset != 0) { FlushPendingDraws(); } @@ -1085,7 +1153,8 @@ void HnRenderPass::UpdateDrawListItemGPUResources(DrawListItem& ListItem, Render void HnRenderPass::RenderPendingDrawItems(RenderState& State) { - size_t item_idx = 0; + size_t item_idx = 0; + Uint32 JointsBufferOffset = ~0u; while (item_idx < m_PendingDrawItems.size()) { const PendingDrawItem& PendingItem = m_PendingDrawItems[item_idx]; @@ -1093,8 +1162,13 @@ void HnRenderPass::RenderPendingDrawItems(RenderState& State) State.SetPipelineState(m_UseFallbackPSO ? m_FallbackPSO : ListItem.pPSO); - IShaderResourceBinding* pSRB = ListItem.Material.GetSRB(PendingItem.BufferOffset); + IShaderResourceBinding* pSRB = ListItem.Material.GetSRB(PendingItem.AttribsBufferOffset); VERIFY(pSRB != nullptr, "Material SRB is null. This may happen if UpdateSRB was not called for this material."); + if (PendingItem.JointsBufferOffset != ~0u && PendingItem.JointsBufferOffset != JointsBufferOffset) + { + JointsBufferOffset = PendingItem.JointsBufferOffset; + ListItem.Material.SetJointsBufferOffset(JointsBufferOffset); + } State.CommitShaderResources(pSRB); State.SetIndexBuffer(ListItem.IndexBuffer); @@ -1106,14 +1180,16 @@ void HnRenderPass::RenderPendingDrawItems(RenderState& State) VERIFY_EXPR(item_idx + PendingItem.DrawCount <= m_PendingDrawItems.size()); for (size_t i = 1; i < PendingItem.DrawCount; ++i) { - const auto& BatchItem = m_PendingDrawItems[item_idx + i].ListItem; + const auto& BatchItem = m_PendingDrawItems[item_idx + i]; + const auto& BatchListItem = BatchItem.ListItem; // clang-format off - VERIFY_EXPR(BatchItem.RenderStateID == ListItem.RenderStateID && - BatchItem.pPSO == ListItem.pPSO && - BatchItem.IndexBuffer == ListItem.IndexBuffer && - BatchItem.NumVertexBuffers == ListItem.NumVertexBuffers && - BatchItem.VertexBuffers == ListItem.VertexBuffers && - BatchItem.DrawItem.GetMaterial()->GetSRB() == ListItem.DrawItem.GetMaterial()->GetSRB()); + VERIFY_EXPR(BatchListItem.RenderStateID == ListItem.RenderStateID && + BatchListItem.pPSO == ListItem.pPSO && + BatchListItem.IndexBuffer == ListItem.IndexBuffer && + BatchListItem.NumVertexBuffers == ListItem.NumVertexBuffers && + BatchListItem.VertexBuffers == ListItem.VertexBuffers && + BatchListItem.DrawItem.GetMaterial()->GetSRB() == ListItem.DrawItem.GetMaterial()->GetSRB() && + BatchItem.JointsBufferOffset == PendingItem.JointsBufferOffset); // clang-format on } VERIFY_EXPR(m_ScratchSpace.size() >= PendingItem.DrawCount * (ListItem.IndexBuffer != nullptr ? sizeof(MultiDrawIndexedItem) : sizeof(MultiDrawItem)));