using System.Linq; using System.IO; using NUnit.Framework; using UnityEngine; using UnityEditor.Graphing; namespace UnityEditor.ShaderGraph.UnitTests { [TestFixture] class TargetTests { [OneTimeSetUp] public void RunBeforeAnyTests() { Debug.unityLogger.logHandler = new ConsoleLogHandler(); } [Test] public void CanCreateBlankGraph() { GraphData graph = new GraphData(); graph.AddContexts(); Assert.IsNotNull(graph.activeTargets); Assert.AreEqual(0, graph.activeTargets.Count()); } public static bool s_ForceVFXFakeTargetVisible = false; #if !VFX_GRAPH_10_0_0_OR_NEWER //A barebone VFXTarget for testing coverage. sealed class VFXTarget : UnityEditor.ShaderGraph.Target { public VFXTarget() { displayName = "Fake VFX Target"; //Should not be displayed outside the test runner. isHidden = !s_ForceVFXFakeTargetVisible; } public override void GetActiveBlocks(ref UnityEditor.ShaderGraph.TargetActiveBlockContext context) { context.AddBlock(ShaderGraph.BlockFields.SurfaceDescription.BaseColor); context.AddBlock(ShaderGraph.BlockFields.SurfaceDescription.Alpha); } public override void GetFields(ref TargetFieldContext context) { } public override void Setup(ref TargetSetupContext context) { } public override bool IsActive() => false; public override bool WorksWithSRP(UnityEngine.Rendering.RenderPipelineAsset scriptableRenderPipeline) { return false; } public override void GetPropertiesGUI(ref TargetPropertyGUIContext context, System.Action onChange, System.Action registerUndo) { } } #endif [Test] public void CanInitializeOutputTargets() { s_ForceVFXFakeTargetVisible = true; GraphData graph = new GraphData(); graph.AddContexts(); graph.InitializeOutputs(new[] { new VFXTarget() }, null); Assert.IsNotNull(graph.activeTargets); Assert.AreEqual(1, graph.activeTargets.Count()); Assert.AreEqual(typeof(VFXTarget), graph.activeTargets.ElementAt(0).GetType()); s_ForceVFXFakeTargetVisible = false; } [Test] public void CanAddTarget() { s_ForceVFXFakeTargetVisible = true; GraphData graph = new GraphData(); graph.AddContexts(); var vfxTarget = graph.allPotentialTargets.FirstOrDefault(x => x is VFXTarget); graph.SetTargetActive(vfxTarget); Assert.IsNotNull(graph.activeTargets); Assert.AreEqual(1, graph.activeTargets.Count()); Assert.AreEqual(vfxTarget, graph.activeTargets.ElementAt(0)); s_ForceVFXFakeTargetVisible = false; } [Test] public void ActiveTargetsArePotentialTargets() { s_ForceVFXFakeTargetVisible = true; GraphData graph = new GraphData(); graph.AddContexts(); var vfxTarget = new VFXTarget(); graph.SetTargetActive(vfxTarget); Assert.IsTrue(graph.allPotentialTargets.Contains(vfxTarget)); s_ForceVFXFakeTargetVisible = false; } [Test] public void GetTargetIndexWorks() { s_ForceVFXFakeTargetVisible = true; GraphData graph = new GraphData(); graph.AddContexts(); int targetIndex = graph.GetTargetIndexByKnownType(typeof(VFXTarget)); Assert.IsTrue(targetIndex >= 0); var vfxTarget = new VFXTarget(); graph.SetTargetActive(vfxTarget); var targetIndex2 = graph.GetTargetIndex(vfxTarget); Assert.AreEqual(targetIndex, targetIndex2); var nonActiveVFXTarget = new VFXTarget(); Assert.AreEqual(-1, graph.GetTargetIndex(nonActiveVFXTarget)); s_ForceVFXFakeTargetVisible = false; } [Test] public void CanRemoveTarget() { s_ForceVFXFakeTargetVisible = true; GraphData graph = new GraphData(); graph.AddContexts(); var vfxTarget = new VFXTarget(); graph.InitializeOutputs(new[] { vfxTarget }, null); graph.SetTargetInactive(vfxTarget); Assert.IsNotNull(graph.activeTargets); Assert.AreEqual(0, graph.activeTargets.Count()); s_ForceVFXFakeTargetVisible = false; } [Test] public void CanSetBlockActive() { s_ForceVFXFakeTargetVisible = true; GraphData graph = new GraphData(); graph.AddContexts(); graph.InitializeOutputs(new[] { new VFXTarget() }, new BlockFieldDescriptor[] { BlockFields.SurfaceDescription.BaseColor, BlockFields.SurfaceDescription.NormalTS }); // Block active state should match VFX Target's default GetActiveBlocks var blocks = graph.GetNodes().ToList(); Assert.AreEqual(2, blocks.Count); Assert.AreEqual(BlockFields.SurfaceDescription.BaseColor, blocks[0].descriptor); Assert.AreEqual(true, blocks[0].isActive); Assert.AreEqual(BlockFields.SurfaceDescription.NormalTS, blocks[1].descriptor); Assert.AreEqual(false, blocks[1].isActive); s_ForceVFXFakeTargetVisible = false; } [Test] public void CanUpdateBlockActiveState() { s_ForceVFXFakeTargetVisible = true; GraphData graph = new GraphData(); graph.AddContexts(); graph.InitializeOutputs(new[] { new VFXTarget() }, new BlockFieldDescriptor[] { BlockFields.SurfaceDescription.BaseColor, BlockFields.SurfaceDescription.NormalTS }); // Remove VFX target var vfxTarget = graph.allPotentialTargets.FirstOrDefault(x => x is VFXTarget); graph.SetTargetInactive(vfxTarget); var activeBlocks = graph.GetActiveBlocksForAllActiveTargets(); graph.UpdateActiveBlocks(activeBlocks); // All blocks should be inactive as there are no active targets var blocks = graph.GetNodes().ToList(); Assert.AreEqual(2, blocks.Count); Assert.AreEqual(BlockFields.SurfaceDescription.BaseColor, blocks[0].descriptor); Assert.AreEqual(false, blocks[0].isActive); Assert.AreEqual(BlockFields.SurfaceDescription.NormalTS, blocks[1].descriptor); Assert.AreEqual(false, blocks[1].isActive); s_ForceVFXFakeTargetVisible = false; } sealed class MultiShaderTarget : UnityEditor.ShaderGraph.Target { public MultiShaderTarget() { displayName = "MultiShader Test Target"; isHidden = false; } public override void GetActiveBlocks(ref UnityEditor.ShaderGraph.TargetActiveBlockContext context) { context.AddBlock(ShaderGraph.BlockFields.SurfaceDescription.BaseColor); context.AddBlock(ShaderGraph.BlockFields.SurfaceDescription.Alpha); } public override void GetFields(ref TargetFieldContext context) { } public static PassDescriptor BuildPass() { PassDescriptor pass = new PassDescriptor() { // Definition displayName = "DepthOnly", referenceName = "SHADERPASS_DEPTHONLY", lightMode = "DepthOnly", useInPreview = true, // Template passTemplatePath = "Packages/com.unity.shadergraph/Editor/Generation/Targets/BuiltIn/Editor/ShaderGraph/Templates/ShaderPass.template", sharedTemplateDirectories = GenerationUtils.GetDefaultSharedTemplateDirectories(), // Port Mask validVertexBlocks = new BlockFieldDescriptor[] { BlockFields.VertexDescription.Position, BlockFields.VertexDescription.Normal, BlockFields.VertexDescription.Tangent }, validPixelBlocks = new BlockFieldDescriptor[] { BlockFields.SurfaceDescription.Alpha, BlockFields.SurfaceDescription.AlphaClipThreshold }, // Fields structs = new StructCollection { { Structs.Attributes }, { Structs.SurfaceDescriptionInputs }, { Structs.VertexDescriptionInputs } }, fieldDependencies = FieldDependencies.Default, // Conditional State renderStates = null, pragmas = new PragmaCollection { { Pragma.Target(ShaderModel.Target30) }, { Pragma.MultiCompileInstancing }, { Pragma.Vertex("vert") }, { Pragma.Fragment("frag") } }, defines = null, keywords = null, includes = null, customInterpolators = null, }; return pass; } public static SubShaderDescriptor BuildSubShader(string additionaShaderID) { SubShaderDescriptor result = new SubShaderDescriptor() { pipelineTag = "TestPipeline", renderType = "Opaque", renderQueue = "Geometry", generatesPreview = true, passes = new PassCollection(), additionalShaderID = additionaShaderID }; result.passes.Add(BuildPass()); return result; } public override void Setup(ref TargetSetupContext context) { var ss = BuildSubShader(null); // primary shader var ss2 = BuildSubShader("{Name}-second"); context.AddSubShader(ss); context.AddSubShader(ss2); } public override bool IsActive() => true; public override bool WorksWithSRP(UnityEngine.Rendering.RenderPipelineAsset scriptableRenderPipeline) { return true; } public override void GetPropertiesGUI(ref TargetPropertyGUIContext context, System.Action onChange, System.Action registerUndo) { } } [Test] public void CanBuildMultipleShaders() { GraphData graph = new GraphData(); graph.AddContexts(); var multiTarget = new MultiShaderTarget(); graph.SetTargetActive(multiTarget); Assert.IsTrue(graph.allPotentialTargets.Contains(multiTarget)); graph.OnEnable(); graph.ValidateGraph(); var generator = new Generator(graph, graph.outputNode, GenerationMode.ForReals, "MyTestShader"); var generatedShaders = generator.allGeneratedShaders.ToList(); Assert.AreEqual(2, generatedShaders.Count); Assert.IsTrue(generatedShaders[0].shaderName == "MyTestShader"); Assert.IsTrue(generatedShaders[1].shaderName == "MyTestShader-second"); Assert.IsTrue(generatedShaders[0].codeString.Contains("Shader \"MyTestShader\"")); Assert.IsTrue(generatedShaders[1].codeString.Contains("Shader \"MyTestShader-second\"")); // save graph to file on disk and import it... var path = AssetDatabase.GenerateUniqueAssetPath("Assets/multiShaderTest.ShaderGraph"); FileUtilities.WriteShaderGraphToDisk(path, graph); AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate | ImportAssetOptions.DontDownloadFromCacheServer); // check that we actually have two shader assets in the import result // (they won't work, but they should exist) var assets = AssetDatabase.LoadAllAssetsAtPath(path); int shaderCount = assets.OfType().Count(); Assert.AreEqual(2, shaderCount); AssetDatabase.DeleteAsset(path); } } }