BITFALL/Assets/Plugins/CW/Shared/Common/Required/Scripts/CwShaderBundle.cs

593 lines
14 KiB
C#

#define AUTO_SWITCH_SHADERS_TO_CURRENT_PIPELINE
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace CW.Common
{
/// <summary>This asset stores multiple variants of a shader for different rendering pipelines, and allows you to switch between them in the editor.</summary>
//[CreateAssetMenu(fileName = "NewShaderBundle", menuName = "MakeShaderBundle", order = 1)]
public class CwShaderBundle : ScriptableObject
{
public enum Pipeline
{
Invalid = -1,
Standard,
URP2019,
URP2020,
URP2021,
URP2022,
HDRP2019,
HDRP2020,
HDRP2021,
HDRP2022,
COUNT
}
/// <summary>This stores the information unique to each shader variant.</summary>
[System.Serializable]
public class ShaderVariant
{
public Pipeline Pipe;
public string Code;
public int Hash;
public bool Dirty;
public string HashString
{
get
{
return "//<HASH>" + Hash + "</HASH>";
}
}
}
/// <summary>The title of the generated shader.</summary>
public string Title { set { title = value; } get { return title; } } [SerializeField] private string title;
/// <summary>The shader that will be modified to work with the selected rendering pipeline.</summary>
public Shader Target { set { target = value; } get { return target; } } [SerializeField] private Shader target;
/// <summary>The hash code of the currently loaded bundle.</summary>
public int VariantHash { set { variantHash = value; } get { return variantHash; } } [SerializeField] private int variantHash;
/// <summary>The hash code of the current device to make sure the shaders have been loaded properly.</summary>
public int ProjectHash { set { projectHash = value; } get { return projectHash; } } [SerializeField] private int projectHash;
/// <summary>This stores all shader variants for this shader.</summary>
public List<ShaderVariant> Variants { get { if (variants == null) variants = new List<ShaderVariant>(); return variants; } } [SerializeField] private List<ShaderVariant> variants;
public bool Dirty
{
get
{
if (variants != null)
{
foreach (var variant in variants)
{
if (variant.Dirty == true)
{
return true;
}
}
}
return false;
}
}
#if UNITY_EDITOR && AUTO_SWITCH_SHADERS_TO_CURRENT_PIPELINE
private static Pipeline lastAutoSetPipe = Pipeline.Invalid;
#endif
public static int GetProjectHash()
{
return Application.productName.GetHashCode();
}
/// <summary>This tells you which rendering pipeline the project is currently using.</summary>
public static Pipeline DetectProjectPipeline()
{
var crp = UnityEngine.Rendering.GraphicsSettings.currentRenderPipeline;
if (crp != null)
{
var title = crp.GetType().ToString();
if (title.Contains("HighDefinition") == true)
{
#if UNITY_2022_2_OR_NEWER
return Pipeline.HDRP2022;
#elif UNITY_2021_2_OR_NEWER
return Pipeline.HDRP2021;
#elif UNITY_2020_2_OR_NEWER
return Pipeline.HDRP2020;
#else
return Pipeline.HDRP2019;
#endif
}
else// if (title.Contains("Universal") == true)
{
#if UNITY_2022_2_OR_NEWER
return Pipeline.URP2022;
#elif UNITY_2021_2_OR_NEWER
return Pipeline.URP2021;
#elif UNITY_2020_2_OR_NEWER
return Pipeline.URP2020;
#else
return Pipeline.URP2019;
#endif
}
}
else
{
return Pipeline.Standard;
}
//return Pipeline.Invalid;
}
public static bool IsStandard(Pipeline pipe)
{
return pipe == Pipeline.Standard;
}
public static bool IsScriptable(Pipeline pipe)
{
return IsURP(pipe) || IsHDRP(pipe);
}
public static bool IsURP(Pipeline pipe)
{
return pipe == Pipeline.URP2019 || pipe == Pipeline.URP2020 || pipe == Pipeline.URP2021 || pipe == Pipeline.URP2022;
}
public static bool IsHDRP(Pipeline pipe)
{
return pipe == Pipeline.HDRP2019 || pipe == Pipeline.HDRP2020 || pipe == Pipeline.HDRP2021 || pipe == Pipeline.HDRP2022;
}
#if UNITY_EDITOR
/// <summary>This will automatically update all shaders when assemblies reload.</summary>
[InitializeOnLoadMethod]
private static void RegisterAutoSwitch()
{
UnityEditor.EditorApplication.delayCall -= AutoSwitch;
UnityEditor.EditorApplication.delayCall += AutoSwitch;
AssetDatabase.importPackageCompleted -= ForceSwitch;
AssetDatabase.importPackageCompleted += ForceSwitch;
}
private static void ForceSwitch(string name)
{
lastAutoSetPipe = Pipeline.Invalid;
AutoSwitch();
}
private static void AutoSwitch()
{
#if AUTO_SWITCH_SHADERS_TO_CURRENT_PIPELINE
var pipe = DetectProjectPipeline();
var hash = GetProjectHash();
if (lastAutoSetPipe != pipe)
{
lastAutoSetPipe = pipe;
var modified = false;
var guids = UnityEditor.AssetDatabase.FindAssets("t:" + typeof(CwShaderBundle).Name);
foreach (var guid in guids)
{
var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
var bundle = UnityEditor.AssetDatabase.LoadAssetAtPath<CwShaderBundle>(path);
if (bundle != null && bundle.variantHash != 0)
{
if (bundle.projectHash != hash)
{
if (bundle.TrySwitchTo(pipe) == true)
{
modified = true;
continue;
}
}
else
{
foreach (var variant in bundle.Variants)
{
// Correct pipe, but out of sync shader
if (variant.Pipe == pipe && variant.Hash != bundle.variantHash)
{
if (bundle.TrySwitchTo(pipe) == true)
{
modified = true;
continue;
}
}
// Wrong pipe
else if (variant.Pipe != pipe && variant.Hash == bundle.variantHash)
{
if (bundle.TrySwitchTo(pipe) == true)
{
modified = true;
continue;
}
}
}
}
}
}
if (modified == true)
{
Debug.Log(typeof(CwShaderBundle).Name + " changed shaders to pipeline: " + pipe);
}
}
UnityEditor.EditorApplication.delayCall -= AutoSwitch;
UnityEditor.EditorApplication.delayCall += AutoSwitch;
#endif
}
#if __BETTERSHADERS__
private string GetPath()
{
var raw = UnityEditor.AssetDatabase.GetAssetPath(this);
var path = System.IO.Path.ChangeExtension(raw, "surfshader");
if (System.IO.File.Exists(path) == true)
{
return path;
}
return System.IO.Path.ChangeExtension(raw, "stackedshader");
}
public void Compile()
{
if (target != null)
{
UnityEditor.Undo.RecordObject(this, "Compiling Shaders");
if (variants == null)
{
variants = new List<ShaderVariant>();
}
else
{
variants.Clear();
}
var overrides = new JBooth.BetterShaders.OptionOverrides();
var path = GetPath();
overrides.shaderName = title;
if (System.IO.File.Exists(path) == true)
{
for (var i = 0; i < (int)Pipeline.COUNT; i++)
{
// Skip non-implemented pipelines
//if (i == (int)Pipeline.URP2021)
//{
// continue;
//}
CompileVariant((Pipeline)i, overrides, path);
}
}
else
{
Debug.LogError("Failed to compile shader bundle because of missing file: " + path);
}
UnityEditor.EditorUtility.SetDirty(this);
}
}
public void TryCompileFast()
{
var pipe = DetectProjectPipeline();
foreach (var variant in variants)
{
if (variant.Pipe == pipe)
{
var overrides = new JBooth.BetterShaders.OptionOverrides();
var path = GetPath();
overrides.shaderName = title;
// Reset all hashes to mark as dirty
foreach (var v in variants)
{
v.Dirty = true;
}
if (System.IO.File.Exists(path) == true)
{
CompileVariant(overrides, path, variant);
}
else
{
Debug.LogError("Failed to compile shader bundle because of missing file: " + path);
}
return;
}
}
}
private void CompileVariant(Pipeline pipe, JBooth.BetterShaders.OptionOverrides overrides, string path)
{
var variant = new ShaderVariant();
variant.Pipe = pipe;
CompileVariant(overrides, path, variant);
variants.Add(variant);
}
private void CompileVariant(JBooth.BetterShaders.OptionOverrides overrides, string path, ShaderVariant variant)
{
var pipeline = (JBooth.BetterShaders.ShaderBuilder.RenderPipeline)variant.Pipe;
if (path.EndsWith("surfshader"))
{
variant.Code = JBooth.BetterShaders.BetterShaderImporterEditor.BuildExportShader(pipeline, overrides, path);
}
else if (path.EndsWith("stackedshader"))
{
variant.Code = JBooth.BetterShaders.StackedShaderImporterEditor.BuildExportShader(pipeline, overrides, path);
}
variant.Hash = Random.value.GetHashCode() ^ variant.Code.GetHashCode();
variant.Code = variant.Code.Replace("\n\r", "\n");
variant.Code = variant.Code.Replace("\r\n", "\n");
variant.Code = variant.HashString + "\n" + variant.Code;
variant.Dirty = false;
}
#endif
public bool TrySwitchTo(Pipeline pipe)
{
if (variants != null && target != null)
{
foreach (var variant in variants)
{
if (variant.Pipe == pipe)
{
var path = UnityEditor.AssetDatabase.GetAssetPath(target);
// Already up to date?
if (System.IO.File.ReadAllText(path) != variant.Code)
{
System.IO.File.WriteAllText(path, variant.Code);
UnityEditor.AssetDatabase.ImportAsset(path);
UnityEditor.Undo.RecordObject(this, "Switching Pipeline");
variantHash = variant.Hash;
projectHash = GetProjectHash();
UnityEditor.EditorUtility.SetDirty(this);
return true;
}
break;
}
}
}
return false;
}
public static void TrySwitchAllTo(Pipeline pipe)
{
var guids = UnityEditor.AssetDatabase.FindAssets("t:" + typeof(CwShaderBundle).Name);
foreach (var guid in guids)
{
var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
var bundle = UnityEditor.AssetDatabase.LoadAssetAtPath<CwShaderBundle>(path);
if (bundle != null)
{
bundle.TrySwitchTo(pipe);
}
}
}
#endif
}
#if UNITY_EDITOR
[CustomEditor(typeof(CwShaderBundle))]
public class CwShaderBundle_Editor : Editor
{
private CwShaderBundle tgt;
public override void OnInspectorGUI()
{
tgt = (CwShaderBundle)target;
DrawShader();
#if __BETTERSHADERS__
EditorGUILayout.Separator();
DrawCompile();
#endif
EditorGUILayout.Separator();
DrawShaderBundles();
serializedObject.ApplyModifiedProperties();
}
private void DrawShader()
{
var pipe = CwShaderBundle.DetectProjectPipeline();
EditorGUILayout.PropertyField(serializedObject.FindProperty("target"));
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.TextField("Project Pipeline", pipe.ToString());
foreach (var variant in tgt.Variants)
{
var active = variant.Hash == tgt.VariantHash;
EditorGUILayout.Toggle(variant.Pipe.ToString(), active);
}
EditorGUI.EndDisabledGroup();
EditorGUILayout.Separator();
if (GUILayout.Button("Switch Shader To " + pipe.ToString()) == true)
{
tgt.TrySwitchTo(pipe);
serializedObject.Update();
}
if (GUILayout.Button("Switch All Shaders To " + pipe.ToString()) == true)
{
CwShaderBundle.TrySwitchAllTo(pipe);
serializedObject.Update();
}
}
#if __BETTERSHADERS__
private void DrawCompile()
{
EditorGUILayout.LabelField("Better Shaders", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(serializedObject.FindProperty("title"));
EditorGUILayout.Separator();
if (GUILayout.Button("Fast Compile") == true)
{
tgt.TryCompileFast();
tgt.TrySwitchTo(CwShaderBundle.DetectProjectPipeline());
serializedObject.Update();
}
if (GUILayout.Button("Compile") == true)
{
tgt.Compile();
tgt.TrySwitchTo(CwShaderBundle.DetectProjectPipeline());
serializedObject.Update();
}
if (GUILayout.Button("Compile All") == true)
{
var guids = UnityEditor.AssetDatabase.FindAssets("t:" + typeof(CwShaderBundle).Name);
foreach (var guid in guids)
{
var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
var bundle = UnityEditor.AssetDatabase.LoadAssetAtPath<CwShaderBundle>(path);
if (bundle != null)
{
bundle.Compile();
}
}
serializedObject.Update();
}
}
#endif
private void DrawShaderBundles()
{
EditorGUILayout.LabelField("All Shaders In Project", EditorStyles.boldLabel);
var guids = UnityEditor.AssetDatabase.FindAssets("t:" + typeof(CwShaderBundle).Name);
var color = GUI.color;
EditorGUI.BeginDisabledGroup(true);
foreach (var guid in guids)
{
var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
var bundle = UnityEditor.AssetDatabase.LoadAssetAtPath<CwShaderBundle>(path);
if (bundle != null)
{
var title = bundle.Target != null ? bundle.Target.name : "<MISSING>";
GUI.color = bundle.Dirty == true ? Color.red : color;
EditorGUILayout.ObjectField(title, bundle, typeof(CwShaderBundle), false);
GUI.color = color;
EditorGUILayout.Separator();
}
}
EditorGUI.EndDisabledGroup();
GUI.color = color;
}
}
#endif
#if UNITY_EDITOR
#if __BETTERSHADERS__
/// <summary>This will automatically rebuild a shader bundle when its source better shader is modified.</summary>
public class CwShaderBundleDetector : AssetPostprocessor
{
void OnPreprocessAsset()
{
TryCompileFast(".surfshader");
TryCompileFast(".stackedshader");
}
private void TryCompileFast(string end)
{
var shaderPath = assetImporter.assetPath;
if (shaderPath.EndsWith(end) == true)
{
var bundlePath = System.IO.Path.ChangeExtension(shaderPath, "asset");
var bundle = AssetDatabase.LoadAssetAtPath<CwShaderBundle>(bundlePath);
if (bundle != null)
{
var pipe = CwShaderBundle.DetectProjectPipeline();
foreach (var variant in bundle.Variants)
{
if (variant.Pipe == pipe)
{
if (System.IO.File.ReadAllText(shaderPath).Contains(variant.HashString) == true)
{
return;
}
}
}
bundle.TryCompileFast();
bundle.TrySwitchTo(pipe);
}
}
}
}
#endif
#endif
}