diff options
-rw-r--r-- | CMakeLists.txt | 3 | ||||
-rw-r--r-- | cmake/piglit_dispatch.cmake | 44 | ||||
-rw-r--r-- | cmake/piglit_glapi.cmake | 42 | ||||
-rw-r--r-- | glapi/.gitignore | 1 | ||||
-rw-r--r-- | glapi/parse_glspec.py | 469 | ||||
-rw-r--r-- | tests/util/.gitignore | 4 | ||||
-rw-r--r-- | tests/util/gen_dispatch.py | 647 |
7 files changed, 1209 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d7bf35b0..5d814d2e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,6 +169,9 @@ configure_file( "${piglit_BINARY_DIR}/tests/util/config.h" ) +include(cmake/piglit_glapi.cmake) +include(cmake/piglit_dispatch.cmake) + include_directories(src) add_subdirectory(cmake/target_api) add_subdirectory(generated_tests) diff --git a/cmake/piglit_dispatch.cmake b/cmake/piglit_dispatch.cmake new file mode 100644 index 000000000..0b0a2eb76 --- /dev/null +++ b/cmake/piglit_dispatch.cmake @@ -0,0 +1,44 @@ +# Copyright 2012 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 (including the next +# paragraph) 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. + +set(piglit_dispatch_gen_output_dir ${CMAKE_BINARY_DIR}/tests/util) + +file(MAKE_DIRECTORY ${piglit_dispatch_gen_output_dir}) + +set(piglit_dispatch_gen_outputs + ${piglit_dispatch_gen_output_dir}/generated_dispatch.c + ${piglit_dispatch_gen_output_dir}/generated_dispatch.h + ) + +set(piglit_dispatch_gen_inputs + ${CMAKE_SOURCE_DIR}/tests/util/gen_dispatch.py + ${CMAKE_BINARY_DIR}/glapi/glapi.json + ) + +add_custom_command( + OUTPUT ${piglit_dispatch_gen_outputs} + DEPENDS ${piglit_dispatch_gen_inputs} + COMMAND ${python} ${piglit_dispatch_gen_inputs} ${piglit_dispatch_gen_outputs} + ) + +add_custom_target(piglit_dispatch_gen + DEPENDS ${piglit_dispatch_gen_outputs} + ) diff --git a/cmake/piglit_glapi.cmake b/cmake/piglit_glapi.cmake new file mode 100644 index 000000000..bdc0390e0 --- /dev/null +++ b/cmake/piglit_glapi.cmake @@ -0,0 +1,42 @@ +# Copyright 2012 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 (including the next +# paragraph) 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. + +# Note: we're outputting the generated file to a subdirectory of +# ${CMAKE_SOURCE_DIR} so that we can check it back in to source +# control. +set(piglit_glapi_src_dir ${CMAKE_SOURCE_DIR}/glapi) + +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/glapi) + +set(piglit_glapi_output ${CMAKE_BINARY_DIR}/glapi/glapi.json) + +set(piglit_glapi_inputs + ${piglit_glapi_src_dir}/parse_glspec.py + ${piglit_glapi_src_dir}/gl.tm + ${piglit_glapi_src_dir}/gl.spec + ${piglit_glapi_src_dir}/enumext.spec + ) + +add_custom_command( + OUTPUT ${piglit_glapi_output} + DEPENDS ${piglit_glapi_inputs} + COMMAND ${python} ${piglit_glapi_inputs} ${piglit_glapi_output} + ) diff --git a/glapi/.gitignore b/glapi/.gitignore new file mode 100644 index 000000000..10bc9318b --- /dev/null +++ b/glapi/.gitignore @@ -0,0 +1 @@ +glapi.json diff --git a/glapi/parse_glspec.py b/glapi/parse_glspec.py new file mode 100644 index 000000000..ac624eb20 --- /dev/null +++ b/glapi/parse_glspec.py @@ -0,0 +1,469 @@ +# Copyright 2012 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 (including the next +# paragraph) 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. + +# This script generates a JSON description of the GL API based on +# source files published by www.opengl.org. +# +# The source file "gl.tm" is a CSV file mapping from abstract type +# names (in column 0) to C types (in column 3). All other columns are +# ignored. +# +# +# The source file "gl.spec" consists of a record for each API +# function, which looks like this: +# +# # Function name (excluding the "gl" prefix), followed by the names +# # of all function parameters: +# GetVertexAttribdvARB(index, pname, params) +# +# # Property/value pairs follow. Order is irrelevant. +# +# # "return" specifies the return type (as a reference to +# # column 0 of gl.tm). +# return void +# +# # "param" specifies the type of a single function parameter +# # (as a reference to column 0 of gl.tm). In addition, it +# # specifies whether the parameter is passed as an input +# # value, as an input or output array, or an input or output +# # reference. Input arrays and references get translated +# # into const pointers, and Output arrays and references get +# # translated into non-const pointers. Note that for arrays, +# # the size of the array appears in brackets after the word +# # "array". This value is ignored. +# param index UInt32 in value +# param pname VertexAttribPropertyARB in value +# param params Float64 out array [4] +# +# # "category" specifies which extension (or GL version) this +# # function was introduced in. For extensions, the category +# # name is the extension name (without the "GL_" prefix). +# # For GL versions, the category name looks like +# # e.g. "VERSION_1_0" or "VERSION_1_0_DEPRECATED" (for +# # deprecated features). +# category ARB_vertex_program +# +# # "alias" specifies the name of a function that is +# # behaviorally indistinguishable from this function (if +# # any). +# alias GetVertexAttribdv +# +# # Other property/value pairs are ignored. +# +# # Lines in any other format are ignored. +# +# +# The source file "enumext.spec" consists of lines of the form <enum +# name> = <value>, e.g.: +# +# FRONT = 0x0404 +# +# The enum name is the name without the "GL_" prefix. +# +# It is also permissible for the value to be a reference to an enum +# that appeared earlier, e.g.: +# +# DRAW_FRAMEBUFFER_BINDING = GL_FRAMEBUFFER_BINDING +# +# Note that when this happens the value *does* include the "GL_" +# prefix. +# +# +# The JSON output format is as follows: +# { +# "categories": { +# <category name>: { +# "kind": <"GL" for a GL version, "extension" for an extension>, +# "gl_10x_version": <For a GL version, version number times 10>, +# "extension_name" <For an extension, name of the extension> +# }, ... +# }, +# "enums": { +# <enum name, without "GL_" prefix>: { +# "value_int": <value integer> +# "value_str": <value string> +# }, ... +# }, +# "functions": { +# <function name, without "gl" prefix>: { +# "category": <category in which this function appears>, +# "param_names": <list of param names>, +# "param_types": <list of param types>, +# "return_type": <type, or "void" if no return> +# }, ... +# }, +# "function_alias_sets": { +# <list of synonymous function names>, ... +# }, +# } + + + +import collections +import csv +import json +import re +import sys + + +GLSPEC_HEADER_REGEXP = re.compile(r'^(\w+)\((.*)\)$') +GLSPEC_ATTRIBUTE_REGEXP = re.compile(r'^\s+(\w+)\s+(.*)$') +GL_VERSION_REGEXP = re.compile('^VERSION_([0-9])_([0-9])(_DEPRECATED)?$') +ENUM_REGEXP = re.compile(r'^\s+(\w+)\s+=\s+(\w+)$') + + +# Convert a type into a canonical form that is consistent about its +# use of spaces. +# +# Example input: 'const void**' +# Example output: 'const void * *' +def normalize_type(typ): + tokens = [token for token in typ.replace('*', ' * ').split(' ') + if token != ''] + return ' '.join(tokens) + + +# Interpret an enumerated value, which may be in hex or decimal, and +# may include a type suffix such as "ull". +# +# Example input: '0xFFFFFFFFul' +# Example output: 4294967295 +def decode_enum_value(value_str): + for suffix in ('u', 'ul', 'ull'): + if value_str.endswith(suffix): + value_str = value_str[:-len(suffix)] + break + return int(value_str, 0) + + +# Convert an object to a form that can be serialized to JSON. Python +# "set" objects are converted to lists. +def jsonize(obj): + if type(obj) in (set, frozenset): + return sorted(obj) + else: + raise Exception('jsonize failed for {0}'.format(type(obj))) + + +# Iterate through the lines of a file, discarding end-of-line comments +# delimited by "#". Blank lines are discarded, as well as any +# whitespace at the end of a line. +def filter_comments(f): + for line in f: + if '#' in line: + line = line[:line.find('#')] + line = line.rstrip() + if line != '': + yield line.rstrip() + + +# Convert a category name from the form used in the gl.spec file to +# the form we want to output in JSON. E.g.: +# +# - "2.1" is converted into { 'kind': 'GL', 'gl_10x_version': 21 } +# +# - "FOO" is converted into { 'kind': 'extension', 'extension_name': 'GL_FOO' } +def translate_category(category_name): + m = GL_VERSION_REGEXP.match(category_name) + if m: + ones = int(m.group(1)) + tenths = int(m.group(2)) + return '{0}.{1}'.format(ones, tenths), { + 'kind': 'GL', + 'gl_10x_version': 10 * ones + tenths + } + else: + extension_name = 'GL_' + category_name + return extension_name, { + 'kind': 'extension', + 'extension_name': extension_name + } + + +# Data structure keeping track of which function names are known, and +# which names are synonymous with which other names. +class SynonymMap(object): + def __init__(self): + # __name_to_synonyms maps from a function name to the set of + # all names that are synonymous with it (including itself). + self.__name_to_synonyms = {} + + # Add a single function name which is not (yet) known to be + # synonymous with any other name. No effect if the function name + # is already known. + def add_singleton(self, name): + if name not in self.__name_to_synonyms: + self.__name_to_synonyms[name] = frozenset([name]) + return self.__name_to_synonyms[name] + + # Add a pair of function names, and note that they are synonymous. + # Synonymity is transitive, so if either of the two function names + # previously had known synonyms, all synonyms are combined into a + # single set. + def add_alias(self, name, alias): + name_ss = self.add_singleton(name) + alias_ss = self.add_singleton(alias) + combined_set = name_ss | alias_ss + for n in combined_set: + self.__name_to_synonyms[n] = combined_set + + # Get a set of sets of synonymous functions. + def get_synonym_sets(self): + return frozenset(self.__name_to_synonyms.values()) + + +# In-memory representation of the GL API. +class Api(object): + def __init__(self): + # Api.type_translation is a dict mapping abstract type names + # to C types. It is based on the data in the gl.tm file. For + # example, the dict entry for String is: + # + # 'String': 'const GLubyte *' + self.type_translation = {} + + # Api.enums is a dict mapping enum names (without the 'GL_' + # prefix) to a dict containing (a) the enum value expressed as + # an integer, and (b) the enum value expressed as a C literal. + # It is based on the data in the gl.spec file. For example, + # the dict entry for GL_CLIENT_ALL_ATTRIB_BITS is: + # + # 'CLIENT_ALL_ATTRIB_BITS': { 'value_int': 4294967295, + # 'value_str': "0xFFFFFFFF" } + self.enums = {} + + # Api.functions is a dict mapping function names (without the + # 'gl' prefix) to a dict containing (a) the name of the + # category the function is in, (b) the function call parameter + # names, (c) the function call parameter types, and (d) the + # function return type. It is based on the data in the + # gl.spec file, cross-referenced against the type translations + # from the gl.tm file. For example, the dict entry for + # glAreTexturesResident is: + # + # 'AreTexturesResident': { + # 'category': '1.1', + # 'param_names': ['n', 'textures', 'residences'], + # 'param_types': ['GLsizei', 'const GLuint *', 'GLboolean *'], + # 'return_type': ['GLboolean'] } + self.functions = {} + + # Api.synonyms is a SynonymMap object which records which + # function names are aliases of each other. It is based on + # the "alias" declarations from the gl.spec file. + self.synonyms = SynonymMap() + + # Api.categories is a dict mapping category names to a dict + # describing the category. For categories representing a GL + # version, the dict entry looks like this: + # + # '2.1': { 'kind': 'GL', 'gl_10x_version': 21 } + # + # For categories representing an extension, the dict entry + # looks like this: + # + # 'GL_ARB_sync': { 'kind': 'extension', + # 'extension_name': 'GL_ARB_sync' } + self.categories = {} + + # Convert each line in the gl.tm file into a key/value pair in + # self.type_translation, mapping an abstract type name to a C + # type. + def read_gl_tm(self, f): + for line in csv.reader(filter_comments(f)): + name = line[0].strip() + typ = line[3].strip() + if typ == '*': + # gl.tm uses "*" to represent void (for void used as a + # return value). + typ = 'void' + self.type_translation[name] = normalize_type(typ) + + # Group the lines in the gl.spec file into triples (function_name, + # param_names, attributes). For example, the following gl.spec + # input: + # + # Foo(bar, baz): + # x value1 + # y value2 other_info + # y value3 more_info + # + # Produces this output triple: + # + # ('Foo', ['bar', 'baz'], + # {'x': ['value1'], 'y': ['value2 other_info', 'value3 more_info']}) + @staticmethod + def group_gl_spec_functions(f): + function_name = None + param_names = None + attributes = None + for line in filter_comments(f): + m = GLSPEC_HEADER_REGEXP.match(line) + if m: + if function_name: + yield function_name, param_names, attributes + function_name = m.group(1) + if m.group(2) == '': + param_names = [] + else: + param_names = [n.strip() for n in m.group(2).split(',')] + attributes = collections.defaultdict(list) + continue + m = GLSPEC_ATTRIBUTE_REGEXP.match(line) + if m: + attribute_type, attribute_data = m.groups() + attributes[attribute_type].append(attribute_data) + continue + continue + if function_name: + yield function_name, param_names, attributes + + # Process the data in gl.spec, and populate self.functions, + # self.synonyms, and self.categories based on it. + def read_gl_spec(self, f): + for name, param_names, attributes in self.group_gl_spec_functions(f): + if name in self.functions: + raise Exception( + 'Function {0!r} appears more than once'.format(name)) + param_name_to_index = dict( + (param_name, index) + for index, param_name in enumerate(param_names)) + param_types = [None] * len(param_names) + if len(attributes['param']) != len(param_names): + raise Exception( + 'Function {0!r} has a different number of parameters and ' + 'param declarations'.format(name)) + for param_datum in attributes['param']: + param_datum_tokens = param_datum.split() + param_name = param_datum_tokens[0] + param_index = param_name_to_index[param_name] + param_base_type = self.type_translation[param_datum_tokens[1]] + param_dir = param_datum_tokens[2] + param_multiplicity = param_datum_tokens[3] + if param_types[param_index] is not None: + raise Exception( + 'Function {0!r} contains more than one param ' + 'declaration for parameter {1!r}'.format( + name, param_name)) + if param_multiplicity == 'value': + assert param_dir == 'in' + param_type = param_base_type + elif param_multiplicity in ('array', 'reference'): + if param_dir == 'in': + # Note: technically this is not correct if + # param_base_type is a pointer type (e.g. to + # make an "in array" of "void *" we should + # produce "void * const *", not "const void * + # *"). However, the scripts used by the GL + # consortium to produce glext.h from gl.spec + # produce "const void * *", and fortunately + # the only ill effect of this is that clients + # have to do a little more typecasting than + # they should. So to avoid confusing people, + # we're going to make the same mistake, so + # that the resulting function signatures match + # those in glext.h. + param_type = normalize_type( + 'const {0} *'.format(param_base_type)) + elif param_dir == 'out': + param_type = normalize_type( + '{0} *'.format(param_base_type)) + else: + raise Exception( + 'Function {0!r} parameter {1!r} uses unrecognized ' + 'direction {2!r}'.format( + name, param_name, param_dir)) + else: + raise Exception( + 'Function {0!r} parameter {1!r} uses unrecognized ' + 'multiplicity {2!r}'.format( + name, param_name, param_multiplicity)) + param_types[param_index] = param_type + if len(attributes['return']) != 1: + raise Exception( + 'Function {0!r} contains {1} return attributes'.format( + name, len(attributes['return']))) + if len(attributes['category']) != 1: + raise Exception( + 'Function {0!r} contains {1} category attributes'.format( + name, len(attributes['category']))) + category, additional_data = translate_category( + attributes['category'][0]) + if category not in self.categories: + self.categories[category] = additional_data + self.functions[name] = { + 'return_type': self.type_translation[attributes['return'][0]], + 'param_names': param_names, + 'param_types': param_types, + 'category': category, + } + self.synonyms.add_singleton(name) + for alias in attributes['alias']: + self.synonyms.add_alias(name, alias) + + # Convert each line in the enumext.spec file into a key/value pair + # in self.enums, mapping an enum name to a dict. For example, the + # following enumext.spec input: + # + # CLIENT_ALL_ATTRIB_BITS = 0xFFFFFFFF # ClientAttribMask + # + # Produces the dict entry: + # + # 'CLIENT_ALL_ATTRIB_BITS': { 'value_int': 4294967295, + # 'value_str': "0xFFFFFFFF" } + def read_enumext_spec(self, f): + for line in filter_comments(f): + m = ENUM_REGEXP.match(line) + if m: + name, value = m.groups() + if value.startswith('GL_'): + value_rhs = value[3:] + value_int = self.enums[value_rhs]['value_int'] + else: + value_int = decode_enum_value(value) + self.enums[name] = { + 'value_str': value, + 'value_int': value_int + } + + # Convert the stored API into JSON. To make diffing easier, all + # dictionaries are sorted by key, and all sets are sorted by set + # element. + def to_json(self): + return json.dumps({ + 'categories': self.categories, + 'enums': self.enums, + 'functions': self.functions, + 'function_alias_sets': + self.synonyms.get_synonym_sets(), + }, indent = 2, sort_keys = True, default = jsonize) + + +if __name__ == '__main__': + api = Api() + with open(sys.argv[1]) as f: + api.read_gl_tm(f) + with open(sys.argv[2]) as f: + api.read_gl_spec(f) + with open(sys.argv[3]) as f: + api.read_enumext_spec(f) + with open(sys.argv[4], 'w') as f: + f.write(api.to_json()) diff --git a/tests/util/.gitignore b/tests/util/.gitignore index 299bb98e5..8fbd23567 100644 --- a/tests/util/.gitignore +++ b/tests/util/.gitignore @@ -1 +1,3 @@ -config.h
\ No newline at end of file +config.h +generated_dispatch.c +generated_dispatch.h diff --git a/tests/util/gen_dispatch.py b/tests/util/gen_dispatch.py new file mode 100644 index 000000000..96a7f77ff --- /dev/null +++ b/tests/util/gen_dispatch.py @@ -0,0 +1,647 @@ +# Copyright 2012 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 (including the next +# paragraph) 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. + +# This script generates a C file (and corresponding header) allowing +# Piglit to dispatch calls to OpenGL based on a JSON description of +# the GL API (and extensions). +# +# Invoke this script with 3 command line arguments: the JSON input +# filename, the C output filename, and the header outpit filename. +# +# +# The input looks like this: +# +# { +# "categories": { +# <category name>: { +# "kind": <"GL" for a GL version, "extension" for an extension>, +# "gl_10x_version": <For a GL version, version number times 10>, +# "extension_name" <For an extension, name of the extension> +# }, ... +# }, +# "enums": { +# <enum name, without "GL_" prefix>: { +# "value_int": <value integer> +# "value_str": <value string> +# }, ... +# }, +# "functions": { +# <function name, without "gl" prefix>: { +# "category": <category in which this function appears>, +# "param_names": <list of param names>, +# "param_types": <list of param types>, +# "return_type": <type, or "void" if no return> +# }, ... +# }, +# "function_alias_sets": { +# <list of synonymous function names>, ... +# }, +# } +# +# +# The generated header consists of the following: +# +# - A typedef for each function, of the form that would normally +# appear in gl.h or glext.h, e.g.: +# +# typedef GLvoid * (APIENTRY *PFNGLMAPBUFFERPROC)(GLenum, GLenum); +# typedef GLvoid * (APIENTRY *PFNGLMAPBUFFERARBPROC)(GLenum, GLenum); +# +# - A set of extern declarations for "dispatch function pointers". +# There is one dispatch function pointer for each set of synonymous +# functions in the GL API, e.g.: +# +# extern PFNGLMAPBUFFERPROC piglit_dispatch_glMapBuffer; +# +# - A set of #defines mapping each function name to the corresponding +# dispatch function pointer, e.g.: +# +# #define glMapBuffer piglit_dispatch_glMapBuffer +# #define glMapBufferARB piglit_dispatch_glMapBuffer +# +# - A #define for each enum in the GL API, e.g.: +# +# #define GL_FRONT 0x0404 +# +# - A #define for each extension, e.g.: +# +# #define GL_ARB_vertex_buffer_object +# +# - A #define for each known GL version, e.g.: +# +# #define GL_VERSION_1_5 +# +# +# The generated C file consists of the following: +# +# - A resolve function corresponding to each set of synonymous +# functions in the GL API. The resolve function determines which of +# the synonymous names the implementation supports (by consulting +# the current GL version and/or the extension string), and calls +# either get_core_proc() or get_ext_proc() to get the function +# pointer. It stores the result in the dispatch function pointer, +# and then returns it as a generic void(void) function pointer. If +# the implementation does not support any of the synonymous names, +# it calls unsupported(). E.g.: +# +# /* glMapBuffer (GL 1.5) */ +# /* glMapbufferARB (GL_ARB_vertex_buffer_object) */ +# static piglit_dispatch_function_ptr resolve_glMapBuffer() +# { +# if (check_version(15)) +# piglit_dispatch_glMapBuffer = (PFNGLMAPBUFFERPROC) get_core_proc("glMapBuffer", 15); +# else if (check_extension("GL_ARB_vertex_buffer_object")) +# piglit_dispatch_glMapBuffer = (PFNGLMAPBUFFERARBPROC) get_ext_proc("glMapBufferARB"); +# else +# unsupported("MapBuffer"); +# return (piglit_dispatch_function_ptr) piglit_dispatch_glMapBuffer; +# } +# +# - A stub function corresponding to each set of synonymous functions +# in the GL API. The stub function first calls +# check_initialized(). Then it calls the resolve function to +# ensure that the dispatch function pointer is set. Finally, it +# dispatches to the GL function through the dispatch function +# pointer. E.g.: +# +# static GLvoid * APIENTRY stub_glMapBuffer(GLenum target, GLenum access) +# { +# check_initialized(); +# resolve_glMapBuffer(); +# return piglit_dispatch_glMapBuffer(target, access); +# } +# +# - A declaration for each dispatch function pointer, e.g.: +# +# PFNGLMAPBUFFERPROC piglit_dispatch_glMapBuffer = stub_glMapBuffer; +# +# - An function, reset_dispatch_pointers(), which resets each dispatch +# pointer to the corresponding stub function. +# +# - A table function_names, containing the name of each function in +# alphabetical order (including the "gl" prefix). +# +# - A table function_resolvers, containing a pointer to the resolve +# function corresponding to each entry in function_names. + +import collections +import json +import os.path +import sys + + +# Generate a top-of-file comment cautioning that the file is +# auto-generated and should not be manually edited. +def generated_boilerplate(): + return """\ +/** + * This file was automatically generated by the script {0!r}. + * + * DO NOT EDIT! + * + * To regenerate, run "make piglit_dispatch_gen" from the toplevel directory. + * + * Copyright 2012 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 (including the next + * paragraph) 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. + */ +""".format(os.path.basename(sys.argv[0])) + + +# Certain param names used in OpenGL are reserved by some compilers. +# Rename them. +PARAM_NAME_FIXUPS = { 'near': 'hither', 'far': 'yon' } +def fixup_param_name(name): + if name in PARAM_NAME_FIXUPS: + return PARAM_NAME_FIXUPS[name] + else: + return name + + +# Internal representation of a category. +# +# - For a category representing a GL version, Category.kind is 'GL' +# and Category.gl_10x_version is 10 times the GL version (e.g. 21 +# for OpenGL version 2.1). +# +# - For a category representing an extension, Category.kind is +# 'extension' and Category.extension_name is the extension name +# (including the 'GL_' prefix). +class Category(object): + def __init__(self, json_data): + self.kind = json_data['kind'] + if 'gl_10x_version' in json_data: + self.gl_10x_version = json_data['gl_10x_version'] + if 'extension_name' in json_data: + self.extension_name = json_data['extension_name'] + + # Generate a human-readable representation of the category (for + # use in generated comments). + def __str__(self): + if self.kind == 'GL': + return 'GL {0}.{1}'.format( + self.gl_10x_version // 10, self.gl_10x_version % 10) + elif self.kind == 'extension': + return self.extension_name + else: + raise Exception( + 'Unexpected category kind {0!r}'.format(self.kind)) + + +# Internal representation of a GL function. +# +# - Function.name is the name of the function, without the 'gl' +# prefix. +# +# - Function.param_names is a list containing the name of each +# function parameter. +# +# - Function.param_types is a list containing the type of each +# function parameter. +# +# - Function.return_type is the return type of the function, or 'void' +# if the function has no return. +# +# - Function.category is a Category object describing the extension or +# GL version the function is defined in. +class Function(object): + def __init__(self, name, json_data): + self.name = name + self.param_names = [ + fixup_param_name(x) for x in json_data['param_names']] + self.param_types = json_data['param_types'] + self.return_type = json_data['return_type'] + self.category = json_data['category'] + + # Name of the function, with the 'gl' prefix. + @property + def gl_name(self): + return 'gl' + self.name + + # Name of the function signature typedef corresponding to this + # function. E.g. for the glGetString function, this is + # 'PFNGLGETSTRINGPROC'. + @property + def typedef_name(self): + return 'pfn{0}proc'.format(self.gl_name).upper() + + # Generate a string representing the function signature in C. + # + # - name is inserted before the opening paren--use '' to generate + # an anonymous function type signature. + # + # - If anonymous_args is True, then the signature contains only + # the types of the arguments, not the names. + def c_form(self, name, anonymous_args): + if self.param_types: + if anonymous_args: + param_decls = ', '.join(self.param_types) + else: + param_decls = ', '.join( + '{0} {1}'.format(*p) + for p in zip(self.param_types, self.param_names)) + else: + param_decls = 'void' + return '{rettype} {name}({param_decls})'.format( + rettype = self.return_type, name = name, + param_decls = param_decls) + + +# Internal representation of an enum. +# +# - Enum.value_int is the value of the enum, as a Python integer. +# +# - Enum.value_str is the value of the enum, as a string suitable for +# emitting as C code. +class Enum(object): + def __init__(self, json_data): + self.value_int = json_data['value_int'] + self.value_str = json_data['value_str'] + + +# Data structure keeping track of a set of synonymous functions. Such +# a set is called a "dispatch set" because it corresponds to a single +# dispatch pointer. +# +# - DispatchSet.cat_fn_pairs is a list of pairs (category, function) +# for each category this function is defined in. The list is sorted +# by category, with categories of kind 'GL' appearing first. +class DispatchSet(object): + # Initialize a dispatch set given a list of synonymous function + # names. + # + # - all_functions is a dict mapping all possible function names to + # the Function object describing them. + # + # - all_categories is a dict mapping all possible category names + # to the Category object describing them. + def __init__(self, synonym_set, all_functions, all_categories): + self.cat_fn_pairs = [] + for function_name in synonym_set: + function = all_functions[function_name] + category_name = function.category + category = all_categories[category_name] + self.cat_fn_pairs.append((category, function)) + # Sort by category, with GL categories preceding extensions. + self.cat_fn_pairs.sort(key = self.__sort_key) + + # The first Function object in DispatchSet.functions. This + # "primary" function is used to name the dispatch pointer, the + # stub function, and the resolve function. + @property + def primary_function(self): + return self.cat_fn_pairs[0][1] + + # The name of the dispatch pointer that should be generated for + # this dispatch set. + @property + def dispatch_name(self): + return 'piglit_dispatch_' + self.primary_function.gl_name + + # The name of the stub function that should be generated for this + # dispatch set. + @property + def stub_name(self): + return 'stub_' + self.primary_function.gl_name + + # The name of the resolve function that should be generated for + # this dispatch set. + @property + def resolve_name(self): + return 'resolve_' + self.primary_function.gl_name + + @staticmethod + def __sort_key(cat_fn_pair): + if cat_fn_pair[0].kind == 'GL': + return 0, cat_fn_pair[0].gl_10x_version + elif cat_fn_pair[0].kind == 'extension': + return 1, cat_fn_pair[0].extension_name + else: + raise Exception( + 'Unexpected category kind {0!r}'.format(cat_fn_pair[0].kind)) + + +# Data structure keeping track of all of the known functions and +# enums, and synonym relationships that exist between the functions. +# +# - Api.enums is a dict mapping enum name to an Enum object. +# +# - Api.functions is a dict mapping function name to a Function object. +# +# - Api.function_alias_sets is a list of lists, where each constituent +# list is a list of function names that are aliases of each other. +# +# - Api.categories is a dict mapping category name to a Category +# object. +class Api(object): + def __init__(self, json_data): + self.enums = dict( + (key, Enum(value)) + for key, value in json_data['enums'].items()) + self.functions = dict( + (key, Function(key, value)) + for key, value in json_data['functions'].items()) + self.function_alias_sets = json_data['function_alias_sets'] + self.categories = dict( + (key, Category(value)) + for key, value in json_data['categories'].items()) + + # Generate a list of (name, value) pairs representing all enums in + # the API. The resulting list is sorted by enum value. + def compute_unique_enums(self): + enums_by_value = [(enum.value_int, (name, enum.value_str)) + for name, enum in self.enums.items()] + enums_by_value.sort() + return [item[1] for item in enums_by_value] + + # A list of all of the extension names declared in the API, as + # Python strings, sorted alphabetically. + @property + def extensions(self): + return sorted( + [category_name + for category_name, category in self.categories.items() + if category.kind == 'extension']) + + # A list of all of the GL versions declared in the API, as + # integers (e.g. 13 represents GL version 1.3). + @property + def gl_versions(self): + return sorted( + [category.gl_10x_version + for category in self.categories.values() + if category.kind == 'GL']) + + # Generate a list of DispatchSet objects representing all sets of + # synonymous functions in the API. The resulting list is sorted + # by DispatchSet.stub_name. + def compute_dispatch_sets(self): + sets = [DispatchSet(synonym_set, self.functions, self.categories) + for synonym_set in self.function_alias_sets] + sets.sort(key = lambda ds: ds.stub_name) + + return sets + + # Generate a list of Function objects representing all functions + # in the API. The resulting list is sorted by function name. + def compute_unique_functions(self): + return [self.functions[key] for key in sorted(self.functions.keys())] + + +# Read the given input file and return an Api object containing the +# data in it. +def read_api(filename): + with open(filename, 'r') as f: + return Api(json.load(f)) + + +# Generate the resolve function for a given DispatchSet. +def generate_resolve_function(ds): + f0 = ds.primary_function + + # First figure out all the conditions we want to check in order to + # figure out which function to dispatch to, and the code we will + # execute in each case. + condition_code_pairs = [] + for category, f in ds.cat_fn_pairs: + if category.kind == 'GL': + getter = 'get_core_proc("{0}", {1})'.format( + f.gl_name, category.gl_10x_version) + if category.gl_10x_version == 10: + # Function has always been available--no need to check + # a condition. + condition = 'true' + else: + condition = 'check_version({0})'.format( + category.gl_10x_version) + elif category.kind == 'extension': + getter = 'get_ext_proc("{0}")'.format(f.gl_name) + condition = 'check_extension("{0}")'.format( + category.extension_name) + else: + raise Exception( + 'Unexpected category type {0!r}'.format(category.kind)) + + if f.name == 'TexImage3DEXT': + # Special case: glTexImage3DEXT has a slightly different + # type than glTexImage3D (argument 3 is a GLenum rather + # than a GLint). This is not a problem, since GLenum and + # GLint are treated identically by function calling + # conventions. So when calling get_proc_address() on + # glTexImage3DEXT, cast the result to PFNGLTEXIMAGE3DPROC + # to avoid a warning. + typedef_name = 'PFNGLTEXIMAGE3DPROC' + else: + typedef_name = f.typedef_name + + code = '{0} = ({1}) {2};'.format( + ds.dispatch_name, typedef_name, getter) + + condition_code_pairs.append((condition, code)) + + # Finally, if none of the previous conditions were satisfied, then + # the given dispatch set is not supported by the implementation, + # so we want to call the unsupported() function. + condition_code_pairs.append( + ('true', 'unsupported("{0}");'.format(f0.name))) + + # Start the resolve function + resolve_fn = 'static piglit_dispatch_function_ptr {0}()\n'.format( + ds.resolve_name) + resolve_fn += '{\n' + + # Output code that checks each condition in turn and executes the + # appropriate case. To make the generated code more palatable + # (and to avoid compiler warnings), we convert "if (true) FOO;" to + # "FOO;" and "else if (true) FOO;" to "else FOO;". + if condition_code_pairs[0][0] == 'true': + resolve_fn += '\t{0}\n'.format(condition_code_pairs[0][1]) + else: + resolve_fn += '\tif ({0})\n\t\t{1}\n'.format(*condition_code_pairs[0]) + for i in xrange(1, len(condition_code_pairs)): + if condition_code_pairs[i][0] == 'true': + resolve_fn += '\telse\n\t\t{0}\n'.format( + condition_code_pairs[i][1]) + break + else: + resolve_fn += '\telse if ({0})\n\t\t{1}\n'.format( + *condition_code_pairs[i]) + + # Output code to return the dispatch function. + resolve_fn += '\treturn (piglit_dispatch_function_ptr) {0};\n'.format( + ds.dispatch_name) + resolve_fn += '}\n' + return resolve_fn + + +# Generate the stub function for a given DispatchSet. +def generate_stub_function(ds): + f0 = ds.primary_function + + # Start the stub function + stub_fn = 'static {0}\n'.format( + f0.c_form('APIENTRY ' + ds.stub_name, anonymous_args = False)) + stub_fn += '{\n' + stub_fn += '\tcheck_initialized();\n' + stub_fn += '\t{0}();\n'.format(ds.resolve_name) + + # Output the call to the dispatch function. + stub_fn += '\t{0}{1}({2});\n'.format( + 'return ' if f0.return_type != 'void' else '', + ds.dispatch_name, ', '.join(f0.param_names)) + stub_fn += '}\n' + return stub_fn + + +# Generate the reset_dispatch_pointers() function, which sets each +# dispatch pointer to point to the corresponding stub function. +def generate_dispatch_pointer_resetter(dispatch_sets): + result = [] + result.append('static void\n') + result.append('reset_dispatch_pointers()\n') + result.append('{\n') + for ds in dispatch_sets: + result.append( + '\t{0} = {1};\n'.format(ds.dispatch_name, ds.stub_name)) + result.append('}\n') + return ''.join(result) + + +# Generate the function_names and function_resolvers tables. +def generate_function_names_and_resolvers(dispatch_sets): + name_resolver_pairs = [] + for ds in dispatch_sets: + for _, f in ds.cat_fn_pairs: + name_resolver_pairs.append((f.gl_name, ds.resolve_name)) + name_resolver_pairs.sort() + result = [] + result.append('static const char * const function_names[] = {\n') + for name, _ in name_resolver_pairs: + result.append('\t"{0}",\n'.format(name)) + result.append('};\n') + result.append('\n') + result.append('static const piglit_dispatch_resolver_ptr ' + 'function_resolvers[] = {\n') + for _, resolver in name_resolver_pairs: + result.append('\t{0},\n'.format(resolver)) + result.append('};\n') + return ''.join(result) + + +# Generate the C source and header files for the API. +def generate_code(api): + c_contents = [generated_boilerplate()] + h_contents = [generated_boilerplate()] + + unique_functions = api.compute_unique_functions() + + # Emit typedefs for each name + for f in unique_functions: + h_contents.append( + 'typedef {0};\n'.format( + f.c_form('(APIENTRY *{0})'.format(f.typedef_name), + anonymous_args = True))) + + dispatch_sets = api.compute_dispatch_sets() + + for ds in dispatch_sets: + f0 = ds.primary_function + + # Emit comment block + comments = '\n' + for cat, f in ds.cat_fn_pairs: + comments += '/* {0} ({1}) */\n'.format(f.gl_name, cat) + c_contents.append(comments) + h_contents.append(comments) + + # Emit extern declaration of dispatch pointer + h_contents.append( + 'extern {0} {1};\n'.format(f0.typedef_name, ds.dispatch_name)) + + # Emit defines aliasing each GL function to the dispatch + # pointer + for _, f in ds.cat_fn_pairs: + h_contents.append( + '#define {0} {1}\n'.format(f.gl_name, ds.dispatch_name)) + + # Emit resolve function + c_contents.append(generate_resolve_function(ds)) + + # Emit stub function + c_contents.append(generate_stub_function(ds)) + + # Emit initializer for dispatch pointer + c_contents.append( + '{0} {1} = {2};\n'.format( + f0.typedef_name, ds.dispatch_name, ds.stub_name)) + + # Emit dispatch pointer initialization function + c_contents.append(generate_dispatch_pointer_resetter(dispatch_sets)) + + c_contents.append('\n') + + # Emit function_names and function_resolvers tables. + c_contents.append(generate_function_names_and_resolvers(dispatch_sets)) + + # Emit enum #defines + for name, value in api.compute_unique_enums(): + h_contents.append('#define GL_{0} {1}\n'.format(name, value)) + + # Emit extension #defines + h_contents.append('\n') + for ext in api.extensions: + h_contents.append('#define {0}\n'.format(ext)) + + # Emit GL version #defines + h_contents.append('\n') + for ver in api.gl_versions: + h_contents.append('#define GL_VERSION_{0}_{1}\n'.format( + ver // 10, ver % 10)) + + return ''.join(c_contents), ''.join(h_contents) + + +if __name__ == '__main__': + file_to_parse = sys.argv[1] + api = read_api(file_to_parse) + + c_contents, h_contents = generate_code(api) + with open(sys.argv[2], 'w') as f: + f.write(c_contents) + with open(sys.argv[3], 'w') as f: + f.write(h_contents) |