Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
PreProcessor.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 System.Runtime.InteropServices;
8 using System.Text;
9 using System.Text.RegularExpressions;
10 using SiliconStudio.Shaders.Parser;
11 
12 namespace SiliconStudio.Shaders
13 {
14  /// <summary>
15  /// C++ preprocessor using D3DPreprocess method from d3dcompiler API.
16  /// </summary>
17  public partial class PreProcessor
18  {
19 #if !FRAMEWORK_SHADER_USE_SHARPDX
20  [Guid("8BA5FB08-5195-40e2-AC58-0D989C3A0102"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
21  internal interface IBlob
22  {
23  [PreserveSig]
24  System.IntPtr GetBufferPointer();
25 
26  [PreserveSig]
27  System.IntPtr GetBufferSize();
28  }
29 
30  [DllImport("d3dcompiler_43.dll", EntryPoint = "D3DPreprocess", PreserveSig = true, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Ansi)]
31  private static extern int D3DPreprocess(IntPtr ptrData, IntPtr size, [MarshalAs(UnmanagedType.LPStr)] string pSourceName, [MarshalAs(UnmanagedType.LPArray)] ShaderMacro[] pDefines, IntPtr pInclude, [Out] out IBlob ppCodeText, [Out] out IBlob ppErrorMsgs);
32 #endif
33 
34  /// <summary>
35  /// Preprocesses the provided shader or effect source.
36  /// </summary>
37  /// <param name="shaderSource">An array of bytes containing the raw source of the shader or effect to preprocess.</param>
38  /// <param name="sourceFileName">Name of the source file.</param>
39  /// <param name="defines">A set of macros to define during preprocessing.</param>
40  /// <param name="includeDirectories">The include directories used by the preprocessor.</param>
41  /// <returns>
42  /// The preprocessed shader source.
43  /// </returns>
44  public static string Run(string shaderSource, string sourceFileName, ShaderMacro[] defines = null, params string[] includeDirectories)
45  {
46  // Use a default include handler
47  var defaultHandler = new DefaultIncludeHandler();
48 
49  if (includeDirectories != null)
50  defaultHandler.AddDirectories(includeDirectories);
51 
52  defaultHandler.AddDirectory(Environment.CurrentDirectory);
53 
54  var directoryName = Path.GetDirectoryName(sourceFileName);
55  if (!string.IsNullOrEmpty(directoryName))
56  defaultHandler.AddDirectory(directoryName);
57 
58  // Run the processor
59  return Run(shaderSource, sourceFileName, defines, defaultHandler);
60  }
61 
62  /// <summary>
63  /// Preprocesses the provided shader or effect source.
64  /// </summary>
65  /// <param name="shaderSource">An array of bytes containing the raw source of the shader or effect to preprocess.</param>
66  /// <param name="sourceFileName">Name of the source file.</param>
67  /// <param name="defines">A set of macros to define during preprocessing.</param>
68  /// <param name="include">An interface for handling include files.</param>
69  /// <returns>
70  /// The preprocessed shader source.
71  /// </returns>
72  public static string Run(string shaderSource, string sourceFileName = null, ShaderMacro[] defines = null, IncludeHandler include = null)
73  {
74  // Stringify/Concat not supported by D3DCompiler preprocessor.
75  shaderSource = ConcatenateTokens(shaderSource, defines);
76 
77  try
78  {
79 #if FRAMEWORK_SHADER_USE_SHARPDX
80  string compilationErrors;
81  shaderSource = SharpDX.D3DCompiler.ShaderBytecode.Preprocess(
82  shaderSource,
83  defines != null ? defines.Select(x => new SharpDX.Direct3D.ShaderMacro(x.Name, x.Definition)).ToArray() : null,
84  new IncludeShadow(include),
85  out compilationErrors, sourceFileName);
86 #else
87  IBlob blobForText = null;
88  IBlob blobForErrors = null;
89 
90  var shadow = include == null ? null : new IncludeShadow(include);
91 
92  var data = Encoding.ASCII.GetBytes(shaderSource);
93  int result;
94  unsafe
95  {
96  fixed (void* pData = data)
97  result = D3DPreprocess((IntPtr)pData, new IntPtr(data.Length), sourceFileName, PrepareMacros(defines), shadow != null ? shadow.NativePointer : IntPtr.Zero, out blobForText, out blobForErrors);
98  }
99 
100  if (shadow != null)
101  shadow.Dispose();
102 
103  if (result < 0)
104  throw new InvalidOperationException(string.Format("Include errors: {0}", blobForErrors == null ? "" : Marshal.PtrToStringAnsi(blobForErrors.GetBufferPointer())));
105 
106  shaderSource = Marshal.PtrToStringAnsi(blobForText.GetBufferPointer());
107 #endif
108  } catch (Exception ex)
109  {
110  Console.WriteLine("Warning, error while preprocessing file [{0}] : {1}", sourceFileName, ex.Message);
111  }
112  return shaderSource;
113  }
114 
115  internal static ShaderMacro[] PrepareMacros(ShaderMacro[] macros)
116  {
117  if (macros == null)
118  return null;
119 
120  if (macros.Length == 0)
121  return null;
122 
123  if (macros[macros.Length - 1].Name == null && macros[macros.Length - 1].Definition == null)
124  return macros;
125 
126  var macroArray = new ShaderMacro[macros.Length + 1];
127 
128  Array.Copy(macros, macroArray, macros.Length);
129 
130  macroArray[macros.Length] = new ShaderMacro();
131  return macroArray;
132  }
133 
134 #if FRAMEWORK_SHADER_USE_SHARPDX
135  /// <summary>
136  /// Shadow callback for <see cref="IncludeHandler"/>.
137  /// </summary>
138  internal class IncludeShadow : SharpDX.CallbackBase, SharpDX.D3DCompiler.Include
139  {
140  private readonly IncludeHandler callback;
141 
142  public IncludeShadow(IncludeHandler callback)
143  {
144  this.callback = callback;
145  }
146 
147  public Stream Open(SharpDX.D3DCompiler.IncludeType type, string fileName, Stream parentStream)
148  {
149  return callback.Open((IncludeType)type, fileName, parentStream);
150  }
151 
152  public void Close(Stream stream)
153  {
154  callback.Close(stream);
155  }
156  }
157 #else
158  /// <summary>
159  /// Shadow callback for <see cref="IncludeHandler"/>.
160  /// </summary>
161  internal class IncludeShadow : IDisposable
162  {
163  private static readonly IncludeVtbl Vtbl = new IncludeVtbl();
164  private readonly GCHandle handle;
165  private Dictionary<IntPtr, Frame> _frames;
166 
167  public IntPtr NativePointer;
168 
169  public IncludeHandler Callback { get; set; }
170 
171  public IncludeShadow(IncludeHandler callback)
172  {
173  this.Callback = callback;
174  // Allocate ptr to vtbl + ptr to callback together
175  NativePointer = Marshal.AllocHGlobal(IntPtr.Size * 2);
176 
177  handle = GCHandle.Alloc(this);
178  Marshal.WriteIntPtr(NativePointer, Vtbl.Pointer);
179  Marshal.WriteIntPtr(NativePointer, IntPtr.Size, GCHandle.ToIntPtr(handle));
180 
181  _frames = new Dictionary<IntPtr, Frame>();
182  }
183 
184  /// <summary>
185  /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
186  /// </summary>
187  public void Dispose()
188  {
189  Dispose(true);
190  }
191 
192  protected void Dispose(bool disposing)
193  {
194  if (NativePointer != IntPtr.Zero)
195  {
196  handle.Free();
197 
198  foreach (var frame in _frames)
199  frame.Value.Close();
200  _frames = null;
201 
202  // Free instance
203  Marshal.FreeHGlobal(NativePointer);
204  NativePointer = IntPtr.Zero;
205  }
206  Callback = null;
207  }
208 
209 
210  /// <summary>
211  /// Read stream to a byte[] buffer
212  /// </summary>
213  /// <param name = "stream">input stream</param>
214  /// <returns>a byte[] buffer</returns>
215  private static byte[] ReadStream(Stream stream)
216  {
217  int readLength = 0;
218  return ReadStream(stream, ref readLength);
219  }
220 
221  /// <summary>
222  /// Read stream to a byte[] buffer
223  /// </summary>
224  /// <param name = "stream">input stream</param>
225  /// <param name = "readLength">length to read</param>
226  /// <returns>a byte[] buffer</returns>
227  private static byte[] ReadStream(Stream stream, ref int readLength)
228  {
229  int num = readLength;
230  if (num == 0)
231  readLength = (int)(stream.Length - stream.Position);
232  num = readLength;
233 
234  System.Diagnostics.Debug.Assert(num >= 0);
235  if (num == 0)
236  return new byte[0];
237 
238  byte[] buffer = new byte[num];
239  int bytesRead = 0;
240  if (num > 0)
241  {
242  do
243  {
244  bytesRead += stream.Read(buffer, bytesRead, readLength - bytesRead);
245  } while (bytesRead < readLength);
246  }
247  return buffer;
248  }
249 
250  private class Frame
251  {
252  public Frame(Stream stream, GCHandle handle)
253  {
254  Stream = stream;
255  Handle = handle;
256  }
257 
258  public bool IsClosed;
259  public Stream Stream;
260  public GCHandle Handle;
261 
262  public void Close()
263  {
264  if (IsClosed)
265  return;
266  Stream = null;
267  Handle.Free();
268  IsClosed = true;
269  }
270  }
271 
272  /// <summary>
273  /// Internal Include Callback
274  /// </summary>
275  private class IncludeVtbl
276  {
277  public IntPtr Pointer;
278  private List<Delegate> methods;
279 
280  /// <summary>
281  /// Add a method supported by this interface. This method is typically called from inherited constructor.
282  /// </summary>
283  /// <param name="method">the managed delegate method</param>
284  private void AddMethod(Delegate method)
285  {
286  int index = methods.Count;
287  methods.Add(method);
288  Marshal.WriteIntPtr(Pointer, index * IntPtr.Size, Marshal.GetFunctionPointerForDelegate(method));
289  }
290 
291  public IncludeVtbl()
292  {
293  // Allocate ptr to vtbl
294  Pointer = Marshal.AllocHGlobal(IntPtr.Size * 2);
295  methods = new List<Delegate>();
296 
297  AddMethod(new OpenDelegate(OpenImpl));
298  AddMethod(new CloseDelegate(CloseImpl));
299  }
300 
301 
302  private static IncludeShadow ToShadow(IntPtr thisPtr)
303  {
304  var handle = GCHandle.FromIntPtr(Marshal.ReadIntPtr(thisPtr, IntPtr.Size));
305  return (IncludeShadow)handle.Target;
306  }
307 
308  /// <summary>
309  /// A user-implemented method for opening and reading the contents of a shader #include file.
310  /// </summary>
311  /// <param name="thisPtr">This pointer</param>
312  /// <param name="includeType">A <see cref="SharpDX.D3DCompiler.IncludeType"/>-typed value that indicates the location of the #include file. </param>
313  /// <param name="fileNameRef">Name of the #include file.</param>
314  /// <param name="pParentData">Pointer to the container that includes the #include file.</param>
315  /// <param name="dataRef">Pointer to the buffer that Open returns that contains the include directives. This pointer remains valid until <see cref="SharpDX.D3DCompiler.Include.Close"/> is called.</param>
316  /// <param name="bytesRef">Pointer to the number of bytes that Open returns in ppData.</param>
317  /// <returns>The user-implemented method should return S_OK. If Open fails when reading the #include file, the application programming interface (API) that caused Open to be called fails. This failure can occur in one of the following situations:The high-level shader language (HLSL) shader fails one of the D3D10CompileShader*** functions.The effect fails one of the D3D10CreateEffect*** functions.</returns>
318  [UnmanagedFunctionPointer(CallingConvention.StdCall)]
319  private delegate int OpenDelegate(IntPtr thisPtr, IncludeType includeType, IntPtr fileNameRef, IntPtr pParentData, out IntPtr dataRef, out int bytesRef);
320  private static int OpenImpl(IntPtr thisPtr, IncludeType includeType, IntPtr fileNameRef, IntPtr pParentData, out IntPtr dataRef, out int bytesRef)
321  {
322  dataRef = IntPtr.Zero;
323  bytesRef = 0;
324  try
325  {
326 
327  var shadow = ToShadow(thisPtr);
328  var callback = shadow.Callback;
329 
330  Stream stream = null;
331  Stream parentStream = null;
332 
333  if (shadow._frames.ContainsKey(pParentData))
334  parentStream = shadow._frames[pParentData].Stream;
335 
336  stream = callback.Open(includeType, Marshal.PtrToStringAnsi(fileNameRef), parentStream);
337  if (stream == null)
338  return -1;
339 
340  // Read the stream into a byte array and pin it
341  byte[] data = ReadStream(stream);
342  var handle = GCHandle.Alloc(data, GCHandleType.Pinned);
343  dataRef = handle.AddrOfPinnedObject();
344  bytesRef = data.Length;
345 
346  shadow._frames.Add(dataRef, new Frame(stream, handle));
347 
348  return 0;
349  }
350  catch (Exception)
351  {
352  return -1;
353  }
354  }
355 
356  /// <summary>
357  /// A user-implemented method for closing a shader #include file.
358  /// </summary>
359  /// <remarks>
360  /// If <see cref="SharpDX.D3DCompiler.Include.Open"/> was successful, Close is guaranteed to be called before the API using the <see cref="SharpDX.D3DCompiler.Include"/> interface returns.
361  /// </remarks>
362  /// <param name="thisPtr">This pointer</param>
363  /// <param name="pData">Pointer to the buffer that contains the include directives. This is the pointer that was returned by the corresponding <see cref="SharpDX.D3DCompiler.Include.Open"/> call.</param>
364  /// <returns>The user-implemented Close method should return S_OK. If Close fails when it closes the #include file, the application programming interface (API) that caused Close to be called fails. This failure can occur in one of the following situations:The high-level shader language (HLSL) shader fails one of the D3D10CompileShader*** functions.The effect fails one of the D3D10CreateEffect*** functions.</returns>
365  [UnmanagedFunctionPointer(CallingConvention.StdCall)]
366  private delegate int CloseDelegate(IntPtr thisPtr, IntPtr pData);
367  private static int CloseImpl(IntPtr thisPtr, IntPtr pData)
368  {
369  try
370  {
371  var shadow = ToShadow(thisPtr);
372  var callback = shadow.Callback;
373 
374  Frame frame;
375  if (shadow._frames.TryGetValue(pData, out frame))
376  {
377  callback.Close(frame.Stream);
378  }
379  return 0;
380  }
381  catch (Exception)
382  {
383  return -1;
384  }
385  }
386  }
387  }
388 #endif
389 
390  private readonly static Regex ConcatenateTokensRegex = new Regex(@"(\w*)\s*#(#)?\s*(\w+)", RegexOptions.Compiled);
391  private readonly static HashSet<string> PreprocessorKeywords = new HashSet<string>(new[] { "if", "else", "elif", "endif", "define", "undef", "ifdef", "ifndef", "line", "error", "pragma", "include" });
392 
393  private static string ConcatenateTokens(string source, IEnumerable<ShaderMacro> macros)
394  {
395  // TODO: This code is not reliable with comments
396  // Avoid using costly regex if there is no # tokens
397  if (!source.Contains('#'))
398  {
399  return source;
400  }
401 
402  var newSource = new StringBuilder();
403  var reader = new StringReader(source);
404  string sourceLine;
405  while ((sourceLine= reader.ReadLine()) != null)
406  {
407  string line;
408  string comment = null;
409 
410  // If source starts by a pragma, skip this line
411  if (sourceLine.TrimStart().StartsWith("#"))
412  {
413  newSource.AppendLine(sourceLine);
414  continue;
415  }
416 
417  // If source starts by a comment, separate it from the part to process
418  int indexComment = sourceLine.IndexOf("//", StringComparison.InvariantCultureIgnoreCase);
419  if (indexComment >= 0)
420  {
421  comment = sourceLine.Substring(indexComment, sourceLine.Length - indexComment);
422  line = sourceLine.Substring(0, indexComment);
423  }
424  else
425  {
426  line = sourceLine;
427  }
428 
429  // Process every A ## B ## C ## ... patterns
430  // Find first match
431  int position = 0;
432  var match = ConcatenateTokensRegex.Match(line, position);
433 
434  // Early exit
435  if (!match.Success)
436  {
437  newSource.AppendLine(sourceLine);
438  continue;
439  }
440 
441  // Create map of macros.
442  Dictionary<string, string> macroMap = null;
443  int addLength = 0;
444  if (macros != null)
445  {
446  macroMap = new Dictionary<string, string>();
447  foreach (var shaderMacro in macros)
448  {
449  macroMap[shaderMacro.Name] = shaderMacro.Definition;
450 
451  if (shaderMacro.Definition != null)
452  {
453  addLength += shaderMacro.Definition.Length;
454  }
455  }
456  }
457 
458  var stringBuilder = new StringBuilder(line.Length + addLength);
459 
460  while (match.Success)
461  {
462  // Add what was before regex
463  stringBuilder.Append(line, position, match.Index - position);
464 
465  // Check if # (stringify) or ## (concat)
466  bool stringify = !match.Groups[2].Success;
467 
468  var group = match.Groups[3];
469  var token = group.Value;
470  if (stringify && PreprocessorKeywords.Contains(token))
471  {
472  // Ignore some special preprocessor tokens
473  stringBuilder.Append(match.Groups[0].Value);
474  }
475  else
476  {
477  // Expand and add first macro
478  stringBuilder.Append(TransformToken(match.Groups[1].Value, macroMap));
479 
480  if (stringify) // stringification
481  {
482  stringBuilder.Append('"');
483  // TODO: Escape string
484  stringBuilder.Append(EscapeString(TransformToken(token, macroMap, true)));
485  stringBuilder.Append('"');
486  }
487  else // concatenation
488  {
489  stringBuilder.Append(TransformToken(token, macroMap));
490  }
491  }
492 
493  // Find next match
494  position = group.Index + group.Length;
495  match = ConcatenateTokensRegex.Match(line, position);
496  }
497 
498  // Add what is after regex
499  stringBuilder.Append(line, position, line.Length - position);
500 
501  if (comment != null)
502  {
503  stringBuilder.Append(comment);
504  }
505 
506  newSource.AppendLine(stringBuilder.ToString());
507  }
508 
509  return newSource.ToString();
510  }
511 
512  private static string TransformToken(string token, Dictionary<string, string> macros, bool emptyIfNotFound = false)
513  {
514  if (macros == null)
515  return token;
516 
517  string result;
518  return macros.TryGetValue(token, out result) ? result : emptyIfNotFound ? string.Empty : token;
519  }
520 
521  private static string EscapeString(string s)
522  {
523  return s.Replace("\\", "\\\\").Replace("\"", "\\\"");
524  }
525  }
526 }
SiliconStudio.Paradox.Shaders.ShaderMacro ShaderMacro
Default IncludeHandler implementation loading files from a set of predefined directories.
Macro to be used with PreProcessor.
Definition: ShaderMacro.cs:11
IncludeType
Type of include file.
Definition: IncludeType.cs:8
A pointer virtual button.
C++ preprocessor using D3DPreprocess method from d3dcompiler API.
Definition: PreProcessor.cs:17
function s(a)
static string Run(string shaderSource, string sourceFileName=null, ShaderMacro[] defines=null, IncludeHandler include=null)
Preprocesses the provided shader or effect source.
Definition: PreProcessor.cs:72
Callback interface to handle included file requested by the PreProcessor.
_In_ size_t _In_ size_t size
Definition: DirectXTexP.h:175
readonly string Definition
Value of the macro to set.
Definition: ShaderMacro.cs:34
static string Run(string shaderSource, string sourceFileName, ShaderMacro[] defines=null, params string[] includeDirectories)
Preprocesses the provided shader or effect source.
Definition: PreProcessor.cs:44
readonly string Name
Name of the macro to set.
Definition: ShaderMacro.cs:29