Skip to content

Commit e2ce5aa

Browse files
committed
Add shader compilation Python script.
This is still a work-in-progress and should be cleaned up in the future.
1 parent 9e44a6d commit e2ce5aa

File tree

1 file changed

+193
-0
lines changed

1 file changed

+193
-0
lines changed

res/compile.py

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import os
2+
import subprocess
3+
import re
4+
import binascii
5+
import struct
6+
from multiprocessing.pool import ThreadPool
7+
8+
# Set the path to the fxc compiler
9+
fxc = os.environ['DXSDK_DIR'] + 'Utilities\\bin\\x86\\fxc.exe'
10+
featureLevel = '3_0'
11+
workers = 8
12+
13+
# Set the directory you want to start from
14+
shaders = 'gamedata/shaders/r2'
15+
objects = 'gamedata/shaders/r2/objects/r2'
16+
17+
# List all macro identifiers, excluding SMAP
18+
options = [
19+
'FP16_FILTER',
20+
'FP16_BLEND',
21+
'USE_HWSMAP',
22+
'USE_HWSMAP_PCF',
23+
'USE_FETCH4',
24+
'USE_SJITTER',
25+
'USE_BRANCHING',
26+
'USE_VTF',
27+
'USE_TSHADOWS',
28+
'USE_MBLUR',
29+
'USE_SUNFILTER',
30+
'USE_R2_STATIC_SUN',
31+
'FORCE_GLOSS',
32+
'SKIN_COLOR',
33+
'USE_SSAO_BLUR',
34+
'USE_HBAO',
35+
'SSAO_OPT_DATA',
36+
'SKIN_NONE',
37+
'SKIN_0',
38+
'SKIN_1',
39+
'SKIN_2',
40+
'SKIN_3',
41+
'SKIN_4',
42+
'USE_SOFT_WATER',
43+
'USE_SOFT_PARTICLES',
44+
'USE_DOF',
45+
'SUN_SHAFTS_QUALITY',
46+
'SSAO_QUALITY',
47+
'SUN_QUALITY',
48+
'ALLOW_STEEPPARALLAX'
49+
]
50+
51+
skinOptions = [
52+
'SKIN_NONE',
53+
'SKIN_0',
54+
'SKIN_1',
55+
'SKIN_2',
56+
'SKIN_3',
57+
'SKIN_4'
58+
]
59+
60+
forceOptions = [
61+
'USE_HWSMAP'
62+
]
63+
64+
disableOptions = [
65+
'SKIN_COLOR'
66+
]
67+
68+
qualityOptions = {
69+
'SUN_SHAFTS_QUALITY': 3,
70+
'SSAO_QUALITY': 3,
71+
'SUN_QUALITY': 2
72+
}
73+
74+
entrex = re.compile('main_(vs|ps)_[0-9]_[0-9]')
75+
inclex = re.compile('#include\s+"(.*)"')
76+
commex = re.compile("//.*?\n")
77+
78+
def addCRC(filename):
79+
with open(filename) as file:
80+
file = open(filename,'r+b')
81+
buf = file.read()
82+
crc = (binascii.crc32(buf) & 0xFFFFFFFF)
83+
file.seek(0)
84+
file.write(struct.pack("<I", crc))
85+
file.write(buf)
86+
87+
def findOptions(shader):
88+
list = []
89+
with open(shader) as f:
90+
buffer = f.read()
91+
buffer = re.sub(commex, "", buffer) # remove commented lines
92+
for option in options:
93+
if option in buffer:
94+
list.append(option)
95+
includes = inclex.findall(buffer) # find all include files
96+
for include in includes:
97+
list += findOptions(os.path.join(os.path.dirname(shader), include))
98+
return list
99+
100+
def findEntry(shader):
101+
with open(shader) as f:
102+
buffer = f.read()
103+
buffer = re.sub(commex, "", buffer) # remove commented lines
104+
match = entrex.search(buffer) # find the entry point
105+
if match:
106+
return match.group(0)
107+
if 'main' in buffer:
108+
return 'main'
109+
includes = inclex.findall(buffer) # find all include files
110+
for include in includes:
111+
if include != 'common.h':
112+
return findEntry(os.path.join(os.path.dirname(shader), include))
113+
return False
114+
115+
def generateIds(list, id, i):
116+
if i >= len(options):
117+
return [ id ]
118+
if options[i] in list:
119+
if options[i] in forceOptions:
120+
return generateIds(list, id + '1', i + 1)
121+
if options[i] in disableOptions:
122+
return generateIds(list, id + '0', i + 1)
123+
if options[i] in skinOptions:
124+
ids = []
125+
for (index, option) in enumerate(skinOptions):
126+
skin = '0' * len(skinOptions)
127+
skin = skin[:index] + '1' + skin[index+1:]
128+
ids += generateIds(list, id + skin, i + len(skinOptions))
129+
return ids
130+
if options[i] in qualityOptions:
131+
ids = []
132+
for index in range(0, qualityOptions[options[i]]):
133+
ids += generateIds(list, id + str(index), i + 1)
134+
return ids
135+
return generateIds(list, id + '0', i + 1) + generateIds(list, id + '1', i + 1)
136+
else:
137+
return generateIds(list, id + '_', i + 1)
138+
139+
def compileShader(name, stage, flags):
140+
path = os.path.join(shaders, '%s.%s' % (name, stage))
141+
entry = findEntry(path)
142+
if not entry:
143+
print('No entry point found, skipping shader...')
144+
return
145+
146+
profile = '%s_%s' % (stage, featureLevel)
147+
if len(entry) > len('main'):
148+
profile = entry[5:]
149+
150+
object = os.path.join(objects, '%s.%s' % (name, stage))
151+
if os.path.isfile(object):
152+
print('Already compiled, skipping object %s' % flags)
153+
return
154+
155+
args = [fxc, '/nologo', '/LD', '/Zpr', '/E%s' % entry, '/T%s' % profile]
156+
157+
args.append('/D%s=%s' % ('SMAP_size', flags[0:4]))
158+
for idx, option in enumerate(options, 4):
159+
if flags[idx] != '_' and int(flags[idx]) != 0:
160+
args.append('/D%s=%s' % (option, flags[idx]))
161+
if option in skinOptions and option != 'SKIN_NONE':
162+
object = os.path.join(objects, '%s_%s.%s' % (name, option[-1], stage))
163+
args.append('/Fo%s' % os.path.join(object, flags))
164+
args.append(path)
165+
subprocess.check_call(args)
166+
addCRC(os.path.join(object, flags))
167+
168+
pool = ThreadPool(processes=workers)
169+
170+
jobs = []
171+
for file in os.listdir(shaders):
172+
shader = os.path.splitext(file)[0]
173+
stage = os.path.splitext(file)[1][1:]
174+
if stage == 'vs' or stage == 'ps':
175+
print('Found shader: %s' % shader)
176+
flags = findOptions(os.path.join(shaders, file))
177+
outdir = os.path.join(objects, file)
178+
if not os.path.isdir(outdir):
179+
os.makedirs(outdir)
180+
if 'SKIN_NONE' in flags:
181+
for skin in skinOptions[1:]:
182+
outdir = os.path.join(objects, '%s_%s.%s' % (shader, skin[-1], stage))
183+
if (not os.path.isdir(outdir)):
184+
os.makedirs(outdir)
185+
print(flags)
186+
for id in generateIds(flags, "2048", 0):
187+
job = pool.apply_async(compileShader, (shader, stage, id))
188+
jobs.append(job)
189+
for job in jobs:
190+
job.get()
191+
192+
pool.close()
193+
pool.join()

0 commit comments

Comments
 (0)