Flutter Engine
The Flutter Engine
import_conformance_tests.py
Go to the documentation of this file.
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)
static float max(float r, float g, float b)
Definition: hsl.cpp:49
def print(*args, **kwargs)
Definition: run_tests.py:49