104 lines
5.5 KiB
C#
104 lines
5.5 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using NUnit.Framework;
|
|
using UnityEditor.Rendering.Universal.ShaderGraph;
|
|
using UnityEditor.ShaderGraph;
|
|
|
|
namespace UnityEditor.Rendering.Universal.Tests
|
|
{
|
|
[TestFixture]
|
|
class UniversalTerrainLitCodeGenTests
|
|
{
|
|
internal enum VertexContext
|
|
{
|
|
WithPositionBlock,
|
|
Empty,
|
|
}
|
|
|
|
// TerrainLit emits the primary shader plus the hidden _AddPass and _BaseMapGen shaders, so
|
|
// assertions have to cover all of them, not just Generator.generatedShader.
|
|
static List<(string name, string code)> GenerateShaders(VertexContext vertexContext, GenerationMode mode)
|
|
{
|
|
var target = new UniversalTarget();
|
|
Assert.IsTrue(target.TrySetActiveSubTarget(typeof(UniversalTerrainLitSubTarget)),
|
|
"TerrainLit subtarget is not registered on UniversalTarget");
|
|
|
|
var blocks = vertexContext == VertexContext.WithPositionBlock
|
|
? new[] { BlockFields.VertexDescription.Position, BlockFields.SurfaceDescription.BaseColor }
|
|
: new[] { BlockFields.SurfaceDescription.BaseColor };
|
|
|
|
var graph = new GraphData();
|
|
graph.AddContexts();
|
|
graph.InitializeOutputs(new Target[] { target }, blocks);
|
|
|
|
// outputNode stays null for a non-subgraph. A non-null one would make Generator swap in
|
|
// PreviewTarget and drop the terrain subtarget entirely.
|
|
var shaders = new Generator(graph, graph.outputNode, mode, "TerrainLitCodeGen").allGeneratedShaders
|
|
.Select(shader => (shader.shaderName, shader.codeString))
|
|
.ToList();
|
|
|
|
Assert.IsNotEmpty(shaders, "TerrainLit generated no shaders");
|
|
|
|
// The template preprocessor treats '$' as a command marker even inside a comment, and on a
|
|
// malformed directive it splices a marker into the shader instead of failing. Without this
|
|
// check a broken template would leave the assertions below passing on text that is wrong.
|
|
// Every marker is a comment line carrying a caret under the offending column. Matching the
|
|
// caret rather than the word ERROR also catches Expect(), which reports "^ Expected ')'".
|
|
foreach (var (name, code) in shaders)
|
|
StringAssert.DoesNotMatch(@"(?m)^\s*//\s*\^ ", code, $"template preprocessor error in {name}");
|
|
|
|
return shaders;
|
|
}
|
|
|
|
// Asserting mere presence would be satisfied by a single pass out of fifteen, so a new terrain
|
|
// pass whose template forgot the include would slip through. Every terrain pass emits exactly one
|
|
// UNIVERSAL_TERRAIN_ENABLED define and should carry exactly one copy of the function.
|
|
static void AssertDefinedInEveryTerrainPass(string function, VertexContext vertexContext, GenerationMode mode)
|
|
{
|
|
foreach (var (name, code) in GenerateTerrainShaders(vertexContext, mode))
|
|
{
|
|
var passes = Regex.Matches(code, @"#define UNIVERSAL_TERRAIN_ENABLED 1").Count;
|
|
var definitions = Regex.Matches(code, Regex.Escape(function) + @"\(").Count;
|
|
Assert.AreEqual(passes, definitions, $"{function} in {name}");
|
|
}
|
|
}
|
|
|
|
// Preview generation emits the _AddPass shader with no passes at all, and a shader with no
|
|
// terrain pass has nothing to define. Key off the define the terrain passes themselves add.
|
|
static List<(string name, string code)> GenerateTerrainShaders(VertexContext vertexContext, GenerationMode mode)
|
|
{
|
|
var shaders = GenerateShaders(vertexContext, mode)
|
|
.Where(shader => shader.code.Contains("UNIVERSAL_TERRAIN_ENABLED"))
|
|
.ToList();
|
|
|
|
Assert.IsNotEmpty(shaders, "no generated shader turns on the terrain path");
|
|
return shaders;
|
|
}
|
|
|
|
// Varyings.hlsl calls TerrainVaryingGeneration outside of any FEATURES_GRAPH_VERTEX guard, so the
|
|
// terrain passes have to emit it even when the graph's Vertex context is empty. That state is one
|
|
// Delete away: TerrainLit's only active vertex block is Position, it is unconnected by default, and
|
|
// removing it does not trigger the auto re-add. Gating the definition on graphVertex breaks every pass.
|
|
[TestCase(VertexContext.WithPositionBlock, GenerationMode.ForReals)]
|
|
[TestCase(VertexContext.Empty, GenerationMode.ForReals)]
|
|
[TestCase(VertexContext.WithPositionBlock, GenerationMode.Preview)]
|
|
[TestCase(VertexContext.Empty, GenerationMode.Preview)]
|
|
public void TerrainShadersDefineTerrainVaryingGeneration(VertexContext vertexContext, GenerationMode mode)
|
|
{
|
|
AssertDefinedInEveryTerrainPass("TerrainVaryingGeneration", vertexContext, mode);
|
|
}
|
|
|
|
// Same for ConstructTerrainTangent, which the forward and gbuffer passes call under
|
|
// ENABLE_TERRAIN_PERPIXEL_NORMAL.
|
|
[TestCase(VertexContext.WithPositionBlock, GenerationMode.ForReals)]
|
|
[TestCase(VertexContext.Empty, GenerationMode.ForReals)]
|
|
[TestCase(VertexContext.WithPositionBlock, GenerationMode.Preview)]
|
|
[TestCase(VertexContext.Empty, GenerationMode.Preview)]
|
|
public void TerrainShadersDefineConstructTerrainTangent(VertexContext vertexContext, GenerationMode mode)
|
|
{
|
|
AssertDefinedInEveryTerrainPass("ConstructTerrainTangent", vertexContext, mode);
|
|
}
|
|
}
|
|
}
|