ShaderReloadTestComponent.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzCore/IO/Path/Path.h>
  9. #include <AzCore/Utils/Utils.h>
  10. #include <AzFramework/IO/LocalFileIO.h>
  11. #include <Atom/RPI.Public/View.h>
  12. #include <Atom/RPI.Public/RPISystemInterface.h>
  13. #include <Atom/RPI.Reflect/Asset/AssetUtils.h>
  14. #include <SampleComponentManager.h>
  15. #include <SampleComponentConfig.h>
  16. #include <Automation/AssetStatusTracker.h>
  17. #include "ShaderReloadTestComponent.h"
  18. namespace AtomSampleViewer
  19. {
  20. void ShaderReloadTestComponent::Reflect(AZ::ReflectContext* context)
  21. {
  22. if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  23. {
  24. serializeContext->Class < ShaderReloadTestComponent, AZ::Component>()
  25. ->Version(0)
  26. ;
  27. }
  28. }
  29. ShaderReloadTestComponent::ShaderReloadTestComponent()
  30. {
  31. }
  32. void ShaderReloadTestComponent::InitTestDataFolders()
  33. {
  34. m_relativeTestDataFolder = "Shaders/ShaderReloadTest/TestData";
  35. m_relativeTempSourceFolder = "Shaders/ShaderReloadTest/Temp";
  36. auto io = AZ::IO::LocalFileIO::GetInstance();
  37. auto projectPath = AZ::Utils::GetProjectPath();
  38. AzFramework::StringFunc::Path::Join(projectPath.c_str(), m_relativeTestDataFolder.c_str(), m_absoluteTestDataFolder);
  39. if (!io->Exists(m_absoluteTestDataFolder.c_str()))
  40. {
  41. AZ_Error("ShaderReloadTestComponent", false, "Could not find source folder '%s'. This sample can only be used on dev platforms.", m_absoluteTestDataFolder.c_str());
  42. m_absoluteTestDataFolder.clear();
  43. return;
  44. }
  45. AzFramework::StringFunc::Path::Join(projectPath.c_str(), m_relativeTempSourceFolder.c_str(), m_absoluteTempSourceFolder);
  46. if (!io->CreatePath(m_absoluteTempSourceFolder.c_str()))
  47. {
  48. AZ_Error("ShaderReloadTestComponent", false, "Could not create temp folder '%s'.", m_absoluteTempSourceFolder.c_str());
  49. m_absoluteTempSourceFolder.clear();
  50. }
  51. }
  52. void ShaderReloadTestComponent::CopyTestFile(const char * originalName, const char * newName, bool replaceIfExists)
  53. {
  54. auto io = AZ::IO::LocalFileIO::GetInstance();
  55. AZStd::string newFilePath;
  56. AzFramework::StringFunc::Path::Join(m_absoluteTempSourceFolder.c_str(), newName, newFilePath);
  57. if (io->Exists(newFilePath.c_str()))
  58. {
  59. if (!replaceIfExists)
  60. {
  61. return;
  62. }
  63. }
  64. AZStd::string originalFilePath;
  65. AzFramework::StringFunc::Path::Join(m_absoluteTestDataFolder.c_str(), originalName, originalFilePath);
  66. if (!io->Exists(originalFilePath.c_str()))
  67. {
  68. AZ_Error("ShaderReloadTestComponent", false, "Could not find source file '%s'. This sample can only be used on dev platforms.", originalFilePath.c_str());
  69. return;
  70. }
  71. // Instead of copying the file using AZ::IO::LocalFileIO, we load the file and write out a new file over top
  72. // the destination. This is necessary to make the AP reliably detect the changes (if we just copy the file,
  73. // sometimes it recognizes the OS level copy as an updated file and sometimes not).
  74. AZ::IO::Path copyFrom = AZ::IO::Path(originalFilePath);
  75. AZ::IO::Path copyTo = AZ::IO::Path(newFilePath);
  76. auto readResult = AZ::Utils::ReadFile(copyFrom.c_str());
  77. if (!readResult.IsSuccess())
  78. {
  79. AZ_Error("MaterialHotReloadTestComponent", false, "%s", readResult.GetError().c_str());
  80. return;
  81. }
  82. auto writeResult = AZ::Utils::WriteFile(readResult.GetValue(), copyTo.c_str());
  83. if (!writeResult.IsSuccess())
  84. {
  85. AZ_Error("MaterialHotReloadTestComponent", false, "%s", writeResult.GetError().c_str());
  86. return;
  87. }
  88. }
  89. void ShaderReloadTestComponent::DeleteTestFile(const char* tempSourceFile)
  90. {
  91. AZ::IO::Path deletePath = AZ::IO::Path(m_absoluteTempSourceFolder).Append(tempSourceFile);
  92. if (AZ::IO::LocalFileIO::GetInstance()->Exists(deletePath.c_str()))
  93. {
  94. if (!AZ::IO::LocalFileIO::GetInstance()->Remove(deletePath.c_str()))
  95. {
  96. AZ_Error("MaterialHotReloadTestComponent", false, "Failed to delete '%s'.", deletePath.c_str());
  97. }
  98. }
  99. }
  100. bool ShaderReloadTestComponent::ReadInConfig(const AZ::ComponentConfig*)
  101. {
  102. m_scene = AZ::RPI::RPISystemInterface::Get()->GetSceneByName(AZ::Name("RPI"));
  103. return true;
  104. }
  105. void ShaderReloadTestComponent::Activate()
  106. {
  107. InitTestDataFolders();
  108. AZ::TickBus::Handler::BusConnect();
  109. // connect to the bus before creating new pipeline
  110. AZ::Render::Bootstrap::DefaultWindowNotificationBus::Handler::BusConnect();
  111. PreloadFullscreenShader();
  112. }
  113. void ShaderReloadTestComponent::PreloadFullscreenShader()
  114. {
  115. m_initialized = false;
  116. CopyTestFile(RedShaderFile, "Fullscreen.azsl");
  117. CopyTestFile("Fullscreen.shader.txt", "Fullscreen.shader");
  118. m_expectedPixelColor = RED_COLOR;
  119. AZStd::string shaderAssetPath;
  120. AzFramework::StringFunc::Path::Join(m_relativeTempSourceFolder.c_str(), "Fullscreen.azshader", shaderAssetPath);
  121. AZStd::vector<AZ::AssetCollectionAsyncLoader::AssetToLoadInfo> assetList = {
  122. {shaderAssetPath, azrtti_typeid<AZ::RPI::ShaderAsset>()},
  123. };
  124. m_assetLoadManager.LoadAssetsAsync(assetList, [&]([[maybe_unused]] AZStd::string_view assetName, [[maybe_unused]] bool success, size_t pendingAssetCount)
  125. {
  126. AZ_Error(LogName, success, "Error loading asset %s, a crash will occur when OnAllAssetsReadyActivate() is called!", assetName.data());
  127. AZ_TracePrintf(LogName, "Asset %s loaded %s. Wait for %zu more assets before full activation\n", assetName.data(), success ? "successfully" : "UNSUCCESSFULLY", pendingAssetCount);
  128. if (!pendingAssetCount && !m_initialized)
  129. {
  130. OnAllAssetsReadyActivate();
  131. }
  132. });
  133. }
  134. void ShaderReloadTestComponent::OnAllAssetsReadyActivate()
  135. {
  136. ActivateFullscreenTrianglePipeline();
  137. // Create an ImGuiActiveContextScope to ensure the ImGui context on the new pipeline's ImGui pass is activated.
  138. m_imguiScope = AZ::Render::ImGuiActiveContextScope::FromPass({ "FullscreenPipeline", "ImGuiPass" });
  139. m_imguiSidebar.Activate();
  140. m_initialized = true;
  141. }
  142. void ShaderReloadTestComponent::Deactivate()
  143. {
  144. if (m_initialized)
  145. {
  146. m_imguiSidebar.Deactivate();
  147. m_imguiScope = {}; // restores previous ImGui context.
  148. DeactivateFullscreenTrianglePipeline();
  149. m_initialized = false;
  150. }
  151. AZ::Render::Bootstrap::DefaultWindowNotificationBus::Handler::BusDisconnect();
  152. AZ::TickBus::Handler::BusDisconnect();
  153. }
  154. void ShaderReloadTestComponent::DefaultWindowCreated()
  155. {
  156. AZ::Render::Bootstrap::DefaultWindowBus::BroadcastResult(m_windowContext,
  157. &AZ::Render::Bootstrap::DefaultWindowBus::Events::GetDefaultWindowContext);
  158. }
  159. void ShaderReloadTestComponent::ActivateFullscreenTrianglePipeline()
  160. {
  161. // save original render pipeline first and remove it from the scene
  162. m_originalPipeline = m_scene->GetDefaultRenderPipeline();
  163. m_scene->RemoveRenderPipeline(m_originalPipeline->GetId());
  164. // add the checker board pipeline
  165. const AZStd::string pipelineName("Fullscreen");
  166. AZ::RPI::RenderPipelineDescriptor pipelineDesc;
  167. pipelineDesc.m_mainViewTagName = "MainCamera";
  168. pipelineDesc.m_name = pipelineName;
  169. pipelineDesc.m_rootPassTemplate = "FullscreenPipeline";
  170. m_cbPipeline = AZ::RPI::RenderPipeline::CreateRenderPipelineForWindow(pipelineDesc, *m_windowContext);
  171. m_scene->AddRenderPipeline(m_cbPipeline);
  172. m_cbPipeline->SetDefaultView(m_originalPipeline->GetDefaultView());
  173. m_passHierarchy.push_back(pipelineName);
  174. m_passHierarchy.push_back("CopyToSwapChain");
  175. }
  176. void ShaderReloadTestComponent::DeactivateFullscreenTrianglePipeline()
  177. {
  178. // remove cb pipeline before adding original pipeline.
  179. if (!m_cbPipeline)
  180. {
  181. return;
  182. }
  183. m_scene->RemoveRenderPipeline(m_cbPipeline->GetId());
  184. m_scene->AddRenderPipeline(m_originalPipeline);
  185. m_cbPipeline = nullptr;
  186. m_passHierarchy.clear();
  187. }
  188. void ShaderReloadTestComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint timePoint)
  189. {
  190. if (!m_initialized)
  191. {
  192. return;
  193. }
  194. DrawSidebar();
  195. }
  196. void ShaderReloadTestComponent::DrawSidebar()
  197. {
  198. if (!m_imguiSidebar.Begin())
  199. {
  200. return;
  201. }
  202. ImGui::Text("ShaderReloadTest");
  203. if (ScriptableImGui::Button("Red shader"))
  204. {
  205. m_capturedColorAsString.clear();
  206. m_expectedPixelColor = RED_COLOR;
  207. CopyTestFile(RedShaderFile, "Fullscreen.azsl");
  208. }
  209. if (ScriptableImGui::Button("Green shader"))
  210. {
  211. m_capturedColorAsString.clear();
  212. m_expectedPixelColor = GREEN_COLOR;
  213. CopyTestFile(GreenShaderFile, "Fullscreen.azsl");
  214. }
  215. if (ScriptableImGui::Button("Blue shader"))
  216. {
  217. m_capturedColorAsString.clear();
  218. m_expectedPixelColor = BLUE_COLOR;
  219. CopyTestFile(BlueShaderFile, "Fullscreen.azsl");
  220. }
  221. ImGui::Spacing();
  222. ImGui::Text("Expected Color:");
  223. ImGui::Text("0x%08X", m_expectedPixelColor);
  224. ImGui::Spacing();
  225. if (ScriptableImGui::Button("Check color"))
  226. {
  227. if (!m_isCapturingRenderOutput)
  228. {
  229. m_isCapturingRenderOutput = StartRenderOutputCapture();
  230. }
  231. }
  232. ImGui::Spacing();
  233. ImGui::Text("Captured Color:");
  234. ImGui::Text("%s", m_capturedColorAsString.c_str());
  235. m_imguiSidebar.End();
  236. }
  237. bool ShaderReloadTestComponent::StartRenderOutputCapture()
  238. {
  239. auto captureCallback = [&](const AZ::RPI::AttachmentReadback::ReadbackResult& result)
  240. {
  241. if (result.m_dataBuffer)
  242. {
  243. const auto width = result.m_imageDescriptor.m_size.m_width;
  244. const auto height = result.m_imageDescriptor.m_size.m_height;
  245. uint32_t pixelColor = ReadPixel(result.m_dataBuffer.get()->data(), result.m_imageDescriptor, width/8, height/8);
  246. ValidatePixelColor(pixelColor);
  247. }
  248. else
  249. {
  250. AZ_Error(LogName, false, "Failed to capture render output attachment");
  251. ValidatePixelColor(0);
  252. m_capturedColorAsString = "CAPTURE ERROR!";
  253. }
  254. };
  255. m_capturedColorAsString.clear();
  256. bool startedCapture = false;
  257. AZ::Render::FrameCaptureRequestBus::BroadcastResult(
  258. startedCapture, &AZ::Render::FrameCaptureRequestBus::Events::CapturePassAttachmentWithCallback, m_passHierarchy,
  259. AZStd::string("Output"), captureCallback, AZ::RPI::PassAttachmentReadbackOption::Output);
  260. AZ_Error(LogName, startedCapture, "Failed to start CapturePassAttachmentWithCallback");
  261. return startedCapture;
  262. }
  263. uint32_t ShaderReloadTestComponent::ReadPixel(const uint8_t* rawRGBAPixelData, const AZ::RHI::ImageDescriptor& imageDescriptor, uint32_t x, uint32_t y) const
  264. {
  265. const auto width = imageDescriptor.m_size.m_width;
  266. const auto height = imageDescriptor.m_size.m_height;
  267. AZ_Assert((x < width) && (y < height), "Invalid read pixel location (x, y)=(%u, %u) for width=%u, height=%u", x, y, width, height);
  268. auto tmp = reinterpret_cast<const uint32_t *>(rawRGBAPixelData);
  269. const uint32_t pixelColor = tmp[ (width * y) + x];
  270. if (imageDescriptor.m_format == AZ::RHI::Format::R8G8B8A8_UNORM)
  271. {
  272. return pixelColor;
  273. }
  274. else if (imageDescriptor.m_format == AZ::RHI::Format::B8G8R8A8_UNORM)
  275. {
  276. auto getColorComponent = +[](uint32_t color, int bitPosition) {
  277. return (color >> bitPosition) & 0xFF;
  278. };
  279. const uint32_t blueValue = getColorComponent(pixelColor, 0);
  280. const uint32_t greenValue = getColorComponent(pixelColor, 8);
  281. const uint32_t redValue = getColorComponent(pixelColor, 16);
  282. const uint32_t alphaValue = getColorComponent(pixelColor, 24);
  283. return (alphaValue << 24) | (blueValue << 16) | (greenValue << 8) | redValue;
  284. }
  285. AZ_Error(LogName, false, "Invalid pixel format=%u", aznumeric_cast<uint32_t>(imageDescriptor.m_format));
  286. return pixelColor;
  287. }
  288. void ShaderReloadTestComponent::ValidatePixelColor(uint32_t color)
  289. {
  290. AZ_TracePrintf(LogName, "INFO: Got pixel color=0x%08X, expecting pixel color=0x%08X", color, m_expectedPixelColor);
  291. AZ_Error(LogName, m_expectedPixelColor == color, "Invalid pixel color. Got 0x%08X, was expecting 0x%08X", color, m_expectedPixelColor);
  292. m_capturedColorAsString = AZStd::string::format("0x%08X", color);
  293. m_isCapturingRenderOutput = false;
  294. }
  295. }