Add python script to replace bash/perl scripts generating GL hooks

This commit is contained in:
baldurk
2018-07-04 15:54:32 +01:00
parent 28ec4e4fb8
commit d9f439fbbe
+272
View File
@@ -0,0 +1,272 @@
#!/usr/bin/env python3
import os
import io
import sys
import re
import argparse
parser = argparse.ArgumentParser(description='Generate macros for handling GL dispatch table.')
parser.add_argument('-m', '--maxparam', type=int, default=17,
help='The maximum number of parameters to generate')
parsed_args = parser.parse_args()
# on msys, print crlf output
if sys.platform == 'msys':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, newline="\r\n")
# Get the file, relative to this script's location (same directory)
# that way we're not sensitive to CWD
pathname = os.path.abspath(os.path.dirname(sys.argv[0])) + os.path.sep
# Finding definitions in the dispatch table header
def_regex = re.compile('(?P<typedef>PFN.*PROC) (?P<name>.*);(\s*\/\/ aliases +)?(?P<aliases>[a-zA-Z0-9_ ,]*)?')
# Finding a function definition in the official headers
func_regex = re.compile('(WINAPI|APIENTRY) (w?gl[A-Za-z_0-9]+)\s?\(')
# Finding a typedef in the official headers
typedef_regex = re.compile('^typedef (?P<return>[A-Za-z_0-9\s*]+)\([A-Z_ *]* (?P<typedef>PFN[A-Z_0-9]+)\) \((?P<args>.*)\);')
# Replacing float arg[2] with float *arg in definitions
array_regex = re.compile('([A-Za-z_][a-zA-Z_0-9]*) ([A-Za-z_][a-zA-Z_0-9]*)\[[0-9]*\]')
# Split an argument definition up by extracting the last full word
argsplit_regex = re.compile('(.*)([\*\s])([a-zA-Z0-9]+)')
# List of hooks to define, will be filled out when processing the dispatch table header
hooks = []
# A dict of typedef information
# Elements contain:
# 'used': True if it's used by our definitions or not - False if unsupported
# 'function': The name of the function defined with this typedef.
# e.g. typedefs['PFNGLBEGINPROC']['function'] = 'glBegin'.
# 'return': The return type
# 'args': The list of arguments with types and arguments separated
# e.g. [["int", "a"], ["float", "b"]]
typedefs = {}
# Open the dispatch table file
with open(pathname + "gl_dispatch_table.h", 'r') as fp:
# For each line that defines a dispatch pointer, process it
for func in [line.strip() for line in fp.readlines() if "PFN" in line]:
match = def_regex.search(func)
# All lines that contain a dispatch pointer should match the regex
if not match:
raise RuntimeError("Badly formed definition: {0}".format(func))
# Split the list of aliases
aliases = match.group('aliases')
aliases = re.split(', *', aliases) if aliases != '' else []
# Add the hook
hook = { 'typedef': match.group('typedef'), 'name': match.group('name'), 'aliases': aliases }
hooks.append(hook)
# Add the typedefs for the base function and all aliases as used
typedefs['PFN{0}PROC'.format(hook['name'].upper())] = {'used': True}
for a in aliases:
typedefs['PFN{0}PROC'.format(a.upper())] = {'used': True}
# Read all the official headers into a single string
official_headers = []
for header in ['glcorearb.h', 'glext.h', 'gl32.h', 'glesext.h', 'wglext.h', 'legacygl.h']:
with open(pathname + 'official' + os.path.sep + header, 'r') as fp:
official_headers += fp.readlines()
# Look for function definitions and add typedef function names.
for line in official_headers:
match = func_regex.search(line)
if match:
typedef = 'PFN{0}PROC'.format(match.group(2).upper())
if typedef not in typedefs:
typedefs[typedef] = {'used': False}
typedefs[typedef]['function'] = match.group(2)
# Now find typedefs and add return type/argument data
for line in official_headers:
match = typedef_regex.search(line)
if match:
typedef = match.group('typedef')
typedefs[typedef]['return'] = match.group('return').strip()
args = match.group('args')
if args == '' or args == 'void':
args = []
else:
# Replace array arguments with pointers - see glPathGlyphIndexRangeNV
args = array_regex.sub(r"\1 *\2", match.group('args'))
# Create an array with each parameter as an element
args = [a.strip() for a in args.split(',')]
# Split up each argument
args = [re.split(' *, *', argsplit_regex.sub(r"\1\2,\3", a)) for a in args]
typedefs[typedef]['args'] = args
# Print the file, starting with a template header
print('''
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2018 Baldur Karlsson
*
* 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 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.
******************************************************************************/
#pragma once
// This file is autogenerated with gen_dispatch_table.py - any changes will be overwritten next time
// that script is run.
// $ ./gen_dispatch_table.py > gl_dispatch_table_defs.h
// We need to disable clang-format since this struct is programmatically generated
// clang-format off
'''.lstrip())
# Print the 'definitions' of these hooks - can be used for stringification or doing
# GetProcAddress style 'check name, return function'
print('#define ForEachSupported(FUNC) \\')
for hook in hooks:
print(' FUNC({}, {}); \\'.format(hook['name'], hook['name']))
for a in hook['aliases']:
print(' FUNC({}, {}); \\'.format(hook['name'], a))
print("\n\n\n")
# Print the actual definitions - used to forward into FuncWrapperN/AliasWrapperN to define exported
# hook implementations
print('#define DefineSupportedHooks() \\')
for hook in hooks:
typedef = typedefs[hook['typedef']]
num = len(typedef['args'])
arglist = ''
for arg in typedef['args']:
arglist += ', {}, {}'.format(arg[0], arg[1])
print(' FuncWrapper{}({}, {}{}); \\'.format(num, typedef['return'], hook['name'], arglist))
for a in hook['aliases']:
print(' AliasWrapper{}({}, {}, {}{}); \\'.format(num, typedef['return'], a, hook['name'], arglist))
print("\n\n\n")
print('#define ForEachUnsupported(FUNC) \\')
for typedef in typedefs.values():
# Don't print for functions we support, or wgl/etc functions
if typedef['used'] or typedef['function'][0:2] != 'gl':
continue
print(' FUNC({}); \\'.format(typedef['function']))
print("\n\n\n")
# For all typedefs not in the hooks, define them as unsupported
print('#define DefineUnsupportedHooks() \\')
for typedef in typedefs.values():
# Don't print for functions we support, or wgl/etc functions
if typedef['used'] or typedef['function'][0:2] != 'gl':
continue
num = len(typedef['args'])
arglist = ''
for arg in typedef['args']:
arglist += ', {}, {}'.format(arg[0], arg[1])
print(' UnsupportedWrapper{}({}, {}{}); \\'.format(num, typedef['return'], typedef['function'], arglist))
# Now generate wrapper macros
print('''
// the _renderdoc_hooked variants are to make sure we always have a function symbol exported that we
// can return from GetProcAddress. On posix systems if another library (or the application itself)
// creates a symbol called 'glEnable' we'll return the address of that, and break badly. Instead we
// leave the 'naked' versions for applications trying to import those symbols, and declare the
// _renderdoc_hooked for returning as a func pointer. The raw version calls directly into the hooked
// version to hopefully allow the linker to tail-call optimise and reduce the overhead.
''')
template = '''
#define FuncWrapper{num}(ret, function{macroargs}) \\
ret HOOK_CC CONCAT(function, _renderdoc_hooked)({argdecl}) \\
{{ \\
SCOPED_GLCALL(function); \\
return glhook.driver->function({argpass}); \\
}} \\
HOOK_EXPORT ret HOOK_CC function({argdecl}) \\
{{ \\
return CONCAT(function, _renderdoc_hooked)({argpass}); \\
}}
#define AliasWrapper{num}(ret, function, realfunc{macroargs}) \\
ret HOOK_CC CONCAT(function, _renderdoc_hooked)({argdecl}) \\
{{ \\
SCOPED_GLCALL(function); \\
return glhook.driver->realfunc({argpass}); \\
}} \\
HOOK_EXPORT ret HOOK_CC function({argdecl}) \\
{{ \\
return CONCAT(function, _renderdoc_hooked)({argpass}); \\
}}
#define UnsupportedWrapper{num}(ret, function{macroargs}) \\
typedef ret(HOOK_CC *CONCAT(function, _hooktype))({argdecl}); \\
CONCAT(function, _hooktype) CONCAT(unsupported_real_, function) = NULL; \\
ret HOOK_CC CONCAT(function, _renderdoc_hooked)({argdecl}) \\
{{ \\
static bool hit = false; \\
if(hit == false) \\
{{ \\
RDCERR("Function " STRINGIZE(function) " not supported - capture may be broken"); \\
hit = true; \\
}} \\
if(!CONCAT(unsupported_real_, function)) \\
CONCAT(unsupported_real_, function) = \\
(CONCAT(function, _hooktype))glhook.GetUnsupportedFunction(STRINGIZE(function)); \\
return CONCAT(unsupported_real_, function)({argpass}); \\
}} \\
HOOK_EXPORT ret HOOK_CC function({argdecl}) \\
{{ \\
return CONCAT(function, _renderdoc_hooked)({argpass}); \\
}}
'''
for num in range(parsed_args.maxparam+1):
macroargs = ', '.join([('t{0}, p{0}'.format(n+1)) for n in range(num)])
argdecl = ', '.join([('t{0} p{0}'.format(n+1)) for n in range(num)])
argpass = ', '.join([('p{0}'.format(n+1)) for n in range(num)])
macroargs = ', ' + macroargs if num > 0 else macroargs
print(template.format(num=num, macroargs=macroargs,
argdecl=argdecl, argpass=argpass))