vulkan: add helper to fill out spirv caps automatically

The Vulkan XML tells us exactly which caps are implied by which API versions,
features, extensions, and properties. We just need to parse that and
autogenerate some glue code, that way drivers don't need to track this manually.
This reduces the boilerplate needed when bringing up new features.

Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Reviewed-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Reviewed-by: Iván Briano <ivan.briano@intel.com>
Acked-By: Mike Blumenkrantz <michael.blumenkrantz@gmail.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/28905>
This commit is contained in:
Alyssa Rosenzweig 2024-03-26 16:41:19 -04:00 committed by Marge Bot
parent ba11b12a82
commit 1759c0eba7
5 changed files with 147 additions and 0 deletions

View File

@ -0,0 +1 @@
struct spirv_capabilities {};

View File

@ -153,6 +153,17 @@ vk_physical_device_properties = custom_target(
depend_files : vk_physical_device_properties_gen_depend_files,
)
vk_physical_device_spirv_caps = custom_target(
'vk_physical_device_spirv_caps',
input : [vk_physical_device_spirv_caps_gen, vk_api_xml],
output : 'vk_physical_device_spirv_caps.c',
command : [
prog_python, '@INPUT0@', '--xml', '@INPUT1@',
'--out-c', '@OUTPUT0@', '--beta', with_vulkan_beta.to_string()
],
depend_files : vk_physical_device_spirv_caps_gen_depend_files,
)
vk_synchronization_helpers = custom_target(
'vk_synchronization_helpers',
input : [vk_synchronization_helpers_gen, vk_api_xml],
@ -183,6 +194,7 @@ vulkan_lite_runtime_files += [
vk_format_info,
vk_physical_device_features,
vk_physical_device_properties,
vk_physical_device_spirv_caps,
vk_synchronization_helpers,
]
@ -230,6 +242,7 @@ libvulkan_lite_instance = static_library(
# - idep_vulkan_runtime
idep_vulkan_lite_runtime_headers = declare_dependency(
sources : [
spirv_info_h,
vk_cmd_enqueue_entrypoints[0],
vk_cmd_queue[1],
vk_common_entrypoints[0],

View File

@ -145,6 +145,9 @@ VkResult
vk_physical_device_check_device_features(struct vk_physical_device *physical_device,
const VkDeviceCreateInfo *pCreateInfo);
struct spirv_capabilities
vk_physical_device_get_spirv_capabilities(const struct vk_physical_device *pdev);
#ifdef __cplusplus
}
#endif

View File

@ -52,6 +52,10 @@ vk_physical_device_features_gen_depend_files = [
vk_physical_device_properties_gen_depend_files = [
files('vk_extensions.py'),
]
vk_physical_device_spirv_caps_gen_depend_files = [
files('vk_extensions.py'),
files('vk_physical_device_features_gen.py'),
]
vk_synchronization_helpers_gen_depend_files = [
files('vk_extensions.py'),
]
@ -63,6 +67,7 @@ vk_cmd_queue_gen = files('vk_cmd_queue_gen.py')
vk_dispatch_trampolines_gen = files('vk_dispatch_trampolines_gen.py')
vk_physical_device_features_gen = files('vk_physical_device_features_gen.py')
vk_physical_device_properties_gen = files('vk_physical_device_properties_gen.py')
vk_physical_device_spirv_caps_gen = files('vk_physical_device_spirv_caps_gen.py')
vk_synchronization_helpers_gen = files('vk_synchronization_helpers_gen.py')
files_vulkan_util = files(

View File

@ -0,0 +1,125 @@
COPYRIGHT=u"""
/* Copyright 2024 Valve Corporation
* Copyright 2021 Intel Corporation
* SPDX-License-Identifier: MIT
*/
"""
import argparse
from vk_physical_device_features_gen import get_renamed_feature, str_removeprefix
import os
import sys
import xml.etree.ElementTree as et
import mako
from mako.template import Template
TEMPLATE_C = Template(COPYRIGHT + """
/* This file generated from ${filename}, don't edit directly. */
#include "vk_physical_device.h"
#include "vk_instance.h"
#include "vk_shader.h"
/* for spirv_supported_capabilities */
#include "compiler/spirv/spirv_info.h"
struct spirv_capabilities
vk_physical_device_get_spirv_capabilities(const struct vk_physical_device *pdev)
{
const struct vk_features *f = &pdev->supported_features;
const struct vk_device_extension_table *e = &pdev->supported_extensions;
const struct vk_properties *p = &pdev->properties;
uint32_t api_version = pdev->instance->app_info.api_version;
struct spirv_capabilities caps = { false, };
/* We |= for everything because some caps have multiple names but the
* same enum value and they sometimes have different enables in the
* Vulkan spec. To handle this, we just | all the enables together.
*/
% for cap in caps:
caps.${cap} |= ${' | '.join(caps[cap])};
% endfor
return caps;
}
""")
# These don't exist in the SPIR-V headers for one reason or another.
NON_EXISTANT_CAPS = [
# This isn't a cap, it's an execution mode.
#
# https://gitlab.khronos.org/vulkan/vulkan/-/merge_requests/6618
'MaximallyReconvergesKHR',
# This extension got published but never got merged to SPIRV-Headers
#
# https://gitlab.khronos.org/spirv/spirv-extensions/-/merge_requests/238
'ClusterCullingShadingHUAWEI',
# Exclude the one beta cap.
'ShaderEnqueueAMDX',
]
def process_enable(enab):
attrib = enab.attrib
if 'property' in attrib:
if attrib['value'] == 'VK_TRUE':
return f"p->{attrib['member']}"
else:
return f"(p->{attrib['member']} & {attrib['value']})"
elif 'extension' in attrib:
return f"e->{str_removeprefix(attrib['extension'], 'VK_')}"
elif 'feature' in attrib:
feat = get_renamed_feature(attrib['struct'], attrib['feature'])
return f"f->{feat}"
else:
version = attrib['version']
return f"(api_version >= VK_API_{str_removeprefix(version, 'VK_')})"
def get_capabilities(doc, beta):
caps = {}
for cap in doc.findall('./spirvcapabilities/spirvcapability'):
name = cap.attrib['name']
if name in NON_EXISTANT_CAPS:
continue
enables = cap.findall('enable')
lst = caps.setdefault(name, [])
lst += [process_enable(x) for x in enables]
# Remove duplicates
for cap in caps:
caps[cap] = list(dict.fromkeys(caps[cap]))
return caps
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--out-c', required=True, help='Output C file.')
parser.add_argument('--beta', required=True, help='Enable beta extensions.')
parser.add_argument('--xml', required=True, help='Vulkan API XML file.')
args = parser.parse_args()
environment = {
'filename': os.path.basename(__file__),
'caps': get_capabilities(et.parse(args.xml), args.beta),
}
try:
with open(args.out_c, 'w', encoding='utf-8') as f:
f.write(TEMPLATE_C.render(**environment))
except Exception:
# In the event there's an error, this uses some helpers from mako
# to print a useful stack trace and prints it, then exits with
# status 1, if python is run with debug; otherwise it just raises
# the exception
print(mako.exceptions.text_error_template().render(), file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()