mesa/main: Auto-generate MESA_VERBOSE=api trace dispatch

Adds api_trace_c.py that emits from gl_and_es_API.xml a _mesa_trace_Foo
wrapper per entrypoint plus _mesa_init_dispatch_trace(), called from
_mesa_initialize_dispatch_tables() when MESA_VERBOSE & VERBOSE_API.
Each wrapper logs one line via _mesa_debug() and forwards through
ctx->Dispatch.RealPublished.

Scalar arguments are printed type-aware: GLenum decoded via
_mesa_enum_to_string, const GLchar * as a null-guarded string,
GLbitfield as raw hex, numerics with a size-appropriate spec.

Counted array parameters — fixed count="N", or count="counter" with
optional count_scale (e.g. UniformMatrix4fv's value=count*16) — render
their first MAX_TRACE_ARRAY=16 elements inline as "[v0, v1, ...]" via
typed _mesa_trace_format_<T> helpers, with a "..., N of M]" suffix on
truncation. Output pointers, opaque GLvoid * blobs, and parameters with
no count info fall back to %p. No bitmask decoding or return-value
capture — that's apitrace's job.

Signed-off-by: Christian Gmeiner <cgmeiner@igalia.com>
Generated-by: Claude
This commit is contained in:
Christian Gmeiner 2026-04-24 09:14:28 +02:00
parent 9d70d1b2b3
commit 46912a83e8
5 changed files with 298 additions and 0 deletions

View file

@ -0,0 +1,281 @@
# Copyright © 2026 Igalia S.L.
# SPDX-License-Identifier: MIT
#
# Generates api_trace.c: per-entrypoint wrappers that log GL calls to stderr
# and forward to the real dispatch. Installed at context create when
# MESA_VERBOSE=api is set.
import gl_XML
import license
import sys
MAX_TRACE_ARRAY = 16
TRACE_ARRAY_BUFSZ = 512
header = """
#include <inttypes.h>
#include <stdio.h>
#include "glapi/glapi/glapi.h"
#include "main/context.h"
#include "main/enums.h"
#include "main/errors.h"
#include "dispatch.h"
#define MAX_TRACE_ARRAY {max_array}
"""
# ``{p}`` is replaced with the parameter name.
TYPE_FORMAT = {
'GLenum': ('%s', '_mesa_enum_to_string({p})'),
'GLboolean': ('%s', '{p} ? "GL_TRUE" : "GL_FALSE"'),
'GLbitfield': ('0x%x', '{p}'),
'GLbyte': ('%d', '{p}'),
'GLubyte': ('%u', '{p}'),
'GLshort': ('%d', '{p}'),
'GLushort': ('%u', '{p}'),
'GLint': ('%d', '{p}'),
'GLuint': ('%u', '{p}'),
'GLint64': ('%" PRId64 "', '(int64_t){p}'),
'GLuint64': ('%" PRIu64 "', '(uint64_t){p}'),
'GLsizei': ('%d', '{p}'),
'GLintptr': ('%" PRIdPTR "','(intptr_t){p}'),
'GLsizeiptr': ('%" PRIdPTR "','(intptr_t){p}'),
'GLfloat': ('%f', '{p}'),
'GLclampf': ('%f', '{p}'),
'GLdouble': ('%f', '{p}'),
'GLclampd': ('%f', '{p}'),
'GLfixed': ('%d', '{p}'),
'GLclampx': ('%d', '{p}'),
'GLhalfNV': ('0x%x', '{p}'),
'GLvdpauSurfaceNV': ('%" PRIdPTR "', '(intptr_t){p}'),
'GLsync': ('%p', '(void *){p}'),
'GLhandleARB': ('%u', '{p}'),
'GLDEBUGPROC': ('%p', '(void *){p}'),
'GLDEBUGPROCARB': ('%p', '(void *){p}'),
'GLDEBUGPROCAMD': ('%p', '(void *){p}'),
'GLDEBUGPROCKHR': ('%p', '(void *){p}'),
'GLVULKANPROCNV': ('%p', '(void *){p}'),
}
TYPE_ALIASES = {
'GLint64EXT': 'GLint64',
'GLuint64EXT': 'GLuint64',
'GLintptrARB': 'GLintptr',
'GLsizeiptrARB': 'GLsizeiptr',
'float': 'GLfloat',
'int': 'GLint',
}
# Element types not listed here fall back to ``%p`` at the call site. The
# format snippet is a raw C-source fragment so 64-bit specs can break out to
# use the inttypes.h macros.
ARRAY_ELEM_PRINTF = {
'GLfloat': ('"%f"', 'arr[i]'),
'GLdouble': ('"%f"', 'arr[i]'),
'GLclampf': ('"%f"', 'arr[i]'),
'GLclampd': ('"%f"', 'arr[i]'),
'GLbyte': ('"%d"', 'arr[i]'),
'GLshort': ('"%d"', 'arr[i]'),
'GLint': ('"%d"', 'arr[i]'),
'GLsizei': ('"%d"', 'arr[i]'),
'GLfixed': ('"%d"', 'arr[i]'),
'GLubyte': ('"%u"', 'arr[i]'),
'GLushort': ('"%u"', 'arr[i]'),
'GLuint': ('"%u"', 'arr[i]'),
'GLhalfNV': ('"0x%x"', 'arr[i]'),
'GLint64': ('"%" PRId64', '(int64_t)arr[i]'),
'GLuint64': ('"%" PRIu64', '(uint64_t)arr[i]'),
'GLintptr': ('"%" PRIdPTR', '(intptr_t)arr[i]'),
'GLsizeiptr': ('"%" PRIdPTR', '(intptr_t)arr[i]'),
}
def array_count_expr(p):
if p.counter:
scale = p.count_scale if p.count_scale else 1
if scale == 1:
return '(size_t){0}'.format(p.counter)
return '(size_t){0} * {1}'.format(p.counter, scale)
if p.count and p.count >= 2:
return str(p.count)
return None
def classify_param(p):
"""Return one of:
('scalar', spec, expr) printed inline
('array', elem_type, count_expr, name)
('opaque', spec, expr) printed as %p / hex fallback
"""
opaque = ('opaque', '%p', '(void *){0}'.format(p.name))
if not p.is_pointer():
ts = TYPE_ALIASES.get(p.type_string().strip(), p.type_string().strip())
if ts in TYPE_FORMAT:
spec, expr = TYPE_FORMAT[ts]
return ('scalar', spec, expr.format(p=p.name))
return ('scalar', '0x%x', p.name)
base = p.get_base_type_string()
if base in ('GLchar', 'GLcharARB'):
ts = p.type_string().lstrip()
if ts.startswith('const'):
return ('scalar', '%s',
'{p} ? (const char *){p} : "(null)"'.format(p=p.name))
return opaque
if p.is_output:
return opaque
elem = TYPE_ALIASES.get(base, base)
count_expr = array_count_expr(p)
if elem in ARRAY_ELEM_PRINTF and count_expr is not None:
return ('array', elem, count_expr, p.name)
return opaque
class PrintCode(gl_XML.gl_print_base):
def __init__(self):
super(PrintCode, self).__init__()
self.name = 'api_trace_c.py'
self.license = license.bsd_license_template % (
'Copyright (C) 2026 Christian Gmeiner', 'Christian Gmeiner')
def printRealHeader(self):
print(header.format(max_array=MAX_TRACE_ARRAY))
def printRealFooter(self):
pass
def print_array_helper(self, elem):
fmt, val = ARRAY_ELEM_PRINTF[elem]
print('static void')
print('_mesa_trace_format_{0}(char *buf, size_t buflen,'.format(elem))
print(' const {0} *arr, size_t n)'.format(elem))
print('{')
print(' if (!arr) {')
print(' snprintf(buf, buflen, "(null)");')
print(' return;')
print(' }')
print(' size_t shown = (n < MAX_TRACE_ARRAY) ? n : MAX_TRACE_ARRAY;')
print(' size_t pos = 0;')
print(' int w;')
print(' w = snprintf(buf + pos, buflen - pos, "[");')
print(' if (w < 0 || (size_t)w >= buflen - pos) return;')
print(' pos += w;')
print(' for (size_t i = 0; i < shown; i++) {')
print(' if (i > 0) {')
print(' w = snprintf(buf + pos, buflen - pos, ", ");')
print(' if (w < 0 || (size_t)w >= buflen - pos) return;')
print(' pos += w;')
print(' }')
print(' w = snprintf(buf + pos, buflen - pos, {fmt}, {val});'
.format(fmt=fmt, val=val))
print(' if (w < 0 || (size_t)w >= buflen - pos) return;')
print(' pos += w;')
print(' }')
print(' if (n > MAX_TRACE_ARRAY)')
print(' snprintf(buf + pos, buflen - pos,'
' ", ... %zu of %zu]", shown, n);')
print(' else')
print(' snprintf(buf + pos, buflen - pos, "]");')
print('}')
print('')
def print_wrapper(self, f):
params = [p for p in f.parameters if not p.is_padding]
classified = [classify_param(p) for p in params]
specs = []
args = []
prelude = []
for p, c in zip(params, classified):
if c[0] == 'array':
_, elem, count_expr, name = c
buf_name = '{0}_buf'.format(name)
prelude.append('char {buf}[{sz}];'
.format(buf=buf_name, sz=TRACE_ARRAY_BUFSZ))
prelude.append(
'_mesa_trace_format_{elem}({buf}, sizeof({buf}),'
' {name}, {count});'
.format(elem=elem, buf=buf_name,
name=name, count=count_expr))
specs.append('%s')
args.append(buf_name)
else:
_, spec, expr = c
specs.append(spec)
args.append(expr)
ret = f.return_type
param_string = f.get_parameter_string()
called = f.get_called_parameter_string()
print('static {rt} GLAPIENTRY'.format(rt=ret))
print('_mesa_trace_{name}({ps})'.format(name=f.name, ps=param_string))
print('{')
print(' GET_CURRENT_CONTEXT(ctx);')
for stmt in prelude:
print(' {0}'.format(stmt))
if args:
fmt_body = 'gl{0}({1})\\n'.format(f.name, ', '.join(specs))
print(' _mesa_debug(ctx, "{fmt}", {args});'
.format(fmt=fmt_body, args=', '.join(args)))
else:
print(' _mesa_debug(ctx, "gl{0}()\\n");'.format(f.name))
call = 'CALL_{name}(ctx->Dispatch.RealPublished, ({ca}));'.format(
name=f.name, ca=called if called else '')
if ret != 'void':
print(' return {call}'.format(call=call))
else:
print(' {call}'.format(call=call))
print('}')
print('')
def print_install(self, functions):
print('bool')
print('_mesa_init_dispatch_trace(struct gl_context *ctx)')
print('{')
print(' struct _glapi_table *table = _mesa_alloc_dispatch_table(false);')
print(' if (!table)')
print(' return false;')
print('')
for f in functions:
print(' SET_{name}(table, _mesa_trace_{name});'.format(name=f.name))
print('')
print(' ctx->Dispatch.Trace = table;')
print(' return true;')
print('}')
def printBody(self, api):
functions = list(api.functionIterateByOffset())
used_elem_types = set()
for f in functions:
for p in f.parameters:
if p.is_padding:
continue
c = classify_param(p)
if c[0] == 'array':
used_elem_types.add(c[1])
for elem in sorted(used_elem_types):
self.print_array_helper(elem)
for f in functions:
self.print_wrapper(f)
self.print_install(functions)
if __name__ == '__main__':
file_name = sys.argv[1]
printer = PrintCode()
api = gl_XML.parse_GL_API(file_name)
printer.Print(api)

View file

@ -111,6 +111,15 @@ main_unmarshal_table_c = custom_target(
capture : true,
)
main_api_trace_c = custom_target(
'api_trace.c',
input : ['api_trace_c.py', 'gl_and_es_API.xml'],
output : 'api_trace.c',
command : [prog_python, '@INPUT0@', '@INPUT1@'],
depend_files : glapi_xml_py_deps,
capture : true,
)
main_marshal_generated_c = []
foreach x : ['0', '1', '2', '3', '4', '5', '6', '7']
main_marshal_generated_c += custom_target(

View file

@ -906,6 +906,10 @@ _mesa_initialize_dispatch_tables(struct gl_context *ctx)
_mesa_init_dispatch_save_begin_end(ctx);
}
if ((MESA_VERBOSE & VERBOSE_API) &&
!_mesa_init_dispatch_trace(ctx))
return false;
/* This binds the dispatch table to the context, but MakeCurrent will
* bind it for the user. If glthread is enabled, it will override it.
*/

View file

@ -91,6 +91,9 @@ _mesa_alloc_dispatch_tables(gl_api api, struct gl_dispatch *d, bool glthread);
extern bool
_mesa_initialize_dispatch_tables(struct gl_context *ctx);
extern bool
_mesa_init_dispatch_trace(struct gl_context *ctx);
extern void
_mesa_set_dispatch(struct gl_context *ctx, struct _glapi_table *t);

View file

@ -417,6 +417,7 @@ files_libmesa += [
mesa_lex,
program_parse_tab,
main_api_exec_c,
main_api_trace_c,
main_api_exec_decl_h,
main_api_save_h,
main_api_save_init_h,