Naive GetValues() implementation. (#6741)

* Naive GetValues() implementation.
Added the method itself, and the benchmarks which show that even the naive version is currently 10-50% faster than calling GetValue() for multiple values.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Added comments documenting why the const_cast is there.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Fixed link errors by creating new Shared.Tests lib.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Addressed PR feedback.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Fixed incorrect comparison.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>
This commit is contained in:
Mike Balfour
2022-01-07 12:12:09 -06:00
committed by GitHub
parent 8503915cdd
commit 7123ed18be
11 changed files with 466 additions and 78 deletions
@@ -11,6 +11,8 @@
#include <AzCore/Component/EntityId.h>
#include <AzCore/Math/Vector3.h>
#include <AtomCore/std/containers/array_view.h>
namespace GradientSignal
{
struct GradientSampleParams final
@@ -49,6 +51,36 @@ namespace GradientSignal
*/
virtual float GetValue(const GradientSampleParams& sampleParams) const = 0;
/**
* Given a list of positions, generate values. Implementations of this need to be thread-safe without using locks,
* as it can get called from multiple threads simultaneously and has the potential to cause lock inversion deadlocks.
* \param positions The input list of positions to query.
* \param outValues The output list of values. This list is expected to be the same size as the positions list.
*/
virtual void GetValues(AZStd::array_view<AZ::Vector3> positions, AZStd::array_view<float> outValues) const
{
// Reference implementation of GetValues for any gradients that don't have their own optimized implementations.
// This is 10%-60% faster than calling GetValue via EBus many times due to the per-call EBus overhead.
AZ_Assert(
positions.size() == outValues.size(), "input and output lists are different sizes (%zu vs %zu).",
positions.size(), outValues.size());
if (positions.size() == outValues.size())
{
GradientSampleParams sampleParams;
for (size_t index = 0; index < positions.size(); index++)
{
sampleParams.m_position = positions[index];
// The const_cast is necessary for now since array_view currently only supports const entries.
// If/when array_view is fixed to support non-const, or AZStd::span gets created, the const_cast can get removed.
auto& outValue = const_cast<float&>(outValues[index]);
outValue = GetValue(sampleParams);
}
}
}
/**
* Call to check the hierarchy to see if a given entityId exists in the gradient signal chain
*/
@@ -33,6 +33,7 @@ namespace GradientSignal
static void Reflect(AZ::ReflectContext* context);
inline float GetValue(const GradientSampleParams& sampleParams) const;
inline void GetValues(AZStd::array_view<AZ::Vector3> positions, AZStd::array_view<float> outValues) const;
bool IsEntityInHierarchy(const AZ::EntityId& entityId) const;
@@ -145,4 +146,93 @@ namespace GradientSignal
return output * m_opacity;
}
inline void GradientSampler::GetValues(AZStd::array_view<AZ::Vector3> positions, AZStd::array_view<float> outValues) const
{
auto ClearOutputValues = [](AZStd::array_view<float> outValues)
{
// If we don't have a valid gradient (or it is fully transparent), clear out all the output values.
for (size_t index = 0; index < outValues.size(); index++)
{
// The const_cast is necessary for now since array_view currently only supports const entries.
// If/when array_view is fixed to support non-const, or AZStd::span gets created, the const_cast can get removed.
auto& outValue = const_cast<float&>(outValues[index]);
outValue = 0.0f;
}
};
if (m_opacity <= 0.0f || !m_gradientId.IsValid())
{
ClearOutputValues(outValues);
return;
}
AZStd::vector<AZ::Vector3> transformedPositions;
bool useTransformedPositions = false;
// apply transform if set
if (m_enableTransform && GradientSamplerUtil::AreTransformParamsSet(*this))
{
AZ::Matrix3x4 matrix3x4;
matrix3x4.SetFromEulerDegrees(m_rotate);
matrix3x4.MultiplyByScale(m_scale);
matrix3x4.SetTranslation(m_translate);
useTransformedPositions = true;
transformedPositions.resize(positions.size());
for (size_t index = 0; index < positions.size(); index++)
{
transformedPositions[index] = matrix3x4 * positions[index];
}
}
{
// Block other threads from accessing the surface data bus while we are in GetValue (which may call into the SurfaceData bus).
// We lock our surface data mutex *before* checking / setting "isRequestInProgress" so that we prevent race conditions
// that create false detection of cyclic dependencies when multiple requests occur on different threads simultaneously.
// (One case where this was previously able to occur was in rapid updating of the Preview widget on the
// GradientSurfaceDataComponent in the Editor when moving the threshold sliders back and forth rapidly)
auto& surfaceDataContext = SurfaceData::SurfaceDataSystemRequestBus::GetOrCreateContext(false);
typename SurfaceData::SurfaceDataSystemRequestBus::Context::DispatchLockGuard scopeLock(surfaceDataContext.m_contextMutex);
if (m_isRequestInProgress)
{
AZ_ErrorOnce("GradientSignal", !m_isRequestInProgress, "Detected cyclic dependences with gradient entity references");
ClearOutputValues(outValues);
return;
}
else
{
m_isRequestInProgress = true;
GradientRequestBus::Event(
m_gradientId, &GradientRequestBus::Events::GetValues, useTransformedPositions ? transformedPositions : positions,
outValues);
m_isRequestInProgress = false;
}
}
// Perform any post-fetch transformations on the gradient values (invert, levels, opacity).
for (size_t index = 0; index < outValues.size(); index++)
{
// The const_cast is necessary for now since array_view currently only supports const entries.
// If/when array_view is fixed to support non-const, or AZStd::span gets created, the const_cast can get removed.
auto& outValue = const_cast<float&>(outValues[index]);
if (m_invertInput)
{
outValue = 1.0f - outValue;
}
// apply levels if set
if (m_enableLevels && GradientSamplerUtil::AreLevelParamsSet(*this))
{
outValue = GetLevels(outValue, m_inputMid, m_inputMin, m_inputMax, m_outputMin, m_outputMax);
}
outValue = outValue * m_opacity;
}
}
}