// Amplify Shader Editor - Visual Shader Editing Tool // Copyright (c) Amplify Creations, Lda using System; using UnityEngine; using UnityEngine.Rendering; using UnityEditor; using UnityEditor.PackageManager.Requests; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text.RegularExpressions; namespace AmplifyShaderEditor { public enum ASEImportFlags { None = 0, URP = 1 << 0, HDRP = 1 << 1, Both = URP | HDRP } public static class AssetDatabaseEX { private static System.Type type = null; public static System.Type Type { get { return ( type == null ) ? type = System.Type.GetType( "UnityEditor.AssetDatabase, UnityEditor" ) : type; } } public static void ImportPackageImmediately( string packagePath ) { AssetDatabaseEX.Type.InvokeMember( "ImportPackageImmediately", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.InvokeMethod, null, null, new object[] { packagePath } ); } } public enum ASESRPBaseline { ASE_SRP_INVALID = 0, ASE_SRP_10 = 100000, ASE_SRP_11 = 110000, ASE_SRP_12 = 120000, ASE_SRP_13 = 130000, ASE_SRP_14 = 140000, ASE_SRP_15 = 150000, ASE_SRP_16 = 160000, ASE_SRP_17 = 170000 } public class ASESRPPackageDesc { public ASESRPBaseline baseline = ASESRPBaseline.ASE_SRP_INVALID; public string guidURP = string.Empty; public string guidHDRP = string.Empty; public ASESRPPackageDesc( ASESRPBaseline baseline, string guidURP, string guidHDRP ) { this.baseline = baseline; this.guidURP = guidURP; this.guidHDRP = guidHDRP; } } [Serializable] [InitializeOnLoad] public static class ASEPackageManagerHelper { private static string URPPackageId = "com.unity.render-pipelines.universal"; private static string HDRPPackageId = "com.unity.render-pipelines.high-definition"; private static string NewVersionDetectedFormat = "A new {0} version {1} was detected and new templates are being imported.\nPlease hit the Update button on your ASE canvas to recompile your shader under the newest version."; private static string PackageBaseFormat = "ASE_PkgBase_{0}_{1}"; private static string PackageCRCFormat = "ASE_PkgCRC_{0}_{1}"; private static string SRPKeywordFormat = "ASE_SRP_VERSION {0}"; private static string ASEVersionKeywordFormat = "ASE_VERSION {0}"; private static Dictionary m_srpPackageSupport = new Dictionary() { { ( int )ASESRPBaseline.ASE_SRP_10, new ASESRPPackageDesc( ASESRPBaseline.ASE_SRP_10, "b460b52e6c1feae45b70b7ddc2c45bd6", "2243c8b4e1ab6914995699133f67ab5a" ) }, { ( int )ASESRPBaseline.ASE_SRP_11, new ASESRPPackageDesc( ASESRPBaseline.ASE_SRP_11, "b460b52e6c1feae45b70b7ddc2c45bd6", "2243c8b4e1ab6914995699133f67ab5a" ) }, { ( int )ASESRPBaseline.ASE_SRP_12, new ASESRPPackageDesc( ASESRPBaseline.ASE_SRP_12, "57fcea0ed8b5eb347923c4c21fa31b57", "9a5e61a8b3421b944863d0946e32da0a" ) }, { ( int )ASESRPBaseline.ASE_SRP_13, new ASESRPPackageDesc( ASESRPBaseline.ASE_SRP_13, "57fcea0ed8b5eb347923c4c21fa31b57", "9a5e61a8b3421b944863d0946e32da0a" ) }, { ( int )ASESRPBaseline.ASE_SRP_14, new ASESRPPackageDesc( ASESRPBaseline.ASE_SRP_14, "2e9da72e7e3196146bf7d27450013734", "89f0b84148d149d4d96b838d7ef60e92" ) }, { ( int )ASESRPBaseline.ASE_SRP_15, new ASESRPPackageDesc( ASESRPBaseline.ASE_SRP_15, "0904cdf24ddcd5042b024326476220d5", "19939ee2cdb76e0489b1b8cd4bed7f3d" ) }, { ( int )ASESRPBaseline.ASE_SRP_16, new ASESRPPackageDesc( ASESRPBaseline.ASE_SRP_16, "929783250050f8a448821b6ca1f2c578", "70777e8ce9f3c8d4a8182ca2f965cdb2" ) }, { ( int )ASESRPBaseline.ASE_SRP_17, new ASESRPPackageDesc( ASESRPBaseline.ASE_SRP_17, "89da50d95d149b744bf10bd27babcf79", "daf511a6dae20e641a9d69d025f023e4" ) }, }; private static Shader m_lateShader; private static Material m_lateMaterial; private static AmplifyShaderFunction m_lateShaderFunction; private static ListRequest m_packageListRequest = null; private static UnityEditor.PackageManager.PackageInfo m_urpPackageInfo; private static UnityEditor.PackageManager.PackageInfo m_hdrpPackageInfo; public static bool FoundURPVersion { get { return m_urpPackageInfo != null; } } public static bool FoundHDRPVersion { get { return m_hdrpPackageInfo != null; } } private static bool m_lateImport = false; private static string m_latePackageToImport; private static bool m_requireUpdateList = false; private static ASEImportFlags m_importingPackage = ASEImportFlags.None; public static bool CheckImporter { get { return m_importingPackage != ASEImportFlags.None; } } public static bool IsProcessing { get { return m_requireUpdateList && m_importingPackage == ASEImportFlags.None; } } private static ASESRPBaseline m_currentURPBaseline = ASESRPBaseline.ASE_SRP_INVALID; private static ASESRPBaseline m_currentHDRPBaseline = ASESRPBaseline.ASE_SRP_INVALID; public static ASESRPBaseline CurrentURPBaseline { get { return m_currentURPBaseline; } } public static ASESRPBaseline CurrentHDRPBaseline { get { return m_currentHDRPBaseline; } } private static int m_packageURPVersion = -1; // @diogo: starts as missing private static int m_packageHDRPVersion = -1; public static int PackageSRPVersion { get { return ( m_packageHDRPVersion >= m_packageURPVersion ) ? m_packageHDRPVersion : m_packageURPVersion; } } public static int CurrentSRPVersion { get { return UIUtils.CurrentWindow.MainGraphInstance.IsSRP ? PackageSRPVersion : -1; } } private static string m_projectName = null; private static string ProjectName { get { if ( string.IsNullOrEmpty( m_projectName ) ) { string[] s = Application.dataPath.Split( '/' ); m_projectName = s[ s.Length - 2 ]; } return m_projectName; } } static ASEPackageManagerHelper() { RequestInfo( true ); } static void WaitForPackageListBeforeUpdating() { if ( m_packageListRequest.IsCompleted ) { Update(); EditorApplication.update -= WaitForPackageListBeforeUpdating; } } public static void RequestInfo( bool updateWhileWaiting = false ) { if ( !m_requireUpdateList && m_importingPackage == ASEImportFlags.None ) { m_requireUpdateList = true; m_packageListRequest = UnityEditor.PackageManager.Client.List( true ); if ( updateWhileWaiting ) { EditorApplication.update += WaitForPackageListBeforeUpdating; } } } static void FailedPackageImport( string packageName, string errorMessage ) { FinishImporter(); } static void CancelledPackageImport( string packageName ) { FinishImporter(); } static void CompletedPackageImport( string packageName ) { FinishImporter(); } public static void CheckLatePackageImport() { if ( !Application.isPlaying && m_lateImport && !string.IsNullOrEmpty( m_latePackageToImport ) ) { m_lateImport = false; StartImporting( m_latePackageToImport ); m_latePackageToImport = string.Empty; } } public static void StartImporting( string packagePath ) { if ( !Preferences.Project.AutoSRP ) { m_importingPackage = ASEImportFlags.None; return; } if ( Application.isPlaying ) { if ( !m_lateImport ) { m_lateImport = true; m_latePackageToImport = packagePath; Debug.LogWarning( "Amplify Shader Editor requires the \"" + packagePath + "\" package to be installed in order to continue. Please exit Play mode to proceed." ); } return; } AssetDatabase.importPackageCancelled += CancelledPackageImport; AssetDatabase.importPackageCompleted += CompletedPackageImport; AssetDatabase.importPackageFailed += FailedPackageImport; AssetDatabase.ImportPackage( packagePath, false ); //AssetDatabaseEX.ImportPackageImmediately( packagePath ); } public static void FinishImporter() { m_importingPackage = ASEImportFlags.None; AssetDatabase.importPackageCancelled -= CancelledPackageImport; AssetDatabase.importPackageCompleted -= CompletedPackageImport; AssetDatabase.importPackageFailed -= FailedPackageImport; } public static void SetupLateShader( Shader shader ) { if ( shader == null ) return; //If a previous delayed object is pending discard it and register the new one // So the last selection will be the choice of opening //This can happen when trying to open an ASE canvas while importing templates or in play mode if ( m_lateShader != null ) { EditorApplication.delayCall -= LateShaderOpener; } RequestInfo(); m_lateShader = shader; EditorApplication.delayCall += LateShaderOpener; } public static void LateShaderOpener() { Update(); if ( IsProcessing ) { EditorApplication.delayCall += LateShaderOpener; } else { AmplifyShaderEditorWindow.ConvertShaderToASE( m_lateShader ); m_lateShader = null; } } public static void SetupLateMaterial( Material material ) { if ( material == null ) return; //If a previous delayed object is pending discard it and register the new one // So the last selection will be the choice of opening //This can happen when trying to open an ASE canvas while importing templates or in play mode if ( m_lateMaterial != null ) { EditorApplication.delayCall -= LateMaterialOpener; } RequestInfo(); m_lateMaterial = material; EditorApplication.delayCall += LateMaterialOpener; } public static void LateMaterialOpener() { Update(); if ( IsProcessing ) { EditorApplication.delayCall += LateMaterialOpener; } else { AmplifyShaderEditorWindow.LoadMaterialToASE( m_lateMaterial ); m_lateMaterial = null; } } public static void SetupLateShaderFunction( AmplifyShaderFunction shaderFunction ) { if ( shaderFunction == null ) return; //If a previous delayed object is pending discard it and register the new one // So the last selection will be the choice of opening //This can happen when trying to open an ASE canvas while importing templates or in play mode if ( m_lateShaderFunction != null ) { EditorApplication.delayCall -= LateShaderFunctionOpener; } RequestInfo(); m_lateShaderFunction = shaderFunction; EditorApplication.delayCall += LateShaderFunctionOpener; } public static void LateShaderFunctionOpener() { Update(); if ( IsProcessing ) { EditorApplication.delayCall += LateShaderFunctionOpener; } else { AmplifyShaderEditorWindow.LoadShaderFunctionToASE( m_lateShaderFunction, false ); m_lateShaderFunction = null; } } private static readonly string SemVerPattern = @"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"; private static int PackageVersionStringToCode( string version, out int major, out int minor, out int patch ) { MatchCollection matches = Regex.Matches( version, SemVerPattern, RegexOptions.Multiline ); bool validMatch = ( matches.Count > 0 && matches[ 0 ].Groups.Count >= 4 ); major = validMatch ? int.Parse( matches[ 0 ].Groups[ 1 ].Value ) : 99; minor = validMatch ? int.Parse( matches[ 0 ].Groups[ 2 ].Value ) : 99; patch = validMatch ? int.Parse( matches[ 0 ].Groups[ 3 ].Value ) : 99; int versionCode; versionCode = major * 10000; versionCode += minor * 100; versionCode += patch; return versionCode; } private static void CodeToPackageVersionElements( int versionCode, out int major, out int minor, out int patch ) { major = versionCode / 10000; minor = versionCode / 100 - major * 100; patch = versionCode - ( versionCode / 100 ) * 100; } private static int PackageVersionElementsToCode( int major, int minor, int patch ) { return major * 10000 + minor * 100 + patch; } private static void CheckPackageImport( ASEImportFlags flag, ASESRPBaseline baseline, string guid, string version ) { Debug.Assert( flag == ASEImportFlags.HDRP || flag == ASEImportFlags.URP ); string path = AssetDatabase.GUIDToAssetPath( guid ); if ( !string.IsNullOrEmpty( path ) && File.Exists( path ) ) { uint currentCRC = IOUtils.CRC32( File.ReadAllBytes( path ) ); string srpName = flag.ToString(); string packageBaseKey = string.Format( PackageBaseFormat, srpName, ProjectName ); string packageCRCKey = string.Format( PackageCRCFormat, srpName, ProjectName ); ASESRPBaseline savedBaseline = ( ASESRPBaseline )EditorPrefs.GetInt( packageBaseKey ); uint savedCRC = ( uint )EditorPrefs.GetInt( packageCRCKey, 0 ); bool foundNewVersion = ( savedBaseline != baseline ) || ( savedCRC != currentCRC ); EditorPrefs.SetInt( packageBaseKey, ( int )baseline ); EditorPrefs.SetInt( packageCRCKey, ( int )currentCRC ); string testPath0 = string.Empty; string testPath1 = string.Empty; switch ( flag ) { case ASEImportFlags.URP: { testPath0 = AssetDatabase.GUIDToAssetPath( TemplatesManager.URPLitGUID ); testPath1 = AssetDatabase.GUIDToAssetPath( TemplatesManager.URPUnlitGUID ); break; } case ASEImportFlags.HDRP: { testPath0 = AssetDatabase.GUIDToAssetPath( TemplatesManager.HDRPLitGUID ); testPath1 = AssetDatabase.GUIDToAssetPath( TemplatesManager.HDRPUnlitGUID ); break; } } if ( !File.Exists( testPath0 ) || !File.Exists( testPath1 ) || foundNewVersion ) { if ( foundNewVersion ) { Debug.Log( string.Format( NewVersionDetectedFormat, srpName, version ) ); } m_importingPackage |= flag; StartImporting( path ); } } } public static void Update() { CheckLatePackageImport(); if ( m_requireUpdateList && m_importingPackage == ASEImportFlags.None ) { if ( m_packageListRequest != null && m_packageListRequest.IsCompleted && m_packageListRequest.Result != null ) { m_requireUpdateList = false; foreach ( UnityEditor.PackageManager.PackageInfo pi in m_packageListRequest.Result ) { int version = PackageVersionStringToCode( pi.version, out int major, out int minor, out int patch ); int baseline = PackageVersionElementsToCode( major, 0, 0 ); ASESRPPackageDesc match; if ( pi.name.Equals( URPPackageId ) && m_srpPackageSupport.TryGetValue( baseline, out match ) ) { // Universal Rendering Pipeline m_currentURPBaseline = match.baseline; m_packageURPVersion = version; m_urpPackageInfo = pi; CheckPackageImport( ASEImportFlags.URP, match.baseline, match.guidURP, pi.version ); } else if ( pi.name.Equals( HDRPPackageId ) && m_srpPackageSupport.TryGetValue( baseline, out match ) ) { // High-Definition Rendering Pipeline m_currentHDRPBaseline = match.baseline; m_packageHDRPVersion = version; m_hdrpPackageInfo = pi; CheckPackageImport( ASEImportFlags.HDRP, match.baseline, match.guidHDRP, pi.version ); } } } } } public static void SetASEVersionInfoOnDataCollector( ref MasterNodeDataCollector dataCollector ) { if ( m_requireUpdateList ) { Update(); } dataCollector.AddToDirectives( string.Format( ASEVersionKeywordFormat, VersionInfo.FullNumber ), -1, AdditionalLineType.Define ); } public static void SetSRPInfoOnDataCollector( ref MasterNodeDataCollector dataCollector ) { if ( m_requireUpdateList ) { Update(); } if ( dataCollector.CurrentSRPType == TemplateSRPType.HDRP ) { dataCollector.AddToDirectives( string.Format( SRPKeywordFormat, m_packageHDRPVersion ), -1, AdditionalLineType.Define ); } else if ( dataCollector.CurrentSRPType == TemplateSRPType.URP ) { dataCollector.AddToDirectives( string.Format( SRPKeywordFormat, m_packageURPVersion ), -1, AdditionalLineType.Define ); } } } }