# coding=utf-8 # # Copyright © 2011 Intel Corporation # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice (including the next # paragraph) shall be included in all copies or substantial portions of the # Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. # Generate a pair of glsl parser tests for every overloaded version of # every built-in function, which test that the built-in functions are # handled properly when applied to constant arguments inside an array # size declaration. # # In each pair of generated tests, one test exercises the built-in # function in vertex shaders, and the other exercises it in fragment # shaders. # # This program outputs, to stdout, the name of each file it generates. # With the optional argument --names-only, it only outputs the names # of the files; it doesn't generate them. from __future__ import print_function, division, absolute_import from builtin_function import * import abc import optparse import os import os.path from six.moves import range from modules import utils class ParserTest(object): """Class used to build a test of a single built-in. This is an abstract base class--derived types should override test_suffix(), output_var(), and other functions if necessary. """ def __init__(self, signature, test_vectors): """Prepare to build a test for a single built-in. signature is the signature of the built-in (a key from the builtin_function.test_suite dict), and test_vectors is the list of test vectors for testing the given builtin (the corresponding value from the builtin_function.test_suite dict). """ self.__signature = signature self.__test_vectors = test_vectors def glsl_version(self): if self.__signature.version_introduced < 120: # Before version 1.20, built-in function invocations # weren't allowed in constant expressions. So even if # this built-in was introduced prior to 1.20, test it in # version 1.20. return 120 else: return self.__signature.version_introduced def version_directive(self): return '#version {0}\n'.format(self.glsl_version()) def additional_declarations(self): """Return a string containing any additional declarations that should be placed after the version directive. Returns the empty string by default. """ return '' def additional_extensions(self): """Return a list (or other iterable) containing any additional extension requirements that the test has. Returns the empty list by default. """ ret = [] if self.__signature.extension: ret.append(self.__signature.extension) return ret @abc.abstractmethod def test_suffix(self): """Return the suffix that should be used in the test file name to identify the type of shader, e.g. "vert" for a vertex shader test. """ def make_condition(self, test_vector): """Generate a GLSL constant expression that should evaluate to true if the GLSL compiler's constant evaluation produces the correct result for the given test vector, and false if not. """ invocation = self.__signature.template.format( *[glsl_constant(x) for x in test_vector.arguments]) if self.__signature.rettype.base_type == glsl_float: # Test floating-point values within tolerance if self.__signature.name == 'distance': # Don't use the distance() function to test itself. return '{0} <= {1} && {1} <= {2}'.format( test_vector.result - test_vector.tolerance, invocation, test_vector.result + test_vector.tolerance) elif self.__signature.rettype.is_matrix: # We can't apply distance() to matrices. So apply it # to each column and root-sum-square the results. It # is safe to use pow() here because its behavior is # verified in the pow() tests. terms = [] for col in range(self.__signature.rettype.num_cols): terms.append('pow(distance({0}[{1}], {2}), 2)'.format( invocation, col, glsl_constant(test_vector.result[:, col]))) rss_distance = ' + '.join(terms) sq_tolerance = test_vector.tolerance * test_vector.tolerance return '{0} <= {1}'.format( rss_distance, glsl_constant(sq_tolerance)) else: return 'distance({0}, {1}) <= {2}'.format( invocation, glsl_constant(test_vector.result), glsl_constant(test_vector.tolerance)) else: # Test non-floating point values exactly assert not self.__signature.rettype.is_matrix if self.__signature.name == 'equal': # Don't use the equal() function to test itself. assert self.__signature.rettype.is_vector terms = [] for row in range(self.__signature.rettype.num_rows): terms.append('{0}[{1}] == {2}'.format( invocation, row, glsl_constant(test_vector.result[row]))) return ' && '.join(terms) elif self.__signature.rettype.is_vector: return 'all(equal({0}, {1}))'.format( invocation, glsl_constant(test_vector.result)) else: return '{0} == {1}'.format( invocation, glsl_constant(test_vector.result)) def make_shader(self): """Generate the shader code necessary to test the built-in.""" shader = self.version_directive() if self.__signature.extension: shader += '#extension GL_{0} : require\n'.format(self.__signature.extension) shader += self.additional_declarations() shader += '\n' shader += 'void main()\n' shader += '{\n' lengths = [] for i, test_vector in enumerate(self.__test_vectors): shader += ' float[{0} ? 1 : -1] array{1};\n'.format( self.make_condition(test_vector), i) lengths.append('array{0}.length()'.format(i)) shader += ' {0} = vec4({1});\n'.format( self.output_var(), ' + '.join(lengths)) shader += '}\n' return shader def filename(self): argtype_names = '-'.join( str(argtype) for argtype in self.__signature.argtypes) if self.__signature.extension: subdir = self.__signature.extension.lower() else: subdir = 'glsl-{0:1.2f}'.format(float(self.glsl_version()) / 100) return os.path.join( 'spec', subdir, 'compiler', 'built-in-functions', '{0}-{1}.{2}'.format( self.__signature.name, argtype_names, self.test_suffix())) def generate_parser_test(self): """Generate the test and write it to the output file.""" parser_test = '/* [config]\n' parser_test += ' * expect_result: pass\n' parser_test += ' * glsl_version: {0:1.2f}\n'.format( float(self.glsl_version()) / 100) req_extensions = list(self.additional_extensions()) if req_extensions: parser_test += ' * require_extensions: {}\n'.format( ' '.join('GL_{}'.format(r) for r in req_extensions)) parser_test += ' * [end config]\n' parser_test += ' *\n' parser_test += ' * Check that the following test vectors are constant'\ ' folded correctly:\n' for test_vector in self.__test_vectors: parser_test += ' * {0} => {1}\n'.format( self.__signature.template.format( *[glsl_constant(arg) for arg in test_vector.arguments]), glsl_constant(test_vector.result)) parser_test += ' */\n' parser_test += self.make_shader() filename = self.filename() dirname = os.path.dirname(filename) utils.safe_makedirs(dirname) with open(filename, 'w') as f: f.write(parser_test) class VertexParserTest(ParserTest): """Derived class for tests that exercise the built-in in a vertex shader. """ def test_suffix(self): return 'vert' def output_var(self): return 'gl_Position' class GeometryParserTest(ParserTest): """Derived class for tests that exercise the built-in in a geometry shader. """ def glsl_version(self): return max(150, ParserTest.glsl_version(self)) def test_suffix(self): return 'geom' def output_var(self): return 'gl_Position' class FragmentParserTest(ParserTest): """Derived class for tests that exercise the built-in in a fagment shader. """ def test_suffix(self): return 'frag' def output_var(self): return 'gl_FragColor' def all_tests(): for signature, test_vectors in sorted(test_suite.items()): # Assignment operators other than = cannot be used in the constant # array size tests if not signature.name.startswith('op-assign'): yield VertexParserTest(signature, test_vectors) yield GeometryParserTest(signature, test_vectors) yield FragmentParserTest(signature, test_vectors) def main(): desc = 'Generate shader tests that test built-in functions using constant'\ 'array sizes' usage = 'usage: %prog [-h] [--names-only]' parser = optparse.OptionParser(description=desc, usage=usage) parser.add_option('--names-only', dest='names_only', action='store_true', help="Don't output files, just generate a list of" "filenames to stdout") options, args = parser.parse_args() for test in all_tests(): if not options.names_only: test.generate_parser_test() print(test.filename()) if __name__ == '__main__': main()