ShaderReloadTestComponent.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  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. m_fileIoErrorHandler.BusConnect();
  77. auto readResult = AZ::Utils::ReadFile(copyFrom.c_str());
  78. if (!readResult.IsSuccess())
  79. {
  80. m_fileIoErrorHandler.ReportLatestIOError(readResult.GetError());
  81. return;
  82. }
  83. auto writeResult = AZ::Utils::WriteFile(readResult.GetValue(), copyTo.c_str());
  84. if (!writeResult.IsSuccess())
  85. {
  86. m_fileIoErrorHandler.ReportLatestIOError(writeResult.GetError());
  87. return;
  88. }
  89. m_fileIoErrorHandler.BusDisconnect();
  90. }
  91. void ShaderReloadTestComponent::DeleteTestFile(const char* tempSourceFile)
  92. {
  93. AZ::IO::Path deletePath = AZ::IO::Path(m_absoluteTempSourceFolder).Append(tempSourceFile);
  94. if (AZ::IO::LocalFileIO::GetInstance()->Exists(deletePath.c_str()))
  95. {
  96. m_fileIoErrorHandler.BusConnect();
  97. if (!AZ::IO::LocalFileIO::GetInstance()->Remove(deletePath.c_str()))
  98. {
  99. m_fileIoErrorHandler.ReportLatestIOError(AZStd::string::format("Failed to delete '%s'.", deletePath.c_str()));
  100. }
  101. m_fileIoErrorHandler.BusDisconnect();
  102. }
  103. }
  104. bool ShaderReloadTestComponent::ReadInConfig(const AZ::ComponentConfig*)
  105. {
  106. m_scene = AZ::RPI::RPISystemInterface::Get()->GetSceneByName(AZ::Name("RPI"));
  107. return true;
  108. }
  109. void ShaderReloadTestComponent::Activate()
  110. {
  111. InitTestDataFolders();
  112. AZ::TickBus::Handler::BusConnect();
  113. // connect to the bus before creating new pipeline
  114. AZ::Render::Bootstrap::DefaultWindowNotificationBus::Handler::BusConnect();
  115. PreloadFullscreenShader();
  116. }
  117. void ShaderReloadTestComponent::PreloadFullscreenShader()
  118. {
  119. m_initialized = false;
  120. CopyTestFile(RedShaderFile, "Fullscreen.azsl");
  121. CopyTestFile("Fullscreen.shader.txt", "Fullscreen.shader");
  122. m_expectedPixelColor = RED_COLOR;
  123. AZStd::string shaderAssetPath;
  124. AzFramework::StringFunc::Path::Join(m_relativeTempSourceFolder.c_str(), "Fullscreen.azshader", shaderAssetPath);
  125. AZStd::vector<AZ::AssetCollectionAsyncLoader::AssetToLoadInfo> assetList = {
  126. {shaderAssetPath, azrtti_typeid<AZ::RPI::ShaderAsset>()},
  127. };
  128. m_assetLoadManager.LoadAssetsAsync(assetList, [&]([[maybe_unused]] AZStd::string_view assetName, [[maybe_unused]] bool success, size_t pendingAssetCount)
  129. {
  130. AZ_Error(LogName, success, "Error loading asset %s, a crash will occur when OnAllAssetsReadyActivate() is called!", assetName.data());
  131. AZ_TracePrintf(LogName, "Asset %s loaded %s. Wait for %zu more assets before full activation\n", assetName.data(), success ? "successfully" : "UNSUCCESSFULLY", pendingAssetCount);
  132. if (!pendingAssetCount && !m_initialized)
  133. {
  134. OnAllAssetsReadyActivate();
  135. }
  136. });
  137. }
  138. void ShaderReloadTestComponent::OnAllAssetsReadyActivate()
  139. {
  140. ActivateFullscreenTrianglePipeline();
  141. // Create an ImGuiActiveContextScope to ensure the ImGui context on the new pipeline's ImGui pass is activated.
  142. m_imguiScope = AZ::Render::ImGuiActiveContextScope::FromPass({ "FullscreenPipeline", "ImGuiPass" });
  143. m_imguiSidebar.Activate();
  144. m_initialized = true;
  145. }
  146. void ShaderReloadTestComponent::Deactivate()
  147. {
  148. if (m_initialized)
  149. {
  150. m_imguiSidebar.Deactivate();
  151. m_imguiScope = {}; // restores previous ImGui context.
  152. DeactivateFullscreenTrianglePipeline();
  153. m_initialized = false;
  154. }
  155. AZ::Render::Bootstrap::DefaultWindowNotificationBus::Handler::BusDisconnect();
  156. AZ::TickBus::Handler::BusDisconnect();
  157. }
  158. void ShaderReloadTestComponent::DefaultWindowCreated()
  159. {
  160. AZ::Render::Bootstrap::DefaultWindowBus::BroadcastResult(m_windowContext,
  161. &AZ::Render::Bootstrap::DefaultWindowBus::Events::GetDefaultWindowContext);
  162. }
  163. void ShaderReloadTestComponent::ActivateFullscreenTrianglePipeline()
  164. {
  165. // save original render pipeline first and remove it from the scene
  166. m_originalPipeline = m_scene->GetDefaultRenderPipeline();
  167. m_scene->RemoveRenderPipeline(m_originalPipeline->GetId());
  168. // add the checker board pipeline
  169. const AZStd::string pipelineName("Fullscreen");
  170. AZ::RPI::RenderPipelineDescriptor pipelineDesc;
  171. pipelineDesc.m_mainViewTagName = "MainCamera";
  172. pipelineDesc.m_name = pipelineName;
  173. pipelineDesc.m_rootPassTemplate = "FullscreenPipeline";
  174. m_cbPipeline = AZ::RPI::RenderPipeline::CreateRenderPipelineForWindow(pipelineDesc, *m_windowContext);
  175. m_scene->AddRenderPipeline(m_cbPipeline);
  176. m_cbPipeline->SetDefaultView(m_originalPipeline->GetDefaultView());
  177. m_passHierarchy.push_back(pipelineName);
  178. m_passHierarchy.push_back("CopyToSwapChain");
  179. }
  180. void ShaderReloadTestComponent::DeactivateFullscreenTrianglePipeline()
  181. {
  182. // remove cb pipeline before adding original pipeline.
  183. if (!m_cbPipeline)
  184. {
  185. return;
  186. }
  187. m_scene->RemoveRenderPipeline(m_cbPipeline->GetId());
  188. m_scene->AddRenderPipeline(m_originalPipeline);
  189. m_cbPipeline = nullptr;
  190. m_passHierarchy.clear();
  191. }
  192. void ShaderReloadTestComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint timePoint)
  193. {
  194. if (!m_initialized)
  195. {
  196. return;
  197. }
  198. DrawSidebar();
  199. }
  200. void ShaderReloadTestComponent::DrawSidebar()
  201. {
  202. if (!m_imguiSidebar.Begin())
  203. {
  204. return;
  205. }
  206. ImGui::Text("ShaderReloadTest");
  207. if (ScriptableImGui::Button("Red shader"))
  208. {
  209. m_capturedColorAsString.clear();
  210. m_expectedPixelColor = RED_COLOR;
  211. CopyTestFile(RedShaderFile, "Fullscreen.azsl");
  212. }
  213. if (ScriptableImGui::Button("Green shader"))
  214. {
  215. m_capturedColorAsString.clear();
  216. m_expectedPixelColor = GREEN_COLOR;
  217. CopyTestFile(GreenShaderFile, "Fullscreen.azsl");
  218. }
  219. if (ScriptableImGui::Button("Blue shader"))
  220. {
  221. m_capturedColorAsString.clear();
  222. m_expectedPixelColor = BLUE_COLOR;
  223. CopyTestFile(BlueShaderFile, "Fullscreen.azsl");
  224. }
  225. ImGui::Spacing();
  226. ImGui::Text("Expected Color:");
  227. ImGui::Text("0x%08X", m_expectedPixelColor);
  228. ImGui::Spacing();
  229. if (ScriptableImGui::Button("Check color"))
  230. {
  231. if (!m_isCapturingRenderOutput)
  232. {
  233. m_isCapturingRenderOutput = StartRenderOutputCapture();
  234. }
  235. }
  236. ImGui::Spacing();
  237. ImGui::Text("Captured Color:");
  238. ImGui::Text("%s", m_capturedColorAsString.c_str());
  239. m_imguiSidebar.End();
  240. }
  241. bool ShaderReloadTestComponent::StartRenderOutputCapture()
  242. {
  243. auto captureCallback = [&](const AZ::RPI::AttachmentReadback::ReadbackResult& result)
  244. {
  245. if (result.m_dataBuffer)
  246. {
  247. const auto width = result.m_imageDescriptor.m_size.m_width;
  248. const auto height = result.m_imageDescriptor.m_size.m_height;
  249. uint32_t pixelColor = ReadPixel(result.m_dataBuffer.get()->data(), result.m_imageDescriptor, width/8, height/8);
  250. ValidatePixelColor(pixelColor);
  251. }
  252. else
  253. {
  254. AZ_Error(LogName, false, "Failed to capture render output attachment");
  255. ValidatePixelColor(0);
  256. m_capturedColorAsString = "CAPTURE ERROR!";
  257. }
  258. };
  259. m_capturedColorAsString.clear();
  260. bool startedCapture = false;
  261. AZ::Render::FrameCaptureRequestBus::BroadcastResult(
  262. startedCapture, &AZ::Render::FrameCaptureRequestBus::Events::CapturePassAttachmentWithCallback, m_passHierarchy,
  263. AZStd::string("Output"), captureCallback, AZ::RPI::PassAttachmentReadbackOption::Output);
  264. AZ_Error(LogName, startedCapture, "Failed to start CapturePassAttachmentWithCallback");
  265. return startedCapture;
  266. }
  267. uint32_t ShaderReloadTestComponent::ReadPixel(const uint8_t* rawRGBAPixelData, const AZ::RHI::ImageDescriptor& imageDescriptor, uint32_t x, uint32_t y) const
  268. {
  269. const auto width = imageDescriptor.m_size.m_width;
  270. const auto height = imageDescriptor.m_size.m_height;
  271. AZ_Assert((x < width) && (y < height), "Invalid read pixel location (x, y)=(%u, %u) for width=%u, height=%u", x, y, width, height);
  272. auto tmp = reinterpret_cast<const uint32_t *>(rawRGBAPixelData);
  273. const uint32_t pixelColor = tmp[ (width * y) + x];
  274. if (imageDescriptor.m_format == AZ::RHI::Format::R8G8B8A8_UNORM)
  275. {
  276. return pixelColor;
  277. }
  278. else if (imageDescriptor.m_format == AZ::RHI::Format::B8G8R8A8_UNORM)
  279. {
  280. auto getColorComponent = +[](uint32_t color, int bitPosition) {
  281. return (color >> bitPosition) & 0xFF;
  282. };
  283. const uint32_t blueValue = getColorComponent(pixelColor, 0);
  284. const uint32_t greenValue = getColorComponent(pixelColor, 8);
  285. const uint32_t redValue = getColorComponent(pixelColor, 16);
  286. const uint32_t alphaValue = getColorComponent(pixelColor, 24);
  287. return (alphaValue << 24) | (blueValue << 16) | (greenValue << 8) | redValue;
  288. }
  289. AZ_Error(LogName, false, "Invalid pixel format=%u", aznumeric_cast<uint32_t>(imageDescriptor.m_format));
  290. return pixelColor;
  291. }
  292. void ShaderReloadTestComponent::ValidatePixelColor(uint32_t color)
  293. {
  294. AZ_TracePrintf(LogName, "INFO: Got pixel color=0x%08X, expecting pixel color=0x%08X", color, m_expectedPixelColor);
  295. AZ_Error(LogName, m_expectedPixelColor == color, "Invalid pixel color. Got 0x%08X, was expecting 0x%08X", color, m_expectedPixelColor);
  296. m_capturedColorAsString = AZStd::string::format("0x%08X", color);
  297. m_isCapturingRenderOutput = false;
  298. }
  299. }