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..da815bd620f --- /dev/null +++ b/src/mesa/glapi/glapi/gen/api_trace_c.py @@ -0,0 +1,226 @@ +# 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 apiexec +import gl_XML +import license + + +TRACE_ARRAY_BUFSZ = 512 + + +header = """ +#include +#include + +#include "glapi/glapi/glapi.h" +#include "main/api_trace_helpers.h" +#include "main/context.h" +#include "main/enums.h" +#include "main/errors.h" +#include "dispatch.h" +""" + + +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. +ARRAY_ELEM_KIND = { + 'GLfloat': 'MESA_TRACE_ELEM_FLOAT', + 'GLclampf': 'MESA_TRACE_ELEM_FLOAT', + 'GLdouble': 'MESA_TRACE_ELEM_DOUBLE', + 'GLclampd': 'MESA_TRACE_ELEM_DOUBLE', + 'GLbyte': 'MESA_TRACE_ELEM_BYTE', + 'GLshort': 'MESA_TRACE_ELEM_SHORT', + 'GLint': 'MESA_TRACE_ELEM_INT', + 'GLsizei': 'MESA_TRACE_ELEM_INT', + 'GLfixed': 'MESA_TRACE_ELEM_INT', + 'GLclampx': 'MESA_TRACE_ELEM_INT', + 'GLubyte': 'MESA_TRACE_ELEM_UBYTE', + 'GLushort': 'MESA_TRACE_ELEM_USHORT', + 'GLuint': 'MESA_TRACE_ELEM_UINT', + 'GLhalfNV': 'MESA_TRACE_ELEM_HALF', + 'GLint64': 'MESA_TRACE_ELEM_INT64', + 'GLuint64': 'MESA_TRACE_ELEM_UINT64', + 'GLintptr': 'MESA_TRACE_ELEM_INTPTR', + 'GLsizeiptr': 'MESA_TRACE_ELEM_INTPTR', +} + + +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', kind, 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_KIND and count_expr is not None: + return ('array', ARRAY_ELEM_KIND[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) + + def printRealFooter(self): + pass + + 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': + _, kind, 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_array({buf}, sizeof({buf}),' + ' {name}, {count}, {kind});' + .format(buf=buf_name, name=name, + count=count_expr, kind=kind)) + 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()) + for f in functions: + self.print_wrapper(f) + self.print_install(functions) + + +if __name__ == '__main__': + apiexec.print_glapi_file(PrintCode()) diff --git a/src/mesa/glapi/glapi/gen/meson.build b/src/mesa/glapi/glapi/gen/meson.build index b8a107e4f7d..5a07a723e70 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@', '-f', '@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/api_trace_helpers.c b/src/mesa/main/api_trace_helpers.c new file mode 100644 index 00000000000..220e011d880 --- /dev/null +++ b/src/mesa/main/api_trace_helpers.c @@ -0,0 +1,101 @@ +/* + * Copyright © 2026 Igalia S.L. + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#include "util/glheader.h" + +#include "main/api_trace_helpers.h" + +void +_mesa_trace_format_array(char *buf, size_t buflen, + const void *arr, size_t n, + enum mesa_trace_elem_kind kind) +{ + static const size_t MAX_TRACE_ARRAY = 16; + + if (!arr) { + snprintf(buf, buflen, "(null)"); + return; + } + + size_t shown = (n < MAX_TRACE_ARRAY) ? n : MAX_TRACE_ARRAY; + size_t pos = 0; + int w; + + w = snprintf(buf + pos, buflen - pos, "["); + if (w < 0 || (size_t)w >= buflen - pos) + return; + pos += w; + + for (size_t i = 0; i < shown; i++) { + if (i > 0) { + w = snprintf(buf + pos, buflen - pos, ", "); + if (w < 0 || (size_t)w >= buflen - pos) + return; + pos += w; + } + + switch (kind) { + case MESA_TRACE_ELEM_FLOAT: + w = snprintf(buf + pos, buflen - pos, "%f", + ((const GLfloat *)arr)[i]); + break; + case MESA_TRACE_ELEM_DOUBLE: + w = snprintf(buf + pos, buflen - pos, "%f", + ((const GLdouble *)arr)[i]); + break; + case MESA_TRACE_ELEM_BYTE: + w = snprintf(buf + pos, buflen - pos, "%d", + ((const GLbyte *)arr)[i]); + break; + case MESA_TRACE_ELEM_SHORT: + w = snprintf(buf + pos, buflen - pos, "%d", + ((const GLshort *)arr)[i]); + break; + case MESA_TRACE_ELEM_INT: + w = snprintf(buf + pos, buflen - pos, "%d", + ((const GLint *)arr)[i]); + break; + case MESA_TRACE_ELEM_UBYTE: + w = snprintf(buf + pos, buflen - pos, "%u", + ((const GLubyte *)arr)[i]); + break; + case MESA_TRACE_ELEM_USHORT: + w = snprintf(buf + pos, buflen - pos, "%u", + ((const GLushort *)arr)[i]); + break; + case MESA_TRACE_ELEM_UINT: + w = snprintf(buf + pos, buflen - pos, "%u", + ((const GLuint *)arr)[i]); + break; + case MESA_TRACE_ELEM_HALF: + w = snprintf(buf + pos, buflen - pos, "0x%x", + ((const GLhalfNV *)arr)[i]); + break; + case MESA_TRACE_ELEM_INT64: + w = snprintf(buf + pos, buflen - pos, "%" PRId64, + (int64_t)((const GLint64 *)arr)[i]); + break; + case MESA_TRACE_ELEM_UINT64: + w = snprintf(buf + pos, buflen - pos, "%" PRIu64, + (uint64_t)((const GLuint64 *)arr)[i]); + break; + case MESA_TRACE_ELEM_INTPTR: + w = snprintf(buf + pos, buflen - pos, "%" PRIdPTR, + (intptr_t)((const GLintptr *)arr)[i]); + break; + } + if (w < 0 || (size_t)w >= buflen - pos) + return; + pos += w; + } + + if (n > MAX_TRACE_ARRAY) + snprintf(buf + pos, buflen - pos, ", ... %zu of %zu]", shown, n); + else + snprintf(buf + pos, buflen - pos, "]"); +} diff --git a/src/mesa/main/api_trace_helpers.h b/src/mesa/main/api_trace_helpers.h new file mode 100644 index 00000000000..25eed647a7e --- /dev/null +++ b/src/mesa/main/api_trace_helpers.h @@ -0,0 +1,28 @@ +/* + * Copyright © 2026 Igalia S.L. + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include + +enum mesa_trace_elem_kind { + MESA_TRACE_ELEM_FLOAT, + MESA_TRACE_ELEM_DOUBLE, + MESA_TRACE_ELEM_BYTE, + MESA_TRACE_ELEM_SHORT, + MESA_TRACE_ELEM_INT, + MESA_TRACE_ELEM_UBYTE, + MESA_TRACE_ELEM_USHORT, + MESA_TRACE_ELEM_UINT, + MESA_TRACE_ELEM_HALF, + MESA_TRACE_ELEM_INT64, + MESA_TRACE_ELEM_UINT64, + MESA_TRACE_ELEM_INTPTR, +}; + +void +_mesa_trace_format_array(char *buf, size_t buflen, + const void *arr, size_t n, + enum mesa_trace_elem_kind kind); diff --git a/src/mesa/main/context.c b/src/mesa/main/context.c index 799aaf798c9..b08f8601681 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 979d70b1a2a..08eb7e55ec2 100644 --- a/src/mesa/meson.build +++ b/src/mesa/meson.build @@ -10,6 +10,8 @@ files_libmesa = files( 'main/accum.h', 'main/api_arrayelt.c', 'main/api_arrayelt.h', + 'main/api_trace_helpers.c', + 'main/api_trace_helpers.h', 'main/arbprogram.c', 'main/arrayobj.c', 'main/arrayobj.h', @@ -417,6 +419,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,