• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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