Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
EffectCompilerCache.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.IO;
6 using System.Linq;
7 using SiliconStudio.Core;
8 using SiliconStudio.Core.Diagnostics;
9 using SiliconStudio.Core.IO;
10 using SiliconStudio.Core.Serialization;
11 using SiliconStudio.Core.Serialization.Assets;
12 using SiliconStudio.Core.Serialization.Serializers;
13 using SiliconStudio.Core.Storage;
14 using SiliconStudio.Paradox.Effects;
15 
16 namespace SiliconStudio.Paradox.Shaders.Compiler
17 {
18  /// <summary>
19  /// Checks if an effect has already been compiled in its cache before deferring to a real <see cref="IEffectCompiler"/>.
20  /// </summary>
21  [DataSerializerGlobal(null, typeof(KeyValuePair<HashSourceCollection, EffectBytecode>))]
23  {
24  private static readonly Logger Log = GlobalLogger.GetLogger("EffectCompilerCache");
25  private readonly Dictionary<ObjectId, EffectBytecode> storedResults = new Dictionary<ObjectId, EffectBytecode>();
26  private const string CompiledShadersKey = "__shaders_bytecode__";
27 
28  public EffectCompilerCache(EffectCompilerBase compiler) : base(compiler)
29  {
30  }
31 
32  public override EffectBytecode Compile(ShaderMixinSource mixin, string fullEffectName, ShaderMixinParameters compilerParameters, HashSet<string> modifiedShaders, HashSet<string> recentlyModifiedShaders, LoggerResult log)
33  {
34  var database = AssetManager.FileProvider;
35  if (database == null)
36  {
37  throw new NotSupportedException("Using the cache requires to AssetManager.FileProvider to be valid.");
38  }
39 
40  // remove the old shaders
41  if (recentlyModifiedShaders != null && recentlyModifiedShaders.Count != 0)
42  RemoveObsoleteStoredResults(recentlyModifiedShaders);
43 
44  var ids = ShaderMixinObjectId.Compute(mixin, compilerParameters);
45 
46  EffectBytecode bytecode = null;
47  lock (storedResults)
48  {
49  if (storedResults.TryGetValue(ids.FullParametersId, out bytecode))
50  {
51  return bytecode;
52  }
53 
54  // Final url of the compiled bytecode
55  var compiledUrl = string.Format("{0}/{1}", CompiledShadersKey, ids.CompileParametersId);
56 
57  // ------------------------------------------------------------------------------------------------------------
58  // 1) Try to load latest bytecode
59  // ------------------------------------------------------------------------------------------------------------
60  ObjectId bytecodeId;
61  if (database.AssetIndexMap.TryGetValue(compiledUrl, out bytecodeId))
62  {
63  using (var stream = database.ObjectDatabase.OpenStream(bytecodeId))
64  {
65  var localBytecode = BinarySerialization.Read<EffectBytecode>(stream);
66 
67  // If latest bytecode is in sync
68  if (!Platform.IsWindowsDesktop || CheckBytecodeInSyncAgainstSources(localBytecode, database))
69  {
70  bytecode = localBytecode;
71 
72  // if bytecode contains a modified shource, do not use it.
73  if (modifiedShaders != null && modifiedShaders.Count != 0 && IsBytecodeObsolete(bytecode, modifiedShaders))
74  bytecode = null;
75  }
76  }
77  }
78 
79  // On non Windows platform, we are expecting to have the bytecode stored directly
80  if (!Platform.IsWindowsDesktop && bytecode == null)
81  {
82  Log.Error("Unable to find compiled shaders [{0}] for mixin [{1}] with parameters [{2}]", compiledUrl, mixin, compilerParameters.ToStringDetailed());
83  throw new InvalidOperationException("Unable to find compiled shaders [{0}]".ToFormat(compiledUrl));
84  }
85 
86  // ------------------------------------------------------------------------------------------------------------
87  // 2) Try to load from intermediate results
88  // ------------------------------------------------------------------------------------------------------------
89  if (bytecode == null)
90  {
91  // Check if this id has already a ShaderBytecodeStore
92  var isObjectInDatabase = database.ObjectDatabase.Exists(ids.CompileParametersId);
93 
94  // Try to load from an existing ShaderBytecode
95  if ((modifiedShaders == null || modifiedShaders.Count == 0) && isObjectInDatabase)
96  {
97  var stream = database.ObjectDatabase.OpenStream(ids.CompileParametersId, VirtualFileMode.Open, VirtualFileAccess.Read, VirtualFileShare.Read);
98  using (var resultsStore = new ShaderBytecodeStore(stream))
99  {
100  // Load new values
101  resultsStore.LoadNewValues();
102 
103  var storedValues = resultsStore.GetValues();
104 
105  foreach (KeyValuePair<HashSourceCollection, EffectBytecode> hashResults in storedValues)
106  {
107  if (CheckBytecodeInSyncAgainstSources(hashResults.Value, database))
108  {
109  bytecode = hashResults.Value;
110  break;
111  }
112  }
113  }
114  }
115 
116  // --------------------------------------------------------------------------------------------------------
117  // 3) Bytecode was not found in the cache on disk, we need to compile it
118  // --------------------------------------------------------------------------------------------------------
119  if (bytecode == null)
120  {
121  // Open the database for writing
122  var stream = database.ObjectDatabase.OpenStream(ids.CompileParametersId, VirtualFileMode.OpenOrCreate, VirtualFileAccess.ReadWrite, VirtualFileShare.ReadWrite);
123  using (var resultsStore = new ShaderBytecodeStore(stream))
124  {
125  var localLogger = new LoggerResult();
126 
127  // Compile the mixin
128  bytecode = base.Compile(mixin, fullEffectName, compilerParameters, modifiedShaders, recentlyModifiedShaders, localLogger);
129  log.Info("New effect compiled [{0}]\r\n{1}", ids.CompileParametersId, compilerParameters.ToStringDetailed());
130  localLogger.CopyTo(log);
131 
132  // If there are any errors, return immediately
133  if (localLogger.HasErrors)
134  {
135  return null;
136  }
137 
138  // Else store the bytecode for this set of HashSources
139  resultsStore[bytecode.HashSources] = bytecode;
140  }
141  }
142 
143  // Save latest bytecode into the storage
144  using (var stream = database.OpenStream(compiledUrl, VirtualFileMode.Create, VirtualFileAccess.Write, VirtualFileShare.Write))
145  {
146  BinarySerialization.Write(stream, bytecode);
147  }
148  }
149  else
150  {
151  // clone the bytecode since it is not the first time we load it.
152  bytecode = bytecode.Clone();
153  }
154 
155  // Store the bytecode in the memory cache
156  storedResults[ids.FullParametersId] = bytecode;
157  }
158 
159  return bytecode;
160  }
161 
162  private void RemoveObsoleteStoredResults(HashSet<string> modifiedShaders)
163  {
164  lock (storedResults)
165  {
166  var keysToRemove = new List<ObjectId>();
167  foreach (var bytecodePair in storedResults)
168  {
169  if (IsBytecodeObsolete(bytecodePair.Value, modifiedShaders))
170  keysToRemove.Add(bytecodePair.Key);
171  }
172 
173  foreach (var key in keysToRemove)
174  storedResults.Remove(key);
175  }
176  }
177 
178  private bool IsBytecodeObsolete(EffectBytecode bytecode, HashSet<string> modifiedShaders)
179  {
180  return bytecode.HashSources.Any(x => modifiedShaders.Contains(x.Key));
181  }
182 
183  /// <summary>
184  /// Checks if the specified bytecode is synchronized with latest source
185  /// </summary>
186  /// <param name="byteCode">The byte code.</param>
187  /// <param name="database">The database.</param>
188  /// <returns><c>true</c> if bytecode is synchronized with latest source, <c>false</c> otherwise.</returns>
189  private static bool CheckBytecodeInSyncAgainstSources(EffectBytecode byteCode, DatabaseFileProvider database)
190  {
191  var usedSources = byteCode.HashSources;
192 
193  // Find a bytecode that is using the same hash for its pdxsl sources
194  foreach (var usedSource in usedSources)
195  {
196  ObjectId currentId;
197  if (!database.AssetIndexMap.TryGetValue(usedSource.Key, out currentId) || usedSource.Value != currentId)
198  {
199  return false;
200  }
201  }
202  return true;
203  }
204 
205  private class ShaderBytecodeStore : DictionaryStore<HashSourceCollection, EffectBytecode>
206  {
207  public ShaderBytecodeStore(Stream stream) : base(stream)
208  {
209  }
210  }
211  }
212 }
Checks if an effect has already been compiled in its cache before deferring to a real IEffectCompiler...
A mixin performing a combination of ShaderClassSource and other mixins.
SiliconStudio.Core.Diagnostics.LoggerResult LoggerResult
A logger that stores messages locally useful for internal log scenarios.
Definition: LoggerResult.cs:14
Base class for implementations of IEffectCompiler, providing some helper functions.
Base implementation for ILogger.
Definition: Logger.cs:10
static readonly bool IsWindowsDesktop
Gets a value indicating whether the running platform is windows desktop.
Definition: Platform.cs:48
Helper class that delegates actual compilation to another IEffectCompiler.
A hash to uniquely identify data.
Definition: ObjectId.cs:13
Platform specific queries and functions.
Definition: Platform.cs:15
override EffectBytecode Compile(ShaderMixinSource mixin, string fullEffectName, ShaderMixinParameters compilerParameters, HashSet< string > modifiedShaders, HashSet< string > recentlyModifiedShaders, LoggerResult log)
Compiles the ShaderMixinSource into a platform bytecode.
Contains a compiled shader with bytecode for each stage.
bool TryGetValue(string url, out ObjectId objectId)
Output message to log right away.