From 8e1829074d2dd53dea7974fd70e9d2eb27aef367 Mon Sep 17 00:00:00 2001 From: Daniel Rakos Date: Wed, 4 Oct 2023 16:05:25 +0200 Subject: [PATCH] build: API parameterization --- CMakeLists.txt | 6 +++- scripts/CMakeLists.txt | 2 ++ scripts/generate_source.py | 31 +++++++++++++---- scripts/generators/base_generator.py | 52 +++++++++++++++++++++++----- scripts/known_good.json | 1 + 5 files changed, 77 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0034fcd..5680158 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,10 @@ set(CMAKE_CXX_VISIBILITY_PRESET "hidden") set(CMAKE_C_VISIBILITY_PRESET "hidden") set(CMAKE_VISIBILITY_INLINES_HIDDEN "YES") +# This variable enables downstream users to customize the target API +# variant (e.g. Vulkan SC) +set(API_TYPE "vulkan") + add_subdirectory(scripts) find_package(VulkanHeaders CONFIG QUIET) @@ -35,7 +39,7 @@ if (VUL_IS_TOP_LEVEL) include(GNUInstallDirs) - install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/vulkan" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/${API_TYPE}/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/vulkan") set_target_properties(VulkanLayerSettings PROPERTIES EXPORT_NAME "LayerSettings") set_target_properties(VulkanUtilityHeaders PROPERTIES EXPORT_NAME "UtilityHeaders") diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt index 3b8c9d4..2f782a7 100644 --- a/scripts/CMakeLists.txt +++ b/scripts/CMakeLists.txt @@ -40,6 +40,8 @@ if (UPDATE_DEPS) endif() list(APPEND update_dep_command "--config") list(APPEND update_dep_command "${_build_type}") + list(APPEND update_dep_command "--api") + list(APPEND update_dep_command "${API_TYPE}") set(UPDATE_DEPS_DIR_SUFFIX "${_build_type}") if (ANDROID) diff --git a/scripts/generate_source.py b/scripts/generate_source.py index f3825a9..21b8d50 100755 --- a/scripts/generate_source.py +++ b/scripts/generate_source.py @@ -27,23 +27,32 @@ def RunGenerators(api: str, registry: str, targetFilter: str) -> None: from generators.format_utils_generator import FormatUtilsOutputGenerator from generators.struct_helper_generator import StructHelperOutputGenerator + # These set fields that are needed by both OutputGenerator and BaseGenerator, + # but are uniform and don't need to be set at a per-generated file level + from generators.base_generator import (SetTargetApiName, SetMergedApiNames) + SetTargetApiName(api) + # Build up a list of all generators and custom options generators = { 'vk_dispatch_table.h' : { 'generator' : DispatchTableOutputGenerator, - 'directory' : 'include/vulkan/utility', + 'genCombined': True, + 'directory' : f'include/{api}/utility', }, 'vk_enum_string_helper.h' : { 'generator' : EnumStringHelperOutputGenerator, - 'directory' : 'include/vulkan', + 'genCombined': True, + 'directory' : f'include/{api}', }, 'vk_format_utils.h' : { 'generator' : FormatUtilsOutputGenerator, - 'directory' : 'include/vulkan/utility', + 'genCombined': True, + 'directory' : f'include/{api}/utility', }, 'vk_struct_helper.hpp' : { 'generator' : StructHelperOutputGenerator, - 'directory' : 'include/vulkan/utility', + 'genCombined': True, + 'directory' : f'include/{api}/utility', }, } @@ -61,11 +70,21 @@ def RunGenerators(api: str, registry: str, targetFilter: str) -> None: generator = generators[target]['generator'] gen = generator() + # This code and the 'genCombined' generator metadata is used by downstream + # users to generate code with all Vulkan APIs merged into the target API variant + # (e.g. Vulkan SC) when needed. The constructed apiList is also used to filter + # out non-applicable extensions later below. + apiList = [api] + if api != 'vulkan' and generators[target]['genCombined']: + SetMergedApiNames('vulkan') + apiList.append('vulkan') + else: + SetMergedApiNames(None) + outDirectory = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', generators[target]['directory'])) options = BaseGeneratorOptions( customFileName = target, - customDirectory = outDirectory, - customApiName = 'vulkan') + customDirectory = outDirectory) # Create the registry object with the specified generator and generator # options. The options are set before XML loading as they may affect it. diff --git a/scripts/generators/base_generator.py b/scripts/generators/base_generator.py index 5134ed2..350adf3 100644 --- a/scripts/generators/base_generator.py +++ b/scripts/generators/base_generator.py @@ -66,6 +66,30 @@ def SetTargetApiName(apiname: str) -> None: global globalApiName globalApiName = apiname +def SetMergedApiNames(names: str) -> None: + global mergedApiNames + mergedApiNames = names + + +# This class is a container for any source code, data, or other behavior that is necessary to +# customize the generator script for a specific target API variant (e.g. Vulkan SC). As such, +# all of these API-specific interfaces and their use in the generator script are part of the +# contract between this repository and its downstream users. Changing or removing any of these +# interfaces or their use in the generator script will have downstream effects and thus +# should be avoided unless absolutely necessary. +class APISpecific: + # Version object factory method + @staticmethod + def createApiVersion(targetApiName: str, name: str, number: str) -> Version: + match targetApiName: + + # Vulkan specific API version creation + case 'vulkan': + nameApi = name.replace('VK_', 'VK_API_') + nameString = f'"{name}"' + return Version(name, nameString, nameApi, number) + + # This Generator Option is used across all generators. # After years of use, it has shown that most the options are unified across each generator (file) # as it is easier to modifiy things per-file that need the difference @@ -79,7 +103,7 @@ def __init__(self, filename = customFileName if customFileName else globalFileName, directory = customDirectory if customDirectory else globalDirectory, apiname = customApiName if customApiName else globalApiName, - mergeApiNames = None, + mergeApiNames = mergedApiNames, defaultExtensions = customApiName if customApiName else globalApiName, emitExtensions = '.*', emitSpirv = '.*', @@ -97,6 +121,7 @@ class BaseGenerator(OutputGenerator): def __init__(self): OutputGenerator.__init__(self, None, None, None) self.vk = VulkanObject() + self.targetApiName = globalApiName # reg.py has a `self.featureName` but this is nicer because # it will be either the Version or Extension object @@ -162,6 +187,15 @@ def applyExtensionDependency(self): # one or more extension and/or core version names for required in dict: for commandName in dict[required]: + # Skip commands removed in the target API + # This check is needed because parts of the base generator code bypass the + # dependency resolution logic in the registry tooling and thus the generator + # may attempt to generate code for commands which are not supported in the + # target API variant, thus this check needs to happen even if any specific + # target API variant may not specifically need it + if not commandName in self.vk.commands: + continue + command = self.vk.commands[commandName] # Make sure list is unique command.extensions.extend([extension] if extension not in command.extensions else []) @@ -307,9 +341,7 @@ def beginFeature(self, interface, emit): else: # version number = interface.get('number') if number != '1.0': - nameApi = name.replace('VK_', 'VK_API_') - nameString = f'"{name}"' - self.currentVersion = Version(name, nameString, nameApi, number) + self.currentVersion = APISpecific.createApiVersion(self.targetApiName, name, number) self.vk.versions[name] = self.currentVersion def endFeature(self): @@ -638,8 +670,10 @@ def genSyncStage(self, sync): equivalent = SyncEquivalent(stages, accesses, False) flagName = syncElem.get('name') - flag = [x for x in self.vk.bitmasks['VkPipelineStageFlagBits2'].flags if x.name == flagName][0] - self.vk.syncStage.append(SyncStage(flag, support, equivalent)) + flag = [x for x in self.vk.bitmasks['VkPipelineStageFlagBits2'].flags if x.name == flagName] + # This check is needed because not all API variants have VK_KHR_synchronization2 + if flag: + self.vk.syncStage.append(SyncStage(flag[0], support, equivalent)) def genSyncAccess(self, sync): OutputGenerator.genSyncAccess(self, sync) @@ -663,8 +697,10 @@ def genSyncAccess(self, sync): equivalent = SyncEquivalent(stages, accesses, False) flagName = syncElem.get('name') - flag = [x for x in self.vk.bitmasks['VkAccessFlagBits2'].flags if x.name == flagName][0] - self.vk.syncAccess.append(SyncAccess(flag, support, equivalent)) + flag = [x for x in self.vk.bitmasks['VkAccessFlagBits2'].flags if x.name == flagName] + # This check is needed because not all API variants have VK_KHR_synchronization2 + if flag: + self.vk.syncAccess.append(SyncAccess(flag[0], support, equivalent)) def genSyncPipeline(self, sync): OutputGenerator.genSyncPipeline(self, sync) diff --git a/scripts/known_good.json b/scripts/known_good.json index 2f10c63..851bb0d 100644 --- a/scripts/known_good.json +++ b/scripts/known_good.json @@ -2,6 +2,7 @@ "repos": [ { "name": "Vulkan-Headers", + "api": "vulkan", "url": "https://github.com/KhronosGroup/Vulkan-Headers.git", "sub_dir": "Vulkan-Headers", "build_dir": "Vulkan-Headers/build",