Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
EffectCompiler.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.Collections.ObjectModel;
6 using System.IO;
7 using System.Text;
8 
9 using SiliconStudio.Core;
10 using SiliconStudio.Core.Diagnostics;
11 using SiliconStudio.Core.Storage;
12 using SiliconStudio.Paradox.Effects;
13 using SiliconStudio.Paradox.Graphics;
14 using SiliconStudio.Paradox.Shaders.Parser;
15 using SiliconStudio.Shaders.Utility;
18 
19 namespace SiliconStudio.Paradox.Shaders.Compiler
20 {
21  /// <summary>
22  /// An <see cref="IEffectCompiler"/> which will compile effect into multiple shader code, and compile them with a <see cref="IShaderCompiler"/>.
23  /// </summary>
25  {
26  private bool d3dcompilerLoaded = false;
27  private static readonly Object WriterLock = new Object();
28 
29  private ShaderMixinParser shaderMixinParser;
30 
31  public List<string> SourceDirectories { get; private set; }
32 
33  public Dictionary<string, string> UrlToFilePath { get; private set; }
34 
35  public EffectCompiler()
36  {
37  NativeLibrary.PreloadLibrary("d3dcompiler_47.dll");
38  SourceDirectories = new List<string>();
39  UrlToFilePath = new Dictionary<string, string>();
40  }
41 
42  #region Public methods
43 
44  public override EffectBytecode Compile(ShaderMixinSource shaderMixinSource, string fullEffectName, ShaderMixinParameters compilerParameters, HashSet<string> modifiedShaders, HashSet<string> recentlyModifiedShaders, LoggerResult log)
45  {
46  // Load D3D compiler dll
47  // Note: No lock, it's probably fine if it gets called from multiple threads at the same time.
48  if (Platform.IsWindowsDesktop && !d3dcompilerLoaded)
49  {
50  NativeLibrary.PreloadLibrary("d3dcompiler_47.dll");
51  d3dcompilerLoaded = true;
52  }
53 
54  // Make a copy of shaderMixinSource. Use deep clone since shaderMixinSource can be altered during compilation (e.g. macros)
55  var shaderMixinSourceCopy = new ShaderMixinSource();
56  shaderMixinSourceCopy.DeepCloneFrom(shaderMixinSource);
57 
58  shaderMixinSource = shaderMixinSourceCopy;
59 
60  // Generate platform-specific macros
61  var platform = compilerParameters.Get(CompilerParameters.GraphicsPlatformKey);
62  switch (platform)
63  {
64  case GraphicsPlatform.Direct3D11:
65  shaderMixinSource.AddMacro("SILICONSTUDIO_PARADOX_GRAPHICS_API_DIRECT3D", 1);
66  shaderMixinSource.AddMacro("SILICONSTUDIO_PARADOX_GRAPHICS_API_DIRECT3D11", 1);
67  break;
68  case GraphicsPlatform.OpenGL:
69  shaderMixinSource.AddMacro("SILICONSTUDIO_PARADOX_GRAPHICS_API_OPENGL", 1);
70  shaderMixinSource.AddMacro("SILICONSTUDIO_PARADOX_GRAPHICS_API_OPENGLCORE", 1);
71  break;
72  case GraphicsPlatform.OpenGLES:
73  shaderMixinSource.AddMacro("SILICONSTUDIO_PARADOX_GRAPHICS_API_OPENGL", 1);
74  shaderMixinSource.AddMacro("SILICONSTUDIO_PARADOX_GRAPHICS_API_OPENGLES", 1);
75  break;
76  default:
77  throw new NotSupportedException();
78  }
79 
80  // Generate the AST from the mixin description
81  if (shaderMixinParser == null)
82  {
83  shaderMixinParser = new ShaderMixinParser();
84  shaderMixinParser.SourceManager.LookupDirectoryList = SourceDirectories; // TODO: temp
85  shaderMixinParser.SourceManager.UrlToFilePath = UrlToFilePath; // TODO: temp
86  }
87 
88  if (recentlyModifiedShaders != null && recentlyModifiedShaders.Count > 0)
89  {
90  shaderMixinParser.DeleteObsoleteCache(GetShaderNames(recentlyModifiedShaders));
91  recentlyModifiedShaders.Clear();
92  }
93  var parsingResult = shaderMixinParser.Parse(shaderMixinSource, shaderMixinSource.Macros.ToArray(), modifiedShaders);
94 
95  // Copy log from parser results to output
96  CopyLogs(parsingResult, log);
97 
98  // Return directly if there are any errors
99  if (parsingResult.HasErrors)
100  {
101  return null;
102  }
103 
104  // Convert the AST to HLSL
105  var writer = new SiliconStudio.Shaders.Writer.Hlsl.HlslWriter {EnablePreprocessorLine = false};
106  writer.Visit(parsingResult.Shader);
107  var shaderSourceText = writer.Text;
108 
109  // -------------------------------------------------------
110  // Save shader log
111  // TODO: TEMP code to allow debugging generated shaders on Windows Desktop
112 #if SILICONSTUDIO_PLATFORM_WINDOWS_DESKTOP
113  var shaderId = ObjectId.FromBytes(Encoding.UTF8.GetBytes(shaderSourceText));
114 
115  var logDir = "log";
116  if (!Directory.Exists(logDir))
117  {
118  Directory.CreateDirectory(logDir);
119  }
120  var shaderSourceFilename = Path.Combine(logDir, "shader_" + shaderId);
121  lock (WriterLock) // protect write in case the same shader is created twice
122  {
123  if (!File.Exists(shaderSourceFilename))
124  {
125  var builder = new StringBuilder();
126  builder.AppendLine("/***** Used Parameters *****");
127  builder.Append(" * EffectName: ");
128  builder.AppendLine(fullEffectName ?? "");
129  WriteParameters(builder, compilerParameters, 0, false);
130  builder.AppendLine(" ***************************/");
131  builder.Append(shaderSourceText);
132  File.WriteAllText(shaderSourceFilename, builder.ToString());
133  }
134  }
135 #endif
136  // -------------------------------------------------------
137 
138  var bytecode = new EffectBytecode { Reflection = parsingResult.Reflection, HashSources = parsingResult.HashSources };
139 
140  // Select the correct backend compiler
141  IShaderCompiler compiler;
142  switch (platform)
143  {
144  case GraphicsPlatform.Direct3D11:
145  compiler = new Direct3D.ShaderCompiler();
146  break;
147  case GraphicsPlatform.OpenGL:
148  case GraphicsPlatform.OpenGLES:
149  compiler = new OpenGL.ShaderCompiler();
150  break;
151  default:
152  throw new NotSupportedException();
153  }
154 
155  var shaderStageBytecodes = new List<ShaderBytecode>();
156 
157  foreach (var stageBinding in parsingResult.EntryPoints)
158  {
159  // Compile
160  var result = compiler.Compile(shaderSourceText, stageBinding.Value, stageBinding.Key, compilerParameters, bytecode.Reflection, shaderSourceFilename);
161  result.CopyTo(log);
162 
163  if (result.HasErrors)
164  {
165  continue;
166  }
167 
168  // -------------------------------------------------------
169  // Append bytecode id to shader log
170 #if SILICONSTUDIO_PLATFORM_WINDOWS_DESKTOP
171  lock (WriterLock) // protect write in case the same shader is created twice
172  {
173  if (File.Exists(shaderSourceFilename))
174  {
175  // Append at the end of the shader the bytecodes Id
176  File.AppendAllText(shaderSourceFilename, "\n// {0} {1}".ToFormat(stageBinding.Key, result.Bytecode.Id));
177  }
178  }
179 #endif
180  // -------------------------------------------------------
181 
182  shaderStageBytecodes.Add(result.Bytecode);
183 
184  // When this is a compute shader, there is no need to scan other stages
185  if (stageBinding.Key == ShaderStage.Compute)
186  break;
187  }
188 
189  // Get the current time of compilation
190  bytecode.Time = DateTime.Now;
191 
192  // In case of Direct3D, we can safely remove reflection data as it is entirely resolved at compile time.
193  if (platform == GraphicsPlatform.Direct3D11)
194  {
195  CleanupReflection(bytecode.Reflection);
196  }
197 
198  bytecode.Stages = shaderStageBytecodes.ToArray();
199  return bytecode;
200  }
201 
202  #endregion
203 
204  #region Private static methods
205 
206  private static void CopyLogs(SiliconStudio.Shaders.Utility.LoggerResult inputLog, LoggerResult outputLog)
207  {
208  foreach (var inputMessage in inputLog.Messages)
209  {
210  var logType = LogMessageType.Info;
211  switch (inputMessage.Level)
212  {
213  case ReportMessageLevel.Error:
214  logType = LogMessageType.Error;
215  break;
216  case ReportMessageLevel.Info:
217  logType = LogMessageType.Info;
218  break;
219  case ReportMessageLevel.Warning:
220  logType = LogMessageType.Warning;
221  break;
222  }
223  var outputMessage = new LogMessage(inputMessage.Span.ToString(), logType, string.Format(" {0}: {1}", inputMessage.Code, inputMessage.Text));
224  outputLog.Log(outputMessage);
225  }
226  outputLog.HasErrors = inputLog.HasErrors;
227  }
228 
229  private static void WriteParameters(StringBuilder builder, ParameterCollection parameters, int indent, bool isArray)
230  {
231  var indentation = "";
232  for (var i = 0; i < indent - 1; ++i)
233  indentation += " ";
234  var first = true;
235  foreach (var usedParam in parameters)
236  {
237  builder.Append(" * ");
238  builder.Append(indentation);
239  if (isArray && first)
240  {
241  builder.Append(" - ");
242  first = false;
243  }
244  else if (indent > 0)
245  builder.Append(" ");
246 
247  if (usedParam.Key == null)
248  builder.Append("NullKey");
249  else
250  builder.Append(usedParam.Key);
251  builder.Append(": ");
252  if (usedParam.Value == null)
253  builder.AppendLine("NullValue");
254  else
255  {
256  builder.AppendLine(usedParam.Value.ToString());
257  if (usedParam.Value is ParameterCollection)
258  WriteParameters(builder, usedParam.Value as ParameterCollection, indent+1, false);
259  else if (usedParam.Value is ParameterCollection[])
260  {
261  var collectionArray = (ParameterCollection[])usedParam.Value;
262  foreach (var collection in collectionArray)
263  WriteParameters(builder, collection, indent + 1, true);
264  }
265 
266  }
267  }
268  }
269 
270  private static void CleanupReflection(EffectReflection reflection)
271  {
272  for (int i = reflection.ConstantBuffers.Count - 1; i >= 0; i--)
273  {
274  var cBuffer = reflection.ConstantBuffers[i];
275  if (cBuffer.Stage == ShaderStage.None)
276  {
277  reflection.ConstantBuffers.RemoveAt(i);
278  }
279  }
280 
281  for (int i = reflection.ResourceBindings.Count - 1; i >= 0; i--)
282  {
283  var resourceBinding = reflection.ResourceBindings[i];
284  if (resourceBinding.Stage == ShaderStage.None)
285  {
286  reflection.ResourceBindings.RemoveAt(i);
287  }
288  }
289  }
290 
291  private static HashSet<string> GetShaderNames(HashSet<string> shaderPaths)
292  {
293  var shaderNames = new HashSet<string>();
294 
295  foreach (var shader in shaderPaths)
296  {
297  if (String.IsNullOrEmpty(shader))
298  continue;
299 
300  var shaderNameWithExtensionParts = shader.Split('/');
301  var shaderNameWithExtension = shaderNameWithExtensionParts[shaderNameWithExtensionParts.Length - 1];
302  var shaderNameParts = shaderNameWithExtension.Split('.');
303  var shaderName = shaderNameParts[0];
304 
305  shaderNames.Add(shaderName);
306  }
307  if (shaderNames.Count == 0)
308  return null;
309 
310  return shaderNames;
311  }
312 
313  #endregion
314  }
315 }
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
GraphicsPlatform
The graphics platform.
Base class for implementations of IEffectCompiler, providing some helper functions.
System.Text.Encoding Encoding
static readonly bool IsWindowsDesktop
Gets a value indicating whether the running platform is windows desktop.
Definition: Platform.cs:48
An IEffectCompiler which will compile effect into multiple shader code, and compile them with a IShad...
System.IO.File File
override EffectBytecode Compile(ShaderMixinSource shaderMixinSource, string fullEffectName, ShaderMixinParameters compilerParameters, HashSet< string > modifiedShaders, HashSet< string > recentlyModifiedShaders, LoggerResult log)
Compiles the ShaderMixinSource into a platform bytecode.
A base log message used by the logging infrastructure.
Definition: LogMessage.cs:13
ShaderStage
Enum to specify shader stage.
Definition: ShaderStage.cs:12
Platform specific queries and functions.
Definition: Platform.cs:15
Contains a compiled shader with bytecode for each stage.
A container to handle a hierarchical collection of effect variables.