-
Notifications
You must be signed in to change notification settings - Fork 36
/
Copy pathminifyShadersFolder.py
186 lines (157 loc) · 7.02 KB
/
minifyShadersFolder.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# Minifies all the HLSL or HLSLI files in the provided folder. Removing unneeded characters (including comments and extra whitespace)
# then pack into a set of strings in a C++ header, along with a directory map to access by filename.
import glob
import os
import shutil
import sys
import re
import subprocess
# Dump usage and exit if we have incorrect number of args.
if(len(sys.argv)<3 or len(sys.argv)>4):
print("Usage: python minifyHLSLFolder.py inputFolder outputFile [outputNamespace] [slangCompiler] [slangCompiler]")
print(" inputFolder - Folder containing Slang files")
print(" outputMinifiedFile - Output minified C++ header file")
print(" slangCompiler - Folder for slang executable to compile main entryPoint file (if omitted, just minifies no compilation)")
print(" mainEntryPoint - Main entry point filename, also used for precompiled DXIL C++ header filename (if omitted, assumes MainEntryPoint)")
sys.exit(-1)
# Parse the command line arguments.
hlslFolder = sys.argv[1]
outputFile = sys.argv[2]
nameSpaceName = os.path.splitext(os.path.basename(outputFile))[0] # Default namespace is output filename without extension or path.
slangc = ""
if(len(sys.argv)==4):
slangc = sys.argv[3]
mainEntryPoint = "MainEntryPoints"
if(len(sys.argv)==5):
mainEntryPoint = sys.argv[4]
# Build list of files
files = glob.glob(hlslFolder+'/*.*sl*')
# See if any of the HLSL files are newer than output header file.
try:
outfileStat = os.stat(outputFile)
outputModTime = outfileStat.st_mtime
except:
outputModTime = 0
#Compare the modified date on all the HLSL files in folder to output header file.
numFiles = len(files)
n = 0
rebuildNeeded = False
entryPointFile = ""
for hlslFile in files:
if(mainEntryPoint in hlslFile):
entryPointFile = hlslFile
fileModTime = os.stat(hlslFile).st_mtime
if(fileModTime>outputModTime):
rebuildNeeded=True
# Early out, if nothing to be done.
if(not rebuildNeeded):
print("Nothing to be done, output header file %s up to date" % (outputFile))
sys.exit(0)
# If we have a slang compiler and entry point filename, then precompile it.
if(len(slangc)>0 and len(entryPointFile)>0):
compiledFile = os.path.dirname(outputFile) + "/"+mainEntryPoint +".h"
print("Compiling main entry point %s with Slang compiler %s to DXIL in header %s"%(entryPointFile, slangc, compiledFile))
compiledTempFile = os.path.dirname(outputFile) +"/"+ mainEntryPoint +".dxil"
variableName = "g_s"+mainEntryPoint+"DXIL";
cmd = [slangc, entryPointFile, "-DDIRECTX=1", "-DRUNTIME_COMPILE_EVALUATE_MATERIAL_FUNCTION=1", "-target", "dxil", "-profile","lib_6_3","-o", compiledTempFile];
result = subprocess.run( cmd, capture_output=True)
if(result.returncode!=0):
print("Compliation failed on "+entryPointFile)
print(result.stderr.decode())
sys.exit(-1)
f = open(compiledTempFile, mode="rb")
data = bytearray(f.read())
numBytes = len(data)
numWords = int(numBytes/4)
if(numWords*4 !=numBytes):
numWords=numWords+1;
headerStr = "static const array<unsigned int, "+str(numWords)+"> "+variableName+" = {\n";
for i in range(numWords):
wordBytes = bytes([data[i*4+3],data[i*4+2],data[i*4+1],data[i*4+0]])
headerStr += "0x"+str(wordBytes.hex());
if(i<numWords-1):
headerStr+=", "
if(i%8==7):
headerStr+="\n"
headerStr+="};\n"
with open(compiledFile, 'w') as f:
f.write(headerStr)
print("Minifying %d files from %s" % (numFiles, hlslFolder))
# Open header output file (exit if open fails.)
try:
headerOutput = open(outputFile , "w")
except:
print("Failed to open output header:"+outputFile)
sys.exit(-1)
# Begin directory map string
directorySymbolName = "g_sDirectory"
directoryString = '\n\t// Directory map, used to get file contents from HLSL filename.\n'
directoryString = directoryString+'\tstatic const std::map<std::string , const std::string &> '+directorySymbolName+' = {\n'
# Begin header file with namespace
headerOutput.write('// Minified HLSL header file.\n// Automatically generated by '+sys.argv[0]+' from '+str(numFiles)+' files in folder '+hlslFolder+'\n')
headerOutput.write('namespace '+nameSpaceName+' {\n')
# Minify all the HLSL files
for hlslFile in files:
# Print message
print("\t Minifying file %d of %d: %s" % ((n+1), numFiles, hlslFile))
# Get base filename (with path)
baseFilename = os.path.basename(hlslFile);
# Open pre-HLSL file (exit if open fails.)
try:
hlslFileStream = open(hlslFile, "r")
except:
print("Failed to open:"+hlslFile)
sys.exit(-1)
# Read all lines of HLSL to an array.
lines = hlslFileStream.readlines()
# Work out string symbol name from path (based filename without extension)
stringName = "g_s"+os.path.splitext(os.path.basename(hlslFile))[0];
# Check we don't have a name collision with the directory map object.
if(stringName==directorySymbolName):
print("Invalid HLSL filename "+baseFilename+" name conflict with built-in directory map named "+directorySymbolName)
# Begin minified C string.
headerOutput.write('\n\t// Minified string created from '+hlslFile+'\n')
headerOutput.write('\tstatic const std::string '+stringName+' = \n')
# Prepend a #line statement to help with debugging
lines.insert(0, '#line 1 \"'+re.sub(r"\\", "/", hlslFile)+'\"\n')
# Current header file output line and line number.
currOutputStr = ""
lineNo=0;
# Iterate through all the lines.
for ln in lines:
# Remove single line comments (TODO: Remove multi-line comments)
ln = re.sub(r"//.*\n", "\n", ln)
# Replace single backslash with double backslash in C source (8x required as python AND DXC needed escaping).
ln = re.sub(r"\\", "\\\\\\\\", ln)
# Escape newlines
ln = re.sub(r"\n", "\\\\n", ln)
# Remove extra non-newline whitespace.
ln = re.sub(r"[^\S\r\n]+", " ", ln)
# Escape quotes.
ln = re.sub(r"\"", "\\\"", ln)
# Increment line number.
lineNo=lineNo+1
# Append newline to end of file.
if(lineNo==len(lines)):
ln+="\\n";
# Append to current line in header file string.
currOutputStr += ln
# When current line longer than 100 chars, write to header string (wrapping in quotes and adding newline+backslash)
if(len(currOutputStr)>100):
headerOutput.write('\t\"'+currOutputStr+'\" \\\n')
currOutputStr=""
# Finish writing minified string.
headerOutput.write('\t\t\"'+currOutputStr+'\" \n\t;\n')
# Add entry to directory map string.
n=n+1
directoryString = directoryString + ('\t\t{ "%s", %s }'% (baseFilename, stringName))
if(n==len(files)):
directoryString = directoryString + "\n\t};\n"
else:
directoryString = directoryString + ",\n"
# Write directory map string
headerOutput.write(directoryString)
# End namespace
headerOutput.write("}\n")
# Close header file.
headerOutput.close()