using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using System.IO; using UnityEditor; using NUnit.Framework; using UnityEngine.TestTools; namespace UnityEngine.Experimental.Rendering { public class TestFrameworkTools { public static int compareTileSize = 32; public static float compareThreshold = 0.01f; public static int frameWait = 100; public enum ComparisonMethod {RMSE, Jzazbz, Lab} public static ComparisonMethod comparisonMethod = ComparisonMethod.Lab; public static readonly string s_RootPath = Directory.GetParent(Directory.GetFiles(Application.dataPath, "SRPMARKER", SearchOption.AllDirectories).First()).ToString(); // path where the tests live public static string[] s_Path = { "Tests", "GraphicsTests", "RenderPipeline" }; public static Dictionary renderPipelineAssets = new Dictionary() { { "HDRP", "HDRenderPipeline/CommonAssets/RP_Assets/HDRP_Test_Def.asset" }, { "LWRP", "LightweightPipeline/LightweightPipelineAsset.asset" } }; // Renderpipeline assets used for the tests public static Dictionary renderPipelineScenesFolder = new Dictionary() { { "HDRP", "HDRenderPipeline/Scenes" }, { "LWRP", "LightweightPipeline/Scenes" } }; // info that gets generated for use // in a dod way public struct TestInfo { public string name; public string comment; public float threshold; public string relativePath; public string templatePath; public int frameWait; public int sceneListIndex; public override string ToString() { if (string.IsNullOrEmpty(comment)) return name; else return string.Format("{0}: {1}", name, comment); } } // Get additionalSceneInfo public static Dictionary GetAdditionalInfos ( string path) { Dictionary o = new Dictionary(); AdditionalTestSceneInfos additionalTestSceneInfos = AssetDatabase.LoadAssetAtPath(path); if (additionalTestSceneInfos != null) { for (int i=0 ; i allPaths_List = new List(allPaths); allPaths_List.Sort(); // Get the play mode scenes List playModeScenes = new List(); foreach( TestInfo ti in CollectScenesPlayMode.GetScenesForPipeline( _pipelinePath ) ) { playModeScenes.Add(ti.templatePath); } // Get the additional infos var additionalInfos = GetAdditionalInfos( "Assets"+Path.Combine(filesPath.Replace(Application.dataPath, ""), "AdditionalTestSceneInfos.asset") ); // construct all the needed test infos for (int i = 0; i < allPaths_List.Count; ++i) { var path = allPaths_List[i]; var p = new FileInfo(path); var split = s_Path.Aggregate("", Path.Combine); split = string.Format("{0}{1}", split, Path.DirectorySeparatorChar); var splitPaths = p.FullName.Split(new[] { split }, StringSplitOptions.RemoveEmptyEntries); // Filter out play mode tests from the list if (playModeScenes.Contains(splitPaths.Last())) continue; string sceneNum = p.Name.Split("_"[0])[0]; TestInfo testInfo = new TestInfo() { name = p.Name, comment = additionalInfos.ContainsKey(sceneNum)? additionalInfos[sceneNum].comment:null, relativePath = splitPaths.Last(), templatePath = splitPaths.Last(), threshold = compareThreshold, frameWait = frameWait }; if (fixtureParam) yield return new TestFixtureData(testInfo); else yield return testInfo; } } } public static class CollectScenesPlayMode { public static IEnumerable HDRP { get { return GetScenesForPipelineID("HDRP"); } } public static IEnumerable HDRP_Param { get { return GetScenesForPipelineID("HDRP", true); } } public static IEnumerable LWRP { get { return GetScenesForPipelineID("LWRP"); } } public static IEnumerable GetScenesForPipelineID(string _pipelineID, bool fixtureParam = false) { return GetScenesForPipeline(renderPipelineScenesFolder[_pipelineID]); } public static IEnumerable GetScenesForPipeline(string _pipelinePath, bool fixtureParam = false) { #if UNITY_EDITOR string absoluteScenesPath = s_Path.Aggregate(s_RootPath, Path.Combine); string assetScenesPath = absoluteScenesPath.Substring(absoluteScenesPath.IndexOf("Assets")); string filesPath = Path.Combine(assetScenesPath, _pipelinePath); string listFilePath = Path.Combine(filesPath, "EditorPlayModeTests.asset"); EditorPlayModeTests listFile = (EditorPlayModeTests) AssetDatabase.LoadMainAssetAtPath(listFilePath); if ( listFile == null) { AssetDatabase.CreateAsset(ScriptableObject.CreateInstance(), listFilePath); AssetDatabase.Refresh(); yield return null; } else { // Get the additional infos var additionalInfos = GetAdditionalInfos( Path.Combine(filesPath, "AdditionalTestSceneInfos.asset") ); for ( int i=0 ; i threshold) return false; } } return true; /* int numberOfPixels = pixels1.Length; float sumOfSquaredColorDistances = 0; for (int i = 0; i < numberOfPixels; i++) { Color p1 = pixels1[i]; Color p2 = pixels2[i]; Color diff = p1 - p2; diff = diff * diff; sumOfSquaredColorDistances += (diff.r + diff.g + diff.b) / 3.0f; } float rmse = Mathf.Sqrt(sumOfSquaredColorDistances / numberOfPixels); return rmse < threshold; */ } public static Texture2D RenderSetupToTexture( SetupSceneForRenderPipelineTest _testSetup) { // Setup Render Target Camera testCamera = _testSetup.cameraToUse; var rtDesc = new RenderTextureDescriptor( _testSetup.width, _testSetup.height, (_testSetup.hdr && testCamera.allowHDR) ? RenderTextureFormat.ARGBHalf : RenderTextureFormat.ARGB32, 24); #if UNITY_EDITOR rtDesc.sRGB = PlayerSettings.colorSpace == ColorSpace.Linear; #endif rtDesc.msaaSamples = _testSetup.msaaSamples; // render the scene var tempTarget = RenderTexture.GetTemporary(rtDesc); var oldTarget = _testSetup.cameraToUse.targetTexture; _testSetup.cameraToUse.targetTexture = tempTarget; _testSetup.cameraToUse.Render(); _testSetup.cameraToUse.targetTexture = oldTarget; // Readback the rendered texture var oldActive = RenderTexture.active; RenderTexture.active = tempTarget; var captured = new Texture2D(tempTarget.width, tempTarget.height, TextureFormat.RGB24, false); captured.ReadPixels(new Rect(0, 0, _testSetup.width, _testSetup.height), 0, 0); RenderTexture.active = oldActive; return captured; } public static bool FindReferenceImage(TestInfo _testInfo, ref Texture2D _fromDisk, Texture2D _captured, ref string _dumpFileLocation) { var templatePath = Path.Combine(s_RootPath, "ImageTemplates"); // find the reference image _dumpFileLocation = Path.Combine(templatePath, string.Format("{0}.{1}", _testInfo.templatePath, "png")); //Debug.Log("Template file at: " + _dumpFileLocation); if (!File.Exists(_dumpFileLocation)) { // no reference exists, create it var fileInfo = new FileInfo(_dumpFileLocation); fileInfo.Directory.Create(); var generated = _captured.EncodeToPNG(); File.WriteAllBytes(_dumpFileLocation, generated); return false; } var template = File.ReadAllBytes(_dumpFileLocation); _fromDisk.LoadImage(template, false); return true; } public static Texture2D GetTemplateImage(string _templatePath) { string templatePath = Path.Combine(s_RootPath, "ImageTemplates"); templatePath = Path.Combine(templatePath, string.Format("{0}.{1}", _templatePath, "png")); if (File.Exists(templatePath)) { byte[] template = File.ReadAllBytes(templatePath); Texture2D o = new Texture2D(4, 4); return o.LoadImage(template, false) ? o : null; } else return null; } public static Texture2D GetTemplateImage(TestInfo _testInfo) { return GetTemplateImage(_testInfo.templatePath); } #if UNITY_EDITOR public static Texture2D GetTemplateImage(UnityEngine.Object _sceneAsset, ref string path) { string _scenePath = AssetDatabase.GetAssetPath(_sceneAsset); var p = new FileInfo(_scenePath); var split = s_Path.Aggregate("", Path.Combine); split = string.Format("{0}{1}", split, Path.DirectorySeparatorChar); var splitPaths = p.FullName.Split(new[] { split }, StringSplitOptions.RemoveEmptyEntries); path = splitPaths.Last(); TestInfo testInfo = new TestInfo { name = p.Name, relativePath = p.ToString(), templatePath = splitPaths.Last(), threshold = 0.02f, frameWait = 100, }; return GetTemplateImage(testInfo); } #endif public static class AssertFix { public static void TestWithMessages( bool? _comparison, string _fail = "Test failed", string _pass = null ) { if (_comparison.HasValue) { if (_comparison.Value) NUnit.Framework.Assert.IsTrue(true, _pass); else throw new System.Exception(_fail); } else throw new System.Exception("Test comparison is null."); } } // Folowing color conversion code source : https://github.com/nschloe/colorio // RGB to XYZ 100 : file:///C:/Users/Remy/Downloads/srgb.pdf static Vector3 RGB2XYZ ( Color color ) { return new Vector3( color.r * 0.4124564f + color.g * 0.3575761f + color.b * 0.1804375f, color.r * 0.2126729f + color.g * 0.7151522f + color.b * 0.0721750f, color.r * 0.0193339f + color.g * 0.1191920f + color.b * 0.9503041f ) * 100; } // JzAzBz color conversion : https://www.osapublishing.org/oe/fulltext.cfm?uri=oe-25-13-15131&id=368272 static Vector3 RGB2JzAzBz (Color color) { Vector3 xyz = RGB2XYZ( color); float b = 1.15f; float g = 0.66f; float c1 = 0.8359375f; // 3424f / 2^12 float c2 = 18.8515625f; // 2413f / 2^7 float c3 = 18.6875f; // 2392f / 2^7 float n = 0.15930175781f; // 2610/2^14 float p= 134.034375f; // 1.7*2523/2^5 float d = -0.56f; float d0 = 1.6295499532821566E-11f; float x2 = b * xyz.x - (b-1) * xyz.z; float y2 = g * xyz.y - (g-1) * xyz.x; Vector3 lms = new Vector3( 0.41478372f * x2 + 0.579999f * y2 + 0.0146480f * xyz.z, -0.2015100f * x2 + 1.120649f * y2 + 0.0531008f * xyz.z, -0.0166008f * x2 + 0.264800f * y2 + 0.6684799f * xyz.z ); Vector3 lmsPowN = Vec3Pow(lms/10000f, n); Vector3 tmp = Vec3Divide( Vector3.one * c1 + c2 * lmsPowN , Vector3.one + c3 * lmsPowN ); Vector3 lms2 = Vec3Pow( tmp , p ) ; Vector3 jab = new Vector3( 0.5f * lms2.x + 0.5f * lms2.y, 3.524000f * lms2.x + -4.066708f * lms2.y + 0.542708f * lms2.z, 0.199076f * lms2.x + 1.096799f * lms2.y + -1.295875f * lms2.z ); jab.x = (((1f+d)*jab.x)/(1f+d*jab.x))-d0; return jab; } static float JzAzBzDiff( Vector3 v1, Vector3 v2) { float c1 = Mathf.Sqrt(v1.y*v1.y + v1.z*v1.z); float c2 = Mathf.Sqrt(v2.y*v2.y + v2.z*v2.z); float h1 = Mathf.Atan(v1.z/v1.y); float h2 = Mathf.Atan(v2.z/v2.y); float deltaH = 2*Mathf.Sqrt( c1*c2 ) * Mathf.Sin((h1-h2)/2f); return Mathf.Sqrt( Mathf.Pow( v1.x-v2.x ,2f) + Mathf.Pow(c1-c2, 2f) + deltaH * deltaH ); } static Vector3 RGB2Lab( Color color ) { Vector3 xyz = RGB2XYZ( color); float xn = 95.047f; float yn = 100f; float zn = 108.883f; return new Vector3( 116f * XYZ2LabFunc( xyz.y / yn ) - 16f, 500f * ( XYZ2LabFunc(xyz.x / xn) - XYZ2LabFunc(xyz.y/yn) ), 200f * (XYZ2LabFunc(xyz.y / yn) - XYZ2LabFunc(xyz.z / zn)) ); } static float XYZ2LabFunc( float f ) { float delta = 6f/29f; if ( f > delta ) return Mathf.Pow(f, 1f/3f); else return f/(3*delta*delta) + 4f / 29f; } static Vector3 Vec3Pow (Vector3 v, float p) { return new Vector3( Mathf.Pow(v.x, p), Mathf.Pow(v.y, p), Mathf.Pow(v.z, p) ); } static Vector3 Vec3Divide(Vector3 a, Vector3 b ) { return new Vector3(a.x/b.x, a.y/b.y, a.z/b.z); } } }