diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfiler.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfiler.h index 2248474820..3fedc99566 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfiler.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfiler.h @@ -30,6 +30,12 @@ namespace AZ const char* const m_groupName = nullptr; const char* const m_regionName = nullptr; + + struct Hash + { + AZStd::size_t operator()(const GroupRegionName& name) const; + }; + bool operator==(const GroupRegionName& other) const; }; CachedTimeRegion() = default; @@ -95,6 +101,9 @@ namespace AZ virtual void SetProfilerEnabled(bool enabled) = 0; virtual bool IsProfilerEnabled() const = 0 ; + + //! Used by AZ_ATOM_PROFILE_DYNAMIC to create GroupRegionNames with known lifetimes. + virtual const CachedTimeRegion::GroupRegionName& InsertDynamicName(const char* groupName, const AZStd::string& regionName) = 0; }; } // namespace RPI @@ -120,3 +129,10 @@ namespace AZ #define AZ_ATOM_PROFILE_FUNCTION(groupName, regionName) \ AZ_TRACE_METHOD(); \ AZ_ATOM_PROFILE_TIME_GROUP_REGION(groupName, regionName) \ + +//! Macro that allows for region names to be submitted at runtime. Use sparingly - this acquires a lock and allocates new objects within a map. +#define AZ_ATOM_PROFILE_DYNAMIC(groupName, regionName) \ + static_assert(AZStd::is_convertible_v, "Runtime group names are not allowed, use a static string literal instead."); \ + const AZ::RHI::CachedTimeRegion::GroupRegionName& AZ_JOIN(groupRegionName, __LINE__) = \ + AZ::RHI::CpuProfiler::Get()->InsertDynamicName(groupName, regionName); \ + AZ::RHI::TimeRegion AZ_JOIN(timeRegion, __LINE__)(&AZ_JOIN(groupRegionName, __LINE__)); diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfilerImpl.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfilerImpl.h index 7d3b0c5b81..2e4ca67db8 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfilerImpl.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfilerImpl.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -114,9 +115,11 @@ namespace AZ bool IsContinuousCaptureInProgress() const final override; void SetProfilerEnabled(bool enabled) final override; bool IsProfilerEnabled() const final override; + const CachedTimeRegion::GroupRegionName& InsertDynamicName(const char* groupName, const AZStd::string& regionName) final override; private: static constexpr AZStd::size_t MaxFramesToSave = 2 * 60 * 120; // 2 minutes of 120fps + static constexpr AZStd::size_t MaxRegionStringPoolSize = 16384; // Max amount of unique strings to save in the pool before throwing warnings. // Lazily create and register the local thread data void RegisterThreadStorage(); @@ -129,6 +132,15 @@ namespace AZ AZStd::vector, AZ::OSStdAllocator> m_registeredThreads; AZStd::mutex m_threadRegisterMutex; + // Pool for GroupRegionNames that are generated at runtime through AZ_ATOM_PROFILE_DYNAMIC. Each unique + // combination of group name and region name submitted will be stored in this pool to emulate static lifetime. + AZStd::unordered_set m_dynamicGroupRegionNamePool; + + // String pool for storing region names submitted at runtime. Each call to AZ_ATOM_PROFILE_DYNAMIC will either construct + // a string in this pool or use an already-existing entry. + AZStd::unordered_set m_regionNameStringPool; + AZStd::mutex m_dynamicNameMutex; + // Thread local storage, gets lazily allocated when a thread is created static thread_local CpuTimingLocalStorage* ms_threadLocalStorage; diff --git a/Gems/Atom/RHI/Code/Source/RHI/CpuProfilerImpl.cpp b/Gems/Atom/RHI/Code/Source/RHI/CpuProfilerImpl.cpp index cdfd4ac469..d41b5d656e 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/CpuProfilerImpl.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/CpuProfilerImpl.cpp @@ -74,6 +74,20 @@ namespace AZ { } + AZStd::size_t CachedTimeRegion::GroupRegionName::Hash::operator()(const CachedTimeRegion::GroupRegionName& name) const + { + AZStd::size_t seed = 0; + AZStd::hash_combine(seed, name.m_groupName); + AZStd::hash_combine(seed, name.m_regionName); + return seed; + } + + bool CachedTimeRegion::GroupRegionName::operator==(const GroupRegionName& other) const + { + return (m_groupName == other.m_groupName) && (m_regionName == other.m_regionName); + } + + // --- CpuProfilerImpl --- void CpuProfilerImpl::Init() @@ -218,6 +232,19 @@ namespace AZ return m_enabled; } + const CachedTimeRegion::GroupRegionName& CpuProfilerImpl::InsertDynamicName(const char* groupName, const AZStd::string& regionName) + { + AZStd::scoped_lock lock(m_dynamicNameMutex); + AZ_Warning("CpuProfiler", m_regionNameStringPool.size() < MaxRegionStringPoolSize, + "Stored dynamic region names are accumulating. Consider removing a AZ_ATOM_PROFILE_DYNAMIC invocation."); + auto [regionNameItr, wasRegionInserted] = m_regionNameStringPool.insert(regionName); + + CachedTimeRegion::GroupRegionName newGroupRegionName(groupName, regionNameItr->c_str()); + auto [groupRegionNameItr, wasGroupRegionInserted] = m_dynamicGroupRegionNamePool.insert(newGroupRegionName); + + return *groupRegionNameItr; + } + void CpuProfilerImpl::OnSystemTick() { if (!m_enabled)