1#!/usr/bin/env python3 2# 3# Copyright 2021 Google LLC 4# 5# Use of this source code is governed by a BSD-style license that can be 6# found in the LICENSE file. 7# 8# GLSL ES2 conformance test files can be found at 9# https://github.com/KhronosGroup/VK-GL-CTS/tree/master/data/gles2/shaders 10# 11# Usage: 12# cat ${TEST_FILES}/*.test | ./import_conformance_tests.py 13# 14# This will generate two directories, "pass" and "fail", containing finished runtime shaders. 15# Note that some tests were originally designed to fail, because a conforming compiler should not 16# allow the program. A handful of others fail because they are incompatible with SkSL. This script 17# identifies SkSL-incompatible tests them and moves them from "pass" to "fail" automatically. 18# 19# Not all ES2 test files are meaningful in SkSL. These input files are not supported: 20# - linkage.test: Runtime Effects only handle fragment processing 21# - invalid_texture_functions.test: no GL texture functions in Runtime Effects 22# - preprocessor.test: no preprocessor in SkSL 23 24import os 25import pyparsing as pp 26import re 27import sys 28 29# Each case can contain expected input/output values, sometimes in [bracketed|lists] and 30# sometimes not. 31# GLSL code appears in ""double-double quotes"" and is indicated as vert/frag-specific or "both". 32# Some tests denote that they are expected to pass/fail. (Presumably, "pass" is the default.) 33# We ignore descriptions and version fields. 34wordWithUnderscores = pp.Word(pp.alphanums + '_') 35 36pipeList = pp.delimited_list(pp.SkipTo(pp.Literal("|") | pp.Literal("]")), delim="|") 37bracketedPipeList = pp.Group(pp.Literal("[").suppress() + 38 pipeList + 39 pp.Literal("]").suppress()) 40unbracketedValue = pp.Group(pp.SkipTo(";")) 41 42valueList = (pp.Word(pp.alphanums) + # type 43 pp.Word(pp.alphanums) + # varname 44 pp.Literal("=").suppress() + 45 (bracketedPipeList | unbracketedValue) + 46 pp.Literal(";").suppress()) 47value = pp.Group((pp.Keyword("input") | pp.Keyword("output") | pp.Keyword("uniform")) + 48 valueList) 49values = (pp.Keyword("values") + 50 pp.Literal("{").suppress() + 51 pp.ZeroOrMore(value) + 52 pp.Literal("}").suppress()) 53 54expectation = (pp.Keyword("expect").suppress() + (pp.Keyword("compile_fail") | 55 pp.Keyword("pass"))) 56 57code = ((pp.Keyword("both") + pp.QuotedString('""', multiline=True)) | 58 (pp.Keyword("vertex") + pp.QuotedString('""', multiline=True) + 59 pp.Keyword("fragment") + pp.QuotedString('""', multiline=True))) 60 61reqGlsl100 = pp.Keyword("require").suppress() + pp.Keyword("full_glsl_es_100_support") 62 63desc = pp.Keyword("desc") + pp.QuotedString('"') 64version100es = pp.Keyword("version") + pp.Keyword("100") + pp.Keyword("es") 65ignoredCaseItem = (desc | version100es).suppress() 66 67caseItem = pp.Group(values | expectation | code | reqGlsl100) | ignoredCaseItem 68 69caseBody = pp.ZeroOrMore(caseItem) 70 71blockEnd = pp.Keyword("end").suppress(); 72 73caseHeader = pp.Keyword("case") + wordWithUnderscores 74case = pp.Group(caseHeader + caseBody + blockEnd) 75 76# Groups can be nested to any depth (or can be absent), and may contain any number of cases. 77# The names in the group header are ignored. 78groupHeader = (pp.Keyword("group") + wordWithUnderscores + pp.QuotedString('"')).suppress() 79 80group = pp.Forward() 81group <<= pp.OneOrMore(case | (groupHeader + group + blockEnd)) 82 83# The full grammar is just the group specification, plus the fact that # indicates a comment. 84grammar = group 85group.ignore('#' + pp.restOfLine) 86 87testCases = grammar.parse_string(sys.stdin.read(), parse_all=True) 88 89# Write output files in subdirectories next to this script. 90testDirectory = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) 91passDirectory = testDirectory + "/pass" 92failDirectory = testDirectory + "/fail" 93os.makedirs(passDirectory, exist_ok=True) 94os.makedirs(failDirectory, exist_ok=True) 95written = {} 96 97for c in testCases: 98 # Parse the case header 99 assert c[0] == 'case' 100 c.pop(0) 101 102 testName = c[0] 103 assert isinstance(testName, str) 104 c.pop(0) 105 106 # Parse the case body 107 skipTest = '' 108 expectPass = True 109 allowMismatch = False 110 testCode = '' 111 inputs = [] 112 outputs = [] 113 114 for b in c: 115 caseItem = b[0] 116 b.pop(0) 117 118 if caseItem == 'compile_fail': 119 expectPass = False 120 121 elif caseItem == 'pass': 122 expectPass = True 123 124 elif caseItem == 'vertex' or caseItem == 'fragment': 125 skipTest = 'Uses vertex' 126 127 elif caseItem == 'both': 128 testCode = b[0] 129 assert isinstance(testCode, str) 130 131 elif caseItem == 'values': 132 for v in b: 133 valueType = v[0] 134 v.pop(0) 135 136 if valueType == 'uniform': 137 skipTest = 'Uses uniform' 138 elif valueType == 'input': 139 inputs.append(v.asList()) 140 elif valueType == 'output': 141 outputs.append(v.asList()) 142 143 elif caseItem == 'full_glsl_es_100_support': 144 skipTest = 'Uses while loop' 145 146 else: 147 assert 0 148 149 if skipTest == '': 150 if "void main" not in testCode: 151 skipTest = 'Missing main' 152 153 if skipTest != '': 154 print("skipped %s (%s)" % (testName, skipTest)) 155 continue 156 157 # The test is safe to run, but it might not get the same result. 158 # SkSL does not guarantee that function arguments will always be evaluated left-to-right. 159 if re.fullmatch('argument_eval_order_[12]', testName): 160 allowMismatch = True 161 print("allowing mismatch in %s" % testName) 162 163 # The ES2 conformance tests allow floating point comparisons to be off by 0.05: 164 # https://osscs.corp.google.com/android/platform/superproject/+/master:external/deqp/external/openglcts/modules/common/glcShaderLibraryCase.cpp;l=714;drc=84322c9402f810da3cd80b52e9f9ef72150a9004 165 # A few tests require this slop factor to pass, regardless of GPU precision 166 # (e.g. the test is written to expect 2.19, but actually computes 2.194285) 167 compare = lambda type, a, b : '((' + a + ') == (' + b + '))' 168 if (testName == 'math_float' or 169 testName == 'struct' or 170 testName == 'nested_struct' or 171 testName == 'nested_builtin_funcs'): 172 compare = lambda type, a, b : ( 173 '(floor(20 * abs((' + a + ') - (' + b + '))) == ' + type + '(0))' 174 ) 175 176 # Switch tests to a "fail" expectation instead of "pass" when SkSL and GLSL disagree. 177 # SkSL does not support casts which discard elements such as `float(myFloat4)`. 178 if (re.fullmatch('(vec|bvec|ivec)[234]_to_(float|int|bool)', testName) or 179 re.fullmatch('(vec|bvec|ivec)[34]_to_(vec|bvec|ivec)2', testName) or 180 re.fullmatch('(vec|bvec|ivec)[4]_to_(vec|bvec|ivec)3', testName) or 181 # SkSL requires that function out-parameters match the precision of the variable passed in. 182 re.fullmatch('(out|inout)_lowp_(int|float)', testName) or 183 # SkSL rejects code that fails to return a value; GLSL ES2 allows it. 184 testName == 'missing_returns' or 185 # SkSL does not support a global `precision` directive. 186 testName == 'default_vs_explicit_precision' or 187 # SkSL does not allow variables to be created without an enclosing scope. 188 testName == 'variable_in_if_hides_global_variable'): 189 assert expectPass 190 expectPass = False 191 print("moved %s to fail" % testName) 192 193 # Apply fixups to the test code. 194 # SkSL doesn't support the `precision` keyword, so comment it out if it appears. 195 testCode = testCode.replace("precision highp ", "// precision highp "); 196 testCode = testCode.replace("precision mediump ", "// precision mediump "); 197 testCode = testCode.replace("precision lowp ", "// precision lowp "); 198 199 # SkSL doesn't support the `#version` declaration. 200 testCode = testCode.replace("#version", "// #version"); 201 202 # Rename the `main` function to `execute_test`. 203 testCode = testCode.replace("void main", "bool execute_test"); 204 205 # Replace ${POSITION_FRAG_COLOR} with a scratch variable. 206 if "${POSITION_FRAG_COLOR}" in testCode: 207 testCode = testCode.replace("${POSITION_FRAG_COLOR}", "PositionFragColor"); 208 if "${DECLARATIONS}" in testCode: 209 testCode = testCode.replace("${DECLARATIONS}", 210 "vec4 PositionFragColor;\n${DECLARATIONS}"); 211 else: 212 testCode = "vec4 PositionFragColor;\n" + testCode 213 214 # Create a runnable SkSL test by returning green or red based on the test result. 215 testCode += "\n" 216 testCode += "half4 main(float2 coords) {\n" 217 testCode += " return execute_test() ? half4(0,1,0,1) : half4(1,0,0,1);\n" 218 testCode += "}\n" 219 220 testDirectory = passDirectory 221 if not expectPass: 222 testDirectory = failDirectory 223 224 # Find the total number of input/output fields. 225 numVariables = 0 226 for v in inputs + outputs: 227 numVariables = max(numVariables, len(v[2])) 228 229 if numVariables > 0: 230 assert "${DECLARATIONS}" in testCode 231 assert "${OUTPUT}" in testCode 232 for varIndex in range(0, numVariables): 233 testSpecialization = testCode 234 235 # Assemble input variable declarations for ${DECLARATIONS}. 236 declarations = "" 237 for v in inputs: 238 if len(v[2]) > varIndex: 239 declarations += "%s %s = %s;\n" % (v[0], v[1], v[2][varIndex]); 240 241 # Assemble output variable declarations for ${DECLARATIONS}. 242 for v in outputs: 243 declarations += "%s %s;\n" % (v[0], v[1]); 244 245 # Verify output values inside ${OUTPUT}. 246 outputChecks = "return true" 247 if not allowMismatch: 248 for v in outputs: 249 if len(v[2]) > varIndex: 250 outputChecks += " && " + compare(v[0], v[1], v[2][varIndex]) 251 252 outputChecks += ";\n" 253 254 # Apply fixups to the test code. 255 testSpecialization = testSpecialization.replace("${DECLARATIONS}", declarations) 256 testSpecialization = testSpecialization.replace("${SETUP}", '') 257 testSpecialization = testSpecialization.replace("${OUTPUT}", outputChecks) 258 259 # Generate an SkSL test file. 260 path = "%s/%s_%d.rts" % (testDirectory, testName, varIndex) 261 assert path not in written 262 written[path] = True 263 f = open(path, "w") 264 f.write(testSpecialization) 265 266 else: # not (numVariables > 0) 267 testCode = testCode.replace("${DECLARATIONS}", '') 268 testCode = testCode.replace("${SETUP}", '') 269 testCode = testCode.replace("${OUTPUT}", 'return true;') 270 271 # Generate an SkSL test file. 272 path = "%s/%s.rts" % (testDirectory, testName) 273 assert path not in written 274 written[path] = True 275 f = open(path, "w") 276 f.write(testCode) 277