From 46912a83e8bc966adb4cb07a105e9973c0ae5d8c Mon Sep 17 00:00:00 2001 From: Christian Gmeiner Date: Fri, 24 Apr 2026 09:14:28 +0200 Subject: [PATCH] mesa/main: Auto-generate MESA_VERBOSE=api trace dispatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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_ 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 Generated-by: Claude --- src/mesa/glapi/glapi/gen/api_trace_c.py | 281 ++++++++++++++++++++++++ src/mesa/glapi/glapi/gen/meson.build | 9 + src/mesa/main/context.c | 4 + src/mesa/main/context.h | 3 + src/mesa/meson.build | 1 + 5 files changed, 298 insertions(+) create mode 100644 src/mesa/glapi/glapi/gen/api_trace_c.py diff --git a/src/mesa/glapi/glapi/gen/api_trace_c.py b/src/mesa/glapi/glapi/gen/api_trace_c.py new file mode 100644 index 00000000000..3c0e87445af --- /dev/null +++ b/src/mesa/glapi/glapi/gen/api_trace_c.py @@ -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 +#include + +#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) diff --git a/src/mesa/glapi/glapi/gen/meson.build b/src/mesa/glapi/glapi/gen/meson.build index b8a107e4f7d..d11d6a06f12 100644 --- a/src/mesa/glapi/glapi/gen/meson.build +++ b/src/mesa/glapi/glapi/gen/meson.build @@ -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( diff --git a/src/mesa/main/context.c b/src/mesa/main/context.c index 5df84fdd37e..4d72dad74de 100644 --- a/src/mesa/main/context.c +++ b/src/mesa/main/context.c @@ -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. */ diff --git a/src/mesa/main/context.h b/src/mesa/main/context.h index f51533615a7..bc2a80bfead 100644 --- a/src/mesa/main/context.h +++ b/src/mesa/main/context.h @@ -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); diff --git a/src/mesa/meson.build b/src/mesa/meson.build index 2b801082eac..21eabf00d42 100644 --- a/src/mesa/meson.build +++ b/src/mesa/meson.build @@ -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,