diff --git a/renderdoc/driver/gl/gen_dispatch_table.py b/renderdoc/driver/gl/gen_dispatch_table.py new file mode 100644 index 000000000..b445fa5d7 --- /dev/null +++ b/renderdoc/driver/gl/gen_dispatch_table.py @@ -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('(?PPFN.*PROC) (?P.*);(\s*\/\/ aliases +)?(?P[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[A-Za-z_0-9\s*]+)\([A-Z_ *]* (?PPFN[A-Z_0-9]+)\) \((?P.*)\);') + +# 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))