Browse Source

Add superluminal, Tracy and Optick profilers integration as Gems (#834)

* Add superluminal profiler integration

Signed-off-by: guillaume-haerinck <[email protected]>

* Add support for tracy profiler

Signed-off-by: guillaume-haerinck <[email protected]>

* add basic optick support and clang-format everything

Signed-off-by: guillaume-haerinck <[email protected]>

* Move optick into 3rd party folder

Signed-off-by: guillaume-haerinck <[email protected]>

* Move optick into 3rd party folder

Signed-off-by: guillaume-haerinck <[email protected]>

* Review feedbacks

Signed-off-by: guillaume-haerinck <[email protected]>

* Superluminal profiler cmake message

Signed-off-by: guillaume-haerinck <[email protected]>

---------

Signed-off-by: guillaume-haerinck <[email protected]>
Guillaume Haerinck 2 months ago
parent
commit
1b25a21923
34 changed files with 1522 additions and 0 deletions
  1. 11 0
      Gems/ExternalProfilers/OptickProfiler/CMakeLists.txt
  2. 83 0
      Gems/ExternalProfilers/OptickProfiler/Code/CMakeLists.txt
  3. 129 0
      Gems/ExternalProfilers/OptickProfiler/Code/Source/OptickProfilerEventForwarder.cpp
  4. 70 0
      Gems/ExternalProfilers/OptickProfiler/Code/Source/OptickProfilerEventForwarder.h
  5. 51 0
      Gems/ExternalProfilers/OptickProfiler/Code/Source/ProfilerModule.cpp
  6. 114 0
      Gems/ExternalProfilers/OptickProfiler/Code/Source/ProfilerSystemComponent.cpp
  7. 54 0
      Gems/ExternalProfilers/OptickProfiler/Code/Source/ProfilerSystemComponent.h
  8. 28 0
      Gems/ExternalProfilers/OptickProfiler/Code/optick.patch
  9. 14 0
      Gems/ExternalProfilers/OptickProfiler/Code/optickprofiler_files.cmake
  10. 11 0
      Gems/ExternalProfilers/OptickProfiler/Code/optickprofiler_shared_files.cmake
  11. 23 0
      Gems/ExternalProfilers/OptickProfiler/gem.json
  12. 3 0
      Gems/ExternalProfilers/OptickProfiler/preview.png
  13. 11 0
      Gems/ExternalProfilers/SuperluminalProfiler/CMakeLists.txt
  14. 66 0
      Gems/ExternalProfilers/SuperluminalProfiler/Code/CMakeLists.txt
  15. 51 0
      Gems/ExternalProfilers/SuperluminalProfiler/Code/Source/ProfilerModule.cpp
  16. 101 0
      Gems/ExternalProfilers/SuperluminalProfiler/Code/Source/ProfilerSystemComponent.cpp
  17. 53 0
      Gems/ExternalProfilers/SuperluminalProfiler/Code/Source/ProfilerSystemComponent.h
  18. 56 0
      Gems/ExternalProfilers/SuperluminalProfiler/Code/Source/SuperluminalProfilerEventForwarder.cpp
  19. 49 0
      Gems/ExternalProfilers/SuperluminalProfiler/Code/Source/SuperluminalProfilerEventForwarder.h
  20. 14 0
      Gems/ExternalProfilers/SuperluminalProfiler/Code/superluminalprofiler_files.cmake
  21. 11 0
      Gems/ExternalProfilers/SuperluminalProfiler/Code/superluminalprofiler_shared_files.cmake
  22. 24 0
      Gems/ExternalProfilers/SuperluminalProfiler/gem.json
  23. 3 0
      Gems/ExternalProfilers/SuperluminalProfiler/preview.png
  24. 11 0
      Gems/ExternalProfilers/TracyProfiler/CMakeLists.txt
  25. 85 0
      Gems/ExternalProfilers/TracyProfiler/Code/CMakeLists.txt
  26. 51 0
      Gems/ExternalProfilers/TracyProfiler/Code/Source/ProfilerModule.cpp
  27. 101 0
      Gems/ExternalProfilers/TracyProfiler/Code/Source/ProfilerSystemComponent.cpp
  28. 53 0
      Gems/ExternalProfilers/TracyProfiler/Code/Source/ProfilerSystemComponent.h
  29. 83 0
      Gems/ExternalProfilers/TracyProfiler/Code/Source/TracyProfilerEventForwarder.cpp
  30. 56 0
      Gems/ExternalProfilers/TracyProfiler/Code/Source/TracyProfilerEventForwarder.h
  31. 14 0
      Gems/ExternalProfilers/TracyProfiler/Code/tracyprofiler_files.cmake
  32. 11 0
      Gems/ExternalProfilers/TracyProfiler/Code/tracyprofiler_shared_files.cmake
  33. 24 0
      Gems/ExternalProfilers/TracyProfiler/gem.json
  34. 3 0
      Gems/ExternalProfilers/TracyProfiler/preview.png

+ 11 - 0
Gems/ExternalProfilers/OptickProfiler/CMakeLists.txt

@@ -0,0 +1,11 @@
+#
+# 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
+#
+#
+
+o3de_gem_setup("OptickProfiler")
+
+add_subdirectory(Code)

+ 83 - 0
Gems/ExternalProfilers/OptickProfiler/Code/CMakeLists.txt

@@ -0,0 +1,83 @@
+#
+# 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
+#
+#
+
+# Expose some internal of Optick for better integration with 03DE
+set(OPTICK_GIT_PATCH "${CMAKE_CURRENT_SOURCE_DIR}/optick.patch")
+
+# Add Optick dependency using FetchContent
+include(FetchContent)
+FetchContent_Declare(
+    Optick
+    GIT_REPOSITORY "https://github.com/bombomby/optick.git"
+    GIT_TAG "8abd28dee1a4034c973a3d32cd1777118e72df7e" # version 1.4.0+
+    PATCH_COMMAND cmake -P "${LY_ROOT_FOLDER}/cmake/PatchIfNotAlreadyPatched.cmake" ${OPTICK_GIT_PATCH}
+)
+
+set(OPTICK_ENABLED ON)
+set(OPTICK_BUILD_GUI_APP OFF)
+set(OPTICK_INSTALL_TARGETS OFF)
+
+message(STATUS "OptickProfiler Gem uses https://github.com/bombomby/optick.git version 1.4.0 (License: MIT)")
+message(STATUS "      With patch: ${OPTICK_GIT_PATCH}")
+
+FetchContent_MakeAvailable(Optick)
+
+# Let's not clutter the root of any IDE folder structure with 3rd party dependencies
+# Setting the FOLDER makes it show up there in the solution build in VS and similarly
+# any other IDEs that organize in folders.
+set_target_properties(
+        OptickCore 
+    PROPERTIES 
+        FOLDER "3rdParty Dependencies"
+)
+
+ly_add_target(
+    NAME ${gem_name}.Static STATIC
+    NAMESPACE Gem
+    FILES_CMAKE
+        optickprofiler_files.cmake
+    INCLUDE_DIRECTORIES
+        PUBLIC
+            Include
+        PRIVATE
+            Source
+    BUILD_DEPENDENCIES
+        PUBLIC
+            AZ::AzCore
+            AZ::AzFramework
+            OptickCore
+)
+
+ly_add_target(
+    NAME ${gem_name} ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
+    NAMESPACE Gem
+    FILES_CMAKE
+        optickprofiler_shared_files.cmake
+    INCLUDE_DIRECTORIES
+        PUBLIC
+            Include
+        PRIVATE
+            Source
+    BUILD_DEPENDENCIES
+        PRIVATE
+            Gem::${gem_name}.Static
+)
+
+ly_add_source_properties(
+    SOURCES
+        Source/ProfilerModule.cpp
+    PROPERTY COMPILE_DEFINITIONS
+        VALUES
+            O3DE_GEM_NAME=${gem_name}
+            O3DE_GEM_VERSION=${gem_version})
+
+ly_create_alias(NAME ${gem_name}.Servers NAMESPACE Gem TARGETS Gem::${gem_name})
+ly_create_alias(NAME ${gem_name}.Builders NAMESPACE Gem TARGETS Gem::${gem_name})
+ly_create_alias(NAME ${gem_name}.Clients NAMESPACE Gem TARGETS Gem::${gem_name})
+ly_create_alias(NAME ${gem_name}.Unified NAMESPACE Gem TARGETS Gem::${gem_name})
+ly_create_alias(NAME ${gem_name}.Tools NAMESPACE Gem TARGETS Gem::${gem_name})

+ 129 - 0
Gems/ExternalProfilers/OptickProfiler/Code/Source/OptickProfilerEventForwarder.cpp

@@ -0,0 +1,129 @@
+/*
+ * 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 <OptickProfilerEventForwarder.h>
+
+#include <AzCore/Debug/ProfilerBus.h>
+#include <AzCore/Interface/Interface.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <AzCore/Utils/Utils.h>
+#include <AzCore/std/parallel/thread.h>
+#include <AzCore/std/time.h>
+#include <optick.h>
+
+namespace OptickProfiler
+{
+    thread_local Optick::EventStorage* OptickProfilerEventForwarder::m_pOptickStorage = nullptr;
+
+    bool OnOptickStateChanged(Optick::State::Type state)
+    {
+        switch (state)
+        {
+        case Optick::State::DUMP_CAPTURE:
+            {
+                auto projectName = AZ::Utils::GetProjectName();
+                Optick::AttachSummary("Project", projectName.c_str());
+            }
+            break;
+        }
+        return true;
+    }
+
+    void OptickProfilerEventForwarder::Init()
+    {
+        AZ::Interface<AZ::Debug::Profiler>::Register(this);
+        AZ::TickBus::Handler::BusConnect();
+        AZStd::ThreadEventBus::Handler::BusConnect();
+
+        Optick::SetStateChangedCallback(OnOptickStateChanged);
+        m_pOptickFrameTag = Optick::EventDescription::Create("Frame", "", 0);
+        m_pOptickStorage = Optick::RegisterStorage("Main Thread", AZStd::this_thread::get_id().m_id);
+
+        m_initialized = true;
+    }
+
+    void OptickProfilerEventForwarder::Shutdown()
+    {
+        if (!m_initialized)
+        {
+            return;
+        }
+
+        // When this call is made, no more thread profiling calls can be performed anymore
+        AZ::Interface<AZ::Debug::Profiler>::Unregister(this);
+
+        // Wait for the remaining threads that might still be processing its profiling calls
+        AZStd::unique_lock<AZStd::shared_mutex> shutdownLock(m_shutdownMutex);
+
+        AZStd::ThreadEventBus::Handler::BusDisconnect();
+        AZ::TickBus::Handler::BusDisconnect();
+
+        Optick::StopCapture();
+        Optick::Shutdown();
+    }
+
+    void OptickProfilerEventForwarder::BeginRegion(
+        [[maybe_unused]] const AZ::Debug::Budget* budget, [[maybe_unused]] const char* eventName, ...)
+    {
+        if (!m_pOptickStorage && m_threadIdToNameMutex.try_lock_shared())
+        {
+            AZStd::native_thread_id_type id = AZStd::this_thread::get_id().m_id;
+            const AZStd::string& name = m_threadIdToName.contains(id) ? m_threadIdToName.at(id) : "";
+
+            m_pOptickStorage = Optick::RegisterStorage(name.c_str(), id);
+            m_threadIdToNameMutex.unlock_shared();
+        }
+
+        // Try to lock here, the shutdownMutex will only be contested when the CpuProfiler is shutting down.
+        if (m_shutdownMutex.try_lock_shared())
+        {
+            Optick::EventDescription* pDesc = Optick::EventDescription::CreateShared(eventName);
+            Optick::Event::Push(m_pOptickStorage, pDesc, Optick::GetHighPrecisionTime());
+            m_shutdownMutex.unlock_shared();
+        }
+    }
+
+    void OptickProfilerEventForwarder::EndRegion([[maybe_unused]] const AZ::Debug::Budget* budget)
+    {
+        // Try to lock here, the shutdownMutex will only be contested when the CpuProfiler is shutting down.
+        if (m_shutdownMutex.try_lock_shared())
+        {
+            Optick::Event::Pop(m_pOptickStorage, Optick::GetHighPrecisionTime());
+            m_shutdownMutex.unlock_shared();
+        }
+    }
+
+    int OptickProfilerEventForwarder::GetTickOrder()
+    {
+        return AZ::ComponentTickBus::TICK_FIRST;
+    }
+
+    void OptickProfilerEventForwarder::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint timePoint)
+    {
+        const int64_t frameTick = Optick::GetHighPrecisionTime();
+
+        Optick::Event::Pop(m_pOptickStorage, frameTick);
+        Optick::EndFrame(Optick::FrameType::CPU);
+
+        Optick::Update();
+
+        const uint32_t frameNumber = Optick::BeginFrame(Optick::FrameType::CPU, frameTick);
+        Optick::Event::Push(m_pOptickStorage, Optick::GetFrameDescription(), frameTick);
+        Optick::AttachTag(m_pOptickStorage, *m_pOptickFrameTag, frameNumber, frameTick);
+    }
+
+    void OptickProfilerEventForwarder::OnThreadEnter(const AZStd::thread::id& id, const AZStd::thread_desc* desc)
+    {
+        AZStd::unique_lock<AZStd::shared_mutex> lock(m_threadIdToNameMutex);
+        m_threadIdToName[id.m_id] = desc->m_name;
+    }
+
+    void OptickProfilerEventForwarder::OnThreadExit([[maybe_unused]] const AZStd::thread::id& id)
+    {
+    }
+} // namespace OptickProfiler

+ 70 - 0
Gems/ExternalProfilers/OptickProfiler/Code/Source/OptickProfilerEventForwarder.h

@@ -0,0 +1,70 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <AzCore/Component/TickBus.h>
+#include <AzCore/Debug/Profiler.h>
+#include <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/Name/Name.h>
+#include <AzCore/RTTI/RTTI.h>
+#include <AzCore/std/containers/unordered_map.h>
+#include <AzCore/std/parallel/mutex.h>
+#include <AzCore/std/parallel/shared_mutex.h>
+#include <AzCore/std/parallel/threadbus.h>
+
+namespace Optick
+{
+    struct EventDescription;
+    struct EventStorage;
+} // namespace Optick
+
+namespace OptickProfiler
+{
+    //! Listen to 03DE frame/profiling events and forward them to the Optick profiling library
+    class OptickProfilerEventForwarder final
+        : public AZ::Debug::Profiler
+        , public AZ::TickBus::Handler
+        , public AZStd::ThreadEventBus::Handler
+    {
+    public:
+        AZ_RTTI(OptickProfilerEventForwarder, "{E4076EA4-EF44-499A-9750-37B623BBBF7C}", AZ::Debug::Profiler);
+        AZ_CLASS_ALLOCATOR(OptickProfilerEventForwarder, AZ::SystemAllocator);
+
+        OptickProfilerEventForwarder() = default;
+        ~OptickProfilerEventForwarder() = default;
+
+        //! Registers/un-registers the AZ::Debug::Profiler instance to the interface
+        void Init();
+        void Shutdown();
+
+        //! AZ::Debug::Profiler overrides
+        void BeginRegion(const AZ::Debug::Budget* budget, const char* eventName, ...) final override;
+        void EndRegion(const AZ::Debug::Budget* budget) final override;
+
+        //! AZ::TickBus::Handler overrides
+        int GetTickOrder() final override;
+        void OnTick(float deltaTime, AZ::ScriptTimePoint timePoint) final override;
+
+        //! AZStd::ThreadEventBus::Handler overrides
+        virtual void OnThreadEnter(const AZStd::thread::id& id, const AZStd::thread_desc* desc) final override;
+        virtual void OnThreadExit(const AZStd::thread::id& id) final override;
+
+    private:
+        // This lock will only be contested when the OptickProfilerEventForwarder's Shutdown() method has been called
+        AZStd::shared_mutex m_shutdownMutex;
+
+        AZStd::shared_mutex m_threadIdToNameMutex;
+        AZStd::unordered_map<AZStd::native_thread_id_type, AZStd::string> m_threadIdToName;
+
+        static thread_local Optick::EventStorage* m_pOptickStorage;
+        Optick::EventDescription* m_pOptickFrameTag = nullptr;
+
+        bool m_initialized = false;
+    };
+} // namespace OptickProfiler

+ 51 - 0
Gems/ExternalProfilers/OptickProfiler/Code/Source/ProfilerModule.cpp

@@ -0,0 +1,51 @@
+/*
+ * 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 <ProfilerSystemComponent.h>
+
+#include <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/Module/Module.h>
+
+namespace OptickProfiler
+{
+    class ProfilerModule : public AZ::Module
+    {
+    public:
+        AZ_RTTI(ProfilerModule, "{189570C0-0E0E-4826-8AEC-DCE972CFC9B2}", AZ::Module);
+        AZ_CLASS_ALLOCATOR(ProfilerModule, AZ::SystemAllocator);
+
+        ProfilerModule()
+        {
+            // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
+            // Add ALL components descriptors associated with this gem to m_descriptors.
+            // This will associate the AzTypeInfo information for the components with the the SerializeContext, BehaviorContext and
+            // EditContext. This happens through the [MyComponent]::Reflect() function.
+            m_descriptors.insert(
+                m_descriptors.end(),
+                {
+                    ProfilerSystemComponent::CreateDescriptor(),
+                });
+        }
+
+        /**
+         * Add required SystemComponents to the SystemEntity.
+         */
+        AZ::ComponentTypeList GetRequiredSystemComponents() const override
+        {
+            return AZ::ComponentTypeList{
+                azrtti_typeid<ProfilerSystemComponent>(),
+            };
+        }
+    };
+} // namespace OptickProfiler
+
+#if defined(O3DE_GEM_NAME)
+AZ_DECLARE_MODULE_CLASS(AZ_JOIN(Gem_, O3DE_GEM_NAME), OptickProfiler::ProfilerModule)
+#else
+AZ_DECLARE_MODULE_CLASS(Gem_OptickProfiler, OptickProfiler::ProfilerModule)
+#endif

+ 114 - 0
Gems/ExternalProfilers/OptickProfiler/Code/Source/ProfilerSystemComponent.cpp

@@ -0,0 +1,114 @@
+/*
+ * 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 "ProfilerSystemComponent.h"
+
+#include <AzCore/IO/FileIO.h>
+#include <AzCore/RTTI/BehaviorContext.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/EditContextConstants.inl>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <optick.h>
+
+namespace OptickProfiler
+{
+    static constexpr AZ::Crc32 profilerServiceCrc = AZ_CRC_CE("ProfilerService");
+
+    void ProfilerSystemComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serialize->Class<ProfilerSystemComponent, AZ::Component>()->Version(0);
+        }
+    }
+
+    void ProfilerSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+    {
+        provided.push_back(profilerServiceCrc);
+    }
+
+    void ProfilerSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+    {
+        incompatible.push_back(profilerServiceCrc);
+    }
+
+    void ProfilerSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
+    {
+    }
+
+    void ProfilerSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
+    {
+    }
+
+    ProfilerSystemComponent::ProfilerSystemComponent()
+    {
+        if (AZ::Debug::ProfilerSystemInterface::Get() == nullptr)
+        {
+            AZ::Debug::ProfilerSystemInterface::Register(this);
+        }
+    }
+
+    ProfilerSystemComponent::~ProfilerSystemComponent()
+    {
+        if (AZ::Debug::ProfilerSystemInterface::Get() == this)
+        {
+            AZ::Debug::ProfilerSystemInterface::Unregister(this);
+        }
+    }
+
+    void ProfilerSystemComponent::Activate()
+    {
+        m_eventForwarder.Init();
+    }
+
+    void ProfilerSystemComponent::Deactivate()
+    {
+        m_eventForwarder.Shutdown();
+    }
+
+    bool ProfilerSystemComponent::IsActive() const
+    {
+        return false;
+    }
+
+    void ProfilerSystemComponent::SetActive([[maybe_unused]] bool enabled)
+    {
+    }
+
+    bool ProfilerSystemComponent::CaptureFrame([[maybe_unused]] const AZStd::string& outputFilePath)
+    {
+        return false;
+    }
+
+    bool ProfilerSystemComponent::StartCapture(AZStd::string outputFilePath)
+    {
+        if (!m_cpuCaptureInProgress && Optick::StartCapture())
+        {
+            m_captureFile = AZStd::move(outputFilePath);
+            m_cpuCaptureInProgress = true;
+            return true;
+        }
+        return false;
+    }
+
+    bool ProfilerSystemComponent::EndCapture()
+    {
+        if (m_cpuCaptureInProgress && Optick::StopCapture())
+        {
+            m_cpuCaptureInProgress = false;
+            return Optick::SaveCapture(m_captureFile.c_str());
+        }
+        return false;
+    }
+
+    bool ProfilerSystemComponent::IsCaptureInProgress() const
+    {
+        return m_cpuCaptureInProgress;
+    }
+
+} // namespace OptickProfiler

+ 54 - 0
Gems/ExternalProfilers/OptickProfiler/Code/Source/ProfilerSystemComponent.h

@@ -0,0 +1,54 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include "OptickProfilerEventForwarder.h"
+
+#include <AzCore/Component/Component.h>
+#include <AzCore/Debug/ProfilerBus.h>
+
+namespace OptickProfiler
+{
+    class ProfilerSystemComponent
+        : public AZ::Component
+        , protected AZ::Debug::ProfilerRequests
+    {
+    public:
+        AZ_COMPONENT(ProfilerSystemComponent, "{E140D972-C1C0-44A7-A563-9F973944A8A1}");
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
+        static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
+        static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
+        static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
+
+        ProfilerSystemComponent();
+        ~ProfilerSystemComponent();
+
+    protected:
+        // AZ::Component interface implementation
+        void Activate() override;
+        void Deactivate() override;
+
+        // ProfilerRequests interface implementation
+        bool IsActive() const override;
+        void SetActive(bool active) override;
+        bool CaptureFrame(const AZStd::string& outputFilePath) override;
+        bool StartCapture(AZStd::string outputFilePath) override;
+        bool EndCapture() override;
+        bool IsCaptureInProgress() const override;
+
+    private:
+        AZStd::string m_captureFile;
+        AZStd::atomic_bool m_cpuCaptureInProgress{ false };
+        OptickProfilerEventForwarder m_eventForwarder;
+    };
+
+} // namespace OptickProfiler

+ 28 - 0
Gems/ExternalProfilers/OptickProfiler/Code/optick.patch

@@ -0,0 +1,28 @@
+diff --git a/src/optick.h b/src/optick.h
+index 96b28b9..8782d73 100644
+--- a/src/optick.h
++++ b/src/optick.h
+@@ -787,6 +787,7 @@ struct OPTICK_API GPUContextScope
+ };
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ OPTICK_API const EventDescription* GetFrameDescription(FrameType::Type frame = FrameType::CPU);
++OPTICK_API void AttachTag(EventStorage* storage, const EventDescription& description, uint32_t val, int64_t timestamp);
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ typedef void* (*AllocateFn)(size_t);
+ typedef void  (*DeallocateFn)(void*);
+diff --git a/src/optick_core.cpp b/src/optick_core.cpp
+index a992c4f..b5b3537 100644
+--- a/src/optick_core.cpp
++++ b/src/optick_core.cpp
+@@ -1823,6 +1823,11 @@ OPTICK_API const EventDescription* GetFrameDescription(FrameType::Type frame)
+ 	return Core::Get().GetFrameDescription(frame);
+ }
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
++OPTICK_API void AttachTag(EventStorage* storage, const EventDescription& description, uint32_t val, int64_t timestamp)
++{
++    storage->tagU32Buffer.Add(TagU32(description, val, timestamp));
++}
++////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ OPTICK_API void SetAllocator(AllocateFn allocateFn, DeallocateFn deallocateFn, InitThreadCb initThreadCb)
+ {
+ 	Memory::SetAllocator(allocateFn, deallocateFn, initThreadCb);

+ 14 - 0
Gems/ExternalProfilers/OptickProfiler/Code/optickprofiler_files.cmake

@@ -0,0 +1,14 @@
+#
+# 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
+#
+#
+
+set(FILES
+    Source/OptickProfilerEventForwarder.h
+    Source/OptickProfilerEventForwarder.cpp
+    Source/ProfilerSystemComponent.cpp
+    Source/ProfilerSystemComponent.h
+)

+ 11 - 0
Gems/ExternalProfilers/OptickProfiler/Code/optickprofiler_shared_files.cmake

@@ -0,0 +1,11 @@
+#
+# 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
+#
+#
+
+set(FILES
+    Source/ProfilerModule.cpp
+)

+ 23 - 0
Gems/ExternalProfilers/OptickProfiler/gem.json

@@ -0,0 +1,23 @@
+{
+    "gem_name": "OptickProfiler",
+    "version": "0.0.0",
+    "display_name": "Optick Profiler",
+    "license": "Apache-2.0 Or MIT",
+    "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
+    "origin": "O3DE Extras",
+    "origin_url": "https://github.com/o3de/o3de-extras/development/Gems/ExternalProfilers/OptickProfiler",
+    "type": "Code",
+    "summary": "Implements the O3DE profiler tags to be used by the Optick third party profiler",
+    "canonical_tags": [
+        "Gem",
+        "Tools"
+    ],
+    "user_tags": [
+        "Profiler"
+    ],
+    "icon_path": "preview.png",
+    "requirements": "Users will need to download Optick GUI from the <a href='https://github.com/bombomby/optick/releases'>Github release</a>.",
+    "dependencies": [
+    ],
+    "provided_unique_service": "Profiler"
+}

+ 3 - 0
Gems/ExternalProfilers/OptickProfiler/preview.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b9aceaec5ac0ed223a91709a95b6679f498ea9ee91c47228dcd2af49bed805c2
+size 3343

+ 11 - 0
Gems/ExternalProfilers/SuperluminalProfiler/CMakeLists.txt

@@ -0,0 +1,11 @@
+#
+# 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
+#
+#
+
+o3de_gem_setup("SuperluminalProfiler")
+
+add_subdirectory(Code)

+ 66 - 0
Gems/ExternalProfilers/SuperluminalProfiler/Code/CMakeLists.txt

@@ -0,0 +1,66 @@
+#
+# 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
+#
+#
+
+set(SUPERLUMINAL_API_PATH "C:/Program Files/Superluminal/Performance/API" CACHE PATH "Path to the Superluminal 3rd party profiler API folder.")
+list(APPEND CMAKE_MODULE_PATH ${SUPERLUMINAL_API_PATH})
+
+if (NOT EXISTS ${SUPERLUMINAL_API_PATH})
+    message(FATAL_ERROR "Superluminal profiler not found. Either update SUPERLUMINAL_API_PATH cmake variable or download Superluminal from https://superluminal.eu/")
+else()
+    message(STATUS "SuperluminalProfiler Gem uses ${SUPERLUMINAL_API_PATH} (License: Commercial)")
+endif()
+
+find_package(SuperluminalAPI REQUIRED)
+
+ly_add_target(
+    NAME ${gem_name}.Static STATIC
+    NAMESPACE Gem
+    FILES_CMAKE
+        superluminalprofiler_files.cmake
+    INCLUDE_DIRECTORIES
+        PUBLIC
+            Include
+        PRIVATE
+            Source
+            ${SuperluminalAPI_INCLUDE_DIRS}
+    BUILD_DEPENDENCIES
+        PUBLIC
+            AZ::AzCore
+            AZ::AzFramework
+        PRIVATE
+            ${SuperluminalAPI_LIBS_RELEASE}
+)
+
+ly_add_target(
+    NAME ${gem_name} ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
+    NAMESPACE Gem
+    FILES_CMAKE
+        superluminalprofiler_shared_files.cmake
+    INCLUDE_DIRECTORIES
+        PUBLIC
+            Include
+        PRIVATE
+            Source
+    BUILD_DEPENDENCIES
+        PRIVATE
+            Gem::${gem_name}.Static
+)
+
+ly_add_source_properties(
+    SOURCES
+        Source/ProfilerModule.cpp
+    PROPERTY COMPILE_DEFINITIONS
+        VALUES
+            O3DE_GEM_NAME=${gem_name}
+            O3DE_GEM_VERSION=${gem_version})
+
+ly_create_alias(NAME ${gem_name}.Servers NAMESPACE Gem TARGETS Gem::${gem_name})
+ly_create_alias(NAME ${gem_name}.Builders NAMESPACE Gem TARGETS Gem::${gem_name})
+ly_create_alias(NAME ${gem_name}.Clients NAMESPACE Gem TARGETS Gem::${gem_name})
+ly_create_alias(NAME ${gem_name}.Unified NAMESPACE Gem TARGETS Gem::${gem_name})
+ly_create_alias(NAME ${gem_name}.Tools NAMESPACE Gem TARGETS Gem::${gem_name})

+ 51 - 0
Gems/ExternalProfilers/SuperluminalProfiler/Code/Source/ProfilerModule.cpp

@@ -0,0 +1,51 @@
+/*
+ * 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 <ProfilerSystemComponent.h>
+
+#include <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/Module/Module.h>
+
+namespace SuperluminalProfiler
+{
+    class ProfilerModule : public AZ::Module
+    {
+    public:
+        AZ_RTTI(ProfilerModule, "{7C666AFB-E699-42FB-8F32-DAEE5A62CD01}", AZ::Module);
+        AZ_CLASS_ALLOCATOR(ProfilerModule, AZ::SystemAllocator);
+
+        ProfilerModule()
+        {
+            // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
+            // Add ALL components descriptors associated with this gem to m_descriptors.
+            // This will associate the AzTypeInfo information for the components with the the SerializeContext, BehaviorContext and
+            // EditContext. This happens through the [MyComponent]::Reflect() function.
+            m_descriptors.insert(
+                m_descriptors.end(),
+                {
+                    ProfilerSystemComponent::CreateDescriptor(),
+                });
+        }
+
+        /**
+         * Add required SystemComponents to the SystemEntity.
+         */
+        AZ::ComponentTypeList GetRequiredSystemComponents() const override
+        {
+            return AZ::ComponentTypeList{
+                azrtti_typeid<ProfilerSystemComponent>(),
+            };
+        }
+    };
+} // namespace SuperluminalProfiler
+
+#if defined(O3DE_GEM_NAME)
+AZ_DECLARE_MODULE_CLASS(AZ_JOIN(Gem_, O3DE_GEM_NAME), SuperluminalProfiler::ProfilerModule)
+#else
+AZ_DECLARE_MODULE_CLASS(Gem_SuperluminalProfiler, SuperluminalProfiler::ProfilerModule)
+#endif

+ 101 - 0
Gems/ExternalProfilers/SuperluminalProfiler/Code/Source/ProfilerSystemComponent.cpp

@@ -0,0 +1,101 @@
+/*
+ * 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 "ProfilerSystemComponent.h"
+
+#include <AzCore/RTTI/BehaviorContext.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/EditContextConstants.inl>
+#include <AzCore/Serialization/SerializeContext.h>
+
+namespace SuperluminalProfiler
+{
+    static constexpr AZ::Crc32 profilerServiceCrc = AZ_CRC_CE("ProfilerService");
+
+    void ProfilerSystemComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serialize->Class<ProfilerSystemComponent, AZ::Component>()->Version(0);
+        }
+    }
+
+    void ProfilerSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+    {
+        provided.push_back(profilerServiceCrc);
+    }
+
+    void ProfilerSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+    {
+        incompatible.push_back(profilerServiceCrc);
+    }
+
+    void ProfilerSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
+    {
+    }
+
+    void ProfilerSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
+    {
+    }
+
+    ProfilerSystemComponent::ProfilerSystemComponent()
+    {
+        if (AZ::Debug::ProfilerSystemInterface::Get() == nullptr)
+        {
+            AZ::Debug::ProfilerSystemInterface::Register(this);
+        }
+    }
+
+    ProfilerSystemComponent::~ProfilerSystemComponent()
+    {
+        if (AZ::Debug::ProfilerSystemInterface::Get() == this)
+        {
+            AZ::Debug::ProfilerSystemInterface::Unregister(this);
+        }
+    }
+
+    void ProfilerSystemComponent::Activate()
+    {
+        m_eventForwarder.Init();
+    }
+
+    void ProfilerSystemComponent::Deactivate()
+    {
+        m_eventForwarder.Shutdown();
+    }
+
+    bool ProfilerSystemComponent::IsActive() const
+    {
+        return false;
+    }
+
+    void ProfilerSystemComponent::SetActive([[maybe_unused]] bool enabled)
+    {
+    }
+
+    bool ProfilerSystemComponent::CaptureFrame([[maybe_unused]] const AZStd::string& outputFilePath)
+    {
+        return true;
+    }
+
+    bool ProfilerSystemComponent::StartCapture([[maybe_unused]] AZStd::string outputFilePath)
+    {
+        return true;
+    }
+
+    bool ProfilerSystemComponent::EndCapture()
+    {
+        return true;
+    }
+
+    bool ProfilerSystemComponent::IsCaptureInProgress() const
+    {
+        return false;
+    }
+
+} // namespace SuperluminalProfiler

+ 53 - 0
Gems/ExternalProfilers/SuperluminalProfiler/Code/Source/ProfilerSystemComponent.h

@@ -0,0 +1,53 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include "SuperluminalProfilerEventForwarder.h"
+
+#include <AzCore/Component/Component.h>
+#include <AzCore/Debug/ProfilerBus.h>
+
+namespace SuperluminalProfiler
+{
+    class ProfilerSystemComponent
+        : public AZ::Component
+        , protected AZ::Debug::ProfilerRequests
+    {
+    public:
+        AZ_COMPONENT(ProfilerSystemComponent, "{C920A0CC-A053-4A0E-8550-DC44FF03A2D1}");
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
+        static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
+        static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
+        static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
+
+        ProfilerSystemComponent();
+        ~ProfilerSystemComponent();
+
+    protected:
+        // AZ::Component interface implementation
+        void Activate() override;
+        void Deactivate() override;
+
+        // ProfilerRequests interface implementation
+        bool IsActive() const override;
+        void SetActive(bool active) override;
+        bool CaptureFrame(const AZStd::string& outputFilePath) override;
+        bool StartCapture(AZStd::string outputFilePath) override;
+        bool EndCapture() override;
+        bool IsCaptureInProgress() const override;
+
+    private:
+        AZStd::atomic_bool m_cpuCaptureInProgress{ false };
+        SuperluminalProfilerEventForwarder m_eventForwarder;
+    };
+
+} // namespace SuperluminalProfiler

+ 56 - 0
Gems/ExternalProfilers/SuperluminalProfiler/Code/Source/SuperluminalProfilerEventForwarder.cpp

@@ -0,0 +1,56 @@
+/*
+ * 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 <SuperluminalProfilerEventForwarder.h>
+
+#include <AzCore/Interface/Interface.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <Superluminal/PerformanceAPI.h>
+
+namespace SuperluminalProfiler
+{
+    void SuperluminalProfilerEventForwarder::Init()
+    {
+        AZ::Interface<AZ::Debug::Profiler>::Register(this);
+        m_initialized = true;
+    }
+
+    void SuperluminalProfilerEventForwarder::Shutdown()
+    {
+        if (!m_initialized)
+        {
+            return;
+        }
+
+        // When this call is made, no more thread profiling calls can be performed anymore
+        AZ::Interface<AZ::Debug::Profiler>::Unregister(this);
+
+        // Wait for the remaining threads that might still be processing its profiling calls
+        AZStd::unique_lock<AZStd::shared_mutex> shutdownLock(m_shutdownMutex);
+    }
+
+    void SuperluminalProfilerEventForwarder::BeginRegion(const AZ::Debug::Budget* budget, const char* eventName, ...)
+    {
+        // Try to lock here, the shutdownMutex will only be contested when the CpuProfiler is shutting down.
+        if (m_shutdownMutex.try_lock_shared())
+        {
+            PerformanceAPI_BeginEvent(eventName, budget->Name(), budget->Crc());
+            m_shutdownMutex.unlock_shared();
+        }
+    }
+
+    void SuperluminalProfilerEventForwarder::EndRegion([[maybe_unused]] const AZ::Debug::Budget* budget)
+    {
+        // Try to lock here, the shutdownMutex will only be contested when the CpuProfiler is shutting down.
+        if (m_shutdownMutex.try_lock_shared())
+        {
+            PerformanceAPI_EndEvent();
+            m_shutdownMutex.unlock_shared();
+        }
+    }
+} // namespace SuperluminalProfiler

+ 49 - 0
Gems/ExternalProfilers/SuperluminalProfiler/Code/Source/SuperluminalProfilerEventForwarder.h

@@ -0,0 +1,49 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <AzCore/Component/TickBus.h>
+#include <AzCore/Debug/Profiler.h>
+#include <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/Name/Name.h>
+#include <AzCore/RTTI/RTTI.h>
+#include <AzCore/std/parallel/mutex.h>
+#include <AzCore/std/parallel/shared_mutex.h>
+
+namespace SuperluminalProfiler
+{
+    //! Listen to 03DE frame/profiling events and forward them to the Superluminal profiling library
+    class SuperluminalProfilerEventForwarder final : public AZ::Debug::Profiler
+    {
+    public:
+        AZ_RTTI(SuperluminalProfilerEventForwarder, "{A05E7DC4-AB00-41BC-A739-8E58908CB84F}", AZ::Debug::Profiler);
+        AZ_CLASS_ALLOCATOR(SuperluminalProfilerEventForwarder, AZ::SystemAllocator);
+
+        SuperluminalProfilerEventForwarder() = default;
+        ~SuperluminalProfilerEventForwarder() = default;
+
+        //! Registers/un-registers the AZ::Debug::Profiler instance to the interface
+        void Init();
+        void Shutdown();
+
+        //! AZ::Debug::Profiler overrides...
+        void BeginRegion(const AZ::Debug::Budget* budget, const char* eventName, ...) final override;
+        void EndRegion(const AZ::Debug::Budget* budget) final override;
+
+        //! Check to see if a programmatic capture is currently in progress, implies
+        //! that the profiler is active if returns True.
+        bool IsContinuousCaptureInProgress() const;
+
+    private:
+        // This lock will only be contested when the SuperluminalProfilerEventForwarder's Shutdown() method has been called
+        AZStd::shared_mutex m_shutdownMutex;
+
+        bool m_initialized = false;
+    };
+} // namespace SuperluminalProfiler

+ 14 - 0
Gems/ExternalProfilers/SuperluminalProfiler/Code/superluminalprofiler_files.cmake

@@ -0,0 +1,14 @@
+#
+# 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
+#
+#
+
+set(FILES
+    Source/SuperluminalProfilerEventForwarder.h
+    Source/SuperluminalProfilerEventForwarder.cpp
+    Source/ProfilerSystemComponent.cpp
+    Source/ProfilerSystemComponent.h
+)

+ 11 - 0
Gems/ExternalProfilers/SuperluminalProfiler/Code/superluminalprofiler_shared_files.cmake

@@ -0,0 +1,11 @@
+#
+# 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
+#
+#
+
+set(FILES
+    Source/ProfilerModule.cpp
+)

+ 24 - 0
Gems/ExternalProfilers/SuperluminalProfiler/gem.json

@@ -0,0 +1,24 @@
+{
+    "gem_name": "SuperluminalProfiler",
+    "version": "0.0.0",
+    "display_name": "Superluminal Profiler",
+    "license": "Apache-2.0 Or MIT",
+    "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
+    "origin": "O3DE Extras",
+    "origin_url": "https://github.com/o3de/o3de-extras/development/Gems/ExternalProfilers/SuperluminalProfiler",
+    "type": "Code",
+    "summary": "Implements the O3DE profiler tags to be used by the superluminal third party profiler",
+    "canonical_tags": [
+        "Gem",
+        "Tools"
+    ],
+    "user_tags": [
+        "Profiler"
+    ],
+    "icon_path": "preview.png",
+    "requirements": "Users will need to download Superluminal from the <a href='https://superluminal.eu/'>Superluminal Web Site</a>.",
+    "documentation_url": "https://www.superluminal.eu/docs/documentation.html",
+    "dependencies": [
+    ],
+    "provided_unique_service": "Profiler"
+}

+ 3 - 0
Gems/ExternalProfilers/SuperluminalProfiler/preview.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e94d306395c37690f4ff8cb71ebde477e7446e0b0c23e15f23c0a0d0c9ea221b
+size 3286

+ 11 - 0
Gems/ExternalProfilers/TracyProfiler/CMakeLists.txt

@@ -0,0 +1,11 @@
+#
+# 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
+#
+#
+
+o3de_gem_setup("TracyProfiler")
+
+add_subdirectory(Code)

+ 85 - 0
Gems/ExternalProfilers/TracyProfiler/Code/CMakeLists.txt

@@ -0,0 +1,85 @@
+#
+# 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
+#
+#
+
+# Add Tracy dependency using FetchContent
+include(FetchContent)
+FetchContent_Declare(
+    Tracy
+    GIT_REPOSITORY "https://github.com/wolfpld/tracy.git"
+    GIT_TAG "5d542dc09f3d9378d005092a4ad446bd405f819a" # version 0.11.1
+)
+
+set(TRACY_ENABLE ON)
+set(TRACY_CALLSTACK ON)
+set(TRACY_ON_DEMAND ON)
+
+FetchContent_MakeAvailable(Tracy)
+
+message(STATUS "TracyProfiler gem uses https://github.com/wolfpld/tracy version 0.11.1 (License: BSD-3-Clause)")
+
+# Let's not clutter the root of any IDE folder structure with 3rd party dependencies
+# Setting the FOLDER makes it show up there in the solution build in VS and similarly
+# any other IDEs that organize in folders.
+set_target_properties(
+        TracyClient 
+    PROPERTIES 
+        FOLDER "3rdParty Dependencies"
+)
+
+ly_add_target(
+    NAME ${gem_name}.Static STATIC
+    NAMESPACE Gem
+    FILES_CMAKE
+        tracyprofiler_files.cmake
+    INCLUDE_DIRECTORIES
+        PUBLIC
+            Include
+        PRIVATE
+            Source
+            ${Tracy_SOURCE_DIR}/public/tracy
+    COMPILE_DEFINITIONS
+        PRIVATE
+            TRACY_ENABLE
+    BUILD_DEPENDENCIES
+        PUBLIC
+            AZ::AzCore
+            AZ::AzFramework
+            TracyClient
+)
+
+ly_add_target(
+    NAME ${gem_name} ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
+    NAMESPACE Gem
+    FILES_CMAKE
+        tracyprofiler_shared_files.cmake
+    INCLUDE_DIRECTORIES
+        PUBLIC
+            Include
+        PRIVATE
+            Source
+    COMPILE_DEFINITIONS
+        PRIVATE
+            TRACY_ENABLE
+    BUILD_DEPENDENCIES
+        PRIVATE
+            Gem::${gem_name}.Static
+)
+
+ly_add_source_properties(
+    SOURCES
+        Source/ProfilerModule.cpp
+    PROPERTY COMPILE_DEFINITIONS
+        VALUES
+            O3DE_GEM_NAME=${gem_name}
+            O3DE_GEM_VERSION=${gem_version})
+
+ly_create_alias(NAME ${gem_name}.Servers NAMESPACE Gem TARGETS Gem::${gem_name})
+ly_create_alias(NAME ${gem_name}.Builders NAMESPACE Gem TARGETS Gem::${gem_name})
+ly_create_alias(NAME ${gem_name}.Clients NAMESPACE Gem TARGETS Gem::${gem_name})
+ly_create_alias(NAME ${gem_name}.Unified NAMESPACE Gem TARGETS Gem::${gem_name})
+ly_create_alias(NAME ${gem_name}.Tools NAMESPACE Gem TARGETS Gem::${gem_name})

+ 51 - 0
Gems/ExternalProfilers/TracyProfiler/Code/Source/ProfilerModule.cpp

@@ -0,0 +1,51 @@
+/*
+ * 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 <ProfilerSystemComponent.h>
+
+#include <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/Module/Module.h>
+
+namespace TracyProfiler
+{
+    class ProfilerModule : public AZ::Module
+    {
+    public:
+        AZ_RTTI(ProfilerModule, "{BCE29245-571B-44C2-94E7-84D7E06F5A1F}", AZ::Module);
+        AZ_CLASS_ALLOCATOR(ProfilerModule, AZ::SystemAllocator);
+
+        ProfilerModule()
+        {
+            // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
+            // Add ALL components descriptors associated with this gem to m_descriptors.
+            // This will associate the AzTypeInfo information for the components with the the SerializeContext, BehaviorContext and
+            // EditContext. This happens through the [MyComponent]::Reflect() function.
+            m_descriptors.insert(
+                m_descriptors.end(),
+                {
+                    ProfilerSystemComponent::CreateDescriptor(),
+                });
+        }
+
+        /**
+         * Add required SystemComponents to the SystemEntity.
+         */
+        AZ::ComponentTypeList GetRequiredSystemComponents() const override
+        {
+            return AZ::ComponentTypeList{
+                azrtti_typeid<ProfilerSystemComponent>(),
+            };
+        }
+    };
+} // namespace TracyProfiler
+
+#if defined(O3DE_GEM_NAME)
+AZ_DECLARE_MODULE_CLASS(AZ_JOIN(Gem_, O3DE_GEM_NAME), TracyProfiler::ProfilerModule)
+#else
+AZ_DECLARE_MODULE_CLASS(Gem_TracyProfiler, TracyProfiler::ProfilerModule)
+#endif

+ 101 - 0
Gems/ExternalProfilers/TracyProfiler/Code/Source/ProfilerSystemComponent.cpp

@@ -0,0 +1,101 @@
+/*
+ * 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 "ProfilerSystemComponent.h"
+
+#include <AzCore/RTTI/BehaviorContext.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/EditContextConstants.inl>
+#include <AzCore/Serialization/SerializeContext.h>
+
+namespace TracyProfiler
+{
+    static constexpr AZ::Crc32 profilerServiceCrc = AZ_CRC_CE("ProfilerService");
+
+    void ProfilerSystemComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serialize->Class<ProfilerSystemComponent, AZ::Component>()->Version(0);
+        }
+    }
+
+    void ProfilerSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+    {
+        provided.push_back(profilerServiceCrc);
+    }
+
+    void ProfilerSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+    {
+        incompatible.push_back(profilerServiceCrc);
+    }
+
+    void ProfilerSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
+    {
+    }
+
+    void ProfilerSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
+    {
+    }
+
+    ProfilerSystemComponent::ProfilerSystemComponent()
+    {
+        if (AZ::Debug::ProfilerSystemInterface::Get() == nullptr)
+        {
+            AZ::Debug::ProfilerSystemInterface::Register(this);
+        }
+    }
+
+    ProfilerSystemComponent::~ProfilerSystemComponent()
+    {
+        if (AZ::Debug::ProfilerSystemInterface::Get() == this)
+        {
+            AZ::Debug::ProfilerSystemInterface::Unregister(this);
+        }
+    }
+
+    void ProfilerSystemComponent::Activate()
+    {
+        m_eventForwarder.Init();
+    }
+
+    void ProfilerSystemComponent::Deactivate()
+    {
+        m_eventForwarder.Shutdown();
+    }
+
+    bool ProfilerSystemComponent::IsActive() const
+    {
+        return false;
+    }
+
+    void ProfilerSystemComponent::SetActive([[maybe_unused]] bool enabled)
+    {
+    }
+
+    bool ProfilerSystemComponent::CaptureFrame([[maybe_unused]] const AZStd::string& outputFilePath)
+    {
+        return true;
+    }
+
+    bool ProfilerSystemComponent::StartCapture([[maybe_unused]] AZStd::string outputFilePath)
+    {
+        return true;
+    }
+
+    bool ProfilerSystemComponent::EndCapture()
+    {
+        return true;
+    }
+
+    bool ProfilerSystemComponent::IsCaptureInProgress() const
+    {
+        return false;
+    }
+
+} // namespace TracyProfiler

+ 53 - 0
Gems/ExternalProfilers/TracyProfiler/Code/Source/ProfilerSystemComponent.h

@@ -0,0 +1,53 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include "TracyProfilerEventForwarder.h"
+
+#include <AzCore/Component/Component.h>
+#include <AzCore/Debug/ProfilerBus.h>
+
+namespace TracyProfiler
+{
+    class ProfilerSystemComponent
+        : public AZ::Component
+        , protected AZ::Debug::ProfilerRequests
+    {
+    public:
+        AZ_COMPONENT(ProfilerSystemComponent, "{839DAD12-4571-4484-B71F-2133A5BC1137}");
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
+        static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
+        static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
+        static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
+
+        ProfilerSystemComponent();
+        ~ProfilerSystemComponent();
+
+    protected:
+        // AZ::Component interface implementation
+        void Activate() override;
+        void Deactivate() override;
+
+        // ProfilerRequests interface implementation
+        bool IsActive() const override;
+        void SetActive(bool active) override;
+        bool CaptureFrame(const AZStd::string& outputFilePath) override;
+        bool StartCapture(AZStd::string outputFilePath) override;
+        bool EndCapture() override;
+        bool IsCaptureInProgress() const override;
+
+    private:
+        AZStd::atomic_bool m_cpuCaptureInProgress{ false };
+        TracyProfilerEventForwarder m_eventForwarder;
+    };
+
+} // namespace TracyProfiler

+ 83 - 0
Gems/ExternalProfilers/TracyProfiler/Code/Source/TracyProfilerEventForwarder.cpp

@@ -0,0 +1,83 @@
+/*
+ * 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 <TracyProfilerEventForwarder.h>
+
+#include <AzCore/Interface/Interface.h>
+#include <AzCore/Serialization/SerializeContext.h>
+
+namespace TracyProfiler
+{
+    thread_local TracyProfilerEventForwarder::EventIdStack TracyProfilerEventForwarder::ms_threadLocalStorage;
+
+    void TracyProfilerEventForwarder::Init()
+    {
+        AZ::Interface<AZ::Debug::Profiler>::Register(this);
+        AZ::TickBus::Handler::BusConnect();
+        m_initialized = true;
+    }
+
+    void TracyProfilerEventForwarder::Shutdown()
+    {
+        if (!m_initialized)
+        {
+            return;
+        }
+
+        // When this call is made, no more thread profiling calls can be performed anymore
+        AZ::Interface<AZ::Debug::Profiler>::Unregister(this);
+
+        // Wait for the remaining threads that might still be processing its profiling calls
+        AZStd::unique_lock<AZStd::shared_mutex> shutdownLock(m_shutdownMutex);
+
+        AZ::TickBus::Handler::BusDisconnect();
+    }
+
+    void TracyProfilerEventForwarder::BeginRegion(const AZ::Debug::Budget* budget, const char* eventName, ...)
+    {
+        // Try to lock here, the shutdownMutex will only be contested when the CpuProfiler is shutting down.
+        if (m_shutdownMutex.try_lock_shared())
+        {
+            // Do not use the macro as we don't want to use static storage (else events are wrong)
+            ms_threadLocalStorage.push_back({});
+            TracyCZoneCtx& ctx = ms_threadLocalStorage.back();
+            ctx = ___tracy_emit_zone_begin_alloc_callstack(
+                ___tracy_alloc_srcloc_name(
+                    __LINE__, __FILE__, strlen(__FILE__), __FUNCTION__, strlen(__FUNCTION__), eventName, strlen(eventName), budget->Crc()),
+                TRACY_CALLSTACK,
+                true);
+            TracyCZoneText(ctx, budget->Name(), 1);
+            m_shutdownMutex.unlock_shared();
+        }
+    }
+
+    void TracyProfilerEventForwarder::EndRegion([[maybe_unused]] const AZ::Debug::Budget* budget)
+    {
+        // Try to lock here, the shutdownMutex will only be contested when the CpuProfiler is shutting down.
+        if (m_shutdownMutex.try_lock_shared() && !ms_threadLocalStorage.empty())
+        {
+            auto& ctx = ms_threadLocalStorage.back();
+            TracyCZoneEnd(ctx);
+            ms_threadLocalStorage.pop_back();
+
+            m_shutdownMutex.unlock_shared();
+        }
+    }
+
+    int TracyProfilerEventForwarder::GetTickOrder()
+    {
+        return AZ::ComponentTickBus::TICK_LAST;
+    }
+
+    void TracyProfilerEventForwarder::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint timePoint)
+    {
+        // From Tracy documentation about FrameMark : "Ideally, that would be right after the swap buffers command"
+        TracyCFrameMark;
+    }
+
+} // namespace TracyProfiler

+ 56 - 0
Gems/ExternalProfilers/TracyProfiler/Code/Source/TracyProfilerEventForwarder.h

@@ -0,0 +1,56 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <AzCore/Component/TickBus.h>
+#include <AzCore/Debug/Profiler.h>
+#include <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/Name/Name.h>
+#include <AzCore/RTTI/RTTI.h>
+#include <AzCore/std/containers/vector.h>
+#include <AzCore/std/parallel/mutex.h>
+#include <AzCore/std/parallel/shared_mutex.h>
+#include <tracy/TracyC.h>
+
+namespace TracyProfiler
+{
+    //! Listen to 03DE frame/profiling events and forward them to the Tracy profiling library
+    class TracyProfilerEventForwarder final
+        : public AZ::Debug::Profiler
+        , public AZ::TickBus::Handler
+    {
+    public:
+        AZ_RTTI(TracyProfilerEventForwarder, "{9467E3F6-0581-4E46-A98A-F3C249FD7B24}", AZ::Debug::Profiler);
+        AZ_CLASS_ALLOCATOR(TracyProfilerEventForwarder, AZ::SystemAllocator);
+
+        TracyProfilerEventForwarder() = default;
+        ~TracyProfilerEventForwarder() = default;
+
+        //! Registers/un-registers the AZ::Debug::Profiler instance to the interface
+        void Init();
+        void Shutdown();
+
+        //! AZ::Debug::Profiler overrides...
+        void BeginRegion(const AZ::Debug::Budget* budget, const char* eventName, ...) final override;
+        void EndRegion(const AZ::Debug::Budget* budget) final override;
+
+        //! AZ::TickBus::Handler overrides
+        int GetTickOrder() override;
+        void OnTick(float deltaTime, AZ::ScriptTimePoint timePoint) override;
+
+    private:
+        using EventIdStack = AZStd::vector<TracyCZoneCtx>;
+        static thread_local EventIdStack ms_threadLocalStorage;
+
+        // This lock will only be contested when the TracyProfilerEventForwarder's Shutdown() method has been called
+        AZStd::shared_mutex m_shutdownMutex;
+
+        bool m_initialized = false;
+    };
+} // namespace TracyProfiler

+ 14 - 0
Gems/ExternalProfilers/TracyProfiler/Code/tracyprofiler_files.cmake

@@ -0,0 +1,14 @@
+#
+# 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
+#
+#
+
+set(FILES
+    Source/TracyProfilerEventForwarder.h
+    Source/TracyProfilerEventForwarder.cpp
+    Source/ProfilerSystemComponent.cpp
+    Source/ProfilerSystemComponent.h
+)

+ 11 - 0
Gems/ExternalProfilers/TracyProfiler/Code/tracyprofiler_shared_files.cmake

@@ -0,0 +1,11 @@
+#
+# 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
+#
+#
+
+set(FILES
+    Source/ProfilerModule.cpp
+)

+ 24 - 0
Gems/ExternalProfilers/TracyProfiler/gem.json

@@ -0,0 +1,24 @@
+{
+    "gem_name": "TracyProfiler",
+    "version": "0.0.0",
+    "display_name": "Tracy Profiler",
+    "license": "Apache-2.0 Or MIT",
+    "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
+    "origin": "O3DE Extras",
+    "origin_url": "https://github.com/o3de/o3de-extras/development/Gems/ExternalProfilers/TracyProfiler",
+    "type": "Code",
+    "summary": "Implements the O3DE profiler tags to be used by the Tracy third party profiler",
+    "canonical_tags": [
+        "Gem",
+        "Tools"
+    ],
+    "user_tags": [
+        "Profiler"
+    ],
+    "icon_path": "preview.png",
+    "requirements": "Users will need to download Tracy from the <a href='https://github.com/wolfpld/tracy/releases'>Github release</a>.",
+    "documentation_url": "https://github.com/wolfpld/tracy",
+    "dependencies": [
+    ],
+    "provided_unique_service": "Profiler"
+}

+ 3 - 0
Gems/ExternalProfilers/TracyProfiler/preview.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:577c064676b5b2b065697c98843dc2c4b8db56363548400d166aebaf14073c5f
+size 2486