blob: 3564b6dc0baa1f97b017772baff23b0e85ee6308 [file] [log] [blame]
Hans-Kristian Arntzen75471fb2016-03-02 18:09:16 +01001#!/usr/bin/env python3
2
3import sys
4import os
Arseny Kapoulkinef45075b2017-01-24 22:26:39 -08005import os.path
Hans-Kristian Arntzen75471fb2016-03-02 18:09:16 +01006import subprocess
7import tempfile
8import re
9import itertools
10import hashlib
11import shutil
Hans-Kristian Arntzen0a5b3a62016-03-22 14:44:12 +010012import argparse
Hans-Kristian Arntzen75471fb2016-03-02 18:09:16 +010013
Arseny Kapoulkine49baf412017-01-25 00:01:53 -080014METALC = '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/usr/bin/metal'
15
Hans-Kristian Arntzen75471fb2016-03-02 18:09:16 +010016def parse_stats(stats):
17 m = re.search('([0-9]+) work registers', stats)
18 registers = int(m.group(1)) if m else 0
19
20 m = re.search('([0-9]+) uniform registers', stats)
21 uniform_regs = int(m.group(1)) if m else 0
22
23 m_list = re.findall('(-?[0-9]+)\s+(-?[0-9]+)\s+(-?[0-9]+)', stats)
24 alu_short = float(m_list[1][0]) if m_list else 0
25 ls_short = float(m_list[1][1]) if m_list else 0
26 tex_short = float(m_list[1][2]) if m_list else 0
27 alu_long = float(m_list[2][0]) if m_list else 0
28 ls_long = float(m_list[2][1]) if m_list else 0
29 tex_long = float(m_list[2][2]) if m_list else 0
30
31 return (registers, uniform_regs, alu_short, ls_short, tex_short, alu_long, ls_long, tex_long)
32
33def get_shader_type(shader):
34 _, ext = os.path.splitext(shader)
35 if ext == '.vert':
36 return '--vertex'
37 elif ext == '.frag':
38 return '--fragment'
39 elif ext == '.comp':
40 return '--compute'
41 elif ext == '.tesc':
42 return '--tessellation_control'
43 elif ext == '.tese':
44 return '--tessellation_evaluation'
45 elif ext == '.geom':
46 return '--geometry'
47 else:
48 return ''
49
50def get_shader_stats(shader):
51 f, path = tempfile.mkstemp()
52
53 os.close(f)
54 p = subprocess.Popen(['malisc', get_shader_type(shader), '--core', 'Mali-T760', '-V', shader], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
55 stdout, stderr = p.communicate()
56 os.remove(path)
57
58 if p.returncode != 0:
59 print(stderr.decode('utf-8'))
60 raise OSError('malisc failed')
61 p.wait()
62
63 returned = stdout.decode('utf-8')
64 return parse_stats(returned)
65
Arseny Kapoulkinef45075b2017-01-24 22:26:39 -080066def validate_shader_msl(shader):
Arseny Kapoulkine49baf412017-01-25 00:01:53 -080067 subprocess.check_call([METALC, '-x', 'metal', '-std=ios-metal1.0', '-Werror', shader])
Hans-Kristian Arntzen0a5b3a62016-03-22 14:44:12 +010068
Hans-Kristian Arntzen1c28ec62017-01-18 19:05:57 +010069def cross_compile_msl(shader):
70 spirv_f, spirv_path = tempfile.mkstemp()
71 msl_f, msl_path = tempfile.mkstemp(suffix = os.path.basename(shader))
72 os.close(spirv_f)
73 os.close(msl_f)
74 subprocess.check_call(['glslangValidator', '-V', '-o', spirv_path, shader])
75 spirv_cross_path = './spirv-cross'
76 subprocess.check_call([spirv_cross_path, '--entry', 'main', '--output', msl_path, spirv_path, '--metal'])
77 subprocess.check_call(['spirv-val', spirv_path])
78
Arseny Kapoulkine49baf412017-01-25 00:01:53 -080079 if os.path.exists(METALC):
80 validate_shader_msl(msl_path)
Arseny Kapoulkinef45075b2017-01-24 22:26:39 -080081
Hans-Kristian Arntzen1c28ec62017-01-18 19:05:57 +010082 return (spirv_path, msl_path)
83
Robert Konradcec9c702017-01-26 09:45:17 +010084def cross_compile_hlsl(shader):
85 spirv_f, spirv_path = tempfile.mkstemp()
86 hlsl_f, hlsl_path = tempfile.mkstemp(suffix = os.path.basename(shader))
87 os.close(spirv_f)
88 os.close(hlsl_f)
89 subprocess.check_call(['glslangValidator', '-V', '-o', spirv_path, shader])
90 spirv_cross_path = './spirv-cross'
Robert Konrade7179532017-01-26 10:06:05 +010091 subprocess.check_call([spirv_cross_path, '--entry', 'main', '--output', hlsl_path, spirv_path, '--hlsl'])
Robert Konradcec9c702017-01-26 09:45:17 +010092 subprocess.check_call(['spirv-val', spirv_path])
93
94 # TODO: Add optional validation of the HLSL output.
Robert Konrade7179532017-01-26 10:06:05 +010095 return (spirv_path, hlsl_path)
Robert Konradcec9c702017-01-26 09:45:17 +010096
Arseny Kapoulkinef45075b2017-01-24 22:26:39 -080097def validate_shader(shader, vulkan):
98 if vulkan:
99 subprocess.check_call(['glslangValidator', '-V', shader])
100 else:
101 subprocess.check_call(['glslangValidator', shader])
102
Arseny Kapoulkine24c66252017-01-16 14:19:49 -0800103def cross_compile(shader, vulkan, spirv, invalid_spirv, eliminate, is_legacy, flatten_ubo):
Hans-Kristian Arntzen75471fb2016-03-02 18:09:16 +0100104 spirv_f, spirv_path = tempfile.mkstemp()
105 glsl_f, glsl_path = tempfile.mkstemp(suffix = os.path.basename(shader))
106 os.close(spirv_f)
107 os.close(glsl_f)
108
Hans-Kristian Arntzen45ad58a2016-05-10 23:39:41 +0200109 if vulkan or spirv:
Hans-Kristian Arntzendbee4e42016-05-05 10:16:22 +0200110 vulkan_glsl_f, vulkan_glsl_path = tempfile.mkstemp(suffix = os.path.basename(shader))
111 os.close(vulkan_glsl_f)
112
Hans-Kristian Arntzen45ad58a2016-05-10 23:39:41 +0200113 if spirv:
114 subprocess.check_call(['spirv-as', '-o', spirv_path, shader])
115 else:
Hans-Kristian Arntzenb6847162016-09-10 12:52:23 +0200116 subprocess.check_call(['glslangValidator', '-V', '-o', spirv_path, shader])
Hans-Kristian Arntzen45ad58a2016-05-10 23:39:41 +0200117
Hans-Kristian Arntzen706d3ea2016-09-12 20:11:30 +0200118 if not invalid_spirv:
119 subprocess.check_call(['spirv-val', spirv_path])
Hans-Kristian Arntzendbee4e42016-05-05 10:16:22 +0200120
Arseny Kapoulkine24c66252017-01-16 14:19:49 -0800121 extra_args = []
122 if eliminate:
123 extra_args += ['--remove-unused-variables']
Hans-Kristian Arntzen41f7e5b2017-01-13 16:41:27 +0100124 if is_legacy:
Arseny Kapoulkine24c66252017-01-16 14:19:49 -0800125 extra_args += ['--version', '100', '--es']
126 if flatten_ubo:
127 extra_args += ['--flatten-ubo']
Hans-Kristian Arntzen41f7e5b2017-01-13 16:41:27 +0100128
Hans-Kristian Arntzen78c9a802016-05-11 19:39:38 +0200129 spirv_cross_path = './spirv-cross'
Arseny Kapoulkine24c66252017-01-16 14:19:49 -0800130 subprocess.check_call([spirv_cross_path, '--entry', 'main', '--output', glsl_path, spirv_path] + extra_args)
Hans-Kristian Arntzendbee4e42016-05-05 10:16:22 +0200131
Hans-Kristian Arntzen8869a162016-05-11 19:55:57 +0200132 # A shader might not be possible to make valid GLSL from, skip validation for this case.
Hans-Kristian Arntzen45ad58a2016-05-10 23:39:41 +0200133 if (not ('nocompat' in glsl_path)) and (not spirv):
Hans-Kristian Arntzendbee4e42016-05-05 10:16:22 +0200134 validate_shader(glsl_path, False)
135
Hans-Kristian Arntzen45ad58a2016-05-10 23:39:41 +0200136 if vulkan or spirv:
Arseny Kapoulkine24c66252017-01-16 14:19:49 -0800137 subprocess.check_call([spirv_cross_path, '--entry', 'main', '--vulkan-semantics', '--output', vulkan_glsl_path, spirv_path] + extra_args)
Hans-Kristian Arntzendbee4e42016-05-05 10:16:22 +0200138 validate_shader(vulkan_glsl_path, vulkan)
139
140 return (spirv_path, glsl_path, vulkan_glsl_path if vulkan else None)
Hans-Kristian Arntzen75471fb2016-03-02 18:09:16 +0100141
142def md5_for_file(path):
143 md5 = hashlib.md5()
144 with open(path, 'rb') as f:
145 for chunk in iter(lambda: f.read(8192), b''):
146 md5.update(chunk)
147 return md5.digest()
148
149def make_reference_dir(path):
150 base = os.path.dirname(path)
151 if not os.path.exists(base):
152 os.makedirs(base)
153
Hans-Kristian Arntzen78c9a802016-05-11 19:39:38 +0200154def reference_path(directory, relpath):
155 split_paths = os.path.split(directory)
156 reference_dir = os.path.join(split_paths[0], 'reference/')
157 reference_dir = os.path.join(reference_dir, split_paths[1])
158 return os.path.join(reference_dir, relpath)
159
Hans-Kristian Arntzene50e04c2016-03-22 14:56:50 +0100160def regression_check(shader, glsl, update, keep):
Hans-Kristian Arntzen78c9a802016-05-11 19:39:38 +0200161 reference = reference_path(shader[0], shader[1])
162 joined_path = os.path.join(shader[0], shader[1])
163 print('Reference shader path:', reference)
164
Hans-Kristian Arntzen75471fb2016-03-02 18:09:16 +0100165 if os.path.exists(reference):
166 if md5_for_file(glsl) != md5_for_file(reference):
Hans-Kristian Arntzen0a5b3a62016-03-22 14:44:12 +0100167 if update:
Hans-Kristian Arntzene50e04c2016-03-22 14:56:50 +0100168 print('Generated GLSL has changed for {}!'.format(reference))
Hans-Kristian Arntzen0a5b3a62016-03-22 14:44:12 +0100169 # If we expect changes, update the reference file.
170 if os.path.exists(reference):
171 os.remove(reference)
172 make_reference_dir(reference)
173 shutil.move(glsl, reference)
174 else:
Hans-Kristian Arntzene50e04c2016-03-22 14:56:50 +0100175 print('Generated GLSL in {} does not match reference {}!'.format(glsl, reference))
Hans-Kristian Arntzen45c797d2016-12-16 13:48:30 +0100176 with open(glsl, 'r') as f:
177 print('')
178 print('Generated:')
179 print('======================')
180 print(f.read())
181 print('======================')
182 print('')
183
Hans-Kristian Arntzene50e04c2016-03-22 14:56:50 +0100184 # Otherwise, fail the test. Keep the shader file around so we can inspect.
185 if not keep:
186 os.remove(glsl)
Hans-Kristian Arntzen0a5b3a62016-03-22 14:44:12 +0100187 sys.exit(1)
Hans-Kristian Arntzen75471fb2016-03-02 18:09:16 +0100188 else:
189 os.remove(glsl)
190 else:
Hans-Kristian Arntzen78c9a802016-05-11 19:39:38 +0200191 print('Found new shader {}. Placing GLSL in {}'.format(joined_path, reference))
Hans-Kristian Arntzen75471fb2016-03-02 18:09:16 +0100192 make_reference_dir(reference)
193 shutil.move(glsl, reference)
194
Hans-Kristian Arntzen8869a162016-05-11 19:55:57 +0200195def shader_is_vulkan(shader):
196 return '.vk.' in shader
197
Hans-Kristian Arntzen2c7d2e42016-07-11 12:47:46 +0200198def shader_is_desktop(shader):
199 return '.desktop.' in shader
200
Hans-Kristian Arntzenf61a5d12016-08-26 12:58:50 +0200201def shader_is_eliminate_dead_variables(shader):
202 return '.noeliminate.' not in shader
203
Hans-Kristian Arntzen45ad58a2016-05-10 23:39:41 +0200204def shader_is_spirv(shader):
205 return '.asm.' in shader
206
Hans-Kristian Arntzen706d3ea2016-09-12 20:11:30 +0200207def shader_is_invalid_spirv(shader):
208 return '.invalid.' in shader
209
Hans-Kristian Arntzen41f7e5b2017-01-13 16:41:27 +0100210def shader_is_legacy(shader):
211 return '.legacy.' in shader
212
Arseny Kapoulkine24c66252017-01-16 14:19:49 -0800213def shader_is_flatten_ubo(shader):
214 return '.flatten.' in shader
215
Hans-Kristian Arntzen8869a162016-05-11 19:55:57 +0200216def test_shader(stats, shader, update, keep):
Hans-Kristian Arntzen78c9a802016-05-11 19:39:38 +0200217 joined_path = os.path.join(shader[0], shader[1])
Hans-Kristian Arntzen8869a162016-05-11 19:55:57 +0200218 vulkan = shader_is_vulkan(shader[1])
Hans-Kristian Arntzen2c7d2e42016-07-11 12:47:46 +0200219 desktop = shader_is_desktop(shader[1])
Hans-Kristian Arntzenf61a5d12016-08-26 12:58:50 +0200220 eliminate = shader_is_eliminate_dead_variables(shader[1])
Hans-Kristian Arntzenb6847162016-09-10 12:52:23 +0200221 is_spirv = shader_is_spirv(shader[1])
Hans-Kristian Arntzen706d3ea2016-09-12 20:11:30 +0200222 invalid_spirv = shader_is_invalid_spirv(shader[1])
Hans-Kristian Arntzen41f7e5b2017-01-13 16:41:27 +0100223 is_legacy = shader_is_legacy(shader[1])
Arseny Kapoulkine24c66252017-01-16 14:19:49 -0800224 flatten_ubo = shader_is_flatten_ubo(shader[1])
Hans-Kristian Arntzen78c9a802016-05-11 19:39:38 +0200225
226 print('Testing shader:', joined_path)
Arseny Kapoulkine24c66252017-01-16 14:19:49 -0800227 spirv, glsl, vulkan_glsl = cross_compile(joined_path, vulkan, is_spirv, invalid_spirv, eliminate, is_legacy, flatten_ubo)
Hans-Kristian Arntzen75471fb2016-03-02 18:09:16 +0100228
Hans-Kristian Arntzen8869a162016-05-11 19:55:57 +0200229 # Only test GLSL stats if we have a shader following GL semantics.
Hans-Kristian Arntzenb6847162016-09-10 12:52:23 +0200230 if stats and (not vulkan) and (not is_spirv) and (not desktop):
Hans-Kristian Arntzen0a5b3a62016-03-22 14:44:12 +0100231 cross_stats = get_shader_stats(glsl)
232
Hans-Kristian Arntzene50e04c2016-03-22 14:56:50 +0100233 regression_check(shader, glsl, update, keep)
Hans-Kristian Arntzendbee4e42016-05-05 10:16:22 +0200234 if vulkan_glsl:
Hans-Kristian Arntzen78c9a802016-05-11 19:39:38 +0200235 regression_check((shader[0], shader[1] + '.vk'), vulkan_glsl, update, keep)
Hans-Kristian Arntzen75471fb2016-03-02 18:09:16 +0100236 os.remove(spirv)
237
Hans-Kristian Arntzenb6847162016-09-10 12:52:23 +0200238 if stats and (not vulkan) and (not is_spirv) and (not desktop):
Hans-Kristian Arntzen78c9a802016-05-11 19:39:38 +0200239 pristine_stats = get_shader_stats(joined_path)
Hans-Kristian Arntzen75471fb2016-03-02 18:09:16 +0100240
Hans-Kristian Arntzen0a5b3a62016-03-22 14:44:12 +0100241 a = []
Hans-Kristian Arntzen78c9a802016-05-11 19:39:38 +0200242 a.append(shader[1])
Hans-Kristian Arntzen0a5b3a62016-03-22 14:44:12 +0100243 for i in pristine_stats:
244 a.append(str(i))
245 for i in cross_stats:
246 a.append(str(i))
247 print(','.join(a), file = stats)
248
Hans-Kristian Arntzen1c28ec62017-01-18 19:05:57 +0100249def test_shader_msl(stats, shader, update, keep):
250 joined_path = os.path.join(shader[0], shader[1])
251 print('Testing MSL shader:', joined_path)
252 spirv, msl = cross_compile_msl(joined_path)
253 regression_check(shader, msl, update, keep)
254 os.remove(spirv)
255
Robert Konradcec9c702017-01-26 09:45:17 +0100256def test_shader_hlsl(stats, shader, update, keep):
257 joined_path = os.path.join(shader[0], shader[1])
258 print('Testing HLSL shader:', joined_path)
259 spirv, msl = cross_compile_hlsl(joined_path)
260 regression_check(shader, msl, update, keep)
261 os.remove(spirv)
262
Hans-Kristian Arntzen1c28ec62017-01-18 19:05:57 +0100263def test_shaders_helper(stats, shader_dir, update, malisc, keep, backend):
Hans-Kristian Arntzen78c9a802016-05-11 19:39:38 +0200264 for root, dirs, files in os.walk(os.path.join(shader_dir)):
265 for i in files:
266 path = os.path.join(root, i)
267 relpath = os.path.relpath(path, shader_dir)
Hans-Kristian Arntzen1c28ec62017-01-18 19:05:57 +0100268 if backend == 'metal':
269 test_shader_msl(stats, (shader_dir, relpath), update, keep)
Robert Konradcec9c702017-01-26 09:45:17 +0100270 elif backend == 'hlsl':
271 test_shader_hlsl(stats, (shader_dir, relpath), update, keep)
Hans-Kristian Arntzen1c28ec62017-01-18 19:05:57 +0100272 else:
273 test_shader(stats, (shader_dir, relpath), update, keep)
Hans-Kristian Arntzen78c9a802016-05-11 19:39:38 +0200274
Hans-Kristian Arntzen1c28ec62017-01-18 19:05:57 +0100275def test_shaders(shader_dir, update, malisc, keep, backend):
Hans-Kristian Arntzen0a5b3a62016-03-22 14:44:12 +0100276 if malisc:
277 with open('stats.csv', 'w') as stats:
278 print('Shader,OrigRegs,OrigUniRegs,OrigALUShort,OrigLSShort,OrigTEXShort,OrigALULong,OrigLSLong,OrigTEXLong,CrossRegs,CrossUniRegs,CrossALUShort,CrossLSShort,CrossTEXShort,CrossALULong,CrossLSLong,CrossTEXLong', file = stats)
Hans-Kristian Arntzen1c28ec62017-01-18 19:05:57 +0100279 test_shaders_helper(stats, shader_dir, update, malisc, keep, backend)
Hans-Kristian Arntzen0a5b3a62016-03-22 14:44:12 +0100280 else:
Hans-Kristian Arntzen1c28ec62017-01-18 19:05:57 +0100281 test_shaders_helper(None, shader_dir, update, malisc, keep, backend)
Hans-Kristian Arntzen0a5b3a62016-03-22 14:44:12 +0100282
283def main():
284 parser = argparse.ArgumentParser(description = 'Script for regression testing.')
285 parser.add_argument('folder',
286 help = 'Folder containing shader files to test.')
287 parser.add_argument('--update',
288 action = 'store_true',
289 help = 'Updates reference files if there is a mismatch. Use when legitimate changes in output is found.')
Hans-Kristian Arntzene50e04c2016-03-22 14:56:50 +0100290 parser.add_argument('--keep',
291 action = 'store_true',
292 help = 'Leave failed GLSL shaders on disk if they fail regression. Useful for debugging.')
Hans-Kristian Arntzen0a5b3a62016-03-22 14:44:12 +0100293 parser.add_argument('--malisc',
294 action = 'store_true',
Hans-Kristian Arntzen147e53a2016-04-04 09:36:04 +0200295 help = 'Use malisc offline compiler to determine static cycle counts before and after spirv-cross.')
Hans-Kristian Arntzen1c28ec62017-01-18 19:05:57 +0100296 parser.add_argument('--metal',
297 action = 'store_true',
298 help = 'Test Metal backend.')
Robert Konradcec9c702017-01-26 09:45:17 +0100299 parser.add_argument('--hlsl',
300 action = 'store_true',
301 help = 'Test HLSL backend.')
Hans-Kristian Arntzen0a5b3a62016-03-22 14:44:12 +0100302 args = parser.parse_args()
303
304 if not args.folder:
305 sys.stderr.write('Need shader folder.\n')
306 sys.exit(1)
307
Arseny Kapoulkine49baf412017-01-25 00:01:53 -0800308 if os.path.exists(METALC):
309 subprocess.check_call([METALC, '--version'])
310
Robert Konradcec9c702017-01-26 09:45:17 +0100311 test_shaders(args.folder, args.update, args.malisc, args.keep, 'metal' if args.metal else ('hlsl' if args.hlsl else 'glsl'))
Hans-Kristian Arntzen0a5b3a62016-03-22 14:44:12 +0100312 if args.malisc:
313 print('Stats in stats.csv!')
Hans-Kristian Arntzene50e04c2016-03-22 14:56:50 +0100314 print('Tests completed!')
Hans-Kristian Arntzen75471fb2016-03-02 18:09:16 +0100315
316if __name__ == '__main__':
Hans-Kristian Arntzen0a5b3a62016-03-22 14:44:12 +0100317 main()