1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802
|
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) Vispy Development Team. All Rights Reserved.
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
"""
Scipt to generate code for our gl API, including all its backends.
The files involved in the code generation process are:
* gl2.h - the C header file for the GL ES 2.0 API
* vispy_ext.h - C header file with a subset of GL3+ constants defined
* webgl.idl - the interface definition language for WebGL
* annotations.py - manual annotations for non-trivial functions
* headerparser.py - parses .h and .idl files
* createglapi.py - uses all of the above to generate the actual code
Rationale
---------
The GL ES 2.0 API is relatively small. Therefore a fully automated build
process like PyOpenGL now has is not really necessary. I tried to
automate what we can and simply do the rest with manual annotations.
Some groups of functions (like glUniform and friends) are handled in
*this* file.
Even though the API is quite small, we want to generate several
implementations, such as gl2 (desktop), es2 (angle on Windows), a generic
proxy, a mock backend and possibly more. Therefore automation
is crucial.
Further notes
-------------
This file is pretty big and even though I tried to make the code as clear
as possible, it's not always that easy to read. In effect this code is
not so easy to maintain. I hope it is at least clear enough so it can be
used to maintain the GL API itself.
This function must be run using Python3.
"""
from __future__ import annotations
import os
import sys
import headerparser
from annotations import parse_anotations
from OpenGL import GL # For checking
THISDIR = os.path.abspath(os.path.dirname(__file__))
GLDIR = os.path.join(THISDIR, "..", "vispy", "gloo", "gl")
PREAMBLE = '''"""GL definitions converted to Python by codegen/createglapi.py.
THIS CODE IS AUTO-GENERATED. DO NOT EDIT.
%s
"""
'''
def create_parsers():
# Create a parser for gl2 and es2
parser1 = headerparser.Parser(os.path.join(THISDIR, "headers", "gl2.h"))
parser2 = headerparser.Parser(os.path.join(THISDIR, "headers", "webgl.idl"))
## Check constants and generate API module
# Get names
names1 = set(parser1.constant_names)
names2 = set(parser2.constant_names)
# Check names correspondence
if names1 == names2:
print("Constants in gl2 and webgl are equal")
else:
print("===== Extra names in gl2 =====")
print(", ".join(names1.difference(names2)))
print("===== Extra names in webgl =====")
print(", ".join(names2.difference(names1)))
print("===========")
# Test value correspondence
superset = names1.intersection(names2)
#
constants = {}
for c1 in parser1.constants.values():
if c1.shortname in superset:
constants[c1.shortname] = c1.value
#
assert len(constants) == len(superset)
#
for c2 in parser2.constants.values():
if c2.shortname in constants:
assert c2.value == constants[c2.shortname]
print("Hooray! All constants that occur in both namespaces have equal values.")
return parser1, parser2
DEFINE_ENUM = '''
class Enum(int):
"""Enum (integer) with a meaningfull repr."""
def __new__(cls, name, value):
base = int.__new__(cls, value)
base.name = name
return base
def __repr__(self):
return self.name
'''
DEFINE_CONST_MAP = '''
ENUM_MAP = {}
for var_name, ob in list(globals().items()):
if var_name.startswith('GL_'):
ENUM_MAP[int(ob)] = ob
del ob, var_name
'''
def create_constants_module(constant_definitions: list[headerparser.ConstantDefinition],
extension: bool = False) -> None:
lines = [PREAMBLE % "Constants for OpenGL ES 2.0.", DEFINE_ENUM, ""]
# For extensions, we only take the OES ones, and remove the OES
if extension:
renamed_defs = []
for c in constant_definitions:
if "OES" in c.oname:
c.oname = c.oname.replace("OES", "")
c.oname = c.oname.replace("__", "_").strip("_")
renamed_defs.append(c)
constant_definitions = renamed_defs
# Insert constants
for c in sorted(constant_definitions, key=lambda x: x.oname):
if isinstance(c.value, int):
lines.append("%s = Enum(%r, %r)" % (c.oname, c.oname, c.value))
else:
lines.append("%s = %r" % (c.oname, c.value))
lines.append("")
lines.append(DEFINE_CONST_MAP)
# Write the file
fname = "_constants_ext.py" if extension else "_constants.py"
with open(os.path.join(GLDIR, fname), "wb") as f:
f.write(("\n".join(lines)).encode("utf-8"))
print("wrote %s" % fname)
IGNORE_FUNCTIONS = ["releaseShaderCompiler", "shaderBinary"]
WEBGL_EQUIVALENTS = {
"genBuffers": "createBuffer",
"genFramebuffers": "createFramebuffer",
"genRenderbuffers": "createRenderbuffer",
"genTextures": "createTexture",
"deleteBuffers": "deleteBuffer",
"deleteFramebuffers": "deleteFramebuffer",
"deleteRenderbuffers": "deleteRenderbuffer",
"deleteTextures": "deleteTexture",
"clearDepthf": "clearDepth",
"depthRangef": "depthRange",
"getBufferParameteriv": "getBufferParameter",
"getRenderbufferParameteriv": "getRenderbufferParameter",
"getFramebufferAttachmentParameteriv": "getFramebufferAttachmentParameter",
"getVertexAttribPointerv": "getVertexAttribOffset",
"getProgramiv": "getProgramParameter",
"getShaderiv": "getShaderParameter",
"getBooleanv": "getParameter",
"getFloatv": "getParameter",
"getIntegerv": "getParameter",
"getString": "getParameter", # getParameter is getString + getFloat
}
# Types that we convert easily from Python to C (and back)
EASY_TYPES = {
"void": (type(None), "c_voidp"), # only for output
"GLenum": (int, "c_uint"),
"GLboolean": (bool, "c_bool"),
"GLuint": (int, "c_uint"),
"GLint": (int, "c_int"),
"GLbitfield": (int, "c_uint"),
"GLsizei": (int, "c_int"),
"GLfloat": (float, "c_float"),
"GLclampf": (float, "c_float"),
}
# Types that dont map 1-on-1 to Python values, but that we know
# how to set the ctypes argtypes for.
HARDER_TYPES = {
"GLenum*": ("", "POINTER(ctypes.c_uint)"),
"GLboolean*": ("", "POINTER(ctypes.c_bool)"),
"GLuint*": ("", "POINTER(ctypes.c_uint)"),
"GLint*": ("", "POINTER(ctypes.c_int)"),
"GLsizei*": ("", "POINTER(ctypes.c_int)"),
"GLfloat*": ("", "POINTER(ctypes.c_float)"),
"GLubyte*": ("", "c_char_p"),
"GLchar*": ("", "c_char_p"),
"GLchar**": ("", "POINTER(ctypes.c_char_p)"),
"GLvoid*": ("", "c_void_p"), # or c_voidp?
"GLvoid**": ("", "POINTER(ctypes.c_void_p)"),
"GLintptr": ("", "c_ssize_t"),
"GLsizeiptr": ("", "c_ssize_t"),
}
# Together the EASY_TYPES and HARDER_TYPES should cover all types that
# ES 2.0 uses.
KNOWN_TYPES = EASY_TYPES.copy()
KNOWN_TYPES.update(HARDER_TYPES)
def apiname(funcname):
"""Define what name the API uses, the short or the gl version."""
if funcname.startswith("gl"):
return funcname
else:
if funcname.startswith("_"):
return "_gl" + funcname[1].upper() + funcname[2:]
else:
return "gl" + funcname[0].upper() + funcname[1:]
class FunctionDescription:
def __init__(self, name, es2func, wglfunc, annfunc):
self.name = name
self.apiname = apiname(name)
self.es2 = es2func
self.wgl = wglfunc
self.ann = annfunc
self.args = []
class ApiGenerator:
"""Base API generator class. We derive several subclasses to implement
the different backends.
"""
backend_name = None
DESCRIPTION = "GL API X"
PREAMBLE = ""
def __init__(self):
self.lines = []
self.functions_auto = set()
self.functions_anno = set()
self.functions_todo = set()
def save(self):
# Remove too many whitespace
text = "\n".join(self.lines) + "\n"
for i in range(10):
text = text.replace("\n\n\n\n", "\n\n\n")
# Write
with open(self.filename, "wb") as f:
f.write((PREAMBLE % self.DESCRIPTION).encode("utf-8"))
for line in self.PREAMBLE.splitlines():
f.write(line[4:].encode("utf-8") + b"\n")
f.write(b"\n")
f.write(text.encode("utf-8"))
def add_functions(self, all_functions):
for func_description in all_functions:
self.add_function(func_description)
def add_function(self, des: FunctionDescription):
if des.es2.group:
if des.name.startswith("get"):
assert len(des.es2.group) == 2 # vi and fv
des.es2 = des.es2.group[0] # f comes before 1
self._add_function(des)
else:
self._add_function_group(des)
else:
self._add_function(des)
self.lines.append("\n") # two lines between each function
def _add_function_group(self, des: FunctionDescription) -> tuple[set, set, set]:
lines = self.lines
handled = True
# Create map to es2 function objects
es2funcs = {}
for f in des.es2.group:
cname = f.shortname
es2funcs[cname] = f
if des.name == "uniform":
for t in ("float", "int"):
for i in (1, 2, 3, 4):
args = ", ".join(["v%i" % j for j in range(1, i + 1)])
cname = "uniform%i%s" % (i, t[0])
sig = "%s(location, %s)" % (apiname(cname), args)
self._add_group_function(des, sig, es2funcs[cname])
for t in ("float", "int"):
for i in (1, 2, 3, 4):
cname = "uniform%i%sv" % (i, t[0])
sig = "%s(location, count, values)" % apiname(cname)
self._add_group_function(des, sig, es2funcs[cname])
elif des.name == "uniformMatrix":
for i in (2, 3, 4):
cname = "uniformMatrix%ifv" % i
sig = "%s(location, count, transpose, values)" % apiname(cname)
self._add_group_function(des, sig, es2funcs[cname])
elif des.name == "vertexAttrib":
for i in (1, 2, 3, 4):
args = ", ".join(["v%i" % j for j in range(1, i + 1)])
cname = "vertexAttrib%if" % i
sig = "%s(index, %s)" % (apiname(cname), args)
self._add_group_function(des, sig, es2funcs[cname])
elif des.name == "texParameter":
for t in ("float", "int"):
cname = "texParameter%s" % t[0]
sig = "%s(target, pname, param)" % apiname(cname)
self._add_group_function(des, sig, es2funcs[cname])
else:
handled = False
if handled:
self.functions_auto.add(des.name)
else:
self.functions_todo.add(des.name)
lines.append("# todo: Dont know group %s" % des.name)
def _add_function(self, des):
# Need to be overloaded in subclass
raise NotImplementedError()
def _add_group_function(self, des, sig, es2func):
# Need to be overloaded in subclass
raise NotImplementedError()
class ProxyApiGenerator(ApiGenerator):
"""Generator for the general proxy class that will be loaded into gloo.gl."""
filename = os.path.join(GLDIR, "_proxy.py")
DESCRIPTION = "Base proxy API for GL ES 2.0."
PREAMBLE = '''
class BaseGLProxy(object):
""" Base proxy class for the GL ES 2.0 API. Subclasses should
implement __call__ to process the API calls.
"""
def __call__(self, funcname, returns, *args):
raise NotImplementedError()
'''
def _returns(self, des):
shortame = des.name
for prefix in ("get", "is", "check", "create", "read"):
if shortame.startswith(prefix):
return True
else:
return False
def _add_function(self, des):
ret = self._returns(des)
prefix = "return " if ret else ""
argstr = ", ".join(des.args)
self.lines.append(" def %s(self, %s):" % (des.apiname, argstr))
self.lines.append(
' %sself("%s", %r, %s)' % (prefix, apiname(des.name), ret, argstr)
)
def _add_group_function(self, des, sig, es2func):
ret = self._returns(des)
prefix = "return " if ret else ""
funcname = apiname(sig.split("(")[0])
args = sig.split("(", 1)[1].split(")")[0]
self.lines.append(" def %s(self, %s):" % (funcname, args))
self.lines.append(
' %sself("%s", %r, %s)' % (prefix, funcname, ret, args)
)
class Gl2ApiGenerator(ApiGenerator):
"""Generator for the gl2 (desktop) backend."""
filename = os.path.join(GLDIR, "_gl2.py")
write_c_sig = True
define_argtypes_in_module = False
backend_name = "gl"
DESCRIPTION = "Subset of desktop GL API compatible with GL ES 2.0"
PREAMBLE = """
import ctypes
from .gl2 import _lib, _get_gl_func
"""
def _get_argtype_str(self, es2func):
ct_arg_types = [KNOWN_TYPES.get(arg.ctype, None) for arg in es2func.args]
# Set argument types on ctypes function
if None in ct_arg_types:
argstr = "UNKNOWN_ARGTYPES"
elif es2func.group:
argstr = "UNKNOWN_ARGTYPES"
else:
argstr = ", ".join(["ctypes.%s" % t[1] for t in ct_arg_types[1:]])
argstr = "()" if not argstr else "(%s,)" % argstr
# Set output arg (if available)
if ct_arg_types[0][0] != type(None):
resstr = "ctypes.%s" % ct_arg_types[0][1]
else:
resstr = "None"
return resstr, argstr
def _write_argtypes(self, es2func):
lines = self.lines
ct_arg_types = [KNOWN_TYPES.get(arg.ctype, None) for arg in es2func.args]
# Set argument types on ctypes function
if None in ct_arg_types:
lines.append("# todo: unknown argtypes")
elif es2func.group:
lines.append("# todo: oops, dont set argtypes for group!")
else:
if ct_arg_types[1:]:
argstr = ", ".join(["ctypes.%s" % t[1] for t in ct_arg_types[1:]])
lines.append("_lib.%s.argtypes = %s," % (es2func.glname, argstr))
else:
lines.append("_lib.%s.argtypes = ()" % es2func.glname)
# Set output arg (if available)
if ct_arg_types[0][0] != type(None):
lines.append(
"_lib.%s.restype = ctypes.%s" % (es2func.glname, ct_arg_types[0][1])
)
def _native_call_line(self, name, es2func, cargstr=None, prefix="", indent=4):
resstr, argstr = self._get_argtype_str(es2func)
if cargstr is None:
cargs = [arg.name for arg in es2func.args[1:]]
cargstr = ", ".join(cargs)
lines = "try:\n"
lines += " nativefunc = %s._native\n" % apiname(name)
lines += "except AttributeError:\n"
lines += ' nativefunc = %s._native = _get_gl_func("%s", %s, %s)\n' % (
apiname(name),
es2func.glname,
resstr,
argstr,
)
lines += "%snativefunc(%s)\n" % (prefix, cargstr)
lines = [" " * indent + line for line in lines.splitlines()]
return "\n".join(lines)
def _add_function(self, des):
lines = self.lines
es2func = des.es2
# Write arg types
if self.define_argtypes_in_module:
self._write_argtypes(es2func)
# Get names and types of C-API
ce_arg_types = [arg.ctype for arg in es2func.args[1:]]
ce_arg_names = [arg.name for arg in es2func.args[1:]]
ct_arg_types = [KNOWN_TYPES.get(arg.ctype, None) for arg in es2func.args]
ct_arg_types_easy = [EASY_TYPES.get(arg.ctype, None) for arg in es2func.args]
# Write C function signature, for debugging and development
if self.write_c_sig:
argnamesstr = ", ".join(
[
c_type + " " + c_name
for c_type, c_name in zip(ce_arg_types, ce_arg_names)
]
)
lines.append(
"# %s = %s(%s)" % (es2func.args[0].ctype, es2func.oname, argnamesstr)
)
# Write Python function def
lines.append("def %s(%s):" % (des.apiname, ", ".join(des.args)))
# Construct C function call
cargs = [arg.name for arg in des.es2.args[1:]]
# Now write the body of the function ...
if des.ann:
prefix = "res = "
# Annotation available
self.functions_anno.add(des.name)
callline = self._native_call_line(des.name, es2func, prefix=prefix)
lines.extend(des.ann.get_lines(callline, self.backend_name))
elif es2func.group:
# Group?
self.functions_todo.add(des.name)
lines.append(" pass # todo: Oops. this is a group!")
elif None in ct_arg_types_easy:
self.functions_todo.add(des.name)
lines.append(" pass # todo: Not all easy types!")
elif des.args != [arg.name for arg in des.wgl.args[1:]]:
self.functions_todo.add(des.name)
lines.append(" pass # todo: ES 2.0 and WebGL args do not match!")
else:
# This one is easy!
self.functions_auto.add(des.name)
# Get prefix
prefix = ""
if ct_arg_types[0][0] != type(None):
prefix = "return "
elif des.es2.shortname.startswith("get"):
raise RuntimeError("Get func returns void?")
# Set string
callline = self._native_call_line(des.name, des.es2, prefix=prefix)
lines.append(callline)
if "gl2" in self.__class__.__name__.lower():
# Post-fix special cases for gl2. See discussion in #201
# glDepthRangef and glClearDepthf are not always available,
# and sometimes they do not work if they are
if es2func.oname in ("glDepthRangef", "glClearDepthf"):
for i in range(1, 10):
line = lines[-i]
if not line.strip() or line.startswith("#"):
break
line = line.replace("c_float", "c_double")
line = line.replace("glDepthRangef", "glDepthRange")
line = line.replace("glClearDepthf", "glClearDepth")
lines[-i] = line
def _add_group_function(self, des, sig, es2func):
lines = self.lines
call_line = self._native_call_line
if self.define_argtypes_in_module:
self._write_argtypes(es2func)
funcname = sig.split("(", 1)[0]
args = sig.split("(", 1)[1].split(")")[0]
if des.name == "uniform":
if funcname[-1] != "v":
lines.append("def %s:" % sig)
lines.append(call_line(funcname, es2func, args))
else:
t = {"f": "float", "i": "int"}[funcname[-2]]
lines.append("def %s:" % sig)
lines.append(" values = [%s(val) for val in values]" % t)
lines.append(" values = (ctypes.c_%s*len(values))(*values)" % t)
lines.append(call_line(funcname, es2func, "location, count, values"))
elif des.name == "uniformMatrix":
lines.append("def %s:" % sig)
lines.append(' if not values.flags["C_CONTIGUOUS"]:')
lines.append(" values = values.copy()")
lines.append(' assert values.dtype.name == "float32"')
lines.append(" values_ = values")
lines.append(
" values = values_.ctypes.data_as(ctypes.POINTER(ctypes.c_float))"
)
lines.append(
call_line(funcname, es2func, "location, count, transpose, values")
)
elif des.name == "vertexAttrib":
lines.append("def %s:" % sig)
lines.append(call_line(funcname, es2func, args))
elif des.name == "texParameter":
lines.append("def %s:" % sig)
lines.append(call_line(funcname, es2func, args))
else:
raise ValueError("unknown group func")
class Es2ApiGenerator(Gl2ApiGenerator):
"""Generator for the es2 backend (i.e. Angle on Windows). Very
similar to the gl2 API, but we do not need that deferred loading
of GL functions here.
"""
filename = os.path.join(GLDIR, "_es2.py")
write_c_sig = True
define_argtypes_in_module = True
backend_name = "es"
DESCRIPTION = "GL ES 2.0 API (via Angle/DirectX on Windows)"
PREAMBLE = """
import ctypes
from .es2 import _lib
"""
def _native_call_line(self, name, es2func, cargstr=None, prefix="", indent=4):
resstr, argstr = self._get_argtype_str(es2func)
if cargstr is None:
cargs = [arg.name for arg in es2func.args[1:]]
cargstr = ", ".join(cargs)
return " " * indent + "%s_lib.%s(%s)" % (prefix, es2func.glname, cargstr)
class PyOpenGL2ApiGenerator(ApiGenerator):
"""Generator for a fallback pyopengl backend."""
filename = os.path.join(GLDIR, "_pyopengl2.py")
backend_name = "pyopengl"
DESCRIPTION = "Proxy API for GL ES 2.0 subset, via the PyOpenGL library."
PREAMBLE = """
import ctypes
from OpenGL import GL
import OpenGL.GL.framebufferobjects as FBO
"""
def __init__(self):
ApiGenerator.__init__(self)
self._functions_to_import = []
self._used_functions = []
def _add_function(self, des):
# Fix for FBO?
mod = "GL"
if "renderbuffer" in des.name.lower() or "framebuffer" in des.name.lower():
mod = "FBO"
# Get call line
argstr = ", ".join(des.args)
call_line = " return %s.%s(%s)" % (mod, des.es2.glname, argstr)
# Get annotation lines
ann_lines = []
if des.ann is not None:
ann_lines = des.ann.get_lines(call_line, self.backend_name)
# Use annotation or not
if ann_lines:
self.lines.append("def %s(%s):" % (des.apiname, argstr))
self.lines.extend(ann_lines)
self._used_functions.append(des.es2.glname)
else:
# To be imported from OpenGL.GL
self._functions_to_import.append((des.es2.glname, des.apiname))
def _add_group_function(self, des, sig, es2func):
# All group functions can be directly imported from OpenGL
funcname = apiname(sig.split("(")[0])
self._functions_to_import.append((funcname, funcname))
def save(self):
# Write remaining functions
self.lines.append("# List of functions that we should import from OpenGL.GL")
self.lines.append("_functions_to_import = [")
for name1, name2 in self._functions_to_import:
self.lines.append(' ("%s", "%s"),' % (name1, name2))
self.lines.append(" ]")
self.lines.append("")
# Write used functions
self.lines.append("# List of functions in OpenGL.GL that we use")
self.lines.append("_used_functions = [")
for name in self._used_functions:
self.lines.append(' "%s",' % name)
self.lines.append(" ]")
# Really save
ApiGenerator.save(self)
class FunctionCollector:
def __init__(self, annotations, parser1, parser2):
self.annotations = annotations
self.parser1 = parser1
self.parser2 = parser2
# Keep track of what webGL names we "used"
self.used_webgl_names = set()
# Also keep track of what functions we could handle automatically,
# and which not. Just for reporting.
self.functions_auto = set()
self.functions_anno = set()
self.functions_todo = set()
self.all_functions = []
def collect_function_definitions(self):
"""Process function definitions of ES 2.0, WebGL and annotations.
We try to combine information from these three sources to find the
arguments for the Python API. In this "merging" process we also
check for inconsistencies between the API definitions.
"""
for name in self.parser1.function_names:
if name in IGNORE_FUNCTIONS:
continue
# Get es2 function
es2func = self.parser1.functions[name]
# Get webgl version
lookupname = WEBGL_EQUIVALENTS.get(es2func.shortname, es2func.shortname)
wglfunc = self.parser2.functions.get(lookupname, None)
if wglfunc:
self.used_webgl_names.add(lookupname)
else:
print("WARNING: %s not available in WebGL" % es2func.shortname)
# Convert name
name = WEBGL_EQUIVALENTS.get(name, name)
# Avoid duplicates for getParameter
if name == "getParameter":
if es2func.shortname != "getString":
name = "_" + es2func.shortname
# Get annotated version
annfunc = self.annotations.get(name, None)
# Create description instance
des = FunctionDescription(name, es2func, wglfunc, annfunc)
self.all_functions.append(des)
# Get information about arguments
argnames_es2 = [arg.name for arg in es2func.args[1:]]
if wglfunc:
argnames_wgl = [arg.name for arg in wglfunc.args[1:]]
if annfunc:
argnames_ann = annfunc.args # Can contain 'argname=default'
argnames_ann = [arg.split("=")[0] for arg in argnames_ann]
# Set argumenets specification of our GL API
# Also check and report when we deviate from the WebGL API
if wglfunc and argnames_es2 == argnames_wgl:
if annfunc and argnames_ann != argnames_es2:
des.args = argnames_ann
print(
"WARNING: %s: Annotation overload even though webgl and es2 match."
% name
)
else:
des.args = argnames_es2
elif wglfunc:
if annfunc and argnames_ann != argnames_wgl:
des.args = argnames_ann
print("WARNING: %s: Annotation overload webgl args." % name)
else:
# print('WARNING: %s: assuming wgl args.'%name)
des.args = argnames_wgl
else:
print("WARNING: %s: Could not determine args!!" % name)
# Go over all functions to test if they are in OpenGL
for func in [es2func, wglfunc]:
if func is None:
continue
group = func.group or [func]
for f in group:
# Check opengl
if f.oname.startswith("gl") and not hasattr(GL, f.glname):
print("WARNING: %s seems not available in PyOpenGL" % f.glname)
def check_unused_webgl_funcs(self):
"""Check which WebGL functions we did not find/use."""
for name in set(self.parser2.function_names).difference(self.used_webgl_names):
print("WARNING: WebGL function %s not in Desktop" % name)
def report_status(self):
print(
"Could generate %i functions automatically, and %i with annotations"
% (len(self.functions_auto), len(self.functions_anno))
)
print("Need more info for %i functions." % len(self.functions_todo))
if not self.functions_todo:
print("Hooray! All %i functions are covered!" % len(self.all_functions))
def main():
annotations = parse_anotations()
gl2es2_parser, webgl_parser = create_parsers()
vispy_ext_parser = headerparser.Parser(os.path.join(THISDIR, "headers", "vispy_ext.h"))
constant_definitions = list(gl2es2_parser.constants.values()) + list(vispy_ext_parser.constants.values())
create_constants_module(constant_definitions)
# Get full function definitions and report
func_collector = FunctionCollector(annotations, gl2es2_parser, webgl_parser)
func_collector.collect_function_definitions()
for Gen in [
ProxyApiGenerator,
Gl2ApiGenerator,
Es2ApiGenerator,
PyOpenGL2ApiGenerator,
]:
gen = Gen()
gen.add_functions(func_collector.all_functions)
func_collector.functions_auto.update(gen.functions_auto)
func_collector.functions_anno.update(gen.functions_anno)
func_collector.functions_todo.update(gen.functions_todo)
gen.save()
func_collector.check_unused_webgl_funcs()
func_collector.report_status()
if __name__ == "__main__":
sys.exit(main())
|