/// <summary> /// Render an image from the given cameras and compare it to the reference image. /// </summary> /// <param name="expected">The expected image that should be rendered by the camera.</param> /// <param name="cameras">The cameras to render from.</param> /// <param name="settings">Optional settings that control how the image comparison is performed. Can be null, in which case the rendered image is required to be exactly identical to the reference.</param> public static void AreEqual(Texture2D expected, IEnumerable <Camera> cameras, ImageComparisonSettings settings = null) { if (cameras == null) { throw new ArgumentNullException(nameof(cameras)); } if (settings == null) { settings = new ImageComparisonSettings(); } int width = settings.TargetWidth; int height = settings.TargetHeight; var format = expected != null ? expected.format : TextureFormat.ARGB32; // Some HDRP test fail with HDRP batcher because shaders variant are compiled "on the fly" in editor mode. // Persistent PerMaterial CBUFFER is build during culling, but some nodes could use new variants and CBUFFER will be up to date next frame. // ( this is editor specific, standalone player has no frame delay issue because all variants are ready at init stage ) // This PR adds a dummy rendered frame before doing the real rendering and compare images ( test already has frame delay, but there is no rendering ) int dummyRenderedFrameCount = 1; var rt = RenderTexture.GetTemporary(width, height, 24); Texture2D actual = null; try { for (int i = 0; i < dummyRenderedFrameCount + 1; i++) // x frame delay + the last one is the one really tested ( ie 5 frames delay means 6 frames are rendered ) { foreach (var camera in cameras) { camera.targetTexture = rt; camera.Render(); camera.targetTexture = null; } // only proceed the test on the last renderered frame if (dummyRenderedFrameCount == i) { actual = new Texture2D(width, height, format, false); RenderTexture.active = rt; actual.ReadPixels(new Rect(0, 0, width, height), 0, 0); RenderTexture.active = null; actual.Apply(); AreEqual(expected, actual, settings); } } } finally { RenderTexture.ReleaseTemporary(rt); if (actual != null) { UnityEngine.Object.Destroy(actual); } } }
/// <summary> /// Render an image from the given camera and compare it to the reference image. /// </summary> /// <param name="expected">The expected image that should be rendered by the camera.</param> /// <param name="camera">The camera to render from.</param> /// <param name="settings">Optional settings that control how the image comparison is performed. Can be null, in which case the rendered image is required to be exactly identical to the reference.</param> public static void AreEqual(Texture2D expected, Camera camera, ImageComparisonSettings settings = null) { if (camera == null) { throw new ArgumentNullException(nameof(camera)); } AreEqual(expected, new List <Camera> { camera }, settings); }
/// <summary> /// Render an image from the given camera and check if it allocated memory while doing so. /// </summary> /// <param name="camera">The camera to render from.</param> /// <param name="width"> width of the image to be rendered</param> /// <param name="height"> height of the image to be rendered</param> public static void AllocatesMemory(Camera camera, ImageComparisonSettings settings = null, int gcAllocThreshold = 2) { if (camera == null) { throw new ArgumentNullException(nameof(camera)); } if (settings == null) { settings = new ImageComparisonSettings(); } int width = settings.TargetWidth; int height = settings.TargetHeight; var defaultFormat = (settings.UseHDR) ? SystemInfo.GetGraphicsFormat(DefaultFormat.HDR) : SystemInfo.GetGraphicsFormat(DefaultFormat.LDR); RenderTextureDescriptor desc = new RenderTextureDescriptor(width, height, defaultFormat, 24); var rt = RenderTexture.GetTemporary(desc); try { camera.targetTexture = rt; // Render the first frame at this resolution (Alloc are allowed here) camera.Render(); var gcAllocRecorder = Recorder.Get("GC.Alloc"); gcAllocRecorder.FilterToCurrentThread(); Profiler.BeginSample("GraphicTests_GC_Alloc_Check"); { gcAllocRecorder.enabled = true; camera.Render(); gcAllocRecorder.enabled = false; } Profiler.EndSample(); // There are 2 GC.Alloc overhead for calling Camera.CustomRender int allocationCountOfRenderPipeline = gcAllocRecorder.sampleBlockCount - gcAllocThreshold; if (allocationCountOfRenderPipeline > 0) { throw new Exception($"Memory allocation test failed, {allocationCountOfRenderPipeline} allocations detected. Look for GraphicTests_GC_Alloc_Check in the profiler for more details"); } camera.targetTexture = null; } finally { RenderTexture.ReleaseTemporary(rt); } }
/// <summary> /// Render an image from the given cameras and compare it to the reference image. /// </summary> /// <param name="expected">The expected image that should be rendered by the camera.</param> /// <param name="cameras">The cameras to render from.</param> /// <param name="settings">Optional settings that control how the image comparison is performed. Can be null, in which case the rendered image is required to be exactly identical to the reference.</param> public static void AreEqual(Texture2D expected, IEnumerable <Camera> cameras, ImageComparisonSettings settings = null) { if (cameras == null) { throw new ArgumentNullException(nameof(cameras)); } if (settings == null) { settings = new ImageComparisonSettings(); } int width = settings.TargetWidth; int height = settings.TargetHeight; var format = expected != null ? expected.format : TextureFormat.ARGB32; var rt = RenderTexture.GetTemporary(width, height, 24); Texture2D actual = null; try { foreach (var camera in cameras) { camera.targetTexture = rt; camera.Render(); camera.targetTexture = null; } actual = new Texture2D(width, height, format, false); RenderTexture.active = rt; actual.ReadPixels(new Rect(0, 0, width, height), 0, 0); RenderTexture.active = null; actual.Apply(); AreEqual(expected, actual, settings); } finally { RenderTexture.ReleaseTemporary(rt); if (actual != null) { UnityEngine.Object.Destroy(actual); } } }
/// <summary> /// Compares an image to a 'reference' image to see if it looks correct. /// </summary> /// <param name="expected">What the image is supposed to look like.</param> /// <param name="actual">What the image actually looks like.</param> /// <param name="settings">Optional settings that control how the comparison is performed. Can be null, in which case the images are required to be exactly identical.</param> public static void AreEqual(Texture2D expected, Texture2D actual, ImageComparisonSettings settings = null) { if (actual == null) { throw new ArgumentNullException("actual"); } try { Assert.That(expected, Is.Not.Null, "No reference image was provided."); Assert.That(actual.width, Is.EqualTo(expected.width), "The expected image had width {0}px, but the actual image had width {1}px.", expected.width, actual.width); Assert.That(actual.height, Is.EqualTo(expected.height), "The expected image had height {0}px, but the actual image had height {1}px.", expected.height, actual.height); Assert.That(actual.format, Is.EqualTo(expected.format), "The expected image had format {0} but the actual image had format {1}.", expected.format, actual.format); using (var expectedPixels = new NativeArray <Color32>(expected.GetPixels32(0), Allocator.TempJob)) using (var actualPixels = new NativeArray <Color32>(actual.GetPixels32(0), Allocator.TempJob)) using (var diffPixels = new NativeArray <Color32>(expectedPixels.Length, Allocator.TempJob)) using (var sumOverThreshold = new NativeArray <float>(Mathf.CeilToInt(expectedPixels.Length / (float)k_BatchSize), Allocator.TempJob)) { if (settings == null) { settings = new ImageComparisonSettings(); } new ComputeDiffJob { expected = expectedPixels, actual = actualPixels, diff = diffPixels, sumOverThreshold = sumOverThreshold, pixelThreshold = settings.PerPixelCorrectnessThreshold }.Schedule(expectedPixels.Length, k_BatchSize).Complete(); float averageDeltaE = sumOverThreshold.Sum() / (expected.width * expected.height); try { Assert.That(averageDeltaE, Is.LessThanOrEqualTo(settings.AverageCorrectnessThreshold)); } catch (AssertionException) { var diffImage = new Texture2D(expected.width, expected.height, TextureFormat.RGB24, false); var diffPixelsArray = new Color32[expected.width * expected.height]; diffPixels.CopyTo(diffPixelsArray); diffImage.SetPixels32(diffPixelsArray, 0); diffImage.Apply(false); TestContext.CurrentContext.Test.Properties.Set("DiffImage", Convert.ToBase64String(diffImage.EncodeToPNG())); throw; } } } catch (AssertionException) { TestContext.CurrentContext.Test.Properties.Set("Image", Convert.ToBase64String(actual.EncodeToPNG())); throw; } }
/// <summary> /// Compares an image to a 'reference' image to see if it looks correct. /// </summary> /// <param name="expected">What the image is supposed to look like.</param> /// <param name="actual">What the image actually looks like.</param> /// <param name="settings">Optional settings that control how the comparison is performed. Can be null, in which case the images are required to be exactly identical.</param> public static void AreEqual(Texture2D expected, Texture2D actual, ImageComparisonSettings settings = null) { if (actual == null) { throw new ArgumentNullException("actual"); } #if UNITY_EDITOR var imagesWritten = new HashSet <string>(); var dirName = Path.Combine("Assets/ActualImages", string.Format("{0}/{1}/{2}", UseGraphicsTestCasesAttribute.ColorSpace, UseGraphicsTestCasesAttribute.Platform, UseGraphicsTestCasesAttribute.GraphicsDevice)); Directory.CreateDirectory(dirName); #endif try { Assert.That(expected, Is.Not.Null, "No reference image was provided."); Assert.That(actual.width, Is.EqualTo(expected.width), "The expected image had width {0}px, but the actual image had width {1}px.", expected.width, actual.width); Assert.That(actual.height, Is.EqualTo(expected.height), "The expected image had height {0}px, but the actual image had height {1}px.", expected.height, actual.height); Assert.That(actual.format, Is.EqualTo(expected.format), "The expected image had format {0} but the actual image had format {1}.", expected.format, actual.format); using (var expectedPixels = new NativeArray <Color32>(expected.GetPixels32(0), Allocator.TempJob)) using (var actualPixels = new NativeArray <Color32>(actual.GetPixels32(0), Allocator.TempJob)) using (var diffPixels = new NativeArray <Color32>(expectedPixels.Length, Allocator.TempJob)) using (var sumOverThreshold = new NativeArray <float>(Mathf.CeilToInt(expectedPixels.Length / (float)k_BatchSize), Allocator.TempJob)) { if (settings == null) { settings = new ImageComparisonSettings(); } new ComputeDiffJob { expected = expectedPixels, actual = actualPixels, diff = diffPixels, sumOverThreshold = sumOverThreshold, pixelThreshold = settings.PerPixelCorrectnessThreshold }.Schedule(expectedPixels.Length, k_BatchSize).Complete(); float averageDeltaE = sumOverThreshold.Sum() / (expected.width * expected.height); try { Assert.That(averageDeltaE, Is.LessThanOrEqualTo(settings.AverageCorrectnessThreshold)); } catch (AssertionException) { var diffImage = new Texture2D(expected.width, expected.height, TextureFormat.RGB24, false); var diffPixelsArray = new Color32[expected.width * expected.height]; diffPixels.CopyTo(diffPixelsArray); diffImage.SetPixels32(diffPixelsArray, 0); diffImage.Apply(false); #if UNITY_EDITOR if (sDontWriteToLog) { var bytes = diffImage.EncodeToPNG(); var path = Path.Combine(dirName, TestContext.CurrentContext.Test.Name + ".diff.png"); File.WriteAllBytes(path, bytes); imagesWritten.Add(path); } else #endif TestContext.CurrentContext.Test.Properties.Set("DiffImage", Convert.ToBase64String(diffImage.EncodeToPNG())); throw; } } } catch (AssertionException) { #if UNITY_EDITOR if (sDontWriteToLog) { var bytes = actual.EncodeToPNG(); var path = Path.Combine(dirName, TestContext.CurrentContext.Test.Name + ".png"); File.WriteAllBytes(path, bytes); imagesWritten.Add(path); AssetDatabase.Refresh(); UnityEditor.TestTools.Graphics.Utils.SetupReferenceImageImportSettings(imagesWritten); } else #endif TestContext.CurrentContext.Test.Properties.Set("Image", Convert.ToBase64String(actual.EncodeToPNG())); throw; } }
/// <summary> /// Compares an image to a 'reference' image to see if it looks correct. /// </summary> /// <param name="expected">What the image is supposed to look like.</param> /// <param name="actual">What the image actually looks like.</param> /// <param name="settings">Optional settings that control how the comparison is performed. Can be null, in which case the images are required to be exactly identical.</param> public static void AreEqual(Texture2D expected, Texture2D actual, ImageComparisonSettings settings = null) { if (actual == null) { throw new ArgumentNullException(nameof(actual)); } var dirName = Path.Combine("Assets/ActualImages", string.Format("{0}/{1}/{2}", UseGraphicsTestCasesAttribute.ColorSpace, UseGraphicsTestCasesAttribute.Platform, UseGraphicsTestCasesAttribute.GraphicsDevice)); var failedImageMessage = new FailedImageMessage { PathName = dirName, ImageName = TestContext.CurrentContext.Test.Name, }; try { Assert.That(expected, Is.Not.Null, "No reference image was provided."); Assert.That(actual.width, Is.EqualTo(expected.width), "The expected image had width {0}px, but the actual image had width {1}px.", expected.width, actual.width); Assert.That(actual.height, Is.EqualTo(expected.height), "The expected image had height {0}px, but the actual image had height {1}px.", expected.height, actual.height); Assert.That(actual.format, Is.EqualTo(expected.format), "The expected image had format {0} but the actual image had format {1}.", expected.format, actual.format); using (var expectedPixels = new NativeArray <Color32>(expected.GetPixels32(0), Allocator.TempJob)) using (var actualPixels = new NativeArray <Color32>(actual.GetPixels32(0), Allocator.TempJob)) using (var diffPixels = new NativeArray <Color32>(expectedPixels.Length, Allocator.TempJob)) using (var sumOverThreshold = new NativeArray <float>(Mathf.CeilToInt(expectedPixels.Length / (float)k_BatchSize), Allocator.TempJob)) { if (settings == null) { settings = new ImageComparisonSettings(); } new ComputeDiffJob { expected = expectedPixels, actual = actualPixels, diff = diffPixels, sumOverThreshold = sumOverThreshold, pixelThreshold = settings.PerPixelCorrectnessThreshold }.Schedule(expectedPixels.Length, k_BatchSize).Complete(); float averageDeltaE = sumOverThreshold.Sum() / (expected.width * expected.height); try { Assert.That(averageDeltaE, Is.LessThanOrEqualTo(settings.AverageCorrectnessThreshold)); } catch (AssertionException) { var diffImage = new Texture2D(expected.width, expected.height, TextureFormat.RGB24, false); var diffPixelsArray = new Color32[expected.width * expected.height]; diffPixels.CopyTo(diffPixelsArray); diffImage.SetPixels32(diffPixelsArray, 0); diffImage.Apply(false); TestContext.CurrentContext.Test.Properties.Set("DiffImage", Convert.ToBase64String(diffImage.EncodeToPNG())); failedImageMessage.DiffImage = diffImage.EncodeToPNG(); failedImageMessage.ExpectedImage = expected.EncodeToPNG(); throw; } } } catch (AssertionException) { failedImageMessage.ActualImage = actual.EncodeToPNG(); #if UNITY_EDITOR ImageHandler.instance.SaveImage(failedImageMessage); #else PlayerConnection.instance.Send(FailedImageMessage.MessageId, failedImageMessage.Serialize()); #endif TestContext.CurrentContext.Test.Properties.Set("Image", Convert.ToBase64String(actual.EncodeToPNG())); throw; } }