static string[] OnWillSaveAssets(string[] assetPaths) { //find all TextureRecipe assets, then check if any are being saved List <string> recipeAssetPaths = new List <string>(); var recipeGUIDs = AssetDatabase.FindAssets("t:TextureRecipe"); foreach (var recipeID in recipeGUIDs) { recipeAssetPaths.Add(AssetDatabase.GUIDToAssetPath(recipeID)); } foreach (string assetPath in assetPaths) { if (recipeAssetPaths.Contains(assetPath)) { //We are saving this recipe so generate any other assets that it requires TextureRecipe recipe = AssetDatabase.LoadAssetAtPath <TextureRecipe>(assetPath); GeneratorFactory.createMappings(); var builtSubAssetPaths = bakeSubAssets(recipe); var oldAssetPaths = new List <string>(assetPaths); oldAssetPaths.AddRange(builtSubAssetPaths); assetPaths = oldAssetPaths.ToArray(); } } return(assetPaths); }
static void bakeShaderLayerSubAssets(ShaderLayer shaderLayer, TextureRecipe recipe, List <string> subAssetPaths) { var shader = ShaderGenerator.bakeShader(shaderLayer, recipe); if (null == shader) { UnityEngine.Debug.LogError("Failed to load shader " + shaderLayer.name); return; } subAssetPaths.Add(shader.name); foreach (var node in shaderLayer.nodes) { if (!node.assetsDirty) { continue; } var nodeAssetGenerator = GeneratorFactory.getAssetGenerator(node); if (null != nodeAssetGenerator) { var newAssetPaths = nodeAssetGenerator.generateAssets(node); if (newAssetPaths.Count > 0) { subAssetPaths.AddRange(newAssetPaths); } node.assetsDirty = false; } } }
public TextureRecipeRenderer(TextureRecipe _recipe) { //We are performing a deep copy of the TextureRecipe asset hierarchy. This is so that changes made to the TextureRecipe asset at runtime in the editor //don't persist after exiting Play Mode. It also allows us to have multiple renderers using the same TextureRecipe as a source without writing over //each others settings. We need to traverse the layer and node hierarchies to clone each object individually and also to remap any references onto the //newly-instantiated objects. //TODO: move the layer copying logic into the layer classes to give better layer abstraction recipe = GameObject.Instantiate(_recipe); //TODO: better naming here recipe.name = _recipe.name; //TODO: necessary? recipe.unique_id = _recipe.unique_id; int numLayers = recipe.layerList.Count; for (int i = 0; i < numLayers; i++) { var originalLayer = recipe.layerList[i]; var newLayer = GameObject.Instantiate(originalLayer); recipe.layerList[i] = newLayer; //TODO: layer abstraction if (originalLayer is ShaderLayer) { Dictionary <BaseNode, BaseNode> nodeRemap = new Dictionary <BaseNode, BaseNode>(); var shaderLayer = (ShaderLayer)recipe.layerList[i]; int numNodes = shaderLayer.nodes.Count; for (int j = 0; j < numNodes; j++) { var originalNode = shaderLayer.nodes[j]; var newNode = GameObject.Instantiate(originalNode); shaderLayer.nodes[j] = newNode; nodeRemap[originalNode] = newNode; } for (int j = 0; j < numNodes; j++) { var newNode = shaderLayer.nodes[j]; for (int k = 0; k < newNode.inputs.Count; k++) { var oldInputNode = newNode.inputs[k]; if (null != oldInputNode.inputNode) { // Debug.Assert(nodeRemap.ContainsKey(oldInputNode.inputNode)); if (nodeRemap.ContainsKey(oldInputNode.inputNode)) { BaseNode.NodeInput input = newNode.inputs[k]; input.inputNode = nodeRemap[oldInputNode.inputNode]; newNode.inputs[k] = input; Debug.Assert(newNode.inputs[k].inputNode.name.Contains("Clone")); } } } } shaderLayer.root = (RootNode)nodeRemap[shaderLayer.root]; } } }
public override void OnPreviewGUI(Rect r, GUIStyle s) { //We don't want any of our preview in the header, just normal header elements if (drawingHeader) { return; } TextureRecipe targetRecipe = (TextureRecipe)target; EditorGUILayout.BeginHorizontal(); bool autoRefreshWasEnabled = targetRecipe.autoRefresh; targetRecipe.autoRefresh = GUILayout.Toggle(targetRecipe.autoRefresh, "auto"); if (!autoRefreshWasEnabled && targetRecipe.autoRefresh) { refreshPreviewTexture = true; } if (GUILayout.Button(refreshTex, GUILayout.Width(30.0f))) { refreshPreviewTexture = true; } EditorGUILayout.EndHorizontal(); Texture2D previewTex = null; string key = AssetDatabase.GetAssetPath(target); previewTextures.TryGetValue(key, out previewTex); if (!previewTex) { previewTex = new Texture2D(128, 128); previewTextures[key] = previewTex; } UpdatePreviewTexture(targetRecipe, previewTex); if (r.width > r.height) { r.x = (r.width - r.height) / 2.0f; r.width = r.height; } else { r.y += (r.height - r.width) / 2.0f; r.height = r.width; } EditorGUI.DrawTextureTransparent(r, previewTex); }
static void buildModifiedShaders() { if (!EditorApplication.isPlaying && EditorApplication.isPlayingOrWillChangePlaymode) { //TODO: is there a way to figure out which shaders need building? var recipeGUIDs = AssetDatabase.FindAssets("t:TextureRecipe"); foreach (var recipeID in recipeGUIDs) { string assetPath = AssetDatabase.GUIDToAssetPath(recipeID); TextureRecipe recipe = AssetDatabase.LoadAssetAtPath <TextureRecipe>(assetPath); GeneratorFactory.createMappings(); bakeSubAssets(recipe); } } }
public override Texture2D RenderStaticPreview(string assetPath, UnityEngine.Object[] subAssets, int width, int height) { Texture2D previewTex = null; previewTextures.TryGetValue(assetPath, out previewTex); if (null == previewTex) { TextureRecipe targetRecipe = (TextureRecipe)target; previewTex = new Texture2D(128, 128); previewTextures[assetPath] = previewTex; UpdatePreviewTexture(targetRecipe, previewTex); } return(Instantiate(previewTex)); }
public static List <string> bakeSubAssets(TextureRecipe recipe) { List <string> subAssetPaths = new List <string>(); foreach (var layer in recipe.layerList) { if (layer is ShaderLayer) { bakeShaderLayerSubAssets((ShaderLayer)layer, recipe, subAssetPaths); } } if (subAssetPaths.Count > 0) { AssetDatabase.Refresh(); } return(subAssetPaths); }
void CreateLayerCallback(object obj) { Type objectType = (Type)obj; TextureRecipe t = (TextureRecipe)target; Undo.RecordObject(t, "Create " + objectType.Name); { var layerObject = (RecipeLayerBase)CreateInstance(objectType); t.layerList.Add(layerObject); layerObject.layerName = Utilities.MakeNiceName(objectType.Name); AssetDatabase.AddObjectToAsset(layerObject, t); AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(layerObject)); } Undo.FlushUndoRecordObjects(); serializedObject.Update(); EditorUtility.SetDirty(target); }
void UpdatePreviewTexture(TextureRecipe targetRecipe, Texture2D previewTex) { if (!refreshPreviewTexture) { return; } refreshPreviewTexture = false; GeneratorFactory.createMappings(); RecipeBuildPreProcess.bakeSubAssets(targetRecipe); TextureRecipeRenderer renderer = new TextureRecipeRenderer(targetRecipe); renderer.renderSetup(previewTex); renderer.renderRecipeInternal(); previewTex.ReadPixels(new Rect(0, 0, previewTex.width, previewTex.height), 0, 0); previewTex.Apply(); RenderTexture.active = null; renderer.renderTeardown(); }
private void DrawListElement(Rect rect, int index, bool active, bool focused) { serializedObject.Update(); var layerList = serializedObject.FindProperty("layerList"); if (index < layerList.arraySize) { var listItem = layerList.GetArrayElementAtIndex(index); if (null == listItem.objectReferenceValue) { return; } RecipeLayerBase layerObj = (RecipeLayerBase)listItem.objectReferenceValue; var layerType = listItem.objectReferenceValue.GetType(); var ft = layerType.GetField("layerColor"); EditorGUI.DrawRect(rect, (Color)ft.GetValue(null)); SerializedObject subObject = new SerializedObject(listItem.objectReferenceValue); var nameProp = subObject.FindProperty("layerName"); string layerName = nameProp.stringValue; if (layerName == null || layerName == "") { layerName = Utilities.MakeNiceName(layerName); } var textRect = rect; var textDimensions = GUI.skin.label.CalcSize(new GUIContent(layerName)); textRect.xMax = textRect.xMin + textDimensions.x + 3; GUI.backgroundColor = new Color(1.0f, 1.0f, 1.0f, 0.5f); EditorGUI.BeginChangeCheck(); string newLayerName = EditorGUI.TextField(textRect, layerName); //ensure name value is unique if it changed if (EditorGUI.EndChangeCheck()) { TextureRecipe recipe = (TextureRecipe)target; bool nameAlreadyUsed = false; foreach (var layer in recipe.layerList) { if (layer.layerName == newLayerName) { nameAlreadyUsed = true; break; } } if (!nameAlreadyUsed) { nameProp.stringValue = newLayerName; } } subObject.ApplyModifiedProperties(); } }
public override void OnInspectorGUI() { serializedObject.Update(); TextureRecipe targetRecipe = (TextureRecipe)target; var texWidthProp = serializedObject.FindProperty("TextureWidth"); var texHeightProp = serializedObject.FindProperty("TextureHeight"); texWidthProp.intValue = EditorGUILayout.IntField("Width", texWidthProp.intValue); texHeightProp.intValue = EditorGUILayout.IntField("Height", texHeightProp.intValue); serializedObject.ApplyModifiedProperties(); if (GUILayout.Button("Bake")) { GeneratorFactory.createMappings(); RecipeBuildPreProcess.bakeSubAssets(targetRecipe); TextureRecipeRenderer renderer = new TextureRecipeRenderer(targetRecipe); renderer.renderSetup(); renderer.renderRecipeInternal(); TextureWriter.WriteRenderBufferToTextureFile(targetRecipe.TextureWidth, targetRecipe.TextureHeight, targetRecipe); RenderTexture.active = null; renderer.renderTeardown(); } reorderableList.DoLayoutList(); int listIndex = reorderableList.index; var layerList = serializedObject.FindProperty("layerList"); if (listIndex >= 0 && listIndex < layerList.arraySize) { var listItem = layerList.GetArrayElementAtIndex(listIndex); if (null != listItem.objectReferenceValue) { if (subEditorType != listItem.objectReferenceValue.GetType()) { subEditor = null; } EditorGUI.BeginChangeCheck(); ShaderLayerEditor.recipe = (TextureRecipe)target; CreateCachedEditor(listItem.objectReferenceValue, null, ref subEditor); subEditorType = listItem.objectReferenceValue.GetType(); subEditor.serializedObject.Update(); subEditor.OnInspectorGUI(); subEditor.serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties(); if (EditorGUI.EndChangeCheck() && targetRecipe.autoRefresh) { //do we need this if we set dirty on the target every frame? EditorUtility.SetDirty(listItem.objectReferenceValue); refreshPreviewTexture = true; } if (ShaderLayerEditor.nodeEditor && ShaderLayerEditor.nodeEditor.refreshShader && targetRecipe.autoRefresh) { refreshPreviewTexture = true; ShaderLayerEditor.nodeEditor.refreshShader = false; } } } //We have to do this every frame otherwise changes to the child objects aren't reflected in the inspector when we Undo them //there is perhaps an issue with the order of operations EditorUtility.SetDirty(target); }
public static string getShaderName(TextureRecipe recipe, ShaderLayer shaderLayer) { return(recipe.unique_id + "-" + recipe.name + "-" + shaderLayer.layerName); }
public static string getShaderPath(TextureRecipe recipe, ShaderLayer shaderLayer) { return("TextureKit/" + getShaderName(recipe, shaderLayer)); }
public static Shader bakeShader(ShaderLayer shaderLayer, TextureRecipe recipe) { TextAsset shaderBaseText = (TextAsset)Resources.Load("template.shader"); if (shaderBaseText == null) { Debug.LogError("Failed to template shader"); return(null); } string shaderName = ShaderLayer.getShaderName(recipe, shaderLayer); string shaderPath = ShaderLayer.getShaderPath(recipe, shaderLayer); string shaderText = shaderBaseText.text; List <string> lines = shaderText.Split('\n').ToList <string>(); int numLines = lines.Count; for (int i = 0; i < numLines; i++) { string line = lines[i]; if (line.Contains("$$")) { if (line.Contains("$$ASSETNAME")) { line = line.Replace("$$ASSETNAME", shaderPath); lines[i] = line; } else if (line.Contains("$$STARTFUNCS")) { var funcs = getFunctions(shaderLayer); lines.InsertRange(i + 1, funcs); i += funcs.Count; numLines = lines.Count; } else if (line.Contains("$$STARTPROPS")) { var props = getProperties(shaderLayer); lines.InsertRange(i + 1, props); i += props.Count; numLines = lines.Count; } else if (line.Contains("$$STARTINPUTS")) { var inputs = getInputs(shaderLayer); lines.InsertRange(i + 1, inputs); i += inputs.Count; numLines = lines.Count; } else if (line.Contains("$$STARTSHADER")) { lines.Insert(i + 1, getCalls(shaderLayer)); numLines = lines.Count; i++; } } } string baseAssetPath = "Assets/TextureRecipes/Resources/"; string shaderAssetPath = baseAssetPath + shaderName + ".shader"; //Can't do this because we want to overwrite an existing asset! shaderPath = AssetDatabase.GenerateUniqueAssetPath(shaderPath); //Generating the text for the shader is a very fast operation. Rather than trying to figure out which changes //would have altered the shader, we simply generate the shader as a byproduct of every change, and then see //if it has changed since the last time we output it. The really expensive part is outputting the shader and //triggering the Unity shader post-process and compilation steps (we're talking >1.5s vs 3ms). string[] existingLines = null; existingShaderLines.TryGetValue(shaderPath, out existingLines); string[] newLines = lines.ToArray(); if (existingLines != null) { if (existingLines.Length == newLines.Length) { bool allMatch = true; for (int i = 0; allMatch && (i < newLines.Length); i++) { if (existingLines[i] != newLines[i]) { allMatch = false; } } if (allMatch) { return(Shader.Find(shaderPath)); } } } //File.WriteAllLines(shaderAssetPath, newLines); using (StreamWriter writer = new StreamWriter(shaderAssetPath, false)) { writer.NewLine = "\n"; foreach (string str in newLines) { writer.WriteLine(str); } } existingShaderLines[shaderPath] = newLines; AssetDatabase.Refresh(); return(Shader.Find(shaderPath)); }