Created
August 8, 2023 09:38
-
-
Save chirsz-ever/ca5bfd87b00e2b7ae0b91903dcd57c98 to your computer and use it in GitHub Desktop.
Query OpenGL API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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