[Piglit] [PATCH 2/6] dispatch: Generate piglit-dispatch from Khronos XML

Chad Versace chad.versace at linux.intel.com
Mon Jun 16 09:04:58 PDT 2014


Khronos now generates its headers from XML and no longer maintains the
old, crufty *.spec files. Piglit should do the same. Otherwise,
piglit-dispatch won't be able to pick up new extension functions.

As a really really big bonus, after Piglit starts generating its GL
dispatch code from gl.xml, it's a small step to start generating EGL and
GLX dispatch from egl.xml and glx.xml.

This patch imports 'gl.xml' into a new toplevel 'registry directory, to
follow the precedent of libepoxy.

I did *not* try to redesign piglit-dispatch in this patch. To the
contrary, I attempted to keep the newly generated dispatch code to be as
similar as possible as the old generated code. I did want to clean up
piglit-dispatch's design , but I refrained because "a patch should do
one thing, and do it well".

I strove to keep separate concerns in separate files. File
registry/gl.py parses registry/gl.xml. File tests/util/gen_dispatch.py
consumes the parsing result to generate the code. This decision kept
gen_dispatch.py small and focused.

I hope everyone finds the rewritten gen_dispatch.py more maintainable
and easy to read.

The generated code has changed as following:

  - It now contains the GLES1 API, because gl.xml contains information
    on all OpenGL APIs.

  - The comment block for each function alias set now contains more
    information. For each function in the set, it now lists the
    complete set of providers.

    For example:
      /* glActiveTexture (GL_VERSION_1_3) (GL_VERSION_ES_CM_1_0) (GL_ES_VERSION_2_0) */
      /* glActiveTextureARB (GL_ARB_multitexture) */
      extern PFNGLACTIVETEXTUREPROC piglit_dispatch_glActiveTexture;
      #define glActiveTexture piglit_dispatch_glActiveTexture
      #define glActiveTextureARB piglit_dispatch_glActiveTexture

  - Enums are sorted by group then by value. Old dispatch sorted only by
    value.

    For example:
      /* Enum Group MapBufferUsageMask */
      #define GL_MAP_READ_BIT 0x0001
      #define GL_MAP_READ_BIT_EXT 0x0001
      #define GL_MAP_WRITE_BIT 0x0002
      #define GL_MAP_WRITE_BIT_EXT 0x0002
      ...

  - After function resolve_glCheeseCake resolved the proc address for
    glCheeseCake, old dispatch assigned the proc address to
    piglit_dispatch_glCheeseCake. Function resolve_glCheeseCake returns
    the proc address in new dispatch, and then function
    stub_glCheeseCake performs the assignment to
    piglit_dispatch_glCheeseCake. This difference resulted in no
    behavioral change, and removed the need for superfluous typecasting.

Tested for regressions with:
    piglit run -p x11_egl -x glx -x glean
    Mesa 10.2.1
    Intel Ivybridge
    Fedora 20

Signed-off-by: Chad Versace <chad.versace at linux.intel.com>
---
 cmake/piglit_dispatch.cmake  |   7 +-
 registry/__init__.py         |   0
 registry/gl.py               | 832 +++++++++++++++++++++++++++++++++++++++
 tests/util/gen_dispatch.py   | 916 +++++++++++++------------------------------
 tests/util/piglit-dispatch.h |   3 +-
 5 files changed, 1109 insertions(+), 649 deletions(-)
 create mode 100644 registry/__init__.py
 create mode 100644 registry/gl.py

diff --git a/cmake/piglit_dispatch.cmake b/cmake/piglit_dispatch.cmake
index 0b0a2eb..b4ff2cf 100644
--- a/cmake/piglit_dispatch.cmake
+++ b/cmake/piglit_dispatch.cmake
@@ -19,6 +19,7 @@
 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 # IN THE SOFTWARE.
 
+set(piglit_dispatch_gen_script ${CMAKE_SOURCE_DIR}/tests/util/gen_dispatch.py)
 set(piglit_dispatch_gen_output_dir ${CMAKE_BINARY_DIR}/tests/util)
 
 file(MAKE_DIRECTORY ${piglit_dispatch_gen_output_dir})
@@ -29,14 +30,16 @@ set(piglit_dispatch_gen_outputs
 	)
 
 set(piglit_dispatch_gen_inputs
+	${CMAKE_SOURCE_DIR}/registry/gl.py
+	${CMAKE_SOURCE_DIR}/registry/gl.xml
+	${CMAKE_SOURCE_DIR}/registry/__init__.py
 	${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}
+	COMMAND ${python} ${piglit_dispatch_gen_script} --out-dir ${piglit_dispatch_gen_output_dir}
 	)
 
 add_custom_target(piglit_dispatch_gen
diff --git a/registry/__init__.py b/registry/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/registry/gl.py b/registry/gl.py
new file mode 100644
index 0000000..32adb10
--- /dev/null
+++ b/registry/gl.py
@@ -0,0 +1,832 @@
+# Copyright 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 (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.
+
+"""
+Parse gl.xml into Python objects.
+"""
+
+import os.path
+import re
+import sys
+import xml.etree.ElementTree as ElementTree
+from collections import namedtuple, OrderedDict
+from textwrap import dedent
+
+# Export 'debug' so other Piglit modules can easily enable it.
+debug = False
+
+def _log_debug(msg):
+    if debug:
+        sys.stderr.write('debug: {0}: {1}\n'.format(__name__, msg))
+
+def parse():
+    """Parse gl.xml and return a Registry object."""
+    filename = os.path.join(os.path.dirname(__file__), 'gl.xml')
+    elem = ElementTree.parse(filename)
+    xml_registry = elem.getroot()
+    return Registry(xml_registry)
+
+class OrderedKeyedSet(object):
+    """A set with keyed elements that preserves order of element insertion.
+
+    Why waste words? Let's document the class with example code.
+
+    Example:
+        Cheese = namedtuple('Cheese', ('name', 'flavor'))
+        cheeses = OrderedKeyedSet(key='name')
+        cheeses.add(Cheese(name='cheddar', flavor='good'))
+        cheeses.add(Cheese(name='gouda', flavor='smells like feet'))
+        cheeses.add(Cheese(name='romano', flavor='awesome'))
+
+        # Elements are retrievable by key.
+        assert(cheeses['gouda'].flavor == 'smells like feet')
+
+        # On key collision, the old element is removed.
+        cheeses.add(Cheese(name='gouda', flavor='ok i guess'))
+        assert(cheeses['gouda'].flavor == 'ok i guess')
+
+        # The set preserves order of insertion. Replacement does not alter
+        # order.
+        assert(list(cheeses)[2].name == 'romano')
+
+        # The set is iterable.
+        for cheese in cheeses:
+            print(cheese.name)
+
+        # Yet another example...
+        Bread = namedtuple('Bread', ('name', 'smell'))
+        breads = OrderedKeyedSet(key='name')
+        breads.add(Bread(name='como', smell='subtle')
+        breads.add(Bread(name='sourdough', smell='pleasant'))
+
+        # The set supports some common set operations, such as union.
+        breads_and_cheeses = breads | cheeses
+        assert(len(breads_and_cheeses) == len(breads) + len(cheeses))
+    """
+
+    def __init__(self, key, elems=()):
+        """Create a new set with the given key.
+
+        The given 'key' defines how to calculate each element's key value.  If
+        'key' is a string, then each key value is defined to be `getattr(elem, key)`.
+        If 'key' is a function, then the key value is `key(elem)`.
+        """
+        if isinstance(key, str):
+            self.__key_func = lambda elem: getattr(elem, key)
+        else:
+            self.__key_func = key
+
+        self.__dict = OrderedDict()
+        for i in elems:
+            self.add(i)
+
+    def __or__(x, y):
+        """Same as `union`."""
+        return x.union(y)
+
+    def __contains__(self, key):
+        return key in self.__dict
+
+    def __delitem__(self, key):
+        del self.__dict[key]
+
+    def __getitem__(self, key):
+        return self.__dict[key]
+
+    def __iter__(self):
+        return self.__dict.itervalues()
+
+    def __len__(self):
+        return len(self.__dict)
+
+    def __repr__(self):
+        return '{0}({1})'.format(self.__class__.__name__, list(self.keys()))
+
+    def copy(self):
+        """Return shallow copy."""
+        other = OrderedKeyedSet(key=self.__key_func)
+        other.__dict = self.__dict.copy()
+        return other
+
+    def add(self, x):
+        self.__dict[self.__key_func(x)] = x
+
+    def clear(self):
+        self.__dict.clear()
+
+    def extend(self, elems):
+        for e in elems:
+            self.add(e)
+
+    def get(self, key, default):
+        return self.__dict.get(key, default)
+
+    def get_key(self, x):
+        return self.__key_func(x)
+
+    def items(self, x):
+        return self.__dict.iteritems()
+
+    def keys(self):
+        return self.__dict.iterkeys()
+
+    def values(self):
+        return self.__dict.itervalues()
+
+    def pop(self, key):
+        return self.__dict.pop(key)
+
+    def sort_by_key(self):
+        self.__dict = OrderedDict(sorted(self.items(), key=lambda i: i[0]))
+
+    def sort_by_value(self):
+        self.__dict = OrderedDict(sorted(self.items(), key=lambda i: i[1]))
+
+    def union(self, other):
+        """Return the union of two sets as a new set.
+
+        The the new set is ordered so that all elements of self precede those
+        of other, and the order of elements within each origin set is
+        preserved.
+        
+        The new set's key function is taken from self.  On key collisions, set
+        y has precedence over x.
+        """
+        u = self.copy()
+        u.extend(other)
+        return u
+
+class Registry(object):
+    """The toplevel <registry> element.
+
+    Attributes:
+        features: An OrderedKeyedSet of class Feature, where each element
+        represents a <feature> element.
+
+        extensions: An OrderedKeyedSet of class Extension, where each element
+        represents a <extension> element.
+
+        commands: An OrderedKeyedSet of class Command, where each element
+        represents a <command> element.
+
+        enum_groups: An ordered collection of class EnumGroup, where each
+        element represents an <enums> element.
+
+        enums: An OrderedKeyedSet of class Enum, where each element
+        represents an <enum> element.
+
+        vendor_namespaces: A collection of all vendor prefixes and suffixes.
+        For example, "ARB", "EXT", "CHROMIUM", "NV".
+    """
+
+    def __init__(self, xml_registry):
+        """Parse the <registry> element."""
+
+        assert(xml_registry.tag == 'registry')
+
+        self.features = OrderedKeyedSet(
+            key='name', elems=(
+                Feature(xml_feature)
+                for xml_feature in xml_registry.iterfind('./feature')
+            )
+        )
+
+        self.extensions = OrderedKeyedSet(
+            key='name', elems=(
+                Extension(xml_extension)
+                for xml_extension in xml_registry.iterfind('./extensions/extension')
+            )
+        )
+
+        self.commands = OrderedKeyedSet(
+            key='name', elems=(
+                Command(xml_command)
+                for xml_command in xml_registry.iterfind('./commands/command')
+            )
+        )
+
+        self.enum_groups = [
+            EnumGroup(xml_enums)
+            for xml_enums in xml_registry.iterfind('./enums')
+        ]
+
+        self.__fix_enum_groups()
+
+        self.enums = OrderedKeyedSet(
+            key='name', elems=(
+                enum
+                for group in self.enum_groups
+                for enum in group.enums
+                if not enum.is_collider
+            )
+        )
+
+        self.vendor_namespaces = {
+            extension.vendor_namespace
+            for extension in self.extensions
+            if extension.vendor_namespace is not None
+        }
+
+        for feature in self.features:
+            feature._link_commands(self.commands)
+            feature._link_enums(self.enums)
+
+        for extension in self.extensions:
+            extension._link_commands(self.commands)
+            extension._link_enums(self.enums)
+
+        self.__set_command_name_parts()
+
+    def __fix_enum_groups(self):
+        for enum_group in self.enum_groups:
+            if (enum_group.name is None
+                and enum_group.vendor == 'SGI'
+                and enum_group.start == '0x8000'
+                and enum_group.end == '0x80BF'):
+                   self.enum_groups.remove(enum_group)
+            elif (enum_group.name is None
+                  and enum_group.vendor == 'ARB'
+                  and enum_group.start == None
+                  and enum_group.end == None):
+                     enum_group.start = '0x8000'
+                     enum_group.start = '0x80BF'
+
+    def __set_command_name_parts(self):
+        suffix_choices = '|'.join(self.vendor_namespaces)
+        regex = '^(?P<basename>[a-zA-Z0-9_]+?)(?P<vendor_suffix>{suffix_choices})?$'
+        regex = regex.format(suffix_choices=suffix_choices)
+        _log_debug('setting command name parts with regex {0!r}'.format(regex))
+        regex = re.compile(regex)
+        for command in self.commands:
+            command._set_name_parts(regex)
+
+class Feature(object):
+    """A <feature> XML element.
+
+    Attributes:
+        name: The XML element's 'name' attribute.
+
+        api: The XML element's 'api' attribute.
+
+        version_str:  The XML element's 'number' attribute. For example, "3.1".
+
+        version_float: float(version_str)
+
+        version_int: int(10 * version_float)
+
+        commands: An OrderedKeyedSet of class Command that contains all
+            <command> subelements.
+
+        enums: An OrderedKeyedSet of class Enum that contains all <enum>
+            subelements.
+    """
+
+    def __init__(self, xml_feature):
+        """Parse a <feature> element."""
+
+        # Example <feature> element:
+        #
+        #    <feature api="gles2" name="GL_ES_VERSION_3_1" number="3.1">
+        #        <!-- arrays_of_arrays features -->
+        #        <require/>
+        #        <!-- compute_shader features -->
+        #        <require>
+        #            <command name="glDispatchCompute"/>
+        #            <command name="glDispatchComputeIndirect"/>
+        #            <enum name="GL_COMPUTE_SHADER"/>
+        #            <enum name="GL_MAX_COMPUTE_UNIFORM_BLOCKS"/>
+        #            ...
+        #        </require>
+        #        <!-- draw_indirect features -->
+        #        <require>
+        #            <command name="glDrawArraysIndirect"/>
+        #            <command name="glDrawElementsIndirect"/>
+        #            <enum name="GL_DRAW_INDIRECT_BUFFER"/>
+        #            <enum name="GL_DRAW_INDIRECT_BUFFER_BINDING"/>
+        #        </require>
+        #        ...
+        #    </feature>
+
+        assert(xml_feature.tag == 'feature')
+        self._xml_feature = xml_feature
+
+        # Parse the <feature> tag's attributes.
+        self.api = xml_feature.get('api')
+        self.name = xml_feature.get('name')
+
+        self.version_str = xml_feature.get('number')
+        self.version_float = float(self.version_str)
+        self.version_int = int(10 * self.version_float)
+
+        self.commands = OrderedKeyedSet(key='name')
+        self.enums = OrderedKeyedSet(key='name')
+
+    def _link_commands(self, commands):
+        """Parse <command> subelements and link them to the Command objects in
+        'commands'.
+        """
+        for xml_command in self._xml_feature.iterfind('./require/command'):
+            command_name = xml_command.get('name')
+            command = commands[command_name]
+            assert(isinstance(command, Command))
+            _log_debug('link command {0!r} and feature {1!r})'.format(command.name, self.name))
+            self.commands.add(command)
+            command.features.add(self)
+
+    def _link_enums(self, enums):
+        """Parse <enum> subelements and link them to the Command objects in
+        'enums'.
+        """
+        for xml_enum in self._xml_feature.iterfind('./require/enum'):
+            enum_name = xml_enum.get('name')
+            enum = enums[enum_name]
+            assert(isinstance(enum, Enum))
+            _log_debug('link command {0!r} and feature {1!r}'.format(enum.name, self.name))
+            self.enums.add(enum)
+            enum.features.add(self)
+
+class Extension(object):
+    """An <extension> XML element.
+
+    Attributes:
+        name: The XML element's 'name' attribute.
+
+        supported_apis: The collection of api strings in the XML element's
+            'supported' attribute. For example, ['gl', 'glcore'].
+
+        vendor_namespace: For example, "AMD". May be None.
+
+        commands: An OrderedKeyedSet of class Command that contains all
+            <command> subelements.
+
+        enums: An OrderedKeyedSet of class Enum that contains all <enum>
+            subelements.
+    """
+
+    __vendor_regex = re.compile(r'^GL_(?P<vendor_namespace>[A-Z]+)_')
+
+    def __init__(self, xml_extension):
+        """Parse an <extension> element."""
+
+        # Example <extension> element:
+        #     <extension name="GL_ARB_ES2_compatibility" supported="gl|glcore">
+        #         <require>
+        #             <enum name="GL_FIXED"/>
+        #             <enum name="GL_IMPLEMENTATION_COLOR_READ_TYPE"/>
+        #             ...
+        #             <command name="glReleaseShaderCompiler"/>
+        #             <command name="glShaderBinary"/>
+        #             ...
+        #         </require>
+        #     </extension>
+
+        assert(xml_extension.tag == 'extension')
+        self._xml_extension = xml_extension
+
+        # Parse the <extension> tag's attributes.
+        self.name = xml_extension.get('name')
+        self.supported_apis = xml_extension.get('supported').split('|')
+
+        self.vendor_namespace = None
+        match = Extension.__vendor_regex.match(self.name)
+        if match is not None:
+            self.vendor_namespace = match.groupdict().get('vendor_namespace', None)
+
+        self.commands = OrderedKeyedSet(key='name')
+        self.enums = OrderedKeyedSet(key='name')
+
+    def __cmp__(x, y):
+        """Compare by name."""
+        return cmp(x.name, y.name)
+
+    def _link_commands(self, commands):
+        """Parse <command> subelements and link them to the Command objects in
+        'commands'.
+        """
+        for xml_command in self._xml_extension.iterfind('./require/command'):
+            command_name = xml_command.get('name')
+            command = commands[command_name]
+            assert(isinstance(command, Command))
+            _log_debug('link command {0!r} and extension {1!r}'.format(command.name, self.name))
+            self.commands.add(command)
+            command.extensions.add(self)
+
+    def _link_enums(self, enums):
+        """Parse <enum> subelements and link them to the Command objects in
+        'enums'.
+        """
+        for xml_enum in self._xml_extension.iterfind('./require/enum'):
+            enum_name = xml_enum.get('name')
+            enum = enums[enum_name]
+            assert(isinstance(enum, Enum))
+            _log_debug('link command {0!r} and extension {1!r}'.format(enum.name, self.name))
+            self.enums.add(enum)
+            enum.extensions.add(self)
+
+class CommandParam(object):
+    """A <param> XML element at path command/param.
+
+    Attributes:
+        name
+        c_type
+    """
+    
+    __PARAM_NAME_FIXES = {'near': 'hither', 'far': 'yon'}
+
+    def __init__(self, xml_param, log=None):
+        """Parse a <param> element."""
+
+        # Example <param> elements:
+        #
+        #    <param>const <ptype>GLchar</ptype> *<name>name</name></param>
+        #    <param len="1"><ptype>GLsizei</ptype> *<name>length</name></param>
+        #    <param len="bufSize"><ptype>GLint</ptype> *<name>values</name></param>
+        #    <param><ptype>GLenum</ptype> <name>shadertype</name></param>
+        #    <param group="sync"><ptype>GLsync</ptype> <name>sync</name></param>
+
+        assert(xml_param.tag == 'param')
+
+        self.name = xml_param.find('./name').text
+
+        # Rename the parameter if its name is a reserved keyword in MSVC.
+        self.name =  self.__PARAM_NAME_FIXES.get(self.name, self.name)
+
+        # Pare the C type.
+        c_type_text = list(xml_param.itertext())
+        c_type_text.pop(-1) # Pop off the text from the <name> subelement.
+        c_type_text = (t.strip() for t in c_type_text)
+        self.c_type = ' '.join(c_type_text).strip()
+
+        _log_debug('parsed {0}'.format(self))
+
+    def __repr__(self):
+        templ = (
+            '{self.__class__.__name__}('
+            'name={self.name!r}, '
+            'type={self.c_type!r})')
+        return templ.format(self=self)
+        
+class Command(object):
+    """A <command> XML element.
+
+    Attributes:
+        name: The XML element's 'name' attribute.
+
+        features: An OrderedKeyedSet of class Feature that enumerates all
+            features that require this command.
+
+        extensions: An OrderedKeyedSet of class Extensions that enumerates all
+            extensions that require this command.
+
+        c_return_type: For example, "void *".
+
+        alias: The XML element's 'alias' element. May be None.
+
+        param_list: List of class CommandParam that contains all <param>
+            subelements.
+    """
+    
+    def __init__(self, xml_command):
+        """Parse a <command> element."""
+
+        # Example <command> element:
+        #
+        #    <command>
+        #        <proto>void <name>glTexSubImage2D</name></proto>
+        #        <param group="TextureTarget"><ptype>GLenum</ptype> <name>target</name></param>
+        #        <param group="CheckedInt32"><ptype>GLint</ptype> <name>level</name></param>
+        #        <param group="CheckedInt32"><ptype>GLint</ptype> <name>xoffset</name></param>
+        #        <param group="CheckedInt32"><ptype>GLint</ptype> <name>yoffset</name></param>
+        #        <param><ptype>GLsizei</ptype> <name>width</name></param>
+        #        <param><ptype>GLsizei</ptype> <name>height</name></param>
+        #        <param group="PixelFormat"><ptype>GLenum</ptype> <name>format</name></param>
+        #        <param group="PixelType"><ptype>GLenum</ptype> <name>type</name></param>
+        #        <param len="COMPSIZE(format,type,width,height)">const void *<name>pixels</name></param>
+        #        <glx type="render" opcode="4100"/>
+        #        <glx type="render" opcode="332" name="glTexSubImage2DPBO" comment="PBO protocol"/>
+        #    </command>
+        #
+
+        assert(xml_command.tag == 'command')
+        self.__xml_command = xml_command
+
+        xml_proto = xml_command.find('./proto')
+        self.name = xml_proto.find('./name').text
+        _log_debug('start parsing Command(name={0!r})'.format(self.name))
+
+        self.features = OrderedKeyedSet(key='name')
+        self.extensions = OrderedKeyedSet(key='name')
+
+        # Parse the return type from the <proto> element.
+        #
+        # Example of a difficult <proto> element:
+        #     <proto group="String">const <ptype>GLubyte</ptype> *<name>glGetStringi</name></proto>
+        c_return_type_text = list(xml_proto.itertext())
+        c_return_type_text.pop(-1) # Pop off the text from the <name> subelement.
+        c_return_type_text = (t.strip() for t in c_return_type_text)
+        self.c_return_type = ' '.join(c_return_type_text).strip()
+
+        # Parse alias info, if any.
+        xml_alias = xml_command.find('./alias')
+        if xml_alias is None:
+            self.alias = None
+        else:
+            self.alias = xml_alias.get('name')
+
+        self.param_list = [
+            CommandParam(xml_param)
+            for xml_param in xml_command.iterfind('./param')
+        ]
+
+        _log_debug(('parsed {self.__class__.__name__}('
+                     'name={self.name!r}, '
+                     'alias={self.alias!r}, '
+                     'prototype={self.c_prototype!r}, '
+                     'features={self.features}, '
+                     'extensions={self.extensions})').format(self=self))
+
+    def __cmp__(x, y):
+        return cmp(x.name, y.name)
+
+    def __repr__(self):
+        cls = self.__class__
+        return '{cls.__name__}({self.name!r})'.format(**locals())
+
+    @property
+    def c_prototype(self):
+        """For example, "void glAccum(GLenum o, GLfloat value)"."""
+        return '{self.c_return_type} {self.name}({self.c_named_param_list})'.format(self=self)
+
+    @property
+    def c_funcptr_typedef(self):
+        """For example, "PFNGLACCUMROC" for glAccum."""
+        return 'PFN{0}PROC'.format(self.name).upper()
+
+    @property
+    def c_named_param_list(self):
+        """For example, "GLenum op, GLfloat value" for glAccum."""
+        return ', '.join([
+            '{param.c_type} {param.name}'.format(param=param)
+            for param in self.param_list
+        ])
+
+    @property
+    def c_unnamed_param_list(self):
+        """For example, "GLenum, GLfloat" for glAccum."""
+        return ', '.join([
+            param.c_type
+            for param in self.param_list
+        ])
+
+    @property
+    def c_untyped_param_list(self):
+        """For example, "op, value" for glAccum."""
+        return ', '.join([
+            param.name
+            for param in self.param_list
+        ])
+
+    @property
+    def requirements(self):
+        """The union of features and extensions that require this commnand."""
+        return self.features | self.extensions
+
+    def _set_name_parts(self, regex):
+        groups = regex.match(self.name).groupdict()
+        self.basename = groups['basename']
+        self.vendor_suffix = groups.get('vendor_suffix', None)
+
+class EnumGroup(object):
+    """An <enums> element at path registry/enums.
+
+    Attributes:
+        name: The XML element's 'group' attribute. If the XML does not define
+            'group', then this class invents one.
+
+        type: The XML element's 'type' attribute. If the XML does not define
+            'type', then this class invents one.
+
+        start, end: The XML element's 'start' and 'end' attributes. May be
+            None.
+
+        enums: An OrderedKeyedSet of class Enum that contains all <enum>
+            sublements.
+    """
+
+    # Each EnumGroup belongs to exactly one member of EnumGroup.TYPES.
+    #
+    # Some members in EnumGroup.TYPES are invented and not present in gl.xml.
+    # The only enum type defined explicitly in gl.xml is "bitmask", which
+    # occurs as <enums type="bitmask">.  However, in gl.xml each block of
+    # non-bitmask enums is introduced by a comment that describes the block's
+    # "type", even if the <enums> tag lacks a 'type' attribute. (Thanks,
+    # Khronos, for encoding data in XML comments rather than the XML itself).
+    # EnumGroup.TYPES lists such implicit comment-only types, with invented
+    # names, alongside the types explicitly defined by <enums type=>.
+    TYPES = (
+        # Type 'default_namespace' is self-explanatory. It indicates the large
+        # set of enums from 0x0000 to 0xffff that includes, for example,
+        # GL_POINTS and GL_TEXTURE_2D.
+        'default_namespace',
+
+        # Type 'bitmask' is self-explanatory.
+        'bitmask',
+
+        # Type 'small_index' indicates a small namespace of non-bitmask enums.
+        # As of Khronos revision 26792, 'small_index' groups generally contain
+        # small numbers used for indexed access.
+        'small_index',
+
+        # Type 'special' is used only for the group named "SpecialNumbers". The
+        # group contains enums such as GL_FALSE, GL_ZERO, and GL_INVALID_INDEX.
+        'special',
+    )
+
+    @staticmethod
+    def __fix_xml(xml_elem):
+        """Add missing attributes and fix misnamed ones."""
+        if xml_elem.get('namespace') == 'OcclusionQueryEventMaskAMD':
+            # This tag's attributes are totally broken.
+            xml_elem.set('namespace', 'GL')
+            xml_elem.set('group', 'OcclusionQueryEventMaskAMD')
+            xml_elem.set('type', 'bitmask')
+        elif xml_elem.get('vendor', None) == 'ARB':
+            enums = xml_elem.findall('./enum')
+            if (len(enums) != 0
+                    and enums[0].get('value') == '0x8000'
+                    and enums[-1].get('value') == '0x80BF'):
+                # This tag lacks 'start' and 'end' attributes.
+                xml_elem.set('start','0x8000')
+                xml_elem.set('end', '0x80BF')
+
+    def __init__(self, xml_enums):
+        """Parse an <enums> element."""
+
+        # Example of a bitmask group:
+        #
+        #     <enums namespace="GL" group="SyncObjectMask" type="bitmask">
+        #         <enum value="0x00000001" name="GL_SYNC_FLUSH_COMMANDS_BIT"/>
+        #         <enum value="0x00000001" name="GL_SYNC_FLUSH_COMMANDS_BIT_APPLE"/>
+        #     </enums>
+        #
+        # Example of a group that resides in OpenGL's default enum namespace:
+        #
+        #     <enums namespace="GL" start="0x0000" end="0x7FFF" vendor="ARB" comment="...">
+        #         <enum value="0x0000" name="GL_POINTS"/>
+        #         <enum value="0x0001" name="GL_LINES"/>
+        #         <enum value="0x0002" name="GL_LINE_LOOP"/>
+        #         ...
+        #     </enums>
+        #
+        # Example of a non-bitmask group that resides outside OpenGL's default
+        # enum namespace:
+        #     
+        #     <enums namespace="GL" group="PathRenderingTokenNV" vendor="NV">
+        #         <enum value="0x00" name="GL_CLOSE_PATH_NV"/>
+        #         <enum value="0x02" name="GL_MOVE_TO_NV"/>
+        #         <enum value="0x03" name="GL_RELATIVE_MOVE_TO_NV"/>
+        #         ...
+        #     </enums>
+        #
+
+        EnumGroup.__fix_xml(xml_enums)
+
+        self.name = xml_enums.get('group', None)
+        _log_debug(('start parsing {self.__class__.__name__}('
+                    'name={self.name!r}').format(self=self))
+        self.type = xml_enums.get('type', None)
+        self.start = xml_enums.get('start', None)
+        self.end = xml_enums.get('end', None)
+        self.enums = []
+
+        self.__invent_name_and_type()
+        assert(self.name is not None)
+        assert(self.type in self.TYPES)
+
+        # Parse the <enum> subelements.
+        _log_debug('start parsing subelements of {self}'.format(self=self))
+        self.enums = OrderedKeyedSet(
+            key='name', elems=(
+                Enum(self, xml_enum)
+                for xml_enum in xml_enums.iterfind('./enum')
+            )
+        )
+        _log_debug('parsed {0}'.format(self))
+
+    def __repr__(self):
+        return (
+            '{self.__class__.__name__}('
+            'name={self.name!r}, '
+            'type={self.type!r}, '
+            'start={self.start}, '
+            'end={self.end}, '
+            'enums={enums})'
+        ).format(
+            self=self,
+            enums= ', '.join([repr(e) for e in self.enums]),
+        )
+
+    def __invent_name_and_type(self):
+        """If the XML didn't define a name or type, invent one."""
+        if self.name is None:
+            assert(self.type is None)
+            assert(self.start is not None)
+            assert(self.end is not None)
+            self.name = 'range_{self.start}_{self.end}'.format(self=self)
+            self.type = 'default_namespace'
+        elif self.type is None:
+            self.type = 'small_index'
+        elif self.name == 'SpecialNumbers':
+            assert(self.type is None)
+            self.type = 'special'
+
+    def __post_fix_xml(self):
+        if (self.vendor == "ARB"
+                and self.start is None
+                and self.end is None
+                and self.enums[0].num_value == 0x8000
+                and self.enums[-1].num_value == 0x80BF):
+            self.start = '0x8000'
+            self.end = '0x0BF'
+
+class Enum(object):
+    """An <enum> XML element.
+
+    Attributes:
+        name, api: The XML element's 'name' and 'api' attributes.
+
+        str_value, c_num_literal: Equivalent attributes. The XML element's
+            'value' attribute.
+
+        num_value: The long integer for str_value.
+
+        features: An OrderedKeyedSet of class Feature that enumerates all
+            features that require this enum.
+
+        extensions: An OrderedKeyedSet of class Extensions that enumerates all
+            extensions that require this enum.
+    """
+
+    def __init__(self, enum_group, xml_enum):
+        """Parse an <enum> tag located at path registry/enums/enum."""
+
+        # Example <enum> element:
+        #     <enum value="0x0000" name="GL_POINTS"/>
+
+        assert(isinstance(enum_group, EnumGroup))
+        assert(xml_enum.tag == 'enum')
+
+        self.group = enum_group
+        self.name = xml_enum.get('name')
+        self.api = xml_enum.get('api')
+        self.str_value = xml_enum.get('value')
+        self.features = OrderedKeyedSet(key='name')
+        self.extensions = OrderedKeyedSet(key='name')
+        self.c_num_literal = self.str_value
+
+        if '0x' in self.str_value.lower():
+            base = 16
+        else:
+            base = 10
+        self.num_value = long(self.str_value, base)
+
+        _log_debug('parsed {0}'.format(self))
+
+    def __repr__(self):
+        return ('{self.__class__.__name__}('
+                'name={self.name!r}, '
+                'value={self.str_value!r}, '
+                'features={self.features}, '
+                'extensions={self.extensions})').format(self=self)
+
+    @property
+    def is_collider(self):
+        """Does the enum's name collide with another enum's name?"""
+        if self.name == 'GL_ACTIVE_PROGRAM_EXT' and self.api == 'gles2':
+            # This enum has different numerical values in GL (0x8B8D) and
+            # in GLES (0x8259).
+            return True
+        else:
+            return False
+
+    @property
+    def requirements(self):
+        return self.features | self.extensions
diff --git a/tests/util/gen_dispatch.py b/tests/util/gen_dispatch.py
index c97af94..d445932 100644
--- a/tests/util/gen_dispatch.py
+++ b/tests/util/gen_dispatch.py
@@ -1,4 +1,4 @@
-# Copyright 2012 Intel Corporation
+# Copyright 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"),
@@ -19,664 +19,290 @@
 # 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" or "GLES" for a GL spec API, "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>: {
-#       "categories": <list of categories 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 1
-#
-# - A #define for each known GL version, e.g.:
-#
-#   #define GL_VERSION_1_5 1
-#
-#
-# 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
+"""
+Generate C source code from Khronos XML.
+"""
+
+import argparse
+import mako
 import os.path
 import sys
-try:
-    import simplejson as json
-except:
-    import json
-
-
-# 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)
-        if self.kind == 'GLES':
-            return 'GLES {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.categories = json_data['categories']
-
-    # 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)
+from cStringIO import StringIO
+from collections import namedtuple
+from textwrap import dedent
+from mako.template import Template
+
+PIGLIT_TOP_DIR = os.path.join(os.path.dirname(__file__), '..', '..')
+sys.path.append(PIGLIT_TOP_DIR)
+import registry.gl
+from registry.gl import OrderedKeyedSet
+
+PROG_NAME = os.path.basename(sys.argv[0])
+debug = False
+
+def main():
+    global debug
+
+    argparser = argparse.ArgumentParser(prog=PROG_NAME)
+    argparser.add_argument('-o', '--out-dir')
+    argparser.add_argument('-d', '--debug', action='store_true', default=False)
+    args = argparser.parse_args()
+
+    debug = args.debug
+    if args.out_dir is None:
+        argparser.error('missing OUT_DIR')
+
+    registry.gl.debug = debug
+    gl_registry = registry.gl.parse()
+
+    dispatch_h = open(os.path.join(args.out_dir, 'generated_dispatch.h'), 'w')
+    dispatch_c = open(os.path.join(args.out_dir, 'generated_dispatch.c'), 'w')
+    DispatchCode(gl_registry).emit(dispatch_h, dispatch_c)
+    dispatch_h.close()
+    dispatch_c.close()
+
+def log_debug(msg):
+    if debug:
+        sys.stderr.write('debug: {0}: {1}\n'.format(PROG_NAME, msg))
+
+copyright_block = dedent('''\
+    /**
+     * DO NOT EDIT!
+     * This file was generated by the script {PROG_NAME!r}.
+     *
+     * Copyright 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 (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(PROG_NAME=PROG_NAME)
+
+Api = namedtuple('Api', ('name', 'base_version_int', 'c_piglit_token'))
+"""Api corresponds to the XML <feature> tag's 'api' attribute."""
+
+apis = OrderedKeyedSet(
+    key='name', elems=(
+        Api(name='gl',      c_piglit_token='PIGLIT_DISPATCH_GL',  base_version_int=10),
+        Api(name='glcore',  c_piglit_token='PIGLIT_DISPATCH_GL',  base_version_int=10),
+        Api(name='gles1',   c_piglit_token='PIGLIT_DISPATCH_ES1', base_version_int=11),
+        Api(name='gles2',   c_piglit_token='PIGLIT_DISPATCH_ES1', base_version_int=20),
+    )
+)
+
+class CommandAliasMap(OrderedKeyedSet):
+
+    unaliasable_commands = {
+        'glGetObjectParameterivAPPLE',
+        'glGetObjectParameterivARB',
+    }
+
+    def __init__(self, commands=()):
+        OrderedKeyedSet.__init__(self, key='name', elems=commands)
+
+    def add(self, alias_set):
+        assert(isinstance(alias_set, CommandAliasSet))
+        OrderedKeyedSet.add(self, alias_set)
+
+    def add_commands(self, commands):
+        for command in commands:
+            assert(isinstance(command, registry.gl.Command))
+
+            if command.name in CommandAliasMap.unaliasable_commands:
+                alias_name = command.name
             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)
+                alias_name = command.basename
 
+            alias_set = self.get(alias_name, None)
 
-# 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']
+            if alias_set is None:
+                self.add(CommandAliasSet(alias_name, [command]))
+            else:
+                alias_set.add(command)
 
+class CommandAliasSet(OrderedKeyedSet):
 
-# 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' and then 'GLES' 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]
-            for category_name in function.categories:
-                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]
+    Resolution = namedtuple('Resolution', ('c_condition', 'c_get_proc'))
 
-    # 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
+    def __init__(self, name, commands):
+        OrderedKeyedSet.__init__(self, key='name')
+        self.name = name
+        for command in commands:
+            self.add(command)
 
-    # 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
+    def __cmp__(x, y):
+        return cmp(x.name, y.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 == 'GLES':
-            return 1, cat_fn_pair[0].gl_10x_version
-        elif cat_fn_pair[0].kind == 'extension':
-            return 2, 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).
+    def primary_command(self):
+        return sorted(self)[0]
+
     @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 in ('GL', 'GLES'):
-            getter = 'get_core_proc("{0}", {1})'.format(
-                f.gl_name, category.gl_10x_version)
-
-            condition = ''
-            api_base_version = 0
-            if category.kind == 'GL':
-                condition = 'dispatch_api == PIGLIT_DISPATCH_GL'
-                api_base_version = 10
-            elif category.gl_10x_version >= 20:
-                condition = 'dispatch_api == PIGLIT_DISPATCH_ES2'
-                api_base_version = 20
-            else:
-                condition = 'dispatch_api == PIGLIT_DISPATCH_ES1'
-                api_base_version = 11
-
-            # Only check the version for functions that aren't part of the
-            # core for the PIGLIT_DISPATCH api.
-            if category.gl_10x_version != api_base_version:
-                condition = 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))
-
-    # XXX: glDraw{Arrays,Elements}InstancedARB are exposed by
-    # ARB_instanced_arrays in addition to ARB_draw_instanced, but neither
-    # gl.spec nor gl.json can accomodate an extension with two categories, so
-    # insert these cases here.
-        if f.gl_name in ('glDrawArraysInstancedARB',
-                         'glDrawElementsInstancedARB'):
-            condition = 'check_extension("GL_ARB_instanced_arrays")'
-            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
+    def resolutions(self):
+        """Iterable of Resolution actions to obtain the GL proc address."""
+        Resolution = self.__class__.Resolution
+        for command in self:
+            for feature in command.features:
+                api = apis[feature.api]
+                format_map = dict(command=command, feature=feature, api=api)
+                condition = 'dispatch_api == {api.c_piglit_token}'.format(**format_map)
+                get_proc = 'get_core_proc("{command.name}", {feature.version_int})'.format(**format_map)
+                if feature.version_int > api.base_version_int:
+                    condition += ' && check_version({feature.version_int})'.format(**format_map)
+                yield Resolution(condition, get_proc)
+
+            for extension in command.extensions:
+                format_map = dict(command=command, extension=extension)
+                condition = 'check_extension("{extension.name}")'.format(**format_map)
+                get_proc = 'get_ext_proc("{command.name}")'.format(**format_map)
+                yield Resolution(condition, get_proc)
+
+    def add(self, command):
+        assert(isinstance(command, registry.gl.Command))
+        OrderedKeyedSet.add(self, command)
+
+class DispatchCode(object):
+
+    h_template = Template(dedent('''\
+        ${copyright}
+
+        #ifndef __PIGLIT_DISPATCH_GEN_H__
+        #define __PIGLIT_DISPATCH_GEN_H__
+
+        % for f in sorted(gl_registry.commands):
+        typedef ${f.c_return_type} (APIENTRY *PFN${f.name.upper()}PROC)(${f.c_named_param_list});
+        % endfor
+        % for alias_set in alias_map:
+        <% f0 = alias_set.primary_command %>
+        %   for f in alias_set:
+        <%      cat_list = ' '.join(['(' + cat.name + ')' for cat in f.requirements]) %>\\
+        /* ${f.name} ${cat_list} */
+        %   endfor
+        extern PFN${f0.name.upper()}PROC piglit_dispatch_${f0.name};
+        %   for f in alias_set:
+        #define ${f.name} piglit_dispatch_${f0.name}
+        %   endfor
+        % endfor
+        % for enum_group in gl_registry.enum_groups:
+        %   if len(enum_group.enums) > 0:
+
+        /* Enum Group ${enum_group.name} */
+        %     for enum in enum_group.enums:
+        %         if not enum.is_collider:
+        #define ${enum.name} ${enum.c_num_literal}
+        %         endif
+        %      endfor
+        %   endif
+        % endfor
+
+        % for extension in gl_registry.extensions:
+        #define ${extension.name} 1
+        % endfor
+
+        % for feature in gl_registry.features:
+        #define ${feature.name} 1
+        % endfor
+
+        #endif /*__PIGLIT_DISPATCH_GEN_H__*/'''
+    ))
+
+    c_template = Template(dedent('''\
+        ${copyright}
+        % for alias_set in alias_map:
+        <% f0 = alias_set.primary_command %>
+        %   for f in alias_set:
+        <%      cat_list = ' '.join(['(' + cat.name + ')' for cat in f.requirements]) %>\\
+        /* ${f.name} ${cat_list} */
+        %   endfor
+        static void* resolve_${f0.name}(void)
+        {
+        %   for resolution in alias_set.resolutions:
+        %       if loop.first:
+            if (${resolution.c_condition}) {
+        %       else:
+            } else if (${resolution.c_condition}) {
+        %       endif
+                return ${resolution.c_get_proc};
+        %   endfor
+            } else {
+                unsupported("${f0.name}");
+                return piglit_dispatch_${f0.name};
+            }
+        }
+
+        static ${f0.c_return_type} APIENTRY stub_${f0.name}(${f0.c_named_param_list})
+        {
+        <%
+            if f0.c_return_type == 'void': 
+                maybe_return = ''
             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
-    #
-    # While enum.ext lists some old extension names (defined to 1), it
-    # doesn't contain the full set that appears in glext.h.
-    h_contents.append('\n')
-    for ext in api.extensions:
-        h_contents.append('#define {0} 1\n'.format(ext))
-
-    # Emit GL version #defines
-    #
-    # While enum.ext lists GL versions up to 3.2, it didn't continue
-    # adding them for later GL versions.
-    h_contents.append('\n')
-    for ver in api.gl_versions:
-        h_contents.append('#define GL_VERSION_{0}_{1} 1\n'.format(
-            ver // 10, ver % 10))
-
-    return ''.join(c_contents), ''.join(h_contents)
-
+                maybe_return = 'return '
+        %>\\
+            check_initialized();
+            piglit_dispatch_${f0.name} = resolve_${f0.name}();
+            ${maybe_return}piglit_dispatch_${f0.name}(${f0.c_untyped_param_list});
+        }
+
+        PFN${f0.name.upper()}PROC piglit_dispatch_${f0.name} = stub_${f0.name};
+        % endfor
+
+        static void reset_dispatch_pointers(void)
+        {
+        % for alias_set in alias_map:
+        <% f0 = alias_set.primary_command %>\\
+            piglit_dispatch_${f0.name} = stub_${f0.name};
+        % endfor
+        }
+
+        static const char * function_names[] = {
+        % for f in gl_registry.commands:
+            "${f.name}",
+        % endfor
+        };
+
+        static void* (*const function_resolvers[])(void) = {
+        % for alias_set in alias_map:
+        <% f0 = alias_set.primary_command %>\\
+            resolve_${f0.name},
+        % endfor
+        };
+        '''
+    ))
+
+    def __init__(self, gl_registry):
+        assert(isinstance(gl_registry, registry.gl.Registry))
+        self.gl_registry = gl_registry
+        self.alias_map = CommandAliasMap()
+        self.alias_map.add_commands(gl_registry.commands)
+
+    def emit(self, h_buf, c_buf):
+        for (buf, template) in (
+                (h_buf, DispatchCode.h_template),
+                (c_buf, DispatchCode.c_template)):
+            ctx = mako.runtime.Context(
+                    buffer=buf,
+                    gl_registry=self.gl_registry,
+                    alias_map=self.alias_map,
+                    copyright=copyright_block,
+            )
+            template.render_context(ctx)
 
 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)
+    main()
diff --git a/tests/util/piglit-dispatch.h b/tests/util/piglit-dispatch.h
index c24c186..2d1d5eb 100644
--- a/tests/util/piglit-dispatch.h
+++ b/tests/util/piglit-dispatch.h
@@ -96,6 +96,7 @@ typedef float GLfloat;
 typedef float GLclampf;
 typedef double GLdouble;
 typedef double GLclampd;
+typedef int32_t GLclampx;
 typedef void GLvoid;
 typedef int64_t GLint64EXT;
 typedef uint64_t GLuint64EXT;
@@ -136,8 +137,6 @@ typedef piglit_dispatch_function_ptr (*piglit_get_core_proc_address_function_ptr
 
 typedef piglit_dispatch_function_ptr (*piglit_get_ext_proc_address_function_ptr)(const char *);
 
-typedef piglit_dispatch_function_ptr (*piglit_dispatch_resolver_ptr)();
-
 typedef void (*piglit_error_function_ptr)(const char *);
 
 typedef enum {
-- 
2.0.0.rc1



More information about the Piglit mailing list