[Piglit] [PATCH v2 1/5] arb_uniform_buffer_object: Random test generation infrastructure
Ian Romanick
idr at freedesktop.org
Thu Sep 25 13:36:40 PDT 2014
From: Ian Romanick <ian.d.romanick at intel.com>
This is the core of the random test generation infrastructure. This
Python script can be used stand-alone to generate fully random tests, or
it can be called from other Python code to generate tests in a more
directed manner. Examples of both uses are coming in future patches.
v2: More changes than you can shake a stick at. Many review comments
from Dylan incorporated. Some dead code removed. Some other
(significant) refactoring (iterate_all_block_members and friends). A
couple minor bug fixes (not checking offsets for shared layouts).
Signed-off-by: Ian Romanick <ian.d.romanick at intel.com>
---
generated_tests/random_ubo.py | 1825 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 1825 insertions(+)
create mode 100644 generated_tests/random_ubo.py
diff --git a/generated_tests/random_ubo.py b/generated_tests/random_ubo.py
new file mode 100644
index 0000000..33935e9
--- /dev/null
+++ b/generated_tests/random_ubo.py
@@ -0,0 +1,1825 @@
+#!/usr/bin/env python2
+
+# Copyright (c) 2014 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 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.
+
+import random
+import abc
+import collections
+import struct
+import sys
+from textwrap import dedent
+from mako.template import Template
+
+# Module-local global variable that tracks all of the structure types that
+# have been generated since ever.
+struct_types = dict()
+
+ALL130_TYPES = [
+ "float", "vec2", "vec3", "vec4",
+ "int", "ivec2", "ivec3", "ivec4",
+ "uint", "uvec2", "uvec3", "uvec4",
+ "bool", "bvec2", "bvec3", "bvec4",
+
+ "mat2", "mat2x3", "mat2x4",
+ "mat3x2", "mat3", "mat3x4",
+ "mat4x2", "mat4x3", "mat4"
+]
+
+DOUBLE_TYPES = [
+ "double", "dvec2", "dvec3", "dvec4",
+
+ "dmat2", "dmat2x3", "dmat2x4",
+ "dmat3x2", "dmat3", "dmat3x4",
+ "dmat4x2", "dmat4x3", "dmat4"
+]
+
+ALL400_TYPES = ALL130_TYPES + DOUBLE_TYPES
+
+# All known types, including the redundant NxN matrix types.
+ALL_TYPES = [ "mat2x2", "mat3x3", "mat4x4",
+ "dmat2x2", "dmat3x3", "dmat4x4"] + ALL400_TYPES
+
+def align(offset, alignment):
+ return ((offset + alignment - 1) / alignment) * alignment
+
+
+def isscalar(type):
+ """Return true if the type is a known scalar type from any GLSL version."""
+ return type in ["float", "bool", "int", "uint", "double"]
+
+
+def isvector(type):
+ """Return true if the type is a known vector type from any GLSL version."""
+ return type in ["vec2", "vec3", "vec4",
+ "ivec2", "ivec3", "ivec4",
+ "uvec2", "uvec3", "uvec4",
+ "bvec2", "bvec3", "bvec4",
+ "dvec2", "dvec3", "dvec4"]
+
+
+def ismatrix(type):
+ """Return true if the type is a known matrix type from any GLSL version."""
+ return type in ["mat2", "mat3", "mat4",
+ "mat2x2", "mat2x3", "mat2x4",
+ "mat3x2", "mat3x3", "mat3x4",
+ "mat4x2", "mat4x3", "mat4x4",
+ "dmat2", "dmat3", "dmat4",
+ "dmat2x2", "dmat2x3", "dmat2x4",
+ "dmat3x2", "dmat3x3", "dmat3x4",
+ "dmat4x2", "dmat4x3", "dmat4x4"]
+
+
+def isarray(type):
+ """Return true if the type name appears to be an array type."""
+ return "[" in type
+
+
+def isstructure(type):
+ """Return true if the type name appears to be a structure type.
+
+ This function works by process of elimination. If the named type is not a
+ scalar, vector, matrix, or array, then it must be a structure.
+
+ NOTE: If the type is an array-of-structure type, this function will return
+ false.
+ """
+ return not (isscalar(type) or isvector(type) or ismatrix(type) or
+ isarray(type))
+
+
+def vector_size(type):
+ """Return the number of elements in a vector type."""
+ if isvector(type):
+ return int(type[-1:])
+
+ raise Exception("Non-vector type {}".format(type))
+
+
+def matrix_dimensions(type):
+ """Return the dimensions of an array type.
+
+ The tuple returned is (columns, rows).
+ """
+ if not ismatrix(type):
+ raise Exception("Non-matrix type {}".format(type))
+
+ if "x" in type:
+ s = type[-3:].split("x")
+ return int(s[0]), int(s[1])
+ else:
+ d = int(type[-1:])
+ return d, d
+
+
+def array_elements(type):
+ """Return the number of elements in an array type.
+
+ For non-array types zero is returned.
+ """
+ if "[" not in type:
+ return 0
+
+ # Is there a better way to do this?
+ return int(type.split("[")[1].split("]")[0])
+
+
+def basic_machine_units(type):
+ """Return the size in 'basic machine units' of a scalar type."""
+ if type in ["float", "bool", "int", "uint"]:
+ return 4
+
+ if type == "double":
+ return 8
+
+ raise Exception("Non-scalar type {}".format(type))
+
+
+def array_base_type(type):
+ """Return the type of an element from an array type."""
+ if not isarray(type):
+ raise Exception("Non-array type {}".format(type))
+
+ # XXX This will need changes for arrays-of-arrays.
+ return type.split("[")[0]
+
+
+def vector_base_type(type):
+ """Return the type of an element from a vector."""
+ if not isvector(type):
+ raise Exception("Non-vector type {}".format(type))
+
+ if type[0] == 'v':
+ return "float"
+ elif type[0] == 'i':
+ return "int"
+ elif type[0] == 'u':
+ return "uint"
+ elif type[0] == 'b':
+ return "bool"
+ elif type[0] == 'd':
+ return "double"
+ else:
+ raise Exception("Unknown vector type {}".format(type))
+
+
+def iterate_structures(fields, types_seen=[], types_yielded=[]):
+ """Given a list of fields, yields the structures in the fields in proper
+ declaration order. Detects recurrsion in the types and raises an
+ exception.
+ """
+ for (type, name) in fields:
+ if isarray(type):
+ type = array_base_type(type)
+
+ if not isstructure(type):
+ continue
+
+ if type in types_seen:
+ raise Exception("Type recurrsion involving {}".format(type))
+
+ for t in iterate_structures(struct_types[type],
+ types_seen + [type],
+ types_yielded):
+ yield t
+
+ if type not in types_yielded:
+ types_yielded.append(type)
+ yield type
+
+
+def select_basic_type(types, names):
+ """Return a random type with a matching field name.
+
+ From the list of supplied types, picks a random type. Using supplied
+ unique_name_dict, a name is also chosen. The two are returned as a tuple.
+ """
+ t = random.choice(types)
+ return t, names.get_name(t)
+
+
+def generate_struct_of_basic_types(types, names):
+ """Return a sequence of random types with unique field names."""
+ return [select_basic_type(types, names)
+ for i in xrange(0, random.randint(1,12))]
+
+
+def generate_member_from_description(description, builtin_types, names):
+ """Generate a single block member for a list of required attributes.
+
+ The list of required attributes is a sequence of 'struct' and 'array'
+ perhaps terminated with the name of a GLSL basic type. In addition to any
+ required subfields, structures generated in the sequence will be populated
+ with a random selection of available GLSL types named in builtin_types.
+
+ A matrix orientation can also be listed as the first requirement.
+ However, that is not handled by this function, and it will be ignored.
+
+ The tuple returned is (member_type, member_name).
+ """
+ if len(description) == 0:
+ return select_basic_type(builtin_types, names)
+
+ item = description[0]
+ if item == "array":
+ base_type, name = generate_member_from_description(
+ description[1:],
+ builtin_types,
+ names)
+
+ # If we're making an array of something that can be "big," try to make
+ # the array a little smaller.
+
+ if ismatrix(base_type) or isarray(base_type) or isstructure(base_type):
+ size = random.choice([2, 3, 5, 7])
+ else:
+ size = random.choice([3, 5, 7, 11, 13])
+
+ t = "{}[{}]".format(base_type, size)
+ return t, name
+ elif item == "struct":
+ fields = generate_struct_of_basic_types(builtin_types, names)
+ random.shuffle(fields)
+
+ # Peek ahead. If the next item in the description is a built-in type,
+ # then all of the remaining items must be built-in types. Generate a
+ # list of these.
+
+ if len(description) > 1 and description[1] in ALL_TYPES:
+ required_fields = [generate_member_from_description([i],
+ builtin_types,
+ names)
+ for i in description[1:]]
+
+ else:
+ required_fields = [generate_member_from_description(
+ description[1:],
+ builtin_types,
+ names)]
+
+ # Pick a random spot in the list of "common" fields and insert all of
+ # the required fields there.
+
+ j = random.randint(0, len(fields))
+ f = fields[:j] + required_fields + fields[j:]
+
+ struct_name = "S{}".format(len(struct_types) + 1)
+ struct_types[struct_name] = f
+
+ field_name = names.get_name(struct_name)
+ return struct_name, field_name
+ elif item in ALL_TYPES:
+ return item, names.get_name(item)
+ elif item in ["row_major", "column_major", "#column_major"]:
+ # While "row_major" and "column_major" are valid requirements, they
+ # are not processed here. Just skip over them for now.
+ return generate_member_from_description(description[1:],
+ builtin_types,
+ names)
+
+ raise Exception("Invalid UBO member description {}".format(item))
+
+
+def generate_ubo(description_list, builtin_types):
+ """Generate a uniform block for a list of requirements.
+
+ Each member of description_list is taken to be a sequence of requirements
+ for a single field in the uniform block. These are processed by
+ generate_member_from_description.
+
+ In addition to the list of required fields, the uniform block is also
+ populated with a random selection of available GLSL types named in
+ builtin_types.
+ """
+ layouts = dict()
+ names = unique_name_dict()
+
+ fields = []
+
+ for desc in description_list:
+ m = generate_member_from_description(desc, builtin_types, names)
+ fields.append(m)
+
+ if desc[0] in ["row_major", "column_major", "#column_major"]:
+ layouts[m[1]] = desc[0]
+
+ fields.extend(generate_struct_of_basic_types(builtin_types, names))
+ random.shuffle(fields)
+
+ required_layouts = []
+ for (field_type, field_name) in fields:
+ if field_name in layouts:
+ required_layouts.append(layouts[field_name])
+ else:
+ required_layouts.append(None)
+
+ return fields, required_layouts
+
+
+def generate_layouts(fields, required_layouts, allow_row_major_structure):
+ """Generate a directed random sequence of layout qualifiers.
+
+ Elements in requred_layouts that are non-None will have the same value in
+ the final sequence. Remaining elements matching entries in fields that
+ are either structure or matrix types will have random layouts assigned.
+
+ If allow_row_major_structure is not set, only matrix types will have
+ random layout assignments. Explicit layouts in required_layouts are
+ unaffected.
+ """
+ if required_layouts is None:
+ required_layouts = [None] * len(fields)
+
+ layouts = []
+ for ((type, name), lay) in zip(fields, required_layouts):
+ if isarray(type):
+ type = array_base_type(type)
+
+ if lay:
+ layouts.append(lay)
+ elif isstructure(type) and not allow_row_major_structure:
+ # This would work-around a bug in NVIDIA closed source drivers.
+ # They do not propogate row-major down into structures.
+
+ layouts.append("#column_major")
+ elif ismatrix(type) or isstructure(type):
+ # Choose a random matrix orientation. The #column_major are
+ # ignored when the UBO is emitted, but when a the UBO is
+ # re-emitted with a default row-major layout, these become
+ # "column_major".
+
+ layouts.append(random.choice(["#column_major",
+ "#column_major",
+ "#column_major",
+ "row_major",
+ "row_major",
+ "column_major"]))
+ else:
+ layouts.append("")
+ return layouts
+
+
+def relayout_for_default_row_major(l):
+ """Return a new layout for an assumed default layout of row-major."""
+ if l == "row_major":
+ return "#row_major"
+ elif l == "column_major" or l == "#column_major":
+ return "column_major"
+ elif l == "":
+ return ""
+ else:
+ raise Exception("Invalid layout {}".format(l))
+
+
+def fields_to_glsl_struct(type):
+ """From a name structure type, generate a GLSL definition of that
+ structure.
+ """
+ # The longest type name will have the form 'dmatCxR[##]' for 11
+ # characters. Use this to set the spacing between the field type and the
+ # field name.
+
+ structure_template = Template(dedent("""\
+ struct ${struct_name} {
+ % for (field_type, field_name) in fields:
+ ${"{:<11}".format(field_type)} ${field_name};
+ % endfor
+ };
+ """))
+
+ return structure_template.render(struct_name=type, fields=struct_types[type])
+
+
+def iterate_all_recursive(fields,
+ field_layouts,
+ name_from_API_base,
+ name_from_shader_base,
+ packing,
+ offset,
+ row_major):
+ """In-order recursive iteration of all subfields of a structure type
+
+ fields -- List of fields in the uniform block or structure
+ field_layouts -- Layout for each field in the uniform block (None for
+ structures).
+ name_from_API_base -- Base name of the member as it would be accessed via
+ the GL API.
+ name_from_shader_base -- Base name of the member as it would be accessed via
+ the GLSL shader.
+ packing -- Packing rules used to layout the type
+ offset -- Aligned base offset of the member
+ row_major -- True if the current matrix orientation is row-major
+
+ At each step of the iteration will yield a block_member object
+ representing the member.
+
+ Basic GLSL types will be visited once. Arrays of basic GLSL types will
+ also be visited once.
+
+ A structure will be visited once, then each field in the structure will be
+ visited.
+
+ An array of structures will be visited once, then each element of the
+ array will be visited. Since each element is a structure, each field will
+ be visited.
+
+ Consider the following structure:
+
+ struct S2 {
+ int i;
+ float f[2];
+ };
+
+ struct S1 {
+ S2 s[2];
+ };
+
+ If name_from_API_base is "x", the members would be visited in the
+ following order: "x.s", "x.s[0]", "x.s[0].i", "x.s[0].f[0]", "x.s[1]",
+ "x.s[1].i", "x.s[1].f[0]".
+
+ See also iterate_all_block_members.
+ """
+ if field_layouts is None:
+ field_layouts = [""] * len(fields)
+
+ if len(name_from_shader_base) > 0:
+ fmt = "{base}.{field}"
+ else:
+ fmt = "{field}"
+
+ for ((field_type, field_name), explicit_layout) in zip(fields,
+ field_layouts):
+ name_from_shader = fmt.format(
+ base=name_from_shader_base,
+ field=field_name)
+ name_from_API = fmt.format(
+ base=name_from_API_base,
+ field=field_name)
+
+ if explicit_layout == "row_major":
+ field_row_major = True
+ elif explicit_layout == "column_major":
+ field_row_major = False
+ else:
+ field_row_major = row_major
+
+ if isarray(field_type):
+ base_type = array_base_type(field_type)
+
+ if isstructure(base_type):
+ yield block_member(
+ name_from_shader,
+ name_from_API,
+ field_type,
+ explicit_layout,
+ offset,
+ field_row_major)
+
+ astride = packing.array_stride(field_type, field_row_major)
+ array_member_align = packing.base_alignment(
+ field_type,
+ field_row_major)
+
+ for i in xrange(array_elements(field_type)):
+ name_from_API_with_index = "{}[{}]".format(
+ name_from_API,
+ i)
+ name_from_shader_with_index = "{}[{}]".format(
+ name_from_shader,
+ i)
+
+ o = align(offset, array_member_align) + (astride * i)
+
+ yield block_member(
+ name_from_shader_with_index,
+ name_from_API_with_index,
+ base_type,
+ explicit_layout,
+ o,
+ field_row_major)
+
+ for x in iterate_all_recursive(struct_types[base_type],
+ None,
+ name_from_API_with_index,
+ name_from_shader_with_index,
+ packing,
+ o,
+ field_row_major):
+ yield x
+
+ a = packing.base_alignment(x.GLSL_type, row_major)
+ o = align(o, a) + packing.size(x.GLSL_type, row_major)
+
+ elif ismatrix(base_type):
+ yield block_member(
+ name_from_shader,
+ name_from_API,
+ field_type,
+ explicit_layout,
+ offset,
+ field_row_major)
+ else:
+ yield block_member(
+ name_from_shader,
+ name_from_API,
+ field_type,
+ "",
+ offset,
+ False)
+ elif isstructure(field_type):
+ yield block_member(
+ name_from_shader,
+ name_from_API,
+ field_type,
+ explicit_layout,
+ offset,
+ field_row_major)
+
+ a = packing.base_alignment(field_type, field_row_major)
+
+ for x in iterate_all_recursive(struct_types[field_type],
+ None,
+ name_from_API,
+ name_from_shader,
+ packing,
+ align(offset, a),
+ field_row_major):
+ yield x
+
+ elif ismatrix(field_type):
+ yield block_member(
+ name_from_shader,
+ name_from_API,
+ field_type,
+ explicit_layout,
+ offset,
+ field_row_major)
+ elif isvector(field_type) or isscalar(field_type):
+ yield block_member(
+ name_from_shader,
+ name_from_API,
+ field_type,
+ "",
+ offset,
+ False)
+ else:
+ raise Exception("Malformed type name {}".format(field_type))
+
+ a = packing.base_alignment(field_type, field_row_major)
+ offset = align(offset, a) + packing.size(field_type, field_row_major)
+
+
+def iterate_all_block_members(fields,
+ field_layouts,
+ block_name,
+ instance_name,
+ packing,
+ row_major):
+ """In-order recursive iteration of all subfields of a block
+
+ fields -- List of fields in the uniform block
+ field_layouts -- Layout for each field in the uniform block
+ block_name - Name of the uniform block
+ instance_name - Instance name associated with the block. May be None.
+ packing -- Packing rules used to layout the type
+ row_major -- True if the default block matrix orientation is row-major
+
+ At each step of the iteration will yield a block_member object
+ representing the member.
+
+ Basic GLSL types will be visited once. Arrays of basic GLSL types will
+ also be visited once.
+
+ A structure will be visited once, then each field in the structure will be
+ visited.
+
+ An array of structures will be visited once, then each element of the
+ array will be visited. Since each element is a structure, each field will
+ be visited.
+
+ Consider the following block:
+
+ struct S {
+ int i;
+ float f[2];
+ };
+
+ uniform U {
+ S s[2];
+ };
+
+ The members would be visited in the following order: s, s[0], s[0].i,
+ s[0].f[0], s[1], s[1].i, s[1].f[0].
+ """
+ for x in iterate_all_recursive(fields,
+ field_layouts,
+ block_name,
+ instance_name,
+ packing,
+ 0,
+ row_major):
+ yield x
+
+
+def hash_string(string):
+ """The djb2 string hash algorithm from the old comp.lang.c days. Not a
+ terrific hash, but we just need a pseudorandom number based on the string.
+ This will do.
+
+ This is used instead of the built-in hash function so that the results
+ will be consistent on other systems. See
+ http://stackoverflow.com/questions/793761/built-in-python-hash-function.
+ """
+ h = 5381
+
+ for c in string:
+ h = h * 33 + ord(c)
+
+ return h & 0x0ffffffff
+
+
+def random_data(type, name, offset):
+ """Generate pseudorandom data.
+
+ The data generated is based on the type, name of the field, and offset of
+ the member in the UBO.
+ """
+ if isscalar(type):
+ h = hash_string("{}@{}".format(offset, name))
+
+ if type == "int":
+ return str(h - 0x7fffffff)
+ elif type == "uint":
+ return str(h)
+ elif type == "bool":
+ return str(int((h & 8) == 0))
+ elif type == "float" or type == "double":
+ return str(float(h - 0x7fffffff) / 65535.0)
+ else:
+ raise Exception("Unknown scalar type {}".format(type))
+
+ if isvector(type):
+ scalar = vector_base_type(type)
+
+ x = [random_data(scalar, name, offset + (i * 3))
+ for i in xrange(vector_size(type))]
+ return " ".join(x)
+
+ if ismatrix(type):
+ r, c = matrix_dimensions(type)
+
+ x = [random_data("float", name, offset + (i * 7))
+ for i in xrange(r * c)]
+ return " ".join(x)
+
+
+def generate_test_vectors(fields,
+ field_layouts,
+ block_name,
+ instance_name,
+ packing,
+ row_major):
+ test_vectors = []
+
+ for m in iterate_all_block_members(fields,
+ field_layouts,
+ block_name,
+ instance_name,
+ packing,
+ row_major):
+ a = packing.base_alignment(m.GLSL_type, m.row_major)
+
+ if isarray(m.GLSL_type):
+ base_type = array_base_type(m.GLSL_type)
+ astride = packing.array_stride(m.GLSL_type, m.row_major)
+ name = m.API_name + "[0]"
+ else:
+ base_type = m.GLSL_type
+ astride = 0
+ name = m.API_name
+
+ if ismatrix(base_type):
+ test_vectors.append((
+ name,
+ m.API_type,
+ m.size,
+ align(m.offset, a),
+ astride,
+ packing.matrix_stride(base_type, m.row_major),
+ int(m.row_major)))
+ elif isvector(base_type) or isscalar(base_type):
+ test_vectors.append((
+ name,
+ m.API_type,
+ m.size,
+ align(m.offset, a),
+ astride,
+ 0,
+ 0))
+
+ return test_vectors
+
+
+def scalar_derp(type, name, offset, data):
+ """Return a GLSL code string to compare a scalar with its expected value."""
+ if type == "bool":
+ if int(data) == 0:
+ return name
+ else:
+ return "!" + name
+ elif type == "uint":
+ return "{} != {}u".format(name, data)
+ elif type == "int":
+ return "{} != {}".format(name, data)
+ elif type == "float":
+ # Not all implementations support the bit-cast operators that are used
+ # to do bit-exact comparisons. For this reason float_match needs the
+ # float data and the bit-exact data.
+
+ bits = bit_exact_data(data, "float")
+ return "!float_match({}, {}, {}u)".format(name, data, bits)
+ elif type == "double":
+ bits = bit_exact_data(data, "double")
+
+ # 0xHHHHHHHHLLLLLLLL
+ # 012345678901234567
+
+ hi = "0x" + bits[2:9]
+ lo = "0x" + bits[10:17]
+
+ return "!double_match({}, uvec2({}, {}))".format(name, lo, hi)
+ else:
+ raise Exception("Unknown scalar type {}".format(type))
+
+
+def vector_derp(type, name, offset, data):
+ """Return a list of GLSL code strings that compare each field of a vector
+ its expected value.
+ """
+ scalar = vector_base_type(type)
+ components = ["x", "y", "z", "w"]
+
+ return [scalar_derp(scalar,
+ "{}.{}".format(name, "xyzw"[i]),
+ offset,
+ data[i])
+ for i in xrange(vector_size(type))]
+
+
+def matrix_derp(type, name, offset, data):
+ """Return a list of GLSL code strings that compare each field of a matrix
+ its expected value.
+ """
+ c, r = matrix_dimensions(type)
+
+ if type[0] == 'd':
+ column_type = "dvec{}".format(r)
+ else:
+ column_type = "vec{}".format(r)
+
+ data_pairs = []
+
+ for i in xrange(c):
+ data_pairs.extend(vector_derp(
+ column_type,
+ "{}[{}]".format(name, i),
+ offset,
+ data[(i * r):(i * r) + r]))
+
+ return data_pairs
+
+
+def fudge_type_for_setter(type):
+ """Return the correct type for the GL API to set the uniform.
+
+ Most GLSL data types are set using a the enum name for the actual type.
+ The sole exception is bool and related vector types. For bool types, the
+ same-sized (scalar or vector) integer type is used.
+ """
+ if type[0] == 'b':
+ if type == "bool":
+ return "int"
+ else:
+ return "i" + type[1:]
+ else:
+ return type
+
+
+def bit_exact_data(raw_data, type):
+ """Several places in the test want bit-exact data for all types.
+ For integer types, this is just the raw data. For floating point (both
+ single and double precission) types, the bit-exact data is the hex
+ representation of the IEEE 754 encoding.
+ """
+ if type in ["float", "vec2", "vec3", "vec4",
+ "mat2", "mat2x2", "mat2x3", "mat2x4",
+ "mat3", "mat3x2", "mat3x3", "mat3x4",
+ "mat4", "mat4x2", "mat4x3", "mat4x4"]:
+ exact_data = []
+
+ for d in raw_data.split(" "):
+ p = struct.pack('!f', float(d))
+ u = struct.unpack('!I', p)[0]
+ exact_data.append(hex(u))
+
+ return " ".join(exact_data)
+ elif type in ["double", "dvec2", "dvec3", "dvec4",
+ "dmat2", "dmat2x2", "dmat2x3", "dmat2x4",
+ "dmat3", "dmat3x2", "dmat3x3", "dmat3x4",
+ "dmat4", "dmat4x2", "dmat4x3", "dmat4x4"]:
+ exact_data = []
+
+ for d in raw_data.split(" "):
+ p = struct.pack('!d', float(d))
+ u = struct.unpack('!Q', p)[0]
+
+ # Sometimes the hex() generates a spurious "L" at the end of the
+ # string. Only take the first 18 characters to omit the unwanted
+ # "L". I believe this occurs when bit 63 is set.
+
+ exact_data.append(hex(u)[0:18])
+
+ return " ".join(exact_data)
+ else:
+ return raw_data
+
+
+def generate_data_pairs(uniform_blocks, packing):
+ """Return GLSL code to test values and shader_runner code to set them.
+
+ For each element in each uniform block, two lists are generated, and these
+ lists are returned in a tuple of (checkers, setters). The "checkers" list
+ is a list of GLSL comparisons that return true if the contents of the
+ uniform block element DOES NOT match the expected value.
+
+ The "setters" list is a list of shader_runner "uniform" statements to set
+ each uniform block member to an expected value.
+ """
+ checkers = []
+ setters = []
+
+ for (block_name,
+ instance_name,
+ global_layout,
+ block_layout,
+ fields,
+ field_layouts) in uniform_blocks:
+ for m in iterate_all_block_members(
+ fields,
+ field_layouts,
+ block_name,
+ instance_name,
+ packing,
+ block_row_major_default(global_layout, block_layout)):
+
+ if m.API_type:
+ if isarray(m.GLSL_type):
+ base_type = array_base_type(m.GLSL_type)
+
+ astride = packing.array_stride(m.GLSL_type, m.row_major)
+
+ for i in xrange(array_elements(m.GLSL_type)):
+
+ name = "{}[{}]".format(m.GLSL_name, i)
+ offset = m.offset + (i * astride)
+
+ raw_data = random_data(base_type, m.GLSL_name, offset)
+ setters.append(
+ (fudge_type_for_setter(base_type),
+ "{}[{}]".format(m.API_name, i),
+ bit_exact_data(raw_data, base_type)))
+
+ data = raw_data.split(" ")
+
+ if isscalar(base_type):
+ checkers.append(scalar_derp(base_type,
+ name,
+ offset,
+ data[0]))
+ elif isvector(base_type):
+ checkers.extend(vector_derp(base_type,
+ name,
+ offset,
+ data))
+ elif ismatrix(base_type):
+ checkers.extend(matrix_derp(base_type,
+ name,
+ offset,
+ data))
+ else:
+ raw_data = random_data(m.GLSL_type, m.GLSL_name, m.offset)
+ setters.append((fudge_type_for_setter(m.GLSL_type),
+ m.API_name,
+ bit_exact_data(raw_data, m.GLSL_type)))
+
+ data = raw_data.split(" ")
+
+ if isscalar(m.GLSL_type):
+ checkers.append(scalar_derp(m.GLSL_type,
+ m.GLSL_name,
+ m.offset,
+ data[0]))
+ elif isvector(m.GLSL_type):
+ checkers.extend(vector_derp(m.GLSL_type,
+ m.GLSL_name,
+ m.offset,
+ data))
+ elif ismatrix(m.GLSL_type):
+ checkers.extend(matrix_derp(m.GLSL_type,
+ m.GLSL_name,
+ m.offset,
+ data))
+
+ return checkers, setters
+
+
+def pretty_format_type_data(packing, type, offset, row_major):
+ """Return a string describing the layout of the type
+
+ This string is intended to be a comment embedded in the GLSL shader code
+ that is generated.
+ """
+ a = packing.base_alignment(type, row_major)
+ aligned_offset = align(offset, a)
+ size = packing.size(type, row_major)
+
+ row_major_str = "-"
+ mstride = "-"
+ astride = "-"
+
+ if isarray(type):
+ astride = packing.array_stride(type, row_major)
+
+ base_type = array_base_type(type)
+ if ismatrix(base_type) and row_major:
+ if row_major:
+ row_major_str = "yes"
+ else:
+ row_major_str = "no"
+
+ mstride = packing.matrix_stride(base_type, row_major)
+ else:
+ if ismatrix(type):
+ if row_major:
+ row_major_str = "yes"
+ else:
+ row_major_str = "no"
+
+ mstride = packing.matrix_stride(type, row_major)
+
+ return "{base_align:>3} {base_offset:>4} {aligned_offset:>5} {padded_size:>6} {row_major:^5} {array_stride:>6} {matrix_stride:>6}".format(
+ base_align=a,
+ base_offset=offset,
+ aligned_offset=aligned_offset,
+ padded_size=size,
+ row_major=row_major_str,
+ array_stride=astride,
+ matrix_stride=mstride
+ )
+
+
+def pretty_format_member(m, packing):
+ """Return a string of GLSL code to declare the member.
+
+ m -- a block_member object
+ packing -- packing rules
+
+ If the member is "nested" inside a structure, the string returned will be
+ a comment simply describing the layout of the submember.
+ """
+ # If the name ends in an array subscript, emit a special line to note that
+ # the following fields are the contents of an element of an array of
+ # structures.
+
+ if m.GLSL_name[-1] == "]":
+ n = m.struct_nesting() + 1
+ indent = "// " + (" " * n)
+
+ return "{indent}[{index}".format(indent=indent,
+ index=m.GLSL_name.split("[")[-1])
+
+ # Strip off everything before the last period.
+ name = m.GLSL_name.split(".")[-1]
+
+ n = m.struct_nesting()
+ if n > 0:
+ indent = "// " + (" " * n)
+ field_str = "{indent}{type:<11} {name:<20}".format(
+ indent=indent,
+ type=m.GLSL_type,
+ name=name)[0:31]
+ else:
+ field_str = " {type:<11}{name};{padding}// ".format(
+ type=m.GLSL_type,
+ name=name,
+ padding=" "[len(name):])
+
+ data_str = pretty_format_type_data(
+ packing,
+ m.GLSL_type,
+ m.offset,
+ m.row_major)
+
+ # If there is an explicit layout for the member, prepend it to the member
+ # declaration. This also means that the member must be contained directly
+ # in the UBO (i.e., not nested in a struct), so no additional indentation
+ # is necessary.
+
+ if m.explicit_layout and "#" not in m.explicit_layout:
+ return " layout({layout})\n{field}{data}".format(
+ layout=m.explicit_layout,
+ field=field_str,
+ data=data_str)
+ else:
+ return "{field}{data}".format(field=field_str, data=data_str)
+
+
+def block_row_major_default(global_layout, block_layout):
+ """Return true if the default matrix layout for the block is row-major."""
+ row_major = global_layout is not None and "row_major" in global_layout
+
+ if block_layout:
+ if "row_major" in block_layout:
+ row_major = True
+ elif "column_major" in block_layout:
+ # The block layout can override a previous global layout.
+ row_major = False
+
+ return row_major
+
+
+def generate_block_list(glsl_version, packing, ubo_fields, layouts):
+ """Return list of uniform blocks to be passed to emit_shader_test.
+
+ The supplied ubo_fields and layouts are reformatted as an element in a
+ list of blocks that can be consumed by emit_shader_test. Depending on the
+ value of glsl_version, additional blocks may be added to the list. The
+ new blocks will have the same layout as the original, but the layout will
+ be specified in different ways (e.g., by using a global layout qualifier).
+ """
+
+ blocks = [("UB1", "", None, packing.layout_string(), ubo_fields, layouts)]
+
+ # If the GLSL version is at least 1.50, UBO functionality is significantly
+ # extended.
+ #
+ # 1. Uniform blocks can have instance names. The existence of the name
+ # changes the way the block is queried through the API (with the block
+ # name) and the way it is accessed by the shader (with the instance
+ # name).
+ #
+ # 2. Uniform blocks can be grouped in arrays. UBO arrays must have an
+ # instance name.
+ #
+ # This is used to make the tests dramatically more complex. Each UBO is
+ # emitted three times.
+ #
+ # 1. Without an instance name.
+ #
+ # 2. With an instance name and the per-block matrix layout switched to
+ # row_major. The declared layout of the individual fields is modified
+ # so that this block has the same layout as the previous block.
+ #
+ # 3. With an instance name and an array size. The per-block matrix layout
+ # is empty, but the global matrix layout is changed to row_major. This
+ # block should have the same layout as the previous two.
+
+ if glsl_version >= 150:
+ inverted_layouts = [relayout_for_default_row_major(l) for l in layouts]
+
+ blocks.append(("UB2",
+ "ub2",
+ None,
+ packing.layout_string() + ", row_major",
+ ubo_fields,
+ inverted_layouts))
+
+ blocks.append(("UB3",
+ "ub3",
+ # Disabled to work around Mesa bug #83508.
+ # "ub3[2]",
+ packing.layout_string() + ", row_major",
+ None,
+ ubo_fields,
+ inverted_layouts))
+
+ return blocks
+
+
+def emit_shader_test(blocks, packing, glsl_version, extensions):
+ structures = []
+ test_vectors = []
+
+ for (block_name,
+ instance_name,
+ global_layout,
+ block_layout,
+ fields,
+ field_layouts) in blocks:
+
+ structures.extend([s for s in iterate_structures(fields)])
+
+ test_vectors.extend(generate_test_vectors(
+ fields,
+ field_layouts,
+ block_name,
+ instance_name,
+ packing,
+ block_row_major_default(global_layout, block_layout)))
+
+
+ checkers, setters = generate_data_pairs(blocks, packing)
+
+ # If the GLSL version is at least 1.40, UBOs are already supported, and we
+ # don't need to enable the extension.
+
+ if glsl_version >= 140 and "GL_ARB_uniform_buffer_object" in extensions:
+ extensions.remove("GL_ARB_uniform_buffer_object")
+
+ t = Template(dedent("""\
+ [require]
+ GLSL >= ${glsl_version / 100}.${glsl_version % 100}
+ % for ext in extensions:
+ ${ext}
+ % endfor
+
+ # Do NOT edit the following lines.
+ # GLSL ${glsl_version}
+ # EXTENSIONS ${extensions}
+ # PACKING ${packing.layout_string()}
+ % for s in structures:
+ # STRUCT ("${s}", ${struct_types[s]})
+ % endfor
+ % for b in uniform_blocks:
+ # UBO ${b}
+ % endfor
+ # DATA END
+
+ [vertex shader]
+ % for ext in extensions:
+ #extension ${ext}: require
+ % endfor
+ #extension GL_ARB_shader_bit_encoding: enable
+ #extension GL_ARB_gpu_shader5: enable
+
+ precision highp float;
+ % for s in structures:
+
+ struct ${s} {
+ % for (field_type, field_name) in struct_types[s]:
+ ${"{:<11}".format(field_type)} ${field_name};
+ % endfor
+ };
+ % endfor
+
+ % for (block_name, instance_name, global_layout, block_layout, fields, field_layouts) in uniform_blocks:
+ % if global_layout:
+ layout(${global_layout}) uniform;
+
+ % endif
+ % if block_layout:
+ layout(${block_layout})
+ % endif
+ uniform ${block_name} {
+ // base base align padded row- array matrix
+ // align off. off. size major stride stride
+ % for m in iterate_all_block_members(fields, field_layouts, block_name, instance_name, packing, block_row_major_default(global_layout, block_layout)):
+ ${pretty_format_member(m, packing)}
+ % endfor
+ } ${instance_name};
+ % endfor
+
+ flat out int vertex_pass;
+ in vec4 piglit_vertex;
+
+ #if defined(GL_ARB_shader_bit_encoding) || defined(GL_ARB_gpu_shader5) || __VERSION__ >= 430
+ bool float_match(float u, float f, uint bits) { return floatBitsToUint(u) == bits; }
+ #else
+ bool float_match(float u, float f, uint bits) { return u == f; }
+ #endif
+ % if glsl_version >= 400 or "GL_ARB_gpu_shader_fp64" in extensions:
+
+ bool double_match(double u, uvec2 bits) { return unpackDouble2x32(u) == bits; }
+ %endif
+
+ void main()
+ {
+ /* std140 (or shared) layout prevents any fields or blocks from being
+ * eliminated. Section 2.11.6 of the OpenGL ES 3.0 spec makes this
+ * explicit, but desktop GL specs only imply it.
+ */
+ bool pass = true;
+
+ % for i in xrange(len(checkers)):
+ % if i % 5 == 0:
+ if (${checkers[i]})
+ pass = false;
+ % endif
+ % endfor
+
+ vertex_pass = int(pass);
+ gl_Position = piglit_vertex;
+ }
+
+ [fragment shader]
+ precision highp float;
+
+ out vec4 piglit_fragcolor;
+ flat in int vertex_pass;
+
+ void main()
+ {
+ piglit_fragcolor = bool(vertex_pass) ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
+ }
+
+ [test]
+ link success
+ % for (name, type, size, offset, astride, mstride, row_major) in test_vectors:
+
+ active uniform ${name} GL_UNIFORM_TYPE ${type}
+ active uniform ${name} GL_UNIFORM_SIZE ${size}
+ % if packing.fixed_offsets():
+ active uniform ${name} GL_UNIFORM_OFFSET ${offset}
+ active uniform ${name} GL_UNIFORM_ARRAY_STRIDE ${astride}
+ active uniform ${name} GL_UNIFORM_MATRIX_STRIDE ${mstride}
+ % endif
+ active uniform ${name} GL_UNIFORM_IS_ROW_MAJOR ${row_major}
+ % endfor
+
+ % for (type, name, data) in setters:
+ uniform ${type} ${name} ${data}
+ % endfor
+
+ draw rect -1 -1 2 2
+ probe all rgba 0.0 1.0 0.0 1.0"""))
+
+ return t.render(glsl_version=glsl_version,
+ extensions=extensions,
+ structures=structures,
+ test_vectors=test_vectors,
+ uniform_blocks=blocks,
+ packing=packing,
+ iterate_all_block_members=iterate_all_block_members,
+ pretty_format_member=pretty_format_member,
+ block_row_major_default=block_row_major_default,
+ struct_types=struct_types,
+ checkers=checkers,
+ setters=setters)
+
+
+def generate_file_name(requirements, packing):
+ """Based on the required UBO elements and the packing, generate a
+ plausible file name for the test case.
+ """
+ prefix = packing.layout_string() + "-"
+ suffix = ".shader_test"
+
+ body = "-and-".join(["-".join(req) for req in requirements])
+
+ return prefix + body + suffix
+
+
+class packing_rules(object):
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractproperty
+ def layout_string(self):
+ """Get the string used in a layout qualifier to select this set of
+ layout rules.
+ """
+
+ @abc.abstractproperty
+ def fixed_offsets(self):
+ """Return true if fields in this layout have fixed locations (e.g.,
+ std140) or false if they can vary among implementations (e.g., shared
+ or packed)?
+ """
+
+ @abc.abstractmethod
+ def base_alignment(self, type, row_major):
+ """Determine the base alignment, in bytes, of the named type"""
+
+ @abc.abstractmethod
+ def matrix_stride(self, type, row_major):
+ """Determine the stride, in bytes, from one indexable vector of the
+ matrix (column or row depending on the orientation) to the next.
+ """
+
+ @abc.abstractmethod
+ def array_stride(self, type, row_major):
+ """Determine the stride, in bytes, from one array element to the next.
+ If the type is not an array type, zero is returned.
+ """
+
+ def size(self, type, row_major):
+ """Determine the size, in bytes, of the specified type.
+
+ Applying the packing rules and the row-major setting, determine the
+ size, in bytes, of the entire data type. This will include padded
+ after the data element as required by the GLSL specification.
+ """
+ if "[" in type:
+ return self.array_stride(type, row_major) * array_elements(type)
+
+ if type in ["float", "bool", "int", "uint"]:
+ return 4
+
+ if type == "double":
+ return 8
+
+ if type in ["vec2", "bvec2", "ivec2", "uvec2"]:
+ return 2 * 4
+
+ if type == "dvec2":
+ return 2 * 8
+
+ if type in ["vec3", "bvec3", "ivec3", "uvec3"]:
+ return 3 * 4
+
+ if type == "dvec3":
+ return 3 * 8
+
+ if type in ["vec4", "bvec4", "ivec4", "uvec4"]:
+ return 4 * 4
+
+ if type == "dvec4":
+ return 4 * 8
+
+ if "mat" in type:
+ c, r = matrix_dimensions(type)
+ if not row_major:
+ return c * self.matrix_stride(type, row_major)
+ else:
+ return r * self.matrix_stride(type, row_major)
+
+ if type not in struct_types:
+ raise Exception("Unknown type {}".format(type))
+
+ s = 0
+ fields = struct_types[type]
+ for (t, n) in fields:
+ a = self.base_alignment(t, row_major)
+
+ s = align(s, a) + self.size(t, row_major)
+
+ s = align(s, self.base_alignment(type, row_major))
+ return s
+
+
+class std140_packing_rules(packing_rules):
+ def layout_string(self):
+ return "std140"
+
+ def fixed_offsets(self):
+ return True
+
+ def base_alignment(self, type, row_major):
+ # (4) If the member is an array of scalars or vectors, the base
+ # alignment and array stride are set to match the base alignment
+ # of a single array element, according to rules (1), (2), and (3),
+ # and rounded up to the base alignment of a vec4. The array may
+ # have padding at the end; the base offset of the member following
+ # the array is rounded up to the next multiple of the base
+ # alignment.
+
+ if isarray(type):
+ return max(16,
+ self.base_alignment(array_base_type(type), row_major))
+
+ # (1) If the member is a scalar consuming <N> basic machine units, the
+ # base alignment is <N>.
+
+ if isscalar(type):
+ return basic_machine_units(type)
+
+ if isvector(type):
+ # (2) If the member is a two- or four-component vector with
+ # components consuming <N> basic machine units, the base
+ # alignment is 2<N> or 4<N>, respectively.
+ #
+ # (3) If the member is a three-component vector with components
+ # consuming <N> basic machine units, the base alignment is
+ # 4<N>.
+
+ components = vector_size(type)
+ if components == 2 or components == 4:
+ return components * basic_machine_units(vector_base_type(type))
+ elif components == 3:
+ return 4 * basic_machine_units(vector_base_type(type))
+
+ raise Exception("Invalid vector size {} for type {}".format(
+ components,
+ type))
+ elif ismatrix(type):
+ return self.matrix_stride(type, row_major)
+
+ if type not in struct_types:
+ raise Exception("Unknown type {}".format(type))
+
+ # (9) If the member is a structure, the base alignment of the
+ # structure is <N>, where <N> is the largest base alignment value
+ # of any of its members, and rounded up to the base alignment of a
+ # vec4. The individual members of this sub-structure are then
+ # assigned offsets by applying this set of rules recursively,
+ # where the base offset of the first member of the sub-structure
+ # is equal to the aligned offset of the structure. The structure
+ # may have padding at the end; the base offset of the member
+ # following the sub-structure is rounded up to the next multiple
+ # of the base alignment of the structure.
+
+ a = 16
+ fields = struct_types[type]
+ for (field_type, field_name) in fields:
+ a = max(a, self.base_alignment(field_type, row_major))
+
+ return a
+
+ def matrix_stride(self, type, row_major):
+ c, r = matrix_dimensions(type)
+ if not row_major:
+ # (4) If the member is an array of scalars or vectors, the base
+ # alignment and array stride are set to match the base
+ # alignment of a single array element, according to rules (1),
+ # (2), and (3), and rounded up to the base alignment of a
+ # vec4. The array may have padding at the end; the base offset
+ # of the member following the array is rounded up to the next
+ # multiple of the base alignment.
+ #
+ # (5) If the member is a column-major matrix with <C> columns and
+ # <R> rows, the matrix is stored identically to an array of
+ # <C> column vectors with <R> components each, according to
+ # rule (4).
+
+ if type[0] == 'd':
+ return max(16, self.base_alignment("dvec{}".format(r), False))
+ else:
+ return max(16, self.base_alignment("vec{}".format(r), False))
+ else:
+ # (7) If the member is a row-major matrix with <C> columns and <R>
+ # rows, the matrix is stored identically to an array of <R>
+ # row vectors with <C> components each, according to rule (4).
+
+ if type[0] == 'd':
+ return max(16, self.base_alignment("dvec{}".format(c), False))
+ else:
+ return max(16, self.base_alignment("vec{}".format(c), False))
+
+ def array_stride(self, type, row_major):
+ base_type = array_base_type(type)
+
+ if not isstructure(base_type):
+ # (4) If the member is an array of scalars or vectors, the base
+ # alignment and array stride are set to match the base
+ # alignment of a single array element, according to rules (1),
+ # (2), and (3), and rounded up to the base alignment of a
+ # vec4. The array may have padding at the end; the base offset
+ # of the member following the array is rounded up to the next
+ # multiple of the base alignment.
+ return max(16,
+ max(self.base_alignment(base_type, row_major),
+ self.size(base_type, row_major)))
+ else:
+ # (9) If the member is a structure, the base alignment of the
+ # structure is <N>, where <N> is the largest base alignment
+ # value of any of its members, and rounded up to the base
+ # alignment of a vec4. The individual members of this
+ # sub-structure are then assigned offsets by applying this set
+ # of rules recursively, where the base offset of the first
+ # member of the sub-structure is equal to the aligned offset
+ # of the structure. The structure may have padding at the end;
+ # the base offset of the member following the sub-structure is
+ # rounded up to the next multiple of the base alignment of the
+ # structure.
+ #
+ # (10) If the member is an array of <S> structures, the <S> elements
+ # of the array are laid out in order, according to rule (9).
+
+ return align(self.size(base_type, row_major),
+ self.base_alignment(base_type, row_major))
+
+
+class shared_packing_rules(std140_packing_rules):
+ def layout_string(self):
+ return "shared"
+
+ def fixed_offsets(self):
+ return False
+
+
+class unique_name_dict:
+ """Helper class to generate a unique name for each field.
+
+ Fields for each particular type have names based on the type. Each name
+ is suffixed by a number, and the number is incremented each time a field
+ of that type is created. For example, the first int field will be named
+ "i1", and the second will be named "i2".
+ """
+
+ def __init__(self):
+ self.names = {}
+
+ def trim_name(self, type):
+ """Internal method to reduce a type name to a canonical form
+
+ These names are used to track the type in the self.names dict.
+
+ Array types have the arrayness stripped. Vector types have vector
+ count stripped. Matrix types are converted to canonical "mat#x#"
+ form. All other type names are unmodified.
+ """
+ if isarray(type):
+ t = array_base_type(type)
+ else:
+ t = type
+
+ if ismatrix(t):
+ # Canonicalize matrix type names.
+ c, r = matrix_dimensions(t)
+
+ name = "mat{}x{}".format(c, r)
+ if t[0] == "d":
+ name = "d" + name
+
+ return name
+ elif isscalar(t):
+ return t
+ elif isvector:
+ return t.strip("1234")
+ else:
+ # Assume it must be a structure.
+ return t
+
+ def add_type(self, type):
+ """Internal method to add a previously unknown type to the dictionary
+
+ type -- Actual GLSL type name
+
+ Generates the base name for fields of this type. Adds a tuple to dict
+ (indexed by "type") of (base_name, 1).
+ """
+ if isarray(type):
+ t = array_base_type(type)
+ else:
+ t = type
+
+ if isvector(t):
+ base = "{}v".format(vector_base_type(t)[0])
+ elif ismatrix(t):
+ c, r = matrix_dimensions(t)
+
+ if t[0] == 'd':
+ base = "dm{}{}_".format(c, r)
+ else:
+ base = "m{}{}_".format(c, r)
+ elif isscalar(t):
+ base = t[0]
+ elif t[0] == "S":
+ base = "s{}_".format(t[1:])
+ else:
+ raise Exception("Malformed type name {}".format(t))
+
+ self.names[self.trim_name(t)] = (base, 1)
+ return
+
+ def get_name(self, type):
+ """Return a unique name for a field of the specified type."""
+ t = self.trim_name(type)
+ if t not in self.names:
+ self.add_type(type)
+
+ base, count = self.names[t]
+ self.names[t] = (base, count + 1)
+
+ return "{}{}".format(base, count)
+
+
+class block_member(object):
+ """Helper class to track all of the layout information for a single
+ (sub-)member of a uniform block."""
+
+ TYPE_ENUM = {
+ 'float': "GL_FLOAT",
+ 'vec2': "GL_FLOAT_VEC2",
+ 'vec3': "GL_FLOAT_VEC3",
+ 'vec4': "GL_FLOAT_VEC4",
+
+ 'double': "GL_DOUBLE",
+ 'dvec2': "GL_DOUBLE_VEC2",
+ 'dvec3': "GL_DOUBLE_VEC3",
+ 'dvec4': "GL_DOUBLE_VEC4",
+
+ 'int': "GL_INT",
+ 'ivec2': "GL_INT_VEC2",
+ 'ivec3': "GL_INT_VEC3",
+ 'ivec4': "GL_INT_VEC4",
+
+ 'uint': "GL_UNSIGNED_INT",
+ 'uvec2': "GL_UNSIGNED_INT_VEC2",
+ 'uvec3': "GL_UNSIGNED_INT_VEC3",
+ 'uvec4': "GL_UNSIGNED_INT_VEC4",
+
+ 'bool': "GL_BOOL",
+ 'bvec2': "GL_BOOL_VEC2",
+ 'bvec3': "GL_BOOL_VEC3",
+ 'bvec4': "GL_BOOL_VEC4",
+
+ 'mat2': "GL_FLOAT_MAT2",
+ 'mat2x2': "GL_FLOAT_MAT2",
+ 'mat2x3': "GL_FLOAT_MAT2x3",
+ 'mat2x4': "GL_FLOAT_MAT2x4",
+
+ 'mat3': "GL_FLOAT_MAT3",
+ 'mat3x2': "GL_FLOAT_MAT3x2",
+ 'mat3x3': "GL_FLOAT_MAT3",
+ 'mat3x4': "GL_FLOAT_MAT3x4",
+
+ 'mat4': "GL_FLOAT_MAT4",
+ 'mat4x2': "GL_FLOAT_MAT4x2",
+ 'mat4x3': "GL_FLOAT_MAT4x3",
+ 'mat4x4': "GL_FLOAT_MAT4",
+
+ 'dmat2': "GL_DOUBLE_MAT2",
+ 'dmat2x2': "GL_DOUBLE_MAT2",
+ 'dmat2x3': "GL_DOUBLE_MAT2x3",
+ 'dmat2x4': "GL_DOUBLE_MAT2x4",
+
+ 'dmat3': "GL_DOUBLE_MAT3",
+ 'dmat3x2': "GL_DOUBLE_MAT3x2",
+ 'dmat3x3': "GL_DOUBLE_MAT3",
+ 'dmat3x4': "GL_DOUBLE_MAT3x4",
+
+ 'dmat4': "GL_DOUBLE_MAT4",
+ 'dmat4x2': "GL_DOUBLE_MAT4x2",
+ 'dmat4x3': "GL_DOUBLE_MAT4x3",
+ 'dmat4x4': "GL_DOUBLE_MAT4"
+ }
+
+ def __init__(self,
+ GLSL_name,
+ API_name,
+ GLSL_type,
+ explicit_layout,
+ offset,
+ row_major):
+ self.GLSL_name = GLSL_name
+ self.GLSL_type = GLSL_type
+
+ self.API_name = API_name
+
+ self.explicit_layout = explicit_layout
+ self.offset = offset
+ self.row_major = row_major
+
+ if isarray(GLSL_type):
+ base_type = array_base_type(GLSL_type)
+
+ if isstructure(base_type):
+ self.API_type = None
+ else:
+ self.API_type = self.TYPE_ENUM[base_type];
+
+ self.size = array_elements(GLSL_type)
+ elif isstructure(GLSL_type):
+ self.API_type = None
+ self.size = 1
+ else:
+ self.API_type = self.TYPE_ENUM[GLSL_type];
+ self.size = 1
+
+ def struct_nesting(self):
+ if "." in self.GLSL_name:
+ # If the block has an instance name, the API name will use the
+ # block name instead of the instance name. As a result,
+ # GLSL_name and API_name will be different.
+ #
+ # The first "." is for the block instance name, so it does not count
+ # as structure nesting.
+
+ if self.GLSL_name != self.API_name:
+ return collections.Counter(self.GLSL_name)["."] - 1
+ else:
+ return collections.Counter(self.GLSL_name)["."]
+ else:
+ return 0
+
+
+if __name__ == "__main__":
+ if len(sys.argv) > 1:
+ max_glsl_version = int(sys.argv[1])
+ else:
+ max_glsl_version = 130
+
+ if len(sys.argv) > 2:
+ extensions = sys.argv[2:]
+ else:
+ extensions = []
+
+ # Pick a random GLSL version from the available set of possible versions.
+ glsl_version = random.choice([v for v in [130, 140, 150, 400, 430]
+ if v <= max_glsl_version])
+
+ # Use the GLSL version filter out some extensions that are redundant.
+ if glsl_version >= 140 and "GL_ARB_uniform_buffer_object" in extensions:
+ extensions.remove("GL_ARB_uniform_buffer_object")
+
+ if glsl_version >= 400 and "GL_ARB_gpu_shader_fp64" in extensions:
+ extensions.remove("GL_ARB_gpu_shader_fp64")
+
+ if glsl_version >= 430 and "GL_ARB_arrays_of_arrays" in extensions:
+ extensions.remove("GL_ARB_arrays_of_arrays")
+
+ # Pick a random subset of the remaining extensions.
+ num_ext = len(extensions)
+ if num_ext > 0:
+ random.shuffle(extensions)
+ r = random.randint(0, num_ext)
+ extensions = extensions[:r]
+
+ # Based on the GLSL version and the set of extensions, pick the set of
+ # possible data types.
+ if glsl_version < 400:
+ types = ALL130_TYPES
+ else:
+ types = ALL400_TYPES
+
+ if "GL_ARB_gpu_shader_fp64" in extensions:
+ types.extend(DOUBLE_TYPES)
+
+ # Based on the GLSL version, pick a set of packing rules
+ # FINISHME: Add support for std430_packing_rules() soon.
+ packing = random.choice([std140_packing_rules(), shared_packing_rules()])
+
+ # Based on the GLSL version and the set of available extensions, pick
+ # some required combinations of data structures to include in the UBO.
+ arrays_of_arrays = (glsl_version >= 430 or
+ "GL_ARB_arrays_of_arrays" in extensions)
+
+ allow_row_major_structure = glsl_version >= 150
+
+ requirements = []
+ for i in [1, 2]:
+ x = [random.choice(["array", "struct"])]
+
+ for j in [1, 2, 3]:
+ # If arrays-of-arrays are not supported, don't allow "array" to be
+ # picked twice in a row.
+
+ if x[-1] == "array" and not arrays_of_arrays:
+ x.append("struct")
+ else:
+ x.append(random.choice(["array", "struct"]))
+
+ if "struct" in x and allow_row_major_structure:
+ ordering = random.choice([None,
+ None,
+ None,
+ None,
+ "column_major",
+ "#column_major",
+ "row_major",
+ "row_major"])
+ if ordering:
+ x = [ordering] + x
+
+ requirements.append(x)
+
+ if glsl_version < 140:
+ extensions.append("GL_ARB_uniform_buffer_object")
+
+ # Generate the test!
+ fields, required_layouts = generate_ubo(requirements, types)
+
+ layouts = generate_layouts(
+ fields,
+ required_layouts,
+ allow_row_major_structure)
+
+ blocks = generate_block_list(
+ glsl_version,
+ packing,
+ fields,
+ layouts)
+
+ print emit_shader_test(
+ blocks,
+ packing,
+ glsl_version,
+ extensions)
--
1.8.1.4
More information about the Piglit
mailing list