EditorCubeMapRenderer.cpp 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  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/PlatformDef.h>
  9. #include <AzCore/std/function/function_fwd.h>
  10. #include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
  11. #include <AzToolsFramework/API/ToolsApplicationAPI.h>
  12. #include <CubeMapCapture/EditorCubeMapRenderer.h>
  13. AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
  14. #include <QApplication>
  15. #include <QMessageBox>
  16. #include <QProgressDialog>
  17. AZ_POP_DISABLE_WARNING
  18. namespace AZ
  19. {
  20. namespace Render
  21. {
  22. AZ::u32 EditorCubeMapRenderer::RenderCubeMap(
  23. AZStd::function<void(RenderCubeMapCallback, AZStd::string&)> renderCubeMapFn,
  24. const AZStd::string dialogText,
  25. const AZ::Entity* entity,
  26. const AZStd::string& folderName,
  27. AZStd::string& relativePath,
  28. CubeMapCaptureType captureType,
  29. CubeMapSpecularQualityLevel specularQualityLevel /* = CubeMapSpecularQualityLevel::Medium */)
  30. {
  31. if (m_renderInProgress)
  32. {
  33. return AZ::Edit::PropertyRefreshLevels::None;
  34. }
  35. // retrieve entity visibility
  36. bool isHidden = false;
  37. AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
  38. isHidden,
  39. entity->GetId(),
  40. &AzToolsFramework::EditorEntityInfoRequestBus::Events::IsHidden);
  41. // the entity must be visible in order to capture
  42. if (isHidden)
  43. {
  44. QMessageBox::information(
  45. QApplication::activeWindow(),
  46. "Error",
  47. "Entity must be visible to build the cubemap.",
  48. QMessageBox::Ok);
  49. return AZ::Edit::PropertyRefreshLevels::None;
  50. }
  51. char projectPath[AZ_MAX_PATH_LEN];
  52. AZ::IO::FileIOBase::GetInstance()->ResolvePath("@projectroot@", projectPath, AZ_MAX_PATH_LEN);
  53. // retrieve the source cubemap path from the configuration
  54. // we need to make sure to use the same source cubemap for each capture
  55. AZStd::string cubeMapRelativePath = relativePath;
  56. AZStd::string cubeMapFullPath;
  57. if (!cubeMapRelativePath.empty())
  58. {
  59. // test to see if the cubemap file is actually there, if it was removed we need to
  60. // generate a new filename, otherwise it will cause an error in the asset system
  61. AzFramework::StringFunc::Path::Join(projectPath, cubeMapRelativePath.c_str(), cubeMapFullPath, true, true);
  62. if (!AZ::IO::FileIOBase::GetInstance()->Exists(cubeMapFullPath.c_str()))
  63. {
  64. // clear it to force the generation of a new filename
  65. cubeMapRelativePath.clear();
  66. }
  67. }
  68. // build a new cubemap path if necessary
  69. if (cubeMapRelativePath.empty())
  70. {
  71. // the file name is a combination of the entity name, a UUID, and the filemask
  72. AZ::Uuid uuid = AZ::Uuid::CreateRandom();
  73. AZStd::string uuidString;
  74. uuid.ToString(uuidString);
  75. // determine the file suffix
  76. AZStd::string fileSuffix;
  77. if (captureType == CubeMapCaptureType::Specular)
  78. {
  79. fileSuffix = CubeMapSpecularFileSuffixes[aznumeric_cast<uint32_t>(specularQualityLevel)];
  80. }
  81. else
  82. {
  83. fileSuffix = CubeMapDiffuseFileSuffix;
  84. }
  85. cubeMapRelativePath = folderName + "/" + entity->GetName() + "_" + uuidString + fileSuffix;
  86. // replace any invalid filename characters
  87. auto invalidCharacters = [](char letter)
  88. {
  89. return
  90. letter == ':' || letter == '"' || letter == '\'' ||
  91. letter == '{' || letter == '}' ||
  92. letter == '<' || letter == '>';
  93. };
  94. AZStd::replace_if(cubeMapRelativePath.begin(), cubeMapRelativePath.end(), invalidCharacters, '_');
  95. // build the full source path
  96. AzFramework::StringFunc::Path::Join(projectPath, cubeMapRelativePath.c_str(), cubeMapFullPath, true, true);
  97. }
  98. // make sure the folder is created
  99. AZStd::string cubemapCaptureFolderPath;
  100. AzFramework::StringFunc::Path::GetFolderPath(cubeMapFullPath.data(), cubemapCaptureFolderPath);
  101. AZ::IO::SystemFile::CreateDir(cubemapCaptureFolderPath.c_str());
  102. // check out the file in source control
  103. bool checkedOutSuccessfully = false;
  104. AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
  105. checkedOutSuccessfully,
  106. &AzToolsFramework::ToolsApplicationRequestBus::Events::RequestEditForFileBlocking,
  107. cubeMapFullPath.c_str(),
  108. "Checking out for edit...",
  109. AzToolsFramework::ToolsApplicationRequestBus::Events::RequestEditProgressCallback());
  110. if (!checkedOutSuccessfully)
  111. {
  112. AZ_Error("CubeMapCapture", false, "Source control checkout failed for file [%s]", cubeMapFullPath.c_str());
  113. }
  114. // save the relative source path in the configuration
  115. relativePath = cubeMapRelativePath;
  116. // callback from the EnvironmentCubeMapPass when the cubemap render is complete
  117. RenderCubeMapCallback renderCubeMapCallback = [=](uint8_t* const* cubeMapFaceTextureData, const RHI::Format cubeMapTextureFormat)
  118. {
  119. // write the cubemap data to the .dds file
  120. WriteOutputFile(cubeMapFullPath.c_str(), cubeMapFaceTextureData, cubeMapTextureFormat);
  121. m_renderInProgress = false;
  122. };
  123. // initiate the cubemap bake, this will invoke the buildCubeMapCallback when the cubemap data is ready
  124. m_renderInProgress = true;
  125. AZStd::string cubeMapRelativeAssetPath = cubeMapRelativePath + ".streamingimage";
  126. renderCubeMapFn(renderCubeMapCallback, cubeMapRelativeAssetPath);
  127. // show a dialog box letting the user know the cubemap is capturing
  128. QProgressDialog captureDialog;
  129. captureDialog.setWindowFlags(captureDialog.windowFlags() & ~Qt::WindowCloseButtonHint);
  130. captureDialog.setLabelText(dialogText.c_str());
  131. captureDialog.setWindowModality(Qt::WindowModal);
  132. captureDialog.setMaximumSize(QSize(256, 96));
  133. captureDialog.setMinimum(0);
  134. captureDialog.setMaximum(0);
  135. captureDialog.setMinimumDuration(0);
  136. captureDialog.setAutoClose(false);
  137. captureDialog.setCancelButton(nullptr);
  138. captureDialog.show();
  139. // display until finished or canceled
  140. while (m_renderInProgress)
  141. {
  142. if (captureDialog.wasCanceled())
  143. {
  144. m_renderInProgress = false;
  145. break;
  146. }
  147. QApplication::processEvents();
  148. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(100));
  149. }
  150. return AZ::Edit::PropertyRefreshLevels::ValuesOnly;
  151. }
  152. void EditorCubeMapRenderer::WriteOutputFile(AZStd::string filePath, uint8_t* const* cubeMapTextureData, const RHI::Format cubeMapTextureFormat)
  153. {
  154. static const u32 CubeMapFaceSize = 1024;
  155. static const u32 NumCubeMapFaces = 6;
  156. u32 bytesPerTexel = RHI::GetFormatSize(cubeMapTextureFormat);
  157. u32 bytesPerCubeMapFace = CubeMapFaceSize * CubeMapFaceSize * bytesPerTexel;
  158. AZStd::vector<uint8_t> buffer;
  159. buffer.resize_no_construct(bytesPerCubeMapFace * NumCubeMapFaces);
  160. for (AZ::u32 i = 0; i < NumCubeMapFaces; ++i)
  161. {
  162. memcpy(buffer.data() + (i * bytesPerCubeMapFace), cubeMapTextureData[i], bytesPerCubeMapFace);
  163. }
  164. DdsFile::DdsFileData ddsFileData;
  165. ddsFileData.m_size.m_width = CubeMapFaceSize;
  166. ddsFileData.m_size.m_height = CubeMapFaceSize;
  167. ddsFileData.m_format = cubeMapTextureFormat;
  168. ddsFileData.m_isCubemap = true;
  169. ddsFileData.m_buffer = &buffer;
  170. auto outcome = AZ::DdsFile::WriteFile(filePath, ddsFileData);
  171. if (!outcome)
  172. {
  173. AZ_Warning("WriteDds", false, outcome.GetError().m_message.c_str());
  174. }
  175. }
  176. }
  177. }