[Piglit] [PATCH 4/4] Add constant array size builtin tests.

Paul Berry stereotype441 at gmail.com
Mon Aug 8 14:15:07 PDT 2011


This patch adds a new set of generated tests which parallels those
introduced by commt cccc419e (Add comprehensive tests of builtin
functions with uniform input.)

The new tests validate the correctness of built-in functions when used
to compute array sizes.  The tests work by creating arrays whose size
is 1 if the result of applying the built-in function is within
tolerance of the correct answer, and -1 if not.  Since negative array
sizes are prohibited, any improperly computed values will generate a
compile error.

Though it may seem absurd to compute an array size based on the result
of calling a built-in function, it's worth testing for two reasons:

(1) It is explicitly allowed in GLSL versions since 1.20.

(2) Since array sizes need to be computed at compile time, this is a
convenient way of testing that the GLSL compiler properly implements
constant folding of built-in functions.  Indeed, the original
motivation for these tests was to validate bug fixes to Mesa's
constant folding logic.

Here is an example of one of the generated tests (this test is
exp2-vec2.vert):

/* [config]
 * expect_result: pass
 * glsl_version: 1.20
 * [end config]
 *
 * Check that the following test vectors are constant folded correctly:
 * exp2(vec2(-2.0, -0.66666669)) => vec2(0.25, 0.62996054)
 * exp2(vec2(0.66666669, 2.0)) => vec2(1.587401, 4.0)
 */

void main()
{
  float[distance(exp2(vec2(-2.0, -0.66666669)), vec2(0.25, 0.62996054)) <= 6.7775386e-06 ? 1 : -1] array0;
  float[distance(exp2(vec2(0.66666669, 2.0)), vec2(1.587401, 4.0)) <= 4.3034684e-05 ? 1 : -1] array1;
  gl_Position = vec4(array0.length() + array1.length());
}
---
 generated_tests/CMakeLists.txt                   |    7 +-
 generated_tests/gen_constant_array_size_tests.py |  231 ++++++++++++++++++++++
 tests/all.tests                                  |    3 +
 3 files changed, 240 insertions(+), 1 deletions(-)
 create mode 100644 generated_tests/gen_constant_array_size_tests.py

diff --git a/generated_tests/CMakeLists.txt b/generated_tests/CMakeLists.txt
index 6772813..ac3cb95 100644
--- a/generated_tests/CMakeLists.txt
+++ b/generated_tests/CMakeLists.txt
@@ -41,8 +41,13 @@ piglit_make_generated_tests(
 	gen-builtin-uniform-tests
 	gen_builtin_uniform_tests.py
 	builtin_function.py)
+piglit_make_generated_tests(
+	gen-constant-array-size-tests
+	gen_constant_array_size_tests.py
+	builtin_function.py)
 
 # Add a "gen-tests" target that can be used to generate all the
 # tests without doing any other compilation.
 add_custom_target(gen-tests ALL
-	DEPENDS gen-builtin-uniform-tests)
+	DEPENDS gen-builtin-uniform-tests
+		gen-constant-array-size-tests)
diff --git a/generated_tests/gen_constant_array_size_tests.py b/generated_tests/gen_constant_array_size_tests.py
new file mode 100644
index 0000000..8258866
--- /dev/null
+++ b/generated_tests/gen_constant_array_size_tests.py
@@ -0,0 +1,231 @@
+# 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 builtin_function import *
+import abc
+import optparse
+import os
+import os.path
+
+
+
+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()
+    and output_var().
+    """
+
+    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 < '1.20':
+	    # 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 '1.20'
+	else:
+	    return self.__signature.version_introduced
+
+    def version_directive(self):
+	return '#version {0}\n'.format(self.glsl_version().replace('.', ''))
+
+    @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.
+	"""
+	funcall = '{0}({1})'.format(
+	    self.__signature.name, ', '.join(
+		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,
+		    funcall,
+		    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 xrange(self.__signature.rettype.num_cols):
+		    terms.append('pow(distance({0}[{1}], {2}), 2)'.format(
+			    funcall, 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(
+		    funcall, 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 xrange(self.__signature.rettype.num_rows):
+		    terms.append('{0}[{1}] == {2}'.format(
+			    funcall, row,
+			    glsl_constant(test_vector.result[row])))
+		return ' && '.join(terms)
+	    elif self.__signature.rettype.is_vector:
+		return 'all(equal({0}, {1}))'.format(
+		    funcall, glsl_constant(test_vector.result))
+	    else:
+		return '{0} == {1}'.format(
+		    funcall, glsl_constant(test_vector.result))
+
+    def make_shader(self):
+	"""Generate the shader code necessary to test the built-in."""
+	shader = self.version_directive()
+	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)
+	return os.path.join(
+	    'spec', 'glsl-{0}'.format(self.glsl_version()),
+	    '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}\n'.format(self.glsl_version())
+	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}) => {2}\n'.format(
+		self.__signature.name,
+		', '.join(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)
+	if not os.path.exists(dirname):
+	    os.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 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 test_suite.items():
+	yield VertexParserTest(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()
diff --git a/tests/all.tests b/tests/all.tests
index 62deb87..3c2b237 100644
--- a/tests/all.tests
+++ b/tests/all.tests
@@ -745,6 +745,9 @@ spec['glsl-1.20'] = Group()
 import_glsl_parser_tests(spec['glsl-1.20'],
 			 os.path.join(os.path.dirname(__file__), 'spec', 'glsl-1.20'),
 			 ['preprocessor', 'compiler'])
+import_glsl_parser_tests(spec['glsl-1.20'],
+			 os.path.join(generatedTestDir, 'spec', 'glsl-1.20'),
+			 ['compiler'])
 spec['glsl-1.20']['execution'] = Group()
 add_shader_test_dir(spec['glsl-1.20']['execution'],
 	            os.path.join(os.path.dirname(__file__), 'spec', 'glsl-1.20', 'execution'),
-- 
1.7.6



More information about the Piglit mailing list