yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
e4d1200cb
master
1// options.cpp 2#include "options.h" 3 4#include "../../source/core/slang-io.h" 5#include "../../source/core/slang-string-util.h" 6 7#include <stdio.h> 8#include <stdlib.h> 9 10using namespace Slang ; 11 12/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!! CategorySet !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ 13 14TestCategory * TestCategorySet ::add (String const & name ,TestCategory * parent ) 15{ 16RefPtr < TestCategory > category (new TestCategory ); 17category -> name = name ; 18category -> parent = parent ; 19 20m_categoryMap .add (name ,category ); 21return category ; 22} 23 24TestCategory * TestCategorySet ::find (String const & name ) 25{ 26if (auto category = m_categoryMap .tryGetValue (name )) 27 { 28return category -> Ptr (); 29 } 30return nullptr ; 31} 32 33TestCategory * TestCategorySet ::findOrError (String const & name ) 34{ 35TestCategory * category = find (name ); 36if (!category ) 37 { 38StdWriters ::getError ()."error: unknown test category name '%s'\n" ,name .getBuffer ()); 39 } 40return category ; 41} 42 43/* We need a way to differentiate a subCommand from say a test prefix. Here 44we assume a command is just alpha characters or -, and this would differentiate it from 45typical prefix usage (which is generally a directory). */ 46static bool _isSubCommand (const char * arg ) 47{ 48for (;* arg ;arg ++ ) 49 { 50const char c = * arg ; 51// A command is just letters 52if (!((c >='a' && c <='z' )|| (c >='A' && c <='Z' )|| c == '-' )) 53 { 54return false; 55 } 56 } 57return true; 58} 59 60/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!! Options !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ 61 62/* static */ void Options ::showHelp (WriterHelper stdOut ) 63{ 64stdOut ."Usage: slang-test [options] [test-prefix...]\n" 66"\n" 67"Options:\n" 68" -h, --help Show this help message\n" 69" -bindir <path> Set directory for binaries (default: the path to the " 70"slang-test executable)\n" 71" -test-dir <path> Set directory for test files (default: tests/)\n" 72" -v [level] Set verbosity level (verbose, info, failure)\n" 73" Default: verbose when -v used, info otherwise\n" 74" -hide-ignored Hide results from ignored tests\n" 75" -api-only Only run tests that use specified APIs\n" 76" -verbose-paths Use verbose paths in output\n" 77" -category <name> Only run tests in specified category\n" 78" -exclude <name> Exclude tests in specified category\n" 79" -exclude-prefix <prefix> Exclude tests with specified path prefix\n" 80" -api <expr> Enable specific APIs (e.g., 'vk+dx12' or '+dx11')\n" 81" -synthesizedTestApi <expr> Set APIs for synthesized tests\n" 82" -skip-api-detection Skip API availability detection\n" 83" -server-count <n> Set number of test servers (default: 1)\n" 84" -show-adapter-info Show detailed adapter information\n" 85" -generate-hlsl-baselines Generate HLSL test baselines\n" 86" -skip-reference-image-generation Skip generating reference images for render tests\n" 87" -emit-spirv-via-glsl Emit SPIR-V through GLSL instead of directly\n" 88" -expected-failure-list <file> Specify file containing expected failures\n" 89" -use-shared-library Run tests in-process using shared library\n" 90" -use-test-server Run tests using test server\n" 91" -use-fully-isolated-test-server Run each test in isolated server\n" 92" -capability <name> Compile with the given capability\n" 93" -shuffle-tests Shuffle tests in directories\n" 94" -shuffle-seed <seed> Set shuffle seed (default: 1)\n" 95 96// Recent Windows runtime versions started opening a dialog popup window when 97// `abort()` is called, which breaks the CI workflow and some scripts that 98// expect a normal termination. 99// It can be helpful for debugging but we should ignore it for CI. 100" -ignore-abort-msg Ignore abort message dialog popup on Windows\n" 101 102" -enable-debug-layers [true|false] Enable or disable Validation Layer for Vulkan\n" 103" and Debug Device for DX\n" 104" -cache-rhi-device [true|false] Enable or disable RHI device caching (default: true)\n" 105#if _DEBUG 106" -disable-debug-layers Disable the debug layers (default enabled in debug " 107"build)\n" 108#endif 109"\n" 110"Output modes:\n" 111" -appveyor Use AppVeyor output format\n" 112" -travis Use Travis CI output format\n" 113" -teamcity Use TeamCity output format\n" 114" -xunit Use xUnit output format\n" 115" -xunit2 Use xUnit 2 output format\n" 116"\n" 117"Test prefixes are used to filter which tests to run. If no prefix is specified,\n" 118"all tests will be run.\n" ); 119} 120 121/* static */ Result Options ::parse ( 122int argc , 123char ** argv , 124TestCategorySet * categorySet , 125Slang ::WriterHelper stdOut , 126Slang ::WriterHelper stdError , 127Options * optionsOut ) 128{ 129// Reset the options 130* optionsOut = Options (); 131 132List < const char *> positionalArgs ; 133 134int argCount = argc ; 135char const * const * argCursor = argv ; 136char const * const * argEnd = argCursor + argCount ; 137 138#if _DEBUG 139// Enabling debug layers by default in debug builds. 140// For DX12 it will use the debug layer, for Vulkan it will enable validation layers. 141// 142// CI/CD will explicitly disable this until we address all of VUID errors. 143// https://github.com/shader-slang/slang/issues/4798 144// 145// When you run the Debug build locally, you may see more errors if not disabled with 146// '-enable-debug-layers false'. 147// 148optionsOut -> enableDebugLayers = true; 149#endif 150 151// first argument is the application name 152if (argCursor != argEnd ) 153 { 154optionsOut -> appName = * argCursor ++ ; 155 } 156 157// Check for help flags first 158for (int i = 1 ;i < argc ;i ++ ) 159 { 160if (strcmp (argv [i ],"-h" )== 0 || strcmp (argv [i ],"--help" )== 0 ) 161 { 162showHelp (stdOut ); 163return SLANG_FAIL ; 164 } 165 } 166 167// now iterate over arguments to collect options 168while (argCursor != argEnd ) 169 { 170char const * arg = * argCursor ++ ; 171 172if (arg [0 ]!= '-' ) 173 { 174// We need to determine if this is a command, the confusion is that 175// previously we can specify a test prefix as just a single positional arg. 176// To rule this out, here it can only be a subCommand if it is just text 177 178if (_isSubCommand (arg )) 179 { 180optionsOut -> subCommand = arg ; 181// Make the first arg the command name 182optionsOut -> subCommandArgs .add (optionsOut -> subCommand ); 183 184// Add all the remaining commands to subCommands 185for (;argCursor != argEnd ;++ argCursor ) 186 { 187optionsOut -> subCommandArgs .add (* argCursor ); 188 } 189// Done 190return SLANG_OK ; 191 } 192 193positionalArgs .add (arg ); 194continue ; 195 } 196 197if (strcmp (arg ,"--" )== 0 ) 198 { 199// Add all positional args at the end 200while (argCursor != argEnd ) 201 { 202positionalArgs .add (* argCursor ++ ); 203 } 204break ; 205 } 206 207if (strcmp (arg ,"-bindir" )== 0 ) 208 { 209if (argCursor == argEnd ) 210 { 211stdError ."error: expected operand for '%s'\n" ,arg ); 212showHelp (stdError ); 213return SLANG_FAIL ; 214 } 215optionsOut -> binDir = * argCursor ++ ; 216 } 217else if (strcmp (arg ,"-use-shared-library" )== 0 ) 218 { 219optionsOut -> defaultSpawnType = SpawnType ::UseSharedLibrary ; 220 } 221else if (strcmp (arg ,"-use-test-server" )== 0 ) 222 { 223optionsOut -> defaultSpawnType = SpawnType ::UseTestServer ; 224 } 225else if (strcmp (arg ,"-use-fully-isolated-test-server" )== 0 ) 226 { 227optionsOut -> defaultSpawnType = SpawnType ::UseFullyIsolatedTestServer ; 228 } 229else if (strcmp (arg ,"-v" )== 0 ) 230 { 231if (argCursor == argEnd ) 232 { 233// Default to verbose if no argument provided (backward compatibility) 234optionsOut -> verbosity = VerbosityLevel ::Verbose ; 235 } 236else 237 { 238const char * verbosityArg = * argCursor ; 239if (strcmp (verbosityArg ,"verbose" )== 0 ) 240 { 241optionsOut -> verbosity = VerbosityLevel ::Verbose ; 242argCursor ++ ; 243 } 244else if (strcmp (verbosityArg ,"info" )== 0 ) 245 { 246optionsOut -> verbosity = VerbosityLevel ::Info ; 247argCursor ++ ; 248 } 249else if (strcmp (verbosityArg ,"failure" )== 0 ) 250 { 251optionsOut -> verbosity = VerbosityLevel ::Failure ; 252argCursor ++ ; 253 } 254else 255 { 256// Not a verbosity level, treat as old-style -v 257optionsOut -> verbosity = VerbosityLevel ::Verbose ; 258 } 259 } 260 } 261else if (strcmp (arg ,"-hide-ignored" )== 0 ) 262 { 263optionsOut -> hideIgnored = true; 264 } 265else if (strcmp (arg ,"-api-only" )== 0 ) 266 { 267optionsOut -> apiOnly = true; 268 } 269else if (strcmp (arg ,"-verbose-paths" )== 0 ) 270 { 271optionsOut -> verbosePaths = true; 272 } 273else if (strcmp (arg ,"-generate-hlsl-baselines" )== 0 ) 274 { 275optionsOut -> generateHLSLBaselines = true; 276 } 277else if (strcmp (arg ,"-shuffle-tests" )== 0 ) 278 { 279optionsOut -> shuffleTests = true; 280 } 281else if (strcmp (arg ,"-shuffle-seed" )== 0 ) 282 { 283if (argCursor == argEnd ) 284 { 285stdError ."error: expected operand for '%s'\n" ,arg ); 286showHelp (stdError ); 287return SLANG_FAIL ; 288 } 289optionsOut -> shuffleSeed = stringToInt (* argCursor ++ ); 290if (optionsOut -> shuffleSeed <=0 ) 291 { 292optionsOut -> shuffleSeed = 1 ; 293 } 294 } 295else if (strcmp (arg ,"-release" )== 0 ) 296 { 297// Assumed to be handle by .bat file that called us 298 } 299else if (strcmp (arg ,"-debug" )== 0 ) 300 { 301// Assumed to be handle by .bat file that called us 302 } 303else if (strcmp (arg ,"-configuration" )== 0 ) 304 { 305if (argCursor == argEnd ) 306 { 307stdError ."error: expected operand for '%s'\n" ,arg ); 308showHelp (stdError ); 309return SLANG_FAIL ; 310 } 311argCursor ++ ; 312// Assumed to be handle by .bat file that called us 313 } 314else if (strcmp (arg ,"-platform" )== 0 ) 315 { 316if (argCursor == argEnd ) 317 { 318stdError ."error: expected operand for '%s'\n" ,arg ); 319showHelp (stdError ); 320return SLANG_FAIL ; 321 } 322argCursor ++ ; 323// Assumed to be handle by .bat file that called us 324 } 325else if (strcmp (arg ,"-server-count" )== 0 ) 326 { 327if (argCursor == argEnd ) 328 { 329stdError ."error: expected operand for '%s'\n" ,arg ); 330showHelp (stdError ); 331return SLANG_FAIL ; 332 } 333optionsOut -> serverCount = stringToInt (* argCursor ++ ); 334if (optionsOut -> serverCount <=0 ) 335 { 336optionsOut -> serverCount = 1 ; 337 } 338 } 339else if (strcmp (arg ,"-appveyor" )== 0 ) 340 { 341optionsOut -> outputMode = TestOutputMode ::AppVeyor ; 342optionsOut -> dumpOutputOnFailure = true; 343 } 344else if (strcmp (arg ,"-travis" )== 0 ) 345 { 346optionsOut -> outputMode = TestOutputMode ::Travis ; 347optionsOut -> dumpOutputOnFailure = true; 348 } 349else if (strcmp (arg ,"-xunit" )== 0 ) 350 { 351optionsOut -> outputMode = TestOutputMode ::XUnit ; 352 } 353else if (strcmp (arg ,"-xunit2" )== 0 ) 354 { 355optionsOut -> outputMode = TestOutputMode ::XUnit2 ; 356 } 357else if (strcmp (arg ,"-teamcity" )== 0 ) 358 { 359optionsOut -> outputMode = TestOutputMode ::TeamCity ; 360 } 361else if (strcmp (arg ,"-category" )== 0 ) 362 { 363if (argCursor == argEnd ) 364 { 365stdError ."error: expected operand for '%s'\n" ,arg ); 366showHelp (stdError ); 367return SLANG_FAIL ; 368 } 369auto category = categorySet -> findOrError (* argCursor ++ ); 370if (category ) 371 { 372optionsOut -> includeCategories .add (category ,category ); 373 } 374 } 375else if (strcmp (arg ,"-exclude" )== 0 ) 376 { 377if (argCursor == argEnd ) 378 { 379stdError ."error: expected operand for '%s'\n" ,arg ); 380showHelp (stdError ); 381return SLANG_FAIL ; 382 } 383auto category = categorySet -> findOrError (* argCursor ++ ); 384if (category ) 385 { 386optionsOut -> excludeCategories .add (category ,category ); 387 } 388 } 389else if (strcmp (arg ,"-exclude-prefix" )== 0 ) 390 { 391if (argCursor == argEnd ) 392 { 393stdError ."error: expected operand for '%s'\n" ,arg ); 394showHelp (stdError ); 395return SLANG_FAIL ; 396 } 397Slang ::StringBuilder sb ; 398Slang ::Path ::simplify (* argCursor ++ ,Slang ::Path ::SimplifyStyle ::NoRoot ,sb ); 399optionsOut -> excludePrefixes .add (sb ); 400 } 401else if (strcmp (arg ,"-api" )== 0 ) 402 { 403if (argCursor == argEnd ) 404 { 405stdError ."error: expecting an api expression (eg 'vk+dx12' or '+dx11') '%s'\n" , 407arg ); 408showHelp (stdError ); 409return SLANG_FAIL ; 410 } 411const char * apiList = * argCursor ++ ; 412 413SlangResult res = RenderApiUtil ::parseApiFlags ( 414UnownedStringSlice (apiList ), 415optionsOut -> enabledApis , 416& optionsOut -> enabledApis ); 417if (SLANG_FAILED (res )) 418 { 419stdError ."error: unable to parse api expression '%s'\n" ,apiList ); 420return res ; 421 } 422 } 423else if (strcmp (arg ,"-synthesizedTestApi" )== 0 ) 424 { 425if (argCursor == argEnd ) 426 { 427stdError ."error: expected an api expression (eg 'vk+dx12' or '+dx11') '%s'\n" , 429arg ); 430showHelp (stdError ); 431return SLANG_FAIL ; 432 } 433const char * apiList = * argCursor ++ ; 434 435SlangResult res = RenderApiUtil ::parseApiFlags ( 436UnownedStringSlice (apiList ), 437optionsOut -> synthesizedTestApis , 438& optionsOut -> synthesizedTestApis ); 439if (SLANG_FAILED (res )) 440 { 441stdError ."error: unable to parse api expression '%s'\n" ,apiList ); 442return res ; 443 } 444 } 445else if (strcmp (arg ,"-skip-api-detection" )== 0 ) 446 { 447optionsOut -> skipApiDetection = true; 448 } 449else if (strcmp (arg ,"-emit-spirv-via-glsl" )== 0 ) 450 { 451optionsOut -> emitSPIRVDirectly = false; 452 } 453else if (strcmp (arg ,"-capability" )== 0 ) 454 { 455if (argCursor == argEnd ) 456 { 457stdError ."error: expected operand for '%s'\n" ,arg ); 458showHelp (stdError ); 459return SLANG_FAIL ; 460 } 461optionsOut -> capabilities .add (* argCursor ++ ); 462 } 463else if (strcmp (arg ,"-ignore-abort-msg" )== 0 ) 464 { 465optionsOut -> ignoreAbortMsg = true; 466#ifdef _MSC_VER 467_set_abort_behavior (0 ,_WRITE_ABORT_MSG ); 468#endif 469 } 470else if (strcmp (arg ,"-expected-failure-list" )== 0 ) 471 { 472if (argCursor == argEnd ) 473 { 474stdError ."error: expected operand for '%s'\n" ,arg ); 475showHelp (stdError ); 476return SLANG_FAIL ; 477 } 478auto fileName = * argCursor ++ ; 479String text ; 480File ::readAllText (fileName ,text ); 481List < UnownedStringSlice > lines ; 482StringUtil ::split (text .getUnownedSlice (),'\n' ,lines ); 483for (auto line :lines ) 484 { 485// Remove comments (everything after '#' character) 486auto trimmedLine = line ; 487auto commentIndex = line .indexOf ('#' ); 488if (commentIndex != -1 ) 489 { 490trimmedLine = line .head (commentIndex ); 491 } 492 493// Trim whitespace and skip empty lines 494trimmedLine = trimmedLine .trim (); 495if (trimmedLine .getLength ()> 0 ) 496 { 497optionsOut -> expectedFailureList .add (trimmedLine ); 498 } 499 } 500 } 501else if (strcmp (arg ,"-test-dir" )== 0 ) 502 { 503if (argCursor == argEnd ) 504 { 505stdError ."error: expected operand for '%s'\n" ,arg ); 506showHelp (stdError ); 507return SLANG_FAIL ; 508 } 509optionsOut -> testDir = * argCursor ++ ; 510 } 511else if (strcmp (arg ,"-show-adapter-info" )== 0 ) 512 { 513optionsOut -> showAdapterInfo = true; 514 } 515else if (strcmp (arg ,"-skip-reference-image-generation" )== 0 ) 516 { 517optionsOut -> skipReferenceImageGeneration = true; 518 } 519else if (strcmp (arg ,"-enable-debug-layers" )== 0 ) 520 { 521optionsOut -> enableDebugLayers = true; 522 523if (argCursor == argEnd ) 524 { 525stdError ."error: expected operand for '%s'\n" ,arg ); 526showHelp (stdError ); 527return SLANG_FAIL ; 528 } 529 530// Check for false variants 531const char * value = * argCursor ++ ; 532if (value [0 ]== 'f' || value [0 ]== 'F' || value [0 ]== 'n' || value [0 ]== 'N' || 533value [0 ]== '0' || 534 ((value [0 ]== 'o' || value [0 ]== 'O' )&& (value [1 ]== 'f' || value [1 ]== 'F' ))) 535 { 536optionsOut -> enableDebugLayers = false; 537 } 538 } 539else if (strcmp (arg ,"-cache-rhi-device" )== 0 ) 540 { 541optionsOut -> cacheRhiDevice = true; 542 543if (argCursor == argEnd ) 544 { 545stdError ."error: expected operand for '%s'\n" ,arg ); 546showHelp (stdError ); 547return SLANG_FAIL ; 548 } 549 550// Check for false variants 551const char * value = * argCursor ++ ; 552if (value [0 ]== 'f' || value [0 ]== 'F' || value [0 ]== 'n' || value [0 ]== 'N' || 553value [0 ]== '0' || 554 ((value [0 ]== 'o' || value [0 ]== 'O' )&& (value [1 ]== 'f' || value [1 ]== 'F' ))) 555 { 556optionsOut -> cacheRhiDevice = false; 557 } 558 } 559#if _DEBUG 560else if (strcmp (arg ,"-disable-debug-layers" )== 0 ) 561 { 562stdError ."warning: '-disable-debug-layers' is deprecated, use " 563"'-enable-debug-layers false'\n" ); 564optionsOut -> enableDebugLayers = false; 565 } 566#endif 567else 568 { 569stdError ."unknown option '%s'\n" ,arg ); 570showHelp (stdError ); 571return SLANG_FAIL ; 572 } 573 } 574 575 { 576// Find out what apis are available 577const int availableApis = RenderApiUtil ::getAvailableApis (); 578// Only allow apis we know are available 579optionsOut -> enabledApis &=availableApis ; 580 581// Can only synth for apis that are available 582optionsOut -> synthesizedTestApis &=optionsOut -> enabledApis ; 583 } 584 585 586// first positional argument is source shader path 587optionsOut -> testPrefixes .clear (); 588optionsOut -> testPrefixes .reserve (positionalArgs .getCount ()); 589for (auto testPrefix :positionalArgs ) 590 { 591Slang ::StringBuilder sb ; 592Slang ::Path ::simplify (testPrefix ,Slang ::Path ::SimplifyStyle ::NoRoot ,sb ); 593optionsOut -> testPrefixes .add (sb ); 594 } 595 596if (optionsOut -> binDir .getLength ()== 0 ) 597 { 598// If the binDir isn't set try using the path to the executable 599String exePath = Path ::getExecutablePath (); 600if (exePath .getLength ()) 601 { 602optionsOut -> binDir = Path ::getParentDirectory (exePath ); 603 } 604 } 605 606if (optionsOut -> testDir .getLength ()== 0 ) 607 { 608// If the test directory isn't set, use the "tests" directory 609optionsOut -> testDir = String ("tests/" ); 610 } 611 612return SLANG_OK ; 613}