EditorGradientSignalBakerTests.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  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 <AzTest/AzTest.h>
  9. #include <AzCore/Jobs/Job.h>
  10. #include <AzCore/Jobs/JobManager.h>
  11. #include <AzCore/Math/Aabb.h>
  12. #include <AzCore/Math/Random.h>
  13. #include <AzCore/Memory/Memory.h>
  14. #include <AzCore/Memory/PoolAllocator.h>
  15. #include <AzFramework/IO/LocalFileIO.h>
  16. #include <AzTest/Utils.h>
  17. #include <GradientSignal/Editor/EditorGradientBakerComponent.h>
  18. #include <Tests/FileIOBaseTestTypes.h>
  19. // this needs to be included before OpenImageIO because of WIN32 GetObject macro conflicting with RegistrySettings::GetObject
  20. #include <Tests/GradientSignalTestFixtures.h>
  21. AZ_PUSH_DISABLE_WARNING(4777, "-Wunknown-warning-option")
  22. #include <OpenImageIO/imageio.h>
  23. AZ_POP_DISABLE_WARNING
  24. namespace UnitTest
  25. {
  26. struct EditorGradientSignalBakerTestsFixture
  27. : public GradientSignalTest
  28. {
  29. protected:
  30. AZ::JobManager* m_jobManager = nullptr;
  31. AZ::JobContext* m_jobContext = nullptr;
  32. // We need to use LocalFileIO for these tests so that the image saving code can properly save and rename the test files.
  33. // If we use TestFileIOBase or no FileIOBase, the necessary File IO operations won't exist and the tests will fail.
  34. AZ::IO::LocalFileIO m_fileIO;
  35. AZ::IO::FileIOBase* m_prevFileIO{};
  36. void SetUp() override
  37. {
  38. GradientSignalTest::SetUp();
  39. auto globalContext = AZ::JobContext::GetGlobalContext();
  40. if (globalContext)
  41. {
  42. AZ_Assert(
  43. globalContext->GetJobManager().GetNumWorkerThreads() >= 2,
  44. "Job Manager previously started by test environment with too few threads for this test.");
  45. }
  46. else
  47. {
  48. // Set up job manager with two threads so that we can run and test the preview job logic.
  49. AZ::JobManagerDesc desc;
  50. AZ::JobManagerThreadDesc threadDesc;
  51. desc.m_workerThreads.push_back(threadDesc);
  52. desc.m_workerThreads.push_back(threadDesc);
  53. m_jobManager = aznew AZ::JobManager(desc);
  54. m_jobContext = aznew AZ::JobContext(*m_jobManager);
  55. AZ::JobContext::SetGlobalContext(m_jobContext);
  56. }
  57. m_prevFileIO = AZ::IO::FileIOBase::GetInstance();
  58. AZ::IO::FileIOBase::SetInstance(&m_fileIO);
  59. }
  60. void TearDown() override
  61. {
  62. AZ::IO::FileIOBase::SetInstance(m_prevFileIO);
  63. if (m_jobContext)
  64. {
  65. AZ::JobContext::SetGlobalContext(nullptr);
  66. delete m_jobContext;
  67. delete m_jobManager;
  68. }
  69. GradientSignalTest::TearDown();
  70. }
  71. void TestBakeImage(
  72. AZStd::string extension,
  73. GradientSignal::OutputFormat outputFormat,
  74. AZ::Vector2 outputResolution = AZ::Vector2(10.0f),
  75. bool useValidGradient = true,
  76. AZ::Aabb inputBounds = AZ::Aabb::CreateNull())
  77. {
  78. // If we are going to test against a valid gradient, create a random float value between [0,1) to use as the expected value so
  79. // that we can guarantee each test is executing against a unique image
  80. AZ::SimpleLcgRandom random;
  81. float expectedValue = (useValidGradient) ? random.GetRandomFloat() : 0.0f;
  82. // Build a constant gradient with our expected value to be used as the input to the gradient baker
  83. auto constantGradientEntity = BuildTestConstantGradient(10.0f, expectedValue);
  84. AZ::EntityId inputGradientEntityId;
  85. if (useValidGradient)
  86. {
  87. inputGradientEntityId = constantGradientEntity->GetId();
  88. }
  89. // Setup our gradient baker configuration as per the test inputs
  90. GradientSignal::GradientBakerConfig configuration;
  91. configuration.m_gradientSampler.m_gradientId = inputGradientEntityId;
  92. configuration.m_outputFormat = outputFormat;
  93. configuration.m_outputResolution = outputResolution;
  94. configuration.m_inputBounds = inputGradientEntityId;
  95. // Create a temporary directory that will be deleted (along with its contents) after the test is complete that will hold our
  96. // baked output image
  97. AZ::Test::ScopedAutoTempDirectory tempDir;
  98. // Resolve a full file path for the baked output image based on the extension we are testing inside our temporary directory
  99. AZStd::string outputFilename = "baked_output";
  100. outputFilename += extension;
  101. AZ::IO::Path fullPath(tempDir.Resolve(outputFilename.c_str()));
  102. // Create an input bounds (if one wasn't passed in, which is the default case)
  103. // If an input bounds was explicitly passed in, we are assuming it is for the BoundsHalfOverlap
  104. // which changes which pixels we are going to compare against at the end of the test
  105. bool compareAgainstFirstPixel = true;
  106. if (!inputBounds.IsValid())
  107. {
  108. inputBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(10.0f, 10.0f, 0.0f));
  109. }
  110. else
  111. {
  112. compareAgainstFirstPixel = false;
  113. }
  114. // Create the bake job and wait until it completes
  115. auto bakeJob = aznew GradientSignal::BakeImageJob(configuration, fullPath, inputBounds, configuration.m_inputBounds);
  116. bakeJob->Start();
  117. bakeJob->Wait();
  118. auto imageInput = OIIO::ImageInput::open(fullPath.c_str());
  119. ASSERT_NE(imageInput, nullptr);
  120. // Make sure the image that was loaded had no errors
  121. EXPECT_EQ(imageInput->has_error(), false);
  122. // Make sure the expected image resolution matches the resolution spec of the actual file that was baked
  123. const OIIO::ImageSpec& spec = imageInput->spec();
  124. const int imageResolutionX = aznumeric_cast<int>(configuration.m_outputResolution.GetX());
  125. const int imageResolutionY = aznumeric_cast<int>(configuration.m_outputResolution.GetY());
  126. EXPECT_EQ(spec.width, imageResolutionX);
  127. EXPECT_EQ(spec.height, imageResolutionY);
  128. // Read in the image pixels
  129. AZStd::vector<float> pixels(spec.width * spec.height * spec.nchannels);
  130. imageInput->read_image(OIIO::TypeDesc::FLOAT, pixels.data());
  131. // For most tests we are going to check against the first pixel we find, but for the bounds overlap
  132. // test case we need to instead compare against the opposite edge of the image
  133. int firstPixelIndex = 0;
  134. if (!compareAgainstFirstPixel)
  135. {
  136. firstPixelIndex = (spec.width * spec.nchannels) - 1;
  137. }
  138. // For the R8 output format, we don't have enough granularity to satisfy the default
  139. // float value tolerance, so we need to calculate the actual tolerance threshold
  140. float tolerance = AZ::Constants::Tolerance;
  141. if (outputFormat == GradientSignal::OutputFormat::R8)
  142. {
  143. tolerance = 1.0f / std::numeric_limits<AZ::u8>::max();
  144. }
  145. EXPECT_TRUE(AZ::IsClose(pixels[firstPixelIndex], expectedValue, tolerance));
  146. // For the bounds overlap test case, we need to verify the first pixel (0,0) is outside
  147. // the bounds so it will be 0.0f
  148. if (!compareAgainstFirstPixel)
  149. {
  150. EXPECT_TRUE(AZ::IsClose(pixels[0], 0.0f, tolerance));
  151. }
  152. imageInput->close();
  153. delete bakeJob;
  154. }
  155. };
  156. TEST_F(EditorGradientSignalBakerTestsFixture, InvalidInputGradient)
  157. {
  158. // An invalid input gradient should cause the output image to be entirely 0.0f values
  159. TestBakeImage(".png", GradientSignal::OutputFormat::R8, AZ::Vector2(10.0f), false);
  160. }
  161. TEST_F(EditorGradientSignalBakerTestsFixture, BoundsHalfOverlap)
  162. {
  163. // Creating an input bounds that half overlaps our test shape will result in half being the
  164. // expected constant value and the other half 0.0f
  165. AZ::Aabb inputBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-5.0f, -5.0f, 0.0f), AZ::Vector3(5.0f, 5.0f, 0.0f));
  166. TestBakeImage(".png", GradientSignal::OutputFormat::R8, AZ::Vector2(10.0f), true, inputBounds);
  167. }
  168. TEST_F(EditorGradientSignalBakerTestsFixture, NonSquareOutputResolution)
  169. {
  170. // Verify we support output resolutions where the width isn't equal to the height
  171. TestBakeImage(".png", GradientSignal::OutputFormat::R8, AZ::Vector2(13.0f, 37.0f));
  172. }
  173. TEST_F(EditorGradientSignalBakerTestsFixture, BakedImage_PNG_R8)
  174. {
  175. TestBakeImage(".png", GradientSignal::OutputFormat::R8);
  176. }
  177. TEST_F(EditorGradientSignalBakerTestsFixture, BakedImage_TIFF_R8)
  178. {
  179. TestBakeImage(".tiff", GradientSignal::OutputFormat::R8);
  180. }
  181. TEST_F(EditorGradientSignalBakerTestsFixture, BakedImage_TGA_R8)
  182. {
  183. TestBakeImage(".tga", GradientSignal::OutputFormat::R8);
  184. }
  185. TEST_F(EditorGradientSignalBakerTestsFixture, BakedImage_EXR_R8)
  186. {
  187. TestBakeImage(".exr", GradientSignal::OutputFormat::R8);
  188. }
  189. TEST_F(EditorGradientSignalBakerTestsFixture, BakedImage_PNG_R16)
  190. {
  191. TestBakeImage(".png", GradientSignal::OutputFormat::R16);
  192. }
  193. TEST_F(EditorGradientSignalBakerTestsFixture, BakedImage_TIFF_R16)
  194. {
  195. TestBakeImage(".tiff", GradientSignal::OutputFormat::R16);
  196. }
  197. TEST_F(EditorGradientSignalBakerTestsFixture, BakedImage_TGA_R16)
  198. {
  199. TestBakeImage(".tga", GradientSignal::OutputFormat::R16);
  200. }
  201. TEST_F(EditorGradientSignalBakerTestsFixture, BakedImage_EXR_R16)
  202. {
  203. TestBakeImage(".exr", GradientSignal::OutputFormat::R16);
  204. }
  205. TEST_F(EditorGradientSignalBakerTestsFixture, BakedImage_PNG_R32)
  206. {
  207. TestBakeImage(".png", GradientSignal::OutputFormat::R32);
  208. }
  209. TEST_F(EditorGradientSignalBakerTestsFixture, BakedImage_TIFF_R32)
  210. {
  211. TestBakeImage(".tiff", GradientSignal::OutputFormat::R32);
  212. }
  213. TEST_F(EditorGradientSignalBakerTestsFixture, BakedImage_TGA_R32)
  214. {
  215. TestBakeImage(".tga", GradientSignal::OutputFormat::R32);
  216. }
  217. TEST_F(EditorGradientSignalBakerTestsFixture, BakedImage_EXR_R32)
  218. {
  219. TestBakeImage(".exr", GradientSignal::OutputFormat::R32);
  220. }
  221. }