Browse Source

Merge pull request #442 from aws-lumberyard-dev/cgalvan/gitflow_220509_atomsampleviewer_main

Merged `stabilization/2205` to `main`
Chris Galvan 3 years ago
parent
commit
fdef5021b9
100 changed files with 3944 additions and 2039 deletions
  1. 0 22
      AssetProcessorGamePlatformConfig.setreg
  2. 2 1
      CMakeLists.txt
  3. 4 5
      Gem/Code/Source/AssetLoadTestComponent.cpp
  4. 5 129
      Gem/Code/Source/AtomSampleViewerModule.cpp
  5. 11 4
      Gem/Code/Source/AtomSampleViewerSystemComponent.cpp
  6. 95 0
      Gem/Code/Source/Automation/PrecommitWizardSettings.h
  7. 354 8
      Gem/Code/Source/Automation/ScriptManager.cpp
  8. 22 3
      Gem/Code/Source/Automation/ScriptManager.h
  9. 478 362
      Gem/Code/Source/Automation/ScriptReporter.cpp
  10. 66 6
      Gem/Code/Source/Automation/ScriptReporter.h
  11. 1 0
      Gem/Code/Source/Automation/ScriptableImGui.h
  12. 1 0
      Gem/Code/Source/AuxGeomExampleComponent.cpp
  13. 2 2
      Gem/Code/Source/CheckerboardExampleComponent.cpp
  14. 0 5
      Gem/Code/Source/CheckerboardExampleComponent.h
  15. 34 27
      Gem/Code/Source/CommonSampleComponentBase.cpp
  16. 126 125
      Gem/Code/Source/CommonSampleComponentBase.h
  17. 2 2
      Gem/Code/Source/CullingAndLodExampleComponent.cpp
  18. 6 6
      Gem/Code/Source/DiffuseGIExampleComponent.cpp
  19. 1 1
      Gem/Code/Source/DiffuseGIExampleComponent.h
  20. 3 3
      Gem/Code/Source/DynamicDrawExampleComponent.cpp
  21. 8 3
      Gem/Code/Source/DynamicMaterialTestComponent.cpp
  22. 60 7
      Gem/Code/Source/EntityLatticeTestComponent.cpp
  23. 21 0
      Gem/Code/Source/EntityLatticeTestComponent.h
  24. 1 1
      Gem/Code/Source/LightCullingExampleComponent.cpp
  25. 3 11
      Gem/Code/Source/MaterialHotReloadTestComponent.cpp
  26. 0 3
      Gem/Code/Source/MaterialHotReloadTestComponent.h
  27. 10 8
      Gem/Code/Source/MeshExampleComponent.cpp
  28. 0 3
      Gem/Code/Source/MeshExampleComponent.h
  29. 1 1
      Gem/Code/Source/MultiSceneExampleComponent.h
  30. 1 1
      Gem/Code/Source/Passes/RayTracingAmbientOcclusionPass.cpp
  31. 66 0
      Gem/Code/Source/Performance/100KDraw_10KDrawable_MultiView_ExampleComponent.cpp
  32. 34 0
      Gem/Code/Source/Performance/100KDraw_10KDrawable_MultiView_ExampleComponent.h
  33. 60 0
      Gem/Code/Source/Performance/100KDrawable_SingleView_ExampleComponent.cpp
  34. 33 0
      Gem/Code/Source/Performance/100KDrawable_SingleView_ExampleComponent.h
  35. 545 0
      Gem/Code/Source/Performance/HighInstanceExampleComponent.cpp
  36. 179 0
      Gem/Code/Source/Performance/HighInstanceExampleComponent.h
  37. 1 1
      Gem/Code/Source/Platform/Android/SSRExampleComponent_Traits_Platform.h
  38. 1 1
      Gem/Code/Source/Platform/Linux/EntityLatticeTestComponent_Traits_Platform.h
  39. 1 1
      Gem/Code/Source/Platform/Linux/SSRExampleComponent_Traits_Platform.h
  40. 1 1
      Gem/Code/Source/Platform/Mac/EntityLatticeTestComponent_Traits_Platform.h
  41. 1 1
      Gem/Code/Source/Platform/Mac/SSRExampleComponent_Traits_Platform.h
  42. 1 1
      Gem/Code/Source/Platform/Mac/Utils_Mac.cpp
  43. 1 1
      Gem/Code/Source/Platform/Windows/EntityLatticeTestComponent_Traits_Platform.h
  44. 1 1
      Gem/Code/Source/Platform/Windows/SSRExampleComponent_Traits_Platform.h
  45. 1 1
      Gem/Code/Source/Platform/iOS/SSRExampleComponent_Traits_Platform.h
  46. 2 2
      Gem/Code/Source/RHI/BasicRHIComponent.cpp
  47. 1 2
      Gem/Code/Source/RHI/BindlessPrototypeExampleComponent.cpp
  48. 0 2
      Gem/Code/Source/RHI/CopyQueueComponent.cpp
  49. 1 1
      Gem/Code/Source/RHI/IndirectRenderingExampleComponent.cpp
  50. 2 2
      Gem/Code/Source/RHI/InputAssemblyExampleComponent.cpp
  51. 1 3
      Gem/Code/Source/RHI/MRTExampleComponent.cpp
  52. 1 1
      Gem/Code/Source/RHI/QueryExampleComponent.cpp
  53. 2 2
      Gem/Code/Source/RHI/SubpassExampleComponent.cpp
  54. 342 0
      Gem/Code/Source/ReadbackExampleComponent.cpp
  55. 109 0
      Gem/Code/Source/ReadbackExampleComponent.h
  56. 435 0
      Gem/Code/Source/RenderTargetTextureExampleComponent.cpp
  57. 101 0
      Gem/Code/Source/RenderTargetTextureExampleComponent.h
  58. 2 3
      Gem/Code/Source/SSRExampleComponent.cpp
  59. 178 119
      Gem/Code/Source/SampleComponentManager.cpp
  60. 18 7
      Gem/Code/Source/SampleComponentManager.h
  61. 3 11
      Gem/Code/Source/ShaderReloadTestComponent.cpp
  62. 0 3
      Gem/Code/Source/ShaderReloadTestComponent.h
  63. 0 175
      Gem/Code/Source/ShadingExampleComponent.cpp
  64. 0 87
      Gem/Code/Source/ShadingExampleComponent.h
  65. 7 7
      Gem/Code/Source/ShadowExampleComponent.cpp
  66. 3 3
      Gem/Code/Source/ShadowedSponzaExampleComponent.cpp
  67. 1 0
      Gem/Code/Source/SponzaBenchmarkComponent.cpp
  68. 1 1
      Gem/Code/Source/TonemappingExampleComponent.cpp
  69. 0 42
      Gem/Code/Source/Utils/FileIOErrorHandler.cpp
  70. 0 40
      Gem/Code/Source/Utils/FileIOErrorHandler.h
  71. 112 105
      Gem/Code/Source/Utils/ImGuiHistogramQueue.cpp
  72. 59 55
      Gem/Code/Source/Utils/ImGuiHistogramQueue.h
  73. 11 4
      Gem/Code/atomsampleviewergem_private_files.cmake
  74. 4 1
      Gem/Code/enabled_gems.cmake
  75. 16 94
      Gem/gem.json
  76. 16 34
      Materials/Decal/airship_nose_number_decal.material
  77. 16 34
      Materials/Decal/airship_symbol_decal.material
  78. 16 34
      Materials/Decal/airship_tail_01_decal.material
  79. 16 34
      Materials/Decal/airship_tail_02_decal.material
  80. 16 34
      Materials/Decal/am_mud_decal.material
  81. 16 34
      Materials/Decal/am_road_dust_decal.material
  82. 16 34
      Materials/Decal/brushstoke_01_decal.material
  83. 15 31
      Materials/Decal/scorch_01_decal.material
  84. 2 3
      Materials/DefaultPBR.material
  85. 3 6
      Materials/DefaultPBRTransparent.material
  86. 14 19
      Materials/DiffuseGIExample/blue.material
  87. 14 19
      Materials/DiffuseGIExample/green.material
  88. 14 19
      Materials/DiffuseGIExample/red.material
  89. 14 19
      Materials/DiffuseGIExample/white.material
  90. 14 19
      Materials/DiffuseGIExample/yellow.material
  91. 3 4
      Materials/DynamicMaterialTest/EmissiveMaterial.azsl
  92. 0 4
      Materials/DynamicMaterialTest/EmissiveMaterial.shader
  93. 9 13
      Materials/DynamicMaterialTest/EmissiveWithCppFunctors.material
  94. 9 13
      Materials/DynamicMaterialTest/EmissiveWithLuaFunctors.material
  95. 17 25
      Materials/HotReloadTest/TestData/VariantSelection_FullyBaked.material
  96. 16 24
      Materials/HotReloadTest/TestData/VariantSelection_Root.material
  97. 9 13
      Materials/MinimalPBR/MinimalPBR_BlueMetal.material
  98. 0 2
      Materials/MinimalPBR/MinimalPBR_Default.material
  99. 8 12
      Materials/MinimalPBR/MinimalPBR_RedDielectric.material
  100. 15 26
      Materials/SSRExample/Cube.material

+ 0 - 22
AssetProcessorGamePlatformConfig.setreg

@@ -1,22 +0,0 @@
-{
-    "Amazon": {
-        "AssetProcessor": {
-            "Settings": {
-                "RC cgf": {
-                    "ignore": true
-                },
-                "RC fbx": {
-                    "ignore": true
-                },
-                "ScanFolder AtomTestData": {
-                    "watch": "@ENGINEROOT@/Gems/Atom/TestData",
-                    "recursive": 1,
-                    "order": 1000
-                },
-                "Exclude screenshots": {
-                    "pattern": ".*\\\\/Scripts\\\\/ExpectedScreenshots\\\\/.*"
-                }
-            }
-        }
-    }
-}

+ 2 - 1
CMakeLists.txt

@@ -8,11 +8,12 @@
 
 if(NOT PROJECT_NAME)
     cmake_minimum_required(VERSION 3.19)
+    include(cmake/CompilerSettings.cmake)
     project(AtomSampleViewer
         LANGUAGES C CXX
         VERSION 1.0.0.0
     )
-    include(EngineFinder.cmake OPTIONAL)
+    include(cmake/EngineFinder.cmake OPTIONAL)
     find_package(o3de REQUIRED)
     o3de_initialize()
 else()

+ 4 - 5
Gem/Code/Source/AssetLoadTestComponent.cpp

@@ -16,11 +16,12 @@
 
 #include <Automation/ScriptRunnerBus.h>
 
-#include <AzCore/Debug/EventTrace.h>
 #include <AzCore/Serialization/SerializeContext.h>
 
 #include <RHI/BasicRHIComponent.h>
 
+AZ_DECLARE_BUDGET(AtomSampleViewer);
+
 namespace AtomSampleViewer
 {
     using namespace AZ;
@@ -57,9 +58,7 @@ namespace AtomSampleViewer
         {
             "materials/defaultpbr.azmaterial",
             "materials/presets/pbr/metal_aluminum_polished.azmaterial",
-            "shaders/staticmesh_colorr.azmaterial",
-            "shaders/staticmesh_colorg.azmaterial",
-            "shaders/staticmesh_colorb.azmaterial"
+            "materials/basic_grey.azmaterial"
         };
         m_materialBrowser.SetDefaultPinnedAssets(defaultMaterialAllowlist);
 
@@ -241,7 +240,7 @@ namespace AtomSampleViewer
 
     void AssetLoadTestComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint scriptTime)
     {
-        AZ_TRACE_METHOD();
+        AZ_PROFILE_FUNCTION(AtomSampleViewer);
 
         const float timeSeconds = static_cast<float>(scriptTime.GetSeconds());
 

+ 5 - 129
Gem/Code/Source/AtomSampleViewerModule.cpp

@@ -9,73 +9,11 @@
 #include <AzCore/Module/Module.h>
 #include <AzCore/Memory/SystemAllocator.h>
 
-#include <AreaLightExampleComponent.h>
-#include <AssetLoadTestComponent.h>
-#include <AuxGeomExampleComponent.h>
 #include <AtomSampleViewerSystemComponent.h>
-#include <BakedShaderVariantExampleComponent.h>
-#include <SponzaBenchmarkComponent.h>
-#include <BloomExampleComponent.h>
-#include <CheckerboardExampleComponent.h>
-#include <CullingAndLodExampleComponent.h>
-#include <MultiRenderPipelineExampleComponent.h>
-#include <MultiSceneExampleComponent.h>
-#include <MultiViewSingleSceneAuxGeomExampleComponent.h>
-#include <DepthOfFieldExampleComponent.h>
-#include <DecalExampleComponent.h>
-#include <DynamicDrawExampleComponent.h>
-#include <DynamicMaterialTestComponent.h>
-#include <MaterialHotReloadTestComponent.h>
-#include <ExposureExampleComponent.h>
-#include <LightCullingExampleComponent.h>
-#include <MeshExampleComponent.h>
-#include <MSAA_RPI_ExampleComponent.h>
-#include <ParallaxMappingExampleComponent.h>
 #include <SampleComponentManager.h>
-#include <SceneReloadSoakTestComponent.h>
-#include <ShadingExampleComponent.h>
-#include <ShadowExampleComponent.h>
-#include <ShadowedSponzaExampleComponent.h>
-#include <SkinnedMeshExampleComponent.h>
-#include <SsaoExampleComponent.h>
-#include <StreamingImageExampleComponent.h>
-#include <RootConstantsExampleComponent.h>
-#include <TonemappingExampleComponent.h>
-#include <TransparencyExampleComponent.h>
-#include <DiffuseGIExampleComponent.h>
-#include <SSRExampleComponent.h>
-#include <ShaderReloadTestComponent.h>
 
-#include <RHI/AlphaToCoverageExampleComponent.h>
-#include <RHI/AsyncComputeExampleComponent.h>
-#include <RHI/BindlessPrototypeExampleComponent.h>
-#include <RHI/ComputeExampleComponent.h>
-#include <RHI/CopyQueueComponent.h>
-#include <RHI/IndirectRenderingExampleComponent.h>
-#include <RHI/InputAssemblyExampleComponent.h>
-#include <RHI/SubpassExampleComponent.h>
-#include <RHI/DualSourceBlendingComponent.h>
-#include <RHI/MRTExampleComponent.h>
-#include <RHI/MSAAExampleComponent.h>
-#include <RHI/MultiThreadComponent.h>
-#include <RHI/MultiViewportSwapchainComponent.h>
-#include <RHI/StencilExampleComponent.h>
-#include <RHI/MultipleViewsComponent.h>
-#include <RHI/QueryExampleComponent.h>
-#include <RHI/SwapchainExampleComponent.h>
-#include <RHI/SphericalHarmonicsExampleComponent.h>
-#include <RHI/Texture3dExampleComponent.h>
-#include <RHI/TextureArrayExampleComponent.h>
-#include <RHI/TextureExampleComponent.h>
-#include <RHI/TextureMapExampleComponent.h>
-#include <RHI/TriangleExampleComponent.h>
-#include <RHI/TrianglesConstantBufferExampleComponent.h>
-#include <RHI/RayTracingExampleComponent.h>
-#include <RHI/MatrixAlignmentTestExampleComponent.h>
 #include <AzFramework/Scene/SceneSystemComponent.h>
 
-#include <Atom/Feature/SkinnedMesh/SkinnedMeshInputBuffers.h>
-
 namespace AtomSampleViewer
 {
     class Module final
@@ -92,73 +30,11 @@ namespace AtomSampleViewer
                 SampleComponentManager::CreateDescriptor(),
                 });
 
-            // RHI Samples
-            m_descriptors.insert(m_descriptors.end(), {
-                AlphaToCoverageExampleComponent::CreateDescriptor(),
-                AsyncComputeExampleComponent::CreateDescriptor(),
-                BindlessPrototypeExampleComponent::CreateDescriptor(),
-                ComputeExampleComponent::CreateDescriptor(),
-                CopyQueueComponent::CreateDescriptor(),
-                DualSourceBlendingComponent::CreateDescriptor(),
-                IndirectRenderingExampleComponent::CreateDescriptor(),
-                InputAssemblyExampleComponent::CreateDescriptor(),
-                SubpassExampleComponent::CreateDescriptor(),
-                MRTExampleComponent::CreateDescriptor(),
-                MSAAExampleComponent::CreateDescriptor(),
-                MultiThreadComponent::CreateDescriptor(),
-                MultipleViewsComponent::CreateDescriptor(),
-                MultiViewportSwapchainComponent::CreateDescriptor(),
-                QueryExampleComponent::CreateDescriptor(),
-                StencilExampleComponent::CreateDescriptor(),
-                SwapchainExampleComponent::CreateDescriptor(),
-                SphericalHarmonicsExampleComponent::CreateDescriptor(),
-                Texture3dExampleComponent::CreateDescriptor(),
-                TextureArrayExampleComponent::CreateDescriptor(),
-                TextureExampleComponent::CreateDescriptor(),
-                TextureMapExampleComponent::CreateDescriptor(),
-                TriangleExampleComponent::CreateDescriptor(),
-                TrianglesConstantBufferExampleComponent::CreateDescriptor(),
-                RayTracingExampleComponent::CreateDescriptor(),
-                MatrixAlignmentTestExampleComponent::CreateDescriptor()
-                });
-
-            // RPI Samples
-            m_descriptors.insert(m_descriptors.end(), {
-                AreaLightExampleComponent::CreateDescriptor(),
-                AssetLoadTestComponent::CreateDescriptor(),
-                BakedShaderVariantExampleComponent::CreateDescriptor(),
-                SponzaBenchmarkComponent::CreateDescriptor(),
-                BloomExampleComponent::CreateDescriptor(),
-                CheckerboardExampleComponent::CreateDescriptor(),
-                CullingAndLodExampleComponent::CreateDescriptor(),
-                MultiRenderPipelineExampleComponent::CreateDescriptor(),
-                MultiSceneExampleComponent::CreateDescriptor(),
-                MultiViewSingleSceneAuxGeomExampleComponent::CreateDescriptor(),
-                DecalExampleComponent::CreateDescriptor(),
-                DepthOfFieldExampleComponent::CreateDescriptor(),
-                DynamicMaterialTestComponent::CreateDescriptor(),
-                MaterialHotReloadTestComponent::CreateDescriptor(),
-                ExposureExampleComponent::CreateDescriptor(),
-                MeshExampleComponent::CreateDescriptor(),
-                DynamicDrawExampleComponent::CreateDescriptor(),
-                SceneReloadSoakTestComponent::CreateDescriptor(),
-                ShadingExampleComponent::CreateDescriptor(),
-                ShadowExampleComponent::CreateDescriptor(),
-                ShadowedSponzaExampleComponent::CreateDescriptor(),
-                SkinnedMeshExampleComponent::CreateDescriptor(),
-                SsaoExampleComponent::CreateDescriptor(),
-                LightCullingExampleComponent::CreateDescriptor(),
-                StreamingImageExampleComponent::CreateDescriptor(),
-                AuxGeomExampleComponent::CreateDescriptor(),
-                MSAA_RPI_ExampleComponent::CreateDescriptor(),
-                RootConstantsExampleComponent::CreateDescriptor(),
-                TonemappingExampleComponent::CreateDescriptor(),
-                TransparencyExampleComponent::CreateDescriptor(),
-                ParallaxMappingExampleComponent::CreateDescriptor(),
-                DiffuseGIExampleComponent::CreateDescriptor(),
-                SSRExampleComponent::CreateDescriptor(),
-                ShaderReloadTestComponent::CreateDescriptor(),
-                });
+            AZStd::vector<SampleEntry> samples = SampleComponentManager::GetSamples();
+            for (const SampleEntry& sample : samples)
+            {
+                m_descriptors.emplace_back(sample.m_componentDescriptor);
+            }
         }
 
         ~Module() override = default;

+ 11 - 4
Gem/Code/Source/AtomSampleViewerSystemComponent.cpp

@@ -17,10 +17,12 @@
 #include <AzCore/Asset/AssetManager.h>
 #include <AzCore/Serialization/SerializeContext.h>
 #include <AzCore/Component/Entity.h>
+#include <AzCore/IO/Path/Path.h>
 #include <AzCore/IO/SystemFile.h>
 
 #include <AzFramework/Input/Buses/Requests/InputSystemCursorRequestBus.h>
 #include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
+#include <AzFramework/IO/LocalFileIO.h>
 
 #include <Atom/Bootstrap/DefaultWindowBus.h>
 
@@ -36,6 +38,8 @@
 #include <Utils/ImGuiSaveFilePath.h>
 #include <Utils/Utils.h>
 
+AZ_DEFINE_BUDGET(AtomSampleViewer);
+
 namespace AtomSampleViewer
 {
     void AtomSampleViewerSystemComponent::Reflect(AZ::ReflectContext* context)
@@ -55,6 +59,10 @@ namespace AtomSampleViewer
         StacksShaderInputFunctor::Reflect(context);
 
         ImageComparisonConfig::Reflect(context);
+
+        // Abstract base components is used by multiple components and needs to be reflected in a single location.
+        CommonSampleComponentBase::Reflect(context);
+        EntityLatticeTestComponent::Reflect(context);
     }
 
     void AtomSampleViewerSystemComponent::PerfMetrics::Reflect(AZ::ReflectContext* context)
@@ -68,9 +76,6 @@ namespace AtomSampleViewer
                 ->Field("SecondsToRender", &PerfMetrics::m_timeToFirstRenderSeconds)
                 ;
         }
-
-        // Abstract base component is used by multiple components and needs to be reflected in a single location.
-        EntityLatticeTestComponent::Reflect(context);
     }
 
 
@@ -224,6 +229,8 @@ namespace AtomSampleViewer
 
     void AtomSampleViewerSystemComponent::LogPerfMetrics() const
     {
-        AZ::Utils::SaveObjectToFile("metrics.xml", AZ::DataStream::ST_XML, &m_perfMetrics);
+        AZ::IO::FixedMaxPath resolvedPath;
+        AZ::IO::LocalFileIO::GetInstance()->ResolvePath(resolvedPath, "@user@/PerformanceMetrics.xml");
+        AZ::Utils::SaveObjectToFile(resolvedPath.String(), AZ::DataStream::ST_XML, &m_perfMetrics);
     }
 } // namespace AtomSampleViewer

+ 95 - 0
Gem/Code/Source/Automation/PrecommitWizardSettings.h

@@ -0,0 +1,95 @@
+/*
+ * 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 <Automation/ScriptReporter.h>
+#include <AzCore/std/containers/vector.h>
+#include <AzCore/std/containers/map.h>
+#include <AzCore/Preprocessor/Enum.h>
+
+namespace AtomSampleViewer
+{
+    struct PrecommitWizardSettings
+    {
+        static const int DefaultInspectionSelection = -1;
+        enum class Stage
+        {
+            Intro,
+            RunFullsuiteTest,
+            ReportFullsuiteSummary,
+            ManualInspection,
+            ReportFinalSummary
+        };
+
+        struct ImageDifferenceLevel
+        {
+            enum Levels
+            {
+                NoDifference = 0,
+                LowDifference = 1,
+                ModerateDifference = 2,
+                HighDifference = 3,
+
+                NumDifferenceLevels = 4
+            };
+        };
+        static constexpr const char* ManualInspectionDifferenceLevels[] = {
+            "No Difference",
+            "Low Difference",
+            "Moderate Difference",
+            "High Difference"
+        };
+        static constexpr const char* ManualInspectionOptions[] = {
+            "I don't see any difference",
+            "I see a benign difference",
+            "I see a difference that's *probably* benign",
+            "This looks like a problem"
+        };
+
+        // This function does the following:
+        // 1. Collect passing screenshot tests and sorts them by decreasing diff score.
+        // 2. Collect failed screenshot tests and sorts them by decreasing diff score. 
+        void ProcessScriptReports(const AZStd::vector<ScriptReporter::ScriptReport>& scriptReports)
+        {
+            m_reportsOrderedByThresholdToInspect.clear();
+            m_failedReports.clear();
+
+            for (size_t i = 0; i < scriptReports.size(); ++i)
+            {
+                const AZStd::vector<ScriptReporter::ScreenshotTestInfo>& screenshotTestInfos = scriptReports[i].m_screenshotTests;
+                for (size_t j = 0; j < screenshotTestInfos.size(); ++j)
+                {
+                    // Collect and sort reports that passed by threshold. This will be used to detect false negatives
+                    // e.g. a test is reported to pass by being below the threshold when in fact it's simply because the threshold is too
+                    // high
+                    if (screenshotTestInfos[j].m_officialComparisonResult.m_resultCode == ScriptReporter::ImageComparisonResult::ResultCode::Pass)
+                    {
+                        m_reportsOrderedByThresholdToInspect.insert(AZStd::pair<float, ScriptReporter::ReportIndex>(
+                            screenshotTestInfos[j].m_officialComparisonResult.m_finalDiffScore,
+                            ScriptReporter::ReportIndex{ i, j }));
+                    }
+                    else
+                    {
+                        m_failedReports.insert(AZStd::pair<float, ScriptReporter::ReportIndex>(
+                            screenshotTestInfos[j].m_officialComparisonResult.m_finalDiffScore,
+                            ScriptReporter::ReportIndex{ i, j }));
+                    }
+                }
+            }
+
+            m_reportIterator = m_reportsOrderedByThresholdToInspect.begin();
+        }
+
+        int m_inspectionSelection = DefaultInspectionSelection;
+        Stage m_stage = Stage::Intro;
+        AZStd::string m_exportedPngPath;
+        AZStd::multimap<float, ScriptReporter::ReportIndex, AZStd::greater<float>> m_reportsOrderedByThresholdToInspect;
+        AZStd::multimap<float, ScriptReporter::ReportIndex, AZStd::greater<float>> m_failedReports;
+        AZStd::multimap<float, ScriptReporter::ReportIndex, AZStd::greater<float>>::iterator m_reportIterator;
+        AZStd::unordered_map<ScriptReporter::ReportIndex, ImageDifferenceLevel::Levels> m_reportIndexDifferenceLevelMap;
+    };
+} // namespace AtomSampleViewer

+ 354 - 8
Gem/Code/Source/Automation/ScriptManager.cpp

@@ -9,7 +9,7 @@
 #include <Automation/ScriptManager.h>
 #include <Automation/ScriptableImGui.h>
 #include <SampleComponentManagerBus.h>
-
+#include <imgui/imgui_internal.h>
 #include <Atom/RPI.Reflect/Asset/AssetUtils.h>
 
 #include <Atom/RPI.Public/RPISystemInterface.h>
@@ -18,6 +18,7 @@
 #include <Atom/Component/DebugCamera/ArcBallControllerComponent.h>
 #include <Atom/Feature/ImGui/SystemBus.h>
 #include <Atom/RPI.Reflect/Asset/AssetUtils.h>
+#include <Atom/RHI/Factory.h>
 
 #include <AzCore/Component/Entity.h>
 #include <AzCore/Script/ScriptContext.h>
@@ -277,6 +278,11 @@ namespace AtomSampleViewer
 
         m_scriptReporter.TickImGui();
 
+        if (m_showPrecommitWizard)
+        {
+            ShowPrecommitWizard();
+        }
+
         if (m_testSuiteRunConfig.m_automatedRunEnabled)
         {
             if (m_testSuiteRunConfig.m_isStarted == false)
@@ -394,7 +400,7 @@ namespace AtomSampleViewer
 
                 if (m_frameTimeIsLocked)
                 {
-                    AZ::Interface<AZ::IConsole>::Get()->PerformCommand("t_frameTimeOverride 0");
+                    AZ::Interface<AZ::IConsole>::Get()->PerformCommand("t_simulationTickDeltaOverride 0");
                     m_frameTimeIsLocked = false;
                 }
 
@@ -407,6 +413,7 @@ namespace AtomSampleViewer
                 // In case scripts were aborted while ImGui was temporarily hidden, show it again.
                 SetShowImGui(true);
 
+                m_scriptReporter.SortScriptReports();
                 m_scriptReporter.OpenReportDialog();
 
                 m_shouldPopScript = false;
@@ -447,6 +454,11 @@ namespace AtomSampleViewer
         m_showScriptRunnerDialog = true;
     }
 
+    void ScriptManager::OpenPrecommitWizard()
+    {
+        m_showPrecommitWizard = true;
+    }
+
     void ScriptManager::RunMainTestSuite(const AZStd::string& suiteFilePath, bool exitOnTestEnd, int randomSeed)
     {
         m_testSuiteRunConfig.m_automatedRunEnabled = true;
@@ -547,6 +559,335 @@ namespace AtomSampleViewer
         ImGui::End();
     }
 
+    void ScriptManager::ShowPrecommitWizard()
+    {
+        if (ImGui::Begin("Pre-commit Wizard", &m_showPrecommitWizard))
+        {
+            ShowBackToIntroWarning();
+
+            if (ImGui::Button("Start from the Beginning"))
+            {
+                ImGui::OpenPopup("Return to Intro Window");
+            }
+
+            switch (m_wizardSettings.m_stage)
+            {
+            case PrecommitWizardSettings::Stage::Intro:
+                ShowPrecommitWizardIntro();
+                break;
+            case PrecommitWizardSettings::Stage::RunFullsuiteTest:
+                ShowPrecommitWizardRunFullsuiteTest();
+                break;
+            case PrecommitWizardSettings::Stage::ReportFullsuiteSummary:
+                ShowPrecommitWizardReportFullsuiteTest();
+                break;
+            case PrecommitWizardSettings::Stage::ManualInspection:
+                // Go through each pass in the full suite test and require users to manually pick from a list
+                // to describe the difference if it exists.
+                ShowPrecomitWizardManualInspection();
+                break;
+            case PrecommitWizardSettings::Stage::ReportFinalSummary:
+                // Generate a summary that can be easily copied into the clipboard
+                ShowPrecommitWizardReportFinalSummary();
+                break;
+            }
+        }
+
+        ImGui::End();
+    }
+
+    void ScriptManager::ShowBackToIntroWarning()
+    {
+        if (ImGui::BeginPopupModal("Return to Intro Window", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
+        {
+            ImGui::Text("Warning: You are returning to the intro window. Current full test suite results will be lost.\n\nAre you sure?");
+            if (ImGui::Button("Yes"))
+            {
+                m_wizardSettings = PrecommitWizardSettings();
+                ImGui::CloseCurrentPopup();
+            }
+            ImGui::SameLine();
+            if (ImGui::Button("No"))
+            {
+                ImGui::CloseCurrentPopup();
+            }
+            ImGui::EndPopup();
+        }
+    }
+
+    void ScriptManager::ShowPrecommitWizardIntro()
+    {
+        ImGui::Separator();
+        ImGui::TextWrapped("Here's what will take place...\n"
+                           "1) The standard '_fulltestsuite_' script will be run.\n"
+                           "2) You'll see a summary of the test results. If any tests failed the automatic checks, "
+                           "you are strongly encouraged to address those issues first.\n"
+                           "3) You will be guided through a series of visual screenshot validations. For each one, "
+                           "you'll need to inspect an image comparison and indicate whether you see visual differences.\n"
+                           "4) The final report will be generated. Copy and paste this into your PR description.");
+        ImGui::Separator();
+
+        if (ImGui::Button("Run Full Test Suite"))
+        {
+            PrepareAndExecuteScript(FullSuiteScriptFilepath);
+            m_wizardSettings.m_stage = PrecommitWizardSettings::Stage::RunFullsuiteTest;
+        }
+    }
+
+    void ScriptManager::ShowPrecommitWizardRunFullsuiteTest()
+    {
+        ImGui::Text("Running Full Test Suite. Please Wait...");
+
+        if (m_scriptOperations.empty() && !m_doFinalScriptCleanup)
+        {
+            m_scriptReporter.HideReportDialog();
+            m_wizardSettings.m_stage = PrecommitWizardSettings::Stage::ReportFullsuiteSummary;
+        }
+    }
+
+    void ScriptManager::ShowPrecommitWizardReportFullsuiteTest()
+    {
+        m_scriptReporter.DisplayScriptResultsSummary();
+
+        if (ImGui::Button("See Details..."))
+        {
+            m_scriptReporter.OpenReportDialog();
+        }
+        ImGui::Separator();
+
+        if (ImGui::Button("Back"))
+        {
+            ImGui::OpenPopup("Return to Intro Window");
+        }
+
+        ImGui::SameLine();
+        if (ImGui::Button("Continue"))
+        {
+            m_wizardSettings.ProcessScriptReports(m_scriptReporter.GetScriptReport());
+
+            if (!m_wizardSettings.m_reportsOrderedByThresholdToInspect.empty())
+            {
+                m_wizardSettings.m_stage = PrecommitWizardSettings::Stage::ManualInspection;
+            }
+            else
+            {
+                m_wizardSettings.m_stage = PrecommitWizardSettings::Stage::ReportFinalSummary;
+            }
+        }
+    }
+
+    void ScriptManager::ShowPrecomitWizardManualInspection()
+    {
+        const AZStd::vector<ScriptReporter::ScriptReport>& scriptReports = m_scriptReporter.GetScriptReport();
+
+        // Get the script report and screenshot test corresponding to the current index
+        const ScriptReporter::ReportIndex currentIndex = m_wizardSettings.m_reportIterator->second;
+        const ScriptReporter::ScriptReport& scriptReport = scriptReports[currentIndex.first];
+        const ScriptReporter::ScreenshotTestInfo& screenshotTest = scriptReport.m_screenshotTests[currentIndex.second];
+
+        ImGui::Text("Script Name: %s", scriptReport.m_scriptAssetPath.c_str());
+        ImGui::Text("Screenshot Name: %s", screenshotTest.m_officialBaselineScreenshotFilePath.c_str());
+
+        ImGui::Separator();
+        ImGui::Text("Diff Score: %f", screenshotTest.m_officialComparisonResult.m_finalDiffScore);
+
+        // TODO: Render screenshots in ImGui.
+        ImGui::Separator();
+        // Check if the current index's screenshot has been manually validated and let the user know
+        auto it = m_wizardSettings.m_reportIndexDifferenceLevelMap.find(currentIndex);
+        if (it != m_wizardSettings.m_reportIndexDifferenceLevelMap.end())
+        {
+            m_wizardSettings.m_inspectionSelection = it->second;
+            ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 255, 0, 255));
+            ImGui::Text("This inspection has been added to the report summary");
+            ImGui::PopStyleColor();
+        }
+        else
+        {
+            ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255));
+            ImGui::Text("This inspection has not been added to the report summary");
+            ImGui::PopStyleColor();
+        }
+
+        ImGui::TextWrapped(
+            "Use the 'Export Png' button to inspect the screenshot. Choose from the options below that describe the difference level "
+            "then click 'Next' to add it in the report summary. Once there are no more noticeable differences as the threshold decreases, "
+            "click 'Finish' to generate the report summary. Note that the remaining screenshots that were not manually inspected will not "
+            "appear in the report summary.");
+        for (int i = 0; i < 4; ++i)
+        {
+            ImGui::RadioButton(PrecommitWizardSettings::ManualInspectionOptions[i], &m_wizardSettings.m_inspectionSelection, i);
+        }
+
+        if (ImGui::Button("View Diff"))
+        {
+            if (!Utils::RunDiffTool(screenshotTest.m_officialBaselineScreenshotFilePath, screenshotTest.m_screenshotFilePath))
+            {
+                ImGui::OpenPopup("Cannot open diff tool");
+            }
+        }
+        if (ImGui::BeginPopupModal("Cannot open diff tool", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
+        {
+            ImGui::Text("Image diff is not supported on this platform, or the required diff tool is not installed.");
+            if (ImGui::Button("Ok"))
+            {
+                ImGui::CloseCurrentPopup();
+            }
+            ImGui::EndPopup();
+        }
+        ImGui::SameLine();
+        if (ImGui::Button("Export Png"))
+        {
+            m_wizardSettings.m_exportedPngPath = m_scriptReporter.ExportImageDiff(scriptReport, screenshotTest);
+        }
+        if (!m_wizardSettings.m_exportedPngPath.empty())
+        {
+            ImGui::SameLine();
+            bool copyPath = ImGui::Button("Copy Path");
+
+            ImGui::Text("Diff Png was exported to:");
+            ImGui::SameLine();
+            if (copyPath)
+            {
+                ImGui::LogToClipboard();
+            }
+
+            ImGui::Text("%s", m_wizardSettings.m_exportedPngPath.c_str());
+
+            if (copyPath)
+            {
+                ImGui::LogFinish();
+            }
+        }
+
+        ImGui::Separator();
+        if (ImGui::Button("Back"))
+        {
+            if (m_wizardSettings.m_reportIterator == m_wizardSettings.m_reportsOrderedByThresholdToInspect.begin())
+            {
+                m_wizardSettings.m_stage = PrecommitWizardSettings::Stage::ReportFullsuiteSummary;
+            }
+            else
+            {
+                --m_wizardSettings.m_reportIterator;
+            }
+        }
+        ImGui::SameLine();
+        const bool isNextButtonDisabled =
+            m_wizardSettings.m_inspectionSelection == PrecommitWizardSettings::DefaultInspectionSelection ? true : false;
+        if (isNextButtonDisabled)
+        {
+            ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
+            ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
+        }
+        if (ImGui::Button("Next"))
+        {
+            m_wizardSettings.m_reportIndexDifferenceLevelMap[currentIndex] =
+                PrecommitWizardSettings::ImageDifferenceLevel::Levels(m_wizardSettings.m_inspectionSelection);
+            m_wizardSettings.m_inspectionSelection = PrecommitWizardSettings::DefaultInspectionSelection;
+            m_wizardSettings.m_exportedPngPath.clear();
+            ++m_wizardSettings.m_reportIterator;
+
+            if (m_wizardSettings.m_reportIterator == m_wizardSettings.m_reportsOrderedByThresholdToInspect.end())
+            {
+                // Decrement the iterator to make sure it's pointing to the last element in case the user goes
+                // back to the manual inspection page.
+                --m_wizardSettings.m_reportIterator;
+                m_wizardSettings.m_stage = PrecommitWizardSettings::Stage::ReportFinalSummary;
+            }
+        }
+        if (isNextButtonDisabled)
+        {
+            ImGui::PopItemFlag();
+            ImGui::PopStyleVar();
+        }
+        ImGui::SameLine();
+        if (ImGui::Button("Finish Manual Inspection"))
+        {
+            m_wizardSettings.m_stage = PrecommitWizardSettings::Stage::ReportFinalSummary;
+        }
+        ImGui::SameLine();
+        ImGui::Text(
+            "%zu/%zu", aznumeric_cast<size_t>(AZStd::distance(m_wizardSettings.m_reportsOrderedByThresholdToInspect.begin(), m_wizardSettings.m_reportIterator) + 1),
+            m_wizardSettings.m_reportsOrderedByThresholdToInspect.size());
+    }
+
+    void ScriptManager::ShowPrecommitWizardReportFinalSummary()
+    {
+        ImGui::Text("Tests are complete! here is the final report.");
+
+        bool copyToClipboard = ImGui::Button("Copy To Clipboard");
+
+        const ScriptReporter::ScriptResultsSummary resultsSummary = m_scriptReporter.GetScriptResultSummary();
+        const AZStd::vector<ScriptReporter::ScriptReport>& scriptReports = m_scriptReporter.GetScriptReport();
+        AZStd::vector<AZStd::vector<ScriptReporter::ReportIndex>> imageDifferenceSummary(
+            PrecommitWizardSettings::ImageDifferenceLevel::NumDifferenceLevels);
+        int totalValidatedScreenshots = 0;
+        for (const auto& [reportIndex, differenceLevel] : m_wizardSettings.m_reportIndexDifferenceLevelMap)
+        {
+            imageDifferenceSummary[differenceLevel].push_back(reportIndex);
+        }
+        for (const auto& differenceLevelReports : imageDifferenceSummary)
+        {
+            totalValidatedScreenshots += aznumeric_cast<int>(differenceLevelReports.size());
+        }
+
+        ImGui::Separator();
+
+        if (copyToClipboard)
+        {
+            ImGui::LogToClipboard();
+        }
+
+        ImGui::Text("RHI API: %s", AZ::RHI::Factory::Get().GetName().GetCStr());
+        ImGui::Text("Test Script Count: %zu", scriptReports.size());
+        ImGui::Text("Screenshot Count: %d", resultsSummary.m_totalScreenshotsCount);
+        ImGui::Text(
+            "Manually Validated Screenshots: %d/%zu", totalValidatedScreenshots,
+            m_wizardSettings.m_reportsOrderedByThresholdToInspect.size());
+        ImGui::Text("Screenshot automatic checks failed: %zu", m_wizardSettings.m_failedReports.size());
+        for (const auto& failedReport : m_wizardSettings.m_failedReports)
+        {
+            const ScriptReporter::ReportIndex& reportIndex = failedReport.second;
+            const ScriptReporter::ScriptReport& scriptReport = scriptReports[reportIndex.first];
+            const ScriptReporter::ScreenshotTestInfo& screenshotTest = scriptReport.m_screenshotTests[reportIndex.second];
+            ImGui::Text(
+                "\t%s %s '%s' %f", scriptReport.m_scriptAssetPath.c_str(), screenshotTest.m_screenshotFilePath.c_str(),
+                screenshotTest.m_toleranceLevel.m_name.c_str(), screenshotTest.m_officialComparisonResult.m_finalDiffScore);
+        }
+        // Present the information by printing highest differences first. See enum class ImageDifferenceLevel
+        for (int i = aznumeric_cast<int>(imageDifferenceSummary.size() - 1); i >= 0; --i)
+        {
+            if (!imageDifferenceSummary[i].empty())
+            {
+                ImGui::Text("Screenshot interactive check '%s'", PrecommitWizardSettings::ManualInspectionDifferenceLevels[i]);
+                for (const auto& reportIndex : imageDifferenceSummary[i])
+                {
+                    const ScriptReporter::ScriptReport& scriptReport = scriptReports[reportIndex.first];
+                    const ScriptReporter::ScreenshotTestInfo& screenshotTest = scriptReport.m_screenshotTests[reportIndex.second];
+                    ImGui::Text(
+                        "\t%s %s '%s' %f", scriptReport.m_scriptAssetPath.c_str(), screenshotTest.m_screenshotFilePath.c_str(),
+                        screenshotTest.m_toleranceLevel.m_name.c_str(), screenshotTest.m_officialComparisonResult.m_finalDiffScore);
+                }
+            }
+        }
+
+        if (copyToClipboard)
+        {
+            ImGui::LogFinish();
+        }
+
+        ImGui::Separator();
+        if (ImGui::Button("Back"))
+        {
+            m_wizardSettings.m_stage = PrecommitWizardSettings::Stage::ManualInspection;
+            auto reportIndexToDifference = m_wizardSettings.m_reportIndexDifferenceLevelMap.find(m_wizardSettings.m_reportIterator->second);
+            m_wizardSettings.m_inspectionSelection = reportIndexToDifference != m_wizardSettings.m_reportIndexDifferenceLevelMap.end()
+                ? reportIndexToDifference->second
+                : PrecommitWizardSettings::DefaultInspectionSelection;
+        }
+    }
+
     void ScriptManager::PrepareAndExecuteScript(const AZStd::string& scriptFilePath)
     {
         ReportScriptableAction(AZStd::string::format("RunScript('%s')", scriptFilePath.c_str()));
@@ -903,7 +1244,9 @@ namespace AtomSampleViewer
         {
             AZ_DEBUG_STATIC_MEMEBER(instance, s_instance);
 
-            AZ::Interface<AZ::IConsole>::Get()->PerformCommand(AZStd::string::format("t_frameTimeOverride %f", seconds).c_str());
+            int milliseconds = static_cast<int>(seconds * 1000);
+
+            AZ::Interface<AZ::IConsole>::Get()->PerformCommand(AZStd::string::format("t_simulationTickDeltaOverride %d", milliseconds).c_str());
             s_instance->m_frameTimeIsLocked = true;
         };
 
@@ -916,7 +1259,7 @@ namespace AtomSampleViewer
         {
             AZ_DEBUG_STATIC_MEMEBER(instance, s_instance);
 
-            AZ::Interface<AZ::IConsole>::Get()->PerformCommand("t_frameTimeOverride 0");
+            AZ::Interface<AZ::IConsole>::Get()->PerformCommand("t_simulationTickDeltaOverride 0");
             s_instance->m_frameTimeIsLocked = false;
         };
 
@@ -1295,10 +1638,10 @@ namespace AtomSampleViewer
         ResumeScript();
     }
 
-    void ScriptManager::OnCaptureCpuProfilingStatisticsFinished([[maybe_unused]] bool result, [[maybe_unused]] const AZStd::string& info)
+    void ScriptManager::OnCaptureFinished([[maybe_unused]] bool result, [[maybe_unused]] const AZStd::string& info)
     {
         m_isCapturePending = false;
-        Profiler::ProfilerNotificationBus::Handler::BusDisconnect();
+        AZ::Debug::ProfilerNotificationBus::Handler::BusDisconnect();
         ResumeScript();
     }
 
@@ -1384,10 +1727,13 @@ namespace AtomSampleViewer
         auto operation = [outputFilePath]()
         {
             s_instance->m_isCapturePending = true;
-            s_instance->Profiler::ProfilerNotificationBus::Handler::BusConnect();
+            s_instance->AZ::Debug::ProfilerNotificationBus::Handler::BusConnect();
             s_instance->PauseScript();
 
-            Profiler::ProfilerRequestBus::Broadcast(&Profiler::ProfilerRequestBus::Events::CaptureCpuProfilingStatistics, outputFilePath);
+            if (auto profilerSystem = AZ::Debug::ProfilerSystemInterface::Get(); profilerSystem)
+            {
+                profilerSystem->CaptureFrame(outputFilePath);
+            }
         };
 
         s_instance->m_scriptOperations.push(AZStd::move(operation));

+ 22 - 3
Gem/Code/Source/Automation/ScriptManager.h

@@ -11,13 +11,14 @@
 #include <Atom/Component/DebugCamera/CameraControllerBus.h>
 #include <Atom/Feature/Utils/FrameCaptureBus.h>
 #include <Atom/Feature/Utils/ProfilingCaptureBus.h>
+#include <Automation/PrecommitWizardSettings.h>
 #include <Automation/ScriptRepeaterBus.h>
 #include <Automation/ScriptRunnerBus.h>
 #include <Automation/AssetStatusTracker.h>
 #include <Automation/ScriptReporter.h>
 #include <Automation/ImageComparisonConfig.h>
 #include <Utils/ImGuiAssetBrowser.h>
-#include <Profiler/ProfilerBus.h>
+#include <AzCore/Debug/ProfilerBus.h>
 
 namespace AZ
 {
@@ -50,7 +51,7 @@ namespace AtomSampleViewer
         , public AZ::Debug::CameraControllerNotificationBus::Handler
         , public AZ::Render::FrameCaptureNotificationBus::Handler
         , public AZ::Render::ProfilingCaptureNotificationBus::Handler
-        , public Profiler::ProfilerNotificationBus::Handler
+        , public AZ::Debug::ProfilerNotificationBus::Handler
     {
     public:
         ScriptManager();
@@ -64,13 +65,28 @@ namespace AtomSampleViewer
         void TickImGui();
 
         void OpenScriptRunnerDialog();
+        void OpenPrecommitWizard();
 
         void RunMainTestSuite(const AZStd::string& suiteFilePath, bool exitOnTestEnd, int randomSeed);
 
     private:
+        static constexpr const char* FullSuiteScriptFilepath = "scripts/_fulltestsuite_.bv.luac";
 
         void ShowScriptRunnerDialog();
 
+        // PrecommitWizard Gui
+        void ShowPrecommitWizard();
+        // PrecommitWizard stages
+        void ShowPrecommitWizardIntro();
+        void ShowPrecommitWizardRunFullsuiteTest();
+        void ShowPrecommitWizardReportFullsuiteTest();
+        void ShowPrecomitWizardManualInspection();
+        void ShowPrecommitWizardReportFinalSummary();
+
+        void ShowBackToIntroWarning();
+
+
+
         // Registers functions in a BehaviorContext so they can be exposed to Lua scripts.
         static void ReflectScriptContext(AZ::BehaviorContext* context);
 
@@ -210,7 +226,7 @@ namespace AtomSampleViewer
         void OnCaptureBenchmarkMetadataFinished(bool result, const AZStd::string& info) override;
 
         // ProfilerNotificationBus overrides...
-        void OnCaptureCpuProfilingStatisticsFinished(bool result, const AZStd::string& info) override;
+        void OnCaptureFinished(bool result, const AZStd::string& info) override;
 
         void AbortScripts(const AZStd::string& reason);
 
@@ -319,6 +335,9 @@ namespace AtomSampleViewer
 
         } m_imageComparisonOptions;
 
+        bool m_showPrecommitWizard = false;
+        PrecommitWizardSettings m_wizardSettings;
+
         bool m_showScriptRunnerDialog = false;
         bool m_isCapturePending = false;
         bool m_frameTimeIsLocked = false;

File diff suppressed because it is too large
+ 478 - 362
Gem/Code/Source/Automation/ScriptReporter.cpp


+ 66 - 6
Gem/Code/Source/Automation/ScriptReporter.h

@@ -13,7 +13,8 @@
 #include <Atom/Utils/ImageComparison.h>
 #include <Automation/ImageComparisonConfig.h>
 #include <Utils/ImGuiMessageBox.h>
-#include <Utils/FileIOErrorHandler.h>
+#include <Atom/Utils/PngFile.h>
+#include <imgui/imgui.h>
 
 namespace AtomSampleViewer
 {
@@ -45,6 +46,11 @@ namespace AtomSampleViewer
     class ScriptReporter
     {
     public:
+        // currently set to track the ScriptReport index and the ScreenshotTestInfo index.
+        using ReportIndex = AZStd::pair<size_t, size_t>;
+
+        static constexpr const char* TestResultsFolder = "TestResults";
+        static constexpr const char* UserFolder = "user";
 
         //! Set the list of available tolerance levels, so the report can suggest an alternate level that matches the actual results.
         void SetAvailableToleranceLevels(const AZStd::vector<ImageComparisonToleranceLevel>& toleranceLevels);
@@ -78,6 +84,7 @@ namespace AtomSampleViewer
         //! This displays all the collected script reporting data, provides links to tools for analyzing data like
         //! viewing screenshot diffs. It can be left open during processing and will update in real-time.
         void OpenReportDialog();
+        void HideReportDialog();
 
         //! Called every frame to update the ImGui dialog
         void TickImGui();
@@ -85,8 +92,21 @@ namespace AtomSampleViewer
         //! Returns true if there are any errors or asserts in the script report
         bool HasErrorsAssertsInReport() const;
 
-        // For exporting test results
-        void ExportTestResults();
+        struct ScriptResultsSummary
+        {
+            uint32_t m_totalAsserts = 0;
+            uint32_t m_totalErrors = 0;
+            uint32_t m_totalWarnings = 0;
+            uint32_t m_totalScreenshotsCount = 0;
+            uint32_t m_totalScreenshotsFailed = 0;
+            uint32_t m_totalScreenshotWarnings = 0;
+        };
+
+        //! Displays the script results summary in ImGui.
+        void DisplayScriptResultsSummary();
+
+        //! Retrieves the current script result summary.
+        const ScriptResultsSummary& GetScriptResultSummary() const;
 
         struct ImageComparisonResult
         {
@@ -175,10 +195,19 @@ namespace AtomSampleViewer
 
             AZStd::vector<ScreenshotTestInfo> m_screenshotTests;
         };
-        
+
         const AZStd::vector<ScriptReport>& GetScriptReport() const { return m_scriptReports; }
 
+        // For exporting test results
+        void ExportTestResults();
+        void ExportImageDiff(const char* filePath, const ScreenshotTestInfo& screenshotTest);
+        AZStd::string ExportImageDiff(const ScriptReport& scriptReport, const ScreenshotTestInfo& screenshotTest);
+
+        void SortScriptReports();
+
     private:
+        static const ImGuiTreeNodeFlags FlagDefaultOpen = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_DefaultOpen;
+        static const ImGuiTreeNodeFlags FlagDefaultClosed = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
 
         // Reports a script error using standard formatting that matches ScriptManager
         enum class TraceLevel
@@ -232,16 +261,38 @@ namespace AtomSampleViewer
         const ImageComparisonToleranceLevel* FindBestToleranceLevel(float diffScore, bool filterImperceptibleDiffs) const;
 
         void ShowReportDialog();
-
+        void ShowScreenshotTestInfoTreeNode(const AZStd::string& header, ScriptReport& scriptReport, ScreenshotTestInfo& screenshotResult);
         void ShowDiffButton(const char* buttonLabel, const AZStd::string& imagePathA, const AZStd::string& imagePathB);
 
         // Generates a path to the exported test results file.
+        AZStd::string GenerateTimestamp() const;
+        AZStd::string GenerateAndCreateExportedImageDiffPath(const ScriptReport& scriptReport, const ScreenshotTestInfo& screenshotTest) const;
         AZStd::string GenerateAndCreateExportedTestResultsPath() const;
 
+        // Generates a diff between two images of the same size.
+        void GenerateImageDiff(AZStd::span<const uint8_t> img1, AZStd::span<const uint8_t> img2, AZStd::vector<uint8_t>& buffer);
+
         ScriptReport* GetCurrentScriptReport();
 
+        AZStd::string SeeConsole(uint32_t issueCount, const char* searchString);
+        AZStd::string SeeBelow(uint32_t issueCount);
+        void HighlightTextIf(bool shouldSet, ImVec4 color);
+        void ResetTextHighlight();
+        void HighlightTextFailedOrWarning(bool isFailed, bool isWarning);
+
+        struct HighlightColorSettings
+        {
+            ImVec4 m_highlightPassed;
+            ImVec4 m_highlightFailed;
+            ImVec4 m_highlightWarning;
+
+            void UpdateColorSettings();
+        };
+
+        AZStd::multimap<float, ReportIndex, AZStd::greater<float>> m_descendingThresholdReports;
+        bool m_showReportsSortedByThreshold = true;
+
         ImGuiMessageBox m_messageBox;
-        FileIOErrorHandler m_fileIoErrorHandler;
 
         AZStd::vector<ImageComparisonToleranceLevel> m_availableToleranceLevels;
         AZStd::string m_invalidationMessage;
@@ -249,10 +300,19 @@ namespace AtomSampleViewer
         AZStd::vector<ScriptReport> m_scriptReports; //< Tracks errors for the current active script
         AZStd::vector<size_t> m_currentScriptIndexStack; //< Tracks which of the scripts in m_scriptReports is currently active
         bool m_showReportDialog = false;
+        bool m_colorHasBeenSet = false;
         DisplayOption m_displayOption = DisplayOption::AllResults;
         bool m_forceShowUpdateButtons = false; //< By default, the "Update" buttons are visible only for failed screenshots. This forces them to be visible.
+        bool m_forceShowExportPngDiffButtons = false; //< By default, "Export Png Diff" buttons are visible only for failed screenshots. This forces them to be visible.
         AZStd::string m_officialBaselineSourceFolder; //< Used for updating official baseline screenshots
         AZStd::string m_exportedTestResultsPath = "Click the 'Export Test Results' button."; //< Path to exported test results file (if exported).
+        AZStd::string m_uniqueTimestamp;
+        HighlightColorSettings m_highlightSettings;
+        ScriptResultsSummary m_resultsSummary;
+
+        // Flags set and used by ShowReportDialog()
+        bool m_showAll;
+        bool m_showWarnings;
     };
 
 } // namespace AtomSampleViewer

+ 1 - 0
Gem/Code/Source/Automation/ScriptableImGui.h

@@ -12,6 +12,7 @@
 #include <AzCore/std/string/string.h>
 #include <AzCore/std/containers/unordered_map.h>
 #include <AzCore/Math/Vector3.h>
+#include <AzCore/Memory/SystemAllocator.h>
 #include <imgui/imgui.h>
 
 #define SCRIPTABLE_IMGUI

+ 1 - 0
Gem/Code/Source/AuxGeomExampleComponent.cpp

@@ -71,6 +71,7 @@ namespace AtomSampleViewer
     
     void AuxGeomExampleComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
     {
+        AZ_PROFILE_SCOPE(AzRender, "AuxGeomExampleComponent: OnTick");
         if (m_imguiSidebar.Begin())
         {
             ImGui::Text("Draw Options");

+ 2 - 2
Gem/Code/Source/CheckerboardExampleComponent.cpp

@@ -56,7 +56,7 @@ namespace AtomSampleViewer
         m_meshHandle = m_meshFeatureProcessor->AcquireMesh(AZ::Render::MeshHandleDescriptor{ meshAsset }, materials);
         m_meshFeatureProcessor->SetTransform(m_meshHandle, AZ::Transform::CreateIdentity());
         
-        AZ::Debug::CameraControllerRequestBus::Event(m_cameraEntityId, &AZ::Debug::CameraControllerRequestBus::Events::Enable,
+        AZ::Debug::CameraControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::CameraControllerRequestBus::Events::Enable,
             azrtti_typeid<AZ::Debug::ArcBallControllerComponent>());
 
         // Add an Image based light.
@@ -107,7 +107,7 @@ namespace AtomSampleViewer
         // add the checker board pipeline
         AZ::RPI::RenderPipelineDescriptor pipelineDesc;
         pipelineDesc.m_mainViewTagName = "MainCamera";
-        pipelineDesc.m_name = "Checkerboard";
+        pipelineDesc.m_name = "CheckerboardPipeline";
         pipelineDesc.m_rootPassTemplate = "CheckerboardPipeline";
         m_cbPipeline = AZ::RPI::RenderPipeline::CreateRenderPipelineForWindow(pipelineDesc, *m_windowContext);
         m_scene->AddRenderPipeline(m_cbPipeline);

+ 0 - 5
Gem/Code/Source/CheckerboardExampleComponent.h

@@ -59,11 +59,6 @@ namespace AtomSampleViewer
         // shader ball model
         AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_meshHandle;
 
-        // Not owned by this sample, we look this up based on the config from the
-        // SampleComponentManager
-        AZ::EntityId m_cameraEntityId;
-        AzFramework::EntityContextId m_entityContextId;
-
         AZ::Render::MeshFeatureProcessorInterface* m_meshFeatureProcessor = nullptr;
         Utils::DefaultIBL m_defaultIbl;
 

+ 34 - 27
Gem/Code/Source/CommonSampleComponentBase.cpp

@@ -21,6 +21,16 @@ namespace AtomSampleViewer
     using namespace AZ;
     using namespace RPI;
 
+    void CommonSampleComponentBase::Reflect(ReflectContext* context)
+    {
+        if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
+        {
+            serializeContext->Class<CommonSampleComponentBase, Component>()
+                ->Version(0)
+                ;
+        }
+    }
+
     bool CommonSampleComponentBase::ReadInConfig(const ComponentConfig* baseConfig)
     {
         m_scene = RPI::RPISystemInterface::Get()->GetSceneByName(AZ::Name("RPI"));
@@ -67,11 +77,6 @@ namespace AtomSampleViewer
         skyboxFeatureProcessor->SetSkyboxMode(AZ::Render::SkyBoxMode::Cubemap);
         skyboxFeatureProcessor->Enable(true);
 
-        // We don't necessarily need an entity but PostProcessFeatureProcessorInterface needs an ID to retrieve ExposureControlSettingsInterface.
-        AzFramework::EntityContextRequestBus::EventResult(m_postProcessEntity, m_entityContextId, &AzFramework::EntityContextRequestBus::Events::CreateEntity, "postProcessEntity");
-        AZ_Assert(m_postProcessEntity != nullptr, "Failed to create post process entity.");
-        m_postProcessEntity->Activate();
-
         if (loadDefaultLightingPresets)
         {
             AZStd::list<AZ::Data::AssetInfo> lightingAssetInfoList;
@@ -95,16 +100,15 @@ namespace AtomSampleViewer
         static const char* InitialLightingPresetName = "Palermo Sidewalk";
         for (uint32_t index = 0; index < m_lightingPresets.size(); ++index)
         {
-            if (m_lightingPresets[index].m_displayName == InitialLightingPresetName)
+            if (AZ::StringFunc::Contains(m_lightingPresets[index].m_displayName, InitialLightingPresetName))
             {
                 m_currentLightingPresetIndex = index;
-                OnLightingPresetSelected(m_lightingPresets[m_currentLightingPresetIndex], m_useAlternateSkybox);
+                OnLightingPresetSelected(m_lightingPresets[m_currentLightingPresetIndex].m_preset, m_useAlternateSkybox);
                 break;
             }
         }
 
         AZ::TransformNotificationBus::MultiHandler::BusConnect(m_cameraEntityId);
-        AZ::EntityBus::MultiHandler::BusConnect(m_postProcessEntity->GetId());
     }
 
     void CommonSampleComponentBase::ShutdownLightingPresets()
@@ -118,12 +122,10 @@ namespace AtomSampleViewer
         }
         m_lightHandles.clear();
 
-        ClearLightingPresets();
+        AZ::Render::PostProcessFeatureProcessorInterface* postProcessFeatureProcessor = AZ::RPI::Scene::GetFeatureProcessorForEntityContextId<AZ::Render::PostProcessFeatureProcessorInterface>(m_entityContextId);
+        postProcessFeatureProcessor->RemoveSettingsInterface(GetEntityId());
 
-        if (m_postProcessEntity)
-        {
-            DestroyEntity(m_postProcessEntity, GetEntityContextId());
-        }
+        ClearLightingPresets();
 
         skyboxFeatureProcessor->Enable(false);
 
@@ -145,13 +147,28 @@ namespace AtomSampleViewer
             const AZ::Render::LightingPreset* preset = asset->GetDataAs<AZ::Render::LightingPreset>();
             if (preset)
             {
-                m_lightingPresets.push_back(*preset);
+                AZStd::string displayName;
+                AZ::StringFunc::Path::GetFullFileName(assetPath.c_str(), displayName);
+                AZ::StringFunc::Replace(displayName, ".lightingpreset.azasset", "");
+
+                AZStd::vector<AZStd::string> displayNameParts;
+                AZ::StringFunc::Tokenize(displayName, displayNameParts, "\\/, ()_-");
+                for (auto& displayNamePart : displayNameParts)
+                {
+                    displayNamePart[0] = aznumeric_cast<char>(toupper(displayNamePart[0]));
+                }
+
+                displayName.clear();
+                AZ::StringFunc::Join(displayName, displayNameParts, ' ');
+
+                m_lightingPresets.push_back({ displayName, *preset });
             }
+
             m_lightingPresetsDirty = true;
             if (!m_lightingPresets.empty() && m_currentLightingPresetIndex == InvalidLightingPresetIndex)
             {
                 m_currentLightingPresetIndex = 0;
-                OnLightingPresetSelected(m_lightingPresets[0], m_useAlternateSkybox);
+                OnLightingPresetSelected(m_lightingPresets[0].m_preset, m_useAlternateSkybox);
             }
         }
         else
@@ -208,7 +225,7 @@ namespace AtomSampleViewer
                 {
                     m_currentLightingPresetIndex = i;
                     m_useAlternateSkybox = useThisPresetWithAlternateSkybox;
-                    OnLightingPresetSelected(m_lightingPresets[i], m_useAlternateSkybox);
+                    OnLightingPresetSelected(m_lightingPresets[i].m_preset, m_useAlternateSkybox);
                 }
             }
             ScriptableImGui::EndCombo();
@@ -222,7 +239,7 @@ namespace AtomSampleViewer
         AZ::Render::ImageBasedLightFeatureProcessorInterface* iblFeatureProcessor = AZ::RPI::Scene::GetFeatureProcessorForEntityContextId<AZ::Render::ImageBasedLightFeatureProcessorInterface>(m_entityContextId);
         AZ::Render::DirectionalLightFeatureProcessorInterface* directionalLightFeatureProcessor = AZ::RPI::Scene::GetFeatureProcessorForEntityContextId<AZ::Render::DirectionalLightFeatureProcessorInterface>(m_entityContextId);
 
-        AZ::Render::ExposureControlSettingsInterface* exposureControlSettingInterface = postProcessFeatureProcessor->GetOrCreateSettingsInterface(m_postProcessEntity->GetId())->GetOrCreateExposureControlSettingsInterface();
+        AZ::Render::ExposureControlSettingsInterface* exposureControlSettingInterface = postProcessFeatureProcessor->GetOrCreateSettingsInterface(GetEntityId())->GetOrCreateExposureControlSettingsInterface();
 
         Camera::Configuration cameraConfig;
         Camera::CameraRequestBus::EventResult(cameraConfig, m_cameraEntityId, &Camera::CameraRequestBus::Events::GetCameraConfiguration);
@@ -234,8 +251,6 @@ namespace AtomSampleViewer
             directionalLightFeatureProcessor,
             cameraConfig,
             m_lightHandles,
-            nullptr,
-            AZ::RPI::MaterialPropertyIndex{},
             useAlternateSkybox);
     }
 
@@ -257,14 +272,6 @@ namespace AtomSampleViewer
         }
     }
 
-    void CommonSampleComponentBase::OnLightingPresetEntityShutdown(const AZ::EntityId& entityId)
-    {
-        if (m_postProcessEntity && m_postProcessEntity->GetId() == entityId)
-        {
-            m_postProcessEntity = nullptr;
-        }
-    }
-
     void CommonSampleComponentBase::PreloadAssets(const AZStd::vector<AssetCollectionAsyncLoader::AssetToLoadInfo>& assetList)
     {
         m_isAllAssetsReady = false;

+ 126 - 125
Gem/Code/Source/CommonSampleComponentBase.h

@@ -1,125 +1,126 @@
-/*
- * 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/Component.h>
-#include <AzCore/Component/TransformBus.h>
-#include <AzCore/Component/EntityBus.h>
-#include <AzFramework/Entity/EntityContextBus.h>
-#include <Atom/Feature/Mesh/MeshFeatureProcessorInterface.h>
-#include <Atom/Feature/Utils/LightingPreset.h>
-#include <Atom/Feature/SkyBox/SkyBoxFeatureProcessorInterface.h>
-#include <Atom/Feature/PostProcess/PostProcessFeatureProcessorInterface.h>
-#include <Atom/Feature/ImageBasedLights/ImageBasedLightFeatureProcessorInterface.h>
-#include <Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h>
-#include <Atom/Utils/AssetCollectionAsyncLoader.h>
-
-#include <Utils/ImGuiProgressList.h>
-
-namespace AtomSampleViewer
-{
-    class CommonSampleComponentBase
-        : public AZ::Component
-        , public AZ::TransformNotificationBus::MultiHandler
-        , public AZ::EntityBus::MultiHandler
-    {
-    public:
-        AZ_TYPE_INFO(MaterialHotReloadTestComponent, "{7EECDF09-B774-46C1-AD6E-060CE5717C05}");
-
-        // AZ::Component overrides...
-        bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override;
-
-    protected:
-        //! Init and shut down should be called in derived components' Activate() and Deactivate().
-        //! @param loadDefaultLightingPresets if true, it will scan all lighting presets in the project and load them.
-        void InitLightingPresets(bool loadDefaultLightingPresets = false);
-        void ShutdownLightingPresets();
-
-        //! Add a drop down list to select lighting preset for this sample.
-        //! Lighting presets must be loaded before calling this function, otherwise the list will be hide.
-        //! It should be called between ImGui::Begin() and ImGui::End().
-        //! e.g. Calling it between ImGuiSidebar::Begin() and ImGuiSidebar::End() will embed this list into the side bar.
-        void ImGuiLightingPreset();
-
-        //! Load lighting presets from an asset.
-        //! It will clear any presets loaded previously.
-        void LoadLightingPresetsFromAsset(const AZStd::string& assetPath);
-
-        //! Load lighting presets from an asset.
-        //! Append the presets to the current existing presets.
-        void AppendLightingPresetsFromAsset(const AZStd::string& assetPath);
-
-        //! Clear all lighting presets.
-        void ClearLightingPresets();
-
-        //! Reset internal scene related data
-        void ResetScene();
-
-        //! Apply lighting presets to the scene.
-        //! Derived samples can override this function to have custom behaviors.
-        virtual void OnLightingPresetSelected(const AZ::Render::LightingPreset& preset, bool useAltSkybox);
-
-        //! Return the AtomSampleViewer EntityContextId, retrieved from the ComponentConfig
-        AzFramework::EntityContextId GetEntityContextId() const;
-
-        //! Return the AtomSampleViewer camera EntityId, retrieved from the ComponentConfig
-        AZ::EntityId GetCameraEntityId() const;
-
-        AZ::Render::MeshFeatureProcessorInterface* GetMeshFeatureProcessor() const;
-
-        void OnLightingPresetEntityShutdown(const AZ::EntityId& entityId);
-
-        // Preload assets 
-        void PreloadAssets(const AZStd::vector<AZ::AssetCollectionAsyncLoader::AssetToLoadInfo>& assetList);
-
-        //! Async asset load
-        AZ::AssetCollectionAsyncLoader m_assetLoadManager;
-
-        //! Showing the loading progress of preload assets
-        ImGuiProgressList m_imguiProgressList;
-
-        // The callback might be called more than one time if there are more than one asset are ready in one frame.
-        // Use this flag to prevent OnAllAssetsReadyActivate be called more than one time.
-        bool m_isAllAssetsReady = false;
-
-        AZStd::string m_sampleName;
-
-        AZ::RPI::Scene* m_scene = nullptr;
-
-    private:
-        // AZ::TransformNotificationBus::MultiHandler overrides...
-        void OnTransformChanged(const AZ::Transform&, const AZ::Transform&) override;
-
-        // virtual call back function which is called when all preloaded assets are loaded.
-        virtual void OnAllAssetsReadyActivate() {};
-
-        AzFramework::EntityContextId m_entityContextId;
-        AZ::EntityId m_cameraEntityId;
-        mutable AZ::Render::MeshFeatureProcessorInterface* m_meshFeatureProcessor = nullptr;
-
-        //! All loaded lighting presets.
-        AZStd::vector<AZ::Render::LightingPreset> m_lightingPresets;
-
-        //! Lights created by lighting presets.
-        AZStd::vector<AZ::Render::DirectionalLightFeatureProcessorInterface::LightHandle> m_lightHandles;
-
-        //! Post process entity to handle ExposureControlSettings.
-        AZ::Entity* m_postProcessEntity = nullptr;
-
-        //! Dirty flag is set to true when m_lightingPresets is modified.
-        bool m_lightingPresetsDirty = true;
-
-        //! Current active lighting preset.
-        constexpr static int32_t InvalidLightingPresetIndex = -1;
-        int32_t m_currentLightingPresetIndex = InvalidLightingPresetIndex;
-        bool m_useAlternateSkybox = false; //!< LightingPresets have an alternate skybox that can be used, when this is true. This is usually a blurred version of the primary skybox.
-
-    };
-
-} // namespace AtomSampleViewer
+/*
+ * 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/Component.h>
+#include <AzCore/Component/TransformBus.h>
+#include <AzCore/Component/EntityBus.h>
+#include <AzFramework/Entity/EntityContextBus.h>
+#include <Atom/Feature/Mesh/MeshFeatureProcessorInterface.h>
+#include <Atom/Feature/Utils/LightingPreset.h>
+#include <Atom/Feature/SkyBox/SkyBoxFeatureProcessorInterface.h>
+#include <Atom/Feature/PostProcess/PostProcessFeatureProcessorInterface.h>
+#include <Atom/Feature/ImageBasedLights/ImageBasedLightFeatureProcessorInterface.h>
+#include <Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h>
+#include <Atom/Utils/AssetCollectionAsyncLoader.h>
+
+#include <Utils/ImGuiProgressList.h>
+
+namespace AtomSampleViewer
+{
+    class CommonSampleComponentBase
+        : public AZ::Component
+        , public AZ::TransformNotificationBus::MultiHandler
+        , public AZ::EntityBus::MultiHandler
+    {
+    public:
+        AZ_TYPE_INFO(CommonSampleComponentBase, "{7EECDF09-B774-46C1-AD6E-060CE5717C05}");
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        // AZ::Component overrides...
+        bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override;
+
+    protected:
+        //! Init and shut down should be called in derived components' Activate() and Deactivate().
+        //! @param loadDefaultLightingPresets if true, it will scan all lighting presets in the project and load them.
+        void InitLightingPresets(bool loadDefaultLightingPresets = false);
+        void ShutdownLightingPresets();
+
+        //! Add a drop down list to select lighting preset for this sample.
+        //! Lighting presets must be loaded before calling this function, otherwise the list will be hide.
+        //! It should be called between ImGui::Begin() and ImGui::End().
+        //! e.g. Calling it between ImGuiSidebar::Begin() and ImGuiSidebar::End() will embed this list into the side bar.
+        void ImGuiLightingPreset();
+
+        //! Load lighting presets from an asset.
+        //! It will clear any presets loaded previously.
+        void LoadLightingPresetsFromAsset(const AZStd::string& assetPath);
+
+        //! Load lighting presets from an asset.
+        //! Append the presets to the current existing presets.
+        void AppendLightingPresetsFromAsset(const AZStd::string& assetPath);
+
+        //! Clear all lighting presets.
+        void ClearLightingPresets();
+
+        //! Reset internal scene related data
+        void ResetScene();
+
+        //! Apply lighting presets to the scene.
+        //! Derived samples can override this function to have custom behaviors.
+        virtual void OnLightingPresetSelected(const AZ::Render::LightingPreset& preset, bool useAltSkybox);
+
+        //! Return the AtomSampleViewer EntityContextId, retrieved from the ComponentConfig
+        AzFramework::EntityContextId GetEntityContextId() const;
+
+        //! Return the AtomSampleViewer camera EntityId, retrieved from the ComponentConfig
+        AZ::EntityId GetCameraEntityId() const;
+
+        AZ::Render::MeshFeatureProcessorInterface* GetMeshFeatureProcessor() const;
+
+        // Preload assets 
+        void PreloadAssets(const AZStd::vector<AZ::AssetCollectionAsyncLoader::AssetToLoadInfo>& assetList);
+
+        //! Async asset load
+        AZ::AssetCollectionAsyncLoader m_assetLoadManager;
+
+        //! Showing the loading progress of preload assets
+        ImGuiProgressList m_imguiProgressList;
+
+        // The callback might be called more than one time if there are more than one asset are ready in one frame.
+        // Use this flag to prevent OnAllAssetsReadyActivate be called more than one time.
+        bool m_isAllAssetsReady = false;
+
+        AZStd::string m_sampleName;
+
+        AZ::RPI::Scene* m_scene = nullptr;
+
+    private:
+        // AZ::TransformNotificationBus::MultiHandler overrides...
+        void OnTransformChanged(const AZ::Transform&, const AZ::Transform&) override;
+
+        // virtual call back function which is called when all preloaded assets are loaded.
+        virtual void OnAllAssetsReadyActivate() {};
+
+        AzFramework::EntityContextId m_entityContextId;
+        AZ::EntityId m_cameraEntityId;
+        mutable AZ::Render::MeshFeatureProcessorInterface* m_meshFeatureProcessor = nullptr;
+
+        //! All loaded lighting presets.
+        struct LightingPresetEntry
+        {
+            AZStd::string m_displayName;
+            AZ::Render::LightingPreset m_preset;
+        };
+        AZStd::vector<LightingPresetEntry> m_lightingPresets;
+
+        //! Lights created by lighting presets.
+        AZStd::vector<AZ::Render::DirectionalLightFeatureProcessorInterface::LightHandle> m_lightHandles;
+
+        //! Dirty flag is set to true when m_lightingPresets is modified.
+        bool m_lightingPresetsDirty = true;
+
+        //! Current active lighting preset.
+        constexpr static int32_t InvalidLightingPresetIndex = -1;
+        int32_t m_currentLightingPresetIndex = InvalidLightingPresetIndex;
+        bool m_useAlternateSkybox = false; //!< LightingPresets have an alternate skybox that can be used, when this is true. This is usually a blurred version of the primary skybox.
+    };
+
+} // namespace AtomSampleViewer

+ 2 - 2
Gem/Code/Source/CullingAndLodExampleComponent.cpp

@@ -341,7 +341,7 @@ namespace AtomSampleViewer
             ImGui::Separator();
 
             ImGui::Text("Shadowmap Size");
-            if (ImGui::Combo( "Size", &m_directionalLightShadowmapSizeIndex, s_directionalLightShadowmapSizeLabels, AZ_ARRAY_SIZE(s_directionalLightShadowmapSizeLabels)))
+            if (ImGui::Combo( "Size", &m_directionalLightShadowmapSizeIndex, s_directionalLightShadowmapSizeLabels, aznumeric_cast<int>(AZStd::size(s_directionalLightShadowmapSizeLabels))))
             {
                 m_directionalLightFeatureProcessor->SetShadowmapSize(m_directionalLightHandle, s_shadowmapSizes[m_directionalLightShadowmapSizeIndex]);
             }
@@ -391,7 +391,7 @@ namespace AtomSampleViewer
             ImGui::Spacing();
 
             ImGui::Text("Filtering");
-            if (ImGui::Combo("Filter Method", &m_shadowFilterMethodIndex, s_shadowFilterMethodLabels, AZ_ARRAY_SIZE(s_shadowFilterMethodLabels)))
+            if (ImGui::Combo("Filter Method", &m_shadowFilterMethodIndex, s_shadowFilterMethodLabels, aznumeric_cast<int>(AZStd::size(s_shadowFilterMethodLabels))))
             {
                 m_directionalLightFeatureProcessor->SetShadowFilterMethod(m_directionalLightHandle, s_shadowFilterMethods[m_shadowFilterMethodIndex]);
             }

+ 6 - 6
Gem/Code/Source/DiffuseGIExampleComponent.cpp

@@ -351,12 +351,12 @@ namespace AtomSampleViewer
             AZ::Render::DiffuseProbeGridFeatureProcessorInterface* diffuseProbeGridFeatureProcessor = m_scene->GetFeatureProcessor<AZ::Render::DiffuseProbeGridFeatureProcessorInterface>();
             AZ::Transform transform = AZ::Transform::CreateIdentity();
 
-            m_origin.Set(0.3f, -0.25f, 0.5f);
+            m_origin.Set(-0.8f, 0.5f, -0.055f);
             transform.SetTranslation(m_origin);
             m_diffuseProbeGrid = diffuseProbeGridFeatureProcessor->AddProbeGrid(transform, AZ::Vector3(12.0f, 12.0f, 12.0f), AZ::Vector3(1.5f, 1.5f, 2.0f));
             diffuseProbeGridFeatureProcessor->SetAmbientMultiplier(m_diffuseProbeGrid, m_ambientMultiplier);
 
-            m_viewBias = 0.7f;
+            m_viewBias = 0.5f;
             diffuseProbeGridFeatureProcessor->SetViewBias(m_diffuseProbeGrid, m_viewBias);
 
             m_normalBias = 0.1f;
@@ -382,12 +382,12 @@ namespace AtomSampleViewer
         m_meshHandles[aznumeric_cast<uint32_t>(SponzaMeshes::Inside)] = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ m_sponzaModelAsset });
         GetMeshFeatureProcessor()->SetTransform(m_meshHandles[aznumeric_cast<uint32_t>(SponzaMeshes::Inside)], transform);
         
-        m_directionalLightPitch = AZ::DegToRad(-45.0f);
-        m_directionalLightYaw = AZ::DegToRad(62.0f);
+        m_directionalLightPitch = AZ::DegToRad(-65.0f);
+        m_directionalLightYaw = AZ::DegToRad(65.0f);
         m_directionalLightColor = AZ::Color(0.92f, 0.78f, 0.35f, 1.0f);
         m_directionalLightIntensity = 30.0f;
 
-        m_pointLightPos = AZ::Vector3(10.0f, -4.25f, 1.5f);
+        m_pointLightPos = AZ::Vector3(9.2f, -3.7f, 1.0f);
         m_pointLightColor = AZ::Color(1.0f, 0.0f, 0.0f, 1.0f);
         m_pointLightIntensity = 10.0f;
 
@@ -423,7 +423,7 @@ namespace AtomSampleViewer
             AZ::Render::DiffuseProbeGridFeatureProcessorInterface* diffuseProbeGridFeatureProcessor = m_scene->GetFeatureProcessor<AZ::Render::DiffuseProbeGridFeatureProcessorInterface>();
             transform = AZ::Transform::CreateIdentity();
         
-            m_origin.Set(1.4f, -1.25f, 5.0f);
+            m_origin.Set(0.0f, -0.275f, 5.0f);
             transform.SetTranslation(m_origin);
             m_diffuseProbeGrid = diffuseProbeGridFeatureProcessor->AddProbeGrid(transform, AZ::Vector3(35.0f, 45.0f, 25.0f), AZ::Vector3(3.0f, 3.0f, 4.0f));
             diffuseProbeGridFeatureProcessor->SetAmbientMultiplier(m_diffuseProbeGrid, m_ambientMultiplier);

+ 1 - 1
Gem/Code/Source/DiffuseGIExampleComponent.h

@@ -71,7 +71,7 @@ namespace AtomSampleViewer
 
         // directional light
         float m_directionalLightPitch = -AZ::Constants::QuarterPi;
-        float m_directionalLightYaw = 0.f;
+        float m_directionalLightYaw = 0.0f;
         float m_directionalLightIntensity = 20.0f;
         AZ::Color m_directionalLightColor;
 

+ 3 - 3
Gem/Code/Source/DynamicDrawExampleComponent.cpp

@@ -93,7 +93,7 @@ namespace AtomSampleViewer
 
         AZ_Assert(m_dynamicDraw->IsVertexSizeValid(sizeof(ExampleVertex)), "Invalid vertex format");
 
-        // Dynamic draw for pass        
+        // Dynamic draw for pass
         m_dynamicDraw1ForPass = RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext();
         m_dynamicDraw2ForPass = RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext();
 
@@ -111,7 +111,7 @@ namespace AtomSampleViewer
         m_dynamicDraw1ForPass->SetOutputScope(auxGeomPass);
         m_dynamicDraw1ForPass->EndInit();
 
-        m_dynamicDraw2ForPass->InitShader(shaderAsset);        
+        m_dynamicDraw2ForPass->InitShader(shaderAsset);
         m_dynamicDraw2ForPass->InitVertexFormat(vertexChannels);
         m_dynamicDraw2ForPass->AddDrawStateOptions(RPI::DynamicDrawContext::DrawStateOptions::BlendMode
             | RPI::DynamicDrawContext::DrawStateOptions::DepthState);
@@ -371,7 +371,7 @@ namespace AtomSampleViewer
             blendState.m_enable = false;
             m_dynamicDraw->SetTarget0BlendState(blendState);
             m_dynamicDraw1ForPass->SetTarget0BlendState(blendState);
-            m_dynamicDraw2ForPass->SetTarget0BlendState(blendState);                        
+            m_dynamicDraw2ForPass->SetTarget0BlendState(blendState);
 
             // draw two red quads via view
             drawSrg = m_dynamicDraw->NewDrawSrg();

+ 8 - 3
Gem/Code/Source/DynamicMaterialTestComponent.cpp

@@ -18,12 +18,13 @@
 #include <Atom/RPI.Reflect/Asset/AssetUtils.h>
 
 #include <AzCore/Component/Entity.h>
-#include <AzCore/Debug/EventTrace.h>
 #include <AzCore/Debug/Timer.h>
 #include <AzCore/Math/Random.h>
 
 #include <RHI/BasicRHIComponent.h>
 
+AZ_DECLARE_BUDGET(AtomSampleViewer);
+
 namespace AtomSampleViewer
 {
     using namespace AZ;
@@ -229,7 +230,7 @@ namespace AtomSampleViewer
 
     void DynamicMaterialTestComponent::OnTick(float deltaTime, ScriptTimePoint /*scriptTime*/)
     {
-        AZ_TRACE_METHOD();
+        AZ_PROFILE_FUNCTION(AtomSampleViewer);
 
         if (m_waitingForMeshes)
         {
@@ -301,7 +302,11 @@ namespace AtomSampleViewer
         if (updateMaterials)
         {
             m_materialConfigs[m_currentMaterialConfig].m_updateLatticeMaterials();
-            CompileMaterials();
         }
+
+        // Even if materials weren't changed on this frame, they still might need to be compiled to apply changes
+        // from a previous frame. This could be the result of a material that was just loaded or reinitialized on
+        // the previous frame, possibly caused by a shader variant hot-loading.
+        CompileMaterials();
     }
 } // namespace AtomSampleViewer

+ 60 - 7
Gem/Code/Source/EntityLatticeTestComponent.cpp

@@ -27,6 +27,10 @@ namespace AtomSampleViewer
     using namespace RPI;
 
     constexpr int32_t s_latticeSizeMax = ENTITY_LATTEST_TEST_COMPONENT_MAX;
+    constexpr float s_spacingMax = 100.0f;
+    constexpr float s_spacingMin = 0.5f;
+    constexpr float s_entityScaleMax = 10.0f;
+    constexpr float s_entityScaleMin = 0.1f;
 
     void EntityLatticeTestComponent::Reflect(ReflectContext* context)
     {
@@ -68,8 +72,8 @@ namespace AtomSampleViewer
         // We first rotate the model by 180 degrees before translating it. This is to make it face the camera as it did
         // when the world was Y-up.
         Transform transform = Transform::CreateRotationZ(Constants::Pi);
+        m_worldAabb = AZ::Aabb::CreateNull();
 
-        static Vector3 distance(5.0f, 5.0f, 5.0f);
         for (int32_t x = 0; x < m_latticeWidth; ++x)
         {
             for (int32_t y = 0; y < m_latticeDepth; ++y)
@@ -77,12 +81,14 @@ namespace AtomSampleViewer
                 for (int32_t z = 0; z < m_latticeHeight; ++z)
                 {
                     Vector3 position(
-                        static_cast<float>(x) * distance.GetX(),
-                        static_cast<float>(y) * distance.GetY(),
-                        static_cast<float>(z) * distance.GetZ());
+                        static_cast<float>(x) * m_spacingX,
+                        static_cast<float>(y) * m_spacingY,
+                        static_cast<float>(z) * m_spacingZ);
 
                     transform.SetTranslation(position);
+                    transform.SetUniformScale(m_entityScale);
                     CreateLatticeInstance(transform);
+                    m_worldAabb.AddPoint(transform.GetTranslation());
                 }
             }
         }
@@ -94,11 +100,33 @@ namespace AtomSampleViewer
         return m_latticeWidth * m_latticeHeight * m_latticeDepth;
     }
 
+    AZ::Aabb EntityLatticeTestComponent::GetLatticeAabb() const
+    {
+        return m_worldAabb;
+    }
+
     void EntityLatticeTestComponent::SetLatticeDimensions(uint32_t width, uint32_t depth, uint32_t height)
     {
-        m_latticeWidth = GetClamp<int32_t>(width, 1, s_latticeSizeMax);
-        m_latticeHeight = GetClamp<int32_t>(height, 1, s_latticeSizeMax);
-        m_latticeDepth = GetClamp<int32_t>(depth, 1, s_latticeSizeMax);
+        m_latticeWidth  = AZ::GetClamp<int32_t>(width, 1, s_latticeSizeMax);
+        m_latticeHeight = AZ::GetClamp<int32_t>(height, 1, s_latticeSizeMax);
+        m_latticeDepth  = AZ::GetClamp<int32_t>(depth, 1, s_latticeSizeMax);
+    }
+
+    void EntityLatticeTestComponent::SetLatticeSpacing( float spaceX, float spaceY, float spaceZ)
+    {
+        m_spacingX = AZ::GetClamp<float>(spaceX, s_spacingMin, s_spacingMax);
+        m_spacingY = AZ::GetClamp<float>(spaceY, s_spacingMin, s_spacingMax);
+        m_spacingZ = AZ::GetClamp<float>(spaceZ, s_spacingMin, s_spacingMax);
+    }
+
+    void EntityLatticeTestComponent::SetLatticeEntityScale(float scale)
+    {
+        m_entityScale = AZ::GetClamp<float>(scale, s_entityScaleMin, s_entityScaleMax);
+    }
+
+    void EntityLatticeTestComponent::SetIBLExposure(float exposure)
+    {
+        m_defaultIbl.SetExposure(exposure);
     }
 
     void EntityLatticeTestComponent::RenderImGuiLatticeControls()
@@ -118,6 +146,31 @@ namespace AtomSampleViewer
         ImGui::Text("Lattice Depth");
         latticeChanged |= ScriptableImGui::SliderInt("##LatticeDepth", &m_latticeDepth, 1, s_latticeSizeMax);
 
+        ImGui::Spacing();
+        ImGui::Separator();
+        ImGui::Spacing();
+
+        ImGui::Text("Lattice Spacing X");
+        latticeChanged |= ScriptableImGui::SliderFloat("##LatticeSpaceX", &m_spacingX, 0.5, s_spacingMax);
+
+        ImGui::Spacing();
+
+        ImGui::Text("Lattice Spacing Y");
+        latticeChanged |= ScriptableImGui::SliderFloat("##LatticeSpaceY", &m_spacingY, 0.5, s_spacingMax);
+
+        ImGui::Spacing();
+
+        ImGui::Text("Lattice Spacing Z");
+        latticeChanged |= ScriptableImGui::SliderFloat("##LatticeSpaceZ", &m_spacingZ, 0.5, s_spacingMax);
+
+        ImGui::Spacing();
+        ImGui::Separator();
+        ImGui::Spacing();
+
+        ImGui::Text("Entity Scale");
+        latticeChanged |= ScriptableImGui::SliderFloat("##EntityScale", &m_entityScale, 0.01, s_entityScaleMax);
+
+
         if (latticeChanged)
         {
             RebuildLattice();

+ 21 - 0
Gem/Code/Source/EntityLatticeTestComponent.h

@@ -12,6 +12,8 @@
 #include <Utils/Utils.h>
 #include <EntityLatticeTestComponent_Traits_Platform.h>
 
+#include <AzCore/Math/Aabb.h>
+
 struct ImGuiContext;
 
 namespace AtomSampleViewer
@@ -34,6 +36,10 @@ namespace AtomSampleViewer
         //! Returns total number of instances (width * height * depth)
         uint32_t GetInstanceCount() const;
         
+        //! Returns world space Aabb for the lattice.
+        //! The returned Aabb contains all the entity lattice positions. It does not include the mesh Aabb at each position.
+        AZ::Aabb GetLatticeAabb() const;
+
         //! Call this to render ImGui controls for controlling the size of the lattice.
         void RenderImGuiLatticeControls();
         
@@ -41,6 +47,10 @@ namespace AtomSampleViewer
         virtual void RebuildLattice();
 
         void SetLatticeDimensions(uint32_t width, uint32_t depth, uint32_t height);
+        void SetLatticeSpacing(float spaceX, float spaceY, float spaceZ);
+        void SetLatticeEntityScale(float scale);
+
+        void SetIBLExposure(float exposure);
 
     private:
 
@@ -59,10 +69,21 @@ namespace AtomSampleViewer
 
         void BuildLattice();
 
+    protected:
+        //! Contains the world space Aabb of the lattice positions. Doesn't include the mesh Aabb at each position.
+        AZ::Aabb m_worldAabb;
+
+    private:
         // These are signed to avoid casting with imgui controls.
         int32_t m_latticeWidth = ENTITY_LATTICE_TEST_COMPONENT_WIDTH;
         int32_t m_latticeHeight = ENTITY_LATTICE_TEST_COMPONENT_HEIGHT;
         int32_t m_latticeDepth = ENTITY_LATTICE_TEST_COMPONENT_DEPTH;
+
+        float m_spacingX = 5.0f;
+        float m_spacingY = 5.0f;
+        float m_spacingZ = 5.0f;
+
+        float m_entityScale = 1.0f;
         
         Utils::DefaultIBL m_defaultIbl;
     };

+ 1 - 1
Gem/Code/Source/LightCullingExampleComponent.cpp

@@ -96,7 +96,7 @@ namespace AtomSampleViewer
         m_sampleName = "LightCullingExampleComponent";
 
         // Add some initial lights to illuminate the scene
-        m_settings[(int)LightType::Point].m_numActive = 150;
+        m_settings[(int)LightType::Point].m_numActive = 10;
         m_settings[(int)LightType::Disk].m_intensity = 40.0f;
         m_settings[(int)LightType::Capsule].m_intensity = 10.0f;
     }

+ 3 - 11
Gem/Code/Source/MaterialHotReloadTestComponent.cpp

@@ -212,14 +212,10 @@ namespace AtomSampleViewer
 
         if (AZ::IO::LocalFileIO::GetInstance()->Exists(deletePath.c_str()))
         {
-            m_fileIoErrorHandler.BusConnect();
-
             if (!AZ::IO::LocalFileIO::GetInstance()->Remove(deletePath.c_str()))
             {
-                m_fileIoErrorHandler.ReportLatestIOError(AZStd::string::format("Failed to delete '%s'.", deletePath.c_str()));
+                AZ_Error("MaterialHotReloadTestComponent", false, "Failed to delete '%s'.", deletePath.c_str());
             }
-
-            m_fileIoErrorHandler.BusDisconnect();
         }
     }
 
@@ -232,23 +228,19 @@ namespace AtomSampleViewer
         AZ::IO::Path copyFrom = AZ::IO::Path(m_testDataFolder).Append(testDataFile);
         AZ::IO::Path copyTo = AZ::IO::Path(m_tempSourceFolder).Append(tempSourceFile);
 
-        m_fileIoErrorHandler.BusConnect();
-
         auto readResult = AZ::Utils::ReadFile(copyFrom.c_str());
         if (!readResult.IsSuccess())
         {
-            m_fileIoErrorHandler.ReportLatestIOError(readResult.GetError());
+            AZ_Error("MaterialHotReloadTestComponent", false, "%s", readResult.GetError().c_str());
             return;
         }
 
         auto writeResult = AZ::Utils::WriteFile(readResult.GetValue(), copyTo.c_str());
         if (!writeResult.IsSuccess())
         {
-            m_fileIoErrorHandler.ReportLatestIOError(writeResult.GetError());
+            AZ_Error("MaterialHotReloadTestComponent", false, "%s", writeResult.GetError().c_str());
             return;
         }
-
-        m_fileIoErrorHandler.BusDisconnect();
     }
 
     const char* ToString(AzFramework::AssetSystem::AssetStatus status)

+ 0 - 3
Gem/Code/Source/MaterialHotReloadTestComponent.h

@@ -10,7 +10,6 @@
 
 #include <CommonSampleComponentBase.h>
 #include <Utils/ImGuiSidebar.h>
-#include <Utils/FileIOErrorHandler.h>
 #include <AzCore/Component/TickBus.h>
 #include <Atom/RPI.Reflect/Material/MaterialAsset.h>
 #include <Atom/RPI.Reflect/Model/ModelAsset.h>
@@ -108,8 +107,6 @@ namespace AtomSampleViewer
         AZ::Data::Instance<AZ::RPI::Material> m_shaderVariantIndicatorMaterial_fullyBaked;
         AZ::Data::Instance<AZ::RPI::Material>  m_shaderVariantIndicatorMaterial_current;
 
-        FileIOErrorHandler m_fileIoErrorHandler;
-
         float m_clearAssetsTimeout = 0.0f;
     };
 } // namespace AtomSampleViewer

+ 10 - 8
Gem/Code/Source/MeshExampleComponent.cpp

@@ -381,15 +381,18 @@ namespace AtomSampleViewer
         {
             AZ::Transform groundPlaneTransform = AZ::Transform::CreateIdentity();
 
-            AZ::Vector3 modelCenter;
-            float modelRadius;
-            m_modelAsset->GetAabb().GetAsSphere(modelCenter, modelRadius);
+            if (m_modelAsset)
+            {
+                AZ::Vector3 modelCenter;
+                float modelRadius;
+                m_modelAsset->GetAabb().GetAsSphere(modelCenter, modelRadius);
 
-            static const float GroundPlaneRelativeScale = 4.0f;
-            static const float GroundPlaneOffset = 0.01f;
+                static const float GroundPlaneRelativeScale = 4.0f;
+                static const float GroundPlaneOffset = 0.01f;
 
-            groundPlaneTransform.SetUniformScale(GroundPlaneRelativeScale * modelRadius);
-            groundPlaneTransform.SetTranslation(AZ::Vector3(0.0f, 0.0f, m_modelAsset->GetAabb().GetMin().GetZ() - GroundPlaneOffset));
+                groundPlaneTransform.SetUniformScale(GroundPlaneRelativeScale * modelRadius);
+                groundPlaneTransform.SetTranslation(AZ::Vector3(0.0f, 0.0f, m_modelAsset->GetAabb().GetMin().GetZ() - GroundPlaneOffset));
+            }
 
             GetMeshFeatureProcessor()->SetTransform(m_groundPlandMeshHandle, groundPlaneTransform);
         }
@@ -402,7 +405,6 @@ namespace AtomSampleViewer
 
     void MeshExampleComponent::OnEntityDestruction(const AZ::EntityId& entityId)
     {
-        OnLightingPresetEntityShutdown(entityId);
         AZ::EntityBus::MultiHandler::BusDisconnect(entityId);
     }
 

+ 0 - 3
Gem/Code/Source/MeshExampleComponent.h

@@ -86,9 +86,6 @@ namespace AtomSampleViewer
         static const char* CameraControllerNameTable[CameraControllerCount];
         CameraControllerType m_currentCameraControllerType = CameraControllerType::ArcBall;
 
-        // Not owned by this sample, we look this up
-        AZ::Component* m_cameraControlComponent = nullptr;
-
         AZ::Render::MeshFeatureProcessorInterface::ModelChangedEvent::Handler m_changedHandler;
         
         static constexpr float ArcballRadiusMinModifier = 0.01f;

+ 1 - 1
Gem/Code/Source/MultiSceneExampleComponent.h

@@ -16,6 +16,7 @@
 #include <AzCore/Asset/AssetCommon.h>
 #include <AzCore/Component/TickBus.h>
 
+#include <AzFramework/Scene/Scene.h>
 #include <AzFramework/Windowing/WindowBus.h>
 #include <AzFramework/Windowing/NativeWindow.h>
 
@@ -23,7 +24,6 @@
 #include <Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h>
 #include <Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h>
 #include <Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessorInterface.h>
-#include <Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessor.h>
 
 #include <Utils/Utils.h>
 

+ 1 - 1
Gem/Code/Source/Passes/RayTracingAmbientOcclusionPass.cpp

@@ -217,7 +217,7 @@ namespace AZ
                 return;
             }
 
-            RPI::PassAttachment* outputAttachment = GetOutputBinding(0).m_attachment.get();
+            RPI::PassAttachment* outputAttachment = GetOutputBinding(0).GetAttachment().get();
             RHI::Size targetImageSize = outputAttachment->m_descriptor.m_image.m_size;
 
             const RHI::ShaderResourceGroup* shaderResourceGroups[] =

+ 66 - 0
Gem/Code/Source/Performance/100KDraw_10KDrawable_MultiView_ExampleComponent.cpp

@@ -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
+ *
+ */
+
+#include <Performance/100KDraw_10KDrawable_MultiView_ExampleComponent.h>
+#include <SampleComponentManager.h>
+#include <SampleComponentConfig.h>
+
+#include <AzCore/Serialization/SerializeContext.h>
+
+AZ_DECLARE_BUDGET(AtomSampleViewer);
+
+namespace AtomSampleViewer
+{
+    using namespace AZ;
+
+    void _100KDraw10KDrawableExampleComponent::Reflect(AZ::ReflectContext* context)
+    {
+        Base::Reflect(context);
+        if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<_100KDraw10KDrawableExampleComponent, HighInstanceTestComponent>()
+                ->Version(0)
+                ;
+        }
+    }
+
+
+    _100KDraw10KDrawableExampleComponent::_100KDraw10KDrawableExampleComponent() 
+    {
+        m_sampleName = "100KDraw10KEntityTest";
+        InitDefaultValues(m_testParameters);
+    }
+
+    void _100KDraw10KDrawableExampleComponent::InitDefaultValues(HighInstanceTestParameters& defaultParameters)
+    {
+        defaultParameters.m_latticeSize[0] = 22;
+        defaultParameters.m_latticeSize[1] = 22;
+        defaultParameters.m_latticeSize[2] = 22;
+
+        defaultParameters.m_latticeSpacing[0] = 3.0f;
+        defaultParameters.m_latticeSpacing[1] = 3.0f;
+        defaultParameters.m_latticeSpacing[2] = 3.0f;
+
+        defaultParameters.m_numShadowCastingSpotLights = 7;
+        defaultParameters.m_shadowSpotlightInnerAngleDeg = 30.0f;
+        defaultParameters.m_shadowSpotlightOuterAngleDeg = 30.0f;
+        defaultParameters.m_shadowSpotlightMaxDistance = 200.0f;
+        defaultParameters.m_shadowSpotlightIntensity = 50000.f;
+
+        defaultParameters.m_activateDirectionalLight = true;
+        defaultParameters.m_directionalLightIntensity = 5.0f;
+
+        defaultParameters.m_cameraPosition[0] = -81.0f;
+        defaultParameters.m_cameraPosition[1] = 31.0f;
+        defaultParameters.m_cameraPosition[2] = 32.0f;
+        defaultParameters.m_cameraHeadingDeg = -90.0f;
+        defaultParameters.m_cameraPitchDeg = 0.0f;
+        defaultParameters.m_iblExposure = -10.0f;
+    }
+
+} // namespace AtomSampleViewer

+ 34 - 0
Gem/Code/Source/Performance/100KDraw_10KDrawable_MultiView_ExampleComponent.h

@@ -0,0 +1,34 @@
+/*
+ * 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 <Performance/HighInstanceExampleComponent.h>
+
+namespace AtomSampleViewer
+{
+    //! This test loads a 22x22x22 (~10K) lattice of cubes with a white material, shadowed lights are added to the scene until 100K draws are required. 
+    //! This test benchmarks how well atom scales cpu time with the number of draws. 
+    class _100KDraw10KDrawableExampleComponent
+        : public HighInstanceTestComponent
+    {
+        using Base = HighInstanceTestComponent;
+
+    public:
+        AZ_COMPONENT(_100KDraw10KDrawableExampleComponent, "{85C4BEE0-9FBC-4E9A-8425-12555703CC61}", HighInstanceTestComponent);
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        _100KDraw10KDrawableExampleComponent();
+
+    private:
+        AZ_DISABLE_COPY_MOVE(_100KDraw10KDrawableExampleComponent);
+
+        static void InitDefaultValues(HighInstanceTestParameters& defaultParameters);
+    };
+} // namespace AtomSampleViewer

+ 60 - 0
Gem/Code/Source/Performance/100KDrawable_SingleView_ExampleComponent.cpp

@@ -0,0 +1,60 @@
+/*
+ * 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 <Performance/100KDrawable_SingleView_ExampleComponent.h>
+#include <SampleComponentManager.h>
+#include <SampleComponentConfig.h>
+
+#include <AzCore/Serialization/SerializeContext.h>
+
+AZ_DECLARE_BUDGET(AtomSampleViewer);
+
+namespace AtomSampleViewer
+{
+    using namespace AZ;
+
+    void _100KDrawableExampleComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<_100KDrawableExampleComponent, HighInstanceTestComponent>()
+                ->Version(0)
+                ;
+        }
+    }
+
+
+    _100KDrawableExampleComponent::_100KDrawableExampleComponent() 
+    {
+        m_sampleName = "100KEntityTest";
+        InitDefaultValues(m_testParameters);
+    }
+
+    void _100KDrawableExampleComponent::InitDefaultValues(HighInstanceTestParameters& defaultParameters)
+    {
+        defaultParameters.m_latticeSize[0] = 46;
+        defaultParameters.m_latticeSize[1] = 46;
+        defaultParameters.m_latticeSize[2] = 46;
+
+        defaultParameters.m_latticeSpacing[0] = 3.0f;
+        defaultParameters.m_latticeSpacing[1] = 3.0f;
+        defaultParameters.m_latticeSpacing[2] = 3.0f;
+
+        defaultParameters.m_numShadowCastingSpotLights = 0;
+        defaultParameters.m_activateDirectionalLight = false;
+
+        defaultParameters.m_cameraPosition[0] = -173.0f;
+        defaultParameters.m_cameraPosition[1] = 66.0f;
+        defaultParameters.m_cameraPosition[2] = 68.0f;
+        defaultParameters.m_cameraHeadingDeg = -90.0f;
+        defaultParameters.m_cameraPitchDeg = 0.0f;
+        defaultParameters.m_iblExposure = 0.0f;
+    }
+
+    
+} // namespace AtomSampleViewer

+ 33 - 0
Gem/Code/Source/Performance/100KDrawable_SingleView_ExampleComponent.h

@@ -0,0 +1,33 @@
+/*
+ * 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 <Performance/HighInstanceExampleComponent.h>
+
+namespace AtomSampleViewer
+{
+    //! This test loads a 46x46x46 lattice of cubes with a white material. This test is used as a simple cpu performance stress test for Atom.
+    class _100KDrawableExampleComponent
+        : public HighInstanceTestComponent
+    {
+        using Base = HighInstanceTestComponent;
+
+    public:
+        AZ_COMPONENT(_100KDrawableExampleComponent, "{A373EDC7-399F-49BB-A3A9-D81FA7E05E60}", HighInstanceTestComponent);
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        _100KDrawableExampleComponent();
+
+    private:
+        AZ_DISABLE_COPY_MOVE(_100KDrawableExampleComponent);
+
+        static void InitDefaultValues(HighInstanceTestParameters& defaultParameters);
+    };
+} // namespace AtomSampleViewer

+ 545 - 0
Gem/Code/Source/Performance/HighInstanceExampleComponent.cpp

@@ -0,0 +1,545 @@
+/*
+ * 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 <Performance/HighInstanceExampleComponent.h>
+#include <SampleComponentManager.h>
+#include <SampleComponentConfig.h>
+
+#include <Atom/Component/DebugCamera/NoClipControllerBus.h>
+
+#include <Atom/RPI.Reflect/Model/ModelAsset.h>
+#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
+#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
+#include <Atom/RPI.Public/AuxGeom/AuxGeomFeatureProcessorInterface.h>
+#include <Atom/RPI.Public/AuxGeom/AuxGeomDraw.h>
+
+#include <Automation/ScriptRunnerBus.h>
+
+#include <AzCore/Serialization/SerializeContext.h>
+#include <AzFramework/Windowing/WindowBus.h>
+
+#include <RHI/BasicRHIComponent.h>
+
+AZ_DECLARE_BUDGET(AtomSampleViewer);
+
+namespace AtomSampleViewer
+{
+    using namespace AZ;
+
+    void HighInstanceTestComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<HighInstanceTestComponent, EntityLatticeTestComponent>()
+                ->Version(0)
+                ;
+        }
+    }
+
+
+    HighInstanceTestComponent::HighInstanceTestComponent() 
+        : m_materialBrowser("@user@/HighInstanceTestComponent/material_browser.xml")
+        , m_modelBrowser("@user@/HighInstanceTestComponent/model_browser.xml")
+        , m_imguiSidebar("@user@/HighInstanceTestComponent/sidebar.xml")
+    {
+        m_materialBrowser.SetFilter([](const AZ::Data::AssetInfo& assetInfo)
+        {
+            return assetInfo.m_assetType == azrtti_typeid<AZ::RPI::MaterialAsset>();
+        });
+
+        m_modelBrowser.SetFilter([](const AZ::Data::AssetInfo& assetInfo)
+        {
+            return assetInfo.m_assetType == azrtti_typeid<AZ::RPI::ModelAsset>();
+        });
+
+        // Only use a diffuse white material so light colors are easily visible.
+        const AZStd::vector<AZStd::string> materialAllowlist =
+        {
+            "materials/presets/macbeth/19_white_9-5_0-05d.azmaterial",
+        };
+        m_materialBrowser.SetDefaultPinnedAssets(materialAllowlist);
+
+        m_pinnedMaterialCount = static_cast<uint32_t>(m_materialBrowser.GetPinnedAssets().size());
+
+        m_expandedModelList =
+        {
+            "materialeditor/viewportmodels/cone.azmodel",
+            "materialeditor/viewportmodels/cube.azmodel",
+            "materialeditor/viewportmodels/cylinder.azmodel",
+            "materialeditor/viewportmodels/platonicsphere.azmodel",
+            "materialeditor/viewportmodels/polarsphere.azmodel",
+            "materialeditor/viewportmodels/quadsphere.azmodel",
+            "materialeditor/viewportmodels/torus.azmodel",
+            "objects/cube.azmodel",
+            "objects/cylinder.azmodel",
+        };
+        m_simpleModelList =
+        {
+            "objects/cube.azmodel"
+        };
+        m_modelBrowser.SetDefaultPinnedAssets(m_simpleModelList);
+    }
+
+    void HighInstanceTestComponent::Activate()
+    {
+        BuildDiskLightParameters();
+
+        m_directionalLightFeatureProcessor = m_scene->GetFeatureProcessor<Render::DirectionalLightFeatureProcessorInterface>();
+        m_diskLightFeatureProcessor = m_scene->GetFeatureProcessor<Render::DiskLightFeatureProcessorInterface>();
+
+        AZ::TickBus::Handler::BusConnect();
+
+        m_imguiSidebar.Activate();
+        m_imguiSidebar.SetHideSidebar(true);
+        m_materialBrowser.Activate();
+        m_modelBrowser.Activate();
+
+        m_materialBrowser.ResetPinnedAssetsToDefault();
+
+        m_modelBrowser.ResetPinnedAssetsToDefault();
+
+        SetLatticeDimensions(
+            m_testParameters.m_latticeSize[0], 
+            m_testParameters.m_latticeSize[1], 
+            m_testParameters.m_latticeSize[2]);
+        SetLatticeSpacing(
+            m_testParameters.m_latticeSpacing[0],
+            m_testParameters.m_latticeSpacing[1],
+            m_testParameters.m_latticeSpacing[2]);
+        SetLatticeEntityScale(m_testParameters.m_entityScale);
+
+        Base::Activate();
+
+        AzFramework::NativeWindowHandle windowHandle = nullptr;
+        AzFramework::WindowSystemRequestBus::BroadcastResult(
+            windowHandle,
+            &AzFramework::WindowSystemRequestBus::Events::GetDefaultWindowHandle);
+
+        AzFramework::WindowRequestBus::EventResult(
+            m_preActivateVSyncInterval, 
+            windowHandle, 
+            &AzFramework::WindowRequestBus::Events::GetSyncInterval);
+
+        AzFramework::WindowRequestBus::Event(
+            windowHandle, 
+            &AzFramework::WindowRequestBus::Events::SetSyncInterval,
+            0);
+
+            SaveCameraConfiguration();
+            ResetNoClipController();
+
+            SetIBLExposure(m_testParameters.m_iblExposure);
+    }
+
+    void HighInstanceTestComponent::Deactivate()
+    {
+        DestroyLights();
+        RestoreCameraConfiguration();
+        AzFramework::NativeWindowHandle windowHandle = nullptr;
+        AzFramework::WindowSystemRequestBus::BroadcastResult(
+            windowHandle,
+            &AzFramework::WindowSystemRequestBus::Events::GetDefaultWindowHandle);
+
+        AzFramework::WindowRequestBus::Event(
+            windowHandle, 
+            &AzFramework::WindowRequestBus::Events::SetSyncInterval,
+            m_preActivateVSyncInterval);
+
+        m_materialBrowser.Deactivate();
+        m_modelBrowser.Deactivate();
+
+        AZ::TickBus::Handler::BusDisconnect();
+        m_imguiSidebar.Deactivate();
+        Base::Deactivate();
+    }
+    
+    void HighInstanceTestComponent::PrepareCreateLatticeInstances(uint32_t instanceCount)
+    {
+        m_modelInstanceData.reserve(instanceCount);
+        DestroyLights();
+    }
+
+    void HighInstanceTestComponent::CreateLatticeInstance(const AZ::Transform& transform)
+    {
+        m_modelInstanceData.emplace_back<ModelInstanceData>({});
+        ModelInstanceData& data = m_modelInstanceData.back();
+        data.m_modelAssetId = GetRandomModelId();
+        data.m_materialAssetId = GetRandomMaterialId();
+        data.m_transform = transform;
+    }
+
+    void HighInstanceTestComponent::FinalizeLatticeInstances()
+    {
+        AZStd::set<AZ::Data::AssetId> assetIds;
+
+        for (ModelInstanceData& instanceData : m_modelInstanceData)
+        {
+            if (instanceData.m_materialAssetId.IsValid())
+            {
+                assetIds.insert(instanceData.m_materialAssetId);
+            }
+
+            if (instanceData.m_modelAssetId.IsValid())
+            {
+                assetIds.insert(instanceData.m_modelAssetId);
+            }
+        }
+
+        AZStd::vector<AZ::AssetCollectionAsyncLoader::AssetToLoadInfo> assetList;
+
+        for (auto& assetId : assetIds)
+        {
+            AZ::Data::AssetInfo assetInfo;
+            AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, assetId);
+            if (assetInfo.m_assetId.IsValid())
+            {
+                AZ::AssetCollectionAsyncLoader::AssetToLoadInfo info;
+                info.m_assetPath = assetInfo.m_relativePath;
+                info.m_assetType = assetInfo.m_assetType;
+                assetList.push_back(info);
+            }
+        }
+
+        PreloadAssets(assetList);
+
+        // pause script and tick until assets are ready
+        ScriptRunnerRequestBus::Broadcast(&ScriptRunnerRequests::PauseScriptWithTimeout, 120.0f);
+        AZ::TickBus::Handler::BusDisconnect();
+
+        if (m_testParameters.m_numShadowCastingSpotLights > 0 && m_diskLightsEnabled)
+        {
+            CreateSpotLights();
+        }
+
+        if (m_testParameters.m_activateDirectionalLight && m_directionalLightEnabled)
+        {
+            CreateDirectionalLight();
+        }
+    }
+
+    void HighInstanceTestComponent::OnAllAssetsReadyActivate()
+    {
+        AZ::Render::MaterialAssignmentMap materials;
+        for (ModelInstanceData& instanceData : m_modelInstanceData)
+        {
+            AZ::Render::MaterialAssignment& defaultAssignment = materials[AZ::Render::DefaultMaterialAssignmentId];
+            defaultAssignment = {};
+
+            if (instanceData.m_materialAssetId.IsValid())
+            {
+                defaultAssignment.m_materialAsset.Create(instanceData.m_materialAssetId);
+                defaultAssignment.m_materialInstance = AZ::RPI::Material::FindOrCreate(defaultAssignment.m_materialAsset);
+
+                // cache the material when its loaded
+                m_cachedMaterials.insert(defaultAssignment.m_materialAsset);
+            }
+
+            if (instanceData.m_modelAssetId.IsValid())
+            {
+                AZ::Data::Asset<AZ::RPI::ModelAsset> modelAsset;
+                modelAsset.Create(instanceData.m_modelAssetId);
+
+                instanceData.m_meshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ modelAsset }, materials);
+                GetMeshFeatureProcessor()->SetTransform(instanceData.m_meshHandle, instanceData.m_transform);
+            }
+        }
+
+        ScriptRunnerRequestBus::Broadcast(&ScriptRunnerRequests::ResumeScript);
+        AZ::TickBus::Handler::BusConnect();
+    }
+
+    void HighInstanceTestComponent::DestroyLatticeInstances()
+    {
+        DestroyHandles();
+        m_modelInstanceData.clear();
+    }
+
+    void HighInstanceTestComponent::DestroyLights()
+    {
+        m_directionalLightFeatureProcessor->ReleaseLight(m_directionalLightHandle);
+        m_directionalLightHandle = {};
+        for (int index = 0; index < m_diskLights.size(); ++index)
+        {
+            DiskLightHandle& handle = m_diskLights[index].m_handle;
+            m_diskLightFeatureProcessor->ReleaseLight(handle);
+            handle = {};
+        }
+    }
+
+    void HighInstanceTestComponent::DestroyHandles()
+    {
+        for (ModelInstanceData& instanceData : m_modelInstanceData)
+        {
+            GetMeshFeatureProcessor()->ReleaseMesh(instanceData.m_meshHandle);
+            instanceData.m_meshHandle = {};
+        }
+    }
+
+    AZ::Data::AssetId HighInstanceTestComponent::GetRandomModelId() const
+    {
+        auto& modelAllowlist = m_modelBrowser.GetPinnedAssets();
+
+        if (modelAllowlist.size())
+        {
+            const size_t randomModelIndex = rand() % modelAllowlist.size();
+            return modelAllowlist[randomModelIndex].m_assetId;
+        }
+        else
+        {
+            return AZ::RPI::AssetUtils::GetAssetIdForProductPath("testdata/objects/cube/cube.azmodel", AZ::RPI::AssetUtils::TraceLevel::Error);
+        }
+    }
+
+    AZ::Data::AssetId HighInstanceTestComponent::GetRandomMaterialId() const
+    {
+        auto& materialAllowlist = m_materialBrowser.GetPinnedAssets();
+
+        if (materialAllowlist.size())
+        {
+            const size_t randomMaterialIndex = rand() % materialAllowlist.size();
+            return materialAllowlist[randomMaterialIndex].m_assetId;
+        }
+        else
+        {
+            return AZ::RPI::AssetUtils::GetAssetIdForProductPath(DefaultPbrMaterialPath, AZ::RPI::AssetUtils::TraceLevel::Error);
+        }
+    }
+
+
+    void HighInstanceTestComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint scriptTime)
+    {
+		AZ_PROFILE_FUNCTION(AtomSampleViewer);
+
+        if (m_updateTransformEnabled)
+        {
+            float radians = static_cast<float>(fmod(scriptTime.GetSeconds(), AZ::Constants::TwoPi));
+            AZ::Vector3 rotation(radians, radians, radians);
+            AZ::Transform rotationTransform;
+            rotationTransform.SetFromEulerRadians(rotation);
+
+            for (ModelInstanceData& instanceData : m_modelInstanceData)
+            {
+                GetMeshFeatureProcessor()->SetTransform(instanceData.m_meshHandle, instanceData.m_transform * rotationTransform);
+            }
+        }
+
+        bool currentUseSimpleModels = m_useSimpleModels;
+        bool diskLightsEnabled = m_diskLightsEnabled;
+        bool directionalLightEnabled = m_directionalLightEnabled;
+        if (m_imguiSidebar.Begin())
+        {
+            ImGui::Checkbox("Update Transforms Every Frame", &m_updateTransformEnabled);
+
+            ImGui::Spacing();
+            ImGui::Separator();
+            ImGui::Spacing();
+
+            RenderImGuiLatticeControls();
+
+            ImGui::Spacing();
+            ImGui::Separator();
+            ImGui::Spacing();
+
+            ImGui::Checkbox("Use simple models", &m_useSimpleModels);
+
+            ImGui::Spacing();
+            ImGui::Separator();
+            ImGui::Spacing();
+
+            ImGui::Checkbox("Display SpotLight Debug", &m_drawDiskLightDebug);
+
+            ImGui::Spacing();
+            ImGui::Separator();
+            ImGui::Spacing();
+
+            if (m_testParameters.m_numShadowCastingSpotLights > 0)
+            {
+                ImGui::Checkbox("Enable SpotLights", &m_diskLightsEnabled);
+            }
+            if (m_testParameters.m_activateDirectionalLight)
+            {
+                ImGui::Checkbox("Enable Directional Light", &m_directionalLightEnabled);
+            }
+
+            m_imguiSidebar.End();
+        }
+
+        if(diskLightsEnabled != m_diskLightsEnabled || directionalLightEnabled != m_directionalLightEnabled)
+        {
+            DestroyLights();
+            if (m_testParameters.m_numShadowCastingSpotLights > 0 && m_diskLightsEnabled)
+            {
+                CreateSpotLights();
+            }
+            if(m_testParameters.m_activateDirectionalLight && m_directionalLightEnabled)
+            {
+                CreateDirectionalLight();
+            }
+        }
+
+        if (currentUseSimpleModels != m_useSimpleModels)
+        {
+            m_modelBrowser.SetPinnedAssets(m_useSimpleModels?m_simpleModelList:m_expandedModelList);
+            for (ModelInstanceData& instanceData : m_modelInstanceData)
+            {
+                instanceData.m_modelAssetId = GetRandomModelId();
+            }
+            DestroyHandles();
+            DestroyLights();
+            FinalizeLatticeInstances();
+        }
+
+        DrawDiskLightDebugObjects();
+    }
+
+    void HighInstanceTestComponent::ResetNoClipController()
+    {
+        using namespace AZ;
+        using namespace AZ::Debug;
+        AZ::Vector3 camPos = AZ::Vector3::CreateFromFloat3(m_testParameters.m_cameraPosition);
+        Camera::CameraRequestBus::Event(GetCameraEntityId(), &Camera::CameraRequestBus::Events::SetFarClipDistance, 2000.0f);
+        NoClipControllerRequestBus::Event(GetCameraEntityId(), &NoClipControllerRequestBus::Events::SetPosition, camPos);
+        NoClipControllerRequestBus::Event(GetCameraEntityId(), &NoClipControllerRequestBus::Events::SetHeading, AZ::DegToRad(m_testParameters.m_cameraHeadingDeg));
+        NoClipControllerRequestBus::Event(GetCameraEntityId(), &NoClipControllerRequestBus::Events::SetPitch, AZ::DegToRad(m_testParameters.m_cameraPitchDeg));
+    }
+
+    void HighInstanceTestComponent::SaveCameraConfiguration()
+    {
+        Camera::CameraRequestBus::EventResult(m_originalFarClipDistance, GetCameraEntityId(), &Camera::CameraRequestBus::Events::GetFarClipDistance);
+    }
+
+    void HighInstanceTestComponent::RestoreCameraConfiguration()
+    {
+        Camera::CameraRequestBus::Event(GetCameraEntityId(), &Camera::CameraRequestBus::Events::SetFarClipDistance, m_originalFarClipDistance);
+    }
+
+    void HighInstanceTestComponent::CreateSpotLights()
+    {
+        for (int index = 0; index < m_testParameters.m_numShadowCastingSpotLights; ++index)
+        {
+            CreateSpotLight(index);
+        }
+   }
+
+    void HighInstanceTestComponent::CreateSpotLight(int index)
+    {
+        Render::DiskLightFeatureProcessorInterface* const featureProcessor = m_diskLightFeatureProcessor;
+        const DiskLightHandle handle = featureProcessor->AcquireLight();
+
+        AZ::Render::PhotometricColor<AZ::Render::PhotometricUnit::Candela> lightColor(m_diskLights[index].m_color * m_testParameters.m_shadowSpotlightIntensity);
+        featureProcessor->SetRgbIntensity(handle, lightColor);
+        featureProcessor->SetAttenuationRadius(handle, m_testParameters.m_shadowSpotlightMaxDistance);
+        featureProcessor->SetConeAngles(
+            handle, 
+            DegToRad(m_testParameters.m_shadowSpotlightInnerAngleDeg), 
+            DegToRad(m_testParameters.m_shadowSpotlightOuterAngleDeg));
+        featureProcessor->SetShadowsEnabled(handle, true);
+        featureProcessor->SetShadowmapMaxResolution(handle, m_testParameters.m_shadowmapSize);
+        featureProcessor->SetShadowFilterMethod(handle, m_testParameters.m_shadowFilterMethod);
+        featureProcessor->SetFilteringSampleCount(handle, 1);
+
+        const Vector3 aabbOffset = m_diskLights[index].m_relativePosition.GetNormalized() * (0.5f * m_testParameters.m_shadowSpotlightMaxDistance);
+        const Vector3 position = m_worldAabb.GetCenter() + aabbOffset;
+
+        featureProcessor->SetPosition(handle, position);
+        featureProcessor->SetDirection(handle, (-aabbOffset).GetNormalized());
+
+        m_diskLights[index].m_handle = handle;
+    }
+
+    const AZ::Color& HighInstanceTestComponent::GetNextLightColor()
+    {
+        static uint16_t colorIndex = 0;
+        static const AZStd::vector<AZ::Color> colors =
+        {
+            AZ::Colors::Red,
+            AZ::Colors::Green,
+            AZ::Colors::Blue,
+            AZ::Colors::Cyan,
+            AZ::Colors::Fuchsia,
+            AZ::Colors::Yellow,
+            AZ::Colors::SpringGreen
+        };
+
+        const AZ::Color& result = colors[colorIndex];
+        colorIndex = (colorIndex + 1) % colors.size();
+        return result;
+    }
+
+    AZ::Vector3 HighInstanceTestComponent::GetRandomDirection()
+    {
+        return AZ::Vector3(
+            m_random.GetRandomFloat() - 0.5f,
+            m_random.GetRandomFloat() - 0.5f,
+            m_random.GetRandomFloat() - 0.5f).GetNormalized();
+    }
+
+    void HighInstanceTestComponent::BuildDiskLightParameters()
+    {
+        m_random.SetSeed(0);
+        m_diskLights.clear();
+        m_diskLights.reserve(m_testParameters.m_numShadowCastingSpotLights);
+        for (int index = 0; index < m_testParameters.m_numShadowCastingSpotLights; ++index)
+        {
+            m_diskLights.emplace_back(
+                GetNextLightColor(),
+                GetRandomDirection(),
+                m_testParameters.m_shadowmapSize);
+        }
+    }
+
+    void HighInstanceTestComponent::CreateDirectionalLight()
+    {
+        Render::DirectionalLightFeatureProcessorInterface* featureProcessor = m_directionalLightFeatureProcessor;
+        const DirectionalLightHandle handle = featureProcessor->AcquireLight();
+
+        const auto lightTransform = Transform::CreateLookAt(
+            -m_worldAabb.GetMax(),
+            Vector3::CreateZero());
+        featureProcessor->SetDirection(
+            handle,
+            lightTransform.GetBasis(1));
+
+        featureProcessor->SetRgbIntensity(handle, AZ::Render::PhotometricColor<AZ::Render::PhotometricUnit::Lux>(AZ::Color::CreateOne() * m_testParameters.m_directionalLightIntensity));
+        featureProcessor->SetCascadeCount(handle, m_testParameters.m_numDirectionalLightShadowCascades);
+        featureProcessor->SetShadowmapSize(handle, m_testParameters.m_shadowmapSize);
+        featureProcessor->SetViewFrustumCorrectionEnabled(handle, false);
+        featureProcessor->SetShadowFilterMethod(handle, m_testParameters.m_shadowFilterMethod);
+        featureProcessor->SetFilteringSampleCount(handle, 1);
+        featureProcessor->SetGroundHeight(handle, 0.f);
+
+        m_directionalLightHandle = handle;
+    }
+
+
+
+    void HighInstanceTestComponent::DrawDiskLightDebugObjects()
+    {
+        if (auto auxGeom = AZ::RPI::AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(m_scene); 
+            m_drawDiskLightDebug && auxGeom)
+        {
+            for (int i = 0; i < m_diskLights.size(); ++i)
+            {
+                const auto& light = m_diskLights[i];
+                if (light.m_handle.IsValid())
+                {
+                    const Render::DiskLightData& lightData = m_diskLightFeatureProcessor->GetDiskData(light.m_handle);
+                    const float lightDistance = sqrt(1.0f/lightData.m_invAttenuationRadiusSquared);
+                    AZ::Vector3 position = AZ::Vector3::CreateFromFloat3(lightData.m_position.data());
+                    AZ::Vector3 direction = AZ::Vector3::CreateFromFloat3(lightData.m_direction.data()).GetNormalized();
+                    position += direction * lightDistance;
+                    direction *= -1.0f;
+                    float coneSlantLength = lightDistance / lightData.m_cosOuterConeAngle;
+                    float farEndCapRadius = sqrt(coneSlantLength * coneSlantLength - lightDistance * lightDistance);
+                    auxGeom->DrawCone(position, direction, farEndCapRadius, lightDistance, light.m_color, AZ::RPI::AuxGeomDraw::DrawStyle::Line);
+                }
+            }
+        }
+    }
+
+} // namespace AtomSampleViewer

+ 179 - 0
Gem/Code/Source/Performance/HighInstanceExampleComponent.h

@@ -0,0 +1,179 @@
+/*
+ * 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 <EntityLatticeTestComponent.h>
+#include <Utils/ImGuiSidebar.h>
+#include <Utils/ImGuiAssetBrowser.h>
+#include <AzCore/Component/TickBus.h>
+#include <AzCore/Math/Random.h>
+#include <AzCore/Math/Vector3.h>
+#include <AzCore/Math/Color.h>
+#include <AzCore/std/containers/vector.h>
+
+#include <Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h>
+#include <Atom/Feature/CoreLights/ShadowConstants.h>
+#include <Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h>
+
+namespace AtomSampleViewer
+{
+    //! This class is used as base of a set of simple cpu performance stress test for Atom.
+    //! This test loads a X*Y*Z lattice of entities with randomized meshes and materials and creates N shadow casting spotlights plus 0-1 Directional lights
+   struct HighInstanceTestParameters
+   {
+       int m_latticeSize[3] = {22, 22, 22};
+       float m_latticeSpacing[3] = {5.0f, 5.0f, 5.0f};
+       float m_entityScale = 1.0f;
+
+       AZ::Render::ShadowmapSize m_shadowmapSize = AZ::Render::ShadowmapSize::Size256;
+       AZ::Render::ShadowFilterMethod m_shadowFilterMethod = AZ::Render::ShadowFilterMethod::None;
+       int m_numShadowCastingSpotLights = 0;
+       float m_shadowSpotlightInnerAngleDeg = 10.0f;
+       float m_shadowSpotlightOuterAngleDeg = 30.0f;
+       float m_shadowSpotlightMaxDistance = 200.0f;
+       float m_shadowSpotlightIntensity = 500.f; // Value in Candela
+
+       bool m_activateDirectionalLight = false;
+       uint16_t m_numDirectionalLightShadowCascades = 4;
+       float m_directionalLightIntensity = 5.0f; // Value in Lux
+
+       float m_cameraPosition[3] = {0.0f, 0.0f, 0.0f};
+       float m_cameraHeadingDeg = -44.7f;
+       float m_cameraPitchDeg = 25.0f;
+       float m_iblExposure = 0.0f;
+   };
+    class HighInstanceTestComponent
+        : public EntityLatticeTestComponent
+        , public AZ::TickBus::Handler
+    {
+        using Base = EntityLatticeTestComponent;
+
+    public:
+        AZ_RTTI(HighInstanceTestComponent, "{DAA2B63B-7CC0-4696-A44F-49E53C6390B9}", EntityLatticeTestComponent);
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        HighInstanceTestComponent();
+        
+        //! AZ::Component overrides...
+        void Activate() override;
+        void Deactivate() override;
+
+    private:
+        AZ_DISABLE_COPY_MOVE(HighInstanceTestComponent);
+
+        // CommonSampleComponentBase overrides...
+        void OnAllAssetsReadyActivate() override;
+
+        //! EntityLatticeTestComponent overrides...
+        void PrepareCreateLatticeInstances(uint32_t instanceCount) override;
+        void CreateLatticeInstance(const AZ::Transform& transform) override;
+        void FinalizeLatticeInstances() override;
+        void DestroyLatticeInstances() override;
+        void DestroyLights();
+
+        void DestroyHandles();
+
+        AZ::Data::AssetId GetRandomModelId() const;
+        AZ::Data::AssetId GetRandomMaterialId() const;
+
+        void OnTick(float deltaTime, AZ::ScriptTimePoint scriptTime) override;
+
+        void ResetNoClipController();
+        void SaveCameraConfiguration();
+        void RestoreCameraConfiguration();
+
+        const AZ::Color& GetNextLightColor();
+        AZ::Vector3 GetRandomDirection();
+        void BuildDiskLightParameters();
+        void CreateSpotLights();
+        void CreateSpotLight(int index);
+        void DrawDiskLightDebugObjects();
+
+        void CreateDirectionalLight();
+
+    protected:
+        HighInstanceTestParameters m_testParameters;
+
+    private:
+        struct ModelInstanceData
+        {
+            AZ::Transform m_transform;
+            AZ::Data::AssetId m_modelAssetId;
+            AZ::Data::AssetId m_materialAssetId;
+            AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_meshHandle;
+        };
+
+        ImGuiSidebar m_imguiSidebar;
+        ImGuiAssetBrowser m_materialBrowser;
+        ImGuiAssetBrowser m_modelBrowser;
+        
+        AZStd::vector<ModelInstanceData> m_modelInstanceData;
+
+        struct Compare
+        {
+            bool operator()(const AZ::Data::Asset<AZ::RPI::MaterialAsset>& lhs, const AZ::Data::Asset<AZ::RPI::MaterialAsset>& rhs) const
+            {
+                if (lhs.GetId().m_guid == rhs.GetId().m_guid)
+                {
+                    return lhs.GetId().m_subId > rhs.GetId().m_subId;
+                }
+                return lhs.GetId().m_guid > rhs.GetId().m_guid;
+            }
+        };
+
+        using MaterialAssetSet = AZStd::set<AZ::Data::Asset<AZ::RPI::MaterialAsset>, Compare>;
+        MaterialAssetSet m_cachedMaterials;
+        uint32_t m_pinnedMaterialCount = 0;
+        uint32_t m_preActivateVSyncInterval = 0;
+        AZ::SimpleLcgRandom m_random;
+
+        AZStd::vector<AZStd::string> m_expandedModelList; // has models that are more expensive on the gpu
+        AZStd::vector<AZStd::string> m_simpleModelList; // Aims to keep the test cpu bottlenecked by using trivial geometry such as a cube
+        size_t m_lastPinnedModelCount = 0;
+
+        float m_originalFarClipDistance;
+        bool m_updateTransformEnabled = false;
+        bool m_useSimpleModels = true;
+
+        // light settings
+        using DirectionalLightHandle = AZ::Render::DirectionalLightFeatureProcessorInterface::LightHandle;
+        using DiskLightHandle = AZ::Render::DiskLightFeatureProcessorInterface::LightHandle;
+
+        class DiskLight
+        {
+        public:
+            DiskLight() = delete;
+            explicit DiskLight(
+                const AZ::Color& color,
+                const AZ::Vector3& relativePosition,
+                AZ::Render::ShadowmapSize shadowmapSize)
+                : m_color{ color }
+                , m_relativePosition{ relativePosition }
+                , m_shadowmapSize{ shadowmapSize }
+            {}
+            ~DiskLight() = default;
+
+            const AZ::Color m_color;
+            const AZ::Vector3 m_relativePosition;
+            const AZ::Render::ShadowmapSize m_shadowmapSize;
+            DiskLightHandle m_handle;
+        };
+
+        static constexpr float CutoffIntensity = 0.1f;
+
+        AZ::Render::DirectionalLightFeatureProcessorInterface* m_directionalLightFeatureProcessor = nullptr;
+        AZ::Render::DiskLightFeatureProcessorInterface* m_diskLightFeatureProcessor = nullptr;
+        DirectionalLightHandle m_directionalLightHandle;
+        AZStd::vector<DiskLight> m_diskLights;
+        bool m_drawDiskLightDebug = false;
+        bool m_diskLightsEnabled = true; // combined with test parameters to determine final state.
+        bool m_directionalLightEnabled = true; // combined with test parameters to determine final state.
+    };
+} // namespace AtomSampleViewer

+ 1 - 1
Gem/Code/Source/Platform/Android/SSRExampleComponent_Traits_Platform.h

@@ -9,4 +9,4 @@
 // Mali devices have a memory limitation of vertex buffer (180MB), so low resolution mesh model is needed.
 // https://community.arm.com/developer/tools-software/graphics/b/blog/posts/memory-limits-with-vulkan-on-mali-gpus
 // [ATOM-14947]
-#define ATOMSAMPLEVIEWER_TRAIT_SSR_SAMPLE_LUCY_MODEL_NAME                       "objects/lucy/lucy_low.azmodel"
+#define ATOMSAMPLEVIEWER_TRAIT_SSR_SAMPLE_HERMANUBIS_MODEL_NAME "objects/hermanubis/hermanubis_low.azmodel"

+ 1 - 1
Gem/Code/Source/Platform/Linux/EntityLatticeTestComponent_Traits_Platform.h

@@ -9,4 +9,4 @@
 #define ENTITY_LATTICE_TEST_COMPONENT_WIDTH                     5
 #define ENTITY_LATTICE_TEST_COMPONENT_HEIGHT                    5
 #define ENTITY_LATTICE_TEST_COMPONENT_DEPTH                     5
-#define ENTITY_LATTEST_TEST_COMPONENT_MAX                       20
+#define ENTITY_LATTEST_TEST_COMPONENT_MAX                       100

+ 1 - 1
Gem/Code/Source/Platform/Linux/SSRExampleComponent_Traits_Platform.h

@@ -6,4 +6,4 @@
  *
  */
 
-#define ATOMSAMPLEVIEWER_TRAIT_SSR_SAMPLE_LUCY_MODEL_NAME                       "objects/lucy/lucy_high.azmodel"
+#define ATOMSAMPLEVIEWER_TRAIT_SSR_SAMPLE_HERMANUBIS_MODEL_NAME "objects/hermanubis/hermanubis_high.azmodel"

+ 1 - 1
Gem/Code/Source/Platform/Mac/EntityLatticeTestComponent_Traits_Platform.h

@@ -9,4 +9,4 @@
 #define ENTITY_LATTICE_TEST_COMPONENT_WIDTH                     5
 #define ENTITY_LATTICE_TEST_COMPONENT_HEIGHT                    5
 #define ENTITY_LATTICE_TEST_COMPONENT_DEPTH                     5
-#define ENTITY_LATTEST_TEST_COMPONENT_MAX                       20
+#define ENTITY_LATTEST_TEST_COMPONENT_MAX                       100

+ 1 - 1
Gem/Code/Source/Platform/Mac/SSRExampleComponent_Traits_Platform.h

@@ -6,4 +6,4 @@
  *
  */
 
-#define ATOMSAMPLEVIEWER_TRAIT_SSR_SAMPLE_LUCY_MODEL_NAME                       "objects/lucy/lucy_high.azmodel"
+#define ATOMSAMPLEVIEWER_TRAIT_SSR_SAMPLE_HERMANUBIS_MODEL_NAME "objects/hermanubis/hermanubis_high.azmodel"

+ 1 - 1
Gem/Code/Source/Platform/Mac/Utils_Mac.cpp

@@ -32,7 +32,7 @@ namespace AtomSampleViewer
             if (childPid == 0)
             {
                 //In child process
-                char* args[] = { const_cast<char*>(executablePath.c_str()), const_cast<char*>(filePathA.c_str()), const_cast<char*>(filePathB.c_str()) };
+                char* args[] = { const_cast<char*>(executablePath.c_str()), const_cast<char*>(filePathA.c_str()), const_cast<char*>(filePathB.c_str()), static_cast<char*>(0)};
                 execv(executablePath.c_str(), args);
                 
                 AZ_TracePrintf("RunDiffTool", "RunDiffTool: Unable to launch Beyond Compare %s : errno = %s . Make sure you have installed Beyond Compare command line tools.", executablePath.c_str(), strerror(errno));

+ 1 - 1
Gem/Code/Source/Platform/Windows/EntityLatticeTestComponent_Traits_Platform.h

@@ -9,4 +9,4 @@
 #define ENTITY_LATTICE_TEST_COMPONENT_WIDTH                     5
 #define ENTITY_LATTICE_TEST_COMPONENT_HEIGHT                    5
 #define ENTITY_LATTICE_TEST_COMPONENT_DEPTH                     5
-#define ENTITY_LATTEST_TEST_COMPONENT_MAX                       10
+#define ENTITY_LATTEST_TEST_COMPONENT_MAX                       100

+ 1 - 1
Gem/Code/Source/Platform/Windows/SSRExampleComponent_Traits_Platform.h

@@ -6,4 +6,4 @@
  *
  */
 
-#define ATOMSAMPLEVIEWER_TRAIT_SSR_SAMPLE_LUCY_MODEL_NAME                       "objects/lucy/lucy_high.azmodel"
+#define ATOMSAMPLEVIEWER_TRAIT_SSR_SAMPLE_HERMANUBIS_MODEL_NAME "objects/hermanubis/hermanubis_high.azmodel"

+ 1 - 1
Gem/Code/Source/Platform/iOS/SSRExampleComponent_Traits_Platform.h

@@ -6,4 +6,4 @@
  *
  */
 
-#define ATOMSAMPLEVIEWER_TRAIT_SSR_SAMPLE_LUCY_MODEL_NAME                       "objects/lucy/lucy_low.azmodel"
+#define ATOMSAMPLEVIEWER_TRAIT_SSR_SAMPLE_HERMANUBIS_MODEL_NAME "objects/hermanubis/hermanubis_low.azmodel"

+ 2 - 2
Gem/Code/Source/RHI/BasicRHIComponent.cpp

@@ -549,8 +549,8 @@ namespace AtomSampleViewer
         // Validate the compatibility of the image sub resources
         for (uint32_t i = 1; i < imageSubresourceLayouts.size(); i++)
         {
-            const bool compatibleFormat = imageDescriptors[0].m_format == imageDescriptors[i].m_format;
-            const bool compatibleLayout = imageSubresourceLayouts[0].m_size == imageSubresourceLayouts[i].m_size &&
+            [[maybe_unused]] const bool compatibleFormat = imageDescriptors[0].m_format == imageDescriptors[i].m_format;
+            [[maybe_unused]] const bool compatibleLayout = imageSubresourceLayouts[0].m_size == imageSubresourceLayouts[i].m_size &&
                 imageSubresourceLayouts[0].m_rowCount == imageSubresourceLayouts[i].m_rowCount &&
                 imageSubresourceLayouts[0].m_bytesPerRow == imageSubresourceLayouts[i].m_bytesPerRow &&
                 imageSubresourceLayouts[0].m_bytesPerImage == imageSubresourceLayouts[i].m_bytesPerImage;

+ 1 - 2
Gem/Code/Source/RHI/BindlessPrototypeExampleComponent.cpp

@@ -20,7 +20,6 @@
 #include <AzCore/Math/MatrixUtils.h>
 
 #include <AzCore/Component/Entity.h>
-#include <AzCore/Debug/EventTrace.h>
 
 #include <AzFramework/Components/CameraBus.h>
 
@@ -287,7 +286,7 @@ namespace AtomSampleViewer
             {
                 for (int32_t heightIdx = 0; heightIdx < m_objectCountDepth; heightIdx++)
                 {
-                    const int32_t objectCount = widthIdx * depthIdx * heightIdx;
+                    [[maybe_unused]] const int32_t objectCount = widthIdx * depthIdx * heightIdx;
                     AZ_Assert(static_cast<uint32_t>(objectCount) < m_objectCount, "Spawning too many objects");
 
                     // Calculate the object position

+ 0 - 2
Gem/Code/Source/RHI/CopyQueueComponent.cpp

@@ -210,8 +210,6 @@ namespace AtomSampleViewer
                 {
                     RHI::ImageScopeAttachmentDescriptor desc;
                     desc.m_attachmentId = m_outputAttachmentId;
-                    desc.m_loadStoreAction.m_clearValue = RHI::ClearValue::CreateVector4Float(0.0f, 0.0, 0.0, 0.0);
-                    desc.m_loadStoreAction.m_loadAction = RHI::AttachmentLoadAction::Clear;
                     frameGraph.UseColorAttachment(desc);
                 }
 

+ 1 - 1
Gem/Code/Source/RHI/IndirectRenderingExampleComponent.cpp

@@ -239,7 +239,7 @@ namespace AtomSampleViewer
                 sizeof(uint32_t)
             };
 
-            RHI::ValidateStreamBufferViews(m_inputStreamLayout, AZStd::array_view<RHI::StreamBufferView>(m_streamBufferViews.data(), 2));
+            RHI::ValidateStreamBufferViews(m_inputStreamLayout, AZStd::span<const RHI::StreamBufferView>(m_streamBufferViews.data(), 2));
         }
     }
 

+ 2 - 2
Gem/Code/Source/RHI/InputAssemblyExampleComponent.cpp

@@ -370,7 +370,7 @@ namespace AtomSampleViewer
                 {
                     m_streamBufferView[0] = {inputAssemblyBufferView->GetBuffer(), 0, sizeof(BufferData), sizeof(BufferData::value_type)};
 
-                    RHI::ValidateStreamBufferViews(m_inputStreamLayout, AZStd::array_view<RHI::StreamBufferView>(&m_streamBufferView[0], 1));
+                    RHI::ValidateStreamBufferViews(m_inputStreamLayout, AZStd::span<const RHI::StreamBufferView>(&m_streamBufferView[0], 1));
                 }
             }
 
@@ -380,7 +380,7 @@ namespace AtomSampleViewer
                 {
                     m_streamBufferView[1] = {inputAssemblyBufferView->GetBuffer(), 0, sizeof(BufferData), sizeof(BufferData::value_type)};
 
-                    RHI::ValidateStreamBufferViews(m_inputStreamLayout, AZStd::array_view<RHI::StreamBufferView>(&m_streamBufferView[1], 1));
+                    RHI::ValidateStreamBufferViews(m_inputStreamLayout, AZStd::span<const RHI::StreamBufferView>(&m_streamBufferView[1], 1));
                 }
             }
         };

+ 1 - 3
Gem/Code/Source/RHI/MRTExampleComponent.cpp

@@ -324,12 +324,10 @@ namespace AtomSampleViewer
 
         const auto prepareFunctionScreen = [this](RHI::FrameGraphInterface frameGraph, [[maybe_unused]] ScopeData& scopeData)
         {
-            // Binds the swap chain as a color attachment. Clears it to white.
+            // Binds the swap chain as a color attachment.
             {
                 RHI::ImageScopeAttachmentDescriptor descriptor;
                 descriptor.m_attachmentId = m_outputAttachmentId;
-                descriptor.m_loadStoreAction.m_clearValue = RHI::ClearValue::CreateVector4Float(1.0f, 1.0, 1.0, 0.0);
-                descriptor.m_loadStoreAction.m_loadAction = RHI::AttachmentLoadAction::Clear;
                 frameGraph.UseColorAttachment(descriptor);
             }
 

+ 1 - 1
Gem/Code/Source/RHI/QueryExampleComponent.cpp

@@ -192,7 +192,7 @@ namespace AtomSampleViewer
             layoutBuilder.AddBuffer()->Channel("POSITION", RHI::Format::R32G32B32_FLOAT);
             m_quadInputStreamLayout = layoutBuilder.End();
 
-            RHI::ValidateStreamBufferViews(m_quadInputStreamLayout, AZStd::array_view<RHI::StreamBufferView>(&m_quadStreamBufferView, 1));
+            RHI::ValidateStreamBufferViews(m_quadInputStreamLayout, AZStd::span<const RHI::StreamBufferView>(&m_quadStreamBufferView, 1));
         }
     }
 

+ 2 - 2
Gem/Code/Source/RHI/SubpassExampleComponent.cpp

@@ -186,7 +186,7 @@ namespace AtomSampleViewer
             ModelType_Suzanne,
         };
 
-        auto setModelType = [](AZStd::array_view<ModelType> types, AZStd::vector<ModelData>& modelDataList)
+        auto setModelType = [](AZStd::span<const ModelType> types, AZStd::vector<ModelData>& modelDataList)
         {
             modelDataList.resize(types.size());
             for (uint32_t i = 0; i < modelDataList.size(); ++i)
@@ -195,7 +195,7 @@ namespace AtomSampleViewer
             }
         };
 
-        setModelType(AZStd::array_view<ModelType>(&opaqueModels[0], AZ_ARRAY_SIZE(opaqueModels)), m_opaqueModelsData);
+        setModelType(AZStd::span<const ModelType>(&opaqueModels[0], AZ_ARRAY_SIZE(opaqueModels)), m_opaqueModelsData);
     }
 
     void SubpassExampleComponent::LoadShaders()

+ 342 - 0
Gem/Code/Source/ReadbackExampleComponent.cpp

@@ -0,0 +1,342 @@
+/*
+ * 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 <ReadbackExampleComponent.h>
+
+#include <Atom/RPI.Public/Scene.h>
+#include <Atom/RPI.Public/RenderPipeline.h>
+#include <Atom/RPI.Public/Pass/FullscreenTrianglePass.h>
+#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
+#include <Atom/RPI.Public/Image/AttachmentImagePool.h>
+#include <Atom/RPI.Public/RPIUtils.h>
+
+#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
+#include <Atom/RPI.Reflect/Pass/FullscreenTrianglePassData.h>
+
+#include <Automation/ScriptableImGui.h>
+
+namespace AtomSampleViewer
+{
+    static const char* s_readbackPipelineTemplate = "ReadbackPipelineTemplate";
+    static const char* s_fillerPassTemplate = "ReadbackFillerPassTemplate";
+    static const char* s_previewPassTemplate = "ReadbackPreviewPassTemplate";
+
+    static const char* s_fillerShaderPath = "Shaders/Readback/Filler.azshader";
+    static const char* s_previewShaderPath = "Shaders/Readback/Preview.azshader";
+
+    static const char* s_readbackImageName = "ReadbackImage";
+    static const char* s_previewImageName = "PreviewImage";
+
+    void ReadbackExampleComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<ReadbackExampleComponent, AZ::Component>()->Version(0);
+        }
+    }
+
+    ReadbackExampleComponent::ReadbackExampleComponent()
+    {
+    }
+
+    void ReadbackExampleComponent::Activate()
+    {
+        AZ::TickBus::Handler::BusConnect();
+        AZ::Render::Bootstrap::DefaultWindowNotificationBus::Handler::BusConnect();
+
+        ActivatePipeline();
+        CreatePasses();
+
+        m_imguiSidebar.Activate();
+    }
+
+    void ReadbackExampleComponent::Deactivate()
+    {
+        m_imguiSidebar.Deactivate();
+
+        DestroyPasses();
+        DeactivatePipeline();
+
+        AZ::Render::Bootstrap::DefaultWindowNotificationBus::Handler::BusDisconnect();
+        AZ::TickBus::Handler::BusDisconnect();
+    }
+
+    void ReadbackExampleComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint scriptTime)
+    {
+        // Readback was completed, we need to update the preview image
+        if (m_textureNeedsUpdate)
+        {
+            UploadReadbackResult();
+
+            AZ_Error("ReadbackExample", m_resourceWidth == m_readbackStat.m_descriptor.m_size.m_width, "Incorrect resource width read back.");
+            AZ_Error("ReadbackExample", m_resourceHeight == m_readbackStat.m_descriptor.m_size.m_height, "Incorrect resource height read back.");
+
+            m_textureNeedsUpdate = false;
+        }
+
+        DrawSidebar();
+    }
+
+    void ReadbackExampleComponent::DefaultWindowCreated()
+    {
+        AZ::Render::Bootstrap::DefaultWindowBus::BroadcastResult(m_windowContext, &AZ::Render::Bootstrap::DefaultWindowBus::Events::GetDefaultWindowContext);
+    }
+
+    void ReadbackExampleComponent::CreatePipeline()
+    {
+        // Create the pipeline shell
+        AZ::RPI::RenderPipelineDescriptor readbackPipelineDesc;
+        readbackPipelineDesc.m_mainViewTagName = "MainCamera";
+        readbackPipelineDesc.m_name = "ReadbackPipeline";
+        readbackPipelineDesc.m_rootPassTemplate = s_readbackPipelineTemplate;
+
+        m_readbackPipeline = AZ::RPI::RenderPipeline::CreateRenderPipelineForWindow(readbackPipelineDesc, *m_windowContext);
+    }
+
+    void ReadbackExampleComponent::ActivatePipeline()
+    {
+        // Create the pipeline
+        CreatePipeline();
+
+        // Setup the pipeline
+        m_originalPipeline = m_scene->GetDefaultRenderPipeline();
+        m_scene->AddRenderPipeline(m_readbackPipeline);
+        m_scene->RemoveRenderPipeline(m_originalPipeline->GetId());
+        m_scene->SetDefaultRenderPipeline(m_readbackPipeline->GetId());
+
+        // Create an ImGuiActiveContextScope to ensure the ImGui context on the new pipeline's ImGui pass is activated.
+        m_imguiScope = AZ::Render::ImGuiActiveContextScope::FromPass({ m_readbackPipeline->GetId().GetCStr(), "ImGuiPass" });
+    }
+
+    void ReadbackExampleComponent::DeactivatePipeline()
+    {
+        m_imguiScope = {}; // restores previous ImGui context.
+
+        m_scene->AddRenderPipeline(m_originalPipeline);
+        m_scene->RemoveRenderPipeline(m_readbackPipeline->GetId());
+
+        m_readbackPipeline = nullptr;
+    }
+
+    void ReadbackExampleComponent::CreatePasses()
+    {
+        DestroyPasses();
+
+        CreateResources();
+
+        CreateFillerPass();
+        CreatePreviewPass();
+
+        // Add the filler and preview passes
+        AZ::RPI::Ptr<AZ::RPI::ParentPass> rootPass = m_readbackPipeline->GetRootPass();
+        rootPass->InsertChild(m_fillerPass, AZ::RPI::ParentPass::ChildPassIndex(0));
+        rootPass->InsertChild(m_previewPass, AZ::RPI::ParentPass::ChildPassIndex(1));
+    }
+
+    void ReadbackExampleComponent::DestroyPasses()
+    {
+        if (!m_fillerPass)
+        {
+            return;
+        }
+
+        m_fillerPass->QueueForRemoval();
+        m_fillerPass = nullptr;
+
+        m_previewPass->QueueForRemoval();
+        m_previewPass = nullptr;
+    }
+
+    void ReadbackExampleComponent::PassesChanged()
+    {
+        DestroyPasses();
+        CreatePasses();
+    }
+
+    void ReadbackExampleComponent::CreateFillerPass()
+    {
+        // Load the shader
+        AZ::Data::AssetId shaderAssetId;
+        AZ::Data::AssetCatalogRequestBus::BroadcastResult(
+            shaderAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
+            s_fillerShaderPath, azrtti_typeid<AZ::RPI::ShaderAsset>(), false);
+        if (!shaderAssetId.IsValid())
+        {
+            AZ_Assert(false, "[DisplayMapperPass] Unable to obtain asset id for %s.", s_fillerShaderPath);
+        }
+
+        // Create the compute filler pass
+        AZ::RPI::PassRequest createPassRequest;
+        createPassRequest.m_templateName = AZ::Name(s_fillerPassTemplate);
+        createPassRequest.m_passName = AZ::Name("RenderTargetPass");
+
+        // Fill the pass data
+        AZStd::shared_ptr<AZ::RPI::FullscreenTrianglePassData> passData = AZStd::make_shared<AZ::RPI::FullscreenTrianglePassData>();
+        passData->m_shaderAsset.m_assetId = shaderAssetId;
+        passData->m_shaderAsset.m_filePath = s_fillerShaderPath;
+        createPassRequest.m_passData = AZStd::move(passData);
+
+        // Create the connection for the output slot
+        AZ::RPI::PassConnection connection = { AZ::Name("Output"), {AZ::Name("This"), AZ::Name(s_readbackImageName)} };
+        createPassRequest.m_connections.push_back(connection);
+
+        // Register the imported attachment
+        AZ::RPI::PassImageAttachmentDesc imageAttachment;
+        imageAttachment.m_name = s_readbackImageName;
+        imageAttachment.m_lifetime = AZ::RHI::AttachmentLifetimeType::Imported;
+        imageAttachment.m_assetRef.m_assetId = m_readbackImage->GetAssetId();
+        createPassRequest.m_imageAttachmentOverrides.push_back(imageAttachment);
+
+        // Create the pass
+        m_fillerPass = AZ::RPI::PassSystemInterface::Get()->CreatePassFromRequest(&createPassRequest);
+    }
+
+    void ReadbackExampleComponent::CreatePreviewPass()
+    {
+        // Load the shader
+        AZ::Data::AssetId shaderAssetId;
+        AZ::Data::AssetCatalogRequestBus::BroadcastResult(
+            shaderAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
+            s_previewShaderPath, azrtti_typeid<AZ::RPI::ShaderAsset>(), false);
+        if (!shaderAssetId.IsValid())
+        {
+            AZ_Assert(false, "[DisplayMapperPass] Unable to obtain asset id for %s.", s_previewShaderPath);
+        }
+
+        // Create the compute filler pass
+        AZ::RPI::PassRequest createPassRequest;
+        createPassRequest.m_templateName = AZ::Name(s_previewPassTemplate);
+        createPassRequest.m_passName = AZ::Name("PreviewPass");
+
+        AZStd::shared_ptr<AZ::RPI::FullscreenTrianglePassData> passData = AZStd::make_shared<AZ::RPI::FullscreenTrianglePassData>();
+        passData->m_shaderAsset.m_assetId = shaderAssetId;
+        passData->m_shaderAsset.m_filePath = s_previewShaderPath;
+        createPassRequest.m_passData = AZStd::move(passData);
+
+        // Create the connection for the output slot
+        AZ::RPI::PassConnection outputConnection = { AZ::Name("Output"), {AZ::Name("Parent"), AZ::Name("SwapChainOutput")} };
+        createPassRequest.m_connections.push_back(outputConnection);
+        AZ::RPI::PassConnection inputConnection = { AZ::Name("Input"), {AZ::Name("This"), AZ::Name(s_previewImageName)} };
+        createPassRequest.m_connections.push_back(inputConnection);
+
+        // Register the imported attachment
+        AZ::RPI::PassImageAttachmentDesc imageAttachment;
+        imageAttachment.m_name = s_previewImageName;
+        imageAttachment.m_lifetime = AZ::RHI::AttachmentLifetimeType::Imported;
+        imageAttachment.m_assetRef.m_assetId = m_previewImage->GetAssetId();
+        createPassRequest.m_imageAttachmentOverrides.push_back(imageAttachment);
+
+        m_previewPass = AZ::RPI::PassSystemInterface::Get()->CreatePassFromRequest(&createPassRequest);
+    }
+
+    void ReadbackExampleComponent::CreateResources()
+    {
+        AZ::Data::Instance<AZ::RPI::AttachmentImagePool> pool = AZ::RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool();
+
+        // Create the readback target
+        {
+            AZ::RPI::CreateAttachmentImageRequest createRequest;
+            createRequest.m_imageName = AZ::Name(s_readbackImageName);
+            createRequest.m_isUniqueName = false;
+            createRequest.m_imagePool = pool.get();
+            createRequest.m_imageDescriptor = AZ::RHI::ImageDescriptor::Create2D(AZ::RHI::ImageBindFlags::Color | AZ::RHI::ImageBindFlags::ShaderWrite | AZ::RHI::ImageBindFlags::CopyRead | AZ::RHI::ImageBindFlags::CopyWrite, m_resourceWidth, m_resourceHeight, AZ::RHI::Format::R8G8B8A8_UNORM);
+
+            m_readbackImage = AZ::RPI::AttachmentImage::Create(createRequest);
+        }
+
+        // Create the preview image
+        {
+            AZ::RPI::CreateAttachmentImageRequest createRequest;
+            createRequest.m_imageName = AZ::Name(s_previewImageName);
+            createRequest.m_isUniqueName = false;
+            createRequest.m_imagePool = pool.get();
+            createRequest.m_imageDescriptor = AZ::RHI::ImageDescriptor::Create2D(AZ::RHI::ImageBindFlags::ShaderRead | AZ::RHI::ImageBindFlags::CopyRead | AZ::RHI::ImageBindFlags::CopyWrite, m_resourceWidth, m_resourceHeight, AZ::RHI::Format::R8G8B8A8_UNORM);
+
+            m_previewImage = AZ::RPI::AttachmentImage::Create(createRequest);
+        }
+    }
+
+    void ReadbackExampleComponent::PerformReadback()
+    {
+        AZ_Assert(m_fillerPass, "Render target pass is null.");
+
+        if (!m_readback)
+        {
+            m_readback = AZStd::make_shared<AZ::RPI::AttachmentReadback>(AZ::RHI::ScopeId{ "RenderTargetCapture" });
+            m_readback->SetCallback(AZStd::bind(&ReadbackExampleComponent::ReadbackCallback, this, AZStd::placeholders::_1));
+        }
+
+        m_fillerPass->ReadbackAttachment(m_readback, AZ::Name("Output"));
+    }
+
+    void ReadbackExampleComponent::ReadbackCallback(const AZ::RPI::AttachmentReadback::ReadbackResult& result)
+    {
+        const AZ::RHI::ImageSubresourceRange range(0, 0, 0, 0);
+        AZ::RHI::ImageSubresourceLayoutPlaced layout;
+        m_previewImage->GetRHIImage()->GetSubresourceLayouts(range, &layout, nullptr);
+
+        m_textureNeedsUpdate = true;
+        m_resultData = result.m_dataBuffer;
+
+        // Fill the readback stats
+        m_readbackStat.m_name = result.m_name;
+        m_readbackStat.m_bytesRead = result.m_dataBuffer->size();
+        m_readbackStat.m_descriptor = result.m_imageDescriptor;
+    }
+
+    void ReadbackExampleComponent::UploadReadbackResult() const
+    {
+        const AZ::RHI::ImageSubresourceRange range(0, 0, 0, 0);
+        AZ::RHI::ImageSubresourceLayoutPlaced layout;
+        m_previewImage->GetRHIImage()->GetSubresourceLayouts(range, &layout, nullptr);
+        AZ::RHI::ImageUpdateRequest updateRequest;
+        updateRequest.m_image = m_previewImage->GetRHIImage();
+        updateRequest.m_sourceSubresourceLayout = layout;
+        updateRequest.m_sourceData = m_resultData->begin();
+        updateRequest.m_imageSubresourcePixelOffset = AZ::RHI::Origin(0, 0, 0);
+        m_previewImage->UpdateImageContents(updateRequest);
+    }
+
+    void ReadbackExampleComponent::DrawSidebar()
+    {
+        if (m_imguiSidebar.Begin())
+        {
+            ImGui::Text("Readback resource dimensions:");
+            if (ScriptableImGui::SliderInt("Width", reinterpret_cast<int*>(&m_resourceWidth), 1, 2048))
+            {
+                PassesChanged();
+            }
+            if (ScriptableImGui::SliderInt("Height", reinterpret_cast<int*>(&m_resourceHeight), 1, 2048))
+            {
+                PassesChanged();
+            }
+
+            ImGui::Separator();
+            ImGui::NewLine();
+
+            if (ScriptableImGui::Button("Readback")) {
+                PerformReadback();
+            }
+
+            ImGui::NewLine();
+            if (m_resultData)
+            {
+                ImGui::Separator();
+                ImGui::Text("Readback statistics");
+                ImGui::NewLine();
+                ImGui::Text("Name: %s", m_readbackStat.m_name.GetCStr());
+                ImGui::Text("Bytes read: %zu", m_readbackStat.m_bytesRead);
+                ImGui::Text("[%i; %i; %i]", m_readbackStat.m_descriptor.m_size.m_width, m_readbackStat.m_descriptor.m_size.m_height, m_readbackStat.m_descriptor.m_size.m_depth);
+                ImGui::Text("%s", AZ::RHI::ToString(m_readbackStat.m_descriptor.m_format));
+
+            }
+
+            m_imguiSidebar.End();
+        }
+    }
+}

+ 109 - 0
Gem/Code/Source/ReadbackExampleComponent.h

@@ -0,0 +1,109 @@
+/*
+ * 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 <CommonSampleComponentBase.h>
+
+#include <AzCore/Component/TickBus.h>
+#include <Atom/Bootstrap/DefaultWindowBus.h>
+
+#include <Atom/RPI.Public/Pass/AttachmentReadback.h>
+
+#include <Utils/ImGuiSidebar.h>
+#include <Atom/Feature/ImGui/ImGuiUtils.h>
+
+namespace AtomSampleViewer
+{
+    //! --- Readback Test ---
+    //! 
+    //! This test is designed to test the readback capabilities of ATOM
+    //! It is built around two custom passes working in tandem. The first
+    //! one generate and fill a texture with a pattern. Using the RPI::Pass
+    //! readback capabilities (ReadbackAttachment) it then reads that result
+    //! back to host memory. Once read back the result is uploaded to device
+    //! memory to be used as a texture input in the second pass that will
+    //! display it for operator verification.
+
+    class ReadbackExampleComponent final
+        : public CommonSampleComponentBase
+        , public AZ::Render::Bootstrap::DefaultWindowNotificationBus::Handler
+        , public AZ::TickBus::Handler
+    {
+    public:
+        AZ_COMPONENT(ReadbackExampleComponent, "{B4221426-D22B-4C06-AAFD-4EA277B44CC8}", CommonSampleComponentBase);
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        ReadbackExampleComponent();
+        ~ReadbackExampleComponent() override = default;
+
+        void Activate() override;
+        void Deactivate() override;
+
+    private:
+        AZ_DISABLE_COPY_MOVE(ReadbackExampleComponent);
+
+        // AZ::TickBus::Handler overrides...
+        void OnTick(float deltaTime, AZ::ScriptTimePoint scriptTime) override;
+
+        // AZ::Render::Bootstrap::DefaultWindowNotificationBus overrides ...
+        void DefaultWindowCreated() override;
+
+        void CreatePipeline();
+        void ActivatePipeline();
+        void DeactivatePipeline();
+
+        void CreatePasses();
+        void DestroyPasses();
+        void PassesChanged();
+
+        void CreateFillerPass();
+        void CreatePreviewPass();
+
+        void CreateResources();
+
+        void PerformReadback();
+        void ReadbackCallback(const AZ::RPI::AttachmentReadback::ReadbackResult& result);
+        void UploadReadbackResult() const;
+
+        void DrawSidebar();
+
+        // Pass used to render the pattern and support the readback operation
+        AZ::RHI::Ptr<AZ::RPI::Pass> m_fillerPass;
+        // Pass used to display the readback result back on the screen
+        AZ::RHI::Ptr<AZ::RPI::Pass> m_previewPass;
+
+        // Image used as the readback source
+        AZ::Data::Instance<AZ::RPI::AttachmentImage> m_readbackImage;
+        // Image used as the readback result destination
+        AZ::Data::Instance<AZ::RPI::AttachmentImage> m_previewImage;
+
+        // Custom pipeline
+        AZStd::shared_ptr<AZ::RPI::WindowContext> m_windowContext;
+        AZ::RPI::RenderPipelinePtr m_readbackPipeline;
+        AZ::RPI::RenderPipelinePtr m_originalPipeline;
+        AZ::Render::ImGuiActiveContextScope m_imguiScope;
+
+        // Readback
+        AZStd::shared_ptr<AZ::RPI::AttachmentReadback> m_readback;
+        // Holder for the host available copy of the readback data
+        AZStd::shared_ptr<AZStd::vector<uint8_t>> m_resultData;
+        struct {
+            AZ::Name m_name;
+            size_t m_bytesRead;
+            AZ::RHI::ImageDescriptor m_descriptor;
+        } m_readbackStat;
+        bool m_textureNeedsUpdate = false;
+
+        ImGuiSidebar m_imguiSidebar;
+
+        uint32_t m_resourceWidth = 512;
+        uint32_t m_resourceHeight = 512;
+    };
+} // namespace AtomSampleViewer

+ 435 - 0
Gem/Code/Source/RenderTargetTextureExampleComponent.cpp

@@ -0,0 +1,435 @@
+/*
+ * 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 <RenderTargetTextureExampleComponent.h>
+
+#include <Atom/Component/DebugCamera/NoClipControllerComponent.h>
+
+#include <Atom/RHI/Device.h>
+#include <Atom/RHI/Factory.h>
+
+#include <Atom/RPI.Public/DynamicDraw/DynamicDrawInterface.h>
+#include <Atom/RPI.Public/Image/AttachmentImagePool.h>
+#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
+#include <Atom/RPI.Public/RPIUtils.h>
+#include <Atom/RPI.Public/View.h>
+
+#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
+#include <Atom/RPI.Reflect/Model/ModelAsset.h>
+#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
+#include <Atom/RPI.Reflect/Pass/PassDescriptor.h>
+#include <Atom/RPI.Reflect/Pass/RasterPassData.h>
+
+#include <AzCore/Asset/AssetManagerBus.h>
+#include <AzCore/Component/Entity.h>
+#include <AzCore/Math/MatrixUtils.h>
+#include <AzCore/std/smart_ptr/make_shared.h>
+
+#include <AzFramework/Components/TransformComponent.h>
+#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
+
+#include <SampleComponentManager.h>
+#include <SampleComponentConfig.h>
+#include <EntityUtilityFunctions.h>
+
+#include <Automation/ScriptableImGui.h>
+#include <Automation/ScriptRunnerBus.h>
+
+#include <RHI/BasicRHIComponent.h>
+
+namespace AtomSampleViewer
+{
+    using namespace AZ;
+
+    namespace
+    {
+        static constexpr const char TextureFilePath[] = "textures/default/checker_uv_basecolor.png.streamingimage";
+    }
+
+    void RenderTargetTextureExampleComponent::Reflect(ReflectContext* context)
+    {
+        if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
+        {
+            serializeContext->Class<RenderTargetTextureExampleComponent, Component>()
+                ->Version(0)
+                ;
+        }
+    }
+
+    RenderTargetTextureExampleComponent::RenderTargetTextureExampleComponent()
+        : m_imguiSidebar("@user@/RenderTargetTextureExampleComponent/sidebar.xml")
+    {
+    }
+
+    void RenderTargetTextureExampleComponent::Activate()
+    {
+        m_ibl.PreloadAssets();
+
+        // Don't continue the script until assets are ready and scene is setup
+        ScriptRunnerRequestBus::Broadcast(&ScriptRunnerRequests::PauseScriptWithTimeout, 120.0f);
+
+        // preload assets
+        AZStd::vector<AssetCollectionAsyncLoader::AssetToLoadInfo> assetList = {
+            {DefaultPbrMaterialPath, azrtti_typeid<RPI::MaterialAsset>()},
+            {BunnyModelFilePath, azrtti_typeid<RPI::ModelAsset>()},
+            {TextureFilePath, azrtti_typeid<RPI::StreamingImageAsset>()}
+        };
+
+        PreloadAssets(assetList);
+    }
+
+    void RenderTargetTextureExampleComponent::AddRenderTargetPass()
+    {
+        m_renderTargetPassDrawListTag = AZ::Name("rt_1");
+        const Name renderPassTemplateName = Name{ "RenderTargetPassTemplate" };
+        RHI::ClearValue clearValue = RHI::ClearValue::CreateVector4Float(1, 0, 0, 1);
+
+        // Create the template if it doesn't exist
+        if (!RPI::PassSystemInterface::Get()->HasTemplate(renderPassTemplateName))
+        {
+            // first add a pass template from code (an alternative way then using .pass asset for a new template)
+            const AZStd::shared_ptr<RPI::PassTemplate>rtPassTemplate = AZStd::make_shared<RPI::PassTemplate>();
+            rtPassTemplate->m_name = renderPassTemplateName;
+            rtPassTemplate->m_passClass = "RasterPass";
+
+            // only need one slot for render target output
+            RPI::PassSlot slot;
+            slot.m_name = Name("ColorOutput");
+            slot.m_slotType = RPI::PassSlotType::Output;
+            slot.m_scopeAttachmentUsage = RHI::ScopeAttachmentUsage::RenderTarget;
+            //slot.m_loadStoreAction.m_loadAction = RHI::AttachmentLoadAction::DontCare;
+            slot.m_loadStoreAction.m_loadAction = RHI::AttachmentLoadAction::Clear;
+            slot.m_loadStoreAction.m_clearValue = clearValue;
+            rtPassTemplate->AddSlot(slot);
+
+            // connect the slot to attachment
+            RPI::PassConnection connection;
+            connection.m_localSlot = Name("ColorOutput");
+            connection.m_attachmentRef.m_pass = Name("This");
+            connection.m_attachmentRef.m_attachment = Name("RenderTarget");
+            rtPassTemplate->AddOutputConnection(connection);
+
+            RPI::PassSystemInterface::Get()->AddPassTemplate(renderPassTemplateName, rtPassTemplate);
+        }
+
+        // Create pass
+        RPI::PassRequest createPassRequest;
+        createPassRequest.m_templateName = renderPassTemplateName;
+        createPassRequest.m_passName = Name("RenderTargetPass");
+
+        // Create AttacmentImage which is used for pass render target 
+        const uint32_t imageWidth = 512;
+        const uint32_t imageHeight = 512;
+        Data::Instance<RPI::AttachmentImagePool> pool = RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool();
+        RPI::CreateAttachmentImageRequest createRequest;
+        createRequest.m_imagePool = pool.get();
+        createRequest.m_imageDescriptor = RHI::ImageDescriptor::Create2D(AZ::RHI::ImageBindFlags::Color|AZ::RHI::ImageBindFlags::ShaderRead, imageWidth, imageHeight, RHI::Format::R8G8B8A8_UNORM);
+        createRequest.m_imageName = Name("$RT1");
+        createRequest.m_isUniqueName = true;
+        createRequest.m_optimizedClearValue = &clearValue;
+
+        AZStd::shared_ptr<AZ::RPI::RasterPassData> passData = AZStd::make_shared<AZ::RPI::RasterPassData>();
+        passData->m_drawListTag = m_renderTargetPassDrawListTag;
+        passData->m_pipelineViewTag = AZ::Name("MainCamera");
+        passData->m_overrideScissor = RHI::Scissor(0, 0, imageWidth, imageHeight);
+        passData->m_overrideViewport = RHI::Viewport(0, imageWidth, 0, imageHeight);
+        createPassRequest.m_passData = passData;
+
+        m_renderTarget = RPI::AttachmentImage::Create(createRequest);
+
+        // Add image from asset
+        RPI::PassImageAttachmentDesc imageAttachment;
+        imageAttachment.m_name = "RenderTarget";
+        imageAttachment.m_lifetime = RHI::AttachmentLifetimeType::Imported;
+        imageAttachment.m_assetRef.m_assetId = m_renderTarget->GetAssetId();
+        createPassRequest.m_imageAttachmentOverrides.push_back(imageAttachment);
+
+        // Create the pass
+        m_renderTargetPass = RPI::PassSystemInterface::Get()->CreatePassFromRequest(&createPassRequest);
+
+        // add the pass to render pipeline
+        RPI::RenderPipelinePtr renderPipeline = m_scene->GetDefaultRenderPipeline();
+        RPI::Ptr<RPI::ParentPass> rootPass = renderPipeline->GetRootPass();
+        // Insert to the beginning so it will be done before all the other rendering
+        rootPass->InsertChild(m_renderTargetPass, RPI::ParentPass::ChildPassIndex(0));
+
+        // Add a preview pass to preview the render target
+        RPI::PassDescriptor descriptor(Name("RenderTargetPreview"));
+        m_previewPass = RPI::ImageAttachmentPreviewPass::Create(descriptor);
+        rootPass->AddChild(m_previewPass);
+
+        // we need process queued changes to build the pass properly 
+        // which Scene::RebuildPipelineStatesLookup() requires 
+        AZ::RPI::PassSystemInterface::Get()->ProcessQueuedChanges();
+        m_scene->RebuildPipelineStatesLookup();
+    }
+
+    void RenderTargetTextureExampleComponent::RemoveRenderTargetPass()
+    {
+        m_previewPass->ClearPreviewAttachment();
+        m_previewPass->QueueForRemoval();
+        m_previewPass = nullptr;
+
+        m_renderTargetPass->QueueForRemoval();
+        m_renderTargetPass = nullptr;
+        m_renderTarget = nullptr;
+    }
+
+    void RenderTargetTextureExampleComponent::CreateDynamicDraw()
+    {
+        const char* shaderFilepath = "Shaders/SimpleTextured.azshader";
+        AZ::Data::Instance<AZ::RPI::Shader> shader = AZ::RPI::LoadCriticalShader(shaderFilepath);
+
+        RHI::DrawListTagRegistry* drawListTagRegistry = RHI::RHISystemInterface::Get()->GetDrawListTagRegistry();
+        RHI::DrawListTag newTag = drawListTagRegistry->FindTag(m_renderTargetPassDrawListTag);
+
+        
+        AZ::RPI::ShaderOptionList shaderOptions;
+        shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_useColorChannels"), AZ::Name("true")));
+        shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_clamp"), AZ::Name("true")));
+
+        m_dynamicDraw = AZ::RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext();
+        m_dynamicDraw->InitDrawListTag(newTag);
+        m_dynamicDraw->InitShaderWithVariant(shader, &shaderOptions);
+        m_dynamicDraw->InitVertexFormat(
+            { {"POSITION", AZ::RHI::Format::R32G32B32_FLOAT},
+            {"COLOR", AZ::RHI::Format::B8G8R8A8_UNORM},
+            {"TEXCOORD0", AZ::RHI::Format::R32G32_FLOAT} });
+
+        m_dynamicDraw->AddDrawStateOptions(RPI::DynamicDrawContext::DrawStateOptions::BlendMode);
+        m_dynamicDraw->SetOutputScope(m_scene);
+        m_dynamicDraw->EndInit();
+                
+        RHI::TargetBlendState blendState;
+        blendState.m_enable = false;
+        m_dynamicDraw->SetTarget0BlendState(blendState);
+
+        // load texture used for the dynamic draw
+        m_texture = AZ::RPI::LoadStreamingTexture(TextureFilePath);
+    }
+
+    void RenderTargetTextureExampleComponent::OnAllAssetsReadyActivate()
+    {
+        // create render target pass
+        AddRenderTargetPass();
+
+        CreateDynamicDraw();
+
+        // load mesh and material
+        auto meshFeatureProcessor = GetMeshFeatureProcessor();
+        // material
+        auto materialAsset = RPI::AssetUtils::LoadAssetByProductPath<RPI::MaterialAsset>(DefaultPbrMaterialPath,
+            RPI::AssetUtils::TraceLevel::Assert);
+        m_material = AZ::RPI::Material::FindOrCreate(materialAsset);
+        // bunny mesh
+        auto bunnyAsset = RPI::AssetUtils::LoadAssetByProductPath<RPI::ModelAsset>(BunnyModelFilePath,
+            RPI::AssetUtils::TraceLevel::Assert);
+        m_meshHandle = meshFeatureProcessor->AcquireMesh(Render::MeshHandleDescriptor{ bunnyAsset }, m_material);
+        meshFeatureProcessor->SetTransform(m_meshHandle, Transform::CreateTranslation(Vector3(0.f, 0.f, 0.21f)));
+
+        // Set camera to use no clip controller and adjust its fov and transform
+        AZ::Debug::CameraControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::CameraControllerRequestBus::Events::Enable,
+            azrtti_typeid<AZ::Debug::NoClipControllerComponent>());
+        AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequestBus::Events::SetFov, AZ::DegToRad(90));
+        AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequestBus::Events::SetPosition, Vector3(1.244541f, -0.660081f, 1.902831f));
+        AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequestBus::Events::SetHeading, AZ::DegToRad(61.274673f));
+        AZ::Debug::NoClipControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::NoClipControllerRequestBus::Events::SetPitch, AZ::DegToRad(-36.605690f));
+
+        // Setup IBL
+        m_ibl.Init(m_scene);
+
+        // set render target texture to material
+        auto propertyId = m_material->FindPropertyIndex(AZ::Name{ "baseColor.textureMap" });
+        auto image = RPI::AttachmentImage::FindByUniqueName(Name("$RT1"));
+        if (propertyId.IsValid())
+        {
+            m_baseColorTexture = m_material->GetPropertyValue<Data::Instance<RPI::Image>>(propertyId);
+            m_material->SetPropertyValue(propertyId, (Data::Instance<RPI::Image>)image);
+        }
+        else
+        {
+            AZ_Error("ASV", false, "Failed to file property 'baseColor.textureMap' in the material");
+        }
+
+        AZ::TickBus::Handler::BusConnect();
+        m_imguiSidebar.Activate();
+        ScriptRunnerRequestBus::Broadcast(&ScriptRunnerRequests::ResumeScript);
+
+        UpdateRenderTargetPreview();
+    }
+
+    void RenderTargetTextureExampleComponent::Deactivate()
+    {
+        AZ::TickBus::Handler::BusDisconnect();
+
+        m_imguiSidebar.Deactivate();
+
+        // release model and mesh
+        GetMeshFeatureProcessor()->ReleaseMesh(m_meshHandle);
+        m_material = nullptr;
+        m_baseColorTexture = nullptr;
+        m_modelAsset = {};
+
+        m_ibl.Reset();
+
+        AZ::Debug::CameraControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::CameraControllerRequestBus::Events::Disable);
+
+        // Release dynamic draw context
+        m_dynamicDraw = nullptr;
+
+        RemoveRenderTargetPass(); 
+    }
+
+    void RenderTargetTextureExampleComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
+    {
+        // Note, the Compile function need to be called to apply material property change in the next frame after material was created which called copmile()
+        // Compile() only can be called once per frame
+        if (m_material->NeedsCompile())
+        {
+            m_material->Compile();
+        }
+
+        bool drawToRenderTarget = false;
+        if (m_imguiSidebar.Begin())
+        {
+            if (ScriptableImGui::Button("Next Frame"))
+            {
+                drawToRenderTarget = true;
+                m_currentFrame = (m_currentFrame+1)%4;
+            }
+            
+            ImGui::Separator();
+            
+            if (ScriptableImGui::Checkbox("Show Preview", &m_showRenderTargetPreview))
+            {
+                UpdateRenderTargetPreview();
+            }
+            
+            ImGui::Separator();
+            ScriptableImGui::Checkbox("Update Per Second", &m_updatePerSecond);
+
+            if (m_updatePerSecond)
+            {
+                auto currentTime = time.GetSeconds();
+                if (currentTime > m_lastUpdateTime + 1)
+                {                
+                    drawToRenderTarget = true;
+                    m_lastUpdateTime = currentTime;
+                    m_currentFrame = (m_currentFrame+1)%4;
+                }
+            }
+            else if (m_lastUpdateTime == 0)
+            {
+                drawToRenderTarget = true;
+                m_lastUpdateTime = time.GetSeconds();
+            }
+
+            ImGui::Separator();
+            ImGui::Text("Current frame: %d", m_currentFrame+1);
+
+            m_imguiSidebar.End();
+        }
+
+        // have to disable the pass if there is no drawing. otherwise the render target would be cleared.
+        m_renderTargetPass->SetEnabled(drawToRenderTarget);
+
+        if (drawToRenderTarget)
+        {
+            // Draw something to the render target pass
+            DrawToRenderTargetPass();
+        }
+    }
+
+    void RenderTargetTextureExampleComponent::UpdateRenderTargetPreview()
+    {
+        if (m_showRenderTargetPreview)
+        {
+            // Add attachment preview after pass queued changes processed
+            // m_renderTargetPass only has one attachment
+                m_previewPass->PreviewImageAttachmentForPass(m_renderTargetPass.get(), m_renderTargetPass->GetAttachmentBindings()[0].GetAttachment().get());
+        }
+        else
+        {
+            m_previewPass->ClearPreviewAttachment();
+        }
+    }
+   
+    void RenderTargetTextureExampleComponent::DrawToRenderTargetPass()
+    {
+        const int numVerts = 4;
+        const int numFrames = 4;
+        struct Vertex
+        {
+            float position[3];
+            uint32_t color;
+            float uv[2];
+        };
+
+        uint32_t colors[numFrames] =
+        {
+            0xff0000ff,
+            0xffff00ff,
+            0xff00ffff,
+            0xffffffff
+        };
+
+        float uvoffset[numFrames][2] = 
+        {
+            {0, 0.5f},
+            {0.5f, 0.5f},
+            {0, 0},
+            {0.5f, 0},
+        };
+
+        Vertex vertices[numVerts];
+
+        // Create a vertex offset from the position to draw from based on the icon size
+        // Vertex positions are in screen space coordinates
+        auto createVertex = [&](float x, float y, float u, float v) -> Vertex
+        {
+            Vertex vertex;
+            vertex.position[0] = x;
+            vertex.position[1] = y;
+            vertex.position[2] = 0.5f;
+            vertex.color = colors[m_currentFrame];
+            vertex.uv[0] = u + uvoffset[m_currentFrame][0];
+            vertex.uv[1] = v + uvoffset[m_currentFrame][1];
+            return vertex;
+        };
+
+        vertices[0] = createVertex(0.f, 0.f, 0.f, 0.f);
+        vertices[1] = createVertex(0.f, 1.f, 0.f, 0.5f);
+        vertices[2] = createVertex(1.f, 1.f, 0.5f, 0.5f);
+        vertices[3] = createVertex(1.f, 0.f, 0.5f, 0.f);
+        
+        using Indice = AZ::u16;
+        AZStd::array<Indice, 6> indices = {0, 1, 2, 0, 2, 3};
+
+        // setup draw srg
+        auto drawSrg = m_dynamicDraw->NewDrawSrg();
+        
+        AZ::RHI::ShaderInputNameIndex imageInputIndex = "m_texture";
+        AZ::RHI::ShaderInputNameIndex viewProjInputIndex = "m_worldToProj";
+
+        // Set projection matrix
+        const float viewX = 0;
+        const float viewY = 0;
+        const float viewWidth = 1;
+        const float viewHeight = 1;
+        const float zf = 0;
+        const float zn = 1;
+        AZ::Matrix4x4 modelViewProjMat;
+        AZ::MakeOrthographicMatrixRH(modelViewProjMat, viewX, viewX + viewWidth, viewY + viewHeight, viewY, zn, zf);
+        drawSrg->SetConstant(viewProjInputIndex, modelViewProjMat);
+        drawSrg->SetImage(imageInputIndex, m_texture);
+
+        drawSrg->Compile();
+
+        m_dynamicDraw->DrawIndexed(vertices, numVerts, &indices, aznumeric_cast<uint32_t>(indices.size()), RHI::IndexFormat::Uint16, drawSrg);
+    }
+} // namespace AtomSampleViewer

+ 101 - 0
Gem/Code/Source/RenderTargetTextureExampleComponent.h

@@ -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
+ *
+ */
+
+#pragma once
+
+#include <CommonSampleComponentBase.h>
+
+#include <AzCore/Component/EntityBus.h>
+#include <AzCore/Component/TickBus.h>
+
+#include <Utils/Utils.h>
+#include <Utils/ImGuiSidebar.h>
+
+#include <Atom/Feature/ImGui/ImGuiUtils.h>
+#include <Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h>
+#include <Atom/RPI.Public/Image/AttachmentImage.h>
+#include <Atom/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.h>
+
+
+namespace AtomSampleViewer
+{
+    // This example shows how to use a render target as a texture for a mesh's material.
+    // It does following things:
+    // 1. creates a raster pass at runtime with one render target 
+    // 2. the render target is used as a texture input for a standard pbr material
+    // 3. A mesh with this material is rendered to the scene with IBL lighting. 
+    class RenderTargetTextureExampleComponent final
+        : public CommonSampleComponentBase
+        , public AZ::TickBus::Handler
+    {
+    public:
+        AZ_COMPONENT(RenderTargetTextureExampleComponent, "{43069FB5-EE01-4799-8870-3CFC422D09DF}", CommonSampleComponentBase);
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        RenderTargetTextureExampleComponent();
+        ~RenderTargetTextureExampleComponent() override = default;
+
+        // AZ::Component overrides...
+        void Activate() override;
+        void Deactivate() override;
+
+    private:
+        // AZ::TickBus::Handler overrides...
+        void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
+
+        // CommonSampleComponentBase overrides...
+        void OnAllAssetsReadyActivate() override;
+
+        // create the pass render to a render target and add it to render pipeline
+        void AddRenderTargetPass();
+
+        void CreateDynamicDraw();
+
+        // Remove the render target pass
+        void RemoveRenderTargetPass();
+
+        void DrawToRenderTargetPass();
+
+        // show or hide render target preview
+        void UpdateRenderTargetPreview();
+
+        // Rendering content
+        // Mesh
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_meshHandle;
+        AZ::Data::Asset<AZ::RPI::ModelAsset> m_modelAsset;
+
+        // materiala and texture
+        AZ::Data::Instance<AZ::RPI::Material> m_material; 
+        AZ::Data::Instance<AZ::RPI::Image> m_baseColorTexture;
+
+        Utils::DefaultIBL m_ibl;
+
+        // render target
+        AZ::RHI::Ptr<AZ::RPI::Pass> m_renderTargetPass;
+        AZ::Data::Instance<AZ::RPI::AttachmentImage> m_renderTarget;
+        AZ::Name m_renderTargetPassDrawListTag;
+        
+        // to preview the render target
+        AZ::RPI::Ptr<AZ::RPI::ImageAttachmentPreviewPass> m_previewPass;
+
+        // For render something to render target pass
+        AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> m_dynamicDraw;
+        AZ::Data::Instance<AZ::RPI::Image> m_texture;
+        uint32_t m_currentFrame = 0;
+
+        // for ImGui ui
+        bool m_updatePerSecond = false;
+        double m_lastUpdateTime = 0;
+        bool m_showRenderTargetPreview = true;
+
+        // UI
+        ImGuiSidebar m_imguiSidebar;
+
+    };
+} // namespace AtomSampleViewer

+ 2 - 3
Gem/Code/Source/SSRExampleComponent.cpp

@@ -77,10 +77,9 @@ namespace AtomSampleViewer
 
         // statue
         {
-            AZ::Data::Asset<AZ::RPI::MaterialAsset> materialAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::MaterialAsset>("objects/lucy/lucy_stone.azmaterial", AZ::RPI::AssetUtils::TraceLevel::Assert);
-            AZ::Data::Asset<AZ::RPI::ModelAsset> modelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>(ATOMSAMPLEVIEWER_TRAIT_SSR_SAMPLE_LUCY_MODEL_NAME, AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Data::Asset<AZ::RPI::MaterialAsset> materialAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::MaterialAsset>("objects/hermanubis/hermanubis_stone.azmaterial", AZ::RPI::AssetUtils::TraceLevel::Assert);
+            AZ::Data::Asset<AZ::RPI::ModelAsset> modelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>(ATOMSAMPLEVIEWER_TRAIT_SSR_SAMPLE_HERMANUBIS_MODEL_NAME, AZ::RPI::AssetUtils::TraceLevel::Assert);
             AZ::Transform transform = AZ::Transform::CreateIdentity();
-            transform *= AZ::Transform::CreateRotationZ(AZ::Constants::Pi);
             transform.SetTranslation(0.0f, 0.0f, -0.05f);
 
             m_statueMeshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ modelAsset }, AZ::RPI::Material::FindOrCreate(materialAsset));

+ 178 - 119
Gem/Code/Source/SampleComponentManager.cpp

@@ -65,6 +65,9 @@
 #include <RHI/RayTracingExampleComponent.h>
 #include <RHI/MatrixAlignmentTestExampleComponent.h>
 
+#include <Performance/100KDrawable_SingleView_ExampleComponent.h>
+#include <Performance/100KDraw_10KDrawable_MultiView_ExampleComponent.h>
+
 #include <AreaLightExampleComponent.h>
 #include <AssetLoadTestComponent.h>
 #include <AuxGeomExampleComponent.h>
@@ -86,8 +89,8 @@
 #include <MultiRenderPipelineExampleComponent.h>
 #include <MultiSceneExampleComponent.h>
 #include <ParallaxMappingExampleComponent.h>
+#include <RenderTargetTextureExampleComponent.h>
 #include <SceneReloadSoakTestComponent.h>
-#include <ShadingExampleComponent.h>
 #include <ShadowExampleComponent.h>
 #include <ShadowedSponzaExampleComponent.h>
 #include <SkinnedMeshExampleComponent.h>
@@ -100,11 +103,13 @@
 #include <DiffuseGIExampleComponent.h>
 #include <SSRExampleComponent.h>
 #include <ShaderReloadTestComponent.h>
+#include <ReadbackExampleComponent.h>
 
 #include <Atom/Bootstrap/DefaultWindowBus.h>
 
 #include <AzCore/Component/Entity.h>
 #include <AzCore/Debug/Profiler.h>
+#include <AzCore/Debug/ProfilerBus.h>
 #include <AzCore/Serialization/SerializeContext.h>
 #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
 #include <AzCore/std/smart_ptr/make_shared.h>
@@ -122,10 +127,10 @@
 
 #include <Utils/Utils.h>
 
-#include <Profiler/ProfilerBus.h>
 #include <Profiler/ProfilerImGuiBus.h>
 
 #include "ExampleComponentBus.h"
+#include <EntityUtilityFunctions.h>
 
 namespace Platform
 {
@@ -147,44 +152,78 @@ namespace AtomSampleViewer
         return (numSamples == 1) || (numSamples == 2) || (numSamples == 4) || (numSamples == 8);
     }
 
-    SampleEntry SampleEntry::NewRHISample(const AZStd::string& name, const AZ::Uuid& uuid)
+    template <typename T>
+    static SampleEntry NewSample(SamplePipelineType type, const char* menuName, const AZStd::string& name)
     {
         SampleEntry entry;
         entry.m_sampleName = name;
-        entry.m_sampleUuid = uuid;
-        entry.m_pipelineType = SamplePipelineType::RHI;
+        entry.m_sampleUuid = azrtti_typeid<T>();
+        entry.m_pipelineType = type;
+        entry.m_componentDescriptor = T::CreateDescriptor();
+        entry.m_parentMenuName = menuName;
+        entry.m_fullName = entry.m_parentMenuName + '/' + entry.m_sampleName;
+
         return entry;
     }
 
-    SampleEntry SampleEntry::NewRHISample(const AZStd::string& name, const AZ::Uuid& uuid, AZStd::function<bool()> isSupportedFunction)
+    template <typename T>
+    static SampleEntry NewSample(SamplePipelineType type, const char* menuName, const AZStd::string& name, AZStd::function<bool()> isSupportedFunction)
     {
-        SampleEntry entry;
-        entry.m_sampleName = name;
-        entry.m_sampleUuid = uuid;
-        entry.m_pipelineType = SamplePipelineType::RHI;
+        SampleEntry entry = NewSample<T>(type, menuName, name);
         entry.m_isSupportedFunc = isSupportedFunction;
         return entry;
     }
 
-    SampleEntry SampleEntry::NewRPISample(const AZStd::string& name, const AZ::Uuid& uuid)
+    template <typename T>
+    static SampleEntry NewRHISample(const AZStd::string& name)
     {
-        SampleEntry entry;
-        entry.m_sampleName = name;
-        entry.m_sampleUuid = uuid;
-        entry.m_pipelineType = SamplePipelineType::RPI;
-        return entry;
+        return NewSample<T>(SamplePipelineType::RHI, "RHI", name);
     }
 
-    SampleEntry SampleEntry::NewRPISample(const AZStd::string& name, const AZ::Uuid& uuid, AZStd::function<bool()> isSupportedFunction)
+    template <typename T>
+    static SampleEntry NewRHISample(const AZStd::string& name, AZStd::function<bool()> isSupportedFunction)
     {
-        SampleEntry entry;
-        entry.m_sampleName = name;
-        entry.m_sampleUuid = uuid;
-        entry.m_pipelineType = SamplePipelineType::RPI;
+        SampleEntry entry = NewSample<T>(SamplePipelineType::RHI, "RHI", name, isSupportedFunction);
         entry.m_isSupportedFunc = isSupportedFunction;
         return entry;
     }
 
+    template <typename T>
+    static SampleEntry NewRPISample(const AZStd::string& name)
+    {
+        return NewSample<T>(SamplePipelineType::RPI, "RPI", name);
+    }
+
+    template <typename T>
+    static SampleEntry NewRPISample(const AZStd::string& name, AZStd::function<bool()> isSupportedFunction)
+    {
+        return NewSample<T>(SamplePipelineType::RPI, "RPI", name, isSupportedFunction);
+    }
+
+    template <typename T>
+    static SampleEntry NewFeaturesSample(const AZStd::string& name)
+    {
+        return NewSample<T>(SamplePipelineType::RPI, "Features", name);
+    }
+
+    template <typename T>
+    static SampleEntry NewFeaturesSample(const AZStd::string& name, AZStd::function<bool()> isSupportedFunction)
+    {
+        return NewSample<T>(SamplePipelineType::RPI, "Features", name, isSupportedFunction);
+    }
+
+    template <typename T>
+    static SampleEntry NewPerfSample(const AZStd::string& name)
+    {
+        return NewSample<T>(SamplePipelineType::RPI, "Performance", name);
+    }
+
+    template <typename T>
+    static SampleEntry NewPerfSample(const AZStd::string& name, AZStd::function<bool()> isSupportedFunction)
+    {
+        return NewSample<T>(SamplePipelineType::RPI, "Performance", name, isSupportedFunction);
+    }
+
     void SampleComponentManager::Reflect(AZ::ReflectContext* context)
     {
         if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
@@ -205,17 +244,86 @@ namespace AtomSampleViewer
         dependent.push_back(AZ_CRC("AzFrameworkConfigurationSystemComponentService", 0xcc49c96e)); // Ensures a scene is created for the GameEntityContext
     }
 
+    AZStd::vector<SampleEntry> SampleComponentManager::GetSamples()
+    {
+        return {
+            NewRHISample<AlphaToCoverageExampleComponent>("AlphaToCoverage"),
+            NewRHISample<AsyncComputeExampleComponent>("AsyncCompute"),
+            NewRHISample<BindlessPrototypeExampleComponent>("BindlessPrototype", []() {return Utils::GetRHIDevice()->GetFeatures().m_unboundedArrays; }),
+            NewRHISample<ComputeExampleComponent>("Compute"),
+            NewRHISample<CopyQueueComponent>("CopyQueue"),
+            NewRHISample<DualSourceBlendingComponent>("DualSourceBlending", []() {return Utils::GetRHIDevice()->GetFeatures().m_dualSourceBlending; }),
+            NewRHISample<IndirectRenderingExampleComponent>("IndirectRendering", []() {return Utils::GetRHIDevice()->GetFeatures().m_indirectCommandTier > RHI::IndirectCommandTiers::Tier0; }),
+            NewRHISample<InputAssemblyExampleComponent>("InputAssembly"),
+            NewRHISample<MSAAExampleComponent>("MSAA"),
+            NewRHISample<MultipleViewsComponent>("MultipleViews"),
+            NewRHISample<MRTExampleComponent>("MultiRenderTarget"),
+            NewRHISample<MultiThreadComponent>("MultiThread"),
+            NewRHISample<MultiViewportSwapchainComponent>("MultiViewportSwapchainComponent", [] { return IsMultiViewportSwapchainSampleSupported(); }),
+            NewRHISample<QueryExampleComponent>("Queries"),
+            NewRHISample<RayTracingExampleComponent>("RayTracing", []() {return Utils::GetRHIDevice()->GetFeatures().m_rayTracing; }),
+            NewRHISample<SphericalHarmonicsExampleComponent>("SphericalHarmonics"),
+            NewRHISample<StencilExampleComponent>("Stencil"),
+            NewRHISample<SubpassExampleComponent>("Subpass", []() {return Utils::GetRHIDevice()->GetFeatures().m_renderTargetSubpassInputSupport != AZ::RHI::SubpassInputSupportType::NotSupported; }),
+            NewRHISample<SwapchainExampleComponent>("Swapchain"),
+            NewRHISample<TextureExampleComponent>("Texture"),
+            NewRHISample<Texture3dExampleComponent>("Texture3d"),
+            NewRHISample<TextureArrayExampleComponent>("TextureArray"),
+            NewRHISample<TextureMapExampleComponent>("TextureMap"),
+            NewRHISample<TriangleExampleComponent>("Triangle"),
+            NewRHISample<TrianglesConstantBufferExampleComponent>("TrianglesConstantBuffer"),
+            NewRHISample<MatrixAlignmentTestExampleComponent>("MatrixAlignmentTest"),
+            NewRPISample<AssetLoadTestComponent>("AssetLoadTest"),
+            NewRPISample<AuxGeomExampleComponent>("AuxGeom"),
+            NewRPISample<BakedShaderVariantExampleComponent>("BakedShaderVariant"),
+            NewRPISample<SponzaBenchmarkComponent>("SponzaBenchmark"),
+            NewRPISample<CullingAndLodExampleComponent>("CullingAndLod"),
+            NewRPISample<DecalExampleComponent>("Decals"),
+            NewRPISample<DynamicDrawExampleComponent>("DynamicDraw"),
+            NewRPISample<DynamicMaterialTestComponent>("DynamicMaterialTest"),
+            NewRPISample<MaterialHotReloadTestComponent>("MaterialHotReloadTest"),
+            NewRPISample<MeshExampleComponent>("Mesh"),
+            NewRPISample<MSAA_RPI_ExampleComponent>("MSAA"),
+            NewRPISample<MultiRenderPipelineExampleComponent>("MultiRenderPipeline"),
+            NewRPISample<MultiSceneExampleComponent>("MultiScene"),
+            NewRPISample<MultiViewSingleSceneAuxGeomExampleComponent>("MultiViewSingleSceneAuxGeom"),
+            NewRPISample<ReadbackExampleComponent>("Readback"),
+            NewRPISample<RenderTargetTextureExampleComponent>("RenderTargetTexture"),
+            NewRPISample<RootConstantsExampleComponent>("RootConstants"),
+            NewRPISample<SceneReloadSoakTestComponent>("SceneReloadSoakTest"),
+            NewRPISample<StreamingImageExampleComponent>("StreamingImage"),
+            NewRPISample<ShaderReloadTestComponent>("ShaderReloadTest"),
+            NewFeaturesSample<AreaLightExampleComponent>("AreaLight"),
+            NewFeaturesSample<BloomExampleComponent>("Bloom"),
+            NewFeaturesSample<CheckerboardExampleComponent>("Checkerboard", []() {return (Utils::GetRHIDevice()->GetPhysicalDevice().GetDescriptor().m_vendorId != RHI::VendorId::ARM && Utils::GetRHIDevice()->GetPhysicalDevice().GetDescriptor().m_vendorId != RHI::VendorId::Qualcomm); }),
+            NewFeaturesSample<DepthOfFieldExampleComponent>("DepthOfField"),
+            NewFeaturesSample<DiffuseGIExampleComponent>("DiffuseGI", []() {return Utils::GetRHIDevice()->GetFeatures().m_rayTracing; }),
+            NewFeaturesSample<ExposureExampleComponent>("Exposure"),
+            NewFeaturesSample<LightCullingExampleComponent>("LightCulling"),
+            NewFeaturesSample<ParallaxMappingExampleComponent>("Parallax"),
+            NewFeaturesSample<ShadowExampleComponent>("Shadow"),
+            NewFeaturesSample<ShadowedSponzaExampleComponent>("ShadowedSponza"),
+            NewFeaturesSample<SsaoExampleComponent>("SSAO"),
+            NewFeaturesSample<SSRExampleComponent>("SSR"),
+            NewFeaturesSample<TonemappingExampleComponent>("Tonemapping"),
+            NewFeaturesSample<TransparencyExampleComponent>("Transparency"),
+            NewPerfSample<_100KDrawableExampleComponent>("100KDrawable_SingleView"),
+            NewPerfSample<_100KDraw10KDrawableExampleComponent>("100KDraw_10KDrawable_MultiView"),
+        };
+    }
+
     void SampleComponentManager::RegisterSampleComponent(const SampleEntry& sample)
     {
         if (AZStd::find(m_availableSamples.begin(), m_availableSamples.end(), sample) == m_availableSamples.end())
         {
             m_availableSamples.push_back(sample);
+            m_groupedSamples[sample.m_parentMenuName].push_back(static_cast<int32_t>(m_availableSamples.size() - 1));
         }
     }
 
     SampleComponentManager::SampleComponentManager()
         : m_imguiFrameCaptureSaver("@user@/frame_capture.xml")
-        , m_imGuiFrameTimer(FrameTimeLogSize, FrameTimeLogSize)
+        , m_imGuiFrameTimer(FrameTimeLogSize, FrameTimeLogSize, 250.0f)
     {
         m_exampleEntity = aznew AZ::Entity();
 
@@ -235,73 +343,13 @@ namespace AtomSampleViewer
 
     void SampleComponentManager::Init()
     {
-        auto isSupportedFunc = []()
+        AZStd::vector<SampleEntry> samples = GetSamples();
+        for (const SampleEntry& sample : samples)
         {
-            return SampleComponentManager::IsMultiViewportSwapchainSampleSupported();
-        };
-
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/AlphaToCoverage", azrtti_typeid<AlphaToCoverageExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/AsyncCompute", azrtti_typeid<AsyncComputeExampleComponent>() ) );
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/BindlessPrototype", azrtti_typeid<BindlessPrototypeExampleComponent>(), []() {return Utils::GetRHIDevice()->GetFeatures().m_unboundedArrays; } ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/Compute", azrtti_typeid<ComputeExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/CopyQueue", azrtti_typeid<CopyQueueComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/DualSourceBlending", azrtti_typeid<DualSourceBlendingComponent>(), []() {return Utils::GetRHIDevice()->GetFeatures().m_dualSourceBlending; } ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/IndirectRendering", azrtti_typeid<IndirectRenderingExampleComponent>(), []() {return Utils::GetRHIDevice()->GetFeatures().m_indirectCommandTier > RHI::IndirectCommandTiers::Tier0; } ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/InputAssembly", azrtti_typeid<InputAssemblyExampleComponent>()));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/MSAA", azrtti_typeid<MSAAExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/MultipleViews", azrtti_typeid<MultipleViewsComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/MultiRenderTarget", azrtti_typeid<MRTExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/MultiThread", azrtti_typeid<MultiThreadComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/MultiViewportSwapchainComponent", azrtti_typeid<MultiViewportSwapchainComponent>(), isSupportedFunc ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/Queries", azrtti_typeid<QueryExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/RayTracing", azrtti_typeid<RayTracingExampleComponent>(), []() {return Utils::GetRHIDevice()->GetFeatures().m_rayTracing; } ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/SphericalHarmonics", azrtti_typeid<SphericalHarmonicsExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/Stencil", azrtti_typeid<StencilExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/Subpass", azrtti_typeid<SubpassExampleComponent>(), []() {return Utils::GetRHIDevice()->GetFeatures().m_renderTargetSubpassInputSupport != AZ::RHI::SubpassInputSupportType::NotSupported; } ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/Swapchain", azrtti_typeid<SwapchainExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/Texture", azrtti_typeid<TextureExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/Texture3d", azrtti_typeid<Texture3dExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/TextureArray", azrtti_typeid<TextureArrayExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/TextureMap", azrtti_typeid<TextureMapExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/Triangle", azrtti_typeid<TriangleExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/TrianglesConstantBuffer", azrtti_typeid<TrianglesConstantBufferExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRHISample( "RHI/MatrixAlignmentTest", azrtti_typeid<MatrixAlignmentTestExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/AssetLoadTest", azrtti_typeid<AssetLoadTestComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/AuxGeom", azrtti_typeid<AuxGeomExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/BakedShaderVariant", azrtti_typeid<BakedShaderVariantExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/SponzaBenchmark", azrtti_typeid<SponzaBenchmarkComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/CullingAndLod", azrtti_typeid<CullingAndLodExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/Decals", azrtti_typeid<DecalExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/DynamicDraw", azrtti_typeid<DynamicDrawExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/DynamicMaterialTest", azrtti_typeid<DynamicMaterialTestComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/MaterialHotReloadTest", azrtti_typeid<MaterialHotReloadTestComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/Mesh", azrtti_typeid<MeshExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/MSAA", azrtti_typeid<MSAA_RPI_ExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/MultiRenderPipeline", azrtti_typeid<MultiRenderPipelineExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/MultiScene", azrtti_typeid<MultiSceneExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/MultiViewSingleSceneAuxGeom", azrtti_typeid<MultiViewSingleSceneAuxGeomExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/RootConstants", azrtti_typeid<RootConstantsExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/SceneReloadSoakTest", azrtti_typeid<SceneReloadSoakTestComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/Shading", azrtti_typeid<ShadingExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/StreamingImage", azrtti_typeid<StreamingImageExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "RPI/ShaderReloadTest", azrtti_typeid<ShaderReloadTestComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "Features/AreaLight", azrtti_typeid<AreaLightExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "Features/Bloom", azrtti_typeid<BloomExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "Features/Checkerboard", azrtti_typeid<CheckerboardExampleComponent>(), []() {return (Utils::GetRHIDevice()->GetPhysicalDevice().GetDescriptor().m_vendorId != RHI::VendorId::ARM && Utils::GetRHIDevice()->GetPhysicalDevice().GetDescriptor().m_vendorId != RHI::VendorId::Qualcomm); } ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "Features/DepthOfField", azrtti_typeid<DepthOfFieldExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "Features/DiffuseGI", azrtti_typeid<DiffuseGIExampleComponent>(), []() {return Utils::GetRHIDevice()->GetFeatures().m_rayTracing; }));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "Features/Exposure", azrtti_typeid<ExposureExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "Features/LightCulling", azrtti_typeid<LightCullingExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "Features/Parallax", azrtti_typeid<ParallaxMappingExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "Features/Shadow", azrtti_typeid<ShadowExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "Features/ShadowedSponza", azrtti_typeid<ShadowedSponzaExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "Features/SSAO", azrtti_typeid<SsaoExampleComponent>()));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "Features/SSR", azrtti_typeid<SSRExampleComponent>()));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "Features/Tonemapping", azrtti_typeid<TonemappingExampleComponent>() ));
-        SampleComponentManager::RegisterSampleComponent(SampleEntry::NewRPISample( "Features/Transparency", azrtti_typeid<TransparencyExampleComponent>() ));
+            RegisterSampleComponent(sample);
+        }
 
         m_scriptManager = AZStd::make_unique<ScriptManager>();
-
     }
 
     void SampleComponentManager::Activate()
@@ -372,7 +420,7 @@ namespace AtomSampleViewer
         AZ_Printf("SampleComponentManager", "Available Samples -------------------------\n");
         for (size_t i = 0; i < m_availableSamples.size(); ++i)
         {
-            AZStd::string printStr = "\t[" + m_availableSamples[i].m_sampleName + "]";
+            AZStd::string printStr = "\t[" + m_availableSamples[i].m_fullName + "]";
             if (!m_isSampleSupported[i])
             {
                 printStr += " Not Supported ";
@@ -402,7 +450,7 @@ namespace AtomSampleViewer
 
             for (int32_t i = 0; i < m_availableSamples.size(); ++i)
             {
-                AZStd::string sampleName = m_availableSamples[i].m_sampleName;
+                AZStd::string sampleName = m_availableSamples[i].m_fullName;
                 AZStd::to_lower(sampleName.begin(), sampleName.end());
 
                 if (sampleName == targetSampleName)
@@ -452,9 +500,7 @@ namespace AtomSampleViewer
 
     void SampleComponentManager::Deactivate()
     {
-        AzFramework::EntityContextRequestBus::Event(
-            m_entityContextId, &AzFramework::EntityContextRequestBus::Events::DestroyEntity, m_cameraEntity);
-        m_cameraEntity = nullptr;
+        DestroyEntity(m_cameraEntity);
 
         AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
         AZ::Render::ImGuiSystemNotificationBus::Handler::BusDisconnect();
@@ -478,7 +524,7 @@ namespace AtomSampleViewer
 
     void SampleComponentManager::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
     {
-        m_imGuiFrameTimer.PushValue(deltaTime);
+        m_imGuiFrameTimer.PushValue(deltaTime * 1000.0f);
 
         bool screenshotRequest = false;
 
@@ -812,7 +858,6 @@ namespace AtomSampleViewer
                 if (ImGui::MenuItem("Exit", "Ctrl-Q"))
                 {
                     RequestExit();
-                    return;
                 }
                 if (ImGui::MenuItem("Capture Frame...", "Ctrl-P"))
                 {
@@ -832,7 +877,7 @@ namespace AtomSampleViewer
                     Utils::ToggleFullScreenOfDefaultWindow();
                 }
 
-                if (ImGui::MenuItem("Framerate Histogram"))
+                if (ImGui::MenuItem("Frame Time Histogram"))
                 {
                     m_showFramerateHistogram = !m_showFramerateHistogram;
                 }
@@ -861,27 +906,36 @@ namespace AtomSampleViewer
             }
             if (ImGui::BeginMenu("Samples"))
             {
-                for (int32_t i = 0; i < m_availableSamples.size(); i++)
+                for (auto&& [parentMenuName, samples] : m_groupedSamples)
                 {
-                    const char* sampleName = m_availableSamples[i].m_sampleName.c_str();
-                    bool enabled = m_isSampleSupported[i];
-                    if (i < s_alphanumericCount)
+                    if (ImGui::BeginMenu(parentMenuName.c_str()))
                     {
-                        const AZStd::string hotkeyName = AZStd::string::format("Ctrl-%d: ", (i + 1) % 10);
-
-                        if (ImGui::MenuItem(sampleName, hotkeyName.c_str(), false, enabled))
+                        for (int32_t index : samples)
                         {
-                            m_selectedSampleIndex = i;
-                            m_sampleChangeRequest = true;
-                        }
-                    }
-                    else
-                    {
-                        if (ImGui::MenuItem(sampleName, nullptr, false, enabled))
-                        {
-                            m_selectedSampleIndex = i;
-                            m_sampleChangeRequest = true;
+                            SampleEntry& sample = m_availableSamples[index];
+                            const char* sampleName = sample.m_sampleName.c_str();
+                            bool enabled = m_isSampleSupported[index];
+                            if (index < s_alphanumericCount)
+                            {
+                                const AZStd::string hotkeyName = AZStd::string::format("Ctrl-%d: ", (index + 1) % 10);
+
+                                if (ImGui::MenuItem(sampleName, hotkeyName.c_str(), false, enabled))
+                                {
+                                    m_selectedSampleIndex = index;
+                                    m_sampleChangeRequest = true;
+                                }
+                            }
+                            else
+                            {
+                                if (ImGui::MenuItem(sampleName, nullptr, false, enabled))
+                                {
+                                    m_selectedSampleIndex = index;
+                                    m_sampleChangeRequest = true;
+                                }
+                            }
                         }
+
+                        ImGui::EndMenu();
                     }
                 }
 
@@ -909,6 +963,10 @@ namespace AtomSampleViewer
                 {
                     m_scriptManager->OpenScriptRunnerDialog();
                 }
+                if (ImGui::MenuItem("Run Precommit Wizard..."))
+                {
+                    m_scriptManager->OpenPrecommitWizard();
+                }
 
                 ImGui::EndMenu();
             }
@@ -937,9 +995,9 @@ namespace AtomSampleViewer
                 if (ImGui::MenuItem(CpuProfilerToolName))
                 {
                     m_showCpuProfiler = !m_showCpuProfiler;
-                    if (auto profiler = Profiler::ProfilerInterface::Get(); profiler)
+                    if (auto profilerSystem = AZ::Debug::ProfilerSystemInterface::Get(); profilerSystem)
                     {
-                        profiler->SetProfilerEnabled(m_showCpuProfiler);
+                        profilerSystem->SetActive(m_showCpuProfiler);
                     }
 
                     Utils::ReportScriptableAction("ShowTool('%s', %s)", CpuProfilerToolName, m_showCpuProfiler ? "true" : "false");
@@ -1101,12 +1159,12 @@ namespace AtomSampleViewer
 
     void SampleComponentManager::ShowFramerateHistogram(float deltaTime)
     {
-        if (ImGui::Begin("Framerate Histogram", &m_showFramerateHistogram, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings))
+        if (ImGui::Begin("Frame Time Histogram", &m_showFramerateHistogram, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings))
         {
             ImGuiHistogramQueue::WidgetSettings settings;
-            settings.m_reportInverse = true;
-            settings.m_units = "fps";
-            m_imGuiFrameTimer.Tick(deltaTime, settings);
+            settings.m_reportInverse = false;
+            settings.m_units = "ms";
+            m_imGuiFrameTimer.Tick(deltaTime * 1000.0f, settings);
         }
         ImGui::End();
     }
@@ -1290,6 +1348,7 @@ namespace AtomSampleViewer
         //Add debug camera and controller components
         AZ::Debug::CameraComponentConfig cameraConfig(m_windowContext);
         cameraConfig.m_fovY = AZ::Constants::QuarterPi;
+        cameraConfig.m_depthFar = 1000.0f;
 
         m_cameraEntity->CreateComponent(azrtti_typeid<AZ::Debug::CameraComponent>())
             ->SetConfiguration(cameraConfig);
@@ -1320,7 +1379,7 @@ namespace AtomSampleViewer
     {
         for (int32_t i = 0; i < m_availableSamples.size(); i++)
         {
-            if (m_availableSamples[i].m_sampleName == sampleName)
+            if (m_availableSamples[i].m_parentMenuName + '/' + m_availableSamples[i].m_sampleName == sampleName)
             {
                 if (!m_availableSamples[i].m_isSupportedFunc || m_availableSamples[i].m_isSupportedFunc())
                 {
@@ -1526,7 +1585,7 @@ namespace AtomSampleViewer
         // Save a reference to the generated BRDF texture so it doesn't get deleted if all the passes refering to it get deleted and it's ref count goes to zero
         if (!m_brdfTexture)
         {
-            const AZStd::shared_ptr<RPI::PassTemplate> brdfTextureTemplate = RPI::PassSystemInterface::Get()->GetPassTemplate(Name("BRDFTextureTemplate"));
+            const AZStd::shared_ptr<const RPI::PassTemplate> brdfTextureTemplate = RPI::PassSystemInterface::Get()->GetPassTemplate(Name("BRDFTextureTemplate"));
             Data::Asset<RPI::AttachmentImageAsset> brdfImageAsset = RPI::AssetUtils::LoadAssetById<RPI::AttachmentImageAsset>(
                 brdfTextureTemplate->m_imageAttachments[0].m_assetRef.m_assetId, RPI::AssetUtils::TraceLevel::Error);
             if (brdfImageAsset.IsReady())

+ 18 - 7
Gem/Code/Source/SampleComponentManager.h

@@ -12,6 +12,8 @@
 
 #include <Automation/ScriptableImGui.h> // This file needs to be included before "<Atom/Utils/ImGuiPassTree.h>" to enable scriptable imgui for ImguiPassTree
 
+#include <AzCore/std/containers/span.h>
+
 #include <Atom/Feature/ImGui/SystemBus.h>
 #include <Atom/Feature/Utils/FrameCaptureBus.h>
 #include <Atom/RPI.Public/WindowContext.h>
@@ -28,6 +30,8 @@
 
 #include <AzCore/Component/Component.h>
 #include <AzCore/Component/TickBus.h>
+#include <AzCore/std/containers/vector.h>
+#include <AzCore/std/containers/map.h>
 #include <AzCore/std/smart_ptr/shared_ptr.h>
 
 #include <AzFramework/Input/Events/InputChannelEventListener.h>
@@ -55,17 +59,19 @@ namespace AtomSampleViewer
     class SampleEntry
     {
     public:
-        static SampleEntry NewRHISample(const AZStd::string& name, const AZ::Uuid& uuid);
-        static SampleEntry NewRHISample(const AZStd::string& name, const AZ::Uuid& uuid, AZStd::function<bool()> isSupportedFunction);
-        static SampleEntry NewRPISample(const AZStd::string& name, const AZ::Uuid& uuid);
-        static SampleEntry NewRPISample(const AZStd::string& name, const AZ::Uuid& uuid, AZStd::function<bool()> isSupportedFunction);
-
+        AZStd::string m_parentMenuName;
         AZStd::string m_sampleName;
+        // m_parentMenuName/m_sampleName
+        AZStd::string m_fullName;
         AZ::Uuid m_sampleUuid;
         AZStd::function<bool()> m_isSupportedFunc;
         SamplePipelineType m_pipelineType = SamplePipelineType::RHI;
+        AZ::ComponentDescriptor* m_componentDescriptor;
 
-        bool operator==(const SampleEntry& other) { return other.m_sampleName == m_sampleName; }
+        bool operator==(const SampleEntry& other)
+        {
+            return other.m_sampleName == m_sampleName && other.m_parentMenuName == m_parentMenuName;
+        }
     };
 
     class SampleComponentManager final
@@ -86,6 +92,8 @@ namespace AtomSampleViewer
         static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
         static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
 
+        static AZStd::vector<SampleEntry> GetSamples();
+
         SampleComponentManager();
         ~SampleComponentManager() override;
 
@@ -173,6 +181,9 @@ namespace AtomSampleViewer
         bool m_wasActivated = false;
 
         AZStd::vector<SampleEntry> m_availableSamples;
+        // Maps from parent menu item name to a vector of indices into the available samples vector above
+        // Note: we specifically use an ordered map to ensure menus are alphabatized.
+        AZStd::map<AZStd::string, AZStd::vector<int32_t>> m_groupedSamples;
 
         // Entity to hold only example component. It doesn't need an entity context.
         AZ::Entity* m_exampleEntity = nullptr;
@@ -185,7 +196,7 @@ namespace AtomSampleViewer
 
         int32_t m_selectedSampleIndex = -1;
 
-        static constexpr uint32_t FrameTimeLogSize = 10;
+        static constexpr uint32_t FrameTimeLogSize = 30;
         ImGuiHistogramQueue m_imGuiFrameTimer;
 
         bool m_showImGuiMetrics = false;

+ 3 - 11
Gem/Code/Source/ShaderReloadTestComponent.cpp

@@ -91,23 +91,19 @@ namespace AtomSampleViewer
         AZ::IO::Path copyFrom = AZ::IO::Path(originalFilePath);
         AZ::IO::Path copyTo = AZ::IO::Path(newFilePath);
 
-        m_fileIoErrorHandler.BusConnect();
-
         auto readResult = AZ::Utils::ReadFile(copyFrom.c_str());
         if (!readResult.IsSuccess())
         {
-            m_fileIoErrorHandler.ReportLatestIOError(readResult.GetError());
+            AZ_Error("MaterialHotReloadTestComponent", false, "%s", readResult.GetError().c_str());
             return;
         }
 
         auto writeResult = AZ::Utils::WriteFile(readResult.GetValue(), copyTo.c_str());
         if (!writeResult.IsSuccess())
         {
-            m_fileIoErrorHandler.ReportLatestIOError(writeResult.GetError());
+            AZ_Error("MaterialHotReloadTestComponent", false, "%s", writeResult.GetError().c_str());
             return;
         }
-
-        m_fileIoErrorHandler.BusDisconnect();
     }
 
     void ShaderReloadTestComponent::DeleteTestFile(const char* tempSourceFile)
@@ -116,14 +112,10 @@ namespace AtomSampleViewer
 
         if (AZ::IO::LocalFileIO::GetInstance()->Exists(deletePath.c_str()))
         {
-            m_fileIoErrorHandler.BusConnect();
-
             if (!AZ::IO::LocalFileIO::GetInstance()->Remove(deletePath.c_str()))
             {
-                m_fileIoErrorHandler.ReportLatestIOError(AZStd::string::format("Failed to delete '%s'.", deletePath.c_str()));
+                AZ_Error("MaterialHotReloadTestComponent", false, "Failed to delete '%s'.", deletePath.c_str());
             }
-
-            m_fileIoErrorHandler.BusDisconnect();
         }
     }
 

+ 0 - 3
Gem/Code/Source/ShaderReloadTestComponent.h

@@ -21,7 +21,6 @@
 
 #include <Utils/ImGuiSidebar.h>
 #include <Utils/Utils.h>
-#include <Utils/FileIOErrorHandler.h>
 
 namespace AtomSampleViewer
 {
@@ -88,8 +87,6 @@ namespace AtomSampleViewer
         // render output capture and validation.
         void ValidatePixelColor(uint32_t color);
 
-        FileIOErrorHandler m_fileIoErrorHandler;
-
         //! Async asset load. Used to guarantee that "Fullscreen.azshader" exists before
         //! instantiating the FullscreenTriangle.pass.
         AZ::AssetCollectionAsyncLoader m_assetLoadManager;

+ 0 - 175
Gem/Code/Source/ShadingExampleComponent.cpp

@@ -1,175 +0,0 @@
-/*
- * 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 <ShadingExampleComponent.h>
-
-#include <Atom/Component/DebugCamera/ArcBallControllerComponent.h>
-#include <Atom/Component/DebugCamera/NoClipControllerComponent.h>
-
-#include <Atom/Feature/LuxCore/LuxCoreBus.h>
-
-#include <Atom/RPI.Public/View.h>
-#include <Atom/RPI.Public/Image/StreamingImage.h>
-
-#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
-#include <Atom/RPI.Reflect/Model/ModelAsset.h>
-#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
-
-#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
-
-#include <SampleComponentManager.h>
-#include <SampleComponentConfig.h>
-
-#include <RHI/BasicRHIComponent.h>
-
-namespace AtomSampleViewer
-{
-    const char* ShadingExampleComponent::CameraControllerNameTable[CameraControllerCount] =
-    {
-        "ArcBall",
-        "NoClip"
-    };
-
-    void ShadingExampleComponent::Reflect(AZ::ReflectContext* context)
-    {
-        if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
-        {
-            serializeContext->Class < ShadingExampleComponent, AZ::Component>()
-                ->Version(0)
-                ;
-        }
-    }
-
-    ShadingExampleComponent::ShadingExampleComponent()
-    {
-    }
-
-    void ShadingExampleComponent::Activate()
-    {
-        AZ::RPI::AssetUtils::TraceLevel traceLevel = AZ::RPI::AssetUtils::TraceLevel::Assert;
-        auto meshAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>("objects/shaderball_simple.azmodel", traceLevel);
-        auto materialAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::MaterialAsset>(DefaultPbrMaterialPath, traceLevel);
-        m_meshHandle = GetMeshFeatureProcessor()->AcquireMesh(AZ::Render::MeshHandleDescriptor{ meshAsset }, AZ::RPI::Material::FindOrCreate(materialAsset));
-        GetMeshFeatureProcessor()->SetTransform(m_meshHandle, AZ::Transform::CreateIdentity());
-
-        UseArcBallCameraController();
-
-        // Add an Image based light.
-        m_defaultIbl.Init(m_scene);
-
-        // Load default IBL texture asset for LuxCore
-        // We should be able to extract this information from SkyBox in the future
-        LoadIBLImage("textures/sampleenvironment/example_iblskyboxcm.dds.streamingimage");
-
-        AZ::Render::LuxCoreRequestsBus::Broadcast(&AZ::Render::LuxCoreRequestsBus::Events::SetCameraEntityID, GetCameraEntityId());
-        AzFramework::InputChannelEventListener::Connect();
-        AZ::TickBus::Handler::BusConnect();
-    }
-
-    void ShadingExampleComponent::Deactivate()
-    {
-        RemoveController();
-        m_defaultIbl.Reset();
-        GetMeshFeatureProcessor()->ReleaseMesh(m_meshHandle);
-
-        AzFramework::InputChannelEventListener::Disconnect();
-        AZ::Render::LuxCoreRequestsBus::Broadcast(&AZ::Render::LuxCoreRequestsBus::Events::ClearLuxCore);
-        AZ::TickBus::Handler::BusDisconnect();
-    }
-
-    void ShadingExampleComponent::UseArcBallCameraController()
-    {
-        AZ::Debug::CameraControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::CameraControllerRequestBus::Events::Enable,
-            azrtti_typeid<AZ::Debug::ArcBallControllerComponent>());
-    }
-
-    void ShadingExampleComponent::UseNoClipCameraController()
-    {
-        AZ::Debug::CameraControllerRequestBus::Event(GetCameraEntityId(), &AZ::Debug::CameraControllerRequestBus::Events::Enable,
-            azrtti_typeid<AZ::Debug::NoClipControllerComponent>());
-    }
-
-    void ShadingExampleComponent::LoadIBLImage(const char* imagePath)
-    {
-        auto imageAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::StreamingImageAsset>
-            (imagePath, AZ::RPI::AssetUtils::TraceLevel::Assert);
-
-        // FindOrCreate will synchronously load the image if necessary.
-        IBLImage = AZ::RPI::StreamingImage::FindOrCreate(imageAsset);
-        AZ_Assert(IBLImage != nullptr, "Failed to find or create an image instance from image asset");
-    }
-
-    bool ShadingExampleComponent::OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel)
-    {
-        const AzFramework::InputChannelId& inputChannelId = inputChannel.GetInputChannelId();
-        switch (inputChannel.GetState())
-        {
-            case AzFramework::InputChannel::State::Ended:
-            {
-                // L key pressed
-                if (inputChannelId == AzFramework::InputDeviceKeyboard::Key::AlphanumericL)
-                {
-                    if (!m_renderSent)
-                    {
-                        m_renderSent = true;
-                    }
-                }
-            }
-            default:
-            {
-                break;
-            }
-        }
-        return false;
-    }
-    
-    void ShadingExampleComponent::RemoveController()
-    {
-
-    }
-
-    void ShadingExampleComponent::SetArcBallControllerParams()
-    {
-
-    }
-
-    void ShadingExampleComponent::OnTick(float deltaTime, AZ::ScriptTimePoint time)
-    {
-        AZ_UNUSED(deltaTime);
-        AZ_UNUSED(time);
-        
-        if (m_renderSent)
-        {
-            if (!m_dataLoaded)
-            {
-                AZ::Render::LuxCoreRequestsBus::Broadcast(&AZ::Render::LuxCoreRequestsBus::Events::ClearLuxCore);
-
-                AZ::Data::Asset<AZ::RPI::ModelAsset> modelAsset = GetMeshFeatureProcessor()->GetModel(m_meshHandle)->GetModelAsset();
-                AZ::Data::Instance<AZ::RPI::Material> materialInstance = GetMeshFeatureProcessor()->GetMaterialAssignmentMap(m_meshHandle).at(AZ::Render::DefaultMaterialAssignmentId).m_materialInstance;
-
-                AZ::Render::LuxCoreRequestsBus::Broadcast(&AZ::Render::LuxCoreRequestsBus::Events::AddMesh, modelAsset);
-                AZ::Render::LuxCoreRequestsBus::Broadcast(&AZ::Render::LuxCoreRequestsBus::Events::AddMaterial, materialInstance);
-                AZ::Render::LuxCoreRequestsBus::Broadcast(&AZ::Render::LuxCoreRequestsBus::Events::AddObject, modelAsset, materialInstance->GetId());
-
-                AZ::Render::LuxCoreRequestsBus::Broadcast(&AZ::Render::LuxCoreRequestsBus::Events::AddTexture, IBLImage, AZ::Render::LuxCoreTextureType::IBL);
-
-                m_dataLoaded = true;
-            }
-
-            // wait till texture ready
-            AZ::Render::LuxCoreRequestsBus::BroadcastResult(m_textureReady, &AZ::Render::LuxCoreRequestsBus::Events::CheckTextureStatus);
-            if (m_textureReady)
-            {
-                AZ::Render::LuxCoreRequestsBus::Broadcast(&AZ::Render::LuxCoreRequestsBus::Events::RenderInLuxCore);
-
-                m_renderSent = false;
-                m_dataLoaded = false;
-            }
-        }
-    }
-}

+ 0 - 87
Gem/Code/Source/ShadingExampleComponent.h

@@ -1,87 +0,0 @@
-/*
- * 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 <CommonSampleComponentBase.h>
-
-#include <AzCore/Component/EntityBus.h>
-#include <AzCore/Component/TickBus.h>
-
-#include <AzFramework/Input/Events/InputChannelEventListener.h>
-
-#include <Utils/Utils.h>
-
-namespace AtomSampleViewer
-{
-    //!
-    //! This component creates a simple scene to test Atom's shading model.
-    //! On Windows, by pressing 'L' it will launch luxcoreui.exe for PBR validation.
-    //!
-    class ShadingExampleComponent final
-        : public CommonSampleComponentBase
-        , public AzFramework::InputChannelEventListener
-        , public AZ::TickBus::Handler
-    {
-    public:
-        AZ_COMPONENT(ShadingExampleComponent, "{91ED709C-1114-46E3-823C-C0B59FB8E4B6}", CommonSampleComponentBase);
-
-        static void Reflect(AZ::ReflectContext* context);
-
-        ShadingExampleComponent();
-        ~ShadingExampleComponent() override = default;
-
-        void Activate() override;
-        void Deactivate() override;
-
-    private:
-        
-        // AzFramework::InputChannelEventListener
-        bool OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel) override;
-
-        // TickBus::Handler
-        void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
-
-        void UseArcBallCameraController();
-        void UseNoClipCameraController();
-        void RemoveController();
-
-        void SetArcBallControllerParams();
-
-        void LoadIBLImage(const char* imagePath);
-
-        enum class CameraControllerType : int32_t
-        {
-            ArcBall = 0,
-            NoClip,
-            Count
-        };
-
-        static const uint32_t CameraControllerCount = static_cast<uint32_t>(CameraControllerType::Count);
-        static const char* CameraControllerNameTable[CameraControllerCount];
-        CameraControllerType m_currentCameraControllerType = CameraControllerType::ArcBall;
-
-        // Owned by this sample
-        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_meshHandle;
-
-        // Not owned by this sample, we look this up
-        AZ::Component* m_cameraControlComponent = nullptr;
-        Utils::DefaultIBL m_defaultIbl;
-
-        static constexpr float m_arcballRadiusMinModifier = 0.01f;
-        static constexpr float m_arcballRadiusMaxModifier = 2.0f;
-
-        bool m_cameraControllerDisabled = false;
-
-        bool m_renderSent = false;
-        bool m_dataLoaded = false;
-        bool m_textureReady = false;
-        AZ::Data::Instance<AZ::RPI::Image> IBLImage;
-
-    };
-} // namespace AtomSampleViewer

+ 7 - 7
Gem/Code/Source/ShadowExampleComponent.cpp

@@ -507,8 +507,8 @@ namespace AtomSampleViewer
 
             ImGui::Text("Shadowmap Size");
             if (ScriptableImGui::Combo(
-                    "Size##Directional", &m_directionalLightImageSizeIndex, s_shadowmapImageSizeLabels,
-                    AZ_ARRAY_SIZE(s_shadowmapImageSizeLabels)))
+                "Size##Directional", &m_directionalLightImageSizeIndex, s_shadowmapImageSizeLabels,
+                aznumeric_cast<int>(AZStd::size(s_shadowmapImageSizeLabels))))
             {
                 m_directionalLightFeatureProcessor->SetShadowmapSize(
                     m_directionalLightHandle, s_shadowmapImageSizes[m_directionalLightImageSizeIndex]);
@@ -564,8 +564,8 @@ namespace AtomSampleViewer
 
             ImGui::Text("Filtering");
             if (ScriptableImGui::Combo(
-                    "Filter Method##Directional", &m_shadowFilterMethodIndexDirectional, s_shadowFilterMethodLabels,
-                    AZ_ARRAY_SIZE(s_shadowFilterMethodLabels)))
+                "Filter Method##Directional", &m_shadowFilterMethodIndexDirectional, s_shadowFilterMethodLabels,
+                aznumeric_cast<int>(AZStd::size(s_shadowFilterMethodLabels))))
             {
                 m_directionalLightFeatureProcessor->SetShadowFilterMethod(
                     m_directionalLightHandle, s_shadowFilterMethods[m_shadowFilterMethodIndexDirectional]);
@@ -656,7 +656,7 @@ namespace AtomSampleViewer
             ImGui::Text("Shadowmap Size");
             shadowmapSizeChanged = shadowmapSizeChanged ||
                 ScriptableImGui::Combo("Size##Positional", &m_positionalLightImageSizeIndices[index], s_shadowmapImageSizeLabels,
-                                       AZ_ARRAY_SIZE(s_shadowmapImageSizeLabels));
+                    aznumeric_cast<int>(AZStd::size(s_shadowmapImageSizeLabels)));
             if (shadowmapSizeChanged)
             {
                 // Reset shadow parameters when shadow is disabled.
@@ -672,8 +672,8 @@ namespace AtomSampleViewer
 
             ImGui::Text("Filtering");
             if (ScriptableImGui::Combo(
-                    "Filter Method##Positional", &m_shadowFilterMethodIndicesPositional[index], s_shadowFilterMethodLabels,
-                    AZ_ARRAY_SIZE(s_shadowFilterMethodLabels)))
+                "Filter Method##Positional", &m_shadowFilterMethodIndicesPositional[index], s_shadowFilterMethodLabels,
+                aznumeric_cast<int>(AZStd::size(s_shadowFilterMethodLabels))))
             {
                 settingsChanged = true;
             }

+ 3 - 3
Gem/Code/Source/ShadowedSponzaExampleComponent.cpp

@@ -407,7 +407,7 @@ namespace AtomSampleViewer
                 "Size",
                 &m_directionalLightShadowmapSizeIndex,
                 s_directionalLightShadowmapSizeLabels,
-                AZ_ARRAY_SIZE(s_directionalLightShadowmapSizeLabels)))
+                aznumeric_cast<int>(AZStd::size(s_directionalLightShadowmapSizeLabels))))
             {
                 m_directionalLightFeatureProcessor->SetShadowmapSize(
                     m_directionalLightHandle,
@@ -469,7 +469,7 @@ namespace AtomSampleViewer
                 "Filter Method##Directional",
                 &m_shadowFilterMethodIndexDirectional,
                 s_shadowFilterMethodLabels,
-                AZ_ARRAY_SIZE(s_shadowFilterMethodLabels)))
+                aznumeric_cast<int>(AZStd::size(s_shadowFilterMethodLabels))))
             {
                 m_directionalLightFeatureProcessor->SetShadowFilterMethod(
                     m_directionalLightHandle,
@@ -550,7 +550,7 @@ namespace AtomSampleViewer
                 "Filter Method##Spot",
                 &m_shadowFilterMethodIndexDisk,
                 s_shadowFilterMethodLabels,
-                AZ_ARRAY_SIZE(s_shadowFilterMethodLabels)))
+                aznumeric_cast<int>(AZStd::size(s_shadowFilterMethodLabels))))
             {
                 for (int index = 0; index < m_diskLightCount; ++index)
                 {

+ 1 - 0
Gem/Code/Source/SponzaBenchmarkComponent.cpp

@@ -156,6 +156,7 @@ namespace AtomSampleViewer
         m_directionalLightFeatureProcessor->SetShadowmapSize(handle, AZ::Render::ShadowmapSizeNamespace::ShadowmapSize::Size2048);
         m_directionalLightFeatureProcessor->SetViewFrustumCorrectionEnabled(handle, true);
         m_directionalLightFeatureProcessor->SetShadowFilterMethod(handle, AZ::Render::ShadowFilterMethod::EsmPcf);
+        m_directionalLightFeatureProcessor->SetShadowFarClipDistance(handle, 100.0f);
         m_directionalLightFeatureProcessor->SetFilteringSampleCount(handle, 16);
         m_directionalLightFeatureProcessor->SetGroundHeight(handle, 0.f);
         m_directionalLightHandle = handle;

+ 1 - 1
Gem/Code/Source/TonemappingExampleComponent.cpp

@@ -162,7 +162,7 @@ namespace AtomSampleViewer
             "Input Colorspace",
             &m_inputColorSpaceIndex,
             s_colorSpaceLabels,
-            AZ_ARRAY_SIZE(s_colorSpaceLabels)))
+            aznumeric_cast<int>(AZStd::size(s_colorSpaceLabels))))
         {
             m_inputColorSpace = GetColorSpaceIdForIndex(static_cast<uint8_t>(m_inputColorSpaceIndex));
             m_drawImage.m_srg->SetConstant<int>(m_colorSpaceIndex, static_cast<int>(m_inputColorSpace));

+ 0 - 42
Gem/Code/Source/Utils/FileIOErrorHandler.cpp

@@ -1,42 +0,0 @@
-/*
- * 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 <Utils/FileIOErrorHandler.h>
-
-namespace AtomSampleViewer
-{
-    void FileIOErrorHandler::OnError([[maybe_unused]] const AZ::IO::SystemFile* file, [[maybe_unused]] const char* fileName, int errorCode)
-    {
-        m_ioErrorCode = errorCode;
-    }
-
-    void FileIOErrorHandler::BusConnect()
-    {
-        Base::BusConnect();
-        m_ioErrorCode = ErrorCodeNotSet;
-    }
-
-    void FileIOErrorHandler::ReportLatestIOError(AZStd::string message)
-    {
-        AZ_Assert(BusIsConnected(), "FileIOErrorHandler must be connected while calling ReportLatestIOError");
-
-        if (m_ioErrorCode != ErrorCodeNotSet)
-        {
-            message += AZStd::string::format(" IO error code %d.", m_ioErrorCode);
-        }
-
-        AZ_Error("FileIOErrorHandler", false, "%s", message.c_str());
-    }
-
-    void FileIOErrorHandler::BusDisconnect()
-    {
-        Base::BusDisconnect();
-        m_ioErrorCode = ErrorCodeNotSet;
-    }
-
-} // namespace AtomSampleViewer

+ 0 - 40
Gem/Code/Source/Utils/FileIOErrorHandler.h

@@ -1,40 +0,0 @@
-/*
- * 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/IO/FileIOEventBus.h>
-#include <AzCore/std/string/string.h>
-
-namespace AtomSampleViewer
-{
-    //! Use this to report AZ::IO errors and include the OS error code in the message.
-    //! The general pattern is to call:
-    //!     FileIOErrorHandler::BusConnect()
-    //!     if(!someAzIoOperation())
-    //!         FileIOErrorHandler::ReportLatestIOError(myMessage)
-    //!     FileIOErrorHandler::BusDisconnect()
-    class FileIOErrorHandler
-        : public AZ::IO::FileIOEventBus::Handler
-    {
-        using Base = AZ::IO::FileIOEventBus::Handler;
-    public:
-
-        void BusConnect(); //!< Also resets m_ioErrorCode
-        void ReportLatestIOError(AZStd::string message);
-        void BusDisconnect(); //!< Also resets m_ioErrorCode
-
-    private:
-
-        // AZ::IO::FileIOEventBus::Handler overrides...
-        void OnError(const AZ::IO::SystemFile* file, const char* fileName, int errorCode) override;
-
-        static constexpr int ErrorCodeNotSet = -1;
-        int m_ioErrorCode = ErrorCodeNotSet;
-    };
-} // namespace AtomSampleViewer

+ 112 - 105
Gem/Code/Source/Utils/ImGuiHistogramQueue.cpp

@@ -1,105 +1,112 @@
-/*
- * 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 <Utils/ImGuiHistogramQueue.h>
-#include <AzCore/std/string/string.h>
-#include <imgui/imgui.h>
-
-namespace AtomSampleViewer
-{
-
-    ImGuiHistogramQueue::ImGuiHistogramQueue(
-        AZStd::size_t maxSamples,
-        AZStd::size_t runningAverageSamples,
-        float numericDisplayUpdateDelay)
-        : m_maxSamples(maxSamples)
-        , m_runningAverageSamples(runningAverageSamples)
-        , m_numericDisplayDelay(numericDisplayUpdateDelay)
-    {
-        AZ_Assert(m_maxSamples >= m_runningAverageSamples, "maxSamples must be larger");
-
-        m_valueLog.reserve(m_maxSamples);
-        m_averageLog.reserve(m_maxSamples);
-    }
-
-    float ImGuiHistogramQueue::CalculateAverage(AZStd::size_t maxSampleCount)
-    {
-        size_t sampleCount = AZStd::min<size_t>(maxSampleCount, m_valueLog.size());
-
-        float average = 0.0f;
-        for (size_t i = 0; i < sampleCount; ++i)
-        {
-            average += m_valueLog.at(i);
-        }
-        average /= sampleCount;
-
-        return average;
-    }
-
-    void ImGuiHistogramQueue::PushValue(float value)
-    {
-        m_samplesSinceLastDisplayUpdate++;
-
-        // Update the log of all values
-        if (m_valueLog.size() == m_maxSamples)
-        {
-            m_valueLog.pop_back();
-        }
-        m_valueLog.insert(m_valueLog.begin(), value);
-
-        // Calculate running average for line graph
-        if (m_averageLog.size() == m_maxSamples)
-        {
-            m_averageLog.pop_back();
-        }
-        m_averageLog.insert(m_averageLog.begin(), CalculateAverage(m_runningAverageSamples));
-
-        // Calculate average for numeric display
-        if (m_timeSinceLastDisplayUpdate >= m_numericDisplayDelay || m_samplesSinceLastDisplayUpdate >= m_maxSamples)
-        {
-            m_displayedAverage = CalculateAverage(m_samplesSinceLastDisplayUpdate);
-
-            m_timeSinceLastDisplayUpdate = 0.0f;
-            m_samplesSinceLastDisplayUpdate = 0;
-        }
-    }
-
-    void ImGuiHistogramQueue::Tick(float deltaTime, WidgetSettings settings)
-    {
-        if (m_averageLog.empty() || m_valueLog.empty())
-        {
-            return;
-        }
-
-        m_timeSinceLastDisplayUpdate += deltaTime;
-
-        ImVec2 pos = ImGui::GetCursorPos();
-
-        AZStd::string valueString;
-        if (settings.m_reportInverse)
-        {
-            valueString  = AZStd::string::format("%4.2f %s", 1.0 / m_displayedAverage, settings.m_units);
-        }
-        else
-        {
-            valueString = AZStd::string::format("%4.2f %s", m_displayedAverage, settings.m_units);
-        }
-
-        // Draw moving average of values first
-        ImGui::PushStyleColor(ImGuiCol_PlotLines, ImVec4(0.6, 0.8, 0.9, 1.0));
-        ImGui::PlotLines("##Average", &m_averageLog[0], int32_t(m_averageLog.size()), 0, nullptr, 0.0f, m_displayedAverage * 2.0f, ImVec2(300, 50));
-        ImGui::PopStyleColor();
-
-        // Draw individual value bars on top of it (with no background).
-        ImGui::SetCursorPos(pos);
-        ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0, 0, 0, 0));
-        ImGui::PlotHistogram("##Value", &m_valueLog[0], int32_t(m_valueLog.size()), 0, valueString.c_str(), 0.0f, m_displayedAverage * 2.0f, ImVec2(300, 50));
-        ImGui::PopStyleColor();
-    }
-
-} // namespace AtomSampleViewer
+/*
+ * 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 <Utils/ImGuiHistogramQueue.h>
+#include <AzCore/std/string/string.h>
+#include <imgui/imgui.h>
+
+namespace AtomSampleViewer
+{
+
+    ImGuiHistogramQueue::ImGuiHistogramQueue(
+        AZStd::size_t maxSamples,
+        AZStd::size_t runningAverageSamples,
+        float numericDisplayUpdateDelay)
+        : m_maxSamples(maxSamples)
+        , m_runningAverageSamples(runningAverageSamples)
+        , m_numericDisplayDelay(numericDisplayUpdateDelay)
+    {
+        AZ_Assert(m_maxSamples >= m_runningAverageSamples, "maxSamples must be larger");
+
+        m_valueLog.reserve(m_maxSamples);
+        m_averageLog.reserve(m_maxSamples);
+    }
+
+    float ImGuiHistogramQueue::UpdateDisplayedValues(AZStd::size_t maxSampleCount, float& minValue, float& maxValue)
+    {
+        size_t sampleCount = AZStd::min<size_t>(maxSampleCount, m_valueLog.size());
+        float average = 0.f;
+        if (sampleCount > 0)
+        {
+            average = m_valueLog.at(0);
+            minValue = maxValue = average;
+            for (size_t i = 1; i < sampleCount; ++i)
+            {
+                float curValue = m_valueLog.at(i);
+                average += curValue;
+                minValue = minValue > curValue ? curValue : minValue;
+                maxValue = maxValue < curValue ? curValue : maxValue;
+            }
+            average /= sampleCount;
+        }
+        return average;
+    }
+
+    void ImGuiHistogramQueue::PushValue(float value)
+    {
+        m_samplesSinceLastDisplayUpdate++;
+
+        // Update the log of all values
+        if (m_valueLog.size() == m_maxSamples)
+        {
+            m_valueLog.pop_back();
+        }
+        m_valueLog.insert(m_valueLog.begin(), value);
+
+        // Calculate running average for line graph
+        if (m_averageLog.size() == m_maxSamples)
+        {
+            m_averageLog.pop_back();
+        }
+        [[maybe_unused]] float minValue, maxValue; // unused required call parameters
+        m_averageLog.insert(m_averageLog.begin(), UpdateDisplayedValues(m_runningAverageSamples, minValue, maxValue));
+
+        // Calculate average for numeric display
+        if (m_timeSinceLastDisplayUpdate >= m_numericDisplayDelay || m_samplesSinceLastDisplayUpdate >= m_maxSamples)
+        {
+            m_displayedAverage = UpdateDisplayedValues(m_samplesSinceLastDisplayUpdate, m_displayedMinimum, m_displayedMaximum);
+
+            m_timeSinceLastDisplayUpdate = 0.0f;
+            m_samplesSinceLastDisplayUpdate = 0;
+        }
+    }
+
+    void ImGuiHistogramQueue::Tick(float deltaTime, WidgetSettings settings)
+    {
+        if (m_averageLog.empty() || m_valueLog.empty())
+        {
+            return;
+        }
+
+        m_timeSinceLastDisplayUpdate += deltaTime;
+
+        ImVec2 pos = ImGui::GetCursorPos();
+
+        AZStd::string valueString;
+        if (settings.m_reportInverse)
+        {
+            valueString  = AZStd::string::format("%4.2f %s", 1.0 / m_displayedAverage, settings.m_units);
+        }
+        else
+        {
+            valueString = AZStd::string::format("avg:%4.2f %s | min:%4.2f %s | max:%4.2f %s ", m_displayedAverage, settings.m_units, m_displayedMinimum, settings.m_units, m_displayedMaximum, settings.m_units);
+        }
+
+        // Draw moving average of values first
+        ImGui::PushStyleColor(ImGuiCol_PlotLines, ImVec4(0.6, 0.8, 0.9, 1.0));
+        ImGui::PlotLines("##Average", &m_averageLog[0], int32_t(m_averageLog.size()), 0, nullptr, 0.0f, m_displayedAverage * 2.0f, ImVec2(400, 50));
+        ImGui::PopStyleColor();
+
+        // Draw individual value bars on top of it (with no background).
+        ImGui::SetCursorPos(pos);
+        ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0, 0, 0, 0));
+        ImGui::PlotHistogram("##Value", &m_valueLog[0], int32_t(m_valueLog.size()), 0, valueString.c_str(), 0.0f, m_displayedAverage * 2.0f, ImVec2(400, 50));
+        ImGui::PopStyleColor();
+    }
+
+} // namespace AtomSampleViewer

+ 59 - 55
Gem/Code/Source/Utils/ImGuiHistogramQueue.h

@@ -1,55 +1,59 @@
-/*
- * 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/std/containers/vector.h>
-
-namespace AtomSampleViewer
-{
-    //! Tracks time values over multiple frames, computes the average, and draws a historgram.
-    class ImGuiHistogramQueue 
-    {
-    public:
-        //! @param maxSamples the max number of samples that can be recorded in the queue and displayed in the histogram
-        //! @param runningAverageSamples the number of samples to use for calculating running average hash-marks that are overlaid on the histogram
-        //! @param numericDisplayUpdateDelay the number of seconds to delay between updates of the numeric display
-        ImGuiHistogramQueue(
-            AZStd::size_t maxSamples,
-            AZStd::size_t runningAverageSamples,
-            float numericDisplayUpdateDelay = 0.25f);
-
-        struct WidgetSettings
-        {
-            bool m_reportInverse = false; //!< Use 1/average instead of average for displaying the numeric value
-            const char* m_units = "";
-        };
-
-        void PushValue(float value);
-        void Tick(float deltaTime, WidgetSettings settings);
-
-        float GetDisplayedAverage() const { return m_displayedAverage; }
-
-    private:
-
-        float CalculateAverage(AZStd::size_t maxSampleCount);
-
-        AZStd::vector<float> m_valueLog;
-        AZStd::vector<float> m_averageLog;
-
-        const AZStd::size_t m_maxSamples;
-        const AZStd::size_t m_runningAverageSamples;
-        const float m_numericDisplayDelay;
-
-        float m_timeSinceLastDisplayUpdate = 0.0f;
-        int m_samplesSinceLastDisplayUpdate = 0;
-
-        float m_displayedAverage = 0.0f;
-    };
-
-} // namespace AtomSampleViewer
+/*
+ * 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/std/containers/vector.h>
+
+namespace AtomSampleViewer
+{
+    //! Tracks time values over multiple frames, computes the average, and draws a historgram.
+    class ImGuiHistogramQueue 
+    {
+    public:
+        //! @param maxSamples the max number of samples that can be recorded in the queue and displayed in the histogram
+        //! @param runningAverageSamples the number of samples to use for calculating running average hash-marks that are overlaid on the histogram
+        //! @param numericDisplayUpdateDelay the number of seconds to delay between updates of the numeric display
+        ImGuiHistogramQueue(
+            AZStd::size_t maxSamples,
+            AZStd::size_t runningAverageSamples,
+            float numericDisplayUpdateDelay = 0.25f);
+
+        struct WidgetSettings
+        {
+            bool m_reportInverse = false; //!< Use 1/average instead of average for displaying the numeric value
+            const char* m_units = "";
+        };
+
+        void PushValue(float value);
+        void Tick(float deltaTime, WidgetSettings settings);
+
+        float GetDisplayedAverage() const { return m_displayedAverage; }
+        float GetDisplayedMinimum() const { return m_displayedMinimum; }
+        float GetDisplayedMaximum() const { return m_displayedMaximum; }
+
+    private:
+
+        float UpdateDisplayedValues(AZStd::size_t maxSampleCount, float& minValue, float& maxValue);
+
+        AZStd::vector<float> m_valueLog;
+        AZStd::vector<float> m_averageLog;
+
+        const AZStd::size_t m_maxSamples;
+        const AZStd::size_t m_runningAverageSamples;
+        const float m_numericDisplayDelay;
+
+        float m_timeSinceLastDisplayUpdate = 0.0f;
+        int m_samplesSinceLastDisplayUpdate = 0;
+
+        float m_displayedAverage = 0.0f;
+        float m_displayedMinimum = 0.0f;
+        float m_displayedMaximum = 0.0f;
+    };
+
+} // namespace AtomSampleViewer

+ 11 - 4
Gem/Code/atomsampleviewergem_private_files.cmake

@@ -20,6 +20,7 @@ set(FILES
     Source/Automation/AssetStatusTracker.h
     Source/Automation/ImageComparisonConfig.h
     Source/Automation/ImageComparisonConfig.cpp
+    Source/Automation/PrecommitWizardSettings.h
     Source/Automation/ScriptableImGui.cpp
     Source/Automation/ScriptableImGui.h
     Source/Automation/ScriptManager.cpp
@@ -82,6 +83,12 @@ set(FILES
     Source/RHI/RayTracingExampleComponent.h
     Source/RHI/MatrixAlignmentTestExampleComponent.cpp
     Source/RHI/MatrixAlignmentTestExampleComponent.h
+    Source/Performance/HighInstanceExampleComponent.cpp
+    Source/Performance/HighInstanceExampleComponent.h
+    Source/Performance/100KDrawable_SingleView_ExampleComponent.cpp
+    Source/Performance/100KDrawable_SingleView_ExampleComponent.h
+    Source/Performance/100KDraw_10KDrawable_MultiView_ExampleComponent.cpp
+    Source/Performance/100KDraw_10KDrawable_MultiView_ExampleComponent.h
     Source/AreaLightExampleComponent.cpp
     Source/AreaLightExampleComponent.h
     Source/AssetLoadTestComponent.cpp
@@ -143,12 +150,14 @@ set(FILES
     Source/ProceduralSkinnedMesh.h
     Source/ProceduralSkinnedMeshUtils.cpp
     Source/ProceduralSkinnedMeshUtils.h
+    Source/ReadbackExampleComponent.cpp
+    Source/ReadbackExampleComponent.h
+    Source/RenderTargetTextureExampleComponent.cpp
+    Source/RenderTargetTextureExampleComponent.h
     Source/RootConstantsExampleComponent.h
     Source/RootConstantsExampleComponent.cpp
     Source/SceneReloadSoakTestComponent.cpp
     Source/SceneReloadSoakTestComponent.h
-    Source/ShadingExampleComponent.cpp
-    Source/ShadingExampleComponent.h
     Source/ShadowExampleComponent.cpp
     Source/ShadowExampleComponent.h
     Source/ShadowedSponzaExampleComponent.cpp
@@ -169,8 +178,6 @@ set(FILES
     Source/TransparencyExampleComponent.h
     Source/ShaderReloadTestComponent.cpp
     Source/ShaderReloadTestComponent.h
-    Source/Utils/FileIOErrorHandler.cpp
-    Source/Utils/FileIOErrorHandler.h
     Source/Utils/ImGuiAssetBrowser.cpp
     Source/Utils/ImGuiAssetBrowser.h
     Source/Utils/ImGuiHistogramQueue.cpp

+ 4 - 1
Gem/Code/enabled_gems.cmake

@@ -13,10 +13,13 @@ set(ENABLED_GEMS
     LyShine
     Camera
     EMotionFX
-    Atom_AtomBridge
+    Atom
     AtomSampleViewer
     SceneProcessing
     EditorPythonBindings
     ImGui
     Profiler
+    Sponza
+    MaterialEditor
+    UiBasics
 )

+ 16 - 94
Gem/gem.json

@@ -1,98 +1,20 @@
 {
     "gem_name": "AtomSampleViewerGem",
-    "Dependencies": [
-        {
-            "Uuid": "e011969cf32442fdaac2443a960ab5ff",
-            "VersionConstraints": [
-                "~>0.1.0"
-            ],
-            "_comment": "Atom RHI DX12"
-        },
-        {
-            "Uuid": "150d40d376124d98a388dfe890551c03",
-            "VersionConstraints": [
-                "~>0.1.0"
-            ],
-            "_comment": "Atom RHI Vulkan"
-        },
-        {
-            "Uuid": "5f27cdc951e64fe0be9d823dc7acbc28",
-            "VersionConstraints": [
-                "~>0.1.0"
-            ],
-            "_comment": "Atom RHI Metal"
-        },
-        {
-            "Uuid": "a218db9eb2114477b46600fea4441a6c",
-            "VersionConstraints": [
-                "~>0.1.0"
-            ],
-            "_comment": "Atom RPI"
-        },
-        {
-            "Uuid": "013d1b42ad314c929b292c143bcbf045",
-            "VersionConstraints": [
-                "~>0.1"
-            ],
-            "_comment": "Atom.Component.DebugCamera"
-        },
-        {
-            "Uuid": "d32452026dae4b7dba2ad89dbde9c48f",
-            "VersionConstraints": [
-                "~>0.1"
-            ],
-            "_comment": "Atom.Asset.Shader"
-        },
-        {
-            "Uuid": "b58e5eed0901428ca78544b04dbd61bd",
-            "VersionConstraints": [
-                "~>0.1"
-            ],
-            "_comment": "Atom.Feature.Common"
-        },
-        {
-            "Uuid": "5c850809e890497c82cd9999ecb33250",
-            "VersionConstraints": [
-                "~>0.1"
-            ],
-            "_comment": "Atom.Utils"
-        },
-        {
-            "Uuid": "c7ff89ad6e8b4b45b2fadef2bcf12d6e",
-            "VersionConstraints": [
-                "~>0.1"
-            ],
-            "_comment": "Atom_Bootstrap"
-        },
-        {
-            "Uuid": "b55b2738aa4a46c8b034fe98e6e5158b",
-            "VersionConstraints": [
-                "~>0.1"
-            ],
-            "_comment": "Atom_AtomBridge"
-        }
+    "display_name": "AtomSampleViewerGem",
+    "license": "What license AtomSampleViewerGem uses goes here: i.e. Apache-2.0 or MIT",
+    "license_url": "Link to the license web site goes here: i.e. https://opensource.org/licenses/Apache-2.0 Or https://opensource.org/licenses/MIT",
+    "origin": "The name of the originator goes here. i.e. XYZ Inc.",
+    "origin_url": "The primary repo for AtomSampleViewerGem goes here: i.e. http://www.mydomain.com",
+    "type": "Code",
+    "summary": "A short description of AtomSampleViewerGem goes here.",
+    "canonical_tags": [
+        "Gem"
     ],
-    "GemFormatVersion": 4,
-    "Uuid": "2114099d7a8940c2813e93b8b417277e",
-    "Name": "AtomSampleViewerGem",
-    "DisplayName": "AtomSampleViewerGem",
-    "Version": "0.1.0",
-    "LinkType": "Dynamic",
-    "Summary": "A short description of my Gem.",
-    "Tags": ["Game", "Asset"],
-    "IconPath": "preview.png",
-    "IsGameGem": true,
-    "Modules": [
-        {
-            "Type": "GameModule"
-        },
-        {
-            "Name": "Tools",
-            "Type": "EditorModule"
-        },
-        {
-            "Name": "Lib",
-            "Type": "StaticLib"
-        }
-    ]
+    "user_tags": [
+        "AtomSampleViewerGem"
+    ],
+    "icon_path": "preview.png",
+    "requirements": "Notice of any requirements AtomSampleViewerGem has goes here. i.e. This requires X other gem",
+    "documentation_url": "Link to any documentation of AtomSampleViewerGem goes here: i.e. https://o3de.org/docs/user-guide/gems/reference/design/white-box/",
+    "dependencies": []
 }

+ 16 - 34
Materials/Decal/airship_nose_number_decal.material

@@ -1,39 +1,21 @@
 {
-    "description": "",
     "materialType": "Materials\\Types\\StandardPBR.materialtype",
-    "parentMaterial": "",
     "materialTypeVersion": 3,
-    "properties": {
-        "baseColor": {
-            "textureMap": "Materials/Decal/airship_nose_number_decal.tif"
-        },
-        "general": {
-            "applySpecularAA": false
-        },
-        "metallic": {
-            "useTexture": false
-        },
-        "normal": {
-            "textureMap": "Materials/Decal/airship_nose_number_decal_nrm.tif"
-        },
-        "opacity": {
-            "alphaSource": "Split",
-            "doubleSided": true,
-            "factor": 0.6899999976158142,
-            "mode": "Cutout",
-            "textureMap": "Materials/Decal/airship_nose_number_decal.tif"
-        },
-        "roughness": {
-            "useTexture": false
-        },
-        "specularF0": {
-            "useTexture": false
-        },
-        "uv": {
-            "center": [
-                0.0,
-                1.0
-            ]
-        }
+    "propertyValues": {
+        "baseColor.textureMap": "Materials/Decal/airship_nose_number_decal.tif",
+        "general.applySpecularAA": false,
+        "metallic.useTexture": false,
+        "normal.textureMap": "Materials/Decal/airship_nose_number_decal_nrm.tif",
+        "opacity.alphaSource": "Split",
+        "opacity.doubleSided": true,
+        "opacity.factor": 0.6899999976158142,
+        "opacity.mode": "Cutout",
+        "opacity.textureMap": "Materials/Decal/airship_nose_number_decal.tif",
+        "roughness.useTexture": false,
+        "specularF0.useTexture": false,
+        "uv.center": [
+            0.0,
+            1.0
+        ]
     }
 }

+ 16 - 34
Materials/Decal/airship_symbol_decal.material

@@ -1,39 +1,21 @@
 {
-    "description": "",
     "materialType": "Materials\\Types\\StandardPBR.materialtype",
-    "parentMaterial": "",
     "materialTypeVersion": 3,
-    "properties": {
-        "baseColor": {
-            "textureMap": "Materials/Decal/airship_symbol_decal.tif"
-        },
-        "general": {
-            "applySpecularAA": false
-        },
-        "metallic": {
-            "useTexture": false
-        },
-        "normal": {
-            "textureMap": "Materials/Decal/airship_symbol_decal_nrm.tif"
-        },
-        "opacity": {
-            "alphaSource": "Split",
-            "doubleSided": true,
-            "factor": 0.6899999976158142,
-            "mode": "Cutout",
-            "textureMap": "Materials/Decal/airship_symbol_decal.tif"
-        },
-        "roughness": {
-            "useTexture": false
-        },
-        "specularF0": {
-            "useTexture": false
-        },
-        "uv": {
-            "center": [
-                0.0,
-                1.0
-            ]
-        }
+    "propertyValues": {
+        "baseColor.textureMap": "Materials/Decal/airship_symbol_decal.tif",
+        "general.applySpecularAA": false,
+        "metallic.useTexture": false,
+        "normal.textureMap": "Materials/Decal/airship_symbol_decal_nrm.tif",
+        "opacity.alphaSource": "Split",
+        "opacity.doubleSided": true,
+        "opacity.factor": 0.6899999976158142,
+        "opacity.mode": "Cutout",
+        "opacity.textureMap": "Materials/Decal/airship_symbol_decal.tif",
+        "roughness.useTexture": false,
+        "specularF0.useTexture": false,
+        "uv.center": [
+            0.0,
+            1.0
+        ]
     }
 }

+ 16 - 34
Materials/Decal/airship_tail_01_decal.material

@@ -1,39 +1,21 @@
 {
-    "description": "",
     "materialType": "Materials\\Types\\StandardPBR.materialtype",
-    "parentMaterial": "",
     "materialTypeVersion": 3,
-    "properties": {
-        "baseColor": {
-            "textureMap": "Materials/Decal/airship_tail_01_decal.tif"
-        },
-        "general": {
-            "applySpecularAA": false
-        },
-        "metallic": {
-            "useTexture": false
-        },
-        "normal": {
-            "textureMap": "Materials/Decal/airship_tail_01_decal_nrm.tif"
-        },
-        "opacity": {
-            "alphaSource": "Split",
-            "doubleSided": true,
-            "factor": 0.6899999976158142,
-            "mode": "Cutout",
-            "textureMap": "Materials/Decal/airship_tail_01_decal_nrm.tif"
-        },
-        "roughness": {
-            "useTexture": false
-        },
-        "specularF0": {
-            "useTexture": false
-        },
-        "uv": {
-            "center": [
-                0.0,
-                1.0
-            ]
-        }
+    "propertyValues": {
+        "baseColor.textureMap": "Materials/Decal/airship_tail_01_decal.tif",
+        "general.applySpecularAA": false,
+        "metallic.useTexture": false,
+        "normal.textureMap": "Materials/Decal/airship_tail_01_decal_nrm.tif",
+        "opacity.alphaSource": "Split",
+        "opacity.doubleSided": true,
+        "opacity.factor": 0.6899999976158142,
+        "opacity.mode": "Cutout",
+        "opacity.textureMap": "Materials/Decal/airship_tail_01_decal_nrm.tif",
+        "roughness.useTexture": false,
+        "specularF0.useTexture": false,
+        "uv.center": [
+            0.0,
+            1.0
+        ]
     }
 }

+ 16 - 34
Materials/Decal/airship_tail_02_decal.material

@@ -1,39 +1,21 @@
 {
-    "description": "",
     "materialType": "Materials\\Types\\StandardPBR.materialtype",
-    "parentMaterial": "",
     "materialTypeVersion": 3,
-    "properties": {
-        "baseColor": {
-            "textureMap": "Materials/Decal/airship_tail_02_decal.tif"
-        },
-        "general": {
-            "applySpecularAA": false
-        },
-        "metallic": {
-            "useTexture": false
-        },
-        "normal": {
-            "textureMap": "Materials/Decal/airship_tail_02_decal_nrm.tif"
-        },
-        "opacity": {
-            "alphaSource": "Split",
-            "doubleSided": true,
-            "factor": 0.6899999976158142,
-            "mode": "Cutout",
-            "textureMap": "Materials/Decal/airship_tail_02_decal.tif"
-        },
-        "roughness": {
-            "useTexture": false
-        },
-        "specularF0": {
-            "useTexture": false
-        },
-        "uv": {
-            "center": [
-                0.0,
-                1.0
-            ]
-        }
+    "propertyValues": {
+        "baseColor.textureMap": "Materials/Decal/airship_tail_02_decal.tif",
+        "general.applySpecularAA": false,
+        "metallic.useTexture": false,
+        "normal.textureMap": "Materials/Decal/airship_tail_02_decal_nrm.tif",
+        "opacity.alphaSource": "Split",
+        "opacity.doubleSided": true,
+        "opacity.factor": 0.6899999976158142,
+        "opacity.mode": "Cutout",
+        "opacity.textureMap": "Materials/Decal/airship_tail_02_decal.tif",
+        "roughness.useTexture": false,
+        "specularF0.useTexture": false,
+        "uv.center": [
+            0.0,
+            1.0
+        ]
     }
 }

+ 16 - 34
Materials/Decal/am_mud_decal.material

@@ -1,39 +1,21 @@
 {
-    "description": "",
     "materialType": "Materials\\Types\\StandardPBR.materialtype",
-    "parentMaterial": "",
     "materialTypeVersion": 3,
-    "properties": {
-        "baseColor": {
-            "textureMap": "Materials/Decal/am_mud_decal.tif"
-        },
-        "general": {
-            "applySpecularAA": false
-        },
-        "metallic": {
-            "useTexture": false
-        },
-        "normal": {
-            "textureMap": "Materials/Decal/am_mud_decal_nrm.tif"
-        },  
-        "opacity": {
-            "alphaSource": "Split",
-            "doubleSided": true,
-            "factor": 0.6899999976158142,
-            "mode": "Cutout",
-            "textureMap": "Materials/Decal/am_mud_decal.tif"
-        },
-        "roughness": {
-            "useTexture": false
-        },
-        "specularF0": {
-            "useTexture": false
-        },
-        "uv": {
-            "center": [
-                0.0,
-                1.0
-            ]
-        }
+    "propertyValues": {
+        "baseColor.textureMap": "Materials/Decal/am_mud_decal.tif",
+        "general.applySpecularAA": false,
+        "metallic.useTexture": false,
+        "normal.textureMap": "Materials/Decal/am_mud_decal_nrm.tif",
+        "opacity.alphaSource": "Split",
+        "opacity.doubleSided": true,
+        "opacity.factor": 0.6899999976158142,
+        "opacity.mode": "Cutout",
+        "opacity.textureMap": "Materials/Decal/am_mud_decal.tif",
+        "roughness.useTexture": false,
+        "specularF0.useTexture": false,
+        "uv.center": [
+            0.0,
+            1.0
+        ]
     }
 }

+ 16 - 34
Materials/Decal/am_road_dust_decal.material

@@ -1,39 +1,21 @@
 {
-    "description": "",
     "materialType": "Materials\\Types\\StandardPBR.materialtype",
-    "parentMaterial": "",
     "materialTypeVersion": 3,
-    "properties": {
-        "baseColor": {
-            "textureMap": "Materials/Decal/am_road_dust_decal.tif"
-        },
-        "general": {
-            "applySpecularAA": false
-        },
-        "metallic": {
-            "useTexture": false
-        },
-        "normal": {
-            "textureMap": "Materials/Decal/am_road_dust_decal_nrm.tif"
-        },
-        "opacity": {
-            "alphaSource": "Split",
-            "doubleSided": true,
-            "factor": 0.6899999976158142,
-            "mode": "Cutout",
-            "textureMap": "Materials/Decal/am_road_dust_decal.tif"
-        },
-        "roughness": {
-            "useTexture": false
-        },
-        "specularF0": {
-            "useTexture": false
-        },
-        "uv": {
-            "center": [
-                0.0,
-                1.0
-            ]
-        }
+    "propertyValues": {
+        "baseColor.textureMap": "Materials/Decal/am_road_dust_decal.tif",
+        "general.applySpecularAA": false,
+        "metallic.useTexture": false,
+        "normal.textureMap": "Materials/Decal/am_road_dust_decal_nrm.tif",
+        "opacity.alphaSource": "Split",
+        "opacity.doubleSided": true,
+        "opacity.factor": 0.6899999976158142,
+        "opacity.mode": "Cutout",
+        "opacity.textureMap": "Materials/Decal/am_road_dust_decal.tif",
+        "roughness.useTexture": false,
+        "specularF0.useTexture": false,
+        "uv.center": [
+            0.0,
+            1.0
+        ]
     }
 }

+ 16 - 34
Materials/Decal/brushstoke_01_decal.material

@@ -1,39 +1,21 @@
 {
-    "description": "",
     "materialType": "Materials\\Types\\StandardPBR.materialtype",
-    "parentMaterial": "",
     "materialTypeVersion": 3,
-    "properties": {
-        "baseColor": {
-            "textureMap": "Materials/Decal/brushstoke_01_decal.tif"
-        },
-        "general": {
-            "applySpecularAA": false
-        },
-        "metallic": {
-            "useTexture": false
-        },
-        "normal": {
-            "textureMap": "Materials/Decal/brushstoke_01_decal_nrm.tif"
-        },  
-        "opacity": {
-            "alphaSource": "Split",
-            "doubleSided": true,
-            "factor": 0.6899999976158142,
-            "mode": "Cutout",
-            "textureMap": "Materials/Decal/brushstoke_01_decal.tif"
-        },
-        "roughness": {
-            "useTexture": false
-        },
-        "specularF0": {
-            "useTexture": false
-        },
-        "uv": {
-            "center": [
-                0.0,
-                1.0
-            ]
-        }
+    "propertyValues": {
+        "baseColor.textureMap": "Materials/Decal/brushstoke_01_decal.tif",
+        "general.applySpecularAA": false,
+        "metallic.useTexture": false,
+        "normal.textureMap": "Materials/Decal/brushstoke_01_decal_nrm.tif",
+        "opacity.alphaSource": "Split",
+        "opacity.doubleSided": true,
+        "opacity.factor": 0.6899999976158142,
+        "opacity.mode": "Cutout",
+        "opacity.textureMap": "Materials/Decal/brushstoke_01_decal.tif",
+        "roughness.useTexture": false,
+        "specularF0.useTexture": false,
+        "uv.center": [
+            0.0,
+            1.0
+        ]
     }
 }

+ 15 - 31
Materials/Decal/scorch_01_decal.material

@@ -1,36 +1,20 @@
 {
-    "description": "",
     "materialType": "Materials/Types/StandardPBR.materialtype",
-    "parentMaterial": "",
     "materialTypeVersion": 3,
-    "properties": {
-        "baseColor": {
-            "textureMap": "Materials/Decal/scorch_01_decal.tif"
-        },
-        "metallic": {
-            "useTexture": false
-        },
-        "normal": {
-            "textureMap": "Materials/Decal/scorch_01_decal_nrm.tif"
-        }, 
-        "opacity": {
-            "alphaSource": "Split",
-            "doubleSided": true,
-            "factor": 0.6899999976158142,
-            "mode": "Cutout",
-            "textureMap": "Materials/Decal/scorch_01_decal.tif"
-        },
-        "roughness": {
-            "useTexture": false
-        },
-        "specularF0": {
-            "useTexture": false
-        },
-        "uv": {
-            "center": [
-                0.0,
-                1.0
-            ]
-        }
+    "propertyValues": {
+        "baseColor.textureMap": "Materials/Decal/scorch_01_decal.tif",
+        "metallic.useTexture": false,
+        "normal.textureMap": "Materials/Decal/scorch_01_decal_nrm.tif",
+        "opacity.alphaSource": "Split",
+        "opacity.doubleSided": true,
+        "opacity.factor": 0.6899999976158142,
+        "opacity.mode": "Cutout",
+        "opacity.textureMap": "Materials/Decal/scorch_01_decal.tif",
+        "roughness.useTexture": false,
+        "specularF0.useTexture": false,
+        "uv.center": [
+            0.0,
+            1.0
+        ]
     }
 }

+ 2 - 3
Materials/DefaultPBR.material

@@ -1,6 +1,5 @@
 {
-    "description": "",
     "materialType": "Materials/Types/StandardPBR.materialtype",
-    "parentMaterial": "Materials/Presets/PBR/default_grid.material",
-    "materialTypeVersion": 3
+    "materialTypeVersion": 3,
+    "parentMaterial": "Materials/Presets/PBR/default_grid.material"
 }

+ 3 - 6
Materials/DefaultPBRTransparent.material

@@ -1,11 +1,8 @@
 {
-    "description": "",
     "materialType": "Materials/Types/StandardPBR.materialtype",
-    "parentMaterial": "Materials/Presets/PBR/default_grid.material",
     "materialTypeVersion": 3,
-    "properties": {
-        "opacity": {
-            "mode": "Blended"
-        }
+    "parentMaterial": "Materials/Presets/PBR/default_grid.material",
+    "propertyValues": {
+        "opacity.mode": "Blended"
     }
 }

+ 14 - 19
Materials/DiffuseGIExample/blue.material

@@ -1,24 +1,19 @@
 {
-    "description": "",
     "materialType": "Materials\\Types\\StandardPBR.materialtype",
     "materialTypeVersion": 3,
-    "properties": {
-        "baseColor": {
-            "color": [
-                0.0,
-                0.0,
-                1.0,
-                1.0
-            ],
-            "useTexture": false
-        },
-        "irradiance": {
-            "color": [
-                0.0,
-                0.0,
-                1.0,
-                1.0
-            ]
-        }
+    "propertyValues": {
+        "baseColor.color": [
+            0.0,
+            0.0,
+            1.0,
+            1.0
+        ],
+        "baseColor.useTexture": false,
+        "irradiance.color": [
+            0.0,
+            0.0,
+            1.0,
+            1.0
+        ]
     }
 }

+ 14 - 19
Materials/DiffuseGIExample/green.material

@@ -1,24 +1,19 @@
 {
-    "description": "",
     "materialType": "Materials\\Types\\StandardPBR.materialtype",
     "materialTypeVersion": 3,
-    "properties": {
-        "baseColor": {
-            "color": [
-                0.0,
-                1.0,
-                0.0,
-                1.0
-            ],
-            "useTexture": false
-        },
-        "irradiance": {
-            "color": [
-                0.0,
-                1.0,
-                0.0,
-                1.0
-            ]
-        }
+    "propertyValues": {
+        "baseColor.color": [
+            0.0,
+            1.0,
+            0.0,
+            1.0
+        ],
+        "baseColor.useTexture": false,
+        "irradiance.color": [
+            0.0,
+            1.0,
+            0.0,
+            1.0
+        ]
     }
 }

+ 14 - 19
Materials/DiffuseGIExample/red.material

@@ -1,24 +1,19 @@
 {
-    "description": "",
     "materialType": "Materials\\Types\\StandardPBR.materialtype",
     "materialTypeVersion": 3,
-    "properties": {
-        "baseColor": {
-            "color": [
-                1.0,
-                0.0,
-                0.0,
-                1.0
-            ],
-            "useTexture": false
-        },
-        "irradiance": {
-            "color": [
-                1.0,
-                0.0,
-                0.0,
-                1.0
-            ]
-        }
+    "propertyValues": {
+        "baseColor.color": [
+            1.0,
+            0.0,
+            0.0,
+            1.0
+        ],
+        "baseColor.useTexture": false,
+        "irradiance.color": [
+            1.0,
+            0.0,
+            0.0,
+            1.0
+        ]
     }
 }

+ 14 - 19
Materials/DiffuseGIExample/white.material

@@ -1,24 +1,19 @@
 {
-    "description": "",
     "materialType": "Materials\\Types\\StandardPBR.materialtype",
     "materialTypeVersion": 3,
-    "properties": {
-        "baseColor": {
-            "color": [
-                1.0,
-                1.0,
-                1.0,
-                1.0
-            ],
-            "useTexture": false
-        },
-        "irradiance": {
-            "color": [
-                1.0,
-                1.0,
-                1.0,
-                1.0
-            ]
-        }
+    "propertyValues": {
+        "baseColor.color": [
+            1.0,
+            1.0,
+            1.0,
+            1.0
+        ],
+        "baseColor.useTexture": false,
+        "irradiance.color": [
+            1.0,
+            1.0,
+            1.0,
+            1.0
+        ]
     }
 }

+ 14 - 19
Materials/DiffuseGIExample/yellow.material

@@ -1,24 +1,19 @@
 {
-    "description": "",
     "materialType": "Materials\\Types\\StandardPBR.materialtype",
     "materialTypeVersion": 3,
-    "properties": {
-        "baseColor": {
-            "color": [
-                1.0,
-                1.0,
-                0.0,
-                1.0
-            ],
-            "useTexture": false
-        },
-        "irradiance": {
-            "color": [
-                1.0,
-                1.0,
-                0.0,
-                1.0
-            ]
-        }
+    "propertyValues": {
+        "baseColor.color": [
+            1.0,
+            1.0,
+            0.0,
+            1.0
+        ],
+        "baseColor.useTexture": false,
+        "irradiance.color": [
+            1.0,
+            1.0,
+            0.0,
+            1.0
+        ]
     }
 }

+ 3 - 4
Materials/DynamicMaterialTest/EmissiveMaterial.azsl

@@ -43,7 +43,7 @@ struct VSInput
 
 struct VSOutput
 {
-    float4 m_position : SV_Position;
+    precise float4 m_position : SV_Position;
     float3 m_normal: NORMAL;
     float3 m_tangent : TANGENT; 
     float3 m_bitangent : BITANGENT; 
@@ -82,9 +82,8 @@ ForwardPassOutput MainPS(VSOutput IN)
     const float specularF0Factor = 0.5f;
     surface.SetAlbedoAndSpecularF0(baseColor, specularF0Factor, metallic);
 
-    // Clear Coat, Transmission
+    // Clear Coat
     surface.clearCoat.InitializeToZero();
-    surface.transmission.InitializeToZero();
 
     // ------- LightingData -------
 
@@ -124,7 +123,7 @@ ForwardPassOutput MainPS(VSOutput IN)
     ApplyIBL(surface, lightingData);
 
     // Finalize Lighting
-    lightingData.FinalizeLighting(surface.transmission.tint);
+    lightingData.FinalizeLighting();
 
     PbrLightingOutput lightingOutput = GetPbrLightingOutput(surface, lightingData, alpha);
 

+ 0 - 4
Materials/DynamicMaterialTest/EmissiveMaterial.shader

@@ -38,9 +38,5 @@
         ] 
     },
 
-    "CompilerHints" : { 
-        "DisableOptimizations" : false
-    },
-
     "DrawList" : "forward"
 }

+ 9 - 13
Materials/DynamicMaterialTest/EmissiveWithCppFunctors.material

@@ -1,18 +1,14 @@
 {
-    "description": "",
     "materialType": "Materials/DynamicMaterialTest/EmissiveWithCppFunctors.materialtype",
-    "parentMaterial": "",
     "materialTypeVersion": 3,
-    "properties": {
-        "emissive": {
-            "color": [
-                0.0313725508749485,
-                0.4941176474094391,
-                1.0,
-                1.0
-            ],
-            "intensity": 3.0,
-            "textureMap": "Textures/Default/default_basecolor.tif"
-        }
+    "propertyValues": {
+        "emissive.color": [
+            0.0313725508749485,
+            0.4941176474094391,
+            1.0,
+            1.0
+        ],
+        "emissive.intensity": 3.0,
+        "emissive.textureMap": "Textures/Default/default_basecolor.tif"
     }
 }

+ 9 - 13
Materials/DynamicMaterialTest/EmissiveWithLuaFunctors.material

@@ -1,18 +1,14 @@
 {
-    "description": "",
     "materialType": "Materials/DynamicMaterialTest/EmissiveWithLuaFunctors.materialtype",
-    "parentMaterial": "",
     "materialTypeVersion": 3,
-    "properties": {
-        "emissive": {
-            "color": [
-                0.0313725508749485,
-                0.4941176474094391,
-                1.0,
-                1.0
-            ],
-            "intensity": 3.0,
-            "textureMap": "Textures/Default/default_basecolor.tif"
-        }
+    "propertyValues": {
+        "emissive.color": [
+            0.0313725508749485,
+            0.4941176474094391,
+            1.0,
+            1.0
+        ],
+        "emissive.intensity": 3.0,
+        "emissive.textureMap": "Textures/Default/default_basecolor.tif"
     }
 }

+ 17 - 25
Materials/HotReloadTest/TestData/VariantSelection_FullyBaked.material

@@ -1,30 +1,22 @@
 {
-    "description": "",
     "materialType": "Materials/Types/StandardPBR.materialtype",
-    "parentMaterial": "",
     "materialTypeVersion": 3,
-    "properties": {
-        "baseColor": {
-            "color": [
-                0.0,
-                0.0,
-                0.0,
-                1.0
-            ]
-        },
-        "emissive": {
-            "color": [
-                1.0,
-                0.5789272785186768,
-                0.19980163872241975,
-                1.0
-            ],
-            "enable": true,
-            "intensity": 3.0,
-            "textureMap": "Materials/HotReloadTest/TestData/VariantSelection_FullyBaked.png"
-        },
-        "opacity": {
-            "doubleSided": true
-        }
+    "propertyValues": {
+        "baseColor.color": [
+            0.0,
+            0.0,
+            0.0,
+            1.0
+        ],
+        "emissive.color": [
+            1.0,
+            0.5789272785186768,
+            0.19980163872241974,
+            1.0
+        ],
+        "emissive.enable": true,
+        "emissive.intensity": 3.0,
+        "emissive.textureMap": "Materials/HotReloadTest/TestData/VariantSelection_FullyBaked.png",
+        "opacity.doubleSided": true
     }
 }

+ 16 - 24
Materials/HotReloadTest/TestData/VariantSelection_Root.material

@@ -1,29 +1,21 @@
 {
-    "description": "",
     "materialType": "Materials/Types/StandardPBR.materialtype",
-    "parentMaterial": "",
     "materialTypeVersion": 3,
-    "properties": {
-        "baseColor": {
-            "color": [
-                0.0,
-                0.0,
-                0.0,
-                1.0
-            ]
-        },
-        "emissive": {
-            "color": [
-                0.19607843458652497,
-                0.3607843220233917,
-                1.0,
-                1.0
-            ],
-            "enable": true,
-            "textureMap": "Materials/HotReloadTest/TestData/VariantSelection_Root.png"
-        },
-        "opacity": {
-            "doubleSided": true
-        }
+    "propertyValues": {
+        "baseColor.color": [
+            0.0,
+            0.0,
+            0.0,
+            1.0
+        ],
+        "emissive.color": [
+            0.19607843458652496,
+            0.3607843220233917,
+            1.0,
+            1.0
+        ],
+        "emissive.enable": true,
+        "emissive.textureMap": "Materials/HotReloadTest/TestData/VariantSelection_Root.png",
+        "opacity.doubleSided": true
     }
 }

+ 9 - 13
Materials/MinimalPBR/MinimalPBR_BlueMetal.material

@@ -1,18 +1,14 @@
 {
-    "description": "",
     "materialType": "TestData/Materials/Types/MinimalPBR.materialtype",
-    "parentMaterial": "",
     "materialTypeVersion": 3,
-    "properties": {
-        "settings": {
-            "color": [
-                0.04896620288491249,
-                0.13278400897979737,
-                0.9096513390541077,
-                1.0
-            ],
-            "metallic": 1.0,
-            "roughness": 0.13131310045719148
-        }
+    "propertyValues": {
+        "settings.color": [
+            0.04896620288491249,
+            0.13278400897979736,
+            0.9096513390541077,
+            1.0
+        ],
+        "settings.metallic": 1.0,
+        "settings.roughness": 0.13131310045719147
     }
 }

+ 0 - 2
Materials/MinimalPBR/MinimalPBR_Default.material

@@ -1,6 +1,4 @@
 {
-    "description": "",
     "materialType": "TestData/Materials/Types/MinimalPBR.materialtype",
-    "parentMaterial": "",
     "materialTypeVersion": 3
 }

+ 8 - 12
Materials/MinimalPBR/MinimalPBR_RedDielectric.material

@@ -1,17 +1,13 @@
 {
-    "description": "",
     "materialType": "TestData/Materials/Types/MinimalPBR.materialtype",
-    "parentMaterial": "",
     "materialTypeVersion": 3,
-    "properties": {
-        "settings": {
-            "color": [
-                1.0,
-                0.0,
-                0.0,
-                1.0
-            ],
-            "roughness": 0.2727273106575012
-        }
+    "propertyValues": {
+        "settings.color": [
+            1.0,
+            0.0,
+            0.0,
+            1.0
+        ],
+        "settings.roughness": 0.2727273106575012
     }
 }

+ 15 - 26
Materials/SSRExample/Cube.material

@@ -1,30 +1,19 @@
 {
-    "description": "",
     "materialType": "Materials\\Types\\StandardPBR.materialtype",
     "materialTypeVersion": 3,
-    "properties": {
-        "baseColor": {
-            "color": [
-                1.0,
-                0.0,
-                0.0,
-                1.0
-            ],
-            "useTexture": false
-        },
-        "specularF0": {
-            "factor": 0.0,
-            "useTexture": false
-        },
-        "metallic": {
-            "factor": 0.0,
-            "useTexture": false
-        },        
-        "normal": {
-            "textureMap": "Textures/Default/default_normal.tif"
-        },
-        "roughness": {
-            "textureMap": "Textures/Default/default_roughness.tif"
-        }
+    "propertyValues": {
+        "baseColor.color": [
+            1.0,
+            0.0,
+            0.0,
+            1.0
+        ],
+        "baseColor.useTexture": false,
+        "metallic.factor": 0.0,
+        "metallic.useTexture": false,
+        "normal.textureMap": "Textures/Default/default_normal.tif",
+        "roughness.textureMap": "Textures/Default/default_roughness.tif",
+        "specularF0.factor": 0.0,
+        "specularF0.useTexture": false
     }
-}
+}

Some files were not shown because too many files changed in this diff