using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Rendering; namespace UnityEditor.Rendering { internal class CSharpToHLSL { /// /// Generate all shader code from attribute. /// /// An awaitable task. public static async Task GenerateAll() { Dictionary> sourceGenerators = null; try { // Store per source file path the generator definitions sourceGenerators = DictionaryPool>.Get(); // Extract all types with the GenerateHLSL tag foreach (var type in TypeCache.GetTypesWithAttribute()) { var attr = type.GetCustomAttributes(typeof(GenerateHLSL), false).First() as GenerateHLSL; if (!sourceGenerators.TryGetValue(attr.sourcePath, out var generators)) { generators = ListPool.Get(); sourceGenerators.Add(attr.sourcePath, generators); } generators.Add(new ShaderTypeGenerator(type, attr)); } // Generate all files await Task.WhenAll(sourceGenerators.Select(async it => await GenerateAsync($"{it.Key}.hlsl", $"{Path.ChangeExtension(it.Key, "custom")}.hlsl", it.Value))); } finally { // Make sure we always release pooled resources if (sourceGenerators != null) { foreach (var pair in sourceGenerators) ListPool.Release(pair.Value); DictionaryPool>.Release(sourceGenerators); } } } /// /// Generate all shader code from into . /// /// Path of the file to generate. /// Path of the custom file to include. (If it exists) /// Generators to execute. /// Awaitable task. private static async Task GenerateAsync(string targetFilename, string targetCustomFilename, List generators) { var skipFile = false; // Emit atomic element for all generators foreach (var gen in generators.Where(gen => !gen.Generate())) { // Error reporting will be done by the generator. Skip this file. gen.PrintErrors(); skipFile = true; break; } // If an error occured during generation, we abort this file if (skipFile) return; // Check access to the file if (File.Exists(targetFilename)) { FileInfo info = null; try { info = new FileInfo(targetFilename); } catch (UnauthorizedAccessException) { Debug.Log("Access to " + targetFilename + " is denied. Skipping it."); return; } catch (SecurityException) { Debug.Log("You do not have permission to access " + targetFilename + ". Skipping it."); return; } if (info?.IsReadOnly ?? false) { Debug.Log(targetFilename + " is ReadOnly. Skipping it."); return; } } // Generate content using var writer = File.CreateText(targetFilename); writer.NewLine = Environment.NewLine; // Include guard name var guard = Path.GetFileName(targetFilename).Replace(".", "_").ToUpper(); if (!char.IsLetter(guard[0])) guard = "_" + guard; await writer.WriteLineAsync("//"); await writer.WriteLineAsync("// This file was automatically generated. Please don't edit by hand. Execute Editor command [ Edit > Rendering > Generate Shader Includes ] instead"); await writer.WriteLineAsync("//"); await writer.WriteLineAsync(); await writer.WriteLineAsync("#ifndef " + guard); await writer.WriteLineAsync("#define " + guard); foreach (var gen in generators.Where(gen => gen.hasStatics)) await writer.WriteLineAsync(gen.EmitDefines().Replace("\n", writer.NewLine)); foreach (var gen in generators.Where(gen => gen.hasFields)) await writer.WriteLineAsync(gen.EmitTypeDecl().Replace("\n", writer.NewLine)); foreach (var gen in generators.Where(gen => gen.hasFields && gen.needAccessors && !gen.hasPackedInfo)) { await writer.WriteAsync(gen.EmitAccessors().Replace("\n", writer.NewLine)); await writer.WriteAsync(gen.EmitSetters().Replace("\n", writer.NewLine)); const bool emitInitters = true; await writer.WriteAsync(gen.EmitSetters(emitInitters).Replace("\n", writer.NewLine)); } foreach (var gen in generators.Where(gen => gen.hasStatics && gen.hasFields && gen.needParamDebug && !gen.hasPackedInfo)) await writer.WriteLineAsync(gen.EmitFunctions().Replace("\n", writer.NewLine)); foreach (var gen in generators.Where(gen => gen.hasPackedInfo)) await writer.WriteLineAsync(gen.EmitPackedInfo().Replace("\n", writer.NewLine)); await writer.WriteLineAsync(); await writer.WriteLineAsync("#endif"); if (File.Exists(targetCustomFilename)) await writer.WriteAsync($"#include \"{Path.GetFileName(targetCustomFilename)}\""); } } }