diff --git a/src/util/meson.build b/src/util/meson.build index 21962816408..91fb0b89a12 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -211,6 +211,13 @@ format_srgb = custom_target( capture : true, ) +files_mesa_util += custom_target( + input : ['process_shader_stats.py', 'shader_stats.rnc', 'shader_stats.xml'], + output : 'shader_stats.h', + command : [prog_python, '@INPUT@'], + capture : true, +) + # subdir format provide files_mesa_format subdir('format') files_mesa_util += files_mesa_format diff --git a/src/util/process_shader_stats.py b/src/util/process_shader_stats.py new file mode 100644 index 00000000000..2d135c2ac47 --- /dev/null +++ b/src/util/process_shader_stats.py @@ -0,0 +1,232 @@ +# Copyright 2025 Valve Corporation +# SPDX-License-Identifier: MIT + +from mako.template import Template +import xml.etree.ElementTree as ET +import sys +import textwrap + +schema_filename, xml_filename = sys.argv[1:] + +try: + from lxml import etree + import rnc2rng + + rng = rnc2rng.dumps(rnc2rng.load(schema_filename)) + schema = etree.RelaxNG(etree.fromstring(rng.encode())) + valid = schema.validate(etree.parse(xml_filename)) + if not valid: + print(schema.error_log, file=sys.stderr) + sys.exit(1) +except ImportError: + # meson/ninja only displays stderr if the script fails, so this warning is + # only visible when someone changes the XML in a way that causes the below + # code to blow up. Ideally we'd add build dependencies but that might not be + # desirable for exotic platforms... This seems reasonable as a compromise. + print("", file=sys.stderr) + print("****", file=sys.stderr) + print("lxml or rnc2rng missing, skipping validation", file=sys.stderr) + print("If this script fails, install for diagnostics", file=sys.stderr) + print("****", file=sys.stderr) + print("", file=sys.stderr) + +# XXX: cribbed from genxml +def to_alphanum(name): + substitutions = { + ' ': '_', + '/': '_', + '[': '', + ']': '', + '(': '', + ')': '', + '-': '_', + ':': '', + '.': '', + ',': '', + '=': '', + '>': '', + '#': '', + '&': '', + '*': '', + '"': '', + '+': '', + '\'': '', + '?': '', + } + + for i, j in substitutions.items(): + name = name.replace(i, j) + + return name + +def safe_name(name): + name = to_alphanum(name) + if not name[0].isalpha(): + name = '_' + name + + return name + +TYPE_MAP = { + 'i8': ('uint8_t', 'i64', 'd'), + 'u8': ('int8_t', 'u64', 'u'), + 'i16': ('uint16_t', 'i64', 'd'), + 'u16': ('int16_t', 'u64', 'u'), + 'i32': ('uint32_t', 'i64', 'd'), + 'u32': ('int32_t', 'u64', 'u'), + 'i64': ('uint64_t', 'i64', 'd'), + 'u64': ('int64_t', 'u64', 'u'), + 'float': ('float', 'f64', 'f'), + 'bool': ('bool', 'bool', 'u') +} + +class Stat: + def __init__(self, el): + type_ = el.attrib.get('type', 'u32') + + self.name = el.attrib['name'] + self.display = el.attrib.get('display', self.name) + self.description = textwrap.dedent(el.text).replace('\n', ' ').strip() + self.count = int(el.attrib.get('count', 1)) + self.c_name = safe_name(self.display).lower() + self.c_type, self.vk_type, format_specifier = TYPE_MAP[type_] + self.format_strings = [f'%{format_specifier} {self.display.lower()}'] + self.format_args = [f'stats->{self.c_name}'] + + if self.count > 1: + self.format_strings = [self.format_strings[0].replace('#', str(i)) for i in range(self.count)] + self.format_args = [f'{self.format_args[0]}[{i}]' for i in range(self.count)] + +class ISA: + def __init__(self, el): + self.name = el.attrib['name'] + self.stats = [Stat(stat) for stat in el] + self.c_name = safe_name(self.name).lower() + self.c_struct_name = f"struct {self.c_name}_stats" + + # Derive a the format string to print statistics in GL (report.py) + # format. report.py has a weird special case for spills/fills, which we + # need to fix up here. + fmt = ', '.join([x for stat in self.stats for x in stat.format_strings]) + self.format_string = fmt.replace('%u spills, %u fills', '%u:%u spills:fills') + self.format_args = ', '.join([x for stat in self.stats for x in stat.format_args]) + +class Family: + def __init__(self, el): + self.name = el.attrib['name'] + self.isas = [ISA(isa) for isa in el] + self.c_name = safe_name(self.name).lower() + self.c_enum_name = f'enum {self.c_name}_stat_isa' + self.c_struct_name = f'struct {self.c_name}_stats' + + def isa_tag(self, isa): + return f'{self.c_name.upper()}_STAT_{isa.name.upper()}' + +def parse_file(root): + isas = [] + families = [] + + for el in root: + if el.tag == 'isa': + isas.append(ISA(el)) + elif el.tag == 'family': + family = Family(el) + families.append(family) + isas += family.isas + + return (families, isas) + +tree = ET.parse(xml_filename) +root = tree.getroot() +families, isas = parse_file(root) + +template = Template("""\ +#ifndef __SHADER_STATS_H +#define __SHADER_STATS_H +#include +#include +#include "util/u_debug.h" + +% for isa in isas: +${isa.c_struct_name} { +% for stat in isa.stats: +% if stat.count > 1: + ${stat.c_type} ${stat.c_name}[${stat.count}]; +% else: + ${stat.c_type} ${stat.c_name}; +% endif +% endfor +}; + +static inline int +${isa.c_name}_stats_fprintf(FILE *fp, const char *prefix, const ${isa.c_struct_name} *stats) +{ + return fprintf(fp, "%s shader: ${isa.format_string}\\n", prefix, ${isa.format_args}); +} + +static inline void +${isa.c_name}_stats_util_debug(struct util_debug_callback *debug, const char *prefix, const ${isa.c_struct_name} *stats) +{ + util_debug_message(debug, SHADER_INFO, "%s shader: ${isa.format_string}", prefix, ${isa.format_args}); +} + +#define vk_add_${isa.c_name}_stats(out, stats) do { ${'\\\\'} +% for stat in isa.stats: +% for i in range(stat.count): +% if stat.count > 1: + vk_add_exec_statistic_${stat.vk_type}(out, "${stat.name.replace('#', str(i))}", "${stat.description.replace('#', str(i))}", (stats)->${stat.c_name}[${i}]); ${'\\\\'} +% else: + vk_add_exec_statistic_${stat.vk_type}(out, "${stat.name}", "${stat.description}", (stats)->${stat.c_name}); ${'\\\\'} +% endif +% endfor +% endfor +} while(0) + +%endfor + +% for family in families: +${family.c_enum_name} { +% for isa in family.isas: + ${family.isa_tag(isa)}, +% endfor +}; + +${family.c_struct_name} { + ${family.c_enum_name} isa; + union { +% for isa in family.isas: + ${isa.c_struct_name} ${isa.name.lower()}; +% endfor + }; +}; + +#define vk_add_${family.c_name}_stats(out, stats) do { ${'\\\\'} +% for isa in family.isas: + if ((stats)->isa == ${family.isa_tag(isa)}) ${'\\\\'} + vk_add_${isa.c_name}_stats(out, &(stats)->${isa.name.lower()}); ${'\\\\'} +% endfor +} while(0) + +static inline void +${family.c_name}_stats_fprintf(FILE *fp, const char *prefix, const ${family.c_struct_name} *stats) +{ +% for isa in family.isas: + if (stats->isa == ${family.isa_tag(isa)}) + ${isa.c_name}_stats_fprintf(fp, prefix, &stats->${isa.name.lower()}); +% endfor +} + +static inline void +${family.c_name}_stats_util_debug(struct util_debug_callback *debug, const char *prefix, const ${family.c_struct_name} *stats) +{ +% for isa in family.isas: + if (stats->isa == ${family.isa_tag(isa)}) + ${isa.c_name}_stats_util_debug(debug, prefix, &stats->${isa.name.lower()}); +% endfor +} + +% endfor + +#endif +""") + +print(template.render(isas=isas, families=families)) diff --git a/src/util/shader_stats.rnc b/src/util/shader_stats.rnc new file mode 100644 index 00000000000..53962abb0fc --- /dev/null +++ b/src/util/shader_stats.rnc @@ -0,0 +1,26 @@ +# Copyright 2025 Valve Corporation +# SPDX-License-Identifier: MIT + +namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0" + +start = element shaderdb { (isa | family)* } + +family = element family { + attribute name { text }, + isa+ +} + +isa = element isa { + attribute name { text }, + element stat { + attribute name { text }, + attribute display { text }?, + attribute count { xsd:integer }?, + attribute more { "better" | "worse" }?, + attribute hash { xsd:boolean }?, + [ a:defaultValue = "u32" ] attribute type + { "u8" | "s8" | "u16" | "s16" | "u32" | "s32" | "float" | "bool" }?, + + text + }+ +} diff --git a/src/util/shader_stats.xml b/src/util/shader_stats.xml new file mode 100644 index 00000000000..467fc4afdf2 --- /dev/null +++ b/src/util/shader_stats.xml @@ -0,0 +1,2 @@ + +