Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
ParadoxShaderLibrary.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 using System.Text;
7 
8 using SiliconStudio.Core.Extensions;
9 using SiliconStudio.Core.Storage;
10 using SiliconStudio.Paradox.Shaders.Parser.Ast;
11 using SiliconStudio.Paradox.Shaders.Parser.Utility;
12 using SiliconStudio.Shaders.Ast;
13 using SiliconStudio.Shaders.Ast.Hlsl;
14 using SiliconStudio.Shaders.Utility;
15 
16 namespace SiliconStudio.Paradox.Shaders.Parser.Mixins
17 {
18  internal class ParadoxShaderLibrary
19  {
20  #region Delegate
21 
22  public delegate ShaderClassType LoadClassSourceDelegate(ShaderClassSource shaderClassSource, SiliconStudio.Shaders.Parser.ShaderMacro[] shaderMacros, out ObjectId hash, out ObjectId hashPreprocessSource);
23 
24  #endregion
25 
26  #region Public members
27 
28  /// <summary>
29  /// List of all the mixin infos
30  /// </summary>
31  public HashSet<ModuleMixinInfo> MixinInfos = new HashSet<ModuleMixinInfo>();
32 
33  /// <summary>
34  /// Load function
35  /// </summary>
36  public ShaderLoader ShaderLoader { get; private set; }
37 
38  /// <summary>
39  /// Log of all the warnings and errors
40  /// </summary>
41  public LoggerResult ErrorWarningLog = new LoggerResult();
42 
43  /// <summary>
44  /// The source hashes
45  /// </summary>
46  public HashSourceCollection SourceHashes = new HashSourceCollection();
47 
48  /// <summary>
49  /// List of shaders that should be loaded from the disk.
50  /// </summary>
51  public HashSet<string> ModifiedShaders = new HashSet<string>();
52 
53  #endregion
54 
55  #region Private members
56 
57  private int lastMixIndex = 0;
58 
59  /// <summary>
60  /// List of contexts per macros
61  /// </summary>
62  private readonly Dictionary<string, List<ModuleMixinInfo>> mapMacrosToMixins = new Dictionary<string, List<ModuleMixinInfo>>();
63 
64  #endregion
65 
66  #region Constructor
67 
68  public ParadoxShaderLibrary(ShaderLoader loader)
69  {
70  ShaderLoader = loader;
71  }
72 
73  #endregion
74 
75  #region Public methods
76 
77  /// <summary>
78  /// Explore the ShaderSource and add the necessary shaders
79  /// </summary>
80  /// <param name="shaderSource">the ShaderSource to explore</param>
81  /// <param name="macros">the macros used</param>
82  /// <returns></returns>
83  public HashSet<ModuleMixinInfo> LoadShaderSource(ShaderSource shaderSource, SiliconStudio.Shaders.Parser.ShaderMacro[] macros)
84  {
85  var mixinsToAnalyze = new HashSet<ModuleMixinInfo>();
86  ExtendLibrary(shaderSource, macros, mixinsToAnalyze);
87  ReplaceMixins(mixinsToAnalyze); // no longer replace mixin, redo analysis everytime since there is no way to correctly detect something changed
88  return mixinsToAnalyze;
89  }
90 
91  /// <summary>
92  /// Deletes the shader cache for the specified shaders.
93  /// </summary>
94  /// <param name="modifiedShaders">The modified shaders.</param>
95  public void DeleteObsoleteCache(HashSet<string> modifiedShaders)
96  {
97  var mixinsToDelete = new HashSet<ModuleMixinInfo>();
98 
99  foreach (var shaderName in modifiedShaders)
100  {
101  // find the mixin that depends on this shader
102  foreach (var mixin in MixinInfos)
103  {
104  if (mixin.MixinName == shaderName)
105  mixinsToDelete.Add(mixin);
106  else
107  {
108  foreach (var dep in mixin.MinimalContext)
109  {
110  if (dep.MixinName == shaderName)
111  mixinsToDelete.Add(mixin);
112  }
113  }
114  }
115 
116  // remove the source hash
117  SourceHashes.Remove(shaderName);
118  }
119 
120  // delete the mixins
121  foreach (var mixin in mixinsToDelete)
122  {
123  MixinInfos.Remove(mixin);
124 
125  // delete the mixin from the map
126  foreach (var macroMap in mapMacrosToMixins)
127  macroMap.Value.Remove(mixin);
128  }
129 
130  mixinsToDelete.Clear();
131 
132  ShaderLoader.DeleteObsoleteCache(modifiedShaders);
133  }
134 
135  #endregion
136 
137  #region Private methods
138 
139  /// <summary>
140  /// Explore the ShaderSource and add the necessary shaders
141  /// </summary>
142  /// <param name="shaderSource">the ShaderSource to explore</param>
143  /// <param name="macros">the macros used</param>
144  private void ExtendLibrary(ShaderSource shaderSource, SiliconStudio.Shaders.Parser.ShaderMacro[] macros, HashSet<ModuleMixinInfo> mixinToAnalyze)
145  {
146  if (shaderSource is ShaderMixinSource)
147  {
148  var newMacros = MergeMacroSets((ShaderMixinSource)shaderSource, macros);
149  mixinToAnalyze.Add(GetModuleMixinInfo(shaderSource, newMacros));
150  foreach (var composition in ((ShaderMixinSource)shaderSource).Compositions)
151  ExtendLibrary(composition.Value, newMacros, mixinToAnalyze);
152  }
153  else if (shaderSource is ShaderClassSource)
154  mixinToAnalyze.Add(GetModuleMixinInfo(shaderSource, macros));
155  else if (shaderSource is ShaderArraySource)
156  {
157  foreach (var shader in ((ShaderArraySource)shaderSource).Values)
158  ExtendLibrary(shader, macros, mixinToAnalyze);
159  }
160  }
161 
162  /// <summary>
163  /// Get the ModuleMixinInfo based on the ShaderSource and the macros. Creates the needed shader if necessary
164  /// </summary>
165  /// <param name="shaderSource">the ShaderSource</param>
166  /// <param name="macros">the macros</param>
167  /// <param name="macrosString">the name of the macros</param>
168  /// <returns>ModuleMixinInfo.</returns>
169  private ModuleMixinInfo GetModuleMixinInfo(ShaderSource shaderSource, SiliconStudio.Shaders.Parser.ShaderMacro[] macros, string macrosString = null)
170  {
171  if (macros == null)
173 
174  if (macrosString == null)
175  {
176  macrosString = string.Join(",", macros.OrderBy(x => x.Name));
177  }
178 
179  List<ModuleMixinInfo> context;
180  if (!mapMacrosToMixins.TryGetValue(macrosString, out context))
181  {
182  context = new List<ModuleMixinInfo>();
183  mapMacrosToMixins.Add(macrosString, context);
184  }
185 
186  var mixinInfo = context.FirstOrDefault(x => x.AreEqual(shaderSource, macros));
187  if (mixinInfo == null)
188  {
189  mixinInfo = BuildMixinInfo(shaderSource, macros);
190 
191  if (mixinInfo.Instanciated)
192  {
193  MixinInfos.Add(mixinInfo);
194  mapMacrosToMixins[macrosString].Add(mixinInfo);
195 
196  mixinInfo.MinimalContext.Add(mixinInfo);
197 
198  if (!mixinInfo.Log.HasErrors)
199  {
200  LoadNecessaryShaders(mixinInfo, macros, macrosString);
201  }
202  mixinInfo.MinimalContext = new HashSet<ModuleMixinInfo>(mixinInfo.MinimalContext.Distinct());
203  }
204  }
205 
206  return mixinInfo;
207  }
208 
209  /// <summary>
210  /// Replace the mixins
211  /// </summary>
212  /// <param name="mixinInfos">the mixins to verify</param>
213  private void ReplaceMixins(HashSet<ModuleMixinInfo> mixinInfos)
214  {
215  foreach (var mixinInfo in mixinInfos)
216  CheckMixinForReplacement(mixinInfo);
217  }
218 
219  /// <summary>
220  /// Check if a previously analyzed instance of the shader can be used
221  /// </summary>
222  /// <param name="mixinInfo">the ModuleMixinInfo</param>
223  private void CheckMixinForReplacement(ModuleMixinInfo mixinInfo)
224  {
225  // TODO: infinite loop when cross reference (composition & =stage for example)
226  // TODO: change ReplacementChecked to enum None/InProgress/Done
227  if (mixinInfo.ReplacementChecked)
228  return;
229 
230  // Check parents and dependencies
231  mixinInfo.MinimalContext.Where(x => x != mixinInfo).ForEach(CheckMixinForReplacement);
232 
233  foreach (var replaceCandidateMixinInfo in MixinInfos.Where(x => x != mixinInfo && x.ShaderSource.Equals(mixinInfo.ShaderSource) && x.HashPreprocessSource == mixinInfo.HashPreprocessSource))
234  {
235  if (replaceCandidateMixinInfo != null && replaceCandidateMixinInfo.Mixin.DependenciesStatus != AnalysisStatus.None)
236  {
237  if (replaceCandidateMixinInfo.Mixin.MinimalContext != null)
238  {
239  var noNeedToReplaced = replaceCandidateMixinInfo.Mixin.MinimalContext
240  .Where(dep => dep != replaceCandidateMixinInfo.Mixin)
241  .All(dep => mixinInfo.MinimalContext.FirstOrDefault(x => x.Mixin == dep) != null);
242  if (noNeedToReplaced)
243  {
244  mixinInfo.Mixin = replaceCandidateMixinInfo.Mixin;
245  mixinInfo.MixinAst = replaceCandidateMixinInfo.MixinAst;
246  mixinInfo.MixinGenericName = replaceCandidateMixinInfo.MixinGenericName;
247  break;
248  }
249  }
250  }
251  }
252 
253  mixinInfo.ReplacementChecked = true;
254  }
255 
256  /// <summary>
257  /// Build the ModuleMixinInfo class
258  /// </summary>
259  /// <param name="shaderSource">the ShaderSource to load</param>
260  /// <param name="macros">the macros applied on the source</param>
261  /// <returns>the ModuleMixinInfo</returns>
262  private ModuleMixinInfo BuildMixinInfo(ShaderSource shaderSource, SiliconStudio.Shaders.Parser.ShaderMacro[] macros)
263  {
264  ModuleMixinInfo mixinInfo = null;
265 
266  if (shaderSource is ShaderClassSource)
267  {
268  var shaderClassSource = shaderSource as ShaderClassSource;
269  mixinInfo = new ModuleMixinInfo { ShaderSource = shaderClassSource, Macros = macros };
270  LoadMixinFromClassSource(mixinInfo);
271  }
272  else if (shaderSource is ShaderMixinSource)
273  {
274  var shaderMixinSource = shaderSource as ShaderMixinSource;
275 
276  var shaderName = "Mix" + lastMixIndex;
277  ++lastMixIndex;
278  var fakeAst = new ShaderClassType(shaderName);
279  foreach (var classSource in shaderMixinSource.Mixins)
280  {
281  Identifier name;
282  if (classSource.GenericArguments != null && classSource.GenericArguments.Length > 0)
283  name = new IdentifierGeneric(classSource.ClassName, classSource.GenericArguments.Select(x => new Identifier(x.ToString())).ToArray());
284  else
285  name = new Identifier(classSource.ClassName);
286 
287  fakeAst.BaseClasses.Add(new TypeName(name));
288  }
289 
290  mixinInfo = new ModuleMixinInfo
291  {
292  MixinGenericName = shaderName,
293  Macros = macros,
294  MixinAst = fakeAst,
295  ShaderSource = shaderSource,
296  SourceHash = ObjectId.FromBytes(Encoding.UTF8.GetBytes(shaderName)),
297  Instanciated = true
298  };
299  }
300 
301  return mixinInfo;
302  }
303 
304  /// <summary>
305  /// Loads the mixin based on its ShaderSource
306  /// </summary>
307  /// <param name="mixinInfo">the ModuleMixinInfo</param>
308  private void LoadMixinFromClassSource(ModuleMixinInfo mixinInfo)
309  {
310  var classSource = (ShaderClassSource)mixinInfo.ShaderSource;
311 
312  var shaderClass = ShaderLoader.LoadClassSource(classSource, mixinInfo.Macros, mixinInfo.Log, ModifiedShaders);
313 
314  // If result is null, there was some errors while parsing.
315  if (shaderClass == null)
316  return;
317 
318  shaderClass = shaderClass.DeepClone();
319 
320  if (shaderClass.ShaderGenerics != null && shaderClass.ShaderGenerics.Count != 0)
321  mixinInfo.Instanciated = false;
322 
323  mixinInfo.HashPreprocessSource = shaderClass.PreprocessedSourceHash;
324  mixinInfo.SourceHash = shaderClass.SourceHash;
325 
326  if (!SourceHashes.ContainsKey(classSource.ClassName))
327  SourceHashes.Add(classSource.ClassName, shaderClass.SourceHash);
328 
329  // check if it was a generic class and find out if the instanciation was correct
330  if (shaderClass.GenericParameters.Count > 0)
331  {
332  if (classSource.GenericArguments == null || classSource.GenericArguments.Length == 0 || shaderClass.GenericParameters.Count > classSource.GenericArguments.Length)
333  {
334  mixinInfo.Instanciated = false;
335  mixinInfo.Log.Error(ParadoxMessageCode.ErrorClassSourceNotInstantiated, shaderClass.Span, classSource.ClassName);
336  }
337  else
338  {
339  ModuleMixinInfo.CleanIdentifiers(shaderClass.GenericParameters.Select(x => x.Name).ToList());
340  }
341  }
342 
343  mixinInfo.MixinAst = shaderClass;
344  mixinInfo.MixinGenericName = classSource.ClassName;
345  }
346 
347  /// <summary>
348  /// Loads generic classes that may appear in the mixin
349  /// </summary>
350  /// <param name="mixinInfo">The mixin to investigate</param>
351  /// <param name="macros">The macros.</param>
352  /// <param name="macrosString">The macros string.</param>
353  private void LoadNecessaryShaders(ModuleMixinInfo mixinInfo, SiliconStudio.Shaders.Parser.ShaderMacro[] macros, string macrosString)
354  {
355  if (!mixinInfo.Instanciated)
356  return;
357 
358  // Look for all the generic calls
359  var shaderDependencyVisitor = new ShaderDependencyVisitor(mixinInfo.Log, ShaderLoader.SourceManager);
360  shaderDependencyVisitor.Run(mixinInfo.MixinAst);
361 
362  foreach (var foundClass in shaderDependencyVisitor.FoundClasses)
363  {
364  var classSource = new ShaderClassSource(foundClass, null);
365  var foundMixinInfo = GetModuleMixinInfo(classSource, macros, macrosString);
366  mixinInfo.MinimalContext.UnionWith(foundMixinInfo.MinimalContext);
367  }
368 
369  foreach (var id in shaderDependencyVisitor.FoundIdentifiers)
370  {
371  var genericClass = id.Item1;
372  ModuleMixinInfo.CleanIdentifiers(genericClass.Identifiers);
373  var genericParams = BuildShaderGenericParameters(genericClass);
374  var classSource = new ShaderClassSource(genericClass.Text, genericParams);
375 
376  var instanciatedClassInfo = GetModuleMixinInfo(classSource, macros, macrosString);
377  mixinInfo.MinimalContext.UnionWith(instanciatedClassInfo.MinimalContext);
378 
379  var newId = new Identifier(instanciatedClassInfo.MixinName);
380  if (id.Item2 is TypeName) // in the baseclass list or in a variable declaration
381  (id.Item2 as TypeName).Name = newId;
382  else if (id.Item2 is VariableReferenceExpression)
383  (id.Item2 as VariableReferenceExpression).Name = newId;
384  else if (id.Item2 is MemberReferenceExpression)
385  (id.Item2 as MemberReferenceExpression).Member = newId;
386  }
387  }
388 
389  #endregion
390 
391  #region Private static methods
392 
393  /// <summary>
394  /// Build the array of generic parameters
395  /// </summary>
396  /// <param name="genericClass">the shader with its generics</param>
397  /// <returns>the array of generic parameters</returns>
398  private static string[] BuildShaderGenericParameters(IdentifierGeneric genericClass)
399  {
400  var genericParameters = new List<string>();
401 
402  for (int i = 0; i < genericClass.Identifiers.Count; ++i)
403  {
404  var genericName = GetIdentifierName(genericClass.Identifiers[i]);
405  genericParameters.Add(genericName);
406  }
407 
408  return genericParameters.ToArray();
409  }
410 
411  /// <summary>
412  /// Helper function to get the complete name of an identifier
413  /// </summary>
414  /// <param name="identifier">the identifier</param>
415  /// <returns>the identifier name</returns>
416  private static string GetIdentifierName(Identifier identifier)
417  {
418  string genericName;
419  if (identifier is LiteralIdentifier)
420  genericName = (identifier as LiteralIdentifier).Value.Value.ToString();
421  else if (identifier is IdentifierDot)
422  {
423  var idDot = identifier as IdentifierDot;
424  genericName = idDot.Identifiers.Aggregate("", (current, id) => current + (GetIdentifierName(id) + idDot.Separator));
425  genericName = genericName.Substring(0, genericName.Length - idDot.Separator.Length);
426  }
427  else
428  genericName = identifier.Text;
429 
430  if (genericName == null)
431  throw new Exception(string.Format("Unable to find the name of the generic [{0}]", identifier));
432 
433  return genericName;
434  }
435 
436  /// <summary>
437  /// Merge the set of macros in the mixin. The top level macros are always overidden by the child's ones (the one defined in the current ShaderMixinSource).
438  /// Also update the macros of the mixin.
439  /// </summary>
440  /// <param name="mixin">The mixin that will be looked at with the macros.</param>
441  /// <param name="macros">The external macros.</param>
442  /// <returns>An array with all the macros</returns>
443  private SiliconStudio.Shaders.Parser.ShaderMacro[] MergeMacroSets(ShaderMixinSource mixin, SiliconStudio.Shaders.Parser.ShaderMacro[] macros)
444  {
445  var newMacros = new List<SiliconStudio.Shaders.Parser.ShaderMacro>();
446 
447  // get the parent macros
448  foreach (var macro in macros)
449  {
450  newMacros.RemoveAll(x => x.Name == macro.Name);
451  newMacros.Add(macro);
452  }
453 
454  // override with child macros, the mixin's ones
455  foreach (var macro in mixin.Macros)
456  {
457  newMacros.RemoveAll(x => x.Name == macro.Name);
458  var tempMacro = new SiliconStudio.Shaders.Parser.ShaderMacro(macro.Name, macro.Definition);
459  newMacros.Add(tempMacro);
460  }
461 
462  mixin.Macros = newMacros.Select(x => new ShaderMacro(x.Name, x.Definition)).ToList();
463  return newMacros.ToArray();
464  }
465 
466  #endregion
467  }
468 }
SiliconStudio.Paradox.Shaders.ShaderMacro ShaderMacro
A typeless reference.
Definition: TypeName.cs:10
Macro to be used with PreProcessor.
Definition: ShaderMacro.cs:11
Search recursively all in and out dependencies.
SiliconStudio.Core.Diagnostics.LoggerResult LoggerResult
A class to collect parsing/expression messages.
Definition: LoggerResult.cs:13
A member reference in the form {this}.{Name}
A generic identifier in the form Typename
A hash to uniquely identify data.
Definition: ObjectId.cs:13
List< Identifier > Identifiers
Gets or sets the path.
AnalysisStatus
A status needed to analyze the mixin in the correct order within a compilation module ...
Definition: ModuleMixin.cs:276