[Piglit] [PATCH 2/4] arb_shader_precision: add framework for calculating tolerances for complex functions
Micah Fedke
micah.fedke at collabora.co.uk
Wed Feb 18 22:06:32 PST 2015
---
generated_tests/gen_shader_precision_tests.py | 148
++++++++++++++++++++++++--
1 file changed, 137 insertions(+), 11 deletions(-)
diff --git a/generated_tests/gen_shader_precision_tests.py
b/generated_tests/gen_shader_precision_tests.py
index cfa5065..0bda05a 100644
--- a/generated_tests/gen_shader_precision_tests.py
+++ b/generated_tests/gen_shader_precision_tests.py
@@ -49,29 +49,155 @@
from builtin_function import * import mako.template import os
+import struct
+import bigfloat
from templates import template_file
-tolerances = {'pow': 16.0, - 'exp': 3.0,
- 'exp2': 3.0,
- 'log': 3.0,
- 'log2': 3.0,
- 'sqrt': 3.0,
- 'inversesqrt': 2.0,
- 'op-div': 2.5,
- 'op-assign-div': 2.5,
- }
+
+allowed_error_scale = 4.0
trig_builtins = ('sin', 'cos', 'tan', 'asin',
'acos', 'atan', 'sinh', 'cosh', 'tanh',
'asinh', 'acosh', 'atanh')
+high_precision = bigfloat.precision(113)
+low_precision = bigfloat.precision(23)
+
def _is_sequence(arg):
return (not hasattr(arg, "strip") and
hasattr(arg, "__iter__"))
+def _len_any(a):
+ """ a version of len that returns 1 if passed a non-sequence type
+ """
+ return len(a) if _is_sequence(a) else 1
+
+def _floatToBits(f):
+ s = struct.pack('>f', f)
+ return struct.unpack('>l', s)[0]
+
+def _bitsToFloat(b):
+ s = struct.pack('>l', b)
+ return struct.unpack('>f', s)[0]
+
+def _ulpsize(f):
+ """ determine _ulpsize in the direction of nearest infinity
+ which gives the worst case scenario for edge cases
+ """
+ return bigfloat.next_up(f)-f if f >= 0.0 \
+ else f-bigfloat.next_down(f)
+
+def _capture_error(precise, imprecise):
+ """Perform the legwork of calculating the difference in error of
the high
+ precision and low precision runs. Decide whether this difference
in error
+ is within allowable tolerances. The range of allowable tolerances is
+ subjective, as ARB_shader_precision (and GLSL spec as of v4.5) gives no
+ direct guidance for complex functions. Toronto, et. al. use
quadrupled
+ error as a limit in "Practically Accurate Floating-Point Math,"
Computing
+ Now, Oct. 2014. Also use the difference in error and the value of
one ulp
+ at the output to calculate the tolerance range in ulps for use by the
+ shader test, should this vector pass the badlands check.
+ """
+
+ ers = []
+ bls = []
+ cts = []
+ with high_precision:
+ error = bigfloat.abs(precise - imprecise)
+ ers.append(error)
+ with low_precision:
+ ulpsz = _ulpsize(imprecise)
+ with high_precision:
+ bls.append(error > ulpsz*allowed_error_scale)
+ cts.append(bigfloat.round(error/ulpsz))
+ return {'errors':ers, 'badlands':bls, 'component_tolerances':cts}
+
+def _analyze_ref_fn(fn, args):
+ """Many functions contain ill-conditioned regions referred to as
"badlands"
+ (see Toronto, et. al., "Practically Accurate Floating-Point Math,"
+ Computing Now, Oct. 2014). Within these regions errors in the
inputs are
+ magnified significantly, making the function impossible to test
with any
+ reasonable accuracy. A complex function that operates on floating
point
+ numbers has the potential to generate such error propagation even
if the
+ inputs are exact floating point numbers, since intermediate results
can be
+ generated with error. In order to identify and avoid these areas,
we run
+ the function once at a lower precision and once at a higher
precision, and
+ compare the outputs. Propagating errors will be greater at lower
precision
+ and less at higher precision for a given set of function inputs,
allowing
+ us to identify the badlands of the function.
+ """
+
+ ret = {'errors':[], 'badlands':[], 'component_tolerances':[]}
+ with high_precision:
+ precise = fn(args)
+ with low_precision:
+ imprecise = fn(args)
+ if _len_any(imprecise) == 1:
+ ret = _capture_error(precise, imprecise)
+ else:
+ for i, arg in enumerate(imprecise):
+ rettmp = _capture_error(precise[i], arg)
+ ret['errors'].extend(rettmp['errors'])
+ ret['badlands'].extend(rettmp['badlands'])
+
ret['component_tolerances'].extend(rettmp['component_tolerances'])
+ return ret
+
+simple_fns = {'op-mult': 0.0,
+ 'op-assign-mult': 0.0,
+ 'op-div': 2.5,
+ 'op-assign-div': 2.5,
+ 'pow': 16.0, + 'exp': 3.0,
+ 'exp2': 3.0,
+ 'log': 3.0,
+ 'log2': 3.0,
+ 'sqrt': 3.0,
+ 'inversesqrt': 2.0}
+ +complex_fns = {}
+
+componentwise_fns = ('mod', 'mix', 'smoothstep' )
+
+def _gen_tolerance(name, rettype, args):
+ """Return the tolerance that should be allowed for a function for the
+ test vector passed in. Return -1 for any vectors that would push the
+ tolerance outside of acceptable bounds + """
+ if name in simple_fns:
+ if name == 'op-mult' or name == 'op-assign-mult':
+ x_type = glsl_type_of(args[0])
+ y_type = glsl_type_of(args[1])
+ if x_type.is_vector and y_type.is_matrix:
+ mult_func = _vec_times_mat_ref
+ elif x_type.is_matrix and y_type.is_vector:
+ mult_func = _mat_times_vec_ref
+ elif x_type.is_matrix and y_type.is_matrix:
+ mult_func = _mat_times_mat_ref
+ else:
+ return simple_fns[name] + ret =
_analyze_ref_fn(mult_func, args)
+ return -1.0 if any(ret['badlands']) else map(float,
ret['component_tolerances'])
+ else:
+ return simple_fns[name] + elif name in complex_fns:
+ if name in componentwise_fns:
+ ret = {'errors':[], 'badlands':[], 'component_tolerances':[]}
+ for component in range(rettype.num_cols*rettype.num_rows):
+ current_args = []
+ for i, arg in enumerate(args):
+ current_args.append(arg[component%len(arg)] if
_len_any(arg) > 1 else arg)
+ rettmp = _analyze_ref_fn(complex_fns[name], current_args)
+ ret['errors'].extend(rettmp['errors'])
+ ret['badlands'].extend(rettmp['badlands'])
+
ret['component_tolerances'].extend(rettmp['component_tolerances'])
+ else:
+ ret = _analyze_ref_fn(complex_fns[name], args)
+ return -1.0 if any(ret['badlands']) else map(float,
ret['component_tolerances'])
+ else:
+ return 0.0
+
def make_indexers(signature):
"""Build a list of strings which index into every possible
value of the result. For example, if the result is a vec2,
@@ -160,7 +286,7 @@ def main():
with open(output_filename, 'w') as f:
f.write(template.render_unicode(
signature=signature,
test_vectors=test_vectors,
- tolerances=tolerances,
+ tolerances=simple_fns,
invocation=invocation,
num_elements=num_elements,
indexers=indexers,
--
2.2.2
More information about the Piglit
mailing list