Added Async APIs for the various Process*FromList/Region terrain functions. (#7480)
* Added Async APIs for the various Process*FromList terrain functions. Please note that we are currently defaulting the number of worker threads to one, because splitting the work over multiple threads causes contention when locking various mutexes, resulting in slower overall wall time for async requests split over multiple threads vs one where all the work is done on a single thread. The latter is still preferable over a regular synchronous call because it is just as quick and prevents the main thread from blocking. This should be changed once the mutex contention issues have been addressed, so that async calls automatically split the work between available job manager worker threads, unless the ProcessAsyncParams specify a different desired number of jobs. Signed-off-by: bosnichd <bosnichd@amazon.com> * Fix Linux builds by adding missing #include Signed-off-by: bosnichd <bosnichd@amazon.com> * Added a test for cancellation of terrain async requests, and fix it so that it works. Note that the benchmarks show this implementation to be slightly slower than the previous one, which I presume is because we're now calling a 'perSurfacePointFunction' in the inner loop; this can probably be addressed, but will result in a lot of code duplication, and I think efforts will be better spent on removing the mutex contention to enable running multiple terrain async jobs at the same time. Signed-off-by: bosnichd <bosnichd@amazon.com> * Added Async versions for all Process*Region terrain API functions, along with benchmarks. Signed-off-by: bosnichd <bosnichd@amazon.com> * Fix the newly added terrain async request benchmarks to actually use the async APIs. Signed-off-by: bosnichd <bosnichd@amazon.com> * Revert to the original version which just calls the synchronous API from the job function, along with some other updates in response to review feedback. Signed-off-by: bosnichd <bosnichd@amazon.com> * Change the TerrainWorldDebugger to use the async API, along with the following changes: - TerrainJobContext no longer uses a JobCancelGroup so we can guarantee the completion callbacks of associated jobs will be invoked even if it is cancelled. - As a result of the above change, the ProcessAsyncCompleteCallback function signature again accepts the associated TerrainJobContext as a param. - The TerrainProcessAsyncCancellation test has been resurrected and simplified by using binary semaphores instead of condition variables. - All the async related TerrainSystemBenchmark functions have been simplified by using binary semaphores instead of condition variables. - Global cancellation of all terrain jobs on deactivation of the TerrainSystem has been reintroduced, but in a different way than before. - Other miscellaneous changes/fixes made while testing and based on earlier PR feedback. Signed-off-by: bosnichd <bosnichd@amazon.com> * Updates based on review feedback: - Go back to using a vector instead of an array (fixed the original problem by adding custom copy/assignment constructors/operators to the WireframeSector struct). - When calling WireframeSector::Reset, block until any associated in flight has completed. - Added the concept of a minimum number of positions per terrain job. Signed-off-by: bosnichd <bosnichd@amazon.com> * Use semaphore instead of binary_semaphore in a bunch of places to account for the race condition where a completion callback fires before we started waiting for it. Signed-off-by: bosnichd <bosnichd@amazon.com>
This commit is contained in:
@@ -8,10 +8,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzCore/Jobs/JobContext.h>
|
||||
#include <AzCore/Math/Vector2.h>
|
||||
#include <AzCore/Math/Vector3.h>
|
||||
#include <AzCore/Math/Aabb.h>
|
||||
#include <AzCore/std/containers/span.h>
|
||||
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
||||
#include <AzFramework/Entity/EntityContextBus.h>
|
||||
#include <AzFramework/Render/GeometryIntersectionStructures.h>
|
||||
#include <AzFramework/SurfaceData/SurfaceData.h>
|
||||
@@ -193,6 +195,147 @@ namespace AzFramework
|
||||
//! Given a ray, return the closest intersection with terrain.
|
||||
virtual RenderGeometry::RayResult GetClosestIntersection(const RenderGeometry::RayRequest& ray) const = 0;
|
||||
|
||||
//! A JobContext used to run jobs spawned by calls to the various Process*Async functions.
|
||||
class TerrainJobContext : public AZ::JobContext
|
||||
{
|
||||
public:
|
||||
TerrainJobContext(AZ::JobManager& jobManager,
|
||||
int numJobsToComplete)
|
||||
: JobContext(jobManager)
|
||||
, m_numJobsToComplete(numJobsToComplete)
|
||||
{
|
||||
}
|
||||
|
||||
// When a terrain job context is cancelled, all associated
|
||||
// jobs are still guaranteed to at least begin processing,
|
||||
// and if any ProcessAsyncParams::m_completionCallback was
|
||||
// set it's guaranteed to be called even in the event of a
|
||||
// cancellation. If a job only begins processing after its
|
||||
// associated job context has been cancelled, no processing
|
||||
// will occur and the callback will be invoked immediately,
|
||||
// otherwise the job may either run to completion or cease
|
||||
// processing early; the callback is invoked in all cases,
|
||||
// provided one was specified with the original request.
|
||||
void Cancel() { m_isCancelled = true; }
|
||||
|
||||
// Was this TerrainJobContext cancelled?
|
||||
bool IsCancelled() const { return m_isCancelled; }
|
||||
|
||||
// Called by the TerrainSystem when a job associated with
|
||||
// this TerrainJobContext completes. Returns true if this
|
||||
// was the final job to be completed, or false otherwise.
|
||||
bool OnJobCompleted() { return (--m_numJobsToComplete == 0); }
|
||||
|
||||
private:
|
||||
AZStd::atomic_int m_numJobsToComplete = 0;
|
||||
AZStd::atomic_bool m_isCancelled = false;
|
||||
};
|
||||
|
||||
//! Alias for an optional callback function to invoke when the various Process*Async functions complete.
|
||||
//! The TerrainJobContext, returned from the original Process*Async function call, is passed as a param
|
||||
//! to the callback function so it can be queried to see if the job was cancelled or completed normally.
|
||||
typedef AZStd::function<void(AZStd::shared_ptr<TerrainJobContext>)> ProcessAsyncCompleteCallback;
|
||||
|
||||
//! A parameter group struct that can optionally be passed to the various Process*Async API functions.
|
||||
struct ProcessAsyncParams
|
||||
{
|
||||
//! The default minimum number ofpositions per async terrain request job.
|
||||
static constexpr int32_t MinPositionsPerJobDefault = 8;
|
||||
|
||||
//! The default number of jobs which async terrain requests will be split into.
|
||||
static constexpr int32_t NumJobsDefault = 1;
|
||||
|
||||
//! The maximum number of jobs which async terrain requests will be split into.
|
||||
//! This is not the value itself, rather a constant that can be used to request
|
||||
//! the work be split into the maximum number of job manager threads available.
|
||||
static constexpr int32_t NumJobsMax = -1;
|
||||
|
||||
//! The desired number of jobs to split async terrain requests into.
|
||||
//! The actual value used will be clamped to the number of available job manager threads.
|
||||
//!
|
||||
//! Note: Currently, splitting the work over multiple threads causes contention when
|
||||
//! locking various mutexes, resulting in slower overall wall time for async
|
||||
//! requests split over multiple threads vs one where all the work is done on
|
||||
//! a single thread. The latter is still preferable over a regular synchronous
|
||||
//! call because it is just as quick and prevents the main thread from blocking.
|
||||
//! This note should be removed once the mutex contention issues have been addressed.
|
||||
int32_t m_desiredNumberOfJobs = NumJobsDefault;
|
||||
|
||||
//! The minimum number of positions per async terrain request job.
|
||||
int32_t m_minPositionsPerJob = MinPositionsPerJobDefault;
|
||||
|
||||
//! The callback function that will be invoked when a call to a Process*Async function completes.
|
||||
//! If the job is cancelled, the completion callback will not be invoked.
|
||||
ProcessAsyncCompleteCallback m_completionCallback = nullptr;
|
||||
};
|
||||
|
||||
//! Asynchronous versions of the various 'Process*' API functions declared above.
|
||||
//! It's the responsibility of the caller to ensure all callbacks are threadsafe.
|
||||
virtual AZStd::shared_ptr<TerrainJobContext> ProcessHeightsFromListAsync(
|
||||
const AZStd::span<AZ::Vector3>& inPositions,
|
||||
SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
|
||||
virtual AZStd::shared_ptr<TerrainJobContext> ProcessNormalsFromListAsync(
|
||||
const AZStd::span<AZ::Vector3>& inPositions,
|
||||
SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
|
||||
virtual AZStd::shared_ptr<TerrainJobContext> ProcessSurfaceWeightsFromListAsync(
|
||||
const AZStd::span<AZ::Vector3>& inPositions,
|
||||
SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
|
||||
virtual AZStd::shared_ptr<TerrainJobContext> ProcessSurfacePointsFromListAsync(
|
||||
const AZStd::span<AZ::Vector3>& inPositions,
|
||||
SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
|
||||
virtual AZStd::shared_ptr<TerrainJobContext> ProcessHeightsFromListOfVector2Async(
|
||||
const AZStd::span<AZ::Vector2>& inPositions,
|
||||
SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
|
||||
virtual AZStd::shared_ptr<TerrainJobContext> ProcessNormalsFromListOfVector2Async(
|
||||
const AZStd::span<AZ::Vector2>& inPositions,
|
||||
SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
|
||||
virtual AZStd::shared_ptr<TerrainJobContext> ProcessSurfaceWeightsFromListOfVector2Async(
|
||||
const AZStd::span<AZ::Vector2>& inPositions,
|
||||
SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
|
||||
virtual AZStd::shared_ptr<TerrainJobContext> ProcessSurfacePointsFromListOfVector2Async(
|
||||
const AZStd::span<AZ::Vector2>& inPositions,
|
||||
SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
|
||||
virtual AZStd::shared_ptr<TerrainJobContext> ProcessHeightsFromRegionAsync(
|
||||
const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize,
|
||||
SurfacePointRegionFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
|
||||
virtual AZStd::shared_ptr<TerrainJobContext> ProcessNormalsFromRegionAsync(
|
||||
const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize,
|
||||
SurfacePointRegionFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
|
||||
virtual AZStd::shared_ptr<TerrainJobContext> ProcessSurfaceWeightsFromRegionAsync(
|
||||
const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize,
|
||||
SurfacePointRegionFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
|
||||
virtual AZStd::shared_ptr<TerrainJobContext> ProcessSurfacePointsFromRegionAsync(
|
||||
const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize,
|
||||
SurfacePointRegionFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
|
||||
|
||||
private:
|
||||
// Private variations of the GetSurfacePoint API exposed to BehaviorContext that returns a value instead of
|
||||
// using an "out" parameter. The "out" parameter is useful for reusing memory allocated in SurfacePoint when
|
||||
|
||||
@@ -106,5 +106,30 @@ namespace UnitTest
|
||||
GetTerrainRaycastEntityContextId, AzFramework::EntityContextId());
|
||||
MOCK_CONST_METHOD1(
|
||||
GetClosestIntersection, AzFramework::RenderGeometry::RayResult(const AzFramework::RenderGeometry::RayRequest&));
|
||||
MOCK_CONST_METHOD4(
|
||||
ProcessHeightsFromListAsync, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
|
||||
MOCK_CONST_METHOD4(
|
||||
ProcessNormalsFromListAsync, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
|
||||
MOCK_CONST_METHOD4(
|
||||
ProcessSurfaceWeightsFromListAsync, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
|
||||
MOCK_CONST_METHOD4(
|
||||
ProcessSurfacePointsFromListAsync, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
|
||||
MOCK_CONST_METHOD4(
|
||||
ProcessHeightsFromListOfVector2Async, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
|
||||
MOCK_CONST_METHOD4(
|
||||
ProcessNormalsFromListOfVector2Async, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
|
||||
MOCK_CONST_METHOD4(
|
||||
ProcessSurfaceWeightsFromListOfVector2Async, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
|
||||
MOCK_CONST_METHOD4(
|
||||
ProcessSurfacePointsFromListOfVector2Async, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
|
||||
MOCK_CONST_METHOD5(
|
||||
ProcessHeightsFromRegionAsync, AZStd::shared_ptr<TerrainJobContext>(const AZ::Aabb&, const AZ::Vector2&, AzFramework::Terrain::SurfacePointRegionFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
|
||||
MOCK_CONST_METHOD5(
|
||||
ProcessNormalsFromRegionAsync, AZStd::shared_ptr<TerrainJobContext>(const AZ::Aabb&, const AZ::Vector2&, AzFramework::Terrain::SurfacePointRegionFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
|
||||
MOCK_CONST_METHOD5(
|
||||
ProcessSurfaceWeightsFromRegionAsync, AZStd::shared_ptr<TerrainJobContext>(const AZ::Aabb&, const AZ::Vector2&, AzFramework::Terrain::SurfacePointRegionFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
|
||||
MOCK_CONST_METHOD5(
|
||||
ProcessSurfacePointsFromRegionAsync, AZStd::shared_ptr<TerrainJobContext>(const AZ::Aabb&, const AZ::Vector2&, AzFramework::Terrain::SurfacePointRegionFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
|
||||
|
||||
};
|
||||
} // namespace UnitTest
|
||||
|
||||
@@ -170,9 +170,10 @@ namespace Terrain
|
||||
// they'll get refreshed the next time we need to draw them.
|
||||
for (auto& sector : m_wireframeSectors)
|
||||
{
|
||||
AZStd::lock_guard<AZStd::recursive_mutex> lock(sector.m_sectorStateMutex);
|
||||
if (!dirtyRegion2D.IsValid() || dirtyRegion2D.Overlaps(sector.m_aabb))
|
||||
{
|
||||
sector.m_isDirty = true;
|
||||
sector.SetDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,10 +284,13 @@ namespace Terrain
|
||||
sectorAabb.Clamp(worldBounds);
|
||||
|
||||
// If the world space box for the sector doesn't match, set it and mark the sector as dirty so we refresh the height data.
|
||||
if (sector.m_aabb != sectorAabb)
|
||||
{
|
||||
sector.m_aabb = sectorAabb;
|
||||
sector.m_isDirty = true;
|
||||
AZStd::lock_guard<AZStd::recursive_mutex> lock(sector.m_sectorStateMutex);
|
||||
if (sector.m_aabb != sectorAabb)
|
||||
{
|
||||
sector.m_aabb = sectorAabb;
|
||||
sector.SetDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,12 +299,18 @@ namespace Terrain
|
||||
// (Sectors that are outside the world bounds won't have any valid data, so they'll get skipped)
|
||||
for (auto& sector : m_wireframeSectors)
|
||||
{
|
||||
AZStd::lock_guard<AZStd::recursive_mutex> lock(sector.m_sectorStateMutex);
|
||||
if (sector.m_jobContext)
|
||||
{
|
||||
// The previous async request for this sector has yet to complete.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sector.m_isDirty)
|
||||
{
|
||||
RebuildSectorWireframe(sector, heightDataResolution);
|
||||
}
|
||||
|
||||
if (!sector.m_lineVertices.empty())
|
||||
else if (!sector.m_lineVertices.empty())
|
||||
{
|
||||
const AZ::Color primaryColor = AZ::Color(0.25f, 0.25f, 0.25f, 1.0f);
|
||||
debugDisplay.DrawLines(sector.m_lineVertices, primaryColor);
|
||||
@@ -319,6 +329,7 @@ namespace Terrain
|
||||
|
||||
void TerrainWorldDebuggerComponent::RebuildSectorWireframe(WireframeSector& sector, float gridResolution)
|
||||
{
|
||||
AZStd::lock_guard<AZStd::recursive_mutex> lock(sector.m_sectorStateMutex);
|
||||
if (!sector.m_isDirty)
|
||||
{
|
||||
return;
|
||||
@@ -346,16 +357,24 @@ namespace Terrain
|
||||
sector.m_lineVertices.reserve(numSamplesX * numSamplesY * 4);
|
||||
|
||||
// This keeps track of the height from the previous point for the _ line.
|
||||
float previousHeight = 0.0f;
|
||||
sector.m_previousHeight = 0.0f;
|
||||
|
||||
// This keeps track of the heights from the previous row for the | line.
|
||||
AZStd::vector<float> rowHeights(numSamplesX);
|
||||
sector.m_rowHeights.clear();
|
||||
sector.m_rowHeights.resize(numSamplesX);
|
||||
|
||||
// For each terrain height value in the region, create the _| grid lines for that point and cache off the height value
|
||||
// for use with subsequent grid line calculations.
|
||||
auto ProcessHeightValue = [gridResolution, &previousHeight, &rowHeights, §or]
|
||||
auto ProcessHeightValue = [gridResolution, §or]
|
||||
(size_t xIndex, size_t yIndex, const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists)
|
||||
{
|
||||
AZStd::lock_guard<AZStd::recursive_mutex> lock(sector.m_sectorStateMutex);
|
||||
if (sector.m_isDirty)
|
||||
{
|
||||
// Bail out if this sector has become dirty again since the async request started.
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't add any vertices for the first column or first row. These grid lines will be handled by an adjacent sector, if
|
||||
// there is one.
|
||||
if ((xIndex > 0) && (yIndex > 0))
|
||||
@@ -363,23 +382,49 @@ namespace Terrain
|
||||
float x = surfacePoint.m_position.GetX() - gridResolution;
|
||||
float y = surfacePoint.m_position.GetY() - gridResolution;
|
||||
|
||||
sector.m_lineVertices.emplace_back(AZ::Vector3(x, surfacePoint.m_position.GetY(), previousHeight));
|
||||
sector.m_lineVertices.emplace_back(AZ::Vector3(x, surfacePoint.m_position.GetY(), sector.m_previousHeight));
|
||||
sector.m_lineVertices.emplace_back(surfacePoint.m_position);
|
||||
|
||||
sector.m_lineVertices.emplace_back(AZ::Vector3(surfacePoint.m_position.GetX(), y, rowHeights[xIndex]));
|
||||
sector.m_lineVertices.emplace_back(AZ::Vector3(surfacePoint.m_position.GetX(), y, sector.m_rowHeights[xIndex]));
|
||||
sector.m_lineVertices.emplace_back(surfacePoint.m_position);
|
||||
}
|
||||
|
||||
// Save off the heights so that we can use them to draw subsequent columns and rows.
|
||||
previousHeight = surfacePoint.m_position.GetZ();
|
||||
rowHeights[xIndex] = surfacePoint.m_position.GetZ();
|
||||
sector.m_previousHeight = surfacePoint.m_position.GetZ();
|
||||
sector.m_rowHeights[xIndex] = surfacePoint.m_position.GetZ();
|
||||
};
|
||||
|
||||
|
||||
auto completionCallback = [§or](AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext>)
|
||||
{
|
||||
// This must happen outside the lock,
|
||||
// otherwise we will get a deadlock if
|
||||
// WireframeSector::Reset is waiting for
|
||||
// the completion event to be signalled.
|
||||
sector.m_jobCompletionEvent->release();
|
||||
|
||||
// Reset the job context once the async request has completed,
|
||||
// clearing the way for future requests to be made for this sector.
|
||||
AZStd::lock_guard<AZStd::recursive_mutex> lock(sector.m_sectorStateMutex);
|
||||
sector.m_jobContext.reset();
|
||||
};
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams> asyncParams
|
||||
= AZStd::make_shared<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams>();
|
||||
asyncParams->m_completionCallback = completionCallback;
|
||||
|
||||
sector.m_jobCompletionEvent = AZStd::make_unique<AZStd::semaphore>();
|
||||
AZ::Vector2 stepSize = AZ::Vector2(gridResolution);
|
||||
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(&AzFramework::Terrain::TerrainDataRequests::ProcessHeightsFromRegion,
|
||||
region, stepSize, ProcessHeightValue, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT);
|
||||
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
|
||||
sector.m_jobContext,
|
||||
&AzFramework::Terrain::TerrainDataRequests::ProcessHeightsFromRegionAsync,
|
||||
region,
|
||||
stepSize,
|
||||
ProcessHeightValue,
|
||||
AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT,
|
||||
asyncParams);
|
||||
}
|
||||
|
||||
|
||||
void TerrainWorldDebuggerComponent::OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask)
|
||||
{
|
||||
if (dataChangedMask & (TerrainDataChangedMask::Settings | TerrainDataChangedMask::HeightData))
|
||||
@@ -395,5 +440,76 @@ namespace Terrain
|
||||
}
|
||||
}
|
||||
|
||||
TerrainWorldDebuggerComponent::WireframeSector::WireframeSector(const WireframeSector& other)
|
||||
{
|
||||
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_sectorStateMutex);
|
||||
m_jobContext = other.m_jobContext;
|
||||
m_aabb = other.m_aabb;
|
||||
m_lineVertices = other.m_lineVertices;
|
||||
m_rowHeights = other.m_rowHeights;
|
||||
m_previousHeight = other.m_previousHeight;
|
||||
m_isDirty = other.m_isDirty;
|
||||
}
|
||||
|
||||
TerrainWorldDebuggerComponent::WireframeSector::WireframeSector(WireframeSector&& other)
|
||||
{
|
||||
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_sectorStateMutex);
|
||||
m_jobContext = AZStd::move(other.m_jobContext);
|
||||
m_aabb = AZStd::move(other.m_aabb);
|
||||
m_lineVertices = AZStd::move(other.m_lineVertices);
|
||||
m_rowHeights = AZStd::move(other.m_rowHeights);
|
||||
m_previousHeight = AZStd::move(other.m_previousHeight);
|
||||
m_isDirty = AZStd::move(other.m_isDirty);
|
||||
}
|
||||
|
||||
TerrainWorldDebuggerComponent::WireframeSector& TerrainWorldDebuggerComponent::WireframeSector::operator=(const WireframeSector& other)
|
||||
{
|
||||
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_sectorStateMutex);
|
||||
m_jobContext = other.m_jobContext;
|
||||
m_aabb = other.m_aabb;
|
||||
m_lineVertices = other.m_lineVertices;
|
||||
m_rowHeights = other.m_rowHeights;
|
||||
m_previousHeight = other.m_previousHeight;
|
||||
m_isDirty = other.m_isDirty;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TerrainWorldDebuggerComponent::WireframeSector& TerrainWorldDebuggerComponent::WireframeSector::operator=(WireframeSector&& other)
|
||||
{
|
||||
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_sectorStateMutex);
|
||||
m_jobContext = AZStd::move(other.m_jobContext);
|
||||
m_aabb = AZStd::move(other.m_aabb);
|
||||
m_lineVertices = AZStd::move(other.m_lineVertices);
|
||||
m_rowHeights = AZStd::move(other.m_rowHeights);
|
||||
m_previousHeight = AZStd::move(other.m_previousHeight);
|
||||
m_isDirty = AZStd::move(other.m_isDirty);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void TerrainWorldDebuggerComponent::WireframeSector::Reset()
|
||||
{
|
||||
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_sectorStateMutex);
|
||||
if (m_jobContext)
|
||||
{
|
||||
// Cancel the job and wait until it completes.
|
||||
m_jobContext->Cancel();
|
||||
m_jobCompletionEvent->acquire();
|
||||
m_jobCompletionEvent.reset();
|
||||
m_jobContext.reset();
|
||||
}
|
||||
m_aabb = AZ::Aabb::CreateNull();
|
||||
m_lineVertices.clear();
|
||||
m_rowHeights.clear();
|
||||
m_previousHeight = 0.0f;
|
||||
m_isDirty = true;
|
||||
}
|
||||
|
||||
void TerrainWorldDebuggerComponent::WireframeSector::SetDirty()
|
||||
{
|
||||
m_isDirty = true;
|
||||
if (m_jobContext)
|
||||
{
|
||||
m_jobContext->Cancel();
|
||||
}
|
||||
}
|
||||
} // namespace Terrain
|
||||
|
||||
@@ -88,8 +88,25 @@ namespace Terrain
|
||||
// the wireframe representation in each direction.
|
||||
struct WireframeSector
|
||||
{
|
||||
WireframeSector() = default;
|
||||
~WireframeSector() = default;
|
||||
WireframeSector(const WireframeSector& other);
|
||||
WireframeSector(WireframeSector&& other);
|
||||
WireframeSector& operator=(const WireframeSector& other);
|
||||
WireframeSector& operator=(WireframeSector&& other);
|
||||
|
||||
void Reset();
|
||||
|
||||
// This should only be called within the scope of a lock on m_sectorStateMutex.
|
||||
void SetDirty();
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> m_jobContext;
|
||||
AZStd::unique_ptr<AZStd::semaphore> m_jobCompletionEvent;
|
||||
AZStd::recursive_mutex m_sectorStateMutex;
|
||||
AZ::Aabb m_aabb{ AZ::Aabb::CreateNull() };
|
||||
AZStd::vector<AZ::Vector3> m_lineVertices;
|
||||
AZStd::vector<float> m_rowHeights;
|
||||
float m_previousHeight = 0.0f;
|
||||
bool m_isDirty{ true };
|
||||
};
|
||||
|
||||
|
||||
@@ -61,6 +61,10 @@ TerrainSystem::TerrainSystem()
|
||||
|
||||
m_requestedSettings = m_currentSettings;
|
||||
m_requestedSettings.m_worldBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-512.0f), AZ::Vector3(512.0f));
|
||||
|
||||
// Use the global JobManager for terrain jobs (we could create our own dedicated terrain JobManager if needed).
|
||||
AZ::JobManagerBus::BroadcastResult(m_terrainJobManager, &AZ::JobManagerEvents::GetManager);
|
||||
AZ_Assert(m_terrainJobManager, "No global JobManager found.");
|
||||
}
|
||||
|
||||
TerrainSystem::~TerrainSystem()
|
||||
@@ -106,6 +110,16 @@ void TerrainSystem::Activate()
|
||||
|
||||
void TerrainSystem::Deactivate()
|
||||
{
|
||||
{
|
||||
// Cancel all active terrain jobs, and wait until they have completed.
|
||||
AZStd::unique_lock<AZStd::mutex> lock(m_activeTerrainJobContextMutex);
|
||||
for (auto activeTerrainJobContext : m_activeTerrainJobContexts)
|
||||
{
|
||||
activeTerrainJobContext->Cancel();
|
||||
}
|
||||
m_activeTerrainJobContextMutexConditionVariable.wait(lock, [this]{ return m_activeTerrainJobContexts.empty(); });
|
||||
}
|
||||
|
||||
// Stop listening to the bus even before we signal DestroyBegin so that way any calls to the terrain system as a *result* of
|
||||
// calling DestroyBegin will fail to reach the terrain system.
|
||||
AzFramework::Terrain::TerrainDataRequestBus::Handler::BusDisconnect();
|
||||
@@ -670,6 +684,130 @@ AzFramework::RenderGeometry::RayResult TerrainSystem::GetClosestIntersection(
|
||||
return m_terrainRaycastContext.RayIntersect(ray);
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessHeightsFromListAsync(
|
||||
const AZStd::span<AZ::Vector3>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
||||
{
|
||||
return ProcessFromListAsync(AZStd::bind(&TerrainSystem::ProcessHeightsFromList, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
|
||||
inPositions, perPositionCallback, sampleFilter, params);
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessNormalsFromListAsync(
|
||||
const AZStd::span<AZ::Vector3>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
||||
{
|
||||
return ProcessFromListAsync(AZStd::bind(&TerrainSystem::ProcessNormalsFromList, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
|
||||
inPositions, perPositionCallback, sampleFilter, params);
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessSurfaceWeightsFromListAsync(
|
||||
const AZStd::span<AZ::Vector3>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
||||
{
|
||||
return ProcessFromListAsync(AZStd::bind(&TerrainSystem::ProcessSurfaceWeightsFromList, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
|
||||
inPositions, perPositionCallback, sampleFilter, params);
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessSurfacePointsFromListAsync(
|
||||
const AZStd::span<AZ::Vector3>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
||||
{
|
||||
return ProcessFromListAsync(AZStd::bind(&TerrainSystem::ProcessSurfacePointsFromList, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
|
||||
inPositions, perPositionCallback, sampleFilter, params);
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessHeightsFromListOfVector2Async(
|
||||
const AZStd::span<AZ::Vector2>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
||||
{
|
||||
return ProcessFromListAsync(AZStd::bind(&TerrainSystem::ProcessHeightsFromListOfVector2, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
|
||||
inPositions, perPositionCallback, sampleFilter, params);
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessNormalsFromListOfVector2Async(
|
||||
const AZStd::span<AZ::Vector2>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
||||
{
|
||||
return ProcessFromListAsync(AZStd::bind(&TerrainSystem::ProcessNormalsFromListOfVector2, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
|
||||
inPositions, perPositionCallback, sampleFilter, params);
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessSurfaceWeightsFromListOfVector2Async(
|
||||
const AZStd::span<AZ::Vector2>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
||||
{
|
||||
return ProcessFromListAsync(AZStd::bind(&TerrainSystem::ProcessSurfaceWeightsFromListOfVector2, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
|
||||
inPositions, perPositionCallback, sampleFilter, params);
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessSurfacePointsFromListOfVector2Async(
|
||||
const AZStd::span<AZ::Vector2>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
||||
{
|
||||
return ProcessFromListAsync(AZStd::bind(&TerrainSystem::ProcessSurfacePointsFromListOfVector2, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
|
||||
inPositions, perPositionCallback, sampleFilter, params);
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessHeightsFromRegionAsync(
|
||||
const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize,
|
||||
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
||||
Sampler sampleFilter,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
||||
{
|
||||
return ProcessFromRegionAsync(AZStd::bind(&TerrainSystem::ProcessHeightsFromRegion, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4),
|
||||
inRegion, stepSize, perPositionCallback, sampleFilter, params);
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessNormalsFromRegionAsync(
|
||||
const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize,
|
||||
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
||||
Sampler sampleFilter,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
||||
{
|
||||
return ProcessFromRegionAsync(AZStd::bind(&TerrainSystem::ProcessNormalsFromRegion, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4),
|
||||
inRegion, stepSize, perPositionCallback, sampleFilter, params);
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessSurfaceWeightsFromRegionAsync(
|
||||
const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize,
|
||||
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
||||
Sampler sampleFilter,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
||||
{
|
||||
return ProcessFromRegionAsync(AZStd::bind(&TerrainSystem::ProcessSurfaceWeightsFromRegion, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4),
|
||||
inRegion, stepSize, perPositionCallback, sampleFilter, params);
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessSurfacePointsFromRegionAsync(
|
||||
const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize,
|
||||
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
||||
Sampler sampleFilter,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
||||
{
|
||||
return ProcessFromRegionAsync(AZStd::bind(&TerrainSystem::ProcessSurfacePointsFromRegion, this, AZStd::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4),
|
||||
inRegion, stepSize, perPositionCallback, sampleFilter, params);
|
||||
}
|
||||
|
||||
AZ::EntityId TerrainSystem::FindBestAreaEntityAtPosition(float x, float y, AZ::Aabb& bounds) const
|
||||
{
|
||||
AZ::Vector3 inPosition = AZ::Vector3(x, y, 0);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <AzCore/Math/Vector3.h>
|
||||
#include <AzCore/std/algorithm.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzCore/std/parallel/condition_variable.h>
|
||||
#include <AzCore/std/parallel/shared_mutex.h>
|
||||
#include <AzCore/std/containers/map.h>
|
||||
#include <AzCore/std/containers/span.h>
|
||||
@@ -192,7 +193,89 @@ namespace Terrain
|
||||
AzFramework::RenderGeometry::RayResult GetClosestIntersection(
|
||||
const AzFramework::RenderGeometry::RayRequest& ray) const override;
|
||||
|
||||
AZStd::shared_ptr<TerrainJobContext> ProcessHeightsFromListAsync(
|
||||
const AZStd::span<AZ::Vector3>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
|
||||
AZStd::shared_ptr<TerrainJobContext> ProcessNormalsFromListAsync(
|
||||
const AZStd::span<AZ::Vector3>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
|
||||
AZStd::shared_ptr<TerrainJobContext> ProcessSurfaceWeightsFromListAsync(
|
||||
const AZStd::span<AZ::Vector3>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
|
||||
AZStd::shared_ptr<TerrainJobContext> ProcessSurfacePointsFromListAsync(
|
||||
const AZStd::span<AZ::Vector3>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
|
||||
AZStd::shared_ptr<TerrainJobContext> ProcessHeightsFromListOfVector2Async(
|
||||
const AZStd::span<AZ::Vector2>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
|
||||
AZStd::shared_ptr<TerrainJobContext> ProcessNormalsFromListOfVector2Async(
|
||||
const AZStd::span<AZ::Vector2>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
|
||||
AZStd::shared_ptr<TerrainJobContext> ProcessSurfaceWeightsFromListOfVector2Async(
|
||||
const AZStd::span<AZ::Vector2>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
|
||||
AZStd::shared_ptr<TerrainJobContext> ProcessSurfacePointsFromListOfVector2Async(
|
||||
const AZStd::span<AZ::Vector2>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
|
||||
AZStd::shared_ptr<TerrainJobContext> ProcessHeightsFromRegionAsync(
|
||||
const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize,
|
||||
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
|
||||
AZStd::shared_ptr<TerrainJobContext> ProcessNormalsFromRegionAsync(
|
||||
const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize,
|
||||
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
|
||||
AZStd::shared_ptr<TerrainJobContext> ProcessSurfaceWeightsFromRegionAsync(
|
||||
const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize,
|
||||
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
|
||||
AZStd::shared_ptr<TerrainJobContext> ProcessSurfacePointsFromRegionAsync(
|
||||
const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize,
|
||||
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
|
||||
|
||||
private:
|
||||
template<typename SynchronousFunctionType, typename VectorType>
|
||||
AZStd::shared_ptr<TerrainJobContext> ProcessFromListAsync(
|
||||
SynchronousFunctionType synchronousFunction,
|
||||
const AZStd::span<VectorType>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const;
|
||||
|
||||
template<typename SynchronousFunctionType>
|
||||
AZStd::shared_ptr<TerrainJobContext> ProcessFromRegionAsync(
|
||||
SynchronousFunctionType synchronousFunction,
|
||||
const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize,
|
||||
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const;
|
||||
|
||||
void ClampPosition(float x, float y, AZ::Vector2& outPosition, AZ::Vector2& normalizedDelta) const;
|
||||
bool InWorldBounds(float x, float y) const;
|
||||
|
||||
@@ -268,5 +351,166 @@ namespace Terrain
|
||||
AZStd::map<AZ::EntityId, TerrainAreaData, TerrainLayerPriorityComparator> m_registeredAreas;
|
||||
|
||||
mutable TerrainRaycastContext m_terrainRaycastContext;
|
||||
|
||||
AZ::JobManager* m_terrainJobManager = nullptr;
|
||||
mutable AZStd::mutex m_activeTerrainJobContextMutex;
|
||||
mutable AZStd::condition_variable m_activeTerrainJobContextMutexConditionVariable;
|
||||
mutable AZStd::deque<AZStd::shared_ptr<TerrainJobContext>> m_activeTerrainJobContexts;
|
||||
};
|
||||
|
||||
template<typename SynchronousFunctionType, typename VectorType>
|
||||
inline AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessFromListAsync(
|
||||
SynchronousFunctionType synchronousFunction,
|
||||
const AZStd::span<VectorType>& inPositions,
|
||||
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
|
||||
Sampler sampleFilter,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
||||
{
|
||||
// Determine the number of jobs to split the work into based on:
|
||||
// 1. The number of available worker threads.
|
||||
// 2. The desired number of jobs as passed in.
|
||||
// 3. The number of positions being processed.
|
||||
const int32_t numWorkerThreads = m_terrainJobManager->GetNumWorkerThreads();
|
||||
const int32_t numJobsDesired = params ? params->m_desiredNumberOfJobs : ProcessAsyncParams::NumJobsDefault;
|
||||
const int32_t numJobsMax = (numJobsDesired > 0) ? AZStd::min(numWorkerThreads, numJobsDesired) : numWorkerThreads;
|
||||
const int32_t numPositionsToProcess = static_cast<int32_t>(inPositions.size());
|
||||
const int32_t minPositionsPerJob = params && (params->m_desiredNumberOfJobs > 0) ? params->m_desiredNumberOfJobs : ProcessAsyncParams::MinPositionsPerJobDefault;
|
||||
const int32_t numJobs = AZStd::min(numJobsMax, numPositionsToProcess / minPositionsPerJob);
|
||||
if (numJobs <= 0)
|
||||
{
|
||||
AZ_Warning("TerrainSystem", false, "No positions to process.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create a terrain job context, track it, and split the work across multiple jobs.
|
||||
AZStd::shared_ptr<TerrainJobContext> jobContext = AZStd::make_shared<TerrainJobContext>(*m_terrainJobManager, numJobs);
|
||||
{
|
||||
AZStd::unique_lock<AZStd::mutex> lock(m_activeTerrainJobContextMutex);
|
||||
m_activeTerrainJobContexts.push_back(jobContext);
|
||||
}
|
||||
const int32_t numPositionsPerJob = numPositionsToProcess / numJobs;
|
||||
for (int32_t i = 0; i < numJobs; ++i)
|
||||
{
|
||||
// If the number of positions can't be divided evenly by the number of jobs,
|
||||
// ensure we still process the remaining positions along with the final job.
|
||||
const size_t subSpanOffset = i * numPositionsPerJob;
|
||||
const size_t subSpanCount = (i < numJobs - 1) ? numPositionsPerJob : AZStd::dynamic_extent;
|
||||
|
||||
// Define the job function using the sub span of positions to process.
|
||||
const AZStd::span<VectorType>& positionsToProcess = inPositions.subspan(subSpanOffset, subSpanCount);
|
||||
auto jobFunction = [this, synchronousFunction, positionsToProcess, perPositionCallback, sampleFilter, jobContext, params]()
|
||||
{
|
||||
// Process the sub span of positions, unless the associated job context has been cancelled.
|
||||
if (!jobContext->IsCancelled())
|
||||
{
|
||||
synchronousFunction(positionsToProcess, perPositionCallback, sampleFilter);
|
||||
}
|
||||
|
||||
// Decrement the number of completions remaining, invoke the completion callback if this happens
|
||||
// to be the final job completed, and remove this TerrainJobContext from the list of active ones.
|
||||
const bool wasLastJobCompleted = jobContext->OnJobCompleted();
|
||||
if (wasLastJobCompleted)
|
||||
{
|
||||
if (params && params->m_completionCallback)
|
||||
{
|
||||
params->m_completionCallback(jobContext);
|
||||
}
|
||||
|
||||
{
|
||||
AZStd::unique_lock<AZStd::mutex> lock(m_activeTerrainJobContextMutex);
|
||||
m_activeTerrainJobContexts.erase(AZStd::find(m_activeTerrainJobContexts.begin(),
|
||||
m_activeTerrainJobContexts.end(),
|
||||
jobContext));
|
||||
m_activeTerrainJobContextMutexConditionVariable.notify_one();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create the job and start it immediately.
|
||||
AZ::Job* processJob = AZ::CreateJobFunction(jobFunction, true, jobContext.get());
|
||||
processJob->Start();
|
||||
}
|
||||
|
||||
return jobContext;
|
||||
}
|
||||
|
||||
template<typename SynchronousFunctionType>
|
||||
inline AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessFromRegionAsync(
|
||||
SynchronousFunctionType synchronousFunction,
|
||||
const AZ::Aabb& inRegion,
|
||||
const AZ::Vector2& stepSize,
|
||||
AzFramework::Terrain::SurfacePointRegionFillCallback perPositionCallback,
|
||||
Sampler sampleFilter,
|
||||
AZStd::shared_ptr<ProcessAsyncParams> params) const
|
||||
{
|
||||
// ToDo: Determine the number of jobs to split the work into based on:
|
||||
// 1. The number of available worker threads.
|
||||
// 2. The desired number of jobs as passed in.
|
||||
// 3. The size of the area being processed.
|
||||
//
|
||||
// Note: We are currently restricting the number of worker threads to one
|
||||
// because splitting the work over multiple threads causes contention when
|
||||
// locking various mutexes, resulting in slower overall wall time for async
|
||||
// requests split over multiple threads vs one where all the work is done on
|
||||
// a single thread. The latter is still preferable over a regular synchronous
|
||||
// call because it is just as quick and prevents the main thread from blocking.
|
||||
// Once the mutex contention issues have been addressed, we should come up with
|
||||
// an algorithm to break up 'inRegion' into sub-regions (or lists of positions?)
|
||||
// so that async calls automatically split the work between available job manager
|
||||
// worker threads, unless the ProcessAsyncParams specifiy a desired number of jobs.
|
||||
const int32_t numWorkerThreads = m_terrainJobManager->GetNumWorkerThreads();
|
||||
const int32_t numJobsDesired = params ? params->m_desiredNumberOfJobs : ProcessAsyncParams::NumJobsDefault;
|
||||
int32_t numJobs = (numJobsDesired > 0) ? AZStd::min(numWorkerThreads, numJobsDesired) : numWorkerThreads;
|
||||
if (numJobs != 1)
|
||||
{
|
||||
// Temp until we figure out how to break up the region.
|
||||
AZ_Warning("TerrainSystem", false, "We don't yet support breaking up regions.");
|
||||
numJobs = 1;
|
||||
}
|
||||
|
||||
// Create a terrain job context and split the work across multiple jobs.
|
||||
AZStd::shared_ptr<TerrainJobContext> jobContext = AZStd::make_shared<TerrainJobContext>(*m_terrainJobManager, numJobs);
|
||||
{
|
||||
AZStd::unique_lock<AZStd::mutex> lock(m_activeTerrainJobContextMutex);
|
||||
m_activeTerrainJobContexts.push_back(jobContext);
|
||||
}
|
||||
for (int32_t i = 0; i < numJobs; ++i)
|
||||
{
|
||||
// Define the job function using the sub region of positions to process.
|
||||
const AZ::Aabb& subRegion = inRegion; // ToDo: Figure out how to break up the region.
|
||||
auto jobFunction = [this, synchronousFunction, subRegion, stepSize, perPositionCallback, sampleFilter, jobContext, params]()
|
||||
{
|
||||
// Process the sub region of positions, unless the associated job context has been cancelled.
|
||||
if (!jobContext->IsCancelled())
|
||||
{
|
||||
synchronousFunction(subRegion, stepSize, perPositionCallback, sampleFilter);
|
||||
}
|
||||
|
||||
// Decrement the number of completions remaining, invoke the completion callback if this happens
|
||||
// to be the final job completed, and remove this TerrainJobContext from the list of active ones.
|
||||
const bool wasLastJobCompleted = jobContext->OnJobCompleted();
|
||||
if (wasLastJobCompleted)
|
||||
{
|
||||
if (params && params->m_completionCallback)
|
||||
{
|
||||
params->m_completionCallback(jobContext);
|
||||
}
|
||||
|
||||
{
|
||||
AZStd::unique_lock<AZStd::mutex> lock(m_activeTerrainJobContextMutex);
|
||||
m_activeTerrainJobContexts.erase(AZStd::find(m_activeTerrainJobContexts.begin(),
|
||||
m_activeTerrainJobContexts.end(),
|
||||
jobContext));
|
||||
m_activeTerrainJobContextMutexConditionVariable.notify_one();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create the job and start it immediately.
|
||||
AZ::Job* processJob = AZ::CreateJobFunction(jobFunction, true, jobContext.get());
|
||||
processJob->Start();
|
||||
}
|
||||
|
||||
return jobContext;
|
||||
}
|
||||
} // namespace Terrain
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzCore/Math/Random.h>
|
||||
#include <AzCore/Memory/PoolAllocator.h>
|
||||
#include <AzCore/Jobs/JobManagerComponent.h>
|
||||
#include <AzCore/std/parallel/semaphore.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzFramework/Components/TransformComponent.h>
|
||||
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
|
||||
@@ -263,7 +265,17 @@ namespace UnitTest
|
||||
auto spawnerShapeRequests = CreateMockShape(worldBounds, testLayerSpawnerEntity->GetId());
|
||||
ActivateEntity(testLayerSpawnerEntity.get());
|
||||
|
||||
// Create the global job manager.
|
||||
auto serializeContext = AZStd::make_unique<AZ::SerializeContext>();
|
||||
auto jobManagerComponentDescriptor = AZ::JobManagerComponent::CreateDescriptor();
|
||||
jobManagerComponentDescriptor->Reflect(serializeContext.get());
|
||||
auto jobManagerEntity = AZStd::make_unique<AZ::Entity>();
|
||||
jobManagerEntity->CreateComponent<AZ::JobManagerComponent>();
|
||||
jobManagerEntity->Init();
|
||||
jobManagerEntity->Activate();
|
||||
|
||||
// Create the terrain system (do this after creating the terrain layer entity to ensure that we don't need any data refreshes)
|
||||
// Also ensure to do this after creating the global JobManager.
|
||||
auto terrainSystem = CreateAndActivateTerrainSystem(queryResolution, worldBounds);
|
||||
|
||||
// Call the terrain API we're testing for every height and width in our ranges.
|
||||
@@ -372,6 +384,51 @@ namespace UnitTest
|
||||
->Args({ 4096, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessHeightsRegionAsync)(benchmark::State& state)
|
||||
{
|
||||
// Run the benchmark
|
||||
RunTerrainApiBenchmark(
|
||||
state,
|
||||
[]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds,
|
||||
AzFramework::Terrain::TerrainDataRequests::Sampler sampler)
|
||||
{
|
||||
auto perPositionCallback = []([[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex,
|
||||
const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists)
|
||||
{
|
||||
benchmark::DoNotOptimize(surfacePoint.m_position.GetZ());
|
||||
};
|
||||
|
||||
AZStd::semaphore completionEvent;
|
||||
auto completionCallback = [&completionEvent](AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext>)
|
||||
{
|
||||
completionEvent.release();
|
||||
};
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams> asyncParams
|
||||
= AZStd::make_shared<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams>();
|
||||
asyncParams->m_completionCallback = completionCallback;
|
||||
|
||||
AZ::Vector2 stepSize = AZ::Vector2(queryResolution);
|
||||
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(
|
||||
&AzFramework::Terrain::TerrainDataRequests::ProcessHeightsFromRegionAsync, worldBounds, stepSize, perPositionCallback, sampler, asyncParams);
|
||||
|
||||
completionEvent.acquire();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_ProcessHeightsRegionAsync)
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) })
|
||||
->Args({ 4096, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) })
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) })
|
||||
->Args({ 4096, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) })
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 4096, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessHeightsList)(benchmark::State& state)
|
||||
{
|
||||
// Run the benchmark
|
||||
@@ -406,6 +463,51 @@ namespace UnitTest
|
||||
->Args({ 4096, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessHeightsListAsync)(benchmark::State& state)
|
||||
{
|
||||
// Run the benchmark
|
||||
RunTerrainApiBenchmark(
|
||||
state,
|
||||
[this]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds,
|
||||
AzFramework::Terrain::TerrainDataRequests::Sampler sampler)
|
||||
{
|
||||
AZStd::vector<AZ::Vector3> inPositions;
|
||||
GenerateInputPositionsList(queryResolution, worldBounds, inPositions);
|
||||
|
||||
auto perPositionCallback = [](const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists)
|
||||
{
|
||||
benchmark::DoNotOptimize(surfacePoint.m_position.GetZ());
|
||||
};
|
||||
|
||||
AZStd::semaphore completionEvent;
|
||||
auto completionCallback = [&completionEvent](AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext>)
|
||||
{
|
||||
completionEvent.release();
|
||||
};
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams> asyncParams
|
||||
= AZStd::make_shared<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams>();
|
||||
asyncParams->m_completionCallback = completionCallback;
|
||||
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(
|
||||
&AzFramework::Terrain::TerrainDataRequests::ProcessHeightsFromListAsync, inPositions, perPositionCallback, sampler, asyncParams);
|
||||
|
||||
completionEvent.acquire();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_ProcessHeightsListAsync)
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) })
|
||||
->Args({ 4096, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) })
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) })
|
||||
->Args({ 4096, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) })
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 4096, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_GetNormal)(benchmark::State& state)
|
||||
{
|
||||
// Run the benchmark
|
||||
@@ -467,6 +569,48 @@ namespace UnitTest
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessNormalsRegionAsync)(benchmark::State& state)
|
||||
{
|
||||
// Run the benchmark
|
||||
RunTerrainApiBenchmark(
|
||||
state,
|
||||
[]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds,
|
||||
AzFramework::Terrain::TerrainDataRequests::Sampler sampler)
|
||||
{
|
||||
auto perPositionCallback = []([[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex,
|
||||
const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists)
|
||||
{
|
||||
benchmark::DoNotOptimize(surfacePoint.m_normal);
|
||||
};
|
||||
|
||||
AZStd::semaphore completionEvent;
|
||||
auto completionCallback = [&completionEvent](AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext>)
|
||||
{
|
||||
completionEvent.release();
|
||||
};
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams> asyncParams
|
||||
= AZStd::make_shared<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams>();
|
||||
asyncParams->m_completionCallback = completionCallback;
|
||||
|
||||
AZ::Vector2 stepSize = AZ::Vector2(queryResolution);
|
||||
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(
|
||||
&AzFramework::Terrain::TerrainDataRequests::ProcessNormalsFromRegionAsync, worldBounds, stepSize, perPositionCallback, sampler, asyncParams);
|
||||
|
||||
completionEvent.acquire();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_ProcessNormalsRegionAsync)
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) })
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) })
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessNormalsList)(benchmark::State& state)
|
||||
{
|
||||
// Run the benchmark
|
||||
@@ -498,6 +642,48 @@ namespace UnitTest
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessNormalsListAsync)(benchmark::State& state)
|
||||
{
|
||||
// Run the benchmark
|
||||
RunTerrainApiBenchmark(
|
||||
state,
|
||||
[this]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds,
|
||||
AzFramework::Terrain::TerrainDataRequests::Sampler sampler)
|
||||
{
|
||||
AZStd::vector<AZ::Vector3> inPositions;
|
||||
GenerateInputPositionsList(queryResolution, worldBounds, inPositions);
|
||||
|
||||
auto perPositionCallback = [](const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists)
|
||||
{
|
||||
benchmark::DoNotOptimize(surfacePoint.m_normal);
|
||||
};
|
||||
|
||||
AZStd::semaphore completionEvent;
|
||||
auto completionCallback = [&completionEvent](AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext>)
|
||||
{
|
||||
completionEvent.release();
|
||||
};
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams> asyncParams
|
||||
= AZStd::make_shared<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams>();
|
||||
asyncParams->m_completionCallback = completionCallback;
|
||||
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(
|
||||
&AzFramework::Terrain::TerrainDataRequests::ProcessNormalsFromListAsync, inPositions, perPositionCallback, sampler, asyncParams);
|
||||
|
||||
completionEvent.acquire();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_ProcessNormalsListAsync)
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) })
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) })
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_GetSurfaceWeights)(benchmark::State& state)
|
||||
{
|
||||
// Run the benchmark
|
||||
@@ -560,6 +746,48 @@ namespace UnitTest
|
||||
->Args({ 2048, 4, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfaceWeightsRegionAsync)(benchmark::State& state)
|
||||
{
|
||||
// Run the benchmark
|
||||
RunTerrainApiBenchmark(
|
||||
state,
|
||||
[]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds,
|
||||
AzFramework::Terrain::TerrainDataRequests::Sampler sampler)
|
||||
{
|
||||
auto perPositionCallback = []([[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex,
|
||||
const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists)
|
||||
{
|
||||
benchmark::DoNotOptimize(surfacePoint.m_surfaceTags);
|
||||
};
|
||||
|
||||
AZStd::semaphore completionEvent;
|
||||
auto completionCallback = [&completionEvent](AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext>)
|
||||
{
|
||||
completionEvent.release();
|
||||
};
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams> asyncParams
|
||||
= AZStd::make_shared<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams>();
|
||||
asyncParams->m_completionCallback = completionCallback;
|
||||
|
||||
AZ::Vector2 stepSize = AZ::Vector2(queryResolution);
|
||||
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(
|
||||
&AzFramework::Terrain::TerrainDataRequests::ProcessSurfaceWeightsFromRegionAsync, worldBounds, stepSize, perPositionCallback, sampler, asyncParams);
|
||||
|
||||
completionEvent.acquire();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfaceWeightsRegionAsync)
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 1024, 2, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 2048, 2, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 1024, 4, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 2048, 4, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfaceWeightsList)(benchmark::State& state)
|
||||
{
|
||||
// Run the benchmark
|
||||
@@ -591,6 +819,48 @@ namespace UnitTest
|
||||
->Args({ 2048, 4, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfaceWeightsListAsync)(benchmark::State& state)
|
||||
{
|
||||
// Run the benchmark
|
||||
RunTerrainApiBenchmark(
|
||||
state,
|
||||
[this]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds,
|
||||
AzFramework::Terrain::TerrainDataRequests::Sampler sampler)
|
||||
{
|
||||
AZStd::vector<AZ::Vector3> inPositions;
|
||||
GenerateInputPositionsList(queryResolution, worldBounds, inPositions);
|
||||
|
||||
auto perPositionCallback = [](const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists)
|
||||
{
|
||||
benchmark::DoNotOptimize(surfacePoint.m_surfaceTags);
|
||||
};
|
||||
|
||||
AZStd::semaphore completionEvent;
|
||||
auto completionCallback = [&completionEvent](AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext>)
|
||||
{
|
||||
completionEvent.release();
|
||||
};
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams> asyncParams
|
||||
= AZStd::make_shared<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams>();
|
||||
asyncParams->m_completionCallback = completionCallback;
|
||||
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(
|
||||
&AzFramework::Terrain::TerrainDataRequests::ProcessSurfaceWeightsFromListAsync, inPositions, perPositionCallback, sampler, asyncParams);
|
||||
|
||||
completionEvent.acquire();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfaceWeightsListAsync)
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 1024, 2, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 2048, 2, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 1024, 4, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 2048, 4, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_GetSurfacePoints)(benchmark::State& state)
|
||||
{
|
||||
// Run the benchmark
|
||||
@@ -653,6 +923,48 @@ namespace UnitTest
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfacePointsRegionAsync)(benchmark::State& state)
|
||||
{
|
||||
// Run the benchmark
|
||||
RunTerrainApiBenchmark(
|
||||
state,
|
||||
[]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds,
|
||||
AzFramework::Terrain::TerrainDataRequests::Sampler sampler)
|
||||
{
|
||||
auto perPositionCallback = []([[maybe_unused]] size_t xIndex, [[maybe_unused]] size_t yIndex,
|
||||
const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists)
|
||||
{
|
||||
benchmark::DoNotOptimize(surfacePoint);
|
||||
};
|
||||
|
||||
AZStd::semaphore completionEvent;
|
||||
auto completionCallback = [&completionEvent](AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext>)
|
||||
{
|
||||
completionEvent.release();
|
||||
};
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams> asyncParams
|
||||
= AZStd::make_shared<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams>();
|
||||
asyncParams->m_completionCallback = completionCallback;
|
||||
|
||||
AZ::Vector2 stepSize = AZ::Vector2(queryResolution);
|
||||
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(
|
||||
&AzFramework::Terrain::TerrainDataRequests::ProcessSurfacePointsFromRegionAsync, worldBounds, stepSize, perPositionCallback, sampler, asyncParams);
|
||||
|
||||
completionEvent.acquire();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfacePointsRegionAsync)
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) })
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) })
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfacePointsList)(benchmark::State& state)
|
||||
{
|
||||
// Run the benchmark
|
||||
@@ -684,6 +996,48 @@ namespace UnitTest
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfacePointsListAsync)(benchmark::State& state)
|
||||
{
|
||||
// Run the benchmark
|
||||
RunTerrainApiBenchmark(
|
||||
state,
|
||||
[this]([[maybe_unused]] float queryResolution, const AZ::Aabb& worldBounds,
|
||||
AzFramework::Terrain::TerrainDataRequests::Sampler sampler)
|
||||
{
|
||||
AZStd::vector<AZ::Vector3> inPositions;
|
||||
GenerateInputPositionsList(queryResolution, worldBounds, inPositions);
|
||||
|
||||
auto perPositionCallback = [](const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists)
|
||||
{
|
||||
benchmark::DoNotOptimize(surfacePoint);
|
||||
};
|
||||
|
||||
AZStd::semaphore completionEvent;
|
||||
auto completionCallback = [&completionEvent](AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext>)
|
||||
{
|
||||
completionEvent.release();
|
||||
};
|
||||
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams> asyncParams
|
||||
= AZStd::make_shared<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams>();
|
||||
asyncParams->m_completionCallback = completionCallback;
|
||||
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(
|
||||
&AzFramework::Terrain::TerrainDataRequests::ProcessSurfacePointsFromListAsync, inPositions, perPositionCallback, sampler, asyncParams);
|
||||
|
||||
completionEvent.acquire();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
BENCHMARK_REGISTER_F(TerrainSystemBenchmarkFixture, BM_ProcessSurfacePointsListAsync)
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) })
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP) })
|
||||
->Args({ 1024, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Args({ 2048, 1, static_cast<int>(AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT) })
|
||||
->Unit(::benchmark::kMillisecond);
|
||||
|
||||
BENCHMARK_DEFINE_F(TerrainSystemBenchmarkFixture, BM_GetClosestIntersectionRandom)(benchmark::State& state)
|
||||
{
|
||||
// Run the benchmark
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
*/
|
||||
|
||||
#include <AzCore/Component/ComponentApplication.h>
|
||||
#include <AzCore/Jobs/JobManagerComponent.h>
|
||||
#include <AzCore/Memory/MemoryComponent.h>
|
||||
#include <AzCore/std/parallel/semaphore.h>
|
||||
|
||||
#include <AzTest/AzTest.h>
|
||||
|
||||
@@ -64,6 +66,7 @@ namespace UnitTest
|
||||
};
|
||||
|
||||
AZ::ComponentApplication m_app;
|
||||
AZStd::unique_ptr<AZ::Entity> m_jobManagerEntity = nullptr;
|
||||
|
||||
AZStd::unique_ptr<NiceMock<UnitTest::MockBoxShapeComponentRequests>> m_boxShapeRequests;
|
||||
AZStd::unique_ptr<NiceMock<UnitTest::MockShapeComponentRequests>> m_shapeRequests;
|
||||
@@ -72,12 +75,20 @@ namespace UnitTest
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
AZ::AllocatorInstance<AZ::PoolAllocator>::Create();
|
||||
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create();
|
||||
|
||||
AZ::ComponentApplication::Descriptor appDesc;
|
||||
appDesc.m_memoryBlocksByteSize = 20 * 1024 * 1024;
|
||||
appDesc.m_recordingMode = AZ::Debug::AllocationRecords::RECORD_NO_RECORDS;
|
||||
appDesc.m_stackRecordLevels = 20;
|
||||
|
||||
m_app.Create(appDesc);
|
||||
|
||||
// Create the global job manager.
|
||||
m_jobManagerEntity = CreateEntity();
|
||||
CreateComponent<AZ::JobManagerComponent>(m_jobManagerEntity.get());
|
||||
ActivateEntity(m_jobManagerEntity.get());
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
@@ -86,7 +97,15 @@ namespace UnitTest
|
||||
m_shapeRequests.reset();
|
||||
m_terrainAreaHeightRequests.reset();
|
||||
m_terrainAreaSurfaceRequests.reset();
|
||||
|
||||
// Destroy the global job manager.
|
||||
m_jobManagerEntity->Deactivate();
|
||||
m_jobManagerEntity.reset();
|
||||
|
||||
m_app.Destroy();
|
||||
|
||||
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Destroy();
|
||||
AZ::AllocatorInstance<AZ::PoolAllocator>::Destroy();
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::Entity> CreateEntity()
|
||||
@@ -1059,4 +1078,70 @@ namespace UnitTest
|
||||
|
||||
terrainSystem->ProcessSurfacePointsFromRegion(testRegionBox, stepSize, perPositionCallback, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT);
|
||||
}
|
||||
|
||||
TEST_F(TerrainSystemTest, TerrainProcessAsyncCancellation)
|
||||
{
|
||||
// Tests cancellation of the asynchronous terrain API.
|
||||
|
||||
const AZ::Aabb spawnerBox = AZ::Aabb::CreateFromMinMaxValues(-10.0f, -10.0f, -5.0f, 10.0f, 10.0f, 15.0f);
|
||||
auto entity = CreateAndActivateMockTerrainLayerSpawner(
|
||||
spawnerBox,
|
||||
[](AZ::Vector3& position, bool& terrainExists)
|
||||
{
|
||||
// Our generated height will be X + Y.
|
||||
position.SetZ(position.GetX() + position.GetY());
|
||||
terrainExists = true;
|
||||
});
|
||||
|
||||
// Create and activate the terrain system with our testing defaults for world bounds, and a query resolution at 1 meter intervals.
|
||||
auto terrainSystem = CreateAndActivateTerrainSystem();
|
||||
|
||||
// Generate some input positions.
|
||||
AZStd::vector<AZ::Vector3> inPositions;
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
inPositions.push_back({1.0f, 1.0f, 1.0f});
|
||||
}
|
||||
|
||||
// Setup the per position callback so that we can cancel the entire request when it is first invoked.
|
||||
AZStd::atomic_bool asyncRequestCancelled = false;
|
||||
AZStd::semaphore asyncRequestStartedEvent;
|
||||
AZStd::semaphore asyncRequestCancelledEvent;
|
||||
auto perPositionCallback = [&asyncRequestCancelled, &asyncRequestStartedEvent, &asyncRequestCancelledEvent]([[maybe_unused]] const AzFramework::SurfaceData::SurfacePoint& surfacePoint, [[maybe_unused]] bool terrainExists)
|
||||
{
|
||||
if (!asyncRequestCancelled)
|
||||
{
|
||||
// Indicate that the async request has started.
|
||||
asyncRequestStartedEvent.release();
|
||||
|
||||
// Wait until the async request has been cancelled before allowing it to continue.
|
||||
asyncRequestCancelledEvent.acquire();
|
||||
asyncRequestCancelled = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Setup the completion callback so we can check that the entire request was cancelled.
|
||||
AZStd::semaphore asyncRequestCompletedEvent;
|
||||
auto completionCallback = [&asyncRequestCompletedEvent](AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> terrainJobContext)
|
||||
{
|
||||
EXPECT_TRUE(terrainJobContext->IsCancelled());
|
||||
asyncRequestCompletedEvent.release();
|
||||
};
|
||||
|
||||
// Invoke the async request.
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams> asyncParams
|
||||
= AZStd::make_shared<AzFramework::Terrain::TerrainDataRequests::ProcessAsyncParams>();
|
||||
asyncParams->m_completionCallback = completionCallback;
|
||||
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> terrainJobContext
|
||||
= terrainSystem->ProcessHeightsFromListAsync(inPositions, perPositionCallback, AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR, asyncParams);
|
||||
|
||||
// Wait until the async request has started before cancelling it.
|
||||
asyncRequestStartedEvent.acquire();
|
||||
terrainJobContext->Cancel();
|
||||
asyncRequestCancelled = true;
|
||||
asyncRequestCancelledEvent.release();
|
||||
|
||||
// Now wait until the async request has completed after being cancelled.
|
||||
asyncRequestCompletedEvent.acquire();
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
Reference in New Issue
Block a user