3 # Copyright 2015-2021 Arm Limited
4 # SPDX-License-Identifier: Apache-2.0
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
10 # http://www.apache.org/licenses/LICENSE-2.0
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
30 import multiprocessing
33 from functools
import partial
36 def __init__(self
, spirv_cross
, glslang
, spirv_as
, spirv_val
, spirv_opt
):
37 self
.spirv_cross
= spirv_cross
38 self
.glslang
= glslang
39 self
.spirv_as
= spirv_as
40 self
.spirv_val
= spirv_val
41 self
.spirv_opt
= spirv_opt
43 def remove_file(path
):
44 #print('Removing file:', path)
47 def create_temporary(suff
= ''):
48 f
, path
= tempfile
.mkstemp(suffix
= suff
)
50 #print('Creating temporary:', path)
53 def parse_stats(stats
):
54 m
= re
.search('([0-9]+) work registers', stats
)
55 registers
= int(m
.group(1)) if m
else 0
57 m
= re
.search('([0-9]+) uniform registers', stats
)
58 uniform_regs
= int(m
.group(1)) if m
else 0
60 m_list
= re
.findall('(-?[0-9]+)\s+(-?[0-9]+)\s+(-?[0-9]+)', stats
)
61 alu_short
= float(m_list
[1][0]) if m_list
else 0
62 ls_short
= float(m_list
[1][1]) if m_list
else 0
63 tex_short
= float(m_list
[1][2]) if m_list
else 0
64 alu_long
= float(m_list
[2][0]) if m_list
else 0
65 ls_long
= float(m_list
[2][1]) if m_list
else 0
66 tex_long
= float(m_list
[2][2]) if m_list
else 0
68 return (registers
, uniform_regs
, alu_short
, ls_short
, tex_short
, alu_long
, ls_long
, tex_long
)
70 def get_shader_type(shader
):
71 _
, ext
= os
.path
.splitext(shader
)
79 return '--tessellation_control'
81 return '--tessellation_evaluation'
87 def get_shader_stats(shader
):
88 path
= create_temporary()
90 p
= subprocess
.Popen(['malisc', get_shader_type(shader
), '--core', 'Mali-T760', '-V', shader
], stdout
= subprocess
.PIPE
, stderr
= subprocess
.PIPE
)
91 stdout
, stderr
= p
.communicate()
95 print(stderr
.decode('utf-8'))
96 raise OSError('malisc failed')
99 returned
= stdout
.decode('utf-8')
100 return parse_stats(returned
)
102 def print_msl_compiler_version():
104 if platform
.system() == 'Darwin':
105 subprocess
.check_call(['xcrun', '--sdk', 'iphoneos', 'metal', '--version'])
106 print('... are the Metal compiler characteristics.\n') # display after so xcrun FNF is silent
108 # Use Metal Windows toolkit to test on Linux (Wine) and Windows.
109 print('Running on non-macOS system.')
110 subprocess
.check_call(['metal', '-x', 'metal', '--version'])
113 if (e
.errno
!= errno
.ENOENT
): # Ignore xcrun not found error
115 print('Metal SDK is not present.\n')
116 except subprocess
.CalledProcessError
:
119 def path_to_msl_standard(shader
):
120 if '.msl31.' in shader
:
121 return '-std=metal3.1'
122 elif '.msl3.' in shader
:
123 return '-std=metal3.0'
124 elif '.ios.' in shader
:
125 if '.msl2.' in shader
:
126 return '-std=ios-metal2.0'
127 elif '.msl21.' in shader
:
128 return '-std=ios-metal2.1'
129 elif '.msl22.' in shader
:
130 return '-std=ios-metal2.2'
131 elif '.msl23.' in shader
:
132 return '-std=ios-metal2.3'
133 elif '.msl24.' in shader
:
134 return '-std=ios-metal2.4'
135 elif '.msl11.' in shader
:
136 return '-std=ios-metal1.1'
137 elif '.msl10.' in shader
:
138 return '-std=ios-metal1.0'
140 return '-std=ios-metal1.2'
142 if '.msl2.' in shader
:
143 return '-std=macos-metal2.0'
144 elif '.msl21.' in shader
:
145 return '-std=macos-metal2.1'
146 elif '.msl22.' in shader
:
147 return '-std=macos-metal2.2'
148 elif '.msl23.' in shader
:
149 return '-std=macos-metal2.3'
150 elif '.msl24.' in shader
:
151 return '-std=macos-metal2.4'
152 elif '.msl11.' in shader
:
153 return '-std=macos-metal1.1'
155 return '-std=macos-metal1.2'
157 def path_to_msl_standard_cli(shader
):
158 if '.msl31.' in shader
:
160 elif '.msl3.' in shader
:
162 elif '.msl2.' in shader
:
164 elif '.msl21.' in shader
:
166 elif '.msl22.' in shader
:
168 elif '.msl23.' in shader
:
170 elif '.msl24.' in shader
:
172 elif '.msl11.' in shader
:
177 ignore_win_metal_tool
= False
178 def validate_shader_msl(shader
, opt
):
179 msl_path
= reference_path(shader
[0], shader
[1], opt
)
180 global ignore_win_metal_tool
182 if '.ios.' in msl_path
:
187 if platform
.system() == 'Darwin':
188 subprocess
.check_call(['xcrun', '--sdk', msl_os
, 'metal', '-x', 'metal', path_to_msl_standard(msl_path
), '-Werror', '-Wno-unused-variable', msl_path
])
189 print('Compiled Metal shader: ' + msl_path
) # display after so xcrun FNF is silent
190 elif not ignore_win_metal_tool
:
191 # Use Metal Windows toolkit to test on Linux (Wine) and Windows. Running offline tool on Linux gets weird.
192 # Normal winepath doesn't work, it must be Z:/abspath *exactly* for some bizarre reason.
193 target_path
= msl_path
if platform
.system
== 'Windows' else ('Z:' + os
.path
.abspath(msl_path
))
194 subprocess
.check_call(['metal', '-x', 'metal', path_to_msl_standard(msl_path
), '-Werror', '-Wno-unused-variable', target_path
])
196 except OSError as oe
:
197 if (oe
.errno
!= errno
.ENOENT
): # Ignore xcrun or metal not found error
199 print('metal toolkit does not exist, ignoring further attempts to use it.')
200 ignore_win_metal_tool
= True
201 except subprocess
.CalledProcessError
:
202 print('Error compiling Metal shader: ' + msl_path
)
203 raise RuntimeError('Failed to compile Metal shader')
205 def cross_compile_msl(shader
, spirv
, opt
, iterations
, paths
):
206 spirv_path
= create_temporary()
207 msl_path
= create_temporary(os
.path
.basename(shader
))
209 spirv_16
= '.spv16.' in shader
210 spirv_14
= '.spv14.' in shader
214 glslang_env
= 'spirv1.6'
216 spirv_env
= 'vulkan1.1spv1.4'
217 glslang_env
= 'spirv1.4'
219 spirv_env
= 'vulkan1.1'
220 glslang_env
= 'vulkan1.1'
222 spirv_cmd
= [paths
.spirv_as
, '--preserve-numeric-ids', '--target-env', spirv_env
, '-o', spirv_path
, shader
]
225 subprocess
.check_call(spirv_cmd
)
227 subprocess
.check_call([paths
.glslang
, '--amb' ,'--target-env', glslang_env
, '-V', '-o', spirv_path
, shader
])
229 if opt
and (not shader_is_invalid_spirv(shader
)):
230 if '.graphics-robust-access.' in shader
:
231 subprocess
.check_call([paths
.spirv_opt
, '--skip-validation', '-O', '--graphics-robust-access', '-o', spirv_path
, spirv_path
])
233 subprocess
.check_call([paths
.spirv_opt
, '--skip-validation', '-O', '-o', spirv_path
, spirv_path
])
235 spirv_cross_path
= paths
.spirv_cross
237 msl_args
= [spirv_cross_path
, '--output', msl_path
, spirv_path
, '--msl', '--iterations', str(iterations
)]
238 msl_args
.append('--msl-version')
239 msl_args
.append(path_to_msl_standard_cli(shader
))
240 if not '.nomain.' in shader
:
241 msl_args
.append('--entry')
242 msl_args
.append('main')
243 if '.swizzle.' in shader
:
244 msl_args
.append('--msl-swizzle-texture-samples')
245 if '.ios.' in shader
:
246 msl_args
.append('--msl-ios')
247 if '.pad-fragment.' in shader
:
248 msl_args
.append('--msl-pad-fragment-output')
249 if '.capture.' in shader
:
250 msl_args
.append('--msl-capture-output')
251 if '.domain.' in shader
:
252 msl_args
.append('--msl-domain-lower-left')
253 if '.argument.' in shader
:
254 msl_args
.append('--msl-argument-buffers')
255 if '.argument-tier-1.' in shader
:
256 msl_args
.append('--msl-argument-buffer-tier')
258 if '.texture-buffer-native.' in shader
:
259 msl_args
.append('--msl-texture-buffer-native')
260 if '.framebuffer-fetch.' in shader
:
261 msl_args
.append('--msl-framebuffer-fetch')
262 if '.invariant-float-math.' in shader
:
263 msl_args
.append('--msl-invariant-float-math')
264 if '.emulate-cube-array.' in shader
:
265 msl_args
.append('--msl-emulate-cube-array')
266 if '.discrete.' in shader
:
267 # Arbitrary for testing purposes.
268 msl_args
.append('--msl-discrete-descriptor-set')
270 msl_args
.append('--msl-discrete-descriptor-set')
272 if '.force-active.' in shader
:
273 msl_args
.append('--msl-force-active-argument-buffer-resources')
274 if '.line.' in shader
:
275 msl_args
.append('--emit-line-directives')
276 if '.multiview.' in shader
:
277 msl_args
.append('--msl-multiview')
278 if '.no-layered.' in shader
:
279 msl_args
.append('--msl-multiview-no-layered-rendering')
280 if '.viewfromdev.' in shader
:
281 msl_args
.append('--msl-view-index-from-device-index')
282 if '.dispatchbase.' in shader
:
283 msl_args
.append('--msl-dispatch-base')
284 if '.dynamic-buffer.' in shader
:
285 # Arbitrary for testing purposes.
286 msl_args
.append('--msl-dynamic-buffer')
289 msl_args
.append('--msl-dynamic-buffer')
292 if '.inline-block.' in shader
:
293 # Arbitrary for testing purposes.
294 msl_args
.append('--msl-inline-uniform-block')
297 if '.device-argument-buffer.' in shader
:
298 msl_args
.append('--msl-device-argument-buffer')
300 msl_args
.append('--msl-device-argument-buffer')
302 if '.force-native-array.' in shader
:
303 msl_args
.append('--msl-force-native-arrays')
304 if '.zero-initialize.' in shader
:
305 msl_args
.append('--force-zero-initialized-variables')
306 if '.frag-output.' in shader
:
307 # Arbitrary for testing purposes.
308 msl_args
.append('--msl-disable-frag-depth-builtin')
309 msl_args
.append('--msl-disable-frag-stencil-ref-builtin')
310 msl_args
.append('--msl-enable-frag-output-mask')
311 msl_args
.append('0x000000ca')
312 if '.no-user-varying.' in shader
:
313 msl_args
.append('--msl-no-clip-distance-user-varying')
314 if '.shader-inputs.' in shader
:
315 # Arbitrary for testing purposes.
316 msl_args
.append('--msl-shader-input')
318 msl_args
.append('u8')
320 msl_args
.append('--msl-shader-input')
322 msl_args
.append('u16')
324 msl_args
.append('--msl-shader-input')
326 msl_args
.append('other')
328 if '.multi-patch.' in shader
:
329 msl_args
.append('--msl-multi-patch-workgroup')
330 # Arbitrary for testing purposes.
331 msl_args
.append('--msl-shader-input')
333 msl_args
.append('any32')
335 msl_args
.append('--msl-shader-input')
337 msl_args
.append('any16')
339 if '.raw-tess-in.' in shader
:
340 msl_args
.append('--msl-raw-buffer-tese-input')
341 if '.for-tess.' in shader
:
342 msl_args
.append('--msl-vertex-for-tessellation')
343 if '.fixed-sample-mask.' in shader
:
344 msl_args
.append('--msl-additional-fixed-sample-mask')
345 msl_args
.append('0x00000022')
346 if '.arrayed-subpass.' in shader
:
347 msl_args
.append('--msl-arrayed-subpass-input')
348 if '.1d-as-2d.' in shader
:
349 msl_args
.append('--msl-texture-1d-as-2d')
350 if '.simd.' in shader
:
351 msl_args
.append('--msl-ios-use-simdgroup-functions')
352 if '.emulate-subgroup.' in shader
:
353 msl_args
.append('--msl-emulate-subgroups')
354 if '.fixed-subgroup.' in shader
:
355 # Arbitrary for testing purposes.
356 msl_args
.append('--msl-fixed-subgroup-size')
357 msl_args
.append('32')
358 if '.force-sample.' in shader
:
359 msl_args
.append('--msl-force-sample-rate-shading')
360 if '.discard-checks.' in shader
:
361 msl_args
.append('--msl-check-discarded-frag-stores')
362 if '.force-frag-with-side-effects-execution.' in shader
:
363 msl_args
.append('--msl-force-frag-with-side-effects-execution')
364 if '.lod-as-grad.' in shader
:
365 msl_args
.append('--msl-sample-dref-lod-array-as-grad')
366 if '.agx-cube-grad.' in shader
:
367 msl_args
.append('--msl-agx-manual-cube-grad-fixup')
368 if '.decoration-binding.' in shader
:
369 msl_args
.append('--msl-decoration-binding')
370 if '.rich-descriptor.' in shader
:
371 msl_args
.append('--msl-runtime-array-rich-descriptor')
372 if '.replace-recursive-inputs.' in shader
:
373 msl_args
.append('--msl-replace-recursive-inputs')
374 if '.input-attachment-is-ds-attachment.' in shader
:
375 msl_args
.append('--msl-input-attachment-is-ds-attachment')
376 if '.mask-location-0.' in shader
:
377 msl_args
.append('--mask-stage-output-location')
380 if '.mask-location-1.' in shader
:
381 msl_args
.append('--mask-stage-output-location')
384 if '.mask-position.' in shader
:
385 msl_args
.append('--mask-stage-output-builtin')
386 msl_args
.append('Position')
387 if '.mask-point-size.' in shader
:
388 msl_args
.append('--mask-stage-output-builtin')
389 msl_args
.append('PointSize')
390 if '.mask-clip-distance.' in shader
:
391 msl_args
.append('--mask-stage-output-builtin')
392 msl_args
.append('ClipDistance')
393 if '.relax-nan.' in shader
:
394 msl_args
.append('--relax-nan-checks')
396 subprocess
.check_call(msl_args
)
398 if not shader_is_invalid_spirv(msl_path
):
399 subprocess
.check_call([paths
.spirv_val
, '--allow-localsizeid', '--scalar-block-layout', '--target-env', spirv_env
, spirv_path
])
401 return (spirv_path
, msl_path
)
403 def shader_model_hlsl(shader
):
404 if '.vert' in shader
:
405 if '.sm30.' in shader
:
409 elif '.frag' in shader
:
410 if '.sm30.' in shader
:
414 elif '.comp' in shader
:
416 elif '.mesh' in shader
:
418 elif '.task' in shader
:
423 def shader_to_win_path(shader
):
424 # It's (very) convenient to be able to run HLSL testing in wine on Unix-likes, so support that.
426 with subprocess
.Popen(['winepath', '-w', shader
], stdout
= subprocess
.PIPE
, stderr
= subprocess
.PIPE
) as f
:
427 stdout_data
, stderr_data
= f
.communicate()
428 return stdout_data
.decode('utf-8')
429 except OSError as oe
:
430 if (oe
.errno
!= errno
.ENOENT
): # Ignore not found errors
432 except subprocess
.CalledProcessError
:
438 def validate_shader_hlsl(shader
, force_no_external_validation
, paths
):
440 if '.nonuniformresource.' in shader
:
442 if '.fxconly.' in shader
:
444 if '.task' in shader
or '.mesh' in shader
:
447 hlsl_args
= [paths
.glslang
, '--amb', '-e', 'main', '-D', '--target-env', 'vulkan1.1', '-V', shader
]
448 if '.sm30.' in shader
:
449 hlsl_args
.append('--hlsl-dx9-compatible')
452 subprocess
.check_call(hlsl_args
)
454 is_no_fxc
= '.nofxc.' in shader
456 if (not ignore_fxc
) and (not force_no_external_validation
) and (not is_no_fxc
):
458 win_path
= shader_to_win_path(shader
)
459 args
= ['fxc', '-nologo', shader_model_hlsl(shader
), win_path
]
460 if '.nonuniformresource.' in shader
:
461 args
.append('/enable_unbounded_descriptor_tables')
462 subprocess
.check_call(args
)
463 except OSError as oe
:
464 if (oe
.errno
!= errno
.ENOENT
): # Ignore not found errors
465 print('Failed to run FXC.')
469 print('Could not find FXC.')
471 except subprocess
.CalledProcessError
:
472 print('Failed compiling HLSL shader:', shader
, 'with FXC.')
473 raise RuntimeError('Failed compiling HLSL shader')
475 def shader_to_sm(shader
):
476 if '.sm62.' in shader
:
478 elif '.sm61.' in shader
:
480 elif '.sm60.' in shader
:
482 elif '.sm68.' in shader
:
484 elif '.sm51.' in shader
:
486 elif '.sm30.' in shader
:
491 def cross_compile_hlsl(shader
, spirv
, opt
, force_no_external_validation
, iterations
, paths
):
492 spirv_path
= create_temporary()
493 hlsl_path
= create_temporary(os
.path
.basename(shader
))
495 spirv_16
= '.spv16.' in shader
496 spirv_14
= '.spv14.' in shader
500 glslang_env
= 'spirv1.6'
502 spirv_env
= 'vulkan1.1spv1.4'
503 glslang_env
= 'spirv1.4'
505 spirv_env
= 'vulkan1.1'
506 glslang_env
= 'vulkan1.1'
508 spirv_cmd
= [paths
.spirv_as
, '--preserve-numeric-ids', '--target-env', spirv_env
, '-o', spirv_path
, shader
]
511 subprocess
.check_call(spirv_cmd
)
513 subprocess
.check_call([paths
.glslang
, '--amb', '--target-env', glslang_env
, '-V', '-o', spirv_path
, shader
])
515 if opt
and (not shader_is_invalid_spirv(hlsl_path
)):
516 subprocess
.check_call([paths
.spirv_opt
, '--skip-validation', '-O', '-o', spirv_path
, spirv_path
])
518 spirv_cross_path
= paths
.spirv_cross
520 sm
= shader_to_sm(shader
)
522 hlsl_args
= [spirv_cross_path
, '--entry', 'main', '--output', hlsl_path
, spirv_path
, '--hlsl-enable-compat', '--hlsl', '--shader-model', sm
, '--iterations', str(iterations
)]
523 if '.line.' in shader
:
524 hlsl_args
.append('--emit-line-directives')
525 if '.flatten.' in shader
:
526 hlsl_args
.append('--flatten-ubo')
527 if '.force-uav.' in shader
:
528 hlsl_args
.append('--hlsl-force-storage-buffer-as-uav')
529 if '.zero-initialize.' in shader
:
530 hlsl_args
.append('--force-zero-initialized-variables')
531 if '.nonwritable-uav-texture.' in shader
:
532 hlsl_args
.append('--hlsl-nonwritable-uav-texture-as-srv')
533 if '.native-16bit.' in shader
:
534 hlsl_args
.append('--hlsl-enable-16bit-types')
535 if '.flatten-matrix-vertex-input.' in shader
:
536 hlsl_args
.append('--hlsl-flatten-matrix-vertex-input-semantics')
537 if '.relax-nan.' in shader
:
538 hlsl_args
.append('--relax-nan-checks')
539 if '.structured.' in shader
:
540 hlsl_args
.append('--hlsl-preserve-structured-buffers')
541 if '.flip-vert-y.' in shader
:
542 hlsl_args
.append('--flip-vert-y')
544 subprocess
.check_call(hlsl_args
)
546 if not shader_is_invalid_spirv(hlsl_path
):
547 subprocess
.check_call([paths
.spirv_val
, '--allow-localsizeid', '--scalar-block-layout', '--target-env', spirv_env
, spirv_path
])
549 validate_shader_hlsl(hlsl_path
, force_no_external_validation
, paths
)
551 return (spirv_path
, hlsl_path
)
553 def cross_compile_reflect(shader
, spirv
, opt
, iterations
, paths
):
554 spirv_path
= create_temporary()
555 reflect_path
= create_temporary(os
.path
.basename(shader
))
557 spirv_cmd
= [paths
.spirv_as
, '--preserve-numeric-ids', '--target-env', 'vulkan1.1', '-o', spirv_path
, shader
]
560 subprocess
.check_call(spirv_cmd
)
562 subprocess
.check_call([paths
.glslang
, '--amb', '--target-env', 'vulkan1.1', '-V', '-o', spirv_path
, shader
])
564 if opt
and (not shader_is_invalid_spirv(reflect_path
)):
565 subprocess
.check_call([paths
.spirv_opt
, '--skip-validation', '-O', '-o', spirv_path
, spirv_path
])
567 spirv_cross_path
= paths
.spirv_cross
569 sm
= shader_to_sm(shader
)
570 subprocess
.check_call([spirv_cross_path
, '--entry', 'main', '--output', reflect_path
, spirv_path
, '--reflect', '--iterations', str(iterations
)])
571 return (spirv_path
, reflect_path
)
573 def validate_shader(shader
, vulkan
, paths
):
575 spirv_14
= '.spv14.' in shader
576 glslang_env
= 'spirv1.4' if spirv_14
else 'vulkan1.1'
577 subprocess
.check_call([paths
.glslang
, '--amb', '--target-env', glslang_env
, '-V', shader
])
579 subprocess
.check_call([paths
.glslang
, shader
])
581 def cross_compile(shader
, vulkan
, spirv
, invalid_spirv
, eliminate
, is_legacy
, force_es
, flatten_ubo
, sso
, flatten_dim
, opt
, push_ubo
, iterations
, paths
):
582 spirv_path
= create_temporary()
583 glsl_path
= create_temporary(os
.path
.basename(shader
))
585 spirv_16
= '.spv16.' in shader
586 spirv_14
= '.spv14.' in shader
589 glslang_env
= 'spirv1.6'
591 spirv_env
= 'vulkan1.1spv1.4'
592 glslang_env
= 'spirv1.4'
594 spirv_env
= 'vulkan1.1'
595 glslang_env
= 'vulkan1.1'
598 vulkan_glsl_path
= create_temporary('vk' + os
.path
.basename(shader
))
600 spirv_cmd
= [paths
.spirv_as
, '--preserve-numeric-ids', '--target-env', spirv_env
, '-o', spirv_path
, shader
]
603 subprocess
.check_call(spirv_cmd
)
605 glslang_cmd
= [paths
.glslang
, '--amb', '--target-env', glslang_env
, '-V', '-o', spirv_path
, shader
]
607 glslang_cmd
.append('-g')
609 glslang_cmd
.append('-gV')
610 subprocess
.check_call(glslang_cmd
)
612 if opt
and (not invalid_spirv
):
613 subprocess
.check_call([paths
.spirv_opt
, '--skip-validation', '-O', '-o', spirv_path
, spirv_path
])
615 if not invalid_spirv
:
616 subprocess
.check_call([paths
.spirv_val
, '--allow-localsizeid', '--scalar-block-layout', '--target-env', spirv_env
, spirv_path
])
618 extra_args
= ['--iterations', str(iterations
)]
620 extra_args
+= ['--remove-unused-variables']
622 extra_args
+= ['--version', '100', '--es']
624 extra_args
+= ['--version', '310', '--es']
626 extra_args
+= ['--flatten-ubo']
628 extra_args
+= ['--separate-shader-objects']
630 extra_args
+= ['--flatten-multidimensional-arrays']
632 extra_args
+= ['--glsl-emit-push-constant-as-ubo']
633 if '.line.' in shader
:
634 extra_args
+= ['--emit-line-directives']
635 if '.no-samplerless.' in shader
:
636 extra_args
+= ['--vulkan-glsl-disable-ext-samplerless-texture-functions']
637 if '.no-qualifier-deduction.' in shader
:
638 extra_args
+= ['--disable-storage-image-qualifier-deduction']
639 if '.framebuffer-fetch.' in shader
:
640 extra_args
+= ['--glsl-remap-ext-framebuffer-fetch', '0', '0']
641 extra_args
+= ['--glsl-remap-ext-framebuffer-fetch', '1', '1']
642 extra_args
+= ['--glsl-remap-ext-framebuffer-fetch', '2', '2']
643 extra_args
+= ['--glsl-remap-ext-framebuffer-fetch', '3', '3']
644 if '.framebuffer-fetch-noncoherent.' in shader
:
645 extra_args
+= ['--glsl-ext-framebuffer-fetch-noncoherent']
646 if '.zero-initialize.' in shader
:
647 extra_args
+= ['--force-zero-initialized-variables']
648 if '.force-flattened-io.' in shader
:
649 extra_args
+= ['--glsl-force-flattened-io-blocks']
650 if '.relax-nan.' in shader
:
651 extra_args
.append('--relax-nan-checks')
653 spirv_cross_path
= paths
.spirv_cross
655 # A shader might not be possible to make valid GLSL from, skip validation for this case.
656 if (not ('nocompat' in glsl_path
)) or (not vulkan
):
657 subprocess
.check_call([spirv_cross_path
, '--entry', 'main', '--output', glsl_path
, spirv_path
] + extra_args
)
658 if not 'nocompat' in glsl_path
:
659 validate_shader(glsl_path
, False, paths
)
661 remove_file(glsl_path
)
664 if (vulkan
or spirv
) and (not is_legacy
):
665 subprocess
.check_call([spirv_cross_path
, '--entry', 'main', '-V', '--output', vulkan_glsl_path
, spirv_path
] + extra_args
)
666 validate_shader(vulkan_glsl_path
, True, paths
)
667 # SPIR-V shaders might just want to validate Vulkan GLSL output, we don't always care about the output.
669 remove_file(vulkan_glsl_path
)
671 return (spirv_path
, glsl_path
, vulkan_glsl_path
if vulkan
else None)
673 def make_unix_newline(buf
):
674 decoded
= codecs
.decode(buf
, 'utf-8')
675 decoded
= decoded
.replace('\r', '')
676 return codecs
.encode(decoded
, 'utf-8')
678 def md5_for_file(path
):
680 with
open(path
, 'rb') as f
:
681 for chunk
in iter(lambda: make_unix_newline(f
.read(8192)), b
''):
685 def make_reference_dir(path
):
686 base
= os
.path
.dirname(path
)
687 if not os
.path
.exists(base
):
690 def reference_path(directory
, relpath
, opt
):
691 split_paths
= os
.path
.split(directory
)
692 reference_dir
= os
.path
.join(split_paths
[0], 'reference/' + ('opt/' if opt
else ''))
693 reference_dir
= os
.path
.join(reference_dir
, split_paths
[1])
694 return os
.path
.join(reference_dir
, relpath
)
696 def regression_check_reflect(shader
, json_file
, args
):
697 reference
= reference_path(shader
[0], shader
[1], args
.opt
) + '.json'
698 joined_path
= os
.path
.join(shader
[0], shader
[1])
699 print('Reference shader reflection path:', reference
)
700 if os
.path
.exists(reference
):
701 actual
= md5_for_file(json_file
)
702 expected
= md5_for_file(reference
)
703 if actual
!= expected
:
705 print('Generated reflection json has changed for {}!'.format(reference
))
706 # If we expect changes, update the reference file.
707 if os
.path
.exists(reference
):
708 remove_file(reference
)
709 make_reference_dir(reference
)
710 shutil
.move(json_file
, reference
)
712 print('Generated reflection json in {} does not match reference {}!'.format(json_file
, reference
))
714 diff_path
= generate_diff_file(reference
, glsl
)
715 with
open(diff_path
, 'r') as f
:
720 remove_file(diff_path
)
722 with
open(json_file
, 'r') as f
:
725 print('======================')
727 print('======================')
730 # Otherwise, fail the test. Keep the shader file around so we can inspect.
732 remove_file(json_file
)
734 raise RuntimeError('Does not match reference')
736 remove_file(json_file
)
738 print('Found new shader {}. Placing generated source code in {}'.format(joined_path
, reference
))
739 make_reference_dir(reference
)
740 shutil
.move(json_file
, reference
)
742 def generate_diff_file(origin
, generated
):
743 diff_destination
= create_temporary()
744 with
open(diff_destination
, "w") as f
:
746 subprocess
.check_call(["diff", origin
, generated
], stdout
=f
)
747 except subprocess
.CalledProcessError
as e
:
748 # diff returns 1 when the files are different so we can safely
750 if e
.returncode
!= 1:
753 return diff_destination
755 def regression_check(shader
, glsl
, args
):
756 reference
= reference_path(shader
[0], shader
[1], args
.opt
)
757 joined_path
= os
.path
.join(shader
[0], shader
[1])
758 print('Reference shader path:', reference
)
760 if os
.path
.exists(reference
):
761 if md5_for_file(glsl
) != md5_for_file(reference
):
763 print('Generated source code has changed for {}!'.format(reference
))
764 # If we expect changes, update the reference file.
765 if os
.path
.exists(reference
):
766 remove_file(reference
)
767 make_reference_dir(reference
)
768 shutil
.move(glsl
, reference
)
770 print('Generated source code in {} does not match reference {}!'.format(glsl
, reference
))
772 diff_path
= generate_diff_file(reference
, glsl
)
773 with
open(diff_path
, 'r') as f
:
778 remove_file(diff_path
)
780 with
open(glsl
, 'r') as f
:
783 print('======================')
785 print('======================')
788 # Otherwise, fail the test. Keep the shader file around so we can inspect.
791 raise RuntimeError('Does not match reference')
795 print('Found new shader {}. Placing generated source code in {}'.format(joined_path
, reference
))
796 make_reference_dir(reference
)
797 shutil
.move(glsl
, reference
)
799 def shader_is_vulkan(shader
):
800 return '.vk.' in shader
802 def shader_is_desktop(shader
):
803 return '.desktop.' in shader
805 def shader_is_eliminate_dead_variables(shader
):
806 return '.noeliminate.' not in shader
808 def shader_is_spirv(shader
):
809 return '.asm.' in shader
811 def shader_is_invalid_spirv(shader
):
812 return '.invalid.' in shader
814 def shader_is_legacy(shader
):
815 return '.legacy.' in shader
817 def shader_is_force_es(shader
):
818 return '.es.' in shader
820 def shader_is_flatten_ubo(shader
):
821 return '.flatten.' in shader
823 def shader_is_sso(shader
):
824 return '.sso.' in shader
826 def shader_is_flatten_dimensions(shader
):
827 return '.flatten_dim.' in shader
829 def shader_is_noopt(shader
):
830 return '.noopt.' in shader
832 def shader_is_push_ubo(shader
):
833 return '.push-ubo.' in shader
835 def test_shader(stats
, shader
, args
, paths
):
836 joined_path
= os
.path
.join(shader
[0], shader
[1])
837 vulkan
= shader_is_vulkan(shader
[1])
838 desktop
= shader_is_desktop(shader
[1])
839 eliminate
= shader_is_eliminate_dead_variables(shader
[1])
840 is_spirv
= shader_is_spirv(shader
[1])
841 invalid_spirv
= shader_is_invalid_spirv(shader
[1])
842 is_legacy
= shader_is_legacy(shader
[1])
843 force_es
= shader_is_force_es(shader
[1])
844 flatten_ubo
= shader_is_flatten_ubo(shader
[1])
845 sso
= shader_is_sso(shader
[1])
846 flatten_dim
= shader_is_flatten_dimensions(shader
[1])
847 noopt
= shader_is_noopt(shader
[1])
848 push_ubo
= shader_is_push_ubo(shader
[1])
850 print('Testing shader:', joined_path
)
851 spirv
, glsl
, vulkan_glsl
= cross_compile(joined_path
, vulkan
, is_spirv
, invalid_spirv
, eliminate
, is_legacy
, force_es
, flatten_ubo
, sso
, flatten_dim
, args
.opt
and (not noopt
), push_ubo
, args
.iterations
, paths
)
853 # Only test GLSL stats if we have a shader following GL semantics.
854 if stats
and (not vulkan
) and (not is_spirv
) and (not desktop
):
855 cross_stats
= get_shader_stats(glsl
)
858 regression_check(shader
, glsl
, args
)
860 regression_check((shader
[0], shader
[1] + '.vk'), vulkan_glsl
, args
)
864 if stats
and (not vulkan
) and (not is_spirv
) and (not desktop
):
865 pristine_stats
= get_shader_stats(joined_path
)
869 for i
in pristine_stats
:
871 for i
in cross_stats
:
873 print(','.join(a
), file = stats
)
875 def test_shader_msl(stats
, shader
, args
, paths
):
876 joined_path
= os
.path
.join(shader
[0], shader
[1])
877 print('\nTesting MSL shader:', joined_path
)
878 is_spirv
= shader_is_spirv(shader
[1])
879 noopt
= shader_is_noopt(shader
[1])
880 spirv
, msl
= cross_compile_msl(joined_path
, is_spirv
, args
.opt
and (not noopt
), args
.iterations
, paths
)
881 regression_check(shader
, msl
, args
)
883 # Uncomment the following line to print the temp SPIR-V file path.
884 # This temp SPIR-V file is not deleted until after the Metal validation step below.
885 # If Metal validation fails, the temp SPIR-V file can be copied out and
886 # used as input to an invocation of spirv-cross to debug from Xcode directly.
887 # To do so, build spriv-cross using `make DEBUG=1`, then run the spriv-cross
888 # executable from Xcode using args: `--msl --entry main --output msl_path spirv_path`.
889 # print('SPRIV shader: ' + spirv)
891 skip_validation
= '.invalid.' in joined_path
892 if (not args
.force_no_external_validation
) and (not skip_validation
):
893 validate_shader_msl(shader
, args
.opt
)
897 def test_shader_hlsl(stats
, shader
, args
, paths
):
898 joined_path
= os
.path
.join(shader
[0], shader
[1])
899 print('Testing HLSL shader:', joined_path
)
900 is_spirv
= shader_is_spirv(shader
[1])
901 noopt
= shader_is_noopt(shader
[1])
902 spirv
, hlsl
= cross_compile_hlsl(joined_path
, is_spirv
, args
.opt
and (not noopt
), args
.force_no_external_validation
, args
.iterations
, paths
)
903 regression_check(shader
, hlsl
, args
)
906 def test_shader_reflect(stats
, shader
, args
, paths
):
907 joined_path
= os
.path
.join(shader
[0], shader
[1])
908 print('Testing shader reflection:', joined_path
)
909 is_spirv
= shader_is_spirv(shader
[1])
910 noopt
= shader_is_noopt(shader
[1])
911 spirv
, reflect
= cross_compile_reflect(joined_path
, is_spirv
, args
.opt
and (not noopt
), args
.iterations
, paths
)
912 regression_check_reflect(shader
, reflect
, args
)
915 def test_shader_file(relpath
, stats
, args
, backend
):
916 paths
= Paths(args
.spirv_cross
, args
.glslang
, args
.spirv_as
, args
.spirv_val
, args
.spirv_opt
)
919 test_shader_msl(stats
, (args
.folder
, relpath
), args
, paths
)
920 elif backend
== 'hlsl':
921 test_shader_hlsl(stats
, (args
.folder
, relpath
), args
, paths
)
922 elif backend
== 'reflect':
923 test_shader_reflect(stats
, (args
.folder
, relpath
), args
, paths
)
925 test_shader(stats
, (args
.folder
, relpath
), args
, paths
)
927 except Exception as e
:
930 def test_shaders_helper(stats
, backend
, args
):
932 for root
, dirs
, files
in os
.walk(os
.path
.join(args
.folder
)):
933 files
= [ f
for f
in files
if not f
.startswith(".") ] #ignore system files (esp OSX)
935 path
= os
.path
.join(root
, i
)
936 relpath
= os
.path
.relpath(path
, args
.folder
)
937 all_files
.append(relpath
)
939 # The child processes in parallel execution mode don't have the proper state for the global args variable, so
940 # at this point we need to switch to explicit arguments
942 with multiprocessing
.Pool(multiprocessing
.cpu_count()) as pool
:
945 results
.append(pool
.apply_async(test_shader_file
,
946 args
= (f
, stats
, args
, backend
)))
950 results_completed
= [res
.get() for res
in results
]
952 for error
in results_completed
:
953 if error
is not None:
954 print('Error:', error
)
959 e
= test_shader_file(i
, stats
, args
, backend
)
964 def test_shaders(backend
, args
):
966 with
open('stats.csv', 'w') as stats
:
967 print('Shader,OrigRegs,OrigUniRegs,OrigALUShort,OrigLSShort,OrigTEXShort,OrigALULong,OrigLSLong,OrigTEXLong,CrossRegs,CrossUniRegs,CrossALUShort,CrossLSShort,CrossTEXShort,CrossALULong,CrossLSLong,CrossTEXLong', file = stats
)
968 test_shaders_helper(stats
, backend
, args
)
970 test_shaders_helper(None, backend
, args
)
973 parser
= argparse
.ArgumentParser(description
= 'Script for regression testing.')
974 parser
.add_argument('folder',
975 help = 'Folder containing shader files to test.')
976 parser
.add_argument('--update',
977 action
= 'store_true',
978 help = 'Updates reference files if there is a mismatch. Use when legitimate changes in output is found.')
979 parser
.add_argument('--keep',
980 action
= 'store_true',
981 help = 'Leave failed GLSL shaders on disk if they fail regression. Useful for debugging.')
982 parser
.add_argument('--diff',
983 action
= 'store_true',
984 help = 'Displays a diff instead of the generated output on failure. Useful for debugging.')
985 parser
.add_argument('--malisc',
986 action
= 'store_true',
987 help = 'Use malisc offline compiler to determine static cycle counts before and after spirv-cross.')
988 parser
.add_argument('--msl',
989 action
= 'store_true',
990 help = 'Test Metal backend.')
991 parser
.add_argument('--metal',
992 action
= 'store_true',
993 help = 'Deprecated Metal option. Use --msl instead.')
994 parser
.add_argument('--hlsl',
995 action
= 'store_true',
996 help = 'Test HLSL backend.')
997 parser
.add_argument('--force-no-external-validation',
998 action
= 'store_true',
999 help = 'Disable all external validation.')
1000 parser
.add_argument('--opt',
1001 action
= 'store_true',
1002 help = 'Run SPIRV-Tools optimization passes as well.')
1003 parser
.add_argument('--reflect',
1004 action
= 'store_true',
1005 help = 'Test reflection backend.')
1006 parser
.add_argument('--parallel',
1007 action
= 'store_true',
1008 help = 'Execute tests in parallel. Useful for doing regression quickly, but bad for debugging and stat output.')
1009 parser
.add_argument('--spirv-cross',
1010 default
= './spirv-cross',
1011 help = 'Explicit path to spirv-cross')
1012 parser
.add_argument('--glslang',
1013 default
= 'glslangValidator',
1014 help = 'Explicit path to glslangValidator')
1015 parser
.add_argument('--spirv-as',
1016 default
= 'spirv-as',
1017 help = 'Explicit path to spirv-as')
1018 parser
.add_argument('--spirv-val',
1019 default
= 'spirv-val',
1020 help = 'Explicit path to spirv-val')
1021 parser
.add_argument('--spirv-opt',
1022 default
= 'spirv-opt',
1023 help = 'Explicit path to spirv-opt')
1024 parser
.add_argument('--iterations',
1027 help = 'Number of iterations to run SPIRV-Cross (benchmarking)')
1029 args
= parser
.parse_args()
1031 sys
.stderr
.write('Need shader folder.\n')
1034 if (args
.parallel
and (args
.malisc
or args
.force_no_external_validation
or args
.update
)):
1035 sys
.stderr
.write('Parallel execution is disabled when using the flags --update, --malisc or --force-no-external-validation\n')
1036 args
.parallel
= False
1039 print_msl_compiler_version()
1042 if (args
.msl
or args
.metal
):
1049 test_shaders(backend
, args
)
1051 print('Stats in stats.csv!')
1052 print('Tests completed!')
1054 if __name__
== '__main__':