/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include #include #include #include #include #include #include #include #include namespace AZ { namespace RPI { Query::Query(RPI::QueryPool* queryPool, RHI::Interval rhiQueryIndices, RHI::QueryType queryType, RHI::QueryPoolScopeAttachmentType attachmentType, RHI::ScopeAttachmentAccess attachmentAccess) { if (!queryPool) { AZ_Error("RPI::Query", false, "Query must be initialized with valid queryPool"); return; } m_queryPool = queryPool; m_rhiQueryIndices = rhiQueryIndices; m_attachmentType = attachmentType; m_attachmentAccess = attachmentAccess; m_queryType = queryType; // Split the queryIndices into multiple intervals. One for each buffered frame. SubdivideRhiQueryIndices(rhiQueryIndices); } Query::~Query() { UnregisterFromPool(); } void Query::UnregisterFromPool() { if (m_queryPool) { m_queryPool->UnregisterQuery(this); } } RHI::QueryType Query::GetQueryType() const { return m_queryType; } QueryResultCode Query::AddToFrameGraph(RHI::FrameGraphInterface frameGraph) { // Assign the FrameIndex of the query pool. if (!AssignNewFrameIndexToSubQuery(m_queryPool->GetPoolFrameIndex())) { return QueryResultCode::Fail; } // Get the RHI Query indices from the current FrameIndex. const auto rhiQueryIndices = GetRhiQueryIndicesFromCurrentFrame(); if (!rhiQueryIndices) { return QueryResultCode::Fail; } // Tell the FrameGraph which RHI QueryPool, and which RHI Queries need to be used. [[maybe_unused]] RHI::ResultCode resultCode = frameGraph.UseQueryPool(m_queryPool->m_rhiQueryPool, rhiQueryIndices.value(), m_attachmentType, m_attachmentAccess); AZ_Assert(resultCode == RHI::ResultCode::Success, "Failed to add the queries to the scope builder"); // Invalidate the ScopeId. m_cachedScopeId = RHI::ScopeId(); return QueryResultCode::Success; } QueryResultCode Query::BeginQuery(const RHI::FrameGraphExecuteContext& context) { // Return fail if the query wasn't added to frame graph if (m_cachedSubQueryArrayIndex == InvalidQueryIndex) { return QueryResultCode::Fail; } const auto rhiQueryIndices = GetRhiQueryIndicesFromCurrentFrame(); if (!rhiQueryIndices) { return QueryResultCode::Fail; } [[maybe_unused]] RHI::ResultCode resultCode = m_queryPool->BeginQueryInternal(rhiQueryIndices.value(), *context.GetCommandList()); AZ_Assert(resultCode == RHI::ResultCode::Success, "Failed to begin recording the query"); m_cachedScopeId = context.GetScopeId(); return QueryResultCode::Success; } QueryResultCode Query::EndQuery(const RHI::FrameGraphExecuteContext& context) { // return fail if the query wasn't added to frame graph if (m_cachedSubQueryArrayIndex == InvalidQueryIndex) { return QueryResultCode::Fail; } // Validate that the queries are recorded for the same scope. if (m_cachedScopeId != context.GetScopeId()) { AZ_Warning("RPI::Query", false, "The FrameGraphExecuteContext's Scope that is used for RPI::Query::BeginQuery is not the same for RPI::Query::EndQuery."); return QueryResultCode::Fail; } const auto rhiQueryIndices = GetRhiQueryIndicesFromCurrentFrame(); if (!rhiQueryIndices) { return QueryResultCode::Fail; } [[maybe_unused]] RHI::ResultCode resultCode = m_queryPool->EndQueryInternal(rhiQueryIndices.value(), *context.GetCommandList()); AZ_Assert(resultCode == RHI::ResultCode::Success, "Failed to end recording the query"); return QueryResultCode::Success; } QueryResultCode Query::GetLatestResultAndWait(void* queryResult, uint32_t resultSizeInBytes) { if (resultSizeInBytes < m_queryPool->GetQueryResultSize()) { AZ_Warning("RPI::Query", false, "Not enough space to copy the query result into the provided data container."); return QueryResultCode::Fail; } // Get the most recent query index that has been submitted at least 1 frame ago. const uint64_t frameOffset = 1u; const uint32_t recentSubQueryIndex = GetMostRecentSubQueryArrayIndex(frameOffset); if (recentSubQueryIndex == InvalidQueryIndex) { return QueryResultCode::Fail; } const SubQuery& recentSubQuery = m_subQueryArray[recentSubQueryIndex]; // This may stall the calling thread; depending if the query result is already available for polling. return m_queryPool->GetQueryResultFromIndices(static_cast(queryResult), recentSubQuery.m_rhiQueryIndices, RHI::QueryResultFlagBits::Wait); } QueryResultCode Query::GetLatestResult(void* queryResult, uint32_t resultSizeInBytes) { if (resultSizeInBytes < m_queryPool->GetQueryResultSize()) { AZ_Warning("RPI::Query", false, "Not enough space to copy the query result into the provided data container."); return QueryResultCode::Fail; } // Get the most recent query index that has been submitted at least (BufferedFrames-1) frames ago. const uint32_t frameOffset = BufferedFrames - 1u; const uint32_t latestQueryIndex = GetMostRecentSubQueryArrayIndex(frameOffset); if (latestQueryIndex == InvalidQueryIndex) { return QueryResultCode::Fail; } SubQuery& subQuery = m_subQueryArray[latestQueryIndex]; return m_queryPool->GetQueryResultFromIndices(static_cast(queryResult), subQuery.m_rhiQueryIndices, RHI::QueryResultFlagBits::None); } bool Query::AssignNewFrameIndexToSubQuery(uint64_t poolFrameIndex) { #if defined (AZ_RPI_ENABLE_VALIDATION) // Check if the query is already added in this frame. { const auto predicate = [poolFrameIndex](SubQuery& queryIndices) { return queryIndices.m_poolFrameIndex == poolFrameIndex; }; if (AZStd::any_of(m_subQueryArray.begin(), m_subQueryArray.end(), predicate)) { AZ_Warning("RPI::Query", false, "Query is already added in this frame"); return false; } } #else // Check if the FrameIndex is already present for this query instance, meaning that // the user is trying to use the same query multiple times to record within a single frame. if (m_cachedSubQueryArrayIndex != InvalidQueryIndex && m_subQueryArray[m_cachedSubQueryArrayIndex].m_poolFrameIndex == poolFrameIndex) { AZ_Warning("RPI::Query", false, "Query is already added in this frame"); return false; } #endif // Get the oldest query array index. const uint32_t availableQueryIndex = GetOldestOrAvailableSubQueryArrayIndex(); // Cache the index of the most recently added SubQuery. m_cachedSubQueryArrayIndex = availableQueryIndex; // Reuse the oldest query by setting the current FrameIndex. m_subQueryArray[availableQueryIndex].m_poolFrameIndex = poolFrameIndex; return true; } void Query::SubdivideRhiQueryIndices(RHI::Interval rhiQueryIndices) { // Calculate the amount of RHI Queries used for this RPI Query. const uint32_t queryIndicesCount = rhiQueryIndices.m_max - rhiQueryIndices.m_min + 1u; AZ_Assert((queryIndicesCount % m_queryPool->GetQueriesPerResult()) == 0u, "The amount of RHI::Query indices used for the RPI::Query is not a multiple of the number of RHI::Queries required to calculate a single result."); // Calculate the number of query groups. const uint32_t subQueryIndexCount = queryIndicesCount / m_queryPool->GetQueriesPerResult(); AZ_Assert(subQueryIndexCount == BufferedFrames, "The amount of QueryGroups needs to be equal to the defined BufferedFrames"); // Divide the RHI Query indices equally among the SubQueries. for (uint32_t queryGroupIndex = 0u; queryGroupIndex < subQueryIndexCount; queryGroupIndex++) { // Calculate the range of the interval. const uint32_t groupOffset = queryGroupIndex * m_queryPool->GetQueriesPerResult(); const uint32_t min = groupOffset + rhiQueryIndices.m_min; const uint32_t max = groupOffset + rhiQueryIndices.m_min + m_queryPool->GetQueriesPerResult() - 1u; m_subQueryArray[queryGroupIndex].m_rhiQueryIndices = RHI::Interval(min, max); } } template uint32_t Query::ReturnSubQueryArrayIndex(T&& comp, uint64_t initialCachedDelta, bool returnOnInvalidIndex) const { uint32_t cachedQueryIndex = InvalidQueryIndex; uint64_t cachedFrameDelta = initialCachedDelta; for (uint32_t i = 0u; i < m_subQueryArray.size(); i++) { const SubQuery& subQuery = m_subQueryArray[i]; // Return or ignore the index of reused SubQueries. if (subQuery.m_poolFrameIndex == SubQuery::InvalidFrameIndex) { if (returnOnInvalidIndex) { return i; } else { continue; } } // Calculate the delta between the RPI QueryPool's FrameIndex and the SubQuery's cached FrameIndex. const uint64_t poolFrameIndex = m_queryPool->GetPoolFrameIndex(); AZ_Assert(poolFrameIndex >= subQuery.m_poolFrameIndex, "The SubQuery's cached FrameIndex is older than the RPI QueryPool's FrameIndex"); const uint64_t frameDelta = poolFrameIndex - subQuery.m_poolFrameIndex; if (comp(frameDelta, cachedFrameDelta)) { cachedQueryIndex = i; cachedFrameDelta = frameDelta; } } return cachedQueryIndex; } uint32_t Query::GetMostRecentSubQueryArrayIndex(uint64_t threshold) const { return ReturnSubQueryArrayIndex([threshold](uint64_t frameDelta, uint64_t cachedFrameDelta) { return frameDelta < cachedFrameDelta && frameDelta >= threshold; }, SubQuery::InvalidFrameIndex, false); } uint32_t Query::GetOldestOrAvailableSubQueryArrayIndex() const { return ReturnSubQueryArrayIndex([](uint64_t frameDelta, uint64_t cachedFrameDelta) { return frameDelta > cachedFrameDelta; }, 0u, true); } AZStd::optional Query::GetRhiQueryIndicesFromCurrentFrame() const { if (m_cachedSubQueryArrayIndex == InvalidQueryIndex) { return AZStd::nullopt; } const SubQuery& subQuery = m_subQueryArray[m_cachedSubQueryArrayIndex]; const uint64_t poolFrameIndex = m_queryPool->GetPoolFrameIndex(); if (poolFrameIndex != subQuery.m_poolFrameIndex) { AZ_Warning("RPI::Query", false, "FrameIndex doesn't match the one from the query. The recording of a query needs to happen within one frame"); return AZStd::nullopt; } return subQuery.m_rhiQueryIndices; } }; // Namespace RPI }; // Namespace AZ