You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/Atom/RPI/Code/Source/RPI.Public/GpuQuery/Query.cpp

316 lines
13 KiB
C++

/*
* 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 <Atom/RHI/Factory.h>
#include <Atom/RHI/FrameGraphExecuteContext.h>
#include <Atom/RHI/FrameGraphInterface.h>
#include <Atom/RHI/Scope.h>
#include <Atom/RPI.Public/GpuQuery/GpuQuerySystem.h>
#include <Atom/RPI.Public/GpuQuery/Query.h>
#include <Atom/RPI.Public/GpuQuery/QueryPool.h>
#include <Atom/RHI/RHISystemInterface.h>
#include <AzCore/std/parallel/scoped_lock.h>
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<uint64_t*>(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<uint64_t*>(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<typename T>
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<RHI::Interval> 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