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.
316 lines
13 KiB
C++
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
|