1#!/usr/bin/env python3 2 3# Copyright 2015-2021 Arm Limited 4# SPDX-License-Identifier: Apache-2.0 5# 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 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 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. 17 18import sys 19import os 20import os.path 21import subprocess 22import tempfile 23import re 24import itertools 25import hashlib 26import shutil 27import argparse 28import codecs 29import json 30import multiprocessing 31import errno 32from functools import partial 33 34class Paths(): 35 def __init__(self, spirv_cross, glslang, spirv_as, spirv_val, spirv_opt): 36 self.spirv_cross = spirv_cross 37 self.glslang = glslang 38 self.spirv_as = spirv_as 39 self.spirv_val = spirv_val 40 self.spirv_opt = spirv_opt 41 42def remove_file(path): 43 #print('Removing file:', path) 44 os.remove(path) 45 46def create_temporary(suff = ''): 47 f, path = tempfile.mkstemp(suffix = suff) 48 os.close(f) 49 #print('Creating temporary:', path) 50 return path 51 52def parse_stats(stats): 53 m = re.search('([0-9]+) work registers', stats) 54 registers = int(m.group(1)) if m else 0 55 56 m = re.search('([0-9]+) uniform registers', stats) 57 uniform_regs = int(m.group(1)) if m else 0 58 59 m_list = re.findall('(-?[0-9]+)\s+(-?[0-9]+)\s+(-?[0-9]+)', stats) 60 alu_short = float(m_list[1][0]) if m_list else 0 61 ls_short = float(m_list[1][1]) if m_list else 0 62 tex_short = float(m_list[1][2]) if m_list else 0 63 alu_long = float(m_list[2][0]) if m_list else 0 64 ls_long = float(m_list[2][1]) if m_list else 0 65 tex_long = float(m_list[2][2]) if m_list else 0 66 67 return (registers, uniform_regs, alu_short, ls_short, tex_short, alu_long, ls_long, tex_long) 68 69def get_shader_type(shader): 70 _, ext = os.path.splitext(shader) 71 if ext == '.vert': 72 return '--vertex' 73 elif ext == '.frag': 74 return '--fragment' 75 elif ext == '.comp': 76 return '--compute' 77 elif ext == '.tesc': 78 return '--tessellation_control' 79 elif ext == '.tese': 80 return '--tessellation_evaluation' 81 elif ext == '.geom': 82 return '--geometry' 83 else: 84 return '' 85 86def get_shader_stats(shader): 87 path = create_temporary() 88 89 p = subprocess.Popen(['malisc', get_shader_type(shader), '--core', 'Mali-T760', '-V', shader], stdout = subprocess.PIPE, stderr = subprocess.PIPE) 90 stdout, stderr = p.communicate() 91 remove_file(path) 92 93 if p.returncode != 0: 94 print(stderr.decode('utf-8')) 95 raise OSError('malisc failed') 96 p.wait() 97 98 returned = stdout.decode('utf-8') 99 return parse_stats(returned) 100 101def print_msl_compiler_version(): 102 try: 103 subprocess.check_call(['xcrun', '--sdk', 'iphoneos', 'metal', '--version']) 104 print('... are the Metal compiler characteristics.\n') # display after so xcrun FNF is silent 105 except OSError as e: 106 if (e.errno != errno.ENOENT): # Ignore xcrun not found error 107 raise 108 print('Metal SDK is not present.\n') 109 except subprocess.CalledProcessError: 110 pass 111 112def msl_compiler_supports_version(version): 113 try: 114 subprocess.check_call(['xcrun', '--sdk', 'macosx', 'metal', '-x', 'metal', '-std=macos-metal' + version, '-'], 115 stdin = subprocess.DEVNULL, stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL) 116 print('Current SDK supports MSL {0}. Enabling validation for MSL {0} shaders.'.format(version)) 117 return True 118 except OSError as e: 119 print('Failed to check if MSL {} is not supported. It probably is not.'.format(version)) 120 return False 121 except subprocess.CalledProcessError: 122 print('Current SDK does NOT support MSL {0}. Disabling validation for MSL {0} shaders.'.format(version)) 123 return False 124 125def path_to_msl_standard(shader): 126 if '.ios.' in shader: 127 if '.msl2.' in shader: 128 return '-std=ios-metal2.0' 129 elif '.msl21.' in shader: 130 return '-std=ios-metal2.1' 131 elif '.msl22.' in shader: 132 return '-std=ios-metal2.2' 133 elif '.msl23.' in shader: 134 return '-std=ios-metal2.3' 135 elif '.msl11.' in shader: 136 return '-std=ios-metal1.1' 137 elif '.msl10.' in shader: 138 return '-std=ios-metal1.0' 139 else: 140 return '-std=ios-metal1.2' 141 else: 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 '.msl11.' in shader: 151 return '-std=macos-metal1.1' 152 else: 153 return '-std=macos-metal1.2' 154 155def path_to_msl_standard_cli(shader): 156 if '.msl2.' in shader: 157 return '20000' 158 elif '.msl21.' in shader: 159 return '20100' 160 elif '.msl22.' in shader: 161 return '20200' 162 elif '.msl23.' in shader: 163 return '20300' 164 elif '.msl11.' in shader: 165 return '10100' 166 else: 167 return '10200' 168 169def validate_shader_msl(shader, opt): 170 msl_path = reference_path(shader[0], shader[1], opt) 171 try: 172 if '.ios.' in msl_path: 173 msl_os = 'iphoneos' 174 else: 175 msl_os = 'macosx' 176 subprocess.check_call(['xcrun', '--sdk', msl_os, 'metal', '-x', 'metal', path_to_msl_standard(msl_path), '-Werror', '-Wno-unused-variable', msl_path]) 177 print('Compiled Metal shader: ' + msl_path) # display after so xcrun FNF is silent 178 except OSError as oe: 179 if (oe.errno != errno.ENOENT): # Ignore xcrun not found error 180 raise 181 except subprocess.CalledProcessError: 182 print('Error compiling Metal shader: ' + msl_path) 183 raise RuntimeError('Failed to compile Metal shader') 184 185def cross_compile_msl(shader, spirv, opt, iterations, paths): 186 spirv_path = create_temporary() 187 msl_path = create_temporary(os.path.basename(shader)) 188 189 spirv_env = 'vulkan1.1spv1.4' if ('.spv14.' in shader) else 'vulkan1.1' 190 191 spirv_cmd = [paths.spirv_as, '--target-env', spirv_env, '-o', spirv_path, shader] 192 if '.preserve.' in shader: 193 spirv_cmd.append('--preserve-numeric-ids') 194 195 if spirv: 196 subprocess.check_call(spirv_cmd) 197 else: 198 subprocess.check_call([paths.glslang, '--amb' ,'--target-env', 'vulkan1.1', '-V', '-o', spirv_path, shader]) 199 200 if opt and (not shader_is_invalid_spirv(shader)): 201 if '.graphics-robust-access.' in shader: 202 subprocess.check_call([paths.spirv_opt, '--skip-validation', '-O', '--graphics-robust-access', '-o', spirv_path, spirv_path]) 203 else: 204 subprocess.check_call([paths.spirv_opt, '--skip-validation', '-O', '-o', spirv_path, spirv_path]) 205 206 spirv_cross_path = paths.spirv_cross 207 208 msl_args = [spirv_cross_path, '--output', msl_path, spirv_path, '--msl', '--iterations', str(iterations)] 209 msl_args.append('--msl-version') 210 msl_args.append(path_to_msl_standard_cli(shader)) 211 if not '.nomain.' in shader: 212 msl_args.append('--entry') 213 msl_args.append('main') 214 if '.swizzle.' in shader: 215 msl_args.append('--msl-swizzle-texture-samples') 216 if '.ios.' in shader: 217 msl_args.append('--msl-ios') 218 if '.pad-fragment.' in shader: 219 msl_args.append('--msl-pad-fragment-output') 220 if '.capture.' in shader: 221 msl_args.append('--msl-capture-output') 222 if '.domain.' in shader: 223 msl_args.append('--msl-domain-lower-left') 224 if '.argument.' in shader: 225 msl_args.append('--msl-argument-buffers') 226 if '.texture-buffer-native.' in shader: 227 msl_args.append('--msl-texture-buffer-native') 228 if '.framebuffer-fetch.' in shader: 229 msl_args.append('--msl-framebuffer-fetch') 230 if '.invariant-float-math.' in shader: 231 msl_args.append('--msl-invariant-float-math') 232 if '.emulate-cube-array.' in shader: 233 msl_args.append('--msl-emulate-cube-array') 234 if '.discrete.' in shader: 235 # Arbitrary for testing purposes. 236 msl_args.append('--msl-discrete-descriptor-set') 237 msl_args.append('2') 238 msl_args.append('--msl-discrete-descriptor-set') 239 msl_args.append('3') 240 if '.force-active.' in shader: 241 msl_args.append('--msl-force-active-argument-buffer-resources') 242 if '.line.' in shader: 243 msl_args.append('--emit-line-directives') 244 if '.multiview.' in shader: 245 msl_args.append('--msl-multiview') 246 if '.no-layered.' in shader: 247 msl_args.append('--msl-multiview-no-layered-rendering') 248 if '.viewfromdev.' in shader: 249 msl_args.append('--msl-view-index-from-device-index') 250 if '.dispatchbase.' in shader: 251 msl_args.append('--msl-dispatch-base') 252 if '.dynamic-buffer.' in shader: 253 # Arbitrary for testing purposes. 254 msl_args.append('--msl-dynamic-buffer') 255 msl_args.append('0') 256 msl_args.append('0') 257 msl_args.append('--msl-dynamic-buffer') 258 msl_args.append('1') 259 msl_args.append('2') 260 if '.inline-block.' in shader: 261 # Arbitrary for testing purposes. 262 msl_args.append('--msl-inline-uniform-block') 263 msl_args.append('0') 264 msl_args.append('0') 265 if '.device-argument-buffer.' in shader: 266 msl_args.append('--msl-device-argument-buffer') 267 msl_args.append('0') 268 msl_args.append('--msl-device-argument-buffer') 269 msl_args.append('1') 270 if '.force-native-array.' in shader: 271 msl_args.append('--msl-force-native-arrays') 272 if '.zero-initialize.' in shader: 273 msl_args.append('--force-zero-initialized-variables') 274 if '.frag-output.' in shader: 275 # Arbitrary for testing purposes. 276 msl_args.append('--msl-disable-frag-depth-builtin') 277 msl_args.append('--msl-disable-frag-stencil-ref-builtin') 278 msl_args.append('--msl-enable-frag-output-mask') 279 msl_args.append('0x000000ca') 280 if '.no-user-varying.' in shader: 281 msl_args.append('--msl-no-clip-distance-user-varying') 282 if '.shader-inputs.' in shader: 283 # Arbitrary for testing purposes. 284 msl_args.append('--msl-shader-input') 285 msl_args.append('0') 286 msl_args.append('u8') 287 msl_args.append('2') 288 msl_args.append('--msl-shader-input') 289 msl_args.append('1') 290 msl_args.append('u16') 291 msl_args.append('3') 292 msl_args.append('--msl-shader-input') 293 msl_args.append('6') 294 msl_args.append('other') 295 msl_args.append('4') 296 if '.multi-patch.' in shader: 297 msl_args.append('--msl-multi-patch-workgroup') 298 # Arbitrary for testing purposes. 299 msl_args.append('--msl-shader-input') 300 msl_args.append('0') 301 msl_args.append('any32') 302 msl_args.append('3') 303 msl_args.append('--msl-shader-input') 304 msl_args.append('1') 305 msl_args.append('any16') 306 msl_args.append('2') 307 if '.for-tess.' in shader: 308 msl_args.append('--msl-vertex-for-tessellation') 309 if '.fixed-sample-mask.' in shader: 310 msl_args.append('--msl-additional-fixed-sample-mask') 311 msl_args.append('0x00000022') 312 if '.arrayed-subpass.' in shader: 313 msl_args.append('--msl-arrayed-subpass-input') 314 if '.1d-as-2d.' in shader: 315 msl_args.append('--msl-texture-1d-as-2d') 316 if '.simd.' in shader: 317 msl_args.append('--msl-ios-use-simdgroup-functions') 318 if '.emulate-subgroup.' in shader: 319 msl_args.append('--msl-emulate-subgroups') 320 if '.fixed-subgroup.' in shader: 321 # Arbitrary for testing purposes. 322 msl_args.append('--msl-fixed-subgroup-size') 323 msl_args.append('32') 324 if '.force-sample.' in shader: 325 msl_args.append('--msl-force-sample-rate-shading') 326 if '.decoration-binding.' in shader: 327 msl_args.append('--msl-decoration-binding') 328 if '.mask-location-0.' in shader: 329 msl_args.append('--mask-stage-output-location') 330 msl_args.append('0') 331 msl_args.append('0') 332 if '.mask-location-1.' in shader: 333 msl_args.append('--mask-stage-output-location') 334 msl_args.append('1') 335 msl_args.append('0') 336 if '.mask-position.' in shader: 337 msl_args.append('--mask-stage-output-builtin') 338 msl_args.append('Position') 339 if '.mask-point-size.' in shader: 340 msl_args.append('--mask-stage-output-builtin') 341 msl_args.append('PointSize') 342 if '.mask-clip-distance.' in shader: 343 msl_args.append('--mask-stage-output-builtin') 344 msl_args.append('ClipDistance') 345 346 subprocess.check_call(msl_args) 347 348 if not shader_is_invalid_spirv(msl_path): 349 subprocess.check_call([paths.spirv_val, '--scalar-block-layout', '--target-env', spirv_env, spirv_path]) 350 351 return (spirv_path, msl_path) 352 353def shader_model_hlsl(shader): 354 if '.vert' in shader: 355 if '.sm30.' in shader: 356 return '-Tvs_3_0' 357 else: 358 return '-Tvs_5_1' 359 elif '.frag' in shader: 360 if '.sm30.' in shader: 361 return '-Tps_3_0' 362 else: 363 return '-Tps_5_1' 364 elif '.comp' in shader: 365 return '-Tcs_5_1' 366 else: 367 return None 368 369def shader_to_win_path(shader): 370 # It's (very) convenient to be able to run HLSL testing in wine on Unix-likes, so support that. 371 try: 372 with subprocess.Popen(['winepath', '-w', shader], stdout = subprocess.PIPE, stderr = subprocess.PIPE) as f: 373 stdout_data, stderr_data = f.communicate() 374 return stdout_data.decode('utf-8') 375 except OSError as oe: 376 if (oe.errno != errno.ENOENT): # Ignore not found errors 377 return shader 378 except subprocess.CalledProcessError: 379 raise 380 381 return shader 382 383ignore_fxc = False 384def validate_shader_hlsl(shader, force_no_external_validation, paths): 385 test_glslang = True 386 if '.nonuniformresource.' in shader: 387 test_glslang = False 388 if '.fxconly.' in shader: 389 test_glslang = False 390 391 hlsl_args = [paths.glslang, '--amb', '-e', 'main', '-D', '--target-env', 'vulkan1.1', '-V', shader] 392 if '.sm30.' in shader: 393 hlsl_args.append('--hlsl-dx9-compatible') 394 395 if test_glslang: 396 subprocess.check_call(hlsl_args) 397 398 is_no_fxc = '.nofxc.' in shader 399 global ignore_fxc 400 if (not ignore_fxc) and (not force_no_external_validation) and (not is_no_fxc): 401 try: 402 win_path = shader_to_win_path(shader) 403 args = ['fxc', '-nologo', shader_model_hlsl(shader), win_path] 404 if '.nonuniformresource.' in shader: 405 args.append('/enable_unbounded_descriptor_tables') 406 subprocess.check_call(args) 407 except OSError as oe: 408 if (oe.errno != errno.ENOENT): # Ignore not found errors 409 print('Failed to run FXC.') 410 ignore_fxc = True 411 raise 412 else: 413 print('Could not find FXC.') 414 ignore_fxc = True 415 except subprocess.CalledProcessError: 416 print('Failed compiling HLSL shader:', shader, 'with FXC.') 417 raise RuntimeError('Failed compiling HLSL shader') 418 419def shader_to_sm(shader): 420 if '.sm62.' in shader: 421 return '62' 422 elif '.sm60.' in shader: 423 return '60' 424 elif '.sm51.' in shader: 425 return '51' 426 elif '.sm30.' in shader: 427 return '30' 428 else: 429 return '50' 430 431def cross_compile_hlsl(shader, spirv, opt, force_no_external_validation, iterations, paths): 432 spirv_path = create_temporary() 433 hlsl_path = create_temporary(os.path.basename(shader)) 434 435 spirv_env = 'vulkan1.1spv1.4' if '.spv14.' in shader else 'vulkan1.1' 436 spirv_cmd = [paths.spirv_as, '--target-env', spirv_env, '-o', spirv_path, shader] 437 if '.preserve.' in shader: 438 spirv_cmd.append('--preserve-numeric-ids') 439 440 if spirv: 441 subprocess.check_call(spirv_cmd) 442 else: 443 subprocess.check_call([paths.glslang, '--amb', '--target-env', 'vulkan1.1', '-V', '-o', spirv_path, shader]) 444 445 if opt and (not shader_is_invalid_spirv(hlsl_path)): 446 subprocess.check_call([paths.spirv_opt, '--skip-validation', '-O', '-o', spirv_path, spirv_path]) 447 448 spirv_cross_path = paths.spirv_cross 449 450 sm = shader_to_sm(shader) 451 452 hlsl_args = [spirv_cross_path, '--entry', 'main', '--output', hlsl_path, spirv_path, '--hlsl-enable-compat', '--hlsl', '--shader-model', sm, '--iterations', str(iterations)] 453 if '.line.' in shader: 454 hlsl_args.append('--emit-line-directives') 455 if '.force-uav.' in shader: 456 hlsl_args.append('--hlsl-force-storage-buffer-as-uav') 457 if '.zero-initialize.' in shader: 458 hlsl_args.append('--force-zero-initialized-variables') 459 if '.nonwritable-uav-texture.' in shader: 460 hlsl_args.append('--hlsl-nonwritable-uav-texture-as-srv') 461 if '.native-16bit.' in shader: 462 hlsl_args.append('--hlsl-enable-16bit-types') 463 if '.flatten-matrix-vertex-input.' in shader: 464 hlsl_args.append('--hlsl-flatten-matrix-vertex-input-semantics') 465 466 subprocess.check_call(hlsl_args) 467 468 if not shader_is_invalid_spirv(hlsl_path): 469 subprocess.check_call([paths.spirv_val, '--scalar-block-layout', '--target-env', spirv_env, spirv_path]) 470 471 validate_shader_hlsl(hlsl_path, force_no_external_validation, paths) 472 473 return (spirv_path, hlsl_path) 474 475def cross_compile_reflect(shader, spirv, opt, iterations, paths): 476 spirv_path = create_temporary() 477 reflect_path = create_temporary(os.path.basename(shader)) 478 479 spirv_cmd = [paths.spirv_as, '--target-env', 'vulkan1.1', '-o', spirv_path, shader] 480 if '.preserve.' in shader: 481 spirv_cmd.append('--preserve-numeric-ids') 482 483 if spirv: 484 subprocess.check_call(spirv_cmd) 485 else: 486 subprocess.check_call([paths.glslang, '--amb', '--target-env', 'vulkan1.1', '-V', '-o', spirv_path, shader]) 487 488 if opt and (not shader_is_invalid_spirv(reflect_path)): 489 subprocess.check_call([paths.spirv_opt, '--skip-validation', '-O', '-o', spirv_path, spirv_path]) 490 491 spirv_cross_path = paths.spirv_cross 492 493 sm = shader_to_sm(shader) 494 subprocess.check_call([spirv_cross_path, '--entry', 'main', '--output', reflect_path, spirv_path, '--reflect', '--iterations', str(iterations)]) 495 return (spirv_path, reflect_path) 496 497def validate_shader(shader, vulkan, paths): 498 if vulkan: 499 spirv_14 = '.spv14.' in shader 500 glslang_env = 'spirv1.4' if spirv_14 else 'vulkan1.1' 501 subprocess.check_call([paths.glslang, '--amb', '--target-env', glslang_env, '-V', shader]) 502 else: 503 subprocess.check_call([paths.glslang, shader]) 504 505def cross_compile(shader, vulkan, spirv, invalid_spirv, eliminate, is_legacy, flatten_ubo, sso, flatten_dim, opt, push_ubo, iterations, paths): 506 spirv_path = create_temporary() 507 glsl_path = create_temporary(os.path.basename(shader)) 508 509 spirv_14 = '.spv14.' in shader 510 spirv_env = 'vulkan1.1spv1.4' if spirv_14 else 'vulkan1.1' 511 512 if vulkan or spirv: 513 vulkan_glsl_path = create_temporary('vk' + os.path.basename(shader)) 514 515 spirv_cmd = [paths.spirv_as, '--target-env', spirv_env, '-o', spirv_path, shader] 516 if '.preserve.' in shader: 517 spirv_cmd.append('--preserve-numeric-ids') 518 519 if spirv: 520 subprocess.check_call(spirv_cmd) 521 else: 522 glslang_env = 'spirv1.4' if spirv_14 else 'vulkan1.1' 523 subprocess.check_call([paths.glslang, '--amb', '--target-env', glslang_env, '-V', '-o', spirv_path, shader]) 524 525 if opt and (not invalid_spirv): 526 subprocess.check_call([paths.spirv_opt, '--skip-validation', '-O', '-o', spirv_path, spirv_path]) 527 528 if not invalid_spirv: 529 subprocess.check_call([paths.spirv_val, '--scalar-block-layout', '--target-env', spirv_env, spirv_path]) 530 531 extra_args = ['--iterations', str(iterations)] 532 if eliminate: 533 extra_args += ['--remove-unused-variables'] 534 if is_legacy: 535 extra_args += ['--version', '100', '--es'] 536 if flatten_ubo: 537 extra_args += ['--flatten-ubo'] 538 if sso: 539 extra_args += ['--separate-shader-objects'] 540 if flatten_dim: 541 extra_args += ['--flatten-multidimensional-arrays'] 542 if push_ubo: 543 extra_args += ['--glsl-emit-push-constant-as-ubo'] 544 if '.line.' in shader: 545 extra_args += ['--emit-line-directives'] 546 if '.no-samplerless.' in shader: 547 extra_args += ['--vulkan-glsl-disable-ext-samplerless-texture-functions'] 548 if '.no-qualifier-deduction.' in shader: 549 extra_args += ['--disable-storage-image-qualifier-deduction'] 550 if '.framebuffer-fetch.' in shader: 551 extra_args += ['--glsl-remap-ext-framebuffer-fetch', '0', '0'] 552 extra_args += ['--glsl-remap-ext-framebuffer-fetch', '1', '1'] 553 extra_args += ['--glsl-remap-ext-framebuffer-fetch', '2', '2'] 554 extra_args += ['--glsl-remap-ext-framebuffer-fetch', '3', '3'] 555 if '.framebuffer-fetch-noncoherent.' in shader: 556 extra_args += ['--glsl-ext-framebuffer-fetch-noncoherent'] 557 if '.zero-initialize.' in shader: 558 extra_args += ['--force-zero-initialized-variables'] 559 if '.force-flattened-io.' in shader: 560 extra_args += ['--glsl-force-flattened-io-blocks'] 561 562 spirv_cross_path = paths.spirv_cross 563 564 # A shader might not be possible to make valid GLSL from, skip validation for this case. 565 if (not ('nocompat' in glsl_path)) or (not vulkan): 566 subprocess.check_call([spirv_cross_path, '--entry', 'main', '--output', glsl_path, spirv_path] + extra_args) 567 if not 'nocompat' in glsl_path: 568 validate_shader(glsl_path, False, paths) 569 else: 570 remove_file(glsl_path) 571 glsl_path = None 572 573 if (vulkan or spirv) and (not is_legacy): 574 subprocess.check_call([spirv_cross_path, '--entry', 'main', '-V', '--output', vulkan_glsl_path, spirv_path] + extra_args) 575 validate_shader(vulkan_glsl_path, True, paths) 576 # SPIR-V shaders might just want to validate Vulkan GLSL output, we don't always care about the output. 577 if not vulkan: 578 remove_file(vulkan_glsl_path) 579 580 return (spirv_path, glsl_path, vulkan_glsl_path if vulkan else None) 581 582def make_unix_newline(buf): 583 decoded = codecs.decode(buf, 'utf-8') 584 decoded = decoded.replace('\r', '') 585 return codecs.encode(decoded, 'utf-8') 586 587def md5_for_file(path): 588 md5 = hashlib.md5() 589 with open(path, 'rb') as f: 590 for chunk in iter(lambda: make_unix_newline(f.read(8192)), b''): 591 md5.update(chunk) 592 return md5.digest() 593 594def make_reference_dir(path): 595 base = os.path.dirname(path) 596 if not os.path.exists(base): 597 os.makedirs(base) 598 599def reference_path(directory, relpath, opt): 600 split_paths = os.path.split(directory) 601 reference_dir = os.path.join(split_paths[0], 'reference/' + ('opt/' if opt else '')) 602 reference_dir = os.path.join(reference_dir, split_paths[1]) 603 return os.path.join(reference_dir, relpath) 604 605def regression_check_reflect(shader, json_file, args): 606 reference = reference_path(shader[0], shader[1], args.opt) + '.json' 607 joined_path = os.path.join(shader[0], shader[1]) 608 print('Reference shader reflection path:', reference) 609 if os.path.exists(reference): 610 actual = md5_for_file(json_file) 611 expected = md5_for_file(reference) 612 if actual != expected: 613 if args.update: 614 print('Generated reflection json has changed for {}!'.format(reference)) 615 # If we expect changes, update the reference file. 616 if os.path.exists(reference): 617 remove_file(reference) 618 make_reference_dir(reference) 619 shutil.move(json_file, reference) 620 else: 621 print('Generated reflection json in {} does not match reference {}!'.format(json_file, reference)) 622 with open(json_file, 'r') as f: 623 print('') 624 print('Generated:') 625 print('======================') 626 print(f.read()) 627 print('======================') 628 print('') 629 630 # Otherwise, fail the test. Keep the shader file around so we can inspect. 631 if not args.keep: 632 remove_file(json_file) 633 634 raise RuntimeError('Does not match reference') 635 else: 636 remove_file(json_file) 637 else: 638 print('Found new shader {}. Placing generated source code in {}'.format(joined_path, reference)) 639 make_reference_dir(reference) 640 shutil.move(json_file, reference) 641 642def regression_check(shader, glsl, args): 643 reference = reference_path(shader[0], shader[1], args.opt) 644 joined_path = os.path.join(shader[0], shader[1]) 645 print('Reference shader path:', reference) 646 647 if os.path.exists(reference): 648 if md5_for_file(glsl) != md5_for_file(reference): 649 if args.update: 650 print('Generated source code has changed for {}!'.format(reference)) 651 # If we expect changes, update the reference file. 652 if os.path.exists(reference): 653 remove_file(reference) 654 make_reference_dir(reference) 655 shutil.move(glsl, reference) 656 else: 657 print('Generated source code in {} does not match reference {}!'.format(glsl, reference)) 658 with open(glsl, 'r') as f: 659 print('') 660 print('Generated:') 661 print('======================') 662 print(f.read()) 663 print('======================') 664 print('') 665 666 # Otherwise, fail the test. Keep the shader file around so we can inspect. 667 if not args.keep: 668 remove_file(glsl) 669 raise RuntimeError('Does not match reference') 670 else: 671 remove_file(glsl) 672 else: 673 print('Found new shader {}. Placing generated source code in {}'.format(joined_path, reference)) 674 make_reference_dir(reference) 675 shutil.move(glsl, reference) 676 677def shader_is_vulkan(shader): 678 return '.vk.' in shader 679 680def shader_is_desktop(shader): 681 return '.desktop.' in shader 682 683def shader_is_eliminate_dead_variables(shader): 684 return '.noeliminate.' not in shader 685 686def shader_is_spirv(shader): 687 return '.asm.' in shader 688 689def shader_is_invalid_spirv(shader): 690 return '.invalid.' in shader 691 692def shader_is_legacy(shader): 693 return '.legacy.' in shader 694 695def shader_is_flatten_ubo(shader): 696 return '.flatten.' in shader 697 698def shader_is_sso(shader): 699 return '.sso.' in shader 700 701def shader_is_flatten_dimensions(shader): 702 return '.flatten_dim.' in shader 703 704def shader_is_noopt(shader): 705 return '.noopt.' in shader 706 707def shader_is_push_ubo(shader): 708 return '.push-ubo.' in shader 709 710def test_shader(stats, shader, args, paths): 711 joined_path = os.path.join(shader[0], shader[1]) 712 vulkan = shader_is_vulkan(shader[1]) 713 desktop = shader_is_desktop(shader[1]) 714 eliminate = shader_is_eliminate_dead_variables(shader[1]) 715 is_spirv = shader_is_spirv(shader[1]) 716 invalid_spirv = shader_is_invalid_spirv(shader[1]) 717 is_legacy = shader_is_legacy(shader[1]) 718 flatten_ubo = shader_is_flatten_ubo(shader[1]) 719 sso = shader_is_sso(shader[1]) 720 flatten_dim = shader_is_flatten_dimensions(shader[1]) 721 noopt = shader_is_noopt(shader[1]) 722 push_ubo = shader_is_push_ubo(shader[1]) 723 724 print('Testing shader:', joined_path) 725 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) 726 727 # Only test GLSL stats if we have a shader following GL semantics. 728 if stats and (not vulkan) and (not is_spirv) and (not desktop): 729 cross_stats = get_shader_stats(glsl) 730 731 if glsl: 732 regression_check(shader, glsl, args) 733 if vulkan_glsl: 734 regression_check((shader[0], shader[1] + '.vk'), vulkan_glsl, args) 735 736 remove_file(spirv) 737 738 if stats and (not vulkan) and (not is_spirv) and (not desktop): 739 pristine_stats = get_shader_stats(joined_path) 740 741 a = [] 742 a.append(shader[1]) 743 for i in pristine_stats: 744 a.append(str(i)) 745 for i in cross_stats: 746 a.append(str(i)) 747 print(','.join(a), file = stats) 748 749def test_shader_msl(stats, shader, args, paths): 750 joined_path = os.path.join(shader[0], shader[1]) 751 print('\nTesting MSL shader:', joined_path) 752 is_spirv = shader_is_spirv(shader[1]) 753 noopt = shader_is_noopt(shader[1]) 754 spirv, msl = cross_compile_msl(joined_path, is_spirv, args.opt and (not noopt), args.iterations, paths) 755 regression_check(shader, msl, args) 756 757 # Uncomment the following line to print the temp SPIR-V file path. 758 # This temp SPIR-V file is not deleted until after the Metal validation step below. 759 # If Metal validation fails, the temp SPIR-V file can be copied out and 760 # used as input to an invocation of spirv-cross to debug from Xcode directly. 761 # To do so, build spriv-cross using `make DEBUG=1`, then run the spriv-cross 762 # executable from Xcode using args: `--msl --entry main --output msl_path spirv_path`. 763# print('SPRIV shader: ' + spirv) 764 765 shader_is_msl22 = 'msl22' in joined_path 766 shader_is_msl23 = 'msl23' in joined_path 767 skip_validation = (shader_is_msl22 and (not args.msl22)) or (shader_is_msl23 and (not args.msl23)) 768 if '.invalid.' in joined_path: 769 skip_validation = True 770 771 if (not args.force_no_external_validation) and (not skip_validation): 772 validate_shader_msl(shader, args.opt) 773 774 remove_file(spirv) 775 776def test_shader_hlsl(stats, shader, args, paths): 777 joined_path = os.path.join(shader[0], shader[1]) 778 print('Testing HLSL shader:', joined_path) 779 is_spirv = shader_is_spirv(shader[1]) 780 noopt = shader_is_noopt(shader[1]) 781 spirv, hlsl = cross_compile_hlsl(joined_path, is_spirv, args.opt and (not noopt), args.force_no_external_validation, args.iterations, paths) 782 regression_check(shader, hlsl, args) 783 remove_file(spirv) 784 785def test_shader_reflect(stats, shader, args, paths): 786 joined_path = os.path.join(shader[0], shader[1]) 787 print('Testing shader reflection:', joined_path) 788 is_spirv = shader_is_spirv(shader[1]) 789 noopt = shader_is_noopt(shader[1]) 790 spirv, reflect = cross_compile_reflect(joined_path, is_spirv, args.opt and (not noopt), args.iterations, paths) 791 regression_check_reflect(shader, reflect, args) 792 remove_file(spirv) 793 794def test_shader_file(relpath, stats, args, backend): 795 paths = Paths(args.spirv_cross, args.glslang, args.spirv_as, args.spirv_val, args.spirv_opt) 796 try: 797 if backend == 'msl': 798 test_shader_msl(stats, (args.folder, relpath), args, paths) 799 elif backend == 'hlsl': 800 test_shader_hlsl(stats, (args.folder, relpath), args, paths) 801 elif backend == 'reflect': 802 test_shader_reflect(stats, (args.folder, relpath), args, paths) 803 else: 804 test_shader(stats, (args.folder, relpath), args, paths) 805 return None 806 except Exception as e: 807 return e 808 809def test_shaders_helper(stats, backend, args): 810 all_files = [] 811 for root, dirs, files in os.walk(os.path.join(args.folder)): 812 files = [ f for f in files if not f.startswith(".") ] #ignore system files (esp OSX) 813 for i in files: 814 path = os.path.join(root, i) 815 relpath = os.path.relpath(path, args.folder) 816 all_files.append(relpath) 817 818 # The child processes in parallel execution mode don't have the proper state for the global args variable, so 819 # at this point we need to switch to explicit arguments 820 if args.parallel: 821 with multiprocessing.Pool(multiprocessing.cpu_count()) as pool: 822 results = [] 823 for f in all_files: 824 results.append(pool.apply_async(test_shader_file, 825 args = (f, stats, args, backend))) 826 827 pool.close() 828 pool.join() 829 results_completed = [res.get() for res in results] 830 831 for error in results_completed: 832 if error is not None: 833 print('Error:', error) 834 sys.exit(1) 835 836 else: 837 for i in all_files: 838 e = test_shader_file(i, stats, args, backend) 839 if e is not None: 840 print('Error:', e) 841 sys.exit(1) 842 843def test_shaders(backend, args): 844 if args.malisc: 845 with open('stats.csv', 'w') as stats: 846 print('Shader,OrigRegs,OrigUniRegs,OrigALUShort,OrigLSShort,OrigTEXShort,OrigALULong,OrigLSLong,OrigTEXLong,CrossRegs,CrossUniRegs,CrossALUShort,CrossLSShort,CrossTEXShort,CrossALULong,CrossLSLong,CrossTEXLong', file = stats) 847 test_shaders_helper(stats, backend, args) 848 else: 849 test_shaders_helper(None, backend, args) 850 851def main(): 852 parser = argparse.ArgumentParser(description = 'Script for regression testing.') 853 parser.add_argument('folder', 854 help = 'Folder containing shader files to test.') 855 parser.add_argument('--update', 856 action = 'store_true', 857 help = 'Updates reference files if there is a mismatch. Use when legitimate changes in output is found.') 858 parser.add_argument('--keep', 859 action = 'store_true', 860 help = 'Leave failed GLSL shaders on disk if they fail regression. Useful for debugging.') 861 parser.add_argument('--malisc', 862 action = 'store_true', 863 help = 'Use malisc offline compiler to determine static cycle counts before and after spirv-cross.') 864 parser.add_argument('--msl', 865 action = 'store_true', 866 help = 'Test Metal backend.') 867 parser.add_argument('--metal', 868 action = 'store_true', 869 help = 'Deprecated Metal option. Use --msl instead.') 870 parser.add_argument('--hlsl', 871 action = 'store_true', 872 help = 'Test HLSL backend.') 873 parser.add_argument('--force-no-external-validation', 874 action = 'store_true', 875 help = 'Disable all external validation.') 876 parser.add_argument('--opt', 877 action = 'store_true', 878 help = 'Run SPIRV-Tools optimization passes as well.') 879 parser.add_argument('--reflect', 880 action = 'store_true', 881 help = 'Test reflection backend.') 882 parser.add_argument('--parallel', 883 action = 'store_true', 884 help = 'Execute tests in parallel. Useful for doing regression quickly, but bad for debugging and stat output.') 885 parser.add_argument('--spirv-cross', 886 default = './spirv-cross', 887 help = 'Explicit path to spirv-cross') 888 parser.add_argument('--glslang', 889 default = 'glslangValidator', 890 help = 'Explicit path to glslangValidator') 891 parser.add_argument('--spirv-as', 892 default = 'spirv-as', 893 help = 'Explicit path to spirv-as') 894 parser.add_argument('--spirv-val', 895 default = 'spirv-val', 896 help = 'Explicit path to spirv-val') 897 parser.add_argument('--spirv-opt', 898 default = 'spirv-opt', 899 help = 'Explicit path to spirv-opt') 900 parser.add_argument('--iterations', 901 default = 1, 902 type = int, 903 help = 'Number of iterations to run SPIRV-Cross (benchmarking)') 904 905 args = parser.parse_args() 906 if not args.folder: 907 sys.stderr.write('Need shader folder.\n') 908 sys.exit(1) 909 910 if (args.parallel and (args.malisc or args.force_no_external_validation or args.update)): 911 sys.stderr.write('Parallel execution is disabled when using the flags --update, --malisc or --force-no-external-validation\n') 912 args.parallel = False 913 914 args.msl22 = False 915 args.msl23 = False 916 if args.msl: 917 print_msl_compiler_version() 918 args.msl22 = msl_compiler_supports_version('2.2') 919 args.msl23 = msl_compiler_supports_version('2.3') 920 921 backend = 'glsl' 922 if (args.msl or args.metal): 923 backend = 'msl' 924 elif args.hlsl: 925 backend = 'hlsl' 926 elif args.reflect: 927 backend = 'reflect' 928 929 test_shaders(backend, args) 930 if args.malisc: 931 print('Stats in stats.csv!') 932 print('Tests completed!') 933 934if __name__ == '__main__': 935 main() 936