From: Ian Romanick <[email protected]> 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.
Signed-off-by: Ian Romanick <[email protected]> --- generated_tests/random_ubo.py | 1702 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1702 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..f47f2b8 --- /dev/null +++ b/generated_tests/random_ubo.py @@ -0,0 +1,1702 @@ +#!/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 mako.template import Template +from textwrap import dedent + +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 + +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 align(offset, alignment): + return ((offset + alignment - 1) / alignment) * alignment + +def array_elements(type): + if "[" not in type: + return 0 + + # Is there a better way to do this? + return int(type.split("[")[1].split("]")[0]) + +def matrix_dimensions(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) + +class packing_rules: + __metaclass__ = abc.ABCMeta + + @abc.abstractproperty + def layout_string(self): + """Get the string used in a layout qualifier to select this set of + layout rules.""" + return NotImplemented + + @abc.abstractproperty + def fixed_offsets(self): + """Do fields in this layout have fixed locations (e.g., std140) or can + they vary among implementations (e.g., shared or packed)?""" + return NotImplemented + + @abc.abstractmethod + def base_alignment(self, type, row_major): + """Determine the base alignment, in bytes, of the named type""" + return NotImplemented + + @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.""" + return NotImplemented + + @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.""" + return NotImplemented + + def size(self, type, row_major): + 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) + + global struct_types + if type not in struct_types: + raise BaseException("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 + + +def isscalar(type): + return type in ["float", "bool", "int", "uint", "double"] + + +def isvector(type): + return type in [ "vec2", "vec3", "vec4", + "ivec2", "ivec3", "ivec4", + "uvec2", "uvec3", "uvec4", + "bvec2", "bvec3", "bvec4", + "dvec2", "dvec3", "dvec4" ] + +def ismatrix(type): + 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 "[" in type + + +def isstructure(type): + return not (isscalar(type) or isvector(type) or ismatrix(type) or + isarray(type)) + + +def vector_size(type): + if isvector(type): + return int(type[-1:]) + + raise BaseException("Non-vector type {}".format(type)) + + +def basic_machine_units(type): + if type in ["float", "bool", "int", "uint"]: + return 4 + + if type == "double": + return 8 + + raise BaseException("Non-scalar type {}".format(type)) + + +def array_base_type(type): + if not isarray(type): + raise BaseException("Non-array type {}".format(type)) + + return type.split("[")[0] + + +def component_type(type): + if isscalar(type): + return type + elif isvector(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 BaseException("Unknown vector type {}".format(type)) + elif ismatrix(type): + # Should this return the vector type or the scalar type? + raise BaseException("Add support for matrix types when necessary.") + + raise BaseException("Invalid type {}. Perhaps a structure?".format(type)) + + +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(component_type(type)) + elif components == 3: + return 4 * basic_machine_units(component_type(type)) + + raise BaseException("Invalid vector size {} for type {}".format( + components, + type)) + elif ismatrix(type): + return self.matrix_stride(type, row_major) + + global struct_types + if type not in struct_types: + raise BaseException("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 + + +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.""" + + global struct_types + + for (type, name) in fields: + if isarray(type): + type = array_base_type(type) + + if not isstructure(type): + continue + + if type in types_seen: + raise BaseException("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 + + +class unique_name_dict: + def __init__(self): + self.names = {} + + def trim_name(self, type): + 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): + if isarray(type): + t = array_base_type(type) + else: + t = type + + if isvector(t): + base = "{}v".format(component_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 BaseException("Malformed type name {}".format(t)) + + self.names[self.trim_name(t)] = (base, 1) + return + + def get_name(self, 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) + +def select_basic_type(types, names): + t = random.choice(types) + return (t, names.get_name(t)) + +def generate_struct_of_basic_types(types, 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): + global struct_types + global all_types + + 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 BaseException("Invalid UBO member description {}".format(item)) + + +def generate_ubo(description_list, 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): + if required_layouts == 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 layout_invert_default(l): + if l == "row_major": + return "#row_major" + elif l == "column_major" or l == "#column_major": + return "column_major" + elif l == "": + return "" + else: + raise BaseException("Invalid layout {}".format(l)) + +def generate_layouts_for_default_row_major(layouts): + """Generate a new list of layouts that should be the same but assumes the + default matrix layout is row-major (instead of column-major).""" + return [layout_invert_default(l) for l in layouts] + + +def fields_to_glsl_struct(type): + global struct_types + + # 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_struct_fields(type, + name_from_API_base, + name_from_shader_base, + packing, + offset, + row_major): + global struct_types + + for (field_type, field_name) in struct_types[type]: + name_from_shader = "{}.{}".format(name_from_shader_base, field_name) + name_from_API = "{}.{}".format(name_from_API_base, field_name) + + 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, + "", + offset, + row_major) + + astride = packing.array_stride(field_type, row_major) + array_member_align = packing.base_alignment( + field_type, + 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, + "", + o, + row_major) + + for x in iterate_all_struct_fields(base_type, + name_from_API_with_index, + name_from_shader_with_index, + packing, + o, + 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, + "", + offset, + 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, + "", + offset, + row_major) + + a = packing.base_alignment(field_type, row_major) + + for x in iterate_all_struct_fields(field_type, + name_from_API, + name_from_shader, + packing, + align(offset, a), + row_major): + yield x + + elif ismatrix(field_type): + yield block_member( + name_from_shader, + name_from_API, + field_type, + "", + offset, + row_major) + else: + yield block_member( + name_from_shader, + name_from_API, + field_type, + "", + offset, + False) + + a = packing.base_alignment(field_type, row_major) + offset = align(offset, a) + packing.size(field_type, row_major) + +class block_member: + 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 = 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 = 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 + + def isscalar(self): + return isscalar(self.GLSL_type) + + def isvector(self): + return isscalar(self.GLSL_type) + + def ismatrix(self): + return isscalar(self.GLSL_type) + + def vector_size(self): + return vector_size(self.GLSL_type) + + def component_type(self): + return component_type(self.GLSL_type) + + +def iterate_all_block_members(fields, + field_layouts, + block_name, + instance_name, + packing, + row_major): + + offset = 0 + + if len(instance_name) > 0: + fmt = "{base}.{field}" + else: + fmt = "{field}" + + for ((field_type, field_name), l) in zip(fields, field_layouts): + name_from_shader = fmt.format(base=instance_name, field=field_name) + name_from_API = fmt.format(base=block_name, field=field_name) + + if l == "row_major": + field_row_major = True + elif l == "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, + l, + 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, + l, + o, + field_row_major) + + for x in iterate_all_struct_fields(base_type, + 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, + l, + 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, + l, + offset, + field_row_major) + + a = packing.base_alignment(field_type, field_row_major) + + for x in iterate_all_struct_fields(field_type, + 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, + l, + 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 BaseException("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 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.""" + + 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 BaseException("Unknown scalar type {}".format(type)) + + if isvector(type): + scalar = component_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) + + return None + + +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): + 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": + bits = fudge_data_for_setter(data, "float") + return "!float_match({}, {}, {}u)".format(name, data, bits) + elif type == "double": + bits = fudge_data_for_setter(data, "double") + + # 0xHHHHHHHHLLLLLLLL + # 012345678901234567 + + hi = "0x" + bits[2:9] + lo = "0x" + bits[10:17] + + return "!double_match({}, uvec2({}, {}))".format(name, lo, hi) + else: + raise BaseException("Unknown scalar type {}".format(type)) + + +def vector_derp(type, name, offset, data): + scalar = component_type(type) + components = [ "x", "y", "z", "w" ] + + return [scalar_derp(scalar, + "{}.{}".format(name, components[i]), + offset, + data[i]) + for i in xrange(vector_size(type))] + + +def matrix_derp(type, name, offset, data): + (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): + if type[0] == 'b': + if type == "bool": + return "int" + else: + return "i" + type[1:] + else: + return type + + +def fudge_data_for_setter(raw_data, type): + if type in ["float", "vec2", "vec3", "vec4", + "mat2", "mat2x2", "mat2x3", "mat2x4", + "mat3", "mat3x2", "mat3x3", "mat3x4", + "mat4", "mat4x2", "mat4x3", "mat4x4"]: + fudged_data = [] + + for d in raw_data.split(" "): + p = struct.pack('!f', float(d)) + u = struct.unpack('!I', p)[0] + fudged_data.append(hex(u)) + + return " ".join(fudged_data) + elif type in ["double", "dvec2", "dvec3", "dvec4", + "dmat2", "dmat2x2", "dmat2x3", "dmat2x4", + "dmat3", "dmat3x2", "dmat3x3", "dmat3x4", + "dmat4", "dmat4x2", "dmat4x3", "dmat4x4"]: + fudged_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. + + fudged_data.append(hex(u)[0:18]) + + return " ".join(fudged_data) + else: + return raw_data + + +def generate_data_pairs(uniform_blocks, packing): + 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), + fudge_data_for_setter(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, + fudge_data_for_setter(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): + 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): + # 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): + row_major = False + + if global_layout and "row_major" in global_layout: + row_major = True + + 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): + 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 = [layout_invert_default(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} + active uniform ${name} GL_UNIFORM_OFFSET ${offset} + active uniform ${name} GL_UNIFORM_ARRAY_STRIDE ${astride} + active uniform ${name} GL_UNIFORM_MATRIX_STRIDE ${mstride} + 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): + prefix = packing.layout_string() + "-" + suffix = ".shader_test" + + body = "-and-".join(["-".join(req) for req in requirements]) + + return prefix + body + suffix + + +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 = [] + + available_versions = [v for v in [130, 140, 150, 400, 430] + if v <= max_glsl_version] + + # Pick a random GLSL version from the available set of possible versions. + glsl_version = random.choice(available_versions) + + # 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 _______________________________________________ Piglit mailing list [email protected] http://lists.freedesktop.org/mailman/listinfo/piglit
