#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright © 2013 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. import sys import argparse import xml.etree.ElementTree as ET import re import os class GLProvider(object): def __init__(self, condition, condition_name, loader, name): # C code for determining if this function is available. # (e.g. epoxy_is_desktop_gl() && epoxy_gl_version() >= 20 self.condition = condition # A string (possibly with spaces) describing the condition. self.condition_name = condition_name # The loader for getting the symbol -- either dlsym or # getprocaddress. This is a python format string to generate # C code, given self.name. self.loader = loader # The name of the function to be loaded (possibly an # ARB/EXT/whatever-decorated variant). self.name = name # This is the C enum name we'll use for referring to this provider. self.enum = condition_name self.enum = self.enum.replace(' ', '_') self.enum = self.enum.replace('\\"', '') self.enum = self.enum.replace('.', '_') class GLFunction(object): def __init__(self, ret_type, name): self.name = name self.ptr_type = 'PFN' + name.upper() + 'PROC' self.ret_type = ret_type self.providers = {} self.args = [] # This is the string of C code for passing through the # arguments to the function. self.args_list = '' # This is the string of C code for declaring the arguments # list. self.args_decl = 'void' # If present, this is the string name of the function that # this is an alias of. This initially comes from the # registry, and may get updated if it turns out our alias is # itself an alias (for example glFramebufferTextureEXT -> # glFramebufferTextureARB -> glFramebufferTexture) self.alias_name = None # After alias resolution, this is the function that this is an # alias of. self.alias_func = None # For the root of an alias tree, this lists the functions that # are marked as aliases of it, so that it can write a resolver # for all of them. self.alias_exts = [] def add_arg(self, type, name): self.args.append((type, name)) if self.args_decl == 'void': self.args_list = name self.args_decl = type + ' ' + name else: self.args_list += ', ' + name self.args_decl += ', ' + type + ' ' + name def add_provider(self, condition, loader, condition_name): self.providers[condition_name] = GLProvider(condition, condition_name, loader, self.name) def add_alias(self, ext): # We don't support transitivity of aliases. assert not ext.alias_exts assert self.alias_func is None self.alias_exts.append(ext) ext.alias_func = self class Generator(object): def __init__(self, target): self.target = target self.enums = {} self.functions = {} self.sorted_function = None self.max_enum_name_len = 1 self.copyright_comment = None self.typedefs = '' self.out_file = None # GL versions named in the registry, which we should generate # #defines for. self.supported_versions = set() # Extensions named in the registry, which we should generate # #defines for. self.supported_extensions = set() # Dictionary mapping human-readable names of providers to a C # enum token that will be used to reference those names, to # reduce generated binary size. self.provider_enum = {} # Dictionary mapping human-readable names of providers to C # code to detect if it's present. self.provider_condition = {} # Dictionary mapping human-readable names of providers to # format strings for fetching the function pointer when # provided the name of the symbol to be requested. self.provider_loader = {} # These are functions with hand-written wrapper code in # dispatch_common.c. Their dispatch stubs will be replaced # with non-public symbols with a "_unwrapped" suffix. self.wrapped_functions = { 'glBegin', 'glEnd' } def all_text_until_element_name(self, element, element_name): text = '' if element.text is not None: text += element.text for child in element: if child.tag == element_name: break if child.text: text += child.text if child.tail: text += child.tail return text def out(self, text): self.out_file.write(text) def outln(self, text): self.out_file.write(text + '\n') def parse_typedefs(self, reg): for t in reg.findall('types/type'): if 'name' in t.attrib and t.attrib['name'] not in {'GLhandleARB'}: continue # The gles1/gles2-specific types are redundant # declarations, and the different types used for them (int # vs int32_t) caused problems on win32 builds. api = t.get('api') if api: continue if t.text is not None: self.typedefs += t.text for child in t: if child.text: self.typedefs += child.text if child.tail: self.typedefs += child.tail self.typedefs += '\n' def parse_enums(self, reg): for enum in reg.findall('enums/enum'): name = enum.get('name') self.max_enum_name_len = max(self.max_enum_name_len, len(name)) self.enums[name] = enum.get('value') def get_function_return_type(self, proto): # Everything up to the start of the name element is the return type. return self.all_text_until_element_name(proto, 'name').strip() def parse_function_definitions(self, reg): for command in reg.findall('commands/command'): proto = command.find('proto') name = proto.find('name').text ret_type = self.get_function_return_type(proto) func = GLFunction(ret_type, name) for arg in command.findall('param'): func.add_arg(self.all_text_until_element_name(arg, 'name').strip(), arg.find('name').text) alias = command.find('alias') if alias is not None: # Note that some alias references appear before the # target command is defined (glAttachObjectARB() -> # glAttachShader(), for example). func.alias_name = alias.get('name') self.functions[name] = func def drop_weird_glx_functions(self): # Drop a few ancient SGIX GLX extensions that use types not defined # anywhere in Xlib. In glxext.h, they're protected by #ifdefs for the # headers that defined them. weird_functions = [name for name, func in self.functions.items() if 'VLServer' in func.args_decl or 'DMparams' in func.args_decl] for name in weird_functions: del self.functions[name] def resolve_aliases(self): for func in self.functions.values(): # Find the root of the alias tree, and add ourselves to it. if func.alias_name: alias_func = func while alias_func.alias_name: alias_func = self.functions[alias_func.alias_name] func.alias_name = alias_func.name func.alias_func = alias_func alias_func.alias_exts.append(func) def prepare_provider_enum(self): self.provider_enum = {} # We assume that for any given provider, all functions using # it will have the same loader. This lets us generate a # general C function for detecting conditions and calling the # dlsym/getprocaddress, and have our many resolver stubs just # call it with a table of values. for func in self.functions.values(): for provider in func.providers.values(): if provider.condition_name in self.provider_enum: assert(self.provider_condition[provider.condition_name] == provider.condition) assert(self.provider_loader[provider.condition_name] == provider.loader) continue self.provider_enum[provider.condition_name] = provider.enum; self.provider_condition[provider.condition_name] = provider.condition; self.provider_loader[provider.condition_name] = provider.loader; def sort_functions(self): self.sorted_functions = sorted(self.functions.values(), key=lambda func:func.name) def process_require_statements(self, feature, condition, loader, human_name): for command in feature.findall('require/command'): name = command.get('name') func = self.functions[name] func.add_provider(condition, loader, human_name) def parse_function_providers(self, reg): for feature in reg.findall('feature'): api = feature.get('api') # string gl, gles1, gles2, glx m = re.match('([0-9])\.([0-9])', feature.get('number')) version = int(m.group(1)) * 10 + int(m.group(2)) self.supported_versions.add(feature.get('name')) if api == 'gl': human_name = 'Desktop OpenGL {0}'.format(feature.get('number')) condition = 'epoxy_is_desktop_gl()' loader = 'epoxy_get_core_proc_address({0}, {1})'.format('{0}', version) if version >= 11: condition += ' && epoxy_conservative_gl_version() >= {0}'.format(version) elif api == 'gles2': human_name = 'OpenGL ES {0}'.format(feature.get('number')) condition = '!epoxy_is_desktop_gl() && epoxy_gl_version() >= {0}'.format(version) if version <= 20: loader = 'epoxy_gles2_dlsym({0})' else: loader = 'epoxy_get_proc_address({0})' elif api == 'gles1': human_name = 'OpenGL ES 1.0' condition = '!epoxy_is_desktop_gl() && epoxy_gl_version() == 10' loader = 'epoxy_gles1_dlsym({0})' elif api == 'glx': human_name = 'GLX {0}'.format(version) # We could just always use GPA for loading everything # but glXGetProcAddress(), but dlsym() is a more # efficient lookup. if version > 13: condition = 'epoxy_conservative_glx_version() >= {0}'.format(version) loader = 'glXGetProcAddress((const GLubyte *){0})' else: condition = 'true' loader = 'epoxy_glx_dlsym({0})' elif api == 'egl': human_name = 'EGL {0}'.format(version) if version > 10: condition = 'epoxy_conservative_egl_version() >= {0}'.format(version) loader = 'eglGetProcAddress({0})' else: condition = 'true' loader = 'epoxy_egl_dlsym({0})' else: sys.exit('unknown API: "{0}"'.format(api)) self.process_require_statements(feature, condition, loader, human_name) for extension in reg.findall('extensions/extension'): extname = extension.get('name') self.supported_extensions.add(extname) # 'supported' is a set of strings like gl, gles1, gles2, or glx, which are # separated by '|' apis = extension.get('supported').split('|') if 'glx' in apis: human_name = 'GLX extension \\"{0}\\"'.format(extname) condition = 'epoxy_conservative_has_glx_extension("{0}")'.format(extname) loader = 'glXGetProcAddress((const GLubyte *){0})' self.process_require_statements(extension, condition, loader, human_name) if 'egl' in apis: human_name = 'EGL extension \\"{0}\\"'.format(extname) condition = 'epoxy_conservative_has_egl_extension("{0}")'.format(extname) loader = 'eglGetProcAddress({0})' self.process_require_statements(extension, condition, loader, human_name) if {'gl', 'gles1', 'gles2'}.intersection(apis): human_name = 'GL extension \\"{0}\\"'.format(extname) condition = 'epoxy_conservative_has_gl_extension("{0}")'.format(extname) loader = 'epoxy_get_proc_address({0})' self.process_require_statements(extension, condition, loader, human_name) def fixup_bootstrap_function(self, name, loader): # We handle glGetString() and glGetIntegerv() specially, because we # need to use them in the process of deciding on loaders for # resolving, and the naive code generation would result in their # resolvers calling their own resolvers. if name not in self.functions: return func = self.functions[name] func.providers = {} func.add_provider('true', loader, 'always present') def parse(self, file): reg = ET.parse(file) if reg.find('comment') != None: self.copyright_comment = reg.find('comment').text else: self.copyright_comment = '' self.parse_typedefs(reg) self.parse_enums(reg) self.parse_function_definitions(reg) self.parse_function_providers(reg) def write_copyright_comment_body(self): for line in self.copyright_comment.splitlines(): if '-----' in line: break self.outln(' * ' + line) def write_enums(self): for name in sorted(self.supported_versions): self.outln('#define {0} 1'.format(name)) self.outln('') for name in sorted(self.supported_extensions): self.outln('#define {0} 1'.format(name)) self.outln('') for name, value in self.enums.items(): self.outln('#define ' + name.ljust(self.max_enum_name_len + 3) + value + '') def write_function_ptr_typedefs(self): for func in self.sorted_functions: self.outln('typedef {0} (*{1})({2});'.format(func.ret_type, func.ptr_type, func.args_decl)) def write_header_header(self, file): self.out_file = open(file, 'w') self.outln('/* GL dispatch header.') self.outln(' * This is code-generated from the GL API XML files from Khronos.') self.write_copyright_comment_body() self.outln(' */') self.outln('') self.outln('#pragma once') self.outln('#include ') self.outln('#include ') self.outln('') def write_header(self, file): self.write_header_header(file) if self.target != "gl": self.outln('#include "epoxy/gl_generated.h"') if self.target == "egl": self.outln('#include "EGL/eglplatform.h"') else: # Add some ridiculous inttypes.h redefinitions that are from # khrplatform.h and not included in the XML. self.outln('typedef int8_t khronos_int8_t;') self.outln('typedef int16_t khronos_int16_t;') self.outln('typedef int32_t khronos_int32_t;') self.outln('typedef int64_t khronos_int64_t;') self.outln('typedef uint8_t khronos_uint8_t;') self.outln('typedef uint16_t khronos_uint16_t;') self.outln('typedef uint32_t khronos_uint32_t;') self.outln('typedef uint64_t khronos_uint64_t;') self.outln('typedef float khronos_float_t;') self.outln('typedef intptr_t khronos_intptr_t;') self.outln('typedef ptrdiff_t khronos_ssize_t;') self.outln('typedef uint64_t khronos_utime_nanoseconds_t;') self.outln('typedef int64_t khronos_stime_nanoseconds_t;') if self.target == "glx": self.outln('#include ') self.outln('#include ') self.out(self.typedefs) self.outln('') self.write_enums() self.outln('') self.write_function_ptr_typedefs() for func in self.sorted_functions: self.outln('{0} epoxy_{1}({2});'.format(func.ret_type, func.name, func.args_decl)) self.outln('') def write_proto_define_header(self, file, style): self.write_header_header(file) for func in self.sorted_functions: self.outln('#define {0} epoxy_{1}{0}'.format(func.name, style)) def write_function_ptr_resolver(self, func): self.outln('static {0}'.format(func.ptr_type)) self.outln('epoxy_{0}_resolver(void)'.format(func.name)) self.outln('{') providers = [] # Make a local list of all the providers for this alias group for provider in func.providers.values(): providers.append(provider) for alias_func in func.alias_exts: for provider in alias_func.providers.values(): providers.append(provider) self.outln(' static const enum {0}_provider providers[] = {{'.format(self.target)) for provider in providers: self.outln(' {0},'.format(provider.enum)) self.outln(' {0}_provider_terminator'.format(self.target)) self.outln(' };') self.outln(' static const char *entrypoints[] = {') for provider in providers: self.outln(' "{0}",'.format(provider.name)) self.outln(' };') self.outln(' return {0}_provider_resolver("{1}",'.format(self.target, func.name)) self.outln(' providers, entrypoints);') self.outln('}') self.outln('') def write_dispatch_table_stub(self, func): # Use the same resolver for all the aliases of a particular # function. alias_name = func.name if func.alias_name: alias_name = func.alias_name dispatch_table_entry = 'dispatch_table->p{0}'.format(alias_name) if func.name in self.wrapped_functions: function_name = func.name + '_unwrapped' public = '' else: function_name = func.name public = 'PUBLIC ' self.outln('{0}{1}'.format(public, func.ret_type)) self.outln('epoxy_{0}({1})'.format(function_name, func.args_decl)) self.outln('{') self.outln(' if (!{0})'.format(dispatch_table_entry)) self.outln(' {0} = epoxy_{1}_resolver();'.format(dispatch_table_entry, alias_name)) self.outln('') if func.ret_type == 'void': self.outln(' {0}({1});'.format(dispatch_table_entry, func.args_list)) else: self.outln(' return {0}({1});'.format(dispatch_table_entry, func.args_list)) self.outln('}') self.outln('') def write_provider_enums(self): self.outln('enum {0}_provider {{'.format(self.target)) sorted_providers = sorted(self.provider_enum.keys()) # We always put a 0 enum first so that we can have a # terminator in our arrays self.outln(' {0}_provider_terminator = 0,'.format(self.target)) for human_name in sorted_providers: enum = self.provider_enum[human_name] self.outln(' {0},'.format(enum)) self.outln('};') self.outln('') def write_provider_enum_strings(self): self.outln('static const char *enum_strings[] = {') sorted_providers = sorted(self.provider_enum.keys()) for human_name in sorted_providers: enum = self.provider_enum[human_name] self.outln(' [{0}] = "{1}",'.format(enum, human_name)) self.outln('};') self.outln('') def write_provider_resolver(self): self.outln('static void *{0}_provider_resolver(const char *name,'.format(self.target)) self.outln(' const enum {0}_provider *providers,'.format(self.target)) self.outln(' const char **entrypoints)') self.outln('{') self.outln(' int i;') self.outln(' for (i = 0; providers[i] != {0}_provider_terminator; i++) {{'.format(self.target)) self.outln(' switch (providers[i]) {') for human_name in sorted(self.provider_enum.keys()): enum = self.provider_enum[human_name] self.outln(' case {0}:'.format(enum)) self.outln(' if ({0})'.format(self.provider_condition[human_name])) self.outln(' return {0};'.format(self.provider_loader[human_name]).format("entrypoints[i]")) self.outln(' break;') self.outln(' case {0}_provider_terminator:'.format(self.target)) self.outln(' abort(); /* Not reached */') self.outln(' }') self.outln(' }') self.outln('') # If the function isn't provided by any known extension, print # something useful for the poor application developer before # aborting. (In non-epoxy GL usage, the app developer would # call into some blank stub function and segfault). self.outln(' epoxy_print_failure_reasons(name, enum_strings, (const int *)providers);') self.outln(' abort();') self.outln('}') self.outln('') def write_source(self, file): self.out_file = open(file, 'w') self.outln('/* GL dispatch code.') self.outln(' * This is code-generated from the GL API XML files from Khronos.') self.write_copyright_comment_body() self.outln(' */') self.outln('') self.outln('#include ') self.outln('#include ') self.outln('') self.outln('#include "dispatch_common.h"') self.outln('#include "epoxy/{0}.h"'.format(self.target)) self.outln('') self.outln('struct dispatch_table {') for func in self.sorted_functions: # Aliases don't get their own slot, since they use a shared resolver. if not func.alias_name: self.outln(' {0} p{1};'.format(func.ptr_type, func.name)) self.outln('};') self.outln('') self.outln('/* XXX: Make this thread-local and swapped on makecurrent. */') self.outln('static struct dispatch_table local_dispatch_table;') self.outln('static struct dispatch_table *dispatch_table = &local_dispatch_table;') self.outln('') self.write_provider_enums() self.write_provider_enum_strings() self.write_provider_resolver() for func in self.sorted_functions: if not func.alias_func: self.write_function_ptr_resolver(func) for func in self.sorted_functions: self.write_dispatch_table_stub(func) argparser = argparse.ArgumentParser(description='Generate GL dispatch wrappers.') argparser.add_argument('files', metavar='file.xml', nargs='+', help='GL API XML files to be parsed') argparser.add_argument('--dir', metavar='dir', required=True, help='Destination directory') args = argparser.parse_args() srcdir = args.dir + '/src/' incdir = args.dir + '/include/epoxy/' for file in args.files: name = os.path.basename(file).split('.xml')[0] generator = Generator(name) generator.parse(file) generator.drop_weird_glx_functions() generator.sort_functions() generator.resolve_aliases() generator.fixup_bootstrap_function('glGetString', 'epoxy_get_proc_address({0})') generator.fixup_bootstrap_function('glGetIntegerv', 'epoxy_get_proc_address({0})') # While this is technically exposed as a GLX extension, it's # required to be present as a public symbol by the Linux OpenGL # ABI. generator.fixup_bootstrap_function('glXGetProcAddress', 'epoxy_glx_dlsym({0})') generator.prepare_provider_enum() generator.write_header(incdir + name + '_generated.h') generator.write_proto_define_header(incdir + name + '_generated_vtable_defines.h', '') generator.write_source(srcdir + name + '_generated_dispatch.c')