3 # Copyright © 2011 Intel Corporation
5 # Permission is hereby granted, free of charge, to any person obtaining a
6 # copy of this software and associated documentation files (the "Software"),
7 # to deal in the Software without restriction, including without limitation
8 # the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 # and/or sell copies of the Software, and to permit persons to whom the
10 # Software is furnished to do so, subject to the following conditions:
12 # The above copyright notice and this permission notice (including the next
13 # paragraph) shall be included in all copies or substantial portions of the
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 # DEALINGS IN THE SOFTWARE.
24 # Generate a set of shader_runner tests for every overloaded version
25 # of every built-in function, based on the test vectors computed by
26 # builtin_function.py.
28 # In each set of generated tests, one test exercises the built-in
29 # function in each type of shader (vertex, geometry, and fragment).
30 # In all cases, the inputs to the built-in function come from
31 # uniforms, so that the effectiveness of the test won't be
32 # circumvented by constant folding in the GLSL compiler.
34 # The tests operate by invoking the built-in function in the
35 # appropriate shader, applying a scale and offset so that the expected
36 # values are in the range [0.25, 0.75], and then outputting the result
37 # as a solid rgba color, which is then checked using shader_runner's
38 # "probe rgba" command.
40 # For built-in functions whose result type is a matrix, the test
41 # checks one column at a time.
43 # This program outputs, to stdout, the name of each file it generates.
44 # With the optional argument --names-only, it only outputs the names
45 # of the files; it doesn't generate them.
47 from builtin_function_fp64
import *
54 from modules
import utils
57 def compute_offset_and_scale(test_vectors
):
58 """Compute scale and offset values such that for each result in
59 test_vectors, (result - offset) * scale is in the range [0.25,
60 0.75], and scale is less than or equal to 1.0. These values are
61 used to transform the test vectors so that their outputs can be
62 stored in gl_FragColor without overflow.
64 low
= min(numpy
.min(tv
.result
) for tv
in test_vectors
)
65 hi
= max(numpy
.max(tv
.result
) for tv
in test_vectors
)
67 center
= (hi
+ low
)/2.0
71 offset
= center
- span
/2.0
76 def shader_runner_format(values
):
77 """Format the given values for use in a shader_runner "uniform" or
78 "probe rgba" command. Bools are converted to 0's and 1's, and
79 values are separated by spaces.
81 transformed_values
= []
83 if isinstance(value
, (bool, np
.bool_
)):
84 transformed_values
.append(int(value
))
86 transformed_values
.append(value
)
87 return ' '.join(repr(x
) for x
in transformed_values
)
90 def shader_runner_type(glsl_type
):
91 """Return the appropriate type name necessary for binding a
92 uniform of the given type using shader_runner's "uniform" command.
93 Boolean values and vectors are converted to ints, and square
94 matrices are written in "matNxN" form.
96 if glsl_type
.base_type
== glsl_bool
:
97 if glsl_type
.is_scalar
:
100 return 'ivec{0}'.format(glsl_type
.num_rows
)
101 if glsl_type
.is_matrix
:
102 return 'dmat{0}x{1}'.format(glsl_type
.num_cols
, glsl_type
.num_rows
)
104 return str(glsl_type
)
107 class Comparator(object):
108 """Base class which abstracts how we compare expected and actual
111 __metaclass__
= abc
.ABCMeta
113 def make_additional_declarations(self
):
114 """Return additional declarations, if any, that are needed in
120 def make_result_handler(self
, invocation
, output_var
):
121 """Return the shader code that is needed to produce the result
122 and store it in output_var.
124 invocation is the GLSL code to compute the output of the
129 def make_result_test(self
, test_num
, test_vector
):
130 """Return the shader_runner test code that is needed to test a
134 def testname_suffix(self
):
135 """Return a string to be used as a suffix on the test name to
136 distinguish it from tests using other comparators."""
140 class BoolComparator(Comparator
):
141 """Comparator that tests functions returning bools and bvecs by
142 converting them to floats.
144 This comparator causes code to be generated in the following form:
146 rettype result = func(args);
147 output_var = vec4(result, 0.0, ...);
149 def __init__(self
, signature
):
150 assert not signature
.rettype
.is_matrix
151 self
.__signature
= signature
152 self
.__padding
= 4 - signature
.rettype
.num_rows
154 def make_result_handler(self
, invocation
, output_var
):
155 statements
= ' {0} result = {1};\n'.format(
156 self
.__signature
.rettype
, invocation
)
157 statements
+= ' {0} = vec4(result{1});\n'.format(
158 output_var
, ', 0.0' * self
.__padding
)
161 def convert_to_float(self
, value
):
162 """Convert the given vector or scalar value to a list of
163 floats representing the expected color produced by the test.
165 value
= value
*1.0 # convert bools to floats
166 value
= column_major_values(value
)
167 value
+= [0.0] * self
.__padding
170 def make_result_test(self
, test_num
, test_vector
, draw
):
172 test
+= 'probe rgba {0} 0 {1}\n'.format(
174 shader_runner_format(self
.convert_to_float(test_vector
.result
)))
178 class BoolIfComparator(Comparator
):
179 """Comparator that tests functions returning bools by evaluating
180 them inside an if statement.
182 This comparator causes code to be generated in the following form:
185 output_var = vec4(1.0, 1.0, 0.0, 1.0);
187 output_var = vecp(0.0, 0.0, 1.0, 1.0);
189 def __init__(self
, signature
):
190 assert signature
.rettype
== glsl_bool
191 self
.__padding
= 4 - signature
.rettype
.num_rows
193 def make_result_handler(self
, invocation
, output_var
):
194 statements
= ' if({0})\n'.format(invocation
)
195 statements
+= ' {0} = vec4(1.0, 1.0, 0.0, 1.0);\n'.format(
197 statements
+= ' else\n'
198 statements
+= ' {0} = vec4(0.0, 0.0, 1.0, 1.0);\n'.format(
202 def convert_to_float(self
, value
):
203 """Convert the given vector or scalar value to a list of
204 floats representing the expected color produced by the test.
207 return [1.0, 1.0, 0.0, 1.0]
209 return [0.0, 0.0, 1.0, 1.0]
211 def make_result_test(self
, test_num
, test_vector
, draw
):
213 test
+= 'probe rgba {0} 0 {1}\n'.format(
215 shader_runner_format(self
.convert_to_float(test_vector
.result
)))
218 def testname_suffix(self
):
222 class IntComparator(Comparator
):
223 """Comparator that tests functions returning ints or ivecs using a
224 strict equality test.
226 This comparator causes code to be generated in the following form:
228 rettype result = func(args);
229 output_var = result == expected ? vec4(0.0, 1.0, 0.0, 1.0)
230 : vec4(1.0, 0.0, 0.0, 1.0);
232 def __init__(self
, signature
):
233 self
.__signature
= signature
235 def make_additional_declarations(self
):
236 return 'uniform {0} expected;\n'.format(self
.__signature
.rettype
)
238 def make_result_handler(self
, invocation
, output_var
):
239 statements
= ' {0} result = {1};\n'.format(
240 self
.__signature
.rettype
, invocation
)
241 statements
+= ' {v} = {cond} ? {green} : {red};\n'.format(
242 v
=output_var
, cond
='result == expected',
243 green
='vec4(0.0, 1.0, 0.0, 1.0)',
244 red
='vec4(1.0, 0.0, 0.0, 1.0)')
247 def make_result_test(self
, test_num
, test_vector
, draw
):
248 test
= 'uniform {0} expected {1}\n'.format(
249 shader_runner_type(self
.__signature
.rettype
),
250 shader_runner_format(column_major_values(test_vector
.result
)))
252 test
+= 'probe rgba {0} 0 0.0 1.0 0.0 1.0\n'.format(test_num
)
256 class FloatComparator(Comparator
):
257 """Comparator that tests functions returning floats or vecs using a
258 strict equality test.
260 This comparator causes code to be generated in the following form:
262 rettype result = func(args);
263 output_var = distance(result, expected) <= tolerance
264 ? vec4(0.0, 1.0, 0.0, 1.0) : vec4(1.0, 0.0, 0.0, 1.0);
266 def __init__(self
, signature
):
267 self
.__signature
= signature
269 def make_additional_declarations(self
):
270 decls
= 'uniform double tolerance;\n'
271 decls
+= 'uniform {0} expected;\n'.format(self
.__signature
.rettype
)
274 def make_indexers(self
):
275 """Build a list of strings which index into every possible
276 value of the result. For example, if the result is a vec2,
277 then build the indexers ['[0]', '[1]'].
279 if self
.__signature
.rettype
.num_cols
== 1:
282 col_indexers
= ['[{0}]'.format(i
)
283 for i
in range(self
.__signature
.rettype
.num_cols
)]
284 if self
.__signature
.rettype
.num_rows
== 1:
287 row_indexers
= ['[{0}]'.format(i
)
288 for i
in range(self
.__signature
.rettype
.num_rows
)]
289 return [col_indexer
+ row_indexer
290 for col_indexer
in col_indexers
291 for row_indexer
in row_indexers
]
293 def make_result_handler(self
, invocation
, output_var
):
294 statements
= ' {0} result = {1};\n'.format(
295 self
.__signature
.rettype
, invocation
)
296 # Can't use distance when testing itself, or when the rettype
298 if self
.__signature
.name
== 'distance' or \
299 self
.__signature
.rettype
.is_matrix
:
300 statements
+= ' {0} residual = result - expected;\n'.format(
301 self
.__signature
.rettype
)
302 statements
+= ' double error_sq = {0};\n'.format(
304 'residual{0} * residual{0}'.format(indexer
)
305 for indexer
in self
.make_indexers()))
306 condition
= 'error_sq <= tolerance * tolerance'
308 condition
= 'distance(result, expected) <= tolerance'
309 statements
+= ' {v} = {cond} ? {green} : {red};\n'.format(
310 v
=output_var
, cond
=condition
, green
='vec4(0.0, 1.0, 0.0, 1.0)',
311 red
='vec4(1.0, 0.0, 0.0, 1.0)')
314 def make_result_test(self
, test_num
, test_vector
, draw
):
315 test
= 'uniform {0} expected {1}\n'.format(
316 shader_runner_type(self
.__signature
.rettype
),
317 shader_runner_format(column_major_values(test_vector
.result
)))
318 test
+= 'uniform double tolerance {0}\n'.format(
319 shader_runner_format([test_vector
.tolerance
]))
321 test
+= 'probe rgba {0} 0 0.0 1.0 0.0 1.0\n'.format(test_num
)
325 class ShaderTest(object):
326 """Class used to build a test of a single built-in. This is an
327 abstract base class--derived types should override test_prefix(),
328 make_vertex_shader(), make_fragment_shader(), and other functions
331 __metaclass__
= abc
.ABCMeta
333 def __init__(self
, signature
, test_vectors
, use_if
):
334 """Prepare to build a test for a single built-in. signature
335 is the signature of the built-in (a key from the
336 builtin_function.test_suite dict), and test_vectors is the
337 list of test vectors for testing the given builtin (the
338 corresponding value from the builtin_function.test_suite
341 If use_if is True, then the generated test checks the result
342 by using it in an if statement--this only works for builtins
345 self
._signature
= signature
346 self
._test
_vectors
= test_vectors
348 self
._comparator
= BoolIfComparator(signature
)
349 elif signature
.rettype
.base_type
== glsl_bool
:
350 self
._comparator
= BoolComparator(signature
)
351 elif signature
.rettype
.base_type
== glsl_double
:
352 self
._comparator
= FloatComparator(signature
)
354 raise Exception('Unexpected rettype {0}'.format(signature
.rettype
))
356 def glsl_version(self
):
357 return self
._signature
.version_introduced
359 def draw_command(self
):
360 if self
.glsl_version() >= 140:
361 return 'draw arrays GL_TRIANGLE_FAN 0 4\n'
363 return 'draw rect -1 -1 2 2\n'
365 def make_additional_requirements(self
):
366 """Return a string that should be included in the test's
369 if self
._signature
.extension
:
370 return 'GL_{0}\n'.format(self
._signature
.extension
)
374 def test_prefix(self
):
375 """Return the prefix that should be used in the test file name
376 to identify the type of test, e.g. "vs" for a vertex shader
381 def make_vertex_shader(self
):
382 """Return the vertex shader for this test."""
384 def make_geometry_shader(self
):
385 """Return the geometry shader for this test (or None if this
386 test doesn't require a geometry shader). No need to
387 reimplement this function in classes that don't use geometry
392 def make_geometry_layout(self
):
393 """Return the geometry layout for this test (or None if this
394 test doesn't require a geometry layout section). No need to
395 reimplement this function in classes that don't use geometry
401 def make_fragment_shader(self
):
402 """Return the fragment shader for this test."""
404 def make_test_shader(self
, additional_declarations
, prefix_statements
,
405 output_var
, suffix_statements
):
406 """Generate the shader code necessary to test the built-in.
407 additional_declarations is a string containing any
408 declarations that need to be before the main() function of the
409 shader. prefix_statements is a string containing any
410 additional statements than need to be inside the main()
411 function of the shader, before the built-in function is
412 called. output_var is the variable that the result of the
413 built-in function should be assigned to, after conversion to a
414 vec4. suffix_statements is a string containing any additional
415 statements that need to be inside the main() function of the
416 shader, after the built-in function is called.
419 if self
._signature
.extension
:
420 shader
+= '#extension GL_{0} : require\n'.format(self
._signature
.extension
)
421 shader
+= additional_declarations
422 for i
in range(len(self
._signature
.argtypes
)):
423 shader
+= 'uniform {0} arg{1};\n'.format(
424 self
._signature
.argtypes
[i
], i
)
425 shader
+= self
._comparator
.make_additional_declarations()
427 shader
+= 'void main()\n'
429 shader
+= prefix_statements
430 invocation
= self
._signature
.template
.format(
432 for i
in range(len(self
._signature
.argtypes
))])
433 shader
+= self
._comparator
.make_result_handler(invocation
, output_var
)
434 shader
+= suffix_statements
439 """Make the complete shader_runner test file, and return it as
443 for test_num
, test_vector
in enumerate(self
._test
_vectors
):
444 for i
in range(len(test_vector
.arguments
)):
445 test
+= 'uniform {0} arg{1} {2}\n'.format(
446 shader_runner_type(self
._signature
.argtypes
[i
]),
447 i
, shader_runner_format(
448 column_major_values(test_vector
.arguments
[i
])))
449 # Note: shader_runner uses a 250x250 window so we must
450 # ensure that test_num <= 250.
451 test
+= self
._comparator
.make_result_test(
452 test_num
% 250, test_vector
, self
.draw_command())
455 def make_vbo_data(self
):
456 # Starting with GLSL 1.40/GL 3.1, we need to use VBOs and
457 # vertex shader input bindings for our vertex data instead of
458 # the piglit drawing utilities and gl_Vertex.
459 if self
.glsl_version() < 140:
461 vbo
= '[vertex data]\n'
462 vbo
+= 'vertex/float/2\n'
471 argtype_names
= '-'.join(
472 str(argtype
) for argtype
in self
._signature
.argtypes
)
473 if self
._signature
.extension
:
474 subdir
= self
._signature
.extension
.lower()
476 subdir
= 'glsl-{0:1.2f}'.format(float(self
.glsl_version()) / 100)
478 'spec', subdir
, 'execution', 'built-in-functions',
479 '{0}-{1}-{2}{3}.shader_test'.format(
480 self
.test_prefix(), self
._signature
.name
, argtype_names
,
481 self
._comparator
.testname_suffix()))
483 def generate_shader_test(self
):
484 """Generate the test and write it to the output file."""
485 shader_test
= '[require]\n'
486 shader_test
+= 'GLSL >= {0:1.2f}\n'.format(
487 float(self
.glsl_version()) / 100)
488 shader_test
+= self
.make_additional_requirements()
490 shader_test
+= '[vertex shader]\n'
491 shader_test
+= self
.make_vertex_shader()
493 gs
= self
.make_geometry_shader()
495 shader_test
+= '[geometry shader]\n'
498 gl
= self
.make_geometry_layout()
500 shader_test
+= '[geometry layout]\n'
503 shader_test
+= '[fragment shader]\n'
504 shader_test
+= self
.make_fragment_shader()
506 shader_test
+= self
.make_vbo_data()
507 shader_test
+= '[test]\n'
508 shader_test
+= 'clear color 0.0 0.0 1.0 0.0\n'
509 shader_test
+= 'clear\n'
510 shader_test
+= self
.make_test()
511 filename
= self
.filename()
512 dirname
= os
.path
.dirname(filename
)
513 utils
.safe_makedirs(dirname
)
514 with
open(filename
, 'w') as f
:
518 class VertexShaderTest(ShaderTest
):
519 """Derived class for tests that exercise the built-in in a vertex
522 def test_prefix(self
):
525 def make_vertex_shader(self
):
526 if self
.glsl_version() >= 140:
527 return self
.make_test_shader(
528 'in vec4 vertex;\n' +
530 ' gl_Position = vertex;\n',
533 return self
.make_test_shader(
534 'varying vec4 color;\n',
535 ' gl_Position = gl_Vertex;\n',
538 def make_fragment_shader(self
):
539 shader
= '''varying vec4 color;
543 gl_FragColor = color;
549 class GeometryShaderTest(ShaderTest
):
550 """Derived class for tests that exercise the built-in in a
553 def test_prefix(self
):
556 def glsl_version(self
):
557 return max(150, ShaderTest
.glsl_version(self
))
559 def make_vertex_shader(self
):
561 shader
+= "in vec4 vertex;\n"
562 shader
+= "out vec4 vertex_to_gs;\n"
564 shader
+= "void main()\n"
566 shader
+= " vertex_to_gs = vertex;\n"
571 def make_geometry_shader(self
):
572 additional_declarations
= ''
573 additional_declarations
+= 'layout(triangles) in;\n'
574 additional_declarations \
575 += 'layout(triangle_strip, max_vertices = 3) out;\n'
576 additional_declarations
+= 'in vec4 vertex_to_gs[3];\n'
577 additional_declarations
+= 'out vec4 color;\n'
578 return self
.make_test_shader(
579 additional_declarations
,
580 ' vec4 tmp_color;\n',
582 ' for (int i = 0; i < 3; i++) {\n'
583 ' gl_Position = vertex_to_gs[i];\n'
584 ' color = tmp_color;\n'
588 def make_fragment_shader(self
):
589 shader
= '''varying vec4 color;
593 gl_FragColor = color;
599 class FragmentShaderTest(ShaderTest
):
600 """Derived class for tests that exercise the built-in in a
603 def test_prefix(self
):
606 def make_vertex_shader(self
):
608 if self
.glsl_version() >= 140:
609 shader
+= "in vec4 vertex;\n"
611 shader
+= "void main()\n"
613 if self
.glsl_version() >= 140:
614 shader
+= " gl_Position = vertex;\n"
616 shader
+= " gl_Position = gl_Vertex;\n"
621 def make_fragment_shader(self
):
622 return self
.make_test_shader('', '', 'gl_FragColor', '')
626 for use_if
in [False, True]:
627 for signature
, test_vectors
in sorted(test_suite
.items()):
628 if use_if
and signature
.rettype
!= glsl_bool
:
630 yield VertexShaderTest(signature
, test_vectors
, use_if
)
631 yield GeometryShaderTest(signature
, test_vectors
, use_if
)
632 yield FragmentShaderTest(signature
, test_vectors
, use_if
)
636 desc
= 'Generate shader tests that test built-in functions using uniforms'
637 usage
= 'usage: %prog [-h] [--names-only]'
638 parser
= optparse
.OptionParser(description
=desc
, usage
=usage
)
643 help="Don't output files, just generate a list of filenames to stdout")
644 options
, args
= parser
.parse_args()
645 for test
in all_tests():
646 if not options
.names_only
:
647 test
.generate_shader_test()
648 print(test
.filename())
651 if __name__
== '__main__':