Files
sbox-public/engine/Sandbox.Test/ShaderGraph/ShaderGraphTests.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

244 lines
8.5 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
namespace ShaderGraph;
/// <summary>
/// Tests for ShaderGraph compilation
/// These tests verify that shader graphs from the tools addon can compile correctly
/// </summary>
[TestClass]
public class ShaderGraphTests
{
private Sandbox.PackageLoader packageLoader;
private Sandbox.PackageLoader.Enroller enroller;
/// <summary>
/// Initialize the package loader and load the tools addon
/// </summary>
private async Task InitializePackageLoader()
{
packageLoader = new Sandbox.PackageLoader( "shadergraph_test", GetType().Assembly );
packageLoader.ToolsMode = true;
enroller = packageLoader.CreateEnroller( "shadergraph_test" );
var project = Project.AddFromFile( "addons/tools/.sbproj" );
await Project.SyncWithPackageManager();
await Project.CompileAsync();
enroller.LoadPackage( project.Package.FullIdent, true );
packageLoader.Tick();
var loadedAssemblies = enroller.GetLoadedAssemblies();
Assert.IsTrue( loadedAssemblies.Length > 0, "No assemblies were loaded from the tools addon" );
}
/// <summary>
/// Find and return the required ShaderGraph types from loaded assemblies
/// </summary>
private (Type shaderGraphType, Type resultNodeType, Type graphCompilerType) FindShaderGraphTypes()
{
var loadedAssemblies = enroller.GetLoadedAssemblies();
Type shaderGraphType = null;
Type resultNodeType = null;
Type graphCompilerType = null;
foreach ( var loadedAssembly in loadedAssemblies )
{
var types = loadedAssembly.Assembly.GetTypes();
foreach ( var type in types )
{
if ( type.Name == "ShaderGraph" && type.Namespace == "Editor.ShaderGraph" )
shaderGraphType = type;
else if ( type.Name == "Result" && type.Namespace == "Editor.ShaderGraph" )
resultNodeType = type;
else if ( type.Name == "GraphCompiler" && type.Namespace == "Editor.ShaderGraph" )
graphCompilerType = type;
}
}
Assert.IsNotNull( shaderGraphType, "ShaderGraph type not found" );
Assert.IsNotNull( resultNodeType, "Result node type not found" );
Assert.IsNotNull( graphCompilerType, "GraphCompiler type not found" );
return (shaderGraphType, resultNodeType, graphCompilerType);
}
/// <summary>
/// Create a minimal shader graph with a result node
/// </summary>
private object CreateMinimalShaderGraph( Type shaderGraphType, Type resultNodeType, string description )
{
var shaderGraph = Activator.CreateInstance( shaderGraphType );
Assert.IsNotNull( shaderGraph, "Failed to create ShaderGraph instance" );
// Set basic properties
var descriptionProperty = shaderGraphType.GetProperty( "Description" );
descriptionProperty?.SetValue( shaderGraph, description );
// Create a Result node
var resultNode = Activator.CreateInstance( resultNodeType );
Assert.IsNotNull( resultNode, "Failed to create Result node instance" );
// Add the result node to the graph
var addNodeMethod = shaderGraphType.GetMethod( "AddNode" );
Assert.IsNotNull( addNodeMethod, "AddNode method not found" );
addNodeMethod.Invoke( shaderGraph, new[] { resultNode } );
return shaderGraph;
}
/// <summary>
/// Generate shader code from a shader graph using the GraphCompiler
/// </summary>
private string GenerateShaderCode( object shaderGraph, Type shaderGraphType, Type graphCompilerType )
{
// Create a compiler and generate shader code
var compilerConstructor = graphCompilerType.GetConstructor( new[] { shaderGraphType, typeof( bool ) } );
Assert.IsNotNull( compilerConstructor, "GraphCompiler constructor not found" );
var compiler = compilerConstructor.Invoke( new[] { shaderGraph, false } );
Assert.IsNotNull( compiler, "Failed to create GraphCompiler instance" );
// Generate the shader source code
var generateMethod = graphCompilerType.GetMethod( "Generate" );
Assert.IsNotNull( generateMethod, "Generate method not found" );
var shaderCode = generateMethod.Invoke( compiler, null ) as string;
Assert.IsNotNull( shaderCode, "Shader compilation failed - returned null" );
Assert.IsTrue( shaderCode.Length > 0, "Shader compilation returned empty string" );
return shaderCode;
}
/// <summary>
/// Test that we can create a minimal shader graph and compile it to
/// </summary>
[TestMethod]
public async Task GraphToShader()
{
await InitializePackageLoader();
var (shaderGraphType, resultNodeType, graphCompilerType) = FindShaderGraphTypes();
var shaderGraph = CreateMinimalShaderGraph( shaderGraphType, resultNodeType, "Test shader graph" );
var shaderCode = GenerateShaderCode( shaderGraph, shaderGraphType, graphCompilerType );
// Basic validation that it looks like shader code
Assert.IsTrue( shaderCode.Contains( "HEADER" ), "Generated shader doesn't contain expected HEADER section" );
Assert.IsTrue( shaderCode.Contains( "MainPs" ), "Generated shader doesn't contain expected MainPs function" );
Console.WriteLine( $"Generated shader code length: {shaderCode.Length} characters" );
Console.WriteLine( "First 500 characters of generated shader:" );
Console.WriteLine( shaderCode.Substring( 0, Math.Min( 500, shaderCode.Length ) ) );
}
/// <summary>
/// Test that we can create a shader graph, generate shader code, and compile it using shadercompiler.exe
/// </summary>
[TestMethod]
public async Task ShaderCompilation()
{
await InitializePackageLoader();
var (shaderGraphType, resultNodeType, graphCompilerType) = FindShaderGraphTypes();
var shaderGraph = CreateMinimalShaderGraph( shaderGraphType, resultNodeType, "Test shader graph for unit testing" );
var shaderCode = GenerateShaderCode( shaderGraph, shaderGraphType, graphCompilerType );
// Write the shader code to a file in the game directory
var gameDir = Directory.GetCurrentDirectory();
var shadersDir = Path.Combine( gameDir, "core", "shaders" );
Directory.CreateDirectory( shadersDir );
var shaderFileName = "test_shadergraph_unit_test.shader";
var shaderPath = Path.Combine( shadersDir, shaderFileName );
File.WriteAllText( shaderPath, shaderCode );
Console.WriteLine( $"Wrote shader to: {shaderPath}" );
// Find shadercompiler.exe
var shaderCompilerPath = Path.Combine( gameDir, "bin", "managed", "shadercompiler.exe" );
Assert.IsTrue( File.Exists( shaderCompilerPath ), $"Shader compiler not found at: {shaderCompilerPath}" );
// Run shadercompiler.exe on this specific shadershader
var success = await RunShaderCompiler( shaderCompilerPath, gameDir, shaderPath );
Assert.IsTrue( success, "Shader compilation with shadercompiler.exe failed" );
// Check if shader_c file was created
var shaderCPath = shaderPath + "_c";
Assert.IsTrue( File.Exists( shaderCPath ), $"Compiled shader file not found at: {shaderCPath}" );
var shaderCSize = new FileInfo( shaderCPath ).Length;
Assert.IsTrue( shaderCSize > 0, "Compiled shader file is empty" );
Console.WriteLine( $"Successfully compiled shader to {shaderCPath} ({shaderCSize} bytes)" );
// Clean up
try
{
File.Delete( shaderPath );
File.Delete( shaderCPath );
}
catch ( Exception ex )
{
Console.WriteLine( $"Failed to clean up temp files: {ex.Message}" );
}
}
private async Task<bool> RunShaderCompiler( string shaderCompilerPath, string workingDirectory, string shaderFile )
{
using ( var process = new Process() )
{
process.StartInfo.FileName = shaderCompilerPath;
process.StartInfo.Arguments = shaderFile; // Just compile this specific shader
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.WorkingDirectory = workingDirectory;
var outputMessages = new List<string>();
var errorMessages = new List<string>();
process.OutputDataReceived += ( sender, e ) =>
{
if ( e.Data != null )
{
outputMessages.Add( e.Data );
Console.WriteLine( $"ShaderCompiler: {e.Data}" );
}
};
process.ErrorDataReceived += ( sender, e ) =>
{
if ( e.Data != null )
{
errorMessages.Add( e.Data );
Console.WriteLine( $"ShaderCompiler ERROR: {e.Data}" );
}
};
Console.WriteLine( $"Running: {shaderCompilerPath} {process.StartInfo.Arguments}" );
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
await process.WaitForExitAsync();
if ( process.ExitCode != 0 )
{
Console.WriteLine( $"Shader compiler failed with exit code: {process.ExitCode}" );
foreach ( var error in errorMessages )
{
Console.WriteLine( $"Error: {error}" );
}
return false;
}
return true;
}
}
}