blob: b69be1789865c89c291cc454a2afceb6eb84f393 [file]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2026 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import os
import xml.etree.ElementTree as ET
def main():
parser = argparse.ArgumentParser(
description="Generate null_vulkan.cc stubs from vk.xml"
)
parser.add_argument("--vk-xml", required=True, help="Path to vk.xml")
parser.add_argument("--output", required=True, help="Output C++ file path")
args = parser.parse_args()
tree = ET.parse(args.vk_xml)
root = tree.getroot()
# Map platform names to preprocessor protection macros
# e.g., platform "android" -> "VK_USE_PLATFORM_ANDROID_KHR"
platforms = {}
for p in root.findall(".//platform"):
platforms[p.get("name")] = p.get("protect")
# Map extension-level protections and identify disabled / non-Vulkan extensions
cmd_protects = {}
disabled_cmds = set()
for ext in root.findall(".//extension"):
supported = ext.get("supported")
# Skip extensions that do not target the core Vulkan API
if supported and "vulkan" not in supported.split(","):
for cmd in ext.findall(".//command"):
c_name = cmd.get("name")
if c_name:
disabled_cmds.add(c_name)
continue
# Record platform protection macro for commands defined by this extension
ext_platform = ext.get("platform")
ext_protect = ext.get("protect")
protect = ext_protect or (
platforms.get(ext_platform) if ext_platform else None
)
if protect:
for cmd in ext.findall(".//command"):
cmd_name = cmd.get("name")
if cmd_name:
cmd_protects[cmd_name] = protect
# Extract command prototypes, parameters, return types, and alias relations
parsed_cmds = {}
alias_cmds = {}
for cmd in root.findall(".//command"):
alias = cmd.get("alias")
name = cmd.get("name")
protect = cmd.get("protect")
export = cmd.get("export")
if export and "vulkan" not in export.split(","):
continue
if name in disabled_cmds:
continue
# Record alias command mapping (e.g., vkCmdSetEvent2KHR -> vkCmdSetEvent2)
if alias:
alias_cmds[name] = (alias, protect)
continue
proto = cmd.find("proto")
if proto is None:
continue
name_elem = proto.find("name")
if name_elem is None:
continue
name = name_elem.text
if name in disabled_cmds:
continue
return_type_elem = proto.find("type")
return_type = (
return_type_elem.text if return_type_elem is not None else "void"
)
proto_text = "VKAPI_ATTR " + "".join(proto.itertext()).strip()
params_text = []
for param in cmd.findall("param"):
# Filter out parameters specific to non-vulkan APIs (e.g., api="vulkansc")
p_api = param.get("api")
if p_api and "vulkan" not in p_api.split(","):
continue
param_str = "".join(param.itertext()).strip()
params_text.append(" " + param_str)
parsed_cmds[name] = {
"return_type": return_type,
"proto_text": proto_text,
"params_text": params_text,
"protect": protect,
"name": name,
}
# Emit stub implementation
output_lines = [
"// Generated by gen_null_vulkan.py - DO NOT EDIT\n",
"#include <vulkan/vulkan.h>\n",
'#ifdef __cplusplus\nextern "C" {\n#endif\n',
]
written = set()
def emit_cmd(name, info, protect):
if name in written or name in disabled_cmds:
return
written.add(name)
ret_type = info["return_type"]
# Determine stub return statement based on Vulkan function semantics:
# - vkCreateInstance must fail with VK_ERROR_INCOMPATIBLE_DRIVER
# - vkCreateDevice & vkEnumeratePhysicalDevices must fail with VK_ERROR_INITIALIZATION_FAILED
# - ProcAddr queries return NULL
# - General VkResult functions return VK_SUCCESS
# - General VkBool32 functions return VK_TRUE
# - void functions return nothing
if name == "vkCreateInstance":
body = "{ return VK_ERROR_INCOMPATIBLE_DRIVER; }"
elif name in ("vkEnumeratePhysicalDevices", "vkCreateDevice"):
body = "{ return VK_ERROR_INITIALIZATION_FAILED; }"
elif name in ("vkGetInstanceProcAddr", "vkGetDeviceProcAddr"):
body = "{ return NULL; }"
elif ret_type == "VkResult":
body = "{ return VK_SUCCESS; }"
elif ret_type == "VkBool32":
body = "{ return VK_TRUE; }"
elif ret_type == "void":
body = "{}"
elif ret_type == "PFN_vkVoidFunction":
body = "{ return NULL; }"
else:
body = "{ return 0; }"
# Format function signature and replace target function name if processing an alias
proto_prefix = info["proto_text"]
if info["name"] != name and info["name"] in proto_prefix:
idx = proto_prefix.rfind(info["name"])
proto_prefix = proto_prefix[:idx] + name
sig = proto_prefix + "(\n" + ",\n".join(info["params_text"]) + ")"
# Check for platform protection macro (#ifdef VK_USE_PLATFORM_...)
effective_protect = (
protect
or info.get("protect")
or cmd_protects.get(name)
or cmd_protects.get(info["name"])
)
if effective_protect:
output_lines.append(f"#ifdef {effective_protect}")
output_lines.append(f"{sig} {body}\n")
if effective_protect:
output_lines.append(f"#endif /* {effective_protect} */\n")
# Emit primary commands
for name, info in parsed_cmds.items():
emit_cmd(name, info, info["protect"])
# Emit aliased commands
for name, (alias, protect) in alias_cmds.items():
if alias in parsed_cmds:
target_info = parsed_cmds[alias]
emit_cmd(name, target_info, protect or target_info["protect"])
output_lines.append("#ifdef __cplusplus\n}\n#endif\n")
# Write to output file
os.makedirs(os.path.dirname(os.path.abspath(args.output)), exist_ok=True)
with open(args.output, "w", encoding="utf-8") as f:
f.write("\n".join(output_lines))
if __name__ == "__main__":
main()