Browse Source

merging latest dev

Signed-off-by: antonmic <[email protected]>
antonmic 3 years ago
parent
commit
080fce8f5f

+ 1 - 1
Gem/Code/Source/Automation/ScriptManager.cpp

@@ -1373,7 +1373,7 @@ namespace AtomSampleViewer
     {
         auto operation = [command]()
         {
-            AzFramework::ConsoleRequestBus::Broadcast(&AzFramework::ConsoleRequests::ExecuteConsoleCommand, command.c_str());
+            AZ::Interface<AZ::IConsole>::Get()->PerformCommand(command.c_str());
         };
 
         s_instance->m_scriptOperations.push(AZStd::move(operation));

+ 56 - 18
Gem/Code/Source/Automation/ScriptReporter.cpp

@@ -23,6 +23,11 @@ namespace AtomSampleViewer
     {
         "All Results", "Warnings & Errors", "Errors Only",
     };
+    
+    static const char* SortOptions[] =
+    {
+        "Sort by Script", "Sort by Official Baseline Diff Score", "Sort by Local Baseline Diff Score",
+    };
 
     namespace ScreenshotPaths
     {
@@ -148,7 +153,8 @@ namespace AtomSampleViewer
     void ScriptReporter::Reset()
     {
         m_scriptReports.clear();
-        m_descendingThresholdReports.clear();
+        m_reportsSortedByOfficialBaslineScore.clear();
+        m_reportsSortedByLocaBaslineScore.clear();
         m_currentScriptIndexStack.clear();
         m_invalidationMessage.clear();
         m_uniqueTimestamp = GenerateTimestamp();
@@ -422,7 +428,10 @@ namespace AtomSampleViewer
             ImGui::Combo("Display", &displayOption, DiplayOptions, AZ_ARRAY_SIZE(DiplayOptions));
             m_displayOption = (DisplayOption)displayOption;
 
-            ImGui::Checkbox("Show Script Reports Sorted By Threshold", &m_showReportsSortedByThreshold);
+            int sortOption = m_currentSortOption;
+            ImGui::Combo("Sort Results", &sortOption, SortOptions, AZ_ARRAY_SIZE(SortOptions));
+            m_currentSortOption = (SortOption)sortOption;
+
             ImGui::Checkbox("Force Show 'Update' Buttons", &m_forceShowUpdateButtons);
             ImGui::Checkbox("Force Show 'Export Png Diff' Buttons", &m_forceShowExportPngDiffButtons);
 
@@ -431,7 +440,7 @@ namespace AtomSampleViewer
 
             ImGui::Separator();
 
-            if (!m_showReportsSortedByThreshold)
+            if (m_currentSortOption == SortOption::Unsorted)
             {
                 for (ScriptReport& scriptReport : m_scriptReports)
                 {
@@ -540,24 +549,49 @@ namespace AtomSampleViewer
             }
             else
             {
-                for (const auto& [threshold, reportIndex] : m_descendingThresholdReports)
+                const SortedReportIndexMap* sortedReportMap = nullptr;
+                if (m_currentSortOption == SortOption::OfficialBaselineDiffScore)
+                {
+                    sortedReportMap = &m_reportsSortedByOfficialBaslineScore;
+                }
+                else if (m_currentSortOption == SortOption::LocalBaselineDiffScore)
                 {
-                    ScriptReport& scriptReport = m_scriptReports[reportIndex.first];
-                    ScreenshotTestInfo& screenshotResult = scriptReport.m_screenshotTests[reportIndex.second];
+                    sortedReportMap = &m_reportsSortedByLocaBaslineScore;
+                }
 
-                    const bool screenshotPassed = screenshotResult.m_officialComparisonResult.m_resultCode == ImageComparisonResult::ResultCode::Pass;
+                AZ_Assert(sortedReportMap, "Unhandled m_currentSortOption");
 
-                    AZStd::string fileName;
-                    AzFramework::StringFunc::Path::GetFullFileName(screenshotResult.m_screenshotFilePath.c_str(), fileName);
+                if (sortedReportMap)
+                {
+                    for (const auto& [threshold, reportIndex] : *sortedReportMap)
+                    {
+                        ScriptReport& scriptReport = m_scriptReports[reportIndex.first];
+                        ScreenshotTestInfo& screenshotResult = scriptReport.m_screenshotTests[reportIndex.second];
 
-                    AZStd::string header = AZStd::string::format("%s %s %s '%s' %f",
-                        screenshotPassed ? "PASSED" : "FAILED",
-                        scriptReport.m_scriptAssetPath.c_str(),
-                        fileName.c_str(),
-                        screenshotResult.m_toleranceLevel.m_name.c_str(),
-                        screenshotResult.m_officialComparisonResult.m_finalDiffScore);
+                        float diffScore = 0.0f;
+                        if (m_currentSortOption == SortOption::OfficialBaselineDiffScore)
+                        {
+                            diffScore = screenshotResult.m_officialComparisonResult.m_standardDiffScore;
+                        }
+                        else if (m_currentSortOption == SortOption::LocalBaselineDiffScore)
+                        {
+                            diffScore = screenshotResult.m_localComparisonResult.m_standardDiffScore;
+                        }
+
+                        const bool screenshotPassed = screenshotResult.m_officialComparisonResult.m_resultCode == ImageComparisonResult::ResultCode::Pass;
+
+                        AZStd::string fileName;
+                        AzFramework::StringFunc::Path::GetFullFileName(screenshotResult.m_screenshotFilePath.c_str(), fileName);
 
-                    ShowScreenshotTestInfoTreeNode(header, scriptReport, screenshotResult);
+                        AZStd::string header = AZStd::string::format("%f %s %s %s '%s'",
+                            diffScore,
+                            screenshotPassed ? "PASSED" : "FAILED",
+                            scriptReport.m_scriptAssetPath.c_str(),
+                            fileName.c_str(),
+                            screenshotResult.m_toleranceLevel.m_name.c_str());
+
+                        ShowScreenshotTestInfoTreeNode(header, scriptReport, screenshotResult);
+                    }
                 }
             }
             ResetTextHighlight();
@@ -822,8 +856,12 @@ namespace AtomSampleViewer
             const AZStd::vector<ScriptReporter::ScreenshotTestInfo>& screenshotTestInfos = m_scriptReports[i].m_screenshotTests;
             for (size_t j = 0; j < screenshotTestInfos.size(); ++j)
             {
-                m_descendingThresholdReports.insert(AZStd::pair<float, ReportIndex>(
-                    screenshotTestInfos[j].m_officialComparisonResult.m_finalDiffScore,
+                m_reportsSortedByOfficialBaslineScore.insert(AZStd::pair<float, ReportIndex>(
+                    screenshotTestInfos[j].m_officialComparisonResult.m_standardDiffScore,
+                    ReportIndex{ i, j }));
+
+                m_reportsSortedByLocaBaslineScore.insert(AZStd::pair<float, ReportIndex>(
+                    screenshotTestInfos[j].m_localComparisonResult.m_standardDiffScore,
                     ReportIndex{ i, j }));
             }
         }

+ 14 - 2
Gem/Code/Source/Automation/ScriptReporter.h

@@ -224,6 +224,15 @@ namespace AtomSampleViewer
             WarningsAndErrors,
             ErrorsOnly
         };
+        
+        // Controls how screenshot reports are sorted
+        // Must match static const char* DiplayOptions in .cpp file
+        enum SortOption : int
+        {
+            Unsorted,
+            OfficialBaselineDiffScore,
+            LocalBaselineDiffScore
+        };
 
         static void ReportScriptError(const AZStd::string& message);
         static void ReportScriptWarning(const AZStd::string& message);
@@ -289,8 +298,11 @@ namespace AtomSampleViewer
             void UpdateColorSettings();
         };
 
-        AZStd::multimap<float, ReportIndex, AZStd::greater<float>> m_descendingThresholdReports;
-        bool m_showReportsSortedByThreshold = true;
+        using SortedReportIndexMap = AZStd::multimap<float, ReportIndex, AZStd::greater<float>>;
+
+        SortedReportIndexMap m_reportsSortedByOfficialBaslineScore;
+        SortedReportIndexMap m_reportsSortedByLocaBaslineScore;
+        SortOption m_currentSortOption = SortOption::OfficialBaselineDiffScore;
 
         ImGuiMessageBox m_messageBox;
 

+ 4 - 20
Gem/Code/Source/CommonSampleComponentBase.cpp

@@ -77,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;
@@ -114,7 +109,6 @@ namespace AtomSampleViewer
         }
 
         AZ::TransformNotificationBus::MultiHandler::BusConnect(m_cameraEntityId);
-        AZ::EntityBus::MultiHandler::BusConnect(m_postProcessEntity->GetId());
     }
 
     void CommonSampleComponentBase::ShutdownLightingPresets()
@@ -129,12 +123,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);
 
@@ -250,7 +242,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);
@@ -283,14 +275,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;

+ 0 - 6
Gem/Code/Source/CommonSampleComponentBase.h

@@ -75,8 +75,6 @@ namespace AtomSampleViewer
 
         AZ::Render::MeshFeatureProcessorInterface* GetMeshFeatureProcessor() const;
 
-        void OnLightingPresetEntityShutdown(const AZ::EntityId& entityId);
-
         // Preload assets 
         void PreloadAssets(const AZStd::vector<AZ::AssetCollectionAsyncLoader::AssetToLoadInfo>& assetList);
 
@@ -116,9 +114,6 @@ namespace AtomSampleViewer
         //! 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;
 
@@ -126,7 +121,6 @@ namespace AtomSampleViewer
         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

+ 25 - 12
Gem/Code/Source/MaterialHotReloadTestComponent.cpp

@@ -334,19 +334,32 @@ namespace AtomSampleViewer
 
         if (m_material)
         {
-            const ShaderVariantId variantId = m_material->GetShaderCollection()[0].GetShaderVariantId();
-            auto searchResult = m_material->GetShaderCollection()[0].GetShaderAsset()->FindVariantStableId(variantId);
-            if (searchResult.IsFullyBaked())
+            const Render::MeshDrawPacketLods& drawPackets = GetMeshFeatureProcessor()->GetDrawPackets(m_meshHandle);
+            if (!drawPackets.empty())
             {
-                shaderVariantStatus = ShaderVariantStatus::FullyBaked;
-            }
-            else if (searchResult.IsRoot())
-            {
-                shaderVariantStatus = ShaderVariantStatus::Root;
-            }
-            else
-            {
-                shaderVariantStatus = ShaderVariantStatus::PartiallyBaked;
+                AZ_Assert(drawPackets.size() == 1, "Expected exactly 1 LOD");
+                AZ_Assert(drawPackets[0].size() == 1, "Expected exactly 1 mesh");
+                AZ_Assert(drawPackets[0][0].GetMaterial() == m_material, "MeshDrawPacket didn't have the expected material.");
+
+                const RPI::MeshDrawPacket::ShaderList& activeShaderList = drawPackets[0][0].GetActiveShaderList();
+
+                AZ_Assert(activeShaderList.size() == 1, "Expected exactly 1 active shader");
+
+                const ShaderVariantId activeVariantId = activeShaderList[0].m_activeShaderVariantId;
+                ShaderOptionGroup activeShaderOptions{activeShaderList[0].m_shader->GetAsset()->GetShaderOptionGroupLayout(), activeVariantId};
+
+                if (activeShaderOptions.IsFullySpecified())
+                {
+                    shaderVariantStatus = ShaderVariantStatus::FullyBaked;
+                }
+                else if (activeVariantId == activeShaderList[0].m_shader->GetVariant(RootShaderVariantStableId).GetShaderVariantId())
+                {
+                    shaderVariantStatus = ShaderVariantStatus::Root;
+                }
+                else
+                {
+                    shaderVariantStatus = ShaderVariantStatus::PartiallyBaked;
+                }
             }
         }
 

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

@@ -405,7 +405,6 @@ namespace AtomSampleViewer
 
     void MeshExampleComponent::OnEntityDestruction(const AZ::EntityId& entityId)
     {
-        OnLightingPresetEntityShutdown(entityId);
         AZ::EntityBus::MultiHandler::BusDisconnect(entityId);
     }
 

+ 34 - 5
Gem/Code/Source/SampleComponentManager.cpp

@@ -111,6 +111,7 @@
 #include <AzCore/Component/Entity.h>
 #include <AzCore/Debug/Profiler.h>
 #include <AzCore/Debug/ProfilerBus.h>
+#include <AzCore/IO/IStreamerProfiler.h>
 #include <AzCore/Serialization/SerializeContext.h>
 #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
 #include <AzCore/std/smart_ptr/make_shared.h>
@@ -142,10 +143,11 @@ namespace AtomSampleViewer
 {
     namespace
     {
-        const char* PassTreeToolName = "PassTree";
-        const char* CpuProfilerToolName = "CPU Profiler";
-        const char* GpuProfilerToolName = "GPU Profiler";
-        const char* TransientAttachmentProfilerToolName = "Transient Attachment Profiler";
+        constexpr const char* PassTreeToolName = "PassTree";
+        constexpr const char* CpuProfilerToolName = "CPU Profiler";
+        constexpr const char* GpuProfilerToolName = "GPU Profiler";
+        constexpr const char* FileIoProfilerToolName = "File IO Profiler";
+        constexpr const char* TransientAttachmentProfilerToolName = "Transient Attachment Profiler";
     }
 
     bool IsValidNumMSAASamples(int numSamples)
@@ -830,6 +832,11 @@ namespace AtomSampleViewer
             ShowGpuProfilerWindow();
         }
 
+        if (m_showFileIoProfiler)
+        {
+            ShowFileIoProfilerWindow();
+        }
+
         if (m_showTransientAttachmentProfiler)
         {
             ShowTransientAttachmentProfilerWindow();
@@ -861,7 +868,6 @@ namespace AtomSampleViewer
                 if (ImGui::MenuItem("Exit", "Ctrl-Q"))
                 {
                     RequestExit();
-                    return;
                 }
                 if (ImGui::MenuItem("Capture Frame...", "Ctrl-P"))
                 {
@@ -1007,6 +1013,16 @@ namespace AtomSampleViewer
                     Utils::ReportScriptableAction("ShowTool('%s', %s)", CpuProfilerToolName, m_showCpuProfiler ? "true" : "false");
                 }
 
+                if (AZ::IO::StreamerProfiler::Get() != nullptr)
+                {
+                    if (ImGui::MenuItem(FileIoProfilerToolName))
+                    {
+                        m_showFileIoProfiler = !m_showFileIoProfiler;
+                        Utils::ReportScriptableAction(
+                            "ShowTool('%s', %s)", FileIoProfilerToolName, m_showFileIoProfiler ? "true" : "false");
+                    }
+                }
+
                 if (ImGui::MenuItem(GpuProfilerToolName))
                 {
                     m_showGpuProfiler = !m_showGpuProfiler;
@@ -1101,6 +1117,14 @@ namespace AtomSampleViewer
         }
     }
 
+    void SampleComponentManager::ShowFileIoProfilerWindow()
+    {
+        if (auto profilerImGui = AZ::IO::StreamerProfiler::Get(); profilerImGui)
+        {
+            profilerImGui->DrawStatistics(m_showFileIoProfiler);
+        }
+    }
+
     void SampleComponentManager::ShowGpuProfilerWindow()
     {
         m_imguiGpuProfiler.Draw(m_showGpuProfiler, AZ::RPI::PassSystemInterface::Get()->GetRootPass());
@@ -1414,6 +1438,11 @@ namespace AtomSampleViewer
             m_showCpuProfiler = enable;
             return true;
         }
+        else if (toolName == FileIoProfilerToolName)
+        {
+            m_showFileIoProfiler = enable;
+            return true;
+        }
         else if (toolName == GpuProfilerToolName)
         {
             m_showGpuProfiler = enable;

+ 2 - 0
Gem/Code/Source/SampleComponentManager.h

@@ -137,6 +137,7 @@ namespace AtomSampleViewer
         void ShowFrameGraphVisualizerWindow();
         void ShowCpuProfilerWindow();
         void ShowGpuProfilerWindow();
+        void ShowFileIoProfilerWindow();
         void ShowShaderMetricsWindow();
         void ShowTransientAttachmentProfilerWindow();
 
@@ -210,6 +211,7 @@ namespace AtomSampleViewer
         bool m_showCullingDebugWindow = false;
         bool m_showCpuProfiler = false;
         bool m_showGpuProfiler = false;
+        bool m_showFileIoProfiler = false;
         bool m_showTransientAttachmentProfiler = false;
         bool m_showShaderMetrics = false;
 

+ 5 - 2
Gem/Code/Source/SkinnedMeshContainer.cpp

@@ -27,7 +27,7 @@
 
 namespace
 {
-    static const char* const SkinnedMeshMaterial = "shaders/debugvertexnormals.azmaterial";
+    static const char* const SkinnedMeshMaterial = "materials/special/debugvertexstreams.azmaterial";
 }
 
 namespace AtomSampleViewer
@@ -225,7 +225,10 @@ namespace AtomSampleViewer
         // Release the per-instance data
         RenderData& renderData = m_skinnedMeshInstances[i];
         m_skinnedMeshFeatureProcessor->ReleaseSkinnedMesh(renderData.m_skinnedMeshHandle);
-        m_meshFeatureProcessor->ReleaseMesh(*renderData.m_meshHandle);
+        if (renderData.m_meshHandle)
+        {
+            m_meshFeatureProcessor->ReleaseMesh(*renderData.m_meshHandle);
+        }
 
         renderData.m_skinnedMeshInstance.reset();
         renderData.m_boneTransformBuffer.reset();

+ 6 - 1
Gem/Code/Source/Utils/ImGuiMaterialDetails.cpp

@@ -47,7 +47,12 @@ namespace AtomSampleViewer
                                 {
                                     AZ::RPI::ShaderVariantId requestedVariantId = shaderItem.GetShaderVariantId();
                                     AZ::Data::Asset<AZ::RPI::ShaderVariantAsset> selectedVariantAsset =
-                                        shaderItem.GetShaderAsset()->GetVariant(requestedVariantId);
+                                        shaderItem.GetShaderAsset()->GetVariantAsset(requestedVariantId);
+
+                                    if (!selectedVariantAsset)
+                                    {
+                                        selectedVariantAsset = shaderItem.GetShaderAsset()->GetRootVariantAsset();
+                                    }
 
                                     if (selectedVariantAsset)
                                     {

+ 1 - 0
Gem/Code/enabled_gems.cmake

@@ -22,4 +22,5 @@ set(ENABLED_GEMS
     Sponza
     MaterialEditor
     UiBasics
+    StreamerProfiler
 )

+ 3 - 0
Scripts/ExpectedScreenshots/SkinnedMesh/screenshot_skinnedmesh.png

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

+ 12 - 11
Scripts/MaterialHotReloadTest.bv.lua

@@ -19,6 +19,10 @@ ResizeViewport(500, 500)
 
 SelectImageComparisonToleranceLevel("Level E")
 
+-- The default ShaderVariantAsyncLoader service loop delay is too long and would require this
+-- test script to use too long in IdleSeconds()
+ExecuteConsoleCommand("r_ShaderVariantAsyncLoader_ServiceLoopDelayOverride_ms 10")
+
 function SetColorRed()
     AssetTracking_Start()
     AssetTracking_ExpectAsset(g_assetFolder .. "HotReloadTest.material")
@@ -33,7 +37,7 @@ function SetBlendingOn()
     AssetTracking_ExpectAsset(g_assetFolder .. "HotReloadTest.materialtype")
     AssetTracking_ExpectAsset(g_assetFolder .. "HotReloadTest.shader")
     AssetTracking_IdleUntilExpectedAssetsFinish(10)
-    IdleSeconds(1) -- Idle for a bit to give time for the assets to reload
+    IdleSeconds(0.25) -- Idle for a bit to give time for the assets to reload
 end
 
 function SetBlendingOff()
@@ -42,7 +46,7 @@ function SetBlendingOff()
     AssetTracking_ExpectAsset(g_assetFolder .. "HotReloadTest.materialtype")
     AssetTracking_ExpectAsset(g_assetFolder .. "HotReloadTest.shader")
     AssetTracking_IdleUntilExpectedAssetsFinish(10)
-    IdleSeconds(1) -- Idle for a bit to give time for the assets to reload
+    IdleSeconds(0.25) -- Idle for a bit to give time for the assets to reload
 end
 
 CaptureScreenshot(g_screenshotOutputFolder .. '/01_Default.png')
@@ -76,7 +80,7 @@ SetImguiValue('Vertical Pattern', true)
 AssetTracking_ExpectAsset(g_assetFolder .. "HotReloadTest.materialtype")
 AssetTracking_ExpectAsset(g_assetFolder .. "HotReloadTest.shader")
 AssetTracking_IdleUntilExpectedAssetsFinish(10)
-IdleSeconds(1) -- Idle for a bit to give time for the assets to reload
+IdleSeconds(0.25) -- Idle for a bit to give time for the assets to reload
 CaptureScreenshot(g_screenshotOutputFolder .. '/06_VerticalPattern.png')
 
 SetBlendingOn()
@@ -87,7 +91,7 @@ AssetTracking_Start()
 SetImguiValue('ShaderVariantList/All', true)
 AssetTracking_ExpectAsset(g_assetFolder .. "HotReloadTest.shadervariantlist", 3) -- Waiting for 3 products, the list asset and two variant assets
 AssetTracking_IdleUntilExpectedAssetsFinish(10)
-IdleSeconds(1) -- Idle for a bit to give time for the assets to reload
+IdleSeconds(0.25) -- Idle for a bit to give time for the assets to reload
 CaptureScreenshot(g_screenshotOutputFolder .. '/08_Variants_All.png')
 
 -- This will switch to showing the "Shader Variant: Root" message
@@ -125,22 +129,17 @@ AssetTracking_Start()
 SetImguiValue('ShaderVariantList/All', true)
 AssetTracking_ExpectAsset(g_assetFolder .. "HotReloadTest.shadervariantlist", 3) -- Waiting for 3 products, the list asset and two variant assets
 AssetTracking_IdleUntilExpectedAssetsFinish(10)
-IdleSeconds(1) -- Idle for a bit to give time for the assets to reload
+IdleSeconds(0.25) -- Idle for a bit to give time for the assets to reload
 CaptureScreenshot(g_screenshotOutputFolder .. '/13_Variants_All.png')
 -- Now that the material is using a fully-baked variant, modify the .azsl file to force *everything* to rebuild. In the failure case, the runtime
 -- holds onto the old fully-baked variant even though a new root variant is available. In the correct case, the root variant should take priority
 -- over the fully-baked variant because it is more recent.
 AssetTracking_Start()
 SetImguiValue('Horizontal Pattern', true)
--- Note that here we explicitly do NOT wait for HotReloadTest.shadervariantlist even though the ShaderVariantAssets will be rebuild here too; 
--- part of this test is to ensure the updated shader code is used as soon as the root variant is available.
 AssetTracking_ExpectAsset(g_assetFolder .. "HotReloadTest.materialtype")
 AssetTracking_ExpectAsset(g_assetFolder .. "HotReloadTest.shader")
+AssetTracking_ExpectAsset(g_assetFolder .. "HotReloadTest.shadervariantlist", 3) -- Waiting for 3 products, the list asset and two variant assets
 AssetTracking_IdleUntilExpectedAssetsFinish(10)
--- We want this idle to be short enough that the ShaderVariantAssets don't have time to finish building before we take the screenshot, as part
--- of the validation that the root variant gets applied asap. But it also needs to be long enough to account for some delay between the
--- AssetTracking utility and the asset system triggering the reload. We should avoid increasing this if possible (but maybe we'll have no 
--- choice ... we'll see)
 IdleSeconds(0.25) -- Idle for a bit to give time for the assets to reload
 CaptureScreenshot(g_screenshotOutputFolder .. '/14_HorizontalPattern.png')
 
@@ -150,3 +149,5 @@ SetBlendingOff()
 SetColorRed()
 CaptureScreenshot(g_screenshotOutputFolder .. '/15_Red_AfterShaderReload.png')
 
+-- Clear the service loop override back to the default delay
+ExecuteConsoleCommand("r_ShaderVariantAsyncLoader_ServiceLoopDelayOverride_ms 0")

+ 32 - 0
Scripts/SkinnedMesh.bv.lua

@@ -0,0 +1,32 @@
+----------------------------------------------------------------------------------------------------
+--
+-- 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
+--
+--
+--
+----------------------------------------------------------------------------------------------------
+
+g_screenshotOutputFolder = ResolvePath('@user@/Scripts/Screenshots/SkinnedMesh/')
+Print('Saving screenshots to ' .. NormalizePath(g_screenshotOutputFolder))
+
+OpenSample('Features/SkinnedMesh')
+ResizeViewport(1600, 900)
+SelectImageComparisonToleranceLevel("Level B")
+
+SetImguiValue('Sub-mesh count', 3)
+SetImguiValue('Bones Per-Mesh', 78)
+SetImguiValue('Vertices Per-Segment', 693)
+SetImguiValue('Segments Per-Mesh', 65.000000)
+SetImguiValue('Use Fixed Animation Time', true)
+SetImguiValue('Fixed Animation Time', 4.751000)
+SetImguiValue('Draw bones', false)
+
+NoClipCameraController_SetPosition(Vector3(-0.125466, -2.129441, 1.728536))
+NoClipCameraController_SetHeading(DegToRad(-8.116900))
+NoClipCameraController_SetPitch(DegToRad(-31.035244))
+CaptureScreenshot(g_screenshotOutputFolder .. '/screenshot_skinnedmesh.png')
+
+OpenSample(nil)

+ 1 - 0
Scripts/_FullTestSuite_.bv.lua

@@ -65,6 +65,7 @@ tests= {
     RunScriptWrapper('scripts/multiscene.bv.luac'),
     RunScriptWrapper('scripts/shadowtest.bv.luac'),
     RunScriptWrapper('scripts/shadowedsponzatest.bv.luac'),
+    RunScriptWrapper('scripts/skinnedmesh.bv.luac'),
     RunScriptWrapper('scripts/RenderTargetTexture.bv.luac'),
     RunScriptWrapper('scripts/PassTree.bv.luac'),
     -- Seems to cause GPU Device Lost, requires further investigation.

+ 1 - 1
Scripts/build/Jenkins/Jenkinsfile

@@ -20,7 +20,7 @@ ENGINE_ORGANIZATION_NAME = 'o3de'
 ENGINE_BRANCH_DEFAULT = "${env.BRANCH_DEFAULT}" ?: "${env.BRANCH_NAME}"
 
 // Branches with build snapshots
-BUILD_SNAPSHOTS = ['development', 'stabilization/2110']
+BUILD_SNAPSHOTS = ['development', 'stabilization/2205']
 
 // Build snapshots with empty snapshot (for use with 'SNAPSHOT' pipeline parameter)
 BUILD_SNAPSHOTS_WITH_EMPTY = BUILD_SNAPSHOTS + ''

+ 1 - 1
Standalone/PythonTests/Automated/test_AtomSampleViewer_main_suite.py

@@ -52,7 +52,7 @@ class TestAutomationMainSuite:
                                 "Trace::Assert",
                                 "Traceback (most recent call last):"]
             atomsampleviewer_log_monitor.monitor_log_for_lines(
-                unexpected_lines=unexpected_lines, halt_on_unexpected=True, timeout=240)
+                unexpected_lines=unexpected_lines, halt_on_unexpected=True, timeout=400)
         except ly_test_tools.log.log_monitor.LogMonitorException as e:
             expected_screenshots_path = os.path.join(
                 workspace.paths.project(), "Scripts", "ExpectedScreenshots")