Skip to content

Instantly share code, notes, and snippets.

@chirsz-ever
Created August 8, 2023 09:38
Show Gist options
  • Save chirsz-ever/ca5bfd87b00e2b7ae0b91903dcd57c98 to your computer and use it in GitHub Desktop.
Save chirsz-ever/ca5bfd87b00e2b7ae0b91903dcd57c98 to your computer and use it in GitHub Desktop.
Query OpenGL API
#!/usr/bin/env python3
import xml.dom.minidom
import argparse
import re
import os
def parse_args():
ap = argparse.ArgumentParser()
ap.add_argument('token', help='OpenGL API token like "GL_ONE" or "glUseProgram"')
ap.add_argument('--glxml', default=os.path.join(os.path.dirname(__file__), 'gl.xml'))
ap.add_argument('--urls', action='store_true')
args = ap.parse_args()
if args.token == '':
ap.print_help()
exit(1)
return args
def convert_api_name(name):
if name == 'gl':
return 'OpenGL'
elif name.startswith('gles'):
return 'OpenGL ES'
else:
assert name.startswith('glsc'), f'{name=}'
return 'OpenGL SC'
def feat_matches(name: str, token: str|re.Pattern) -> bool:
if isinstance(token, re.Pattern):
return token.match(name)
upper_token = token.upper()
upper_name = name.upper()
if upper_token.startswith('GL'):
return upper_token == upper_name
else:
return 'GL_' + upper_token == upper_name or 'GL' + upper_token == upper_name
# seee https://registry.khronos.org/OpenGL/extensions/
VENDORS = {
'3DFX',
'3DL',
'AMD',
'ANGLE',
'ANDROID',
'APPLE',
'ARB',
'ARM',
'ATI',
'CMAAINTEL',
'DMP',
'EXT',
'FJ',
'GREMEDY',
'HP',
'I3D',
'IBM',
'IGLOO',
'IMG',
'INGR',
'INTEL',
'KHR',
'MESA',
'MESAX',
'NV',
'NVX',
'OES',
'OML',
'OVR',
'PGI',
'QCOM',
'REND',
'S3',
'SGI',
'SGIS',
'SGIX',
'SUN',
'SUNX',
'VIV',
'WIN',
}
def ext_matches(extname: str, token: str|re.Pattern) -> bool:
if isinstance(token, re.Pattern):
return token.match(extname)
if extname.startswith(token):
suffix = extname.removeprefix(token)
return suffix in VENDORS or ('_' + suffix) in VENDORS
return feat_matches(extname, token)
reTokenChar = re.compile(r'[0-9a-zA-Z_]')
def is_token_char(c: str) -> bool:
return reTokenChar.match(c)
# documet url example
# ES 1.1: https://registry.khronos.org/OpenGL-Refpages/es1.1/xhtml/glBindBuffer.xml
# ES 2.0: https://registry.khronos.org/OpenGL-Refpages/es2.0/xhtml/glBindBuffer.xml
# ES 3.0: https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glBindBuffer.xhtml
# ES 3.1: https://registry.khronos.org/OpenGL-Refpages/es3.1/html/glBindBuffer.xhtml
# ES 3.2: https://registry.khronos.org/OpenGL-Refpages/es3/html/glBindBuffer.xhtml
# GL 2.1: https://registry.khronos.org/OpenGL-Refpages/gl2.1/xhtml/glBindBuffer.xml
# GL 4.5: https://registry.khronos.org/OpenGL-Refpages/gl4/html/glBindBuffer.xhtml
DOC_VERSIONS_GL = [ '2.1', '4.5' ]
# DOC_VERSIONS_GLES1 = [ '1.1' ]
DOC_VERSIONS_GLES2 = [ '2.0', '3.0', '3.1', '3.2' ]
def should_show(n: str, introduced_versions: list[str], removed_versions: list[str]) -> bool:
find_iv = False
for iv in introduced_versions[-1::-1]:
if iv <= n:
find_iv = True
break
if find_iv:
for rv in removed_versions:
if iv <= rv <= n:
return False
return True
return False
def make_url(api: str, version: str, token: str) -> str:
if version == '3.2':
version = '3'
elif version == '4.5':
version = '4'
if version < '3':
return f'https://registry.khronos.org/OpenGL-Refpages/{api}{version}/xhtml/{token}.xml'
else:
return f'https://registry.khronos.org/OpenGL-Refpages/{api}{version}/html/{token}.xhtml'
reVendor = re.compile(r'GL_([^_]+)_')
def make_ext_url(name_full: str) -> str:
name = name_full.removeprefix('GL_')
vendor = reVendor.match(name_full)[1]
return f'https://registry.khronos.org/OpenGL/extensions/{vendor}/{name}.txt'
def main():
args = parse_args()
token: str = args.token
print_doc_url = args.urls
dom = xml.dom.minidom.parse(args.glxml)
introduced_versions = []
matched_extensions = set()
removed_versions = []
if token[0].isdigit():
token_value = eval(token)
tokens = set()
# enum str -> value
enums = {}
for enums_tag in dom.getElementsByTagName('enums'):
for enum_tag in filter(lambda n: isinstance(n, xml.dom.minidom.Element) and n.tagName == 'enum', enums_tag.childNodes):
value = enum_tag.getAttribute('value')
name = enum_tag.getAttribute('name')
if eval(value) == token_value:
tokens.add(name)
enums[name] = value
for e, v in enums.items():
print(f'{e} = {v}')
return
if not all(is_token_char(c) for c in token):
token = re.compile(token)
for feat in dom.getElementsByTagName('feature'):
fapi = feat.getAttribute('api')
api_name = convert_api_name(fapi)
api_number = feat.getAttribute('number')
for child in filter(lambda n: isinstance(n, xml.dom.minidom.Element) and n.tagName in ['require', 'remove'], feat.childNodes):
for item in filter(lambda n: isinstance(n, xml.dom.minidom.Element) and n.tagName in ['command', 'enum'], child.childNodes):
item_name = item.getAttribute('name')
if feat_matches(item_name, token):
if child.tagName == 'require':
print(f"{item_name} introduced in {api_name} {api_number}")
introduced_versions.append((fapi, api_number))
else:
assert child.tagName == 'remove'
assert fapi == 'gl'
assert api_number == '3.2'
print(f"{item_name} removed in {api_name} {api_number}")
removed_versions.append((fapi, api_number))
for ext in dom.getElementsByTagName('extension'):
ext_name = ext.getAttribute('name')
for child in filter(lambda n: isinstance(n, xml.dom.minidom.Element) and n.tagName == 'require', ext.childNodes):
for item in filter(lambda n: isinstance(n, xml.dom.minidom.Element) and n.tagName in ['command', 'enum'], child.childNodes):
item_name = item.getAttribute('name')
if ext_matches(item_name, token):
print(f"{item_name} in {ext_name}")
matched_extensions.add(ext_name)
if print_doc_url:
if isinstance(token, str) and not token.startswith('GL_'):
# GL
gl_introduced_versions = sorted(v[1] for v in introduced_versions if v[0] == 'gl')
gl_removed_versions = sorted(v[1] for v in removed_versions if v[0] == 'gl')
# print(f'{gl_introduced_versions=}')
# print(f'{gl_removed_versions=}')
for n in DOC_VERSIONS_GL:
if should_show(n, gl_introduced_versions, gl_removed_versions):
print(make_url('gl', n, token))
# GLES 1.x
if any(v[0] == 'gles1' for v in introduced_versions):
print(make_url('es', '1.1', token))
# GLES 2.0+
gles2_introduced_versions = sorted(v[1] for v in introduced_versions if v[0] == 'gles2')
for n in DOC_VERSIONS_GLES2:
if should_show(n, gles2_introduced_versions, []):
print(make_url('es', n, token))
# Extension
for ext in matched_extensions:
print(make_ext_url(ext))
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment