blob: e8d6fd154d3024cb879036a16d2806319b246152 [file] [log] [blame]
# 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.
"""
from __future__ import print_function
import os.path
import re
import sys
from collections import namedtuple
from copy import copy, deepcopy
# Export 'debug' so other Piglit modules can easily enable it.
debug = False
def _log_debug(msg):
if debug:
print('debug: {0}: {1}'.format(__name__, msg), file=sys.stderr)
# Prefer the external module 'lxml.etree' (it uses libxml2) over Python's
# builtin 'xml.etree.ElementTree'. It's faster.
try:
import lxml.etree as etree
_log_debug('etree is lxml.etree')
except ImportError:
import xml.etree.cElementTree as etree
_log_debug('etree is xml.etree.cElementTree')
# Define a Python 2.6 compatibility wrapper for ElementTree.iterfind.
_etree_iterfind = None
if hasattr(etree.ElementTree(), 'iterfind'):
_etree_iterfind = lambda elem, match: elem.iterfind(match)
_log_debug('_etree_iterfind wraps ElementTree.iterfind')
else:
_etree_iterfind = lambda elem, match: iter(elem.findall(match))
_log_debug(('_etree_iterfind wraps ElementTree.findall for '
'Python 2.6 compatibility'))
def parse():
"""Parse gl.xml and return a Registry object."""
filename = os.path.join(os.path.dirname(__file__), 'gl.xml')
xml_registry = etree.parse(filename).getroot()
_repair_xml(xml_registry)
return Registry(xml_registry)
def _repair_xml(xml_registry):
fixes = set((
'GL_ALL_ATTRIB_BITS',
'glOcclusionQueryEventMaskAMD',
'enums_SGI_0x8000_0x80BF',
'enums_ARB_0x80000_0x80BF',
'gles2_GL_ACTIVE_PROGRAM_EXT',
))
remove_queue = []
def defer_removal(parent, child):
remove_queue.append((parent, child))
for enums in _etree_iterfind(xml_registry, './enums'):
if ('GL_ALL_ATTRIB_BITS' in fixes
and enums.get('group') == 'AttribMask'):
# The XML defines GL_ALL_ATTRIB_BITS incorrectly with all bits
# set (0xFFFFFFFF). From the GL_ARB_multisample spec, v5:
#
# In order to avoid incompatibility with GL implementations
# that do not support SGIS_multisample, ALL_ATTRIB_BITS
# does not include MULTISAMPLE_BIT_ARB.
#
enum = enums.find("./enum[@name='GL_ALL_ATTRIB_BITS']")
enum.set('value', '0x000FFFFF')
fixes.remove('GL_ALL_ATTRIB_BITS')
continue
if ('glOcclusionQueryEventMaskAMD' in fixes
and enums.get('namespace') == 'OcclusionQueryEventMaskAMD'):
# This tag's attributes are totally broken.
enums.set('namespace', 'GL')
enums.set('group', 'OcclusionQueryEventMaskAMD')
enums.set('type', 'bitmask')
fixes.remove('glOcclusionQueryEventMaskAMD')
continue
if ('enums_SGI_0x8000_0x80BF' in fixes
and enums.get('vendor') == 'SGI'
and enums.get('start') == '0x8000'
and enums.get('end') == '0x80BF'):
# This element is empty garbage that overlaps an ARB enum group
# with the same range.
defer_removal(xml_registry, enums)
fixes.remove('enums_SGI_0x8000_0x80BF')
continue
if ('enums_ARB_0x80000_0x80BF' in fixes
and enums.get('vendor') == 'ARB'
and enums.get('group', None) is None
and enums.get('start', None) is None):
# This tag lacks 'start' and 'end' attributes.
enums.set('start', '0x8000')
enums.set('end', '0x80BF')
fixes.remove('enums_ARB_0x80000_0x80BF')
continue
if ('gles2_GL_ACTIVE_PROGRAM_EXT' in fixes
and enums.get('vendor') == 'ARB'
and enums.get('start') <= '0x8259'
and enums.get('end') >= '0x8259'):
# GL_ACTIVE_PROGRAM_EXT has different numerical values in GL
# (0x8B8D) and in GLES (0x8259). Remove the GLES value to avoid
# redefinition collisions.
bad_enum = enums.find(("./enum"
"[@value='0x8259']"
"[@name='GL_ACTIVE_PROGRAM_EXT']"
"[@api='gles2']"))
defer_removal(enums, bad_enum)
fixes.remove('gles2_GL_ACTIVE_PROGRAM_EXT')
continue
for (parent, child) in remove_queue:
parent.remove(child)
if len(fixes) > 0:
raise Exception('failed to apply some xml repairs: ' +
', '.join(map(repr, fixes)))
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)`.
"""
# A linked list contains the set's items. Each list node is a 4-tuple
# [prev, next, key, value]. The root node is permanent.
root = []
root[:] = [root, root, None, None]
self.__list_root = root
# For quick retrieval, we map each key to its node. That is, each map
# pair has form {key: [prev, next, key, value])}.
self.__map = dict()
if isinstance(key, str):
self.__key_func = lambda elem: getattr(elem, key)
else:
self.__key_func = key
for e in elems:
self.add(e)
def __or__(self, other):
"""Same as `union`."""
return self.union(other)
def __contains__(self, key):
return key in self.__map
def __copy__(self):
return OrderedKeyedSet(key=deepcopy(self.__key_func),
elems=iter(self))
def __getitem__(self, key):
return self.__map[key][3]
def __iter__(self):
return self.itervalues()
def __len__(self):
return len(self.__map)
def __repr__(self):
templ = '{self.__class__.__name__}({self.name!r})'
return templ.format(self=self)
def add(self, value):
key = self.__key_func(value)
node = self.__map.get(key, None)
if node is not None:
node[3] = value
else:
root = self.__list_root
old_tail = root[0]
new_tail = [old_tail, root, key, value]
new_tail[0][1] = new_tail
new_tail[1][0] = new_tail
self.__map[key] = new_tail
def clear(self):
self.__map.clear()
root = self.__list_root
root[:] = [root, root, None, None]
def extend(self, elems):
for e in elems:
self.add(e)
def get(self, key, default):
node = self.__map.get(key, None)
if node is not None:
return node[3]
else:
return default
def iteritems(self):
root = self.__list_root
node = root[1]
while node is not root:
yield (node[2], node[3])
node = node[1]
def iterkeys(self):
return (i[0] for i in self.iteritems())
def itervalues(self):
return (i[1] for i in self.iteritems())
def pop(self, key):
node = self.__map.pop(key)
node[0][1] = node[1]
node[1][0] = node[0]
return node[3]
def sort_by_key(self):
sorted_items = sorted(self.__map.iteritems(),
cmp=lambda x, y: cmp(x[0], y[0]))
self.clear()
for item in sorted_items:
self.add(item[1])
def sort_by_value(self):
sorted_values = sorted(self.__map.itervalues())
self.clear()
for value in sorted_values:
self.add(value)
def union(self, other):
"""Return the union of two sets as a new set.
In the new set, all elements of the self set precede those of the other
set. The order of elements in the new set preserves the order of the
original sets.
The new set's key function is copied from self. On key collisions, set
y has precedence over x.
"""
u = copy(self)
u.extend(other)
return u
class ImmutableOrderedKeyedSet(OrderedKeyedSet):
def __init__(self, key, elems):
self.__is_frozen = False
OrderedKeyedSet.__init__(self, key=key, elems=elems)
self.__is_frozen = True
def add(self, value):
if self.__is_frozen:
raise ImmutableError
else:
OrderedKeyedSet.add(self, value)
def pop(self, key):
raise ImmutableError
def clear(self):
raise ImmutableError
class ImmutableError:
pass
# Values that may appear in the XML attributes 'api' and 'supported'.
VALID_APIS = frozenset(('gl', 'glcore', 'gles1', 'gles2'))
class Registry(object):
"""The toplevel <registry> element.
Attributes:
features: An OrderedKeyedSet that contains a Feature for each <feature>
subelement.
extensions: An OrderedKeyedSet that contains an Extension for each
<extension> subelement.
commands: An OrderedKeyedSet that contains a Command for each <command>
subelement.
command_alias_map: A CommandAliasMap that contains a CommandAliasSet
for each equivalence class of commands.
enum_groups: An OrderedKeyedSet that contains an EnumGroup for each
<enums> subelement.
enums: An OrderedKeyedSet that contains an Enum for each <enum>
subelement.
vendor_namespaces: A collection of all vendor prefixes and suffixes,
such as "ARB", "EXT", "CHROMIUM", and "NV".
"""
def __init__(self, xml_registry):
"""Parse the <registry> element."""
assert(xml_registry.tag == 'registry')
self.command_alias_map = CommandAliasMap()
self.commands = OrderedKeyedSet(key='name')
self.enum_groups = []
self.enums = OrderedKeyedSet(key='name')
self.extensions = OrderedKeyedSet(key='name')
self.features = OrderedKeyedSet(key='name')
self.vendor_namespaces = set()
for xml_command in _etree_iterfind(xml_registry,
'./commands/command'):
command = Command(xml_command)
self.commands.add(command)
self.command_alias_map.add(command)
for xml_enums in _etree_iterfind(xml_registry, './enums'):
enum_group = EnumGroup(xml_enums)
self.enum_groups.append(enum_group)
for enum in enum_group.enums:
self.enums.add(enum)
for xml_feature in _etree_iterfind(xml_registry, './feature'):
feature = Feature(xml_feature, command_map=self.commands,
enum_map=self.enums)
self.features.add(feature)
for xml_ext in _etree_iterfind(xml_registry, './extensions/extension'):
ext = Extension(xml_ext, command_map=self.commands,
enum_map=self.enums)
self.extensions.add(ext)
self.vendor_namespaces.add(ext.vendor_namespace)
self.vendor_namespaces.remove(None)
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)
requirements: A collection of Requirement for each Command and Enum
this Feature requires.
"""
def __init__(self, xml_feature, command_map, enum_map):
"""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')
# Parse the <feature> tag's attributes.
self.name = xml_feature.get('name')
self.api = xml_feature.get('api')
self.is_gles = self.name.startswith('GL_ES')
self.version_str = xml_feature.get('number')
self.version_float = float(self.version_str)
self.version_int = int(10 * self.version_float)
self.__parse_requirements(xml_feature, command_map, enum_map)
assert(self.api in VALID_APIS)
assert(len(self.requirements) > 0)
def __cmp__(self, other):
if self is other:
return 0
# Sort features before extensions.
if isinstance(other, Extension):
return -1
# Sort GL before GLES.
diff = cmp(self.is_gles, other.is_gles)
if diff != 0:
return diff
return cmp(self.name, other.name)
def __repr__(self):
templ = '{self.__class__.__name__}({self.name!r})'
return templ.format(self=self)
def __parse_requirements(self, xml_feature, command_map, enum_map):
"""For each <command> and <enum> under a <require>, create
a Requirement that links this Feature to a Command or Enum.
"""
self.requirements = set()
def link(x):
req = Requirement(provider=self, provided=x,
apis=frozenset((self.api,)))
self.requirements.add(req)
x.requirements.add(req)
for xml_cmd in _etree_iterfind(xml_feature, './require/command'):
cmd = command_map[xml_cmd.get('name')]
link(cmd)
for xml_enum in _etree_iterfind(xml_feature, './require/enum'):
enum = enum_map[xml_enum.get('name')]
link(enum)
class Extension(object):
"""An <extension> XML element.
Attributes:
name: The XML element's 'name' attribute.
supported_apis: The set of api strings in the XML element's 'supported'
attribute. For example, set('gl', 'glcore').
vendor_namespace: For example, "AMD". May be None.
requirements: A collection of Requirement for each Command and Enum
this Extension requires.
"""
__VENDOR_REGEX = re.compile(r'^GL_(?P<vendor_namespace>[A-Z]+)_')
RATIFIED_NAMESPACES = ('KHR', 'ARB', 'OES')
def __init__(self, xml_extension, command_map, enum_map):
"""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.name = xml_extension.get('name')
self.vendor_namespace = None
match = Extension.__VENDOR_REGEX.match(self.name)
if match is not None:
groups = match.groupdict()
self.vendor_namespace = groups.get('vendor_namespace', None)
self.supported_apis = xml_extension.get('supported').split('|')
self.supported_apis = frozenset(self.supported_apis)
assert(self.supported_apis <= VALID_APIS)
self.__parse_requirements(xml_extension, command_map, enum_map)
def __cmp__(self, other):
if self is other:
return 0
# Sort features before extensions.
if isinstance(other, Feature):
return 1
# Sort ratified before unratified.
diff = cmp(other.is_ratified, self.is_ratified)
if diff != 0:
return diff
# Sort EXT before others.
diff = cmp(other.vendor_namespace == 'EXT',
self.vendor_namespace == 'EXT')
if diff != 0:
return diff
return cmp(self.name, other.name)
def __repr__(self):
templ = '{self.__class__.__name__}(name={self.name!r})'
return templ.format(self=self)
@property
def is_ratified(self):
"""True if the vendor namespace is one that traditionally requires
ratification by Khronos.
"""
return self.vendor_namespace in self.RATIFIED_NAMESPACES
def __parse_requirements(self, xml_extension, command_map, enum_map):
"""For each <command> and <enum> under a <require>, create
a Requirement that links this Extension to a Command or Enum.
"""
self.requirements = set()
def link(xml_require, x):
api = xml_require.get('api', None)
if api is not None:
assert(api in self.supported_apis)
apis = frozenset((api,))
else:
apis = frozenset(self.supported_apis)
req = Requirement(provider=self, provided=x, apis=apis)
self.requirements.add(req)
x.requirements.add(req)
for xml_req in _etree_iterfind(xml_extension, './require'):
for xml_cmd in _etree_iterfind(xml_req, './command'):
cmd = command_map[xml_cmd.get('name')]
link(xml_req, cmd)
for xml_enum in _etree_iterfind(xml_req, './enum'):
enum = enum_map[xml_enum.get('name')]
link(xml_req, enum)
class Requirement(object):
"""A <require> XML element, which links a provider (Feature or Extension)
to a provided (Command or Enum) for a set of apis.
"""
def __init__(self, provider, provided, apis):
self.provider = provider
self.provided = provided
self.apis = frozenset(apis)
def choose_if(condition, obj):
if condition:
return obj
else:
return None
self.has_feature = isinstance(provider, Feature)
self.has_extension = isinstance(provider, Extension)
self.has_command = isinstance(provided, Command)
self.has_enum = isinstance(provided, Enum)
self.feature = choose_if(self.has_feature, self.provider)
self.extension = choose_if(self.has_extension, self.provider)
self.command = choose_if(self.has_command, self.provided)
self.enum = choose_if(self.has_enum, self.provided)
assert(self.has_feature + self.has_extension == 1)
assert(self.has_command + self.has_enum == 1)
assert(self.apis <= VALID_APIS)
_log_debug('created {0}'.format(self))
def __cmp__(self, other):
"""Sort by 'provider', then by 'provided'."""
diff = cmp(self.provider, other.provider)
if diff != 0:
return diff
diff = cmp(self.provided, other.provided)
if diff != 0:
return diff
return 0
def __repr__(self):
templ = ('{self.__class__.__name__}'
'(provider={self.provider.name!r},'
' provided={self.provided.name!r},'
' apis={api_tuple})')
return templ.format(self=self, api_tuple=tuple(self.apis))
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, which is also the function
name.
c_return_type: For example, "void *".
alias: The XML element's 'alias' element. May be None.
param_list: List of that contains a CommandParam for each <param>
subelement.
requirements: A collection of each Requirement that exposes this
Command.
"""
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')
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.requirements = set()
self.__vendor_namespace = None
# 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 _etree_iterfind(xml_command, './param')
]
_log_debug(('parsed {self.__class__.__name__}('
'name={self.name!r}, '
'alias={self.alias!r}, '
'prototype={self.c_prototype!r})').format(self=self))
def __cmp__(self, other):
return cmp(self.name, other.name)
def __repr__(self):
templ = '{self.__class__.__name__}({self.name!r})'
return templ.format(self=self)
@property
def vendor_namespace(self):
if self.__vendor_namespace is None:
for req in self.__requirements:
ext = req.extension
if ext is None:
continue
if ext.vendor_namespace is None:
continue
if self.name.endswith('_' + ext.vendor_namespace):
self.__vendor_namespace = ext.vendor_namespace
return self.__vendor_namespace
@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
)
class CommandAliasSet(ImmutableOrderedKeyedSet):
def __init__(self, commands):
ImmutableOrderedKeyedSet.__init__(self, key='name',
elems=sorted(commands))
self.__primary_command = None
self.__requirements = None
def __cmp__(self, other):
return cmp(self.name, other.name)
def __repr__(self):
templ = '{self.__class__.__name__}({self.name!r})'
return templ.format(self=self)
@property
def name(self):
return self.primary_command.name
@property
def primary_command(self):
"""The set's first command when sorted by name."""
for command in self:
return command
@property
def requirements(self):
"""A sorted iterator over each Requirement that exposes this
CommandAliasSet.
"""
if self.__requirements is None:
self.__requirements = sorted(
req
for command in self
for req in command.requirements
)
_log_debug('{0} sorted requirements: {1}'.format(
self, self.__requirements))
return iter(self.__requirements)
class CommandAliasMap(object):
def __init__(self):
self.__map = dict()
self.__sorted_unique_values = None
def __getitem__(self, command_name):
return self.__map[command_name]
def __iter__(self):
"""A sorted iterator over the map's unique CommandAliasSet values."""
if self.__sorted_unique_values is None:
self.__sorted_unique_values = sorted(set(self.__map.itervalues()))
return iter(self.__sorted_unique_values)
def get(self, command_name, default):
return self.__map.get(command_name, default)
def add(self, command):
assert(isinstance(command, Command))
_log_debug('adding command {0!r} to CommandAliasMap'.format(command.name))
name = command.name
name_set = self.get(name, None)
assert(self.__is_set_mapping_complete(name_set))
alias = command.alias
alias_set = self.get(command.alias, None)
assert(self.__is_set_mapping_complete(alias_set))
if name_set is alias_set and name_set is not None:
return
# After modifying the contained alias sets, the mapping will no longer
# be sorted.
self.__sorted_unique_values = None
new_set_elems = set((command,))
if name_set is not None:
new_set_elems.update(name_set)
if alias_set is not None:
new_set_elems.update(alias_set)
new_set = CommandAliasSet(new_set_elems)
for other_command in new_set:
self.__map[other_command.name] = new_set
if other_command.alias is not None:
self.__map[other_command.alias] = new_set
def __is_set_mapping_complete(self, alias_set):
if alias_set is None:
return True
for command in alias_set:
if self[command.name] is not alias_set:
return False
if command.alias is None:
continue
if self[command.alias] is not alias_set:
return False
return True
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. Each may be
None.
enums: An OrderedKeyedSet of Enum that contains each <enum> subelement
in this <enums>.
"""
# 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',
)
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>
#
self.name = xml_enums.get('group', None)
_log_debug('start parsing {0}'.format(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)
_log_debug('start parsing <enum> subelements of {0}'.format(self))
self.enums = OrderedKeyedSet(key='name')
for xml_enum in _etree_iterfind(xml_enums, './enum'):
self.enums.add(Enum(self, xml_enum))
_log_debug('parsed {0}'.format(self))
def __repr__(self):
templ = '{self.__class__.__name__}({self.name!r})'
return templ.format(self=self)
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'
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.
requirements: A collection of each Requirement that exposes 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.requirements = set()
self.__vendor_namespace = None
self.group = enum_group
self.name = xml_enum.get('name')
self.api = xml_enum.get('api')
self.str_value = xml_enum.get('value')
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):
templ = ('{self.__class__.__name__}'
'(name={self.name!r},'
' value={self.str_value!r})')
return templ.format(self=self)
@property
def vendor_namespace(self):
if self.__vendor_namespace is None:
for req in self.requirements:
ext = req.extension
if ext is None:
continue
if ext.vendor_namespace is None:
continue
if self.name.endswith('_' + ext.vendor_namespace):
self.__vendor_namespace = ext.vendor_namespace
return self.__vendor_namespace