Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
EffectSystem.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 
8 using SiliconStudio.Core;
9 using SiliconStudio.Core.Diagnostics;
10 using SiliconStudio.Core.IO;
11 using SiliconStudio.Paradox.Games;
12 using SiliconStudio.Paradox.Graphics;
13 using SiliconStudio.Paradox.Shaders;
14 using SiliconStudio.Paradox.Shaders.Compiler;
15 
16 namespace SiliconStudio.Paradox.Effects
17 {
18  /// <summary>
19  /// The effect system.
20  /// </summary>
22  {
23  #region Private static members
24 
25  public static readonly string DefaultSourceShaderFolder = "shaders";
26 
27  #endregion
28 
29  #region Private members
30 
31  private static Logger Log = GlobalLogger.GetLogger("EffectSystem");
32 
33  private readonly IGraphicsDeviceService graphicsDeviceService;
34  private Shaders.Compiler.IEffectCompiler compiler;
35  private Dictionary<EffectBytecode, Effect> cachedEffects = new Dictionary<EffectBytecode, Effect>();
36  private DirectoryWatcher directoryWatcher;
37 
38  private readonly List<EffectUpdateInfos> effectsToRecompile = new List<EffectUpdateInfos>();
39  private readonly List<EffectUpdateInfos> effectsToUpdate = new List<EffectUpdateInfos>();
40  private readonly List<Effect> updatedEffects = new List<Effect>();
41  private readonly HashSet<string> modifiedShaders = new HashSet<string>();
42  private readonly HashSet<string> recentlyModifiedShaders = new HashSet<string>();
43  private bool clearNextFrame = false;
44 
45  #endregion
46 
47  #region Public members
48 
49  public IEffectCompiler Compiler { get { return compiler; } }
50 
51  #endregion
52 
53  #region Constructor
54 
55  /// <summary>
56  /// Initializes a new instance of the <see cref="EffectSystem"/> class.
57  /// </summary>
58  /// <param name="services">The services.</param>
59  public EffectSystem(IServiceRegistry services)
60  : base(services)
61  {
62  Services.AddService(typeof(EffectSystem), this);
63 
64  // Get graphics device service
65  graphicsDeviceService = Services.GetSafeServiceAs<IGraphicsDeviceService>();
66  }
67 
68  #endregion
69 
70  #region Public methods
71 
72  public override void Initialize()
73  {
74  base.Initialize();
75 
76  // Create compiler
77 #if SILICONSTUDIO_PLATFORM_WINDOWS_DESKTOP
78  var effectCompiler = new Shaders.Compiler.EffectCompiler();
79  effectCompiler.SourceDirectories.Add(DefaultSourceShaderFolder);
80 
81  Enabled = true;
82  directoryWatcher = new DirectoryWatcher("*.pdxsl");
83  directoryWatcher.Modified += FileModifiedEvent;
84 
85  // TODO: pdxfx too
86 #else
87  var effectCompiler = new NullEffectCompiler();
88 #endif
89  compiler = new EffectCompilerCache(effectCompiler);
90  }
91 
92  public override void Update(GameTime gameTime)
93  {
94  base.Update(gameTime);
95 
96  // clear at the beginning of the frame
97  if (clearNextFrame)
98  {
99  foreach (var effect in updatedEffects)
100  {
101  effect.Changed = false;
102  }
103  updatedEffects.Clear();
104 
105  clearNextFrame = false;
106  }
107 
108  lock (effectsToUpdate)
109  {
110  if (effectsToUpdate.Count > 0)
111  {
112  foreach (var effectToUpdate in effectsToUpdate)
113  UpdateEffect(effectToUpdate);
114  clearNextFrame = true;
115  }
116  effectsToUpdate.Clear();
117  }
118  }
119 
120  /// <summary>
121  /// Loads the effect.
122  /// </summary>
123  /// <param name="effectName">Name of the effect.</param>
124  /// <param name="compilerParameters">The compiler parameters.</param>
125  /// <returns>A new instance of an effect.</returns>
126  /// <exception cref="System.InvalidOperationException">Could not compile shader. Need fallback.</exception>
127  public Effect LoadEffect(string effectName, CompilerParameters compilerParameters)
128  {
129  if (effectName == null) throw new ArgumentNullException("effectName");
130  if (compilerParameters == null) throw new ArgumentNullException("compilerParameters");
131 
132  string subEffect;
133  // Get the compiled result
134  var compilerResult = GetCompilerResults(effectName, compilerParameters, out subEffect);
135  CheckResult(compilerResult);
136 
137  if (!compilerResult.Bytecodes.ContainsKey(subEffect))
138  {
139  throw new InvalidOperationException(string.Format("Unable to find sub effect [{0}] from effect [{1}]", subEffect, effectName));
140  }
141 
142  // Only take the sub-effect
143  var bytecode = compilerResult.Bytecodes[subEffect];
144 
145  // return it as a fullname instead
146  // TODO: move this to the underlying result, we should not have to do this here
147  bytecode.Name = effectName;
148 
149  return CreateEffect(bytecode, compilerResult.UsedParameters[subEffect]);
150  }
151 
152  /// <summary>
153  /// Loads the effect and its children.
154  /// </summary>
155  /// <param name="effectName">Name of the effect.</param>
156  /// <param name="compilerParameters">The compiler parameters.</param>
157  /// <returns>A new instance of an effect.</returns>
158  /// <exception cref="System.InvalidOperationException">Could not compile shader. Need fallback.</exception>
159  public Dictionary<string, Effect> LoadEffects(string effectName, CompilerParameters compilerParameters)
160  {
161  if (effectName == null) throw new ArgumentNullException("effectName");
162  if (compilerParameters == null) throw new ArgumentNullException("compilerParameters");
163 
164  string subEffect;
165  var compilerResult = GetCompilerResults(effectName, compilerParameters, out subEffect);
166  CheckResult(compilerResult);
167 
168  var result = new Dictionary<string, Effect>();
169 
170  foreach (var byteCodePair in compilerResult.Bytecodes)
171  {
172  var bytecode = byteCodePair.Value;
173  bytecode.Name = effectName;
174 
175  result.Add(byteCodePair.Key, CreateEffect(bytecode, compilerResult.UsedParameters[byteCodePair.Key]));
176  }
177  return result;
178  }
179 
180  #endregion
181 
182  #region Private methods
183 
184  private static void CheckResult(CompilerResults compilerResult)
185  {
186  // Check errors
187  if (compilerResult.HasErrors)
188  {
189  throw new InvalidOperationException("Could not compile shader. See error messages." + compilerResult.ToText());
190  }
191  }
192 
193  private Effect CreateEffect(EffectBytecode bytecode, ShaderMixinParameters usedParameters)
194  {
195  Effect effect;
196  lock (cachedEffects)
197  {
198  if (!cachedEffects.TryGetValue(bytecode, out effect))
199  {
200  effect = new Effect(graphicsDeviceService.GraphicsDevice, bytecode, usedParameters);
201  effect.Name = bytecode.Name;
202  cachedEffects.Add(bytecode, effect);
203 
204 #if SILICONSTUDIO_PLATFORM_WINDOWS_DESKTOP
205  foreach (var sourcePath in bytecode.HashSources.Keys)
206  {
207  using (var pathStream = Asset.OpenAsStream(sourcePath + "/path"))
208  using (var reader = new StreamReader(pathStream))
209  {
210  var path = reader.ReadToEnd();
211  directoryWatcher.Track(path);
212  }
213  }
214 #endif
215  }
216  }
217  return effect;
218  }
219 
220  private CompilerResults GetCompilerResults(string effectName, CompilerParameters compilerParameters, out string subEffect)
221  {
222  compilerParameters.Profile = GraphicsDevice.ShaderProfile.HasValue ? GraphicsDevice.ShaderProfile.Value : graphicsDeviceService.GraphicsDevice.Features.Profile;
223 #if SILICONSTUDIO_PARADOX_GRAPHICS_API_OPENGLCORE
224  compilerParameters.Platform = GraphicsPlatform.OpenGL;
225 #endif
226 #if SILICONSTUDIO_PARADOX_GRAPHICS_API_OPENGLES
227  compilerParameters.Platform = GraphicsPlatform.OpenGLES;
228 #endif
229 
230  // Get main effect name (before the first dot)
231  var mainEffectNameEnd = effectName.IndexOf('.');
232  var mainEffectName = mainEffectNameEnd != -1 ? effectName.Substring(0, mainEffectNameEnd) : effectName;
233 
234  subEffect = mainEffectNameEnd != -1 ? effectName.Substring(mainEffectNameEnd + 1) : string.Empty;
235 
236  // Compile shader
237  var isPdxfx = ShaderMixinManager.Contains(mainEffectName);
238  var source = isPdxfx ? new ShaderMixinGeneratorSource(mainEffectName) : (ShaderSource)new ShaderClassSource(mainEffectName);
239 
240  var compilerResult = compiler.Compile(source, compilerParameters, modifiedShaders, recentlyModifiedShaders);
241 
242  foreach (var message in compilerResult.Messages)
243  {
244  Log.Log(message);
245  }
246 
247  return compilerResult;
248  }
249 
250  private void FileModifiedEvent(object sender, FileEvent e)
251  {
252  if (e.ChangeType == FileEventChangeType.Changed)
253  {
254  var shaderSourceName = DefaultSourceShaderFolder + "/" + e.Name;
255  modifiedShaders.Add(shaderSourceName);
256  recentlyModifiedShaders.Add(shaderSourceName);
257 
258  lock (cachedEffects)
259  {
260  foreach (var bytecode in cachedEffects.Keys)
261  {
262  if (bytecode.HashSources.ContainsKey(shaderSourceName))
263  {
264  var effect = cachedEffects[bytecode];
265  lock (effectsToRecompile)
266  {
267  EffectUpdateInfos updateInfos;
268  updateInfos.Effect = effect;
269  updateInfos.CompilerResults = null;
270  updateInfos.EffectName = null;
271  updateInfos.SubEffectName = null;
272  updateInfos.OldBytecode = bytecode;
273  effectsToRecompile.Add(updateInfos);
274  }
275  }
276  }
277  }
278 
279  lock (effectsToRecompile)
280  {
281  foreach (var effectToRecompile in effectsToRecompile)
282  {
283  RecompileEffect(effectToRecompile);
284  }
285  effectsToRecompile.Clear();
286  }
287  }
288  }
289 
290  private void RecompileEffect(EffectUpdateInfos updateInfos)
291  {
292  var compilerParameters = new CompilerParameters();
293  updateInfos.Effect.CompilationParameters.CopyTo(compilerParameters);
294  var effectName = updateInfos.Effect.Name;
295 
296  string subEffect;
297  var compilerResult = GetCompilerResults(effectName, compilerParameters, out subEffect);
298 
299  // If there are any errors when recompiling return immediately
300  if (compilerResult.HasErrors)
301  {
302  Log.Error("Effect {0} failed to reompile: {0}", compilerResult.ToText());
303  return;
304  }
305 
306  // update information
307  updateInfos.CompilerResults = compilerResult;
308  updateInfos.EffectName = effectName;
309  updateInfos.SubEffectName = subEffect;
310  lock (effectsToUpdate)
311  {
312  effectsToUpdate.Add(updateInfos);
313  }
314  }
315 
316  private void UpdateEffect(EffectUpdateInfos updateInfos)
317  {
318  EffectBytecode bytecode;
319  try
320  {
321  bytecode = updateInfos.CompilerResults.Bytecodes[updateInfos.SubEffectName];
322  }
323  catch (KeyNotFoundException)
324  {
325  Log.Error("The sub-effect {0} wasn't found in the compiler results.", updateInfos.SubEffectName);
326  return;
327  }
328 
329  ShaderMixinParameters parameters;
330  try
331  {
332  parameters = updateInfos.CompilerResults.UsedParameters[updateInfos.SubEffectName];
333  }
334  catch (KeyNotFoundException)
335  {
336  Log.Error("The sub-effect {0} parameters weren't found in the compiler results.", updateInfos.SubEffectName);
337  return;
338  }
339 
340  bytecode.Name = updateInfos.EffectName;
341  updateInfos.Effect.Initialize(GraphicsDevice, bytecode, parameters);
342  updatedEffects.Add(updateInfos.Effect);
343 
344  lock (cachedEffects)
345  {
346  cachedEffects.Remove(updateInfos.OldBytecode);
347 
348  Effect newEffect;
349  if (!cachedEffects.TryGetValue(bytecode, out newEffect))
350  {
351  cachedEffects.Add(bytecode, updateInfos.Effect);
352  }
353  }
354  }
355 
356  #endregion
357 
358  #region Helpers
359 
360  private struct EffectUpdateInfos
361  {
362  public Effect Effect;
364  public string EffectName;
365  public string SubEffectName;
366  public EffectBytecode OldBytecode;
367  }
368 
369  #endregion
370  }
371 }
A shader source that is linked to a pdxfx effect.
Service providing method to access GraphicsDevice life-cycle.
string ToText()
Returns a string representation of this
Checks if an effect has already been compiled in its cache before deferring to a real IEffectCompiler...
FileEventChangeType ChangeType
Gets the type of the change.
Definition: FileEvent.cs:34
A service registry is a IServiceProvider that provides methods to register and unregister services...
override void Initialize()
This method is called when the component is added to the game.
Definition: EffectSystem.cs:72
Performs primitive-based rendering, creates resources, handles system-level variables, adjusts gamma ramp levels, and creates shaders. See The+GraphicsDevice+class to learn more about the class.
Base class for a GameSystemBase component.
Base implementation for ILogger.
Definition: Logger.cs:10
An IEffectCompiler which will compile effect into multiple shader code, and compile them with a IShad...
Current timing used for variable-step (real time) or fixed-step (game time) games.
Definition: GameTime.cs:31
Dictionary< string, Effect > LoadEffects(string effectName, CompilerParameters compilerParameters)
Loads the effect and its children.
override void Update(GameTime gameTime)
This method is called when this game component is updated.
Definition: EffectSystem.cs:92
EffectSystem(IServiceRegistry services)
Initializes a new instance of the EffectSystem class.
Definition: EffectSystem.cs:59
Track file system events from several directories.
Effect LoadEffect(string effectName, CompilerParameters compilerParameters)
Loads the effect.
bool HasErrors
Gets or sets a value indicating whether this instance has errors.
Definition: Logger.cs:46
Contains a compiled shader with bytecode for each stage.
Output message to log right away.
Main interface used to compile a shader.
FileEventChangeType
Change type of file used by FileEvent and DirectoryWatcher.
Ä file event used notified by DirectoryWatcher
Definition: FileEvent.cs:10