[Piglit] [RFC PATCH] glsl-es-3.00: Generate tests for builtin packing functions (v2)

Chad Versace chad.versace at linux.intel.com
Mon Jan 21 00:37:30 PST 2013


Generate the following test files:
  {const,vs,fs}-{pack,unpack}{Snorm,Unorm,Half}2x16.shader_test

The tests are generated by a new Python script,
gen_builtin_packing_tests.py, and placed into directory
spec/glsl-es-3.00/execution/built-in-functions.

v2: Rewrite as a pure Python implementation. On Intel Sandybridge, reduces
    test generation time from 2m to 2s.

CC: Paul Berry <stereotype441 at gmail.com>
Signed-off-by: Chad Versace <chad.versace at linux.intel.com>
---

This patch lives on my gles3-glsl-packing branch.

Tested against my mesa gles3-glsl-packing-branch on Intel gen7 with no
failures.

However, const-packHalf2x16.shader_test invokes the kernel's oom-killer. By
watching top, it seems that there exists a memory explosion somewhere in Mesa
once the shader reaches a certain size. It's possible to avoid the oom-killer
by poking at procfs as root, but that's not acceptable for a piglit test.

To avoid the oom-killer without poking at procfs, it's necessary to reduce the
length of the test inputs in make_inputs_for_pack_half_2x16 by half. I don't
believe it's possible to reasonably test packHalf2x16 with that few test
inputs, though.

Here are the two routes I see for handling the problem:

  1. Reduce the list of test inputs by half. Add a comment stating "We're
     likely not sufficiently testing packHalf2x16 with such a small set of
     test inputs". Given how soon the Mesa 9.1 branch will be cut, this seems
     like a prudent choice.

  2. Investigate the memory explosion and fix it before committing this patch.
     This is an open ended task.

Advice?



 generated_tests/CMakeLists.txt               |    6 +-
 generated_tests/gen_builtin_packing_tests.py | 1186 ++++++++++++++++++++++++++
 2 files changed, 1191 insertions(+), 1 deletion(-)
 create mode 100644 generated_tests/gen_builtin_packing_tests.py

diff --git a/generated_tests/CMakeLists.txt b/generated_tests/CMakeLists.txt
index e371ff8..694a213 100644
--- a/generated_tests/CMakeLists.txt
+++ b/generated_tests/CMakeLists.txt
@@ -20,6 +20,9 @@ endfunction(piglit_make_generated_tests custom_target generator_script)
 
 # Create custom commands and targets to build generated tests.
 piglit_make_generated_tests(
+	builtin_packing_tests.list
+	gen_builtin_packing_tests.py)
+piglit_make_generated_tests(
 	builtin_uniform_tests.list
 	gen_builtin_uniform_tests.py
 	builtin_function.py)
@@ -49,7 +52,8 @@ piglit_make_generated_tests(
 # 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 builtin_uniform_tests.list
+	DEPENDS builtin_packing_tests.list
+		builtin_uniform_tests.list
 		constant_array_size_tests.list
 		interpolation_tests.list
 		non-lvalue_tests.list
diff --git a/generated_tests/gen_builtin_packing_tests.py b/generated_tests/gen_builtin_packing_tests.py
new file mode 100644
index 0000000..06d3be0
--- /dev/null
+++ b/generated_tests/gen_builtin_packing_tests.py
@@ -0,0 +1,1186 @@
+#!/usr/bin/env python2
+# coding=utf-8
+
+import mako.template
+import mako.runtime
+import math
+import numpy as np
+import optparse
+import os
+import sys
+
+from collections import namedtuple
+from mako.template import Template
+from math import copysign, fabs, fmod, frexp, isinf, isnan, modf
+from numpy import int16, int32, uint16, uint32, float32
+from textwrap import dedent
+
+# ----------------------------------------------------------------------------
+# Overview
+# ----------------------------------------------------------------------------
+#
+# This scripts generates tests for the GLSL packing functions, such as
+# packSnorm2x16.
+#
+# In the test templates below, observe that the GLSL function's actual output
+# is compared against multiple expected outputs.  Given an input and
+# a pack/unpackfunction, there exist multiple valid outputs because the GLSL
+# specs permit variation in the implementation of the function. The actual
+# output is dependent on the GLSL compiler's and hardware's choice of rounding
+# mode (for example, to even or to nearest) and handling of subnormal (also
+# called denormalized) floating point numbers.
+
+# ----------------------------------------------------------------------------
+# Templates for test files
+# ----------------------------------------------------------------------------
+
+# Test evaluation of constant pack2x16 expressions.
+const_pack_2x16_template = Template(dedent("""\
+    [require]
+    GL ES >= 3.0
+    GLSL ES >= 3.00
+
+    [vertex shader]
+    const vec4 red = vec4(1, 0, 0, 1);
+    const vec4 green = vec4(0, 1, 0, 1);
+
+    in vec4 vertex;
+    out vec4 vert_color;
+
+    void main()
+    {
+        ${func.result_precision} uint actual;
+
+        gl_Position = vertex;
+
+        % for io in func.inout_seq:
+        actual = ${func.name}(vec2(${io.input[0]}, ${io.input[1]}));
+
+        if (true
+            % for u in io.valid_outputs:
+            && actual != ${u}
+            % endfor
+           ) {
+            vert_color = red;
+            return;
+        }
+
+        % endfor
+
+        vert_color = green;
+    }
+
+    [fragment shader]
+    in vec4 vert_color;
+    out vec4 frag_color;
+
+    void main()
+    {
+        frag_color = vert_color;
+    }
+
+    [vertex data]
+    vertex/float/2
+    -1.0 -1.0
+     1.0 -1.0
+     1.0  1.0
+    -1.0  1.0
+
+    [test]
+    draw arrays GL_TRIANGLE_FAN 0 4
+    probe all rgba 0.0 1.0 0.0 1.0
+"""))
+
+# Test evaluation of constant unpack2x16 expressions.
+const_unpack_2x16_template = Template(dedent("""\
+    [require]
+    GL ES >= 3.0
+    GLSL ES >= 3.00
+
+    [vertex shader]
+    const vec4 red = vec4(1, 0, 0, 1);
+    const vec4 green = vec4(0, 1, 0, 1);
+
+    in vec4 vertex;
+    out vec4 vert_color;
+
+    void main()
+    {
+        ${func.result_precision} vec2 actual;
+
+        gl_Position = vertex;
+
+        % for io in func.inout_seq:
+        actual = ${func.name}(${io.input});
+
+        if (true
+            % for v in io.valid_outputs:
+            && actual != vec2(${v[0]}, ${v[1]})
+            % endfor
+           ) {
+            vert_color = red;
+            return;
+        }
+
+        % endfor
+
+        vert_color = green;
+    }
+
+    [fragment shader]
+    in vec4 vert_color;
+    out vec4 frag_color;
+
+    void main()
+    {
+        frag_color = vert_color;
+    }
+
+    [vertex data]
+    vertex/float/2
+    -1.0 -1.0
+     1.0 -1.0
+     1.0  1.0
+    -1.0  1.0
+
+    [test]
+    draw arrays GL_TRIANGLE_FAN 0 4
+    probe all rgba 0.0 1.0 0.0 1.0
+"""))
+
+# Test execution of pack2x16 functions in the vertex shader.
+vs_pack_2x16_template = Template(dedent("""\
+    [require]
+    GL ES >= 3.0
+    GLSL ES >= 3.00
+
+    [vertex shader]
+    const vec4 red = vec4(1, 0, 0, 1);
+    const vec4 green = vec4(0, 1, 0, 1);
+
+    uniform vec2 func_input;
+
+    % for j in range(func.num_valid_outputs):
+    uniform ${func.result_precision} uint expect${j};
+    % endfor
+
+    in vec4 vertex;
+    out vec4 vert_color;
+
+    void main()
+    {
+        gl_Position = vertex;
+        ${func.result_precision} uint actual = ${func.name}(func_input);
+
+        if (false
+            % for j in range(func.num_valid_outputs):
+            || actual == expect${j}
+            % endfor
+           ) {
+           vert_color = green;
+        } else {
+            vert_color = red;
+        }
+    }
+
+    [fragment shader]
+    in vec4 vert_color;
+    out vec4 frag_color;
+
+    void main()
+    {
+        frag_color = vert_color;
+    }
+
+    [vertex data]
+    vertex/float/2
+    -1.0 -1.0
+     1.0 -1.0
+     1.0  1.0
+    -1.0  1.0
+
+    [test]
+    % for io in func.inout_seq:
+    uniform vec2 func_input ${io.input[0]} ${io.input[1]}
+    % for j in range(func.num_valid_outputs):
+    uniform uint expect${j} ${io.valid_outputs[j]}
+    % endfor
+    draw arrays GL_TRIANGLE_FAN 0 4
+    probe all rgba 0.0 1.0 0.0 1.0
+
+    % endfor
+"""))
+
+# Test execution of unpack2x16 functions in the vertex shader.
+vs_unpack_2x16_template = Template(dedent("""\
+    [require]
+    GL ES >= 3.0
+    GLSL ES >= 3.00
+
+    [vertex shader]
+    const vec4 red = vec4(1, 0, 0, 1);
+    const vec4 green = vec4(0, 1, 0, 1);
+
+    uniform highp uint func_input;
+
+    % for j in range(func.num_valid_outputs):
+    uniform ${func.result_precision} vec2 expect${j};
+    % endfor
+
+    in vec4 vertex;
+    out vec4 vert_color;
+
+    void main()
+    {
+        gl_Position = vertex;
+
+        ${func.result_precision} vec2 actual = ${func.name}(func_input);
+
+        if (false
+            % for j in range(func.num_valid_outputs):
+            || actual == expect${j}
+            % endfor
+           ) {
+            vert_color = green;
+        } else {
+            vert_color = red;
+        }
+    }
+
+    [fragment shader]
+    in vec4 vert_color;
+    out vec4 frag_color;
+
+    void main()
+    {
+        frag_color = vert_color;
+    }
+
+    [vertex data]
+    vertex/float/2
+    -1.0 -1.0
+     1.0 -1.0
+     1.0  1.0
+    -1.0  1.0
+
+    [test]
+    % for io in func.inout_seq:
+    uniform uint func_input ${io.input}
+    % for j in range(func.num_valid_outputs):
+    uniform vec2 expect${j} ${io.valid_outputs[j][0]} ${io.valid_outputs[j][1]}
+    % endfor
+    draw arrays GL_TRIANGLE_FAN 0 4
+    probe all rgba 0.0 1.0 0.0 1.0
+
+    % endfor
+"""))
+
+
+# Test execution of pack2x16 functions in the fragment shader.
+fs_pack_2x16_template = Template(dedent("""\
+    [require]
+    GL ES >= 3.0
+    GLSL ES >= 3.00
+
+    [vertex shader]
+    in vec4 vertex;
+
+    void main()
+    {
+        gl_Position = vertex;
+    }
+
+    [fragment shader]
+    const vec4 red = vec4(1, 0, 0, 1);
+    const vec4 green = vec4(0, 1, 0, 1);
+
+    uniform vec2 func_input;
+
+    % for i in range(func.num_valid_outputs):
+    uniform ${func.result_precision} uint expect${i};
+    % endfor
+
+    out vec4 frag_color;
+
+    void main()
+    {
+        ${func.result_precision} uint actual = ${func.name}(func_input);
+
+        if (false
+            % for i in range(func.num_valid_outputs):
+            || actual == expect${i}
+            % endfor
+           ) {
+            frag_color = green;
+        } else {
+            frag_color = red;
+        }
+    }
+
+    [vertex data]
+    vertex/float/2
+    -1.0 -1.0
+     1.0 -1.0
+     1.0  1.0
+    -1.0  1.0
+
+    [test]
+    % for io in func.inout_seq:
+    uniform vec2 func_input ${io.input[0]} ${io.input[1]}
+    % for i in range(func.num_valid_outputs):
+    uniform uint expect${i} ${io.valid_outputs[i]}
+    % endfor
+    draw arrays GL_TRIANGLE_FAN 0 4
+    probe all rgba 0.0 1.0 0.0 1.0
+
+    % endfor
+"""))
+
+# Test execution of unpack2x16 functions in the fragment shader.
+fs_unpack_2x16_template = Template(dedent("""\
+    [require]
+    GL ES >= 3.0
+    GLSL ES >= 3.00
+
+    [vertex shader]
+    in vec4 vertex;
+
+    void main()
+    {
+        gl_Position = vertex;
+    }
+
+    [fragment shader]
+    const vec4 red = vec4(1, 0, 0, 1);
+    const vec4 green = vec4(0, 1, 0, 1);
+
+    uniform highp uint func_input;
+
+    % for i in range(func.num_valid_outputs):
+    uniform ${func.result_precision} vec2 expect${i};
+    % endfor
+
+    out vec4 frag_color;
+
+    void main()
+    {
+        ${func.result_precision} vec2 actual = ${func.name}(func_input);
+
+        if (false
+            % for i in range(func.num_valid_outputs):
+            || actual == expect${i}
+            % endfor
+           ) {
+            frag_color = green;
+        } else {
+            frag_color = red;
+        }
+    }
+
+    [vertex data]
+    vertex/float/2
+    -1.0 -1.0
+     1.0 -1.0
+     1.0  1.0
+    -1.0  1.0
+
+    [test]
+    % for io in func.inout_seq:
+    uniform uint func_input ${io.input}
+    % for i in range(func.num_valid_outputs):
+    uniform vec2 expect${i} ${io.valid_outputs[i][0]} ${io.valid_outputs[i][1]}
+    % endfor
+    draw arrays GL_TRIANGLE_FAN 0 4
+    probe all rgba 0.0 1.0 0.0 1.0
+
+    % endfor
+"""))
+
+template_table = {
+    ("const", "p", "2x16") : const_pack_2x16_template,
+    ("const", "u", "2x16") : const_unpack_2x16_template,
+    ("vs",    "p", "2x16") : vs_pack_2x16_template,
+    ("vs",    "u", "2x16") : vs_unpack_2x16_template,
+    ("fs",    "p", "2x16") : fs_pack_2x16_template,
+    ("fs",    "u", "2x16") : fs_unpack_2x16_template,
+}
+
+# ----------------------------------------------------------------------------
+# Math for pack/unpack functions
+# ----------------------------------------------------------------------------
+
+class FuncOpts:
+    """Options that modify the evaluation of the GLSL pack/unpack functions.
+
+    Given an input and a pack/unpack function, there exist multiple valid
+    outputs because the GLSL specs permit variation in the implementation of
+    the function. The actual output is dependent on the GLSL compiler's and
+    hardware's choice of rounding mode (for example, to even or to nearest)
+    and handling of subnormal (also called denormalized) floating point
+    numbers.
+
+    This class attempts to capture such permitted variations in implementation
+    behavior. To select a particular behavior, pass the appropriate enums to
+    the constructor.
+
+    What follows is an explanation of the variations and how to select them.
+
+    Flushing subnormal floats to zero
+    ---------------------------------
+    The GLSL ES 3.00 and GLSL 4.10 specs allows implementations to truncate
+    subnormal floats to zero. From section 4.5.1 "Range and Precision" of the
+    two specs:
+        Any subnormal (denormalized) value input into a shader or
+        potentially generated by any operation in a shader can be
+        flushed to 0.
+
+    The constructor parameter 'flush_mode' selects the flushing behavior.
+    Valid values are:
+        - FLUSH_NONE:  Flush no float to zero.
+        - FLUSH_FLOAT32: Flush subnormal float32 values to zero.
+
+    Rounding mode
+    -------------
+    For some packing functions, the GLSL ES 3.00 specification's definition of
+    the function's behavior involves round(), whose behavior at
+    0.5 is an implementation detail. From section 8.3 of the spec:
+        The fraction 0.5 will round in a direction chosen by the
+        implementation, presumably the direction that is fastest.
+
+    The constructor parameter 'round_mode' selects the rounding behavior.
+    Valid values are:
+        - ROUND_TO_EVEN
+        - ROUND_TO_NEAREST
+    """
+
+    FLUSH_NONE = 0
+    FLUSH_FLOAT32 = 1
+
+    ROUND_TO_EVEN = 0
+    ROUND_TO_NEAREST = 1
+
+    def __init__(self, round_mode=ROUND_TO_EVEN, flush_mode=FLUSH_NONE):
+        if round_mode == FuncOpts.ROUND_TO_EVEN:
+            self.__round_func = round_to_even
+        elif round_mode == FuncOpts.ROUND_TO_NEAREST:
+            self.__round_func = round_to_nearest
+        else:
+            assert(False)
+
+        assert(flush_mode in [FuncOpts.FLUSH_NONE,
+                              FuncOpts.FLUSH_FLOAT32])
+        self.__flush_mode = flush_mode
+
+    def round(self, x):
+        """Round a float according to the requested rounding mode."""
+        assert(any(isinstance(x, T) for T in [float, float32]))
+
+        # Drop the floating-point precision from 64 to 32 bits before
+        # rounding.  The loss of precision may shift the float's fractional
+        # value to 0.5, which will affect the rounding.
+        x = float32(x)
+        return self.__round_func(x)
+
+    def maybe_flush_subnormal_float32_to_zero(self, x):
+        "The specified flushing mode determines whether this is a no-op."""
+        assert(isinstance(x, float32))
+
+        if self.__flush_mode == FuncOpts.FLUSH_FLOAT32:
+            return flush_subnormal_float32_to_zero(x)
+        else:
+            return x
+
+def flush_subnormal_float32_to_zero(x):
+    assert(isinstance(x, float32))
+
+    (m, e) = frexp(x)
+    if e < -125:
+        return copysign(0.0, x)
+    else:
+        return x
+
+def clamp(x, min, max):
+    if x < min:
+        return min
+    elif x > max:
+        return max
+    else:
+        return x
+
+def round_to_nearest(x):
+    # Get fractional and integral parts.
+    (f, i) = modf(x)
+
+    if fabs(f) < 0.5:
+        return i
+    else:
+        return i + copysign(1.0, x)
+
+def round_to_even(x):
+    # Get fractional and integral parts.
+    (f, i) = modf(x)
+
+    if fabs(f) < 0.5:
+        return i
+    elif fabs(f) == 0.5:
+        return i + fmod(i, 2.0)
+    else:
+        return i + copysign(1.0, x)
+
+def pack_2x16(pack_1x16_func, x, y, func_opts):
+    """Evaluate a GLSL pack2x16 function.
+
+    :param pack_1x16_func: the component-wise function of the GLSL pack2x16
+        function
+    :param x,y: each a float32
+    :return: a uint32
+    """
+    assert(isinstance(x, float32))
+    assert(isinstance(y, float32))
+
+    x = func_opts.maybe_flush_subnormal_float32_to_zero(x)
+    y = func_opts.maybe_flush_subnormal_float32_to_zero(y)
+
+    ux = pack_1x16_func(x, func_opts)
+    uy = pack_1x16_func(y, func_opts)
+
+    assert(isinstance(ux, uint16))
+    assert(isinstance(uy, uint16))
+
+    return uint32((uy << 16) | ux)
+
+def unpack_2x16(unpack_1x16_func, u, func_opts):
+    """Evaluate a GLSL unpack2x16 function.
+
+    :param unpack_1x16_func: the component-wise function of the GLSL
+        unpack2x16 function
+    :param u: a uint32
+    :return: a 2-tuple of float32
+    """
+    assert(isinstance(u, uint32))
+
+    ux = uint16(u & 0xffff)
+    uy = uint16(u >> 16)
+
+    x = unpack_1x16_func(ux)
+    y = unpack_1x16_func(uy)
+
+    assert(isinstance(x, float32))
+    assert(isinstance(y, float32))
+
+    x = func_opts.maybe_flush_subnormal_float32_to_zero(x)
+    y = func_opts.maybe_flush_subnormal_float32_to_zero(y)
+
+    return (x, y)
+
+def pack_snorm_1x16(f32, func_opts):
+    """Component-wise function of packSnorm2x16."""
+    assert(isinstance(f32, float32))
+    return uint16(int16(func_opts.round(clamp(f32, -1.0, +1.0) * 32767.0)))
+
+def unpack_snorm_1x16(u16):
+    """Component-wise function of unpackSnorm2x16."""
+    assert(isinstance(u16, uint16))
+    return float32(clamp(int16(u16) / 32767.0, -1.0, +1.0))
+
+def pack_unorm_1x16(f32, func_opts):
+    """Component-wise function of packUnorm2x16."""
+    assert(isinstance(f32, float32))
+    return uint16(func_opts.round(clamp(f32, 0.0, 1.0) * 65535.0))
+
+def unpack_unorm_1x16(u16):
+    """Component-wise function of unpackUnorm2x16."""
+    assert(isinstance(u16, uint16))
+    return float32(u16 / 65535.0)
+
+def pack_half_1x16(f32, func_opts):
+    """Component-wise function of packHalf2x16."""
+    assert(isinstance(f32, float32))
+
+    # The bit layout of a float16 is:
+    #
+    #   sign:     15
+    #   exponent: 10:14
+    #   mantissa: 0:9
+    #
+    # The sign, exponent, and mantissa determine its value by:
+    #
+    #  if e = 0 and m = 0, then zero:       (-1)^s * 0
+    #  if e = 0 and m != 0, then subnormal: (-1)^s * 2^(e - 14) * m / 2^10
+    #  if 0 < e < 31, then normal:          (-1)^s * 2^(e - 15) * (1 + m / 2^10)
+    #  if e = 31 and m = 0, then inf:       (-1)^s * inf
+    #  if e = 31 and m != 0, then nan
+    #
+    # where 0 <= m < 2^10.
+    #
+    # Some key boundary values of float16 are:
+    #
+    #   min_normal16  = 2^(1 - 15) * (1 + 0 / 2^10)
+    #   max_normal16  = 2^(30 - 15) * (1 + 1023 / 2^10)
+    #
+    # Observe that each of the above boundary values lies in the range of
+    # normal float32 values. If we represent each of the above boundary values
+    # in the form returned by frexpf() for normal float32 values, 2^E
+    # * F where 0.5 <= F < 1, then:
+    #
+    #   min_normal16 = 2^(-13) * 0.5
+    #   max_normal16 = 2^16 * 0.99951171875
+
+    # The resultant float16's sign, exponent, and mantissa bits.
+    s = 0
+    e = 0
+    m = 0
+
+    # Calculate sign bit.
+    # Use copysign() to handle the case where x is -0.0.
+    if copysign(1.0, f32) < 0.0:
+        s =  1
+
+    # To reduce the number of cases in the if-tree below, decompose `abs(f32)`
+    # rather than `f32`.
+    (F, E) = frexp(fabs(f32))
+
+    # The output of frexp falls into three classes:
+    #   - If f32 is NaN, then F is NaN .
+    #   - If f32 is ±inf, then F is ±inf .
+    #   - If f32 is ±0.0, then F is ±0.0 .
+    #   - Otherwise, f32 = 2^E * F where 0.5 <= F < 1.0 .
+    #
+    # Since we decomposed `abs(f32)`, we only need be concerned with the
+    # postive cases.
+    if isnan(F):
+        # The resultant float16 is NaN.
+        e = 31
+        m = 1
+    elif isinf(F):
+        # The resultant float16 is infinite.
+        e = 31
+        m = 0
+    elif F == 0:
+        # f32 is zero, therefore the resultant float16 is zero.
+        e = 0
+        m = 0
+    elif E < -13:
+        # f32 lies in the range (0.0, min_normal16). Round f32 to a nearby
+        # float16 value. The resultant float16 will be either zero, subnormal,
+        # or normal.
+        e = 0
+        m = int(func_opts.round(2**(E + 24) * F))
+    elif E <= 16:
+        # f32 lies in the range [min_normal16, max_normal16 + max_step16).
+        # Round f32 to a nearby float16 value. The resultant float16 will be
+        # either normal or infinite.
+        e = int(E + 14)
+        m = int(func_opts.round(2**11 * F - 2**10))
+    else:
+        # f32 lies in the range [max_normal16 + max_step16, inf), which is
+        # outside the range of finite float16 values. The resultant float16 is
+        # infinite.
+        e = 31
+        m = 0
+
+    if (m == 1024):
+        # f32 was rounded upwards into the range of the next exponent.  This
+        # correctly handles the case where f32 should be rounded up to float16
+        # infinity.
+        e += 1
+        m = 0
+
+    assert(s == 0 or s == 1)
+    assert(0 <= e and e <= 31)
+    assert(0 <= m and m <= 1023)
+
+    return uint16((s << 15) | (e << 10) | m)
+
+def unpack_half_1x16(u16):
+    """Component-wise function of unpackHalf2x16."""
+    assert(isinstance(u16, uint16))
+
+    # The bit layout of a float16 is:
+    #
+    #   sign:     15
+    #   exponent: 10:14
+    #   mantissa: 0:9
+    #
+    # The sign, exponent, and mantissa determine its value by:
+    #
+    #  if e = 0 and m = 0, then zero:       (-1)^s * 0
+    #  if e = 0 and m != 0, then subnormal: (-1)^s * 2^(e - 14) * m / 2^10
+    #  if 0 < e < 31, then normal:          (-1)^s * 2^(e - 15) * (1 + m / 2^10)
+    #  if e = 31 and m = 0, then inf:       (-1)^s * inf
+    #  if e = 31 and m != 0, then nan
+    #
+    # where 0 <= m < 2^10.
+
+    s = (u16 >> 15) & 0x1
+    e = (u16 >> 10) & 0x1f
+    m = u16 & 0x3ff
+
+    if s == 0:
+        sign = 1.0
+    else:
+        sign = -1.0
+
+    if e == 0:
+        return float32(sign * 2.0**(-14) * (m / 2.0**10))
+    elif 1 <= e and e <= 30:
+        return float32(sign * 2.0**(e - 15.0) * (1.0 +  m / 2.0**10))
+    elif e == 31 and m == 0:
+        return float32(sign * float32("inf"))
+    elif e == 31 and m != 0:
+        return float32("NaN")
+    else:
+        assert(False)
+
+# ----------------------------------------------------------------------------
+# Inputs for GLSL functions
+# ----------------------------------------------------------------------------
+
+# This table maps GLSL pack/unpack function names to a sequence of inputs to
+# the respective component-wise function. It contain two types of mappings:
+#    - name of a pack2x16 function to a sequence of float32
+#    - name of a unpack2x16 function to a sequence of uint16
+input_table = dict()
+
+def make_inputs_for_pack_snorm_2x16():
+    # The domain of packSnorm2x16 is [-inf, +inf]^2. The function clamps
+    # its input into the range [-1, +1]^2.
+    #
+    # We test -0.0 in order to stress the implementation's handling of zero.
+    # The implementation should return a uint16 that encodes -0.0; that is,
+    # a uint16 # with the sign bit set.
+    pos = (
+        0.0, # zero
+        0.1, # near zero
+        0.9, # slightly below the clamp boundary
+        1.0, # the clamp boundary
+        1.1, # slightly above the clamp boundary
+        float("+inf"),
+        )
+    neg = tuple(reversed(tuple(-x for x in pos)))
+    return tuple(float32(x) for x in pos + neg)
+
+input_table["packSnorm2x16"] = make_inputs_for_pack_snorm_2x16()
+
+# XXX: Perhaps there is a better choice of test inputs?
+input_table["unpackSnorm2x16"] = tuple(uint16(u) for u in (
+    0, 1, 2, 3,
+    2**15 - 1,
+    2**15,
+    2**15 + 1,
+    2**16 - 1, # max uint16
+    ))
+
+input_table["packUnorm2x16"] = tuple(float32(x) for x in (
+    # The domain of packUnorm2x16 is [-inf, +inf]^2. The function clamps its
+    # input into the range [0, 1]^2.
+    #
+    # Below are listed important classes in the function's domain, and test
+    # inputs chosen from each class.
+    #   - zero: -0.0, 0.0
+    #   - just inside the clamp range: 0.1, 0.9
+    #   - on the clamp boundary: 0.0, 1.0
+    #   - just outside the clamp range: -0.1, 1.1
+    #   - infinity: -inf, +inf
+    #
+    # We test -0.0 in order to stress the implementation's handling of zero.
+    # The implementation should return a uint16 that encodes +0.0; that is,
+    # *without* the sign bit set.
+    "-inf",
+    -0.1, # slightly below the inner clamp boundary
+    -0.0, # infintesimally below the inner clamp boundary
+    +0.0, # the inner clamp boundary
+    +0.1, # slightly above the inner clamp boundary
+    +0.9, # slightly below the outer clamp boundary
+    +1.0, # the outer clamp boundary
+    +1.1, # slightly above the outer clamp boundary
+    "+inf",
+    ))
+
+# XXX: Perhaps there is a better choice of test inputs?
+input_table["unpackUnorm2x16"] = input_table["unpackSnorm2x16"]
+
+def make_inputs_for_pack_half_2x16():
+    # The domain of packHalf2x16 is ([-inf, +inf] + {NaN})^2. The function
+    # does not clamp its input.
+    #
+    # We test both -0.0 and +0.0 in order to stress the implementation's
+    # handling of zero.
+
+    subnormal_min = 2.0**(-14) * (1.0 / 2.0**10)
+    subnormal_max = 2.0**(-24) * (1023.0 / 2.0**10)
+    normal_min    = 2.0**(-14) * (1.0 + 0.0 / 2.0**10)
+    normal_max    = 2.0**15 * (1.0 + 1023.0 / 2.0**10)
+    min_step      = 2.0**(-24)
+    max_step      = 2.0**5
+
+    pos = tuple(float32(x) for x in (
+        # Inputs that result in 0.0 .
+        #
+        0.0,
+        0.0 + 0.25 * min_step,
+
+        # A thorny input...
+        #
+        # if round_to_even:
+        #   f16 := 0.0
+        # elif round_to_nearest:
+        #    f16 := subnormal_min
+        #
+        0.0 + 0.50 * min_step,
+
+        # Inputs that result in a subnormal float16.
+        #
+        0.0 + 0.75 * min_step,
+        subnormal_min + 0.00 * min_step,
+        subnormal_min + 0.25 * min_step,
+        subnormal_min + 0.50 * min_step,
+        subnormal_min + 0.75 * min_step,
+        subnormal_min + 1.00 * min_step,
+        subnormal_min + 1.25 * min_step,
+        subnormal_min + 1.50 * min_step,
+        subnormal_min + 1.75 * min_step,
+        subnormal_min + 2.00 * min_step,
+
+        normal_min - 2.00 * min_step,
+        normal_min - 1.75 * min_step,
+        normal_min - 1.50 * min_step,
+        normal_min - 1.25 * min_step,
+        normal_min - 1.00 * min_step,
+        normal_min - 0.75 * min_step,
+
+        # Inputs that result in a normal float16.
+        #
+        normal_min - 0.50 * min_step,
+        normal_min - 0.25 * min_step,
+        normal_min + 0.00 * min_step,
+        normal_min + 0.25 * min_step,
+        normal_min + 0.50 * min_step,
+        normal_min + 0.75 * min_step,
+        normal_min + 1.00 * min_step,
+        normal_min + 1.25 * min_step,
+        normal_min + 1.50 * min_step,
+        normal_min + 1.75 * min_step,
+        normal_min + 2.00 * min_step,
+
+        normal_max - 2.00 * max_step,
+        normal_max - 1.75 * max_step,
+        normal_max - 1.50 * max_step,
+        normal_max - 1.25 * max_step,
+        normal_max - 1.00 * max_step,
+        normal_max - 0.75 * max_step,
+        normal_max - 0.50 * max_step,
+        normal_max - 0.25 * max_step,
+        normal_max + 0.00 * max_step,
+        normal_max + 0.25 * max_step,
+
+        # Inputs that result in infinity.
+        #
+        normal_max + 0.50 * max_step,
+        normal_max + 0.75 * max_step,
+        normal_max + 1.00 * max_step,
+
+        "+inf",
+    ))
+
+    neg = tuple(reversed([-x for x in pos]))
+    return neg + pos
+
+input_table["packHalf2x16"] = make_inputs_for_pack_half_2x16()
+
+def make_inputs_for_unpack_half_2x16():
+    # For each of the two classes of float16 values, subnormal and normalized,
+    # below are listed the exponent and mantissa of the class's boundary
+    # values some values slightly inside the bounds.
+    bounds = (
+        ( 0,    0), # zero
+
+        ( 0,    1), # subnormal_min
+        ( 0,    2), # subnormal_min + min_step
+
+        ( 0, 1022), # subnormal_max - min_step
+        ( 0, 1023), # subnormal_max
+
+        ( 1,    0), # normal_min
+        ( 1,    1), # normal_min + min_step
+
+        (30, 1022), # normal_max - max_step
+        (30, 1023), # normal_max
+
+        (31,    0), # inf
+    )
+
+    def make_uint16(s, e, m):
+        return uint16((s << 15) | (e << 10) | m)
+
+    pos = tuple(make_uint16(0, e, m) for (e, m) in bounds)
+    neg = tuple(make_uint16(1, e, m) for (e, m) in reversed(bounds))
+    return neg + pos
+
+input_table["unpackHalf2x16"] = make_inputs_for_unpack_half_2x16()
+
+# ----------------------------------------------------------------------------
+# Expected outputs for GLSL functions
+# ----------------------------------------------------------------------------
+
+# For a given input to a GLSL function, InOutTuple lists all valid outputs.
+#
+# There are multiple types of InOutTuple, described below. In each
+# description, the numerical types actually refer to strings that represent
+# a GLSL literal of that type.
+#
+#   - That for a pack2x16 function: the input is a 2-tuple of float32 and each
+#     output is a uint32. For example, ``InOutTuple(input=("0.0", "0.0"),
+#     valid_outputs=("0u", "0u", "0u"))``.
+#
+#   - That for a unpack2x16 function: the input is a uint32 and each output is
+#     a 2-tuple of float32. For example, ``InOutTuple(input="0x80000000u",
+#     valid_outputs=(("0.0", "-0.0"),))``.
+#
+InOutTuple = namedtuple("InOutTuple", ("input", "valid_outputs"))
+
+def glsl_literal(x):
+    """Convert the given number to a string that represents a GLSL literal.
+
+    :param x: a uint32 or float32
+    """
+    if isinstance(x, uint32):
+        return "{0}u".format(uint32(x))
+    elif isinstance(x, float32):
+        if math.isnan(x):
+            # GLSL ES 3.00 and GLSL 4.10 do not require implementations to
+            # support NaN, so we do not test it.
+            assert(False)
+        elif math.isinf(x):
+            # GLSL ES 3.00 lacks a literal for infinity. However, ±1.0e256
+            # suffices because it lies sufficientlyoutside the range of finite
+            # float32 values.
+            #
+            #  From page 31 of the GLSL ES 3.00 spec:
+            #
+            #   If the value of the floating point number is too large (small)
+            #   to be stored as a single precision value, it is converted to
+            #   positive (negative) infinity.
+            #
+            return repr(copysign(1.0e256, x))
+        elif x == 0 and copysign(1.0, x) == -1.0:
+            # Workaround for numpy-1.7.0, in which repr(float32(-0.0)) does
+            # not return a float literal.
+            # See https://github.com/numpy/numpy/issues/2935 .
+            return "-0.0"
+        else:
+            return repr(x)
+    else:
+        assert(False)
+
+def make_inouts_for_pack_2x16(pack_1x16_func, float32_inputs):
+    """Determine valid outputs for a given GLSL pack2x16 function.
+
+    :param pack_1x16_func: the component-wise function of the pack2x16
+        function
+    :param float32_inputs: a sequence of inputs to pack_1x16_func
+    :return: a sequence of InOutTuple
+    """
+    inout_seq = []
+
+    func_opt_seq = tuple(
+        FuncOpts(round_mode=r, flush_mode=f)
+        for r in (FuncOpts.ROUND_TO_EVEN,
+                  FuncOpts.ROUND_TO_NEAREST)
+        for f in (FuncOpts.FLUSH_NONE,
+                  FuncOpts.FLUSH_FLOAT32)
+        )
+
+    for y in float32_inputs:
+        for x in float32_inputs:
+            assert(isinstance(x, float32))
+
+            valid_outputs = []
+            for func_opts in func_opt_seq:
+                u32 = pack_2x16(pack_1x16_func, x, y, func_opts)
+                assert(isinstance(u32, uint32))
+                valid_outputs.append(glsl_literal(u32))
+
+            inout_seq.append(
+                InOutTuple(input=(glsl_literal(x), glsl_literal(y)),
+                           valid_outputs=valid_outputs))
+    return inout_seq
+
+def make_inouts_for_unpack_2x16(unpack_1x16_func, uint16_inputs):
+    """Determine expected outputs of a given GLSL unpack2x16 function.
+
+    :param unpack_1x16_func: the component-wise function of the unpack2x16
+        function
+    :param uint16_inputs: a sequence of inputs to unpack_1x16_func
+    :return: a sequence of InOutTuple
+    """
+    inout_seq = []
+
+    func_opt_seq = (
+        FuncOpts(flush_mode=FuncOpts.FLUSH_NONE),
+        FuncOpts(flush_mode=FuncOpts.FLUSH_FLOAT32),
+        )
+
+    for y in uint16_inputs:
+        for x in uint16_inputs:
+            assert(isinstance(x, uint16))
+            u32 = uint32((y << 16) | x)
+
+            valid_outputs = []
+            for func_opts in func_opt_seq:
+                vec2 = unpack_2x16(unpack_1x16_func, u32, func_opts)
+                assert(isinstance(vec2[0], float32))
+                assert(isinstance(vec2[1], float32))
+                valid_outputs.append((glsl_literal(vec2[0]),
+                                         glsl_literal(vec2[1])))
+
+            inout_seq.append(
+                InOutTuple(input=glsl_literal(u32),
+                           valid_outputs=valid_outputs))
+
+    return inout_seq
+
+# This table maps GLSL pack/unpack function names to the precision of their
+# return type.
+result_precision_table = {
+    "packSnorm2x16": "highp",
+    "packUnorm2x16": "highp",
+    "packHalf2x16":  "highp",
+
+    "unpackSnorm2x16": "highp",
+    "unpackUnorm2x16": "highp",
+    "unpackHalf2x16":  "mediump",
+    }
+
+# This table maps GLSL pack/unpack function names to a sequence of InOutTuple.
+inout_table = {
+    "packSnorm2x16": make_inouts_for_pack_2x16(pack_snorm_1x16, input_table["packSnorm2x16"]),
+    "packUnorm2x16": make_inouts_for_pack_2x16(pack_unorm_1x16, input_table["packUnorm2x16"]),
+    "packHalf2x16":  make_inouts_for_pack_2x16(pack_half_1x16,  input_table["packHalf2x16"]),
+
+    "unpackSnorm2x16": make_inouts_for_unpack_2x16(unpack_snorm_1x16, input_table["unpackSnorm2x16"]),
+    "unpackUnorm2x16": make_inouts_for_unpack_2x16(unpack_unorm_1x16, input_table["unpackUnorm2x16"]),
+    "unpackHalf2x16":  make_inouts_for_unpack_2x16(unpack_half_1x16,  input_table["unpackHalf2x16"]),
+    }
+
+# ----------------------------------------------------------------------------
+# Generate test files
+# ----------------------------------------------------------------------------
+
+class FuncInfo:
+    """Information for a GLSL pack/unpack function.
+
+    Properties
+    ----------
+    - name: Name of the GLSL function, such as "packSnorm2x16".
+
+    - dimension: Dimension of the GLSL function, such as "2x16".
+
+    - result_precision: Precision of the GLSL function's return type, such as
+      "highp".
+
+    - inout_seq: A sequence of InOutTuple.  The generated test file will test
+      all inputs listed in the sequence.
+
+    - num_valid_outputs: The number of valid outputs for each input of
+      self.inout_seq. (We assume that each input has the  same number of valid
+      outputs).
+    """
+
+    def __init__(self, name):
+        self.name = name
+        self.result_precision = result_precision_table[name]
+        self.inout_seq = inout_table[name]
+        self.num_valid_outputs = len(self.inout_seq[0].valid_outputs)
+
+        if name.endswith("2x16"):
+            self.dimension = "2x16"
+        elif name.endswith("4x8"):
+            self.dimension =  "4x8"
+        else:
+            assert(False)
+
+class ShaderTest:
+    """A .shader_test file."""
+
+    @staticmethod
+    def all_tests():
+        funcs = (
+            FuncInfo("packSnorm2x16"),
+            FuncInfo("packUnorm2x16"),
+            FuncInfo("packHalf2x16"),
+
+            FuncInfo("unpackSnorm2x16"),
+            FuncInfo("unpackUnorm2x16"),
+            FuncInfo("unpackHalf2x16"),
+            )
+
+        execution_stages = (
+            "const",
+            "vs",
+            "fs",
+            )
+
+        for s in execution_stages:
+            for f in funcs:
+                yield ShaderTest(f, s)
+
+    def __init__(self, func_info, execution_stage):
+        assert(isinstance(func_info, FuncInfo))
+        assert(execution_stage in ("const", "vs", "fs"))
+
+        self.__template = template_table[(execution_stage,
+                                          func_info.name[0],
+                                          func_info.dimension)]
+        self.__func_info = func_info
+        self.__filename = os.path.join(
+                            "spec",
+                            "glsl-es-3.00",
+                            "execution",
+                            "built-in-functions",
+                            "{0}-{1}.shader_test"\
+                            .format(execution_stage, func_info.name))
+
+    @property
+    def filename(self):
+        return self.__filename
+
+    def write_file(self):
+        dirname = os.path.dirname(self.filename)
+        if not os.path.exists(dirname):
+            os.makedirs(dirname)
+
+        with open(self.filename, "w") as buffer:
+            ctx = mako.runtime.Context(buffer, func=self.__func_info)
+            self.__template.render_context(ctx)
+
+def main():
+    parser = optparse.OptionParser(
+                description="Generate shader tests that test the built-in " + \
+                            "packing functions",
+                usage="usage: %prog [-h] [--names-only]")
+    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()
+
+    if len(args) != 0:
+        # User gave extra args.
+        parser.print_help()
+        sys.exit(1)
+
+    for test in ShaderTest.all_tests():
+        print(test.filename)
+
+        # Some test files take a long time to generate, so provide status
+        # updates to the user immediately.
+        sys.stdout.flush()
+
+        if not options.names_only:
+            test.write_file()
+
+if __name__ == '__main__':
+    main()
-- 
1.8.1.1



More information about the Piglit mailing list