diff options
Diffstat (limited to '3rdparty/bgfx/3rdparty/spirv-cross/test_shaders.py')
-rwxr-xr-x | 3rdparty/bgfx/3rdparty/spirv-cross/test_shaders.py | 767 |
1 files changed, 767 insertions, 0 deletions
diff --git a/3rdparty/bgfx/3rdparty/spirv-cross/test_shaders.py b/3rdparty/bgfx/3rdparty/spirv-cross/test_shaders.py new file mode 100755 index 00000000000..f58018e9a73 --- /dev/null +++ b/3rdparty/bgfx/3rdparty/spirv-cross/test_shaders.py @@ -0,0 +1,767 @@ +#!/usr/bin/env python3 + +import sys +import os +import os.path +import subprocess +import tempfile +import re +import itertools +import hashlib +import shutil +import argparse +import codecs +import json +import multiprocessing +import errno +from functools import partial + +class Paths(): + def __init__(self, spirv_cross, glslang, spirv_as, spirv_val, spirv_opt): + self.spirv_cross = spirv_cross + self.glslang = glslang + self.spirv_as = spirv_as + self.spirv_val = spirv_val + self.spirv_opt = spirv_opt + +def remove_file(path): + #print('Removing file:', path) + os.remove(path) + +def create_temporary(suff = ''): + f, path = tempfile.mkstemp(suffix = suff) + os.close(f) + #print('Creating temporary:', path) + return path + +def parse_stats(stats): + m = re.search('([0-9]+) work registers', stats) + registers = int(m.group(1)) if m else 0 + + m = re.search('([0-9]+) uniform registers', stats) + uniform_regs = int(m.group(1)) if m else 0 + + m_list = re.findall('(-?[0-9]+)\s+(-?[0-9]+)\s+(-?[0-9]+)', stats) + alu_short = float(m_list[1][0]) if m_list else 0 + ls_short = float(m_list[1][1]) if m_list else 0 + tex_short = float(m_list[1][2]) if m_list else 0 + alu_long = float(m_list[2][0]) if m_list else 0 + ls_long = float(m_list[2][1]) if m_list else 0 + tex_long = float(m_list[2][2]) if m_list else 0 + + return (registers, uniform_regs, alu_short, ls_short, tex_short, alu_long, ls_long, tex_long) + +def get_shader_type(shader): + _, ext = os.path.splitext(shader) + if ext == '.vert': + return '--vertex' + elif ext == '.frag': + return '--fragment' + elif ext == '.comp': + return '--compute' + elif ext == '.tesc': + return '--tessellation_control' + elif ext == '.tese': + return '--tessellation_evaluation' + elif ext == '.geom': + return '--geometry' + else: + return '' + +def get_shader_stats(shader): + path = create_temporary() + + p = subprocess.Popen(['malisc', get_shader_type(shader), '--core', 'Mali-T760', '-V', shader], stdout = subprocess.PIPE, stderr = subprocess.PIPE) + stdout, stderr = p.communicate() + remove_file(path) + + if p.returncode != 0: + print(stderr.decode('utf-8')) + raise OSError('malisc failed') + p.wait() + + returned = stdout.decode('utf-8') + return parse_stats(returned) + +def print_msl_compiler_version(): + try: + subprocess.check_call(['xcrun', '--sdk', 'iphoneos', 'metal', '--version']) + print('...are the Metal compiler characteristics.\n') # display after so xcrun FNF is silent + except OSError as e: + if (e.errno != errno.ENOENT): # Ignore xcrun not found error + raise + except subprocess.CalledProcessError: + pass + +def msl_compiler_supports_22(): + try: + subprocess.check_call(['xcrun', '--sdk', 'macosx', 'metal', '-x', 'metal', '-std=macos-metal2.2', '-'], + stdin = subprocess.DEVNULL, stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL) + print('Current SDK supports MSL 2.2. Enabling validation for MSL 2.2 shaders.') + return True + except OSError as e: + print('Failed to check if MSL 2.2 is not supported. It probably is not.') + return False + except subprocess.CalledProcessError: + print('Current SDK does NOT support MSL 2.2. Disabling validation for MSL 2.2 shaders.') + return False + +def path_to_msl_standard(shader): + if '.ios.' in shader: + if '.msl2.' in shader: + return '-std=ios-metal2.0' + elif '.msl21.' in shader: + return '-std=ios-metal2.1' + elif '.msl22.' in shader: + return '-std=ios-metal2.2' + elif '.msl11.' in shader: + return '-std=ios-metal1.1' + elif '.msl10.' in shader: + return '-std=ios-metal1.0' + else: + return '-std=ios-metal1.2' + else: + if '.msl2.' in shader: + return '-std=macos-metal2.0' + elif '.msl21.' in shader: + return '-std=macos-metal2.1' + elif '.msl22.' in shader: + return '-std=macos-metal2.2' + elif '.msl11.' in shader: + return '-std=macos-metal1.1' + else: + return '-std=macos-metal1.2' + +def path_to_msl_standard_cli(shader): + if '.msl2.' in shader: + return '20000' + elif '.msl21.' in shader: + return '20100' + elif '.msl22.' in shader: + return '20200' + elif '.msl11.' in shader: + return '10100' + else: + return '10200' + +def validate_shader_msl(shader, opt): + msl_path = reference_path(shader[0], shader[1], opt) + try: + if '.ios.' in msl_path: + msl_os = 'iphoneos' + else: + msl_os = 'macosx' + subprocess.check_call(['xcrun', '--sdk', msl_os, 'metal', '-x', 'metal', path_to_msl_standard(msl_path), '-Werror', '-Wno-unused-variable', msl_path]) + print('Compiled Metal shader: ' + msl_path) # display after so xcrun FNF is silent + except OSError as oe: + if (oe.errno != errno.ENOENT): # Ignore xcrun not found error + raise + except subprocess.CalledProcessError: + print('Error compiling Metal shader: ' + msl_path) + raise RuntimeError('Failed to compile Metal shader') + +def cross_compile_msl(shader, spirv, opt, iterations, paths): + spirv_path = create_temporary() + msl_path = create_temporary(os.path.basename(shader)) + + spirv_cmd = [paths.spirv_as, '--target-env', 'vulkan1.1', '-o', spirv_path, shader] + if '.preserve.' in shader: + spirv_cmd.append('--preserve-numeric-ids') + + if spirv: + subprocess.check_call(spirv_cmd) + else: + subprocess.check_call([paths.glslang, '--amb' ,'--target-env', 'vulkan1.1', '-V', '-o', spirv_path, shader]) + + if opt: + subprocess.check_call([paths.spirv_opt, '--skip-validation', '-O', '-o', spirv_path, spirv_path]) + + spirv_cross_path = paths.spirv_cross + + msl_args = [spirv_cross_path, '--entry', 'main', '--output', msl_path, spirv_path, '--msl', '--iterations', str(iterations)] + msl_args.append('--msl-version') + msl_args.append(path_to_msl_standard_cli(shader)) + if '.swizzle.' in shader: + msl_args.append('--msl-swizzle-texture-samples') + if '.ios.' in shader: + msl_args.append('--msl-ios') + if '.pad-fragment.' in shader: + msl_args.append('--msl-pad-fragment-output') + if '.capture.' in shader: + msl_args.append('--msl-capture-output') + if '.domain.' in shader: + msl_args.append('--msl-domain-lower-left') + if '.argument.' in shader: + msl_args.append('--msl-argument-buffers') + if '.texture-buffer-native.' in shader: + msl_args.append('--msl-texture-buffer-native') + if '.discrete.' in shader: + # Arbitrary for testing purposes. + msl_args.append('--msl-discrete-descriptor-set') + msl_args.append('2') + msl_args.append('--msl-discrete-descriptor-set') + msl_args.append('3') + if '.line.' in shader: + msl_args.append('--emit-line-directives') + if '.multiview.' in shader: + msl_args.append('--msl-multiview') + if '.viewfromdev.' in shader: + msl_args.append('--msl-view-index-from-device-index') + if '.dispatchbase.' in shader: + msl_args.append('--msl-dispatch-base') + if '.dynamic-buffer.' in shader: + # Arbitrary for testing purposes. + msl_args.append('--msl-dynamic-buffer') + msl_args.append('0') + msl_args.append('0') + msl_args.append('--msl-dynamic-buffer') + msl_args.append('1') + msl_args.append('2') + + subprocess.check_call(msl_args) + + if not shader_is_invalid_spirv(msl_path): + subprocess.check_call([paths.spirv_val, '--scalar-block-layout', '--target-env', 'vulkan1.1', spirv_path]) + + return (spirv_path, msl_path) + +def shader_model_hlsl(shader): + if '.vert' in shader: + if '.sm30.' in shader: + return '-Tvs_3_0' + else: + return '-Tvs_5_1' + elif '.frag' in shader: + if '.sm30.' in shader: + return '-Tps_3_0' + else: + return '-Tps_5_1' + elif '.comp' in shader: + return '-Tcs_5_1' + else: + return None + +def shader_to_win_path(shader): + # It's (very) convenient to be able to run HLSL testing in wine on Unix-likes, so support that. + try: + with subprocess.Popen(['winepath', '-w', shader], stdout = subprocess.PIPE, stderr = subprocess.PIPE) as f: + stdout_data, stderr_data = f.communicate() + return stdout_data.decode('utf-8') + except OSError as oe: + if (oe.errno != errno.ENOENT): # Ignore not found errors + return shader + except subprocess.CalledProcessError: + raise + + return shader + +ignore_fxc = False +def validate_shader_hlsl(shader, force_no_external_validation, paths): + test_glslang = True + if '.nonuniformresource.' in shader: + test_glslang = False + if '.fxconly.' in shader: + test_glslang = False + + if test_glslang: + subprocess.check_call([paths.glslang, '--amb', '-e', 'main', '-D', '--target-env', 'vulkan1.1', '-V', shader]) + is_no_fxc = '.nofxc.' in shader + global ignore_fxc + if (not ignore_fxc) and (not force_no_external_validation) and (not is_no_fxc): + try: + win_path = shader_to_win_path(shader) + args = ['fxc', '-nologo', shader_model_hlsl(shader), win_path] + if '.nonuniformresource.' in shader: + args.append('/enable_unbounded_descriptor_tables') + subprocess.check_call(args) + except OSError as oe: + if (oe.errno != errno.ENOENT): # Ignore not found errors + print('Failed to run FXC.') + ignore_fxc = True + raise + else: + print('Could not find FXC.') + ignore_fxc = True + except subprocess.CalledProcessError: + print('Failed compiling HLSL shader:', shader, 'with FXC.') + raise RuntimeError('Failed compiling HLSL shader') + +def shader_to_sm(shader): + if '.sm60.' in shader: + return '60' + elif '.sm51.' in shader: + return '51' + elif '.sm30.' in shader: + return '30' + else: + return '50' + +def cross_compile_hlsl(shader, spirv, opt, force_no_external_validation, iterations, paths): + spirv_path = create_temporary() + hlsl_path = create_temporary(os.path.basename(shader)) + + spirv_cmd = [paths.spirv_as, '--target-env', 'vulkan1.1', '-o', spirv_path, shader] + if '.preserve.' in shader: + spirv_cmd.append('--preserve-numeric-ids') + + if spirv: + subprocess.check_call(spirv_cmd) + else: + subprocess.check_call([paths.glslang, '--amb', '--target-env', 'vulkan1.1', '-V', '-o', spirv_path, shader]) + + if opt: + subprocess.check_call([paths.spirv_opt, '--skip-validation', '-O', '-o', spirv_path, spirv_path]) + + spirv_cross_path = paths.spirv_cross + + sm = shader_to_sm(shader) + + hlsl_args = [spirv_cross_path, '--entry', 'main', '--output', hlsl_path, spirv_path, '--hlsl-enable-compat', '--hlsl', '--shader-model', sm, '--iterations', str(iterations)] + if '.line.' in shader: + hlsl_args.append('--emit-line-directives') + subprocess.check_call(hlsl_args) + + if not shader_is_invalid_spirv(hlsl_path): + subprocess.check_call([paths.spirv_val, '--scalar-block-layout', '--target-env', 'vulkan1.1', spirv_path]) + + validate_shader_hlsl(hlsl_path, force_no_external_validation, paths) + + return (spirv_path, hlsl_path) + +def cross_compile_reflect(shader, spirv, opt, iterations, paths): + spirv_path = create_temporary() + reflect_path = create_temporary(os.path.basename(shader)) + + spirv_cmd = [paths.spirv_as, '--target-env', 'vulkan1.1', '-o', spirv_path, shader] + if '.preserve.' in shader: + spirv_cmd.append('--preserve-numeric-ids') + + if spirv: + subprocess.check_call(spirv_cmd) + else: + subprocess.check_call([paths.glslang, '--amb', '--target-env', 'vulkan1.1', '-V', '-o', spirv_path, shader]) + + if opt: + subprocess.check_call([paths.spirv_opt, '--skip-validation', '-O', '-o', spirv_path, spirv_path]) + + spirv_cross_path = paths.spirv_cross + + sm = shader_to_sm(shader) + subprocess.check_call([spirv_cross_path, '--entry', 'main', '--output', reflect_path, spirv_path, '--reflect', '--iterations', str(iterations)]) + return (spirv_path, reflect_path) + +def validate_shader(shader, vulkan, paths): + if vulkan: + subprocess.check_call([paths.glslang, '--amb', '--target-env', 'vulkan1.1', '-V', shader]) + else: + subprocess.check_call([paths.glslang, shader]) + +def cross_compile(shader, vulkan, spirv, invalid_spirv, eliminate, is_legacy, flatten_ubo, sso, flatten_dim, opt, push_ubo, iterations, paths): + spirv_path = create_temporary() + glsl_path = create_temporary(os.path.basename(shader)) + + if vulkan or spirv: + vulkan_glsl_path = create_temporary('vk' + os.path.basename(shader)) + + spirv_cmd = [paths.spirv_as, '--target-env', 'vulkan1.1', '-o', spirv_path, shader] + if '.preserve.' in shader: + spirv_cmd.append('--preserve-numeric-ids') + + if spirv: + subprocess.check_call(spirv_cmd) + else: + subprocess.check_call([paths.glslang, '--amb', '--target-env', 'vulkan1.1', '-V', '-o', spirv_path, shader]) + + if opt and (not invalid_spirv): + subprocess.check_call([paths.spirv_opt, '--skip-validation', '-O', '-o', spirv_path, spirv_path]) + + if not invalid_spirv: + subprocess.check_call([paths.spirv_val, '--scalar-block-layout', '--target-env', 'vulkan1.1', spirv_path]) + + extra_args = ['--iterations', str(iterations)] + if eliminate: + extra_args += ['--remove-unused-variables'] + if is_legacy: + extra_args += ['--version', '100', '--es'] + if flatten_ubo: + extra_args += ['--flatten-ubo'] + if sso: + extra_args += ['--separate-shader-objects'] + if flatten_dim: + extra_args += ['--flatten-multidimensional-arrays'] + if push_ubo: + extra_args += ['--glsl-emit-push-constant-as-ubo'] + if '.line.' in shader: + extra_args += ['--emit-line-directives'] + if '.no-samplerless.' in shader: + extra_args += ['--vulkan-glsl-disable-ext-samplerless-texture-functions'] + + spirv_cross_path = paths.spirv_cross + + # A shader might not be possible to make valid GLSL from, skip validation for this case. + if not ('nocompat' in glsl_path): + subprocess.check_call([spirv_cross_path, '--entry', 'main', '--output', glsl_path, spirv_path] + extra_args) + validate_shader(glsl_path, False, paths) + else: + remove_file(glsl_path) + glsl_path = None + + if vulkan or spirv: + subprocess.check_call([spirv_cross_path, '--entry', 'main', '--vulkan-semantics', '--output', vulkan_glsl_path, spirv_path] + extra_args) + validate_shader(vulkan_glsl_path, True, paths) + # SPIR-V shaders might just want to validate Vulkan GLSL output, we don't always care about the output. + if not vulkan: + remove_file(vulkan_glsl_path) + + return (spirv_path, glsl_path, vulkan_glsl_path if vulkan else None) + +def make_unix_newline(buf): + decoded = codecs.decode(buf, 'utf-8') + decoded = decoded.replace('\r', '') + return codecs.encode(decoded, 'utf-8') + +def md5_for_file(path): + md5 = hashlib.md5() + with open(path, 'rb') as f: + for chunk in iter(lambda: make_unix_newline(f.read(8192)), b''): + md5.update(chunk) + return md5.digest() + +def make_reference_dir(path): + base = os.path.dirname(path) + if not os.path.exists(base): + os.makedirs(base) + +def reference_path(directory, relpath, opt): + split_paths = os.path.split(directory) + reference_dir = os.path.join(split_paths[0], 'reference/' + ('opt/' if opt else '')) + reference_dir = os.path.join(reference_dir, split_paths[1]) + return os.path.join(reference_dir, relpath) + +def regression_check_reflect(shader, json_file, args): + reference = reference_path(shader[0], shader[1], args.opt) + '.json' + joined_path = os.path.join(shader[0], shader[1]) + print('Reference shader reflection path:', reference) + if os.path.exists(reference): + actual = md5_for_file(json_file) + expected = md5_for_file(reference) + if actual != expected: + if args.update: + print('Generated reflection json has changed for {}!'.format(reference)) + # If we expect changes, update the reference file. + if os.path.exists(reference): + remove_file(reference) + make_reference_dir(reference) + shutil.move(json_file, reference) + else: + print('Generated reflection json in {} does not match reference {}!'.format(json_file, reference)) + with open(json_file, 'r') as f: + print('') + print('Generated:') + print('======================') + print(f.read()) + print('======================') + print('') + + # Otherwise, fail the test. Keep the shader file around so we can inspect. + if not args.keep: + remove_file(json_file) + + raise RuntimeError('Does not match reference') + else: + remove_file(json_file) + else: + print('Found new shader {}. Placing generated source code in {}'.format(joined_path, reference)) + make_reference_dir(reference) + shutil.move(json_file, reference) + +def regression_check(shader, glsl, args): + reference = reference_path(shader[0], shader[1], args.opt) + joined_path = os.path.join(shader[0], shader[1]) + print('Reference shader path:', reference) + + if os.path.exists(reference): + if md5_for_file(glsl) != md5_for_file(reference): + if args.update: + print('Generated source code has changed for {}!'.format(reference)) + # If we expect changes, update the reference file. + if os.path.exists(reference): + remove_file(reference) + make_reference_dir(reference) + shutil.move(glsl, reference) + else: + print('Generated source code in {} does not match reference {}!'.format(glsl, reference)) + with open(glsl, 'r') as f: + print('') + print('Generated:') + print('======================') + print(f.read()) + print('======================') + print('') + + # Otherwise, fail the test. Keep the shader file around so we can inspect. + if not args.keep: + remove_file(glsl) + raise RuntimeError('Does not match reference') + else: + remove_file(glsl) + else: + print('Found new shader {}. Placing generated source code in {}'.format(joined_path, reference)) + make_reference_dir(reference) + shutil.move(glsl, reference) + +def shader_is_vulkan(shader): + return '.vk.' in shader + +def shader_is_desktop(shader): + return '.desktop.' in shader + +def shader_is_eliminate_dead_variables(shader): + return '.noeliminate.' not in shader + +def shader_is_spirv(shader): + return '.asm.' in shader + +def shader_is_invalid_spirv(shader): + return '.invalid.' in shader + +def shader_is_legacy(shader): + return '.legacy.' in shader + +def shader_is_flatten_ubo(shader): + return '.flatten.' in shader + +def shader_is_sso(shader): + return '.sso.' in shader + +def shader_is_flatten_dimensions(shader): + return '.flatten_dim.' in shader + +def shader_is_noopt(shader): + return '.noopt.' in shader + +def shader_is_push_ubo(shader): + return '.push-ubo.' in shader + +def test_shader(stats, shader, args, paths): + joined_path = os.path.join(shader[0], shader[1]) + vulkan = shader_is_vulkan(shader[1]) + desktop = shader_is_desktop(shader[1]) + eliminate = shader_is_eliminate_dead_variables(shader[1]) + is_spirv = shader_is_spirv(shader[1]) + invalid_spirv = shader_is_invalid_spirv(shader[1]) + is_legacy = shader_is_legacy(shader[1]) + flatten_ubo = shader_is_flatten_ubo(shader[1]) + sso = shader_is_sso(shader[1]) + flatten_dim = shader_is_flatten_dimensions(shader[1]) + noopt = shader_is_noopt(shader[1]) + push_ubo = shader_is_push_ubo(shader[1]) + + print('Testing shader:', joined_path) + spirv, glsl, vulkan_glsl = cross_compile(joined_path, vulkan, is_spirv, invalid_spirv, eliminate, is_legacy, flatten_ubo, sso, flatten_dim, args.opt and (not noopt), push_ubo, args.iterations, paths) + + # Only test GLSL stats if we have a shader following GL semantics. + if stats and (not vulkan) and (not is_spirv) and (not desktop): + cross_stats = get_shader_stats(glsl) + + if glsl: + regression_check(shader, glsl, args) + if vulkan_glsl: + regression_check((shader[0], shader[1] + '.vk'), vulkan_glsl, args) + + remove_file(spirv) + + if stats and (not vulkan) and (not is_spirv) and (not desktop): + pristine_stats = get_shader_stats(joined_path) + + a = [] + a.append(shader[1]) + for i in pristine_stats: + a.append(str(i)) + for i in cross_stats: + a.append(str(i)) + print(','.join(a), file = stats) + +def test_shader_msl(stats, shader, args, paths): + joined_path = os.path.join(shader[0], shader[1]) + print('\nTesting MSL shader:', joined_path) + is_spirv = shader_is_spirv(shader[1]) + noopt = shader_is_noopt(shader[1]) + spirv, msl = cross_compile_msl(joined_path, is_spirv, args.opt and (not noopt), args.iterations, paths) + regression_check(shader, msl, args) + + # Uncomment the following line to print the temp SPIR-V file path. + # This temp SPIR-V file is not deleted until after the Metal validation step below. + # If Metal validation fails, the temp SPIR-V file can be copied out and + # used as input to an invocation of spirv-cross to debug from Xcode directly. + # To do so, build spriv-cross using `make DEBUG=1`, then run the spriv-cross + # executable from Xcode using args: `--msl --entry main --output msl_path spirv_path`. +# print('SPRIV shader: ' + spirv) + + shader_is_msl22 = 'msl22' in joined_path + skip_validation = shader_is_msl22 and (not args.msl22) + if '.invalid.' in joined_path: + skip_validation = True + + if (not args.force_no_external_validation) and (not skip_validation): + validate_shader_msl(shader, args.opt) + + remove_file(spirv) + +def test_shader_hlsl(stats, shader, args, paths): + joined_path = os.path.join(shader[0], shader[1]) + print('Testing HLSL shader:', joined_path) + is_spirv = shader_is_spirv(shader[1]) + noopt = shader_is_noopt(shader[1]) + spirv, hlsl = cross_compile_hlsl(joined_path, is_spirv, args.opt and (not noopt), args.force_no_external_validation, args.iterations, paths) + regression_check(shader, hlsl, args) + remove_file(spirv) + +def test_shader_reflect(stats, shader, args, paths): + joined_path = os.path.join(shader[0], shader[1]) + print('Testing shader reflection:', joined_path) + is_spirv = shader_is_spirv(shader[1]) + noopt = shader_is_noopt(shader[1]) + spirv, reflect = cross_compile_reflect(joined_path, is_spirv, args.opt and (not noopt), args.iterations, paths) + regression_check_reflect(shader, reflect, args) + remove_file(spirv) + +def test_shader_file(relpath, stats, args, backend): + paths = Paths(args.spirv_cross, args.glslang, args.spirv_as, args.spirv_val, args.spirv_opt) + try: + if backend == 'msl': + test_shader_msl(stats, (args.folder, relpath), args, paths) + elif backend == 'hlsl': + test_shader_hlsl(stats, (args.folder, relpath), args, paths) + elif backend == 'reflect': + test_shader_reflect(stats, (args.folder, relpath), args, paths) + else: + test_shader(stats, (args.folder, relpath), args, paths) + return None + except Exception as e: + return e + +def test_shaders_helper(stats, backend, args): + all_files = [] + for root, dirs, files in os.walk(os.path.join(args.folder)): + files = [ f for f in files if not f.startswith(".") ] #ignore system files (esp OSX) + for i in files: + path = os.path.join(root, i) + relpath = os.path.relpath(path, args.folder) + all_files.append(relpath) + + # The child processes in parallel execution mode don't have the proper state for the global args variable, so + # at this point we need to switch to explicit arguments + if args.parallel: + pool = multiprocessing.Pool(multiprocessing.cpu_count()) + + results = [] + for f in all_files: + results.append(pool.apply_async(test_shader_file, + args = (f, stats, args, backend))) + + for res in results: + error = res.get() + if error is not None: + pool.close() + pool.join() + print('Error:', error) + sys.exit(1) + else: + for i in all_files: + e = test_shader_file(i, stats, args, backend) + if e is not None: + print('Error:', e) + sys.exit(1) + +def test_shaders(backend, args): + if args.malisc: + with open('stats.csv', 'w') as stats: + print('Shader,OrigRegs,OrigUniRegs,OrigALUShort,OrigLSShort,OrigTEXShort,OrigALULong,OrigLSLong,OrigTEXLong,CrossRegs,CrossUniRegs,CrossALUShort,CrossLSShort,CrossTEXShort,CrossALULong,CrossLSLong,CrossTEXLong', file = stats) + test_shaders_helper(stats, backend, args) + else: + test_shaders_helper(None, backend, args) + +def main(): + parser = argparse.ArgumentParser(description = 'Script for regression testing.') + parser.add_argument('folder', + help = 'Folder containing shader files to test.') + parser.add_argument('--update', + action = 'store_true', + help = 'Updates reference files if there is a mismatch. Use when legitimate changes in output is found.') + parser.add_argument('--keep', + action = 'store_true', + help = 'Leave failed GLSL shaders on disk if they fail regression. Useful for debugging.') + parser.add_argument('--malisc', + action = 'store_true', + help = 'Use malisc offline compiler to determine static cycle counts before and after spirv-cross.') + parser.add_argument('--msl', + action = 'store_true', + help = 'Test Metal backend.') + parser.add_argument('--metal', + action = 'store_true', + help = 'Deprecated Metal option. Use --msl instead.') + parser.add_argument('--hlsl', + action = 'store_true', + help = 'Test HLSL backend.') + parser.add_argument('--force-no-external-validation', + action = 'store_true', + help = 'Disable all external validation.') + parser.add_argument('--opt', + action = 'store_true', + help = 'Run SPIRV-Tools optimization passes as well.') + parser.add_argument('--reflect', + action = 'store_true', + help = 'Test reflection backend.') + parser.add_argument('--parallel', + action = 'store_true', + help = 'Execute tests in parallel. Useful for doing regression quickly, but bad for debugging and stat output.') + parser.add_argument('--spirv-cross', + default = './spirv-cross', + help = 'Explicit path to spirv-cross') + parser.add_argument('--glslang', + default = 'glslangValidator', + help = 'Explicit path to glslangValidator') + parser.add_argument('--spirv-as', + default = 'spirv-as', + help = 'Explicit path to spirv-as') + parser.add_argument('--spirv-val', + default = 'spirv-val', + help = 'Explicit path to spirv-val') + parser.add_argument('--spirv-opt', + default = 'spirv-opt', + help = 'Explicit path to spirv-opt') + parser.add_argument('--iterations', + default = 1, + type = int, + help = 'Number of iterations to run SPIRV-Cross (benchmarking)') + + args = parser.parse_args() + if not args.folder: + sys.stderr.write('Need shader folder.\n') + sys.exit(1) + + if (args.parallel and (args.malisc or args.force_no_external_validation or args.update)): + sys.stderr.write('Parallel execution is disabled when using the flags --update, --malisc or --force-no-external-validation\n') + args.parallel = False + + args.msl22 = False + if args.msl: + print_msl_compiler_version() + args.msl22 = msl_compiler_supports_22() + + backend = 'glsl' + if (args.msl or args.metal): + backend = 'msl' + elif args.hlsl: + backend = 'hlsl' + elif args.reflect: + backend = 'reflect' + + test_shaders(backend, args) + if args.malisc: + print('Stats in stats.csv!') + print('Tests completed!') + +if __name__ == '__main__': + main() |