Dynamic rendering is a Vulkan 1.3 feature. Most platforms have support for them, but there are several legacy platforms that don't support dynamic rendering or have driver bugs that don't allow us to use it. This change will make dynamic rendering optional allowing legacy platforms to use Vulkan. **Limitations** `GPU_LOADACTION_CLEAR` is implemented as clear attachments. Render passes do support load clear, but adding support to it would add complexity as it required multiple pipeline variations to support suspend/resume rendering. It isn't clear when which variation should be used what lead to compiling to many pipelines and branches in the codebase. Using clear attachments doesn't require the complexity for what is expected to be only used by platforms not supported by the GPU vendors. Subpass inputs and dual source blending are not supported as Subpass inputs can alter the exact binding location of attachments. Fixing this would add code complexity that is not used. Ref: #129063 **Current state**  Pull Request: https://projects.blender.org/blender/blender/pulls/129062
403 lines
14 KiB
Python
403 lines
14 KiB
Python
# SPDX-FileCopyrightText: 2024 Blender Authors
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
"""
|
|
This script is used to generate parts of `vk_to_string.hh` and `vk_to_string.cc` based on the
|
|
vulkan API commands, features and extensions we use.
|
|
|
|
When to use?
|
|
|
|
Every time we use a new `vkCmd*` or new extension, we should re-generate to detect updates.
|
|
Extensions can alter enum types which we would use.
|
|
|
|
How to use?
|
|
|
|
Generate source code that can be copied in `vk_to_string.cc`:
|
|
`python3 vk_to_string.py <path-to-vk-xml>`
|
|
Generate source code that can be copied into `vk_to_string.hh`:
|
|
`python3 vk_to_string.py <path-to-vk-xml> --header`
|
|
|
|
Every vulkan installation contains a `vk.xml` which contains the specification in a machine
|
|
readable format. `vk.xml` is also part of the vulkan library in blender libraries.
|
|
|
|
The generated source code will be printed to the console.
|
|
"""
|
|
import argparse
|
|
import xml.etree.ElementTree as ET
|
|
|
|
|
|
# List of features blender uses. Features can extend enum flags.
|
|
FEATURES = [
|
|
"VK_VERSION_1_0",
|
|
"VK_VERSION_1_1",
|
|
"VK_VERSION_1_2",
|
|
]
|
|
# List of extensions blender uses. These can extend enum flags.
|
|
EXTENSIONS = [
|
|
"VK_KHR_swapchain",
|
|
"VK_KHR_dynamic_rendering",
|
|
]
|
|
|
|
# List of vkCmd commands blender uses.
|
|
COMMANDS_TO_GEN = [
|
|
"vkCmdClearColorImage",
|
|
"vkCmdClearDepthStencilImage",
|
|
"vkCmdClearAttachments",
|
|
|
|
"vkCmdCopyImageToBuffer",
|
|
"vkCmdCopyBufferToImage",
|
|
"vkCmdCopyImage",
|
|
"vkCmdCopyBuffer",
|
|
|
|
"vkCmdFillBuffer",
|
|
"vkCmdBlitImage",
|
|
|
|
"vkCmdBindDescriptorSets",
|
|
"vkCmdPushConstants",
|
|
"vkCmdBindIndexBuffer",
|
|
"vkCmdBindVertexBuffers",
|
|
"vkCmdBindPipeline",
|
|
|
|
"vkCmdBeginRendering",
|
|
"vkCmdEndRendering",
|
|
"vkCmdBeginRenderPass",
|
|
"vkCmdEndRenderPass",
|
|
"vkCmdDraw",
|
|
"vkCmdDrawIndexed",
|
|
"vkCmdDrawIndirect",
|
|
"vkCmdDrawIndexedIndirect",
|
|
|
|
"vkCmdDispatch",
|
|
"vkCmdDispatchIndirect",
|
|
|
|
"vkCmdPipelineBarrier",
|
|
]
|
|
COMMANDS_TO_GEN = ["vkCmdBeginRenderPass", "vkCmdEndRenderPass"]
|
|
|
|
DEFAULT_ENUMS_TO_GENERATE = [
|
|
"VkObjectType"
|
|
]
|
|
ENUMS_TO_IGNORE = [
|
|
"VkStructureType"
|
|
]
|
|
|
|
# A list of struct members to ignore as they aren't supported or not useful.
|
|
MEMBERS_TO_IGNORE = [
|
|
"sType", "pNext",
|
|
# Disabled as these are arrays.
|
|
"srcOffsets", "dstOffsets",
|
|
# Disabled as it is an union
|
|
"clearValue",
|
|
# Disabled as we don't use cross queue synchronization
|
|
"srcQueueFamilyIndex", "dstQueueFamilyIndex"
|
|
]
|
|
|
|
|
|
### Utils - Formatting ###
|
|
def to_lower_snake_case(string):
|
|
result = ""
|
|
for char in string:
|
|
if char.isupper() and len(result) != 0:
|
|
result += "_"
|
|
result += char.lower()
|
|
return result
|
|
|
|
|
|
### Commands ###
|
|
def extract_type_names(commands, r_types):
|
|
for command in commands:
|
|
for param in command.findall("param"):
|
|
param_type = param.findtext("type")
|
|
if param_type not in r_types:
|
|
r_types.append(param_type)
|
|
|
|
|
|
### Enumerations ###
|
|
def generate_enum_to_string_hpp(enum):
|
|
vk_name = enum.get("name")
|
|
result = ""
|
|
result += f"const char *to_string({vk_name} {to_lower_snake_case(vk_name)});\n"
|
|
return result
|
|
|
|
|
|
def generate_enum_to_string_cpp_case(elem):
|
|
result = ""
|
|
vk_elem_name = elem.get("name")
|
|
result += f" case {vk_elem_name}:\n"
|
|
result += f" return STRINGIFY({vk_elem_name});\n\n"
|
|
return result
|
|
|
|
|
|
def generate_enum_to_string_cpp(enum, features, extensions):
|
|
vk_name = enum.get("name")
|
|
vk_name_parameter = to_lower_snake_case(vk_name)
|
|
result = ""
|
|
result += f"const char *to_string(const {vk_name} {vk_name_parameter})\n"
|
|
result += "{\n"
|
|
result += f" switch ({vk_name_parameter}) {{\n"
|
|
for elem in enum.findall("enum"):
|
|
result += generate_enum_to_string_cpp_case(elem)
|
|
for feature in features:
|
|
enum_extensions = feature.findall(f"require/enum[@extends='{vk_name}']")
|
|
if not enum_extensions:
|
|
continue
|
|
feature_name = feature.get("name")
|
|
result += f" /* Extensions for {feature_name}. */\n"
|
|
for elem in enum_extensions:
|
|
result += generate_enum_to_string_cpp_case(elem)
|
|
for extension in extensions:
|
|
enum_extensions = extension.findall(f"require/enum[@extends='{vk_name}']")
|
|
if not enum_extensions:
|
|
continue
|
|
extension_name = extension.get("name")
|
|
result += f" /* Extensions for {extension_name}. */\n"
|
|
for elem in enum_extensions:
|
|
result += generate_enum_to_string_cpp_case(elem)
|
|
|
|
result += " default:\n"
|
|
result += " break;\n"
|
|
|
|
result += " }\n"
|
|
result += f" return STRINGIFY_ARG({vk_name_parameter});\n"
|
|
result += "}\n"
|
|
return result
|
|
|
|
|
|
### Bit-flags ###
|
|
def generate_bitflag_to_string_hpp(vk_name):
|
|
vk_name_parameter = to_lower_snake_case(vk_name)
|
|
result = ""
|
|
result += f"std::string to_string_{vk_name_parameter}({vk_name} {to_lower_snake_case(vk_name)});\n"
|
|
return result
|
|
|
|
|
|
def generate_bitflag_to_string_cpp_case(vk_parameter_name, elem):
|
|
vk_elem_name = elem.get("name")
|
|
|
|
result = ""
|
|
result += f" if ({vk_parameter_name} & {vk_elem_name}) {{\n"
|
|
result += f" ss << STRINGIFY({vk_elem_name}) << \", \";\n"
|
|
result += f" }}\n"
|
|
return result
|
|
|
|
|
|
def generate_bitflag_to_string_cpp(vk_name, enum, features, extensions):
|
|
vk_enum_name = enum.get("name")
|
|
vk_name_parameter = to_lower_snake_case(vk_name)
|
|
result = ""
|
|
result += f"std::string to_string_{vk_name_parameter}(const {vk_name} {vk_name_parameter})\n"
|
|
result += "{\n"
|
|
result += " std::stringstream ss;\n"
|
|
result += "\n"
|
|
for elem in enum.findall("enum"):
|
|
result += generate_bitflag_to_string_cpp_case(vk_name_parameter, elem)
|
|
for feature in features:
|
|
enum_extensions = feature.findall(f"require/enum[@extends='{vk_enum_name}']")
|
|
if not enum_extensions:
|
|
continue
|
|
feature_name = feature.get("name")
|
|
result += f" /* Extensions for {feature_name}. */\n"
|
|
for elem in enum_extensions:
|
|
result += generate_bitflag_to_string_cpp_case(vk_name_parameter, elem)
|
|
for extension in extensions:
|
|
enum_extensions = extension.findall(f"require/enum[@extends='{vk_enum_name}']")
|
|
if not enum_extensions:
|
|
continue
|
|
extension_name = extension.get("name")
|
|
result += f" /* Extensions for {extension_name}. */\n"
|
|
for elem in enum_extensions:
|
|
result += generate_bitflag_to_string_cpp_case(vk_name_parameter, elem)
|
|
|
|
result += "\n"
|
|
result += f" std::string result = ss.str();\n"
|
|
result += f" if (result.size() >= 2) {{\n"
|
|
result += f" result.erase(result.size() - 2, 2);\n"
|
|
result += f" }}\n"
|
|
result += f" return result;\n"
|
|
result += "}\n"
|
|
return result
|
|
|
|
|
|
### Structs ###
|
|
def generate_struct_to_string_hpp(struct):
|
|
vk_name = struct.get("name")
|
|
vk_name_parameter = to_lower_snake_case(vk_name)
|
|
result = ""
|
|
result += f"std::string to_string(const {vk_name} &{vk_name_parameter}, int indentation_level=0);\n"
|
|
return result
|
|
|
|
|
|
def generate_struct_to_string_cpp(struct, flags_to_generate, enums_to_generate, structs_to_generate):
|
|
vk_name = struct.get("name")
|
|
vk_name_parameter = to_lower_snake_case(vk_name)
|
|
header = ""
|
|
header += f"std::string to_string(const {vk_name} &{vk_name_parameter}, int indentation_level)\n"
|
|
header += f"{{\n"
|
|
result = ""
|
|
result += f" std::stringstream ss;\n"
|
|
pre = ""
|
|
indentation_used = False
|
|
for member in struct.findall("member"):
|
|
member_type = member.findtext("type")
|
|
member_type_parameter = to_lower_snake_case(member_type)
|
|
member_name = member.findtext("name")
|
|
member_name_parameter = to_lower_snake_case(member_name)
|
|
if member_name in MEMBERS_TO_IGNORE:
|
|
continue
|
|
|
|
result += f" ss << \"{pre}{member_name_parameter}=\" << "
|
|
if member_type in flags_to_generate:
|
|
result += f"to_string_{member_type_parameter}({vk_name_parameter}.{member_name})"
|
|
elif member_type in enums_to_generate:
|
|
result += f"to_string({vk_name_parameter}.{member_name})"
|
|
elif member_type in structs_to_generate:
|
|
result += "std::endl;\n"
|
|
result += f" ss << std::string(indentation_level * 2 + 2, ' ') << to_string({vk_name_parameter}.{member_name}, indentation_level + 1);\n"
|
|
result += f" ss << std::string(indentation_level * 2, ' ')"
|
|
indentation_used = True
|
|
else:
|
|
result += f"{vk_name_parameter}.{member_name}"
|
|
result += ";\n"
|
|
pre = ", "
|
|
result += f"\n"
|
|
result += f" return ss.str();\n"
|
|
result += f"}}\n"
|
|
if not indentation_used:
|
|
header += " UNUSED_VARS(indentation_level);\n"
|
|
return header + result
|
|
|
|
|
|
# Parsing vk.xml
|
|
def parse_features(root):
|
|
# Find all features that we use.
|
|
features = []
|
|
for feature_name in FEATURES:
|
|
feature = root.find(f"feature[@name='{feature_name}']")
|
|
assert (feature is not None)
|
|
features.append(feature)
|
|
return features
|
|
|
|
|
|
def parse_extensions(root):
|
|
# Find all extensions that we use.
|
|
extensions = []
|
|
for extension_name in EXTENSIONS:
|
|
extension = root.find(f"extensions/extension[@name='{extension_name}']")
|
|
assert (extension is not None)
|
|
extensions.append(extension)
|
|
return extensions
|
|
|
|
|
|
def parse_all_commands(root):
|
|
commands = []
|
|
for command in root.findall("commands/command"):
|
|
command_name = command.findtext("proto/name")
|
|
if command_name in COMMANDS_TO_GEN:
|
|
commands.append(command)
|
|
return commands
|
|
|
|
|
|
def parse_all_flags(root):
|
|
all_flags = {}
|
|
for flag_type in root.findall("types/type[@category='bitmask']"):
|
|
flag_type_name = flag_type.findtext("name")
|
|
flag_type_bits_name = flag_type.get("requires")
|
|
if flag_type_name and flag_type_bits_name:
|
|
all_flags[flag_type_name] = flag_type_bits_name
|
|
return all_flags
|
|
|
|
|
|
# Extraction of used data types.
|
|
def extract_used_types(root, commands, all_flags):
|
|
enums_to_generate = []
|
|
enums_to_generate.extend(DEFAULT_ENUMS_TO_GENERATE)
|
|
|
|
flags_to_generate = []
|
|
structs_to_generate = []
|
|
|
|
types_undetermined = []
|
|
extract_type_names(commands, types_undetermined)
|
|
while types_undetermined:
|
|
newly_found_types = []
|
|
for type_name in types_undetermined:
|
|
if root.find(f"enums[@name='{type_name}']") is not None:
|
|
if type_name not in enums_to_generate and type_name not in ENUMS_TO_IGNORE:
|
|
enums_to_generate.append(type_name)
|
|
elif type_name in all_flags and type_name not in flags_to_generate:
|
|
flags_to_generate.append(type_name)
|
|
elif type_name not in structs_to_generate:
|
|
struct = root.find(f"types/type[@category='struct'][@name='{type_name}']")
|
|
if struct is not None:
|
|
structs_to_generate.append(type_name)
|
|
for member in struct.findall("member/type"):
|
|
newly_found_types.append(member.text)
|
|
|
|
types_undetermined = newly_found_types
|
|
|
|
enums_to_generate.sort()
|
|
flags_to_generate.sort()
|
|
structs_to_generate.sort()
|
|
|
|
return (enums_to_generate, flags_to_generate, structs_to_generate)
|
|
|
|
|
|
def generate_to_string(vk_xml, header):
|
|
tree = ET.parse(vk_xml)
|
|
root = tree.getroot()
|
|
|
|
commands = parse_all_commands(root)
|
|
features = parse_features(root)
|
|
extensions = parse_extensions(root)
|
|
all_flags = parse_all_flags(root)
|
|
|
|
(enums_to_generate, flags_to_generate, structs_to_generate) = extract_used_types(root, commands, all_flags)
|
|
|
|
vk_to_string = ""
|
|
|
|
if header:
|
|
for enum_to_generate in enums_to_generate:
|
|
for enum in root.findall(f"enums[@name='{enum_to_generate}']"):
|
|
vk_to_string += generate_enum_to_string_hpp(enum)
|
|
for flag_to_generate in flags_to_generate:
|
|
enum_to_generate = all_flags[flag_to_generate]
|
|
for enum in root.findall(f"enums[@name='{enum_to_generate}']"):
|
|
vk_to_string += generate_bitflag_to_string_hpp(flag_to_generate)
|
|
for struct_to_generate in structs_to_generate:
|
|
struct = root.find(f"types/type[@category='struct'][@name='{struct_to_generate}']")
|
|
assert (struct)
|
|
vk_to_string += generate_struct_to_string_hpp(struct)
|
|
else:
|
|
for enum_to_generate in enums_to_generate:
|
|
for enum in root.findall(f"enums[@name='{enum_to_generate}']"):
|
|
vk_to_string += generate_enum_to_string_cpp(enum, features, extensions)
|
|
vk_to_string += "\n"
|
|
for flag_to_generate in flags_to_generate:
|
|
enum_to_generate = all_flags[flag_to_generate]
|
|
for enum in root.findall(f"enums[@name='{enum_to_generate}']"):
|
|
vk_to_string += generate_bitflag_to_string_cpp(flag_to_generate, enum, features, extensions)
|
|
vk_to_string += "\n"
|
|
|
|
for struct_to_generate in structs_to_generate:
|
|
struct = root.find(f"types/type[@category='struct'][@name='{struct_to_generate}']")
|
|
assert (struct is not None)
|
|
vk_to_string += generate_struct_to_string_cpp(struct,
|
|
flags_to_generate,
|
|
enums_to_generate,
|
|
structs_to_generate)
|
|
vk_to_string += "\n"
|
|
|
|
print(vk_to_string)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
parser = argparse.ArgumentParser(
|
|
prog="vk_to_string.py",
|
|
description="Generator for vk_to_string.cc/hh",
|
|
)
|
|
parser.add_argument("vk_xml", help="path to `vk.xml`")
|
|
parser.add_argument("--header", action='store_true', help="generate parts that belong to `vk_to_string.hh`")
|
|
args = parser.parse_args()
|
|
generate_to_string(**dict(args._get_kwargs()))
|