| #!/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() |