Files
2025-05-07 15:07:44 +08:00

206 lines
6.4 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
// Shapes © Freya Holmér - https://twitter.com/FreyaHolmer/
// Website & Documentation - https://acegikmo.com/shapes/
namespace Shapes {
public static class CodegenMpbs {
static CodeWriter code;
static Dictionary<string, string> propToVarName;
const string SIMPLE_TRANSFER = "protected override void TransferShapeProperties() => _ = 0; // :>";
static string[] excludedShaders = { "Texture Core" }; // manually set up
public static void Generate() {
// load core files
TextAsset[] coreShaders = ShapesIO.LoadCoreShaders()
.Where( t => excludedShaders.Contains( t.name ) == false )
.OrderBy( x => x.name )
.ToArray();
// Dictionary for (_X) to (ShapesMaterialUtils.propX)
propToVarName = LoadPropertyNames();
// generate code
code = new CodeWriter();
using( code.MainScope( typeof(CodegenMpbs), "System.Collections.Generic", "UnityEngine" ) ) {
// main shaders
coreShaders.ForEach( GenerateMpbClass );
// text is a special case
code.Spacer();
using( code.Scope( "internal class MpbText : MetaMpb" ) )
code.Append( SIMPLE_TRANSFER );
}
// save
string path = ShapesIO.RootFolder + "/Scripts/Runtime/Immediate Mode/MetaMpbShapes.cs";
code.WriteTo( path );
}
static Dictionary<string, string> LoadPropertyNames() {
var dict = new Dictionary<string, string>();
string path = ShapesIO.RootFolder + "/Scripts/Runtime/Utils/ShapesMaterialUtils.cs";
// manually parse this out I guess~
foreach( string s in File.ReadAllLines( path ) ) {
if( s.TrimStart().StartsWith( "public static readonly int prop" ) ) {
string propNameVar = s.Between( "readonly int ", " = Shader.PropertyToID" );
string propNameShader = s.Between( "ToID( \"", "\" );" );
dict[propNameShader] = propNameVar;
}
}
return dict;
}
// scuffed regex~
static string Between( this string s, string pre, string post ) => s.Split( new[] { pre }, StringSplitOptions.None )[1].Split( new[] { post }, StringSplitOptions.None )[0].Trim();
struct PropertyVar {
public PropertyVar( string type, string nameShaderProp ) {
this.type = type;
this.nameShaderProp = nameShaderProp;
this.nameCsVar = char.ToLower( nameShaderProp[1] ) + nameShaderProp.Substring( 2 );
this.nameCsVarProp = propToVarName[nameShaderProp];
}
public string type; // Vector4
public string nameShaderProp; // _Rect
public string nameCsVar; // rect
public string nameCsVarProp; // ShapesMaterialUtils.propRect
}
struct Property {
public Property( string type, string name ) {
this.type = type;
this.name = name;
if( type == "Single" )
this.type = "float";
}
public string type; // Vector4
public string name; // _Rect
}
static List<Property> GetInterfaceListProperties<T>() {
return typeof(T).GetProperties()
.Select( p => new Property( p.PropertyType.GenericTypeArguments[0].Name, p.Name ) )
.OrderBy( p => p.name ).ToList();
}
static void GenerateMpbClass( TextAsset coreFile ) {
string path = AssetDatabase.GetAssetPath( coreFile );
string[] lines = File.ReadAllLines( path );
bool filled = false;
bool dashed = false;
List<PropertyVar> properties = new List<PropertyVar>();
List<Property> propertiesFill = GetInterfaceListProperties<IFillableMpb>();
List<Property> propertiesDash = GetInterfaceListProperties<IDashableMpb>();
// find all properties from the core shader files
bool propStart = false;
foreach( var s in lines ) {
string sTrimmed = s.TrimStart();
if( propStart ) {
if( sTrimmed.StartsWith( "SHAPES_FILL_PROPERTIES" ) ) {
filled = true;
} else if( sTrimmed.StartsWith( "SHAPES_DASH_PROPERTIES" ) ) {
dashed = true;
} else if( sTrimmed.StartsWith( "PROP_DEF(" ) ) {
string contents = s.Between( "_DEF(", ")" );
string[] typeAndName = contents.Split( ',' );
string type = ShaderTypeToCsType( typeAndName[0].Trim() );
string name = typeAndName[1].Trim();
if( name != "_Color" ) // skip global properties
properties.Add( new PropertyVar( type, name ) );
} else if( sTrimmed.StartsWith( "UNITY_INSTANCING_BUFFER_END" ) )
break; // done
} else if( sTrimmed.StartsWith( "UNITY_INSTANCING_BUFFER_START" ) )
propStart = true;
}
string shapeName = coreFile.name.Substring( 0, coreFile.name.Length - " Core".Length );
string className = "Mpb" + shapeName.Replace( " ", "" );
string baseTypes = "MetaMpb";
if( filled ) baseTypes += $", {nameof(IFillableMpb)}";
if( dashed ) baseTypes += $", {nameof(IDashableMpb)}";
properties = properties.OrderBy( p => p.nameCsVar ).ToList();
code.Spacer();
using( code.Scope( $"internal class {className} : {baseTypes}" ) ) {
// shape vars
if( properties.Count > 0 ) {
code.Spacer();
foreach( PropertyVar p in properties )
code.Append( $"internal readonly List<{p.type}> {p.nameCsVar} = InitList<{p.type}>();" );
}
// fill/dash properties
if( filled ) {
code.Spacer();
code.Comment( "fill boilerplate" );
foreach( Property p in propertiesFill )
code.Append( $"List<{p.type}> {nameof(IFillableMpb)}.{p.name} {{ get; }} = InitList<{p.type}>();" );
}
if( dashed ) {
code.Spacer();
code.Comment( "dash boilerplate" );
foreach( Property p in propertiesDash )
code.Append( $"List<{p.type}> {nameof(IDashableMpb)}.{p.name} {{ get; }} = InitList<{p.type}>();" );
}
// transfer function
code.Spacer();
if( properties.Count == 0 ) {
code.Append( "protected override void TransferShapeProperties() => _ = 0; // :>" );
} else {
using( code.Scope( "protected override void TransferShapeProperties()" ) ) {
foreach( PropertyVar p in properties )
code.Append( $"Transfer( {nameof(ShapesMaterialUtils)}.{p.nameCsVarProp}, {p.nameCsVar} );" );
}
}
code.Spacer();
}
}
static string ShaderTypeToCsType( string t ) {
switch( t ) {
case "int":
case "half":
case "fixed":
case "float":
return "float"; // SetFloat()
case "int2":
case "half2":
case "fixed2":
case "float2":
case "int3":
case "half3":
case "fixed3":
case "float3":
case "int4":
case "half4":
case "fixed4":
case "float4":
return "Vector4"; // SetVector()
}
throw new ArgumentException( "INVALID TYPE: " + t );
}
}
}