blob: 96eaa31fa7c6c2e91a9c073764bb85b9b620aba2 [file] [log] [blame]
#!/usr/bin/python3 -i
#
# Copyright (c) 2015-2024 The Khronos Group Inc.
# Copyright (c) 2015-2024 Valve Corporation
# Copyright (c) 2015-2024 LunarG, Inc.
# Copyright (c) 2015-2024 Google Inc.
# Copyright (c) 2023-2024 RasterGrid Kft.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import re
from generators.base_generator import BaseGenerator
from generators.generator_utils import PlatformGuardHelper
# Need pyparsing because the Vulkan-Headers use it in dependencyBNF
from pyparsing import ParseResults
# From the Vulkan-Headers
from parse_dependency import dependencyBNF
def parseExpr(expr): return dependencyBNF().parseString(expr, parseAll=True)
def dependCheck(pr: ParseResults, token, op, start_group, end_group) -> None:
"""
Run a set of callbacks on a boolean expression.
token: run on a non-operator, non-parenthetical token
op: run on an operator token
start_group: run on a '(' token
end_group: run on a ')' token
"""
for r in pr:
if isinstance(r, ParseResults):
start_group()
dependCheck(r, token, op, start_group, end_group)
end_group()
elif r in ',+':
op(r)
else:
token(r)
def exprValues(pr: ParseResults) -> list:
"""
Return a list of all "values" (i.e., non-operators) in the parsed expression.
"""
values = []
dependCheck(pr, lambda x: values.append(x), lambda x: None, lambda: None, lambda: None)
return values
def exprToCpp(pr: ParseResults, opt = lambda x: x) -> str:
r = []
printExt = lambda x: r.append(opt(x))
printOp = lambda x: r.append(' && ' if x == '+' else ' || ')
openParen = lambda: r.append('(')
closeParen = lambda: r.append(')')
dependCheck(pr, printExt, printOp, openParen, closeParen)
return ''.join(r)
class ExtensionHelperOutputGenerator(BaseGenerator):
def __init__(self):
BaseGenerator.__init__(self)
# [ Feature name | name in struct InstanceExtensions ]
self.fieldName = dict()
# [ Extension name : List[Extension | Version] ]
self.requiredExpression = dict()
def generate(self):
for extension in self.vk.extensions.values():
self.fieldName[extension.name] = extension.name.lower()
self.requiredExpression[extension.name] = list()
if extension.depends is not None:
# This is a work around for https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/5372
temp = re.sub(r',VK_VERSION_1_\d+', '', extension.depends)
# It can look like (VK_KHR_timeline_semaphore,VK_VERSION_1_2) or (VK_VERSION_1_2,VK_KHR_timeline_semaphore)
temp = re.sub(r'VK_VERSION_1_\d+,', '', temp)
for reqs in exprValues(parseExpr(temp)):
feature = self.vk.extensions[reqs] if reqs in self.vk.extensions else self.vk.versions[reqs]
self.requiredExpression[extension.name].append(feature)
for version in self.vk.versions.keys():
self.fieldName[version] = version.lower().replace('version', 'feature_version')
self.write(f'''// *** THIS FILE IS GENERATED - DO NOT EDIT ***
// See {os.path.basename(__file__)} for modifications
/***************************************************************************
*
* Copyright (c) 2015-2024 The Khronos Group Inc.
* Copyright (c) 2015-2024 Valve Corporation
* Copyright (c) 2015-2024 LunarG, Inc.
* Copyright (c) 2015-2024 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
****************************************************************************/\n''')
self.write('// NOLINTBEGIN') # Wrap for clang-tidy to ignore
if self.filename == 'vk_extension_helper.h':
self.generateHeader()
elif self.filename == 'vk_extension_helper.cpp':
self.generateSource()
else:
self.write(f'\nFile name {self.filename} has no code to generate\n')
self.write('// NOLINTEND') # Wrap for clang-tidy to ignore
def generateHeader(self):
guard_helper = PlatformGuardHelper()
out = []
out.append('''
#pragma once
#include <string>
#include <utility>
#include <vector>
#include <cassert>
#include <vulkan/vulkan.h>
#include "containers/custom_containers.h"
#include "generated/vk_api_version.h"
#include "generated/error_location_helper.h"
// Extensions (unlike functions, struct, etc) are passed in as strings.
// The goal is to turn the string to a enum and pass that around the layers.
// Only when we need to print an error do we try and turn it back into a string
vvl::Extension GetExtension(std::string extension);
enum ExtEnabled : unsigned char {
kNotEnabled,
kEnabledByCreateinfo, // Extension is passed at vkCreateDevice/vkCreateInstance time
kEnabledByApiLevel, // the API version implicitly enabled it
kEnabledByInteraction, // is implicity enabled by anthoer extension
};
// Map of promoted extension information per version (a separate map exists for instance and device extensions).
// The map is keyed by the version number (e.g. VK_API_VERSION_1_1) and each value is a pair consisting of the
// version string (e.g. "VK_VERSION_1_1") and the set of name of the promoted extensions.
typedef vvl::unordered_map<uint32_t, std::pair<const char*, vvl::unordered_set<vvl::Extension>>> PromotedExtensionInfoMap;
const PromotedExtensionInfoMap& GetInstancePromotionInfoMap();
const PromotedExtensionInfoMap& GetDevicePromotionInfoMap();
/*
This function is a helper to know if the extension is enabled.
Times to use it
- To determine the VUID
- The VU mentions the use of the extension
- Extension exposes property limits being validated
- Checking not enabled
- if (!IsExtEnabled(...)) { }
- Special extensions that being EXPOSED alters the VUs
- IsExtEnabled(device_extensions.vk_khr_portability_subset)
- Special extensions that alter behaviour of enabled
- IsExtEnabled(device_extensions.vk_khr_maintenance*)
Times to NOT use it
- If checking if a struct or enum is being used. There are a stateless checks
to make sure the new Structs/Enums are not being used without this enabled.
- If checking if the extension's feature enable status, because if the feature
is enabled, then we already validated that extension is enabled.
- Some variables (ex. viewMask) require the extension to be used if non-zero
*/
[[maybe_unused]] static bool IsExtEnabled(ExtEnabled extension) { return (extension != kNotEnabled); }
[[maybe_unused]] static bool IsExtEnabledByCreateinfo(ExtEnabled extension) { return (extension == kEnabledByCreateinfo); }
''')
out.append('\nstruct InstanceExtensions {\n')
for version in self.vk.versions.keys():
out.append(f' ExtEnabled {self.fieldName[version]}{{kNotEnabled}};\n')
out.extend([f' ExtEnabled {ext.name.lower()}{{kNotEnabled}};\n' for ext in self.vk.extensions.values() if ext.instance])
out.append('''
struct Requirement {
const ExtEnabled InstanceExtensions::*enabled;
const char *name;
};
typedef std::vector<Requirement> RequirementVec;
struct Info {
Info(ExtEnabled InstanceExtensions::*state_, const RequirementVec requirements_)
: state(state_), requirements(requirements_) {}
ExtEnabled InstanceExtensions::*state;
RequirementVec requirements;
};
typedef vvl::unordered_map<vvl::Extension, Info> InfoMap;
static const InfoMap &GetInfoMap() {
static const InfoMap info_map = {
''')
for extension in [x for x in self.vk.extensions.values() if x.instance]:
out.extend(guard_helper.add_guard(extension.protect))
reqs = ''
# This is only done to match whitespace from before code we refactored
if self.requiredExpression[extension.name]:
reqs += '{\n'
reqs += ',\n'.join([f'{{&InstanceExtensions::{self.fieldName[feature.name]}, {feature.nameString}}}' for feature in self.requiredExpression[extension.name]])
reqs += '}'
out.append(f'{{vvl::Extension::_{extension.name}, Info(&InstanceExtensions::{extension.name.lower()}, {{{reqs}}})}},\n')
out.extend(guard_helper.add_guard(None))
out.append('''
};
return info_map;
}
static const Info &GetInfo(vvl::Extension extension_name) {
static const Info empty_info{nullptr, RequirementVec()};
const auto &ext_map = InstanceExtensions::GetInfoMap();
const auto info = ext_map.find(extension_name);
return (info != ext_map.cend()) ? info->second : empty_info;
}
APIVersion InitFromInstanceCreateInfo(APIVersion requested_api_version, const VkInstanceCreateInfo *pCreateInfo);
};
''')
out.append('\nstruct DeviceExtensions : public InstanceExtensions {\n')
for version in self.vk.versions.keys():
out.append(f' ExtEnabled {self.fieldName[version]}{{kNotEnabled}};\n')
out.extend([f' ExtEnabled {ext.name.lower()}{{kNotEnabled}};\n' for ext in self.vk.extensions.values() if ext.device])
out.append('''
struct Requirement {
const ExtEnabled DeviceExtensions::*enabled;
const char *name;
};
typedef std::vector<Requirement> RequirementVec;
struct Info {
Info(ExtEnabled DeviceExtensions::*state_, const RequirementVec requirements_)
: state(state_), requirements(requirements_) {}
ExtEnabled DeviceExtensions::*state;
RequirementVec requirements;
};
typedef vvl::unordered_map<vvl::Extension, Info> InfoMap;
static const InfoMap &GetInfoMap() {
static const InfoMap info_map = {
''')
for extension in [x for x in self.vk.extensions.values() if x.device]:
out.extend(guard_helper.add_guard(extension.protect))
reqs = ''
# This is only done to match whitespace from before code we refactored
if self.requiredExpression[extension.name]:
reqs += '{\n'
reqs += ',\n'.join([f'{{&DeviceExtensions::{self.fieldName[feature.name]}, {feature.nameString}}}' for feature in self.requiredExpression[extension.name]])
reqs += '}'
out.append(f'{{vvl::Extension::_{extension.name}, Info(&DeviceExtensions::{extension.name.lower()}, {{{reqs}}})}},\n')
out.extend(guard_helper.add_guard(None))
out.append('''
};
return info_map;
}
static const Info &GetInfo(vvl::Extension extension_name) {
static const Info empty_info{nullptr, RequirementVec()};
const auto &ext_map = DeviceExtensions::GetInfoMap();
const auto info = ext_map.find(extension_name);
return (info != ext_map.cend()) ? info->second : empty_info;
}
DeviceExtensions() = default;
DeviceExtensions(const InstanceExtensions &instance_ext) : InstanceExtensions(instance_ext) {}
APIVersion InitFromDeviceCreateInfo(const InstanceExtensions *instance_extensions, APIVersion requested_api_version,
const VkDeviceCreateInfo *pCreateInfo = nullptr);
};
const InstanceExtensions::Info &GetInstanceVersionMap(const char* version);
const DeviceExtensions::Info &GetDeviceVersionMap(const char* version);
''')
out.append('''
constexpr bool IsInstanceExtension(vvl::Extension extension) {
switch (extension) {
''')
out.extend([f'case vvl::Extension::_{x.name}:\n' for x in self.vk.extensions.values() if x.instance])
out.append(''' return true;''')
out.append('''default: return false;
}
}\n''')
out.append('''
constexpr bool IsDeviceExtension(vvl::Extension extension) {
switch (extension) {
''')
out.extend([f'case vvl::Extension::_{x.name}:\n' for x in self.vk.extensions.values() if x.device])
out.append(''' return true;''')
out.append('''default: return false;
}
}\n''')
self.write(''.join(out))
def generateSource(self):
out = []
out.append('''
#include "vk_extension_helper.h"
vvl::Extension GetExtension(std::string extension) {
static const vvl::unordered_map<std::string, vvl::Extension> extension_map {
''')
for extension in self.vk.extensions.values():
out.append(f' {{"{extension.name}", vvl::Extension::_{extension.name}}},\n')
out.append(''' };
const auto it = extension_map.find(extension);
return (it == extension_map.end()) ? vvl::Extension::Empty : it->second;
}
const PromotedExtensionInfoMap &GetInstancePromotionInfoMap() {
static const PromotedExtensionInfoMap promoted_map = {
''')
for version in self.vk.versions.keys():
promoted_ext_list = [x for x in self.vk.extensions.values() if x.promotedTo == version and getattr(x, 'instance')]
if len(promoted_ext_list) > 0:
out.append(f'{{{version.replace("VERSION", "API_VERSION")},{{"{version}",{{')
out.extend([' %s,\n' % f'vvl::Extension::_{ext.name}' for ext in promoted_ext_list])
out.append('}}},\n')
out.append('''
};
return promoted_map;
}
const PromotedExtensionInfoMap &GetDevicePromotionInfoMap() {
static const PromotedExtensionInfoMap promoted_map = {
''')
for version in self.vk.versions.keys():
promoted_ext_list = [x for x in self.vk.extensions.values() if x.promotedTo == version and getattr(x, 'device')]
if len(promoted_ext_list) > 0:
out.append(f'{{{version.replace("VERSION", "API_VERSION")},{{"{version}",{{')
out.extend([' %s,\n' % f'vvl::Extension::_{ext.name}' for ext in promoted_ext_list])
out.append('}}},\n')
out.append('''
};
return promoted_map;
}
const InstanceExtensions::Info &GetInstanceVersionMap(const char* version) {
static const InstanceExtensions::Info empty_info{nullptr, InstanceExtensions::RequirementVec()};
static const vvl::unordered_map<std::string_view, InstanceExtensions::Info> version_map = {
''')
for version in self.vk.versions.keys():
out.append(f'{{"{version}", InstanceExtensions::Info(&InstanceExtensions::{self.fieldName[version]}, {{}})}},\n')
out.append('''};
const auto info = version_map.find(version);
return (info != version_map.cend()) ? info->second : empty_info;
}
const DeviceExtensions::Info &GetDeviceVersionMap(const char* version) {
static const DeviceExtensions::Info empty_info{nullptr, DeviceExtensions::RequirementVec()};
static const vvl::unordered_map<std::string_view, DeviceExtensions::Info> version_map = {
''')
for version in self.vk.versions.keys():
out.append(f'{{"{version}", DeviceExtensions::Info(&DeviceExtensions::{self.fieldName[version]}, {{}})}},\n')
out.append('''};
const auto info = version_map.find(version);
return (info != version_map.cend()) ? info->second : empty_info;
}
APIVersion InstanceExtensions::InitFromInstanceCreateInfo(APIVersion requested_api_version, const VkInstanceCreateInfo* pCreateInfo) {
// Initialize struct data, robust to invalid pCreateInfo
auto api_version = NormalizeApiVersion(requested_api_version);
if (!api_version.Valid()) return api_version;
const auto promotion_info_map = GetInstancePromotionInfoMap();
for (const auto& version_it : promotion_info_map) {
auto info = GetInstanceVersionMap(version_it.second.first);
if (api_version >= version_it.first) {
if (info.state) this->*(info.state) = kEnabledByCreateinfo;
for (const auto& extension : version_it.second.second) {
info = GetInfo(extension);
assert(info.state);
if (info.state) this->*(info.state) = kEnabledByApiLevel;
}
}
}
// CreateInfo takes precedence over promoted
if (pCreateInfo && pCreateInfo->ppEnabledExtensionNames) {
for (uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; i++) {
if (!pCreateInfo->ppEnabledExtensionNames[i]) continue;
vvl::Extension extension = GetExtension(pCreateInfo->ppEnabledExtensionNames[i]);
auto info = GetInfo(extension);
if (info.state) this->*(info.state) = kEnabledByCreateinfo;
}
}
return api_version;
}
APIVersion DeviceExtensions::InitFromDeviceCreateInfo(const InstanceExtensions* instance_extensions, APIVersion requested_api_version,
const VkDeviceCreateInfo* pCreateInfo) {
// Initialize: this to defaults, base class fields to input.
assert(instance_extensions);
*this = DeviceExtensions(*instance_extensions);
// Initialize struct data, robust to invalid pCreateInfo
auto api_version = NormalizeApiVersion(requested_api_version);
if (!api_version.Valid()) return api_version;
const auto promotion_info_map = GetDevicePromotionInfoMap();
for (const auto& version_it : promotion_info_map) {
auto info = GetDeviceVersionMap(version_it.second.first);
if (api_version >= version_it.first) {
if (info.state) this->*(info.state) = kEnabledByCreateinfo;
for (const auto& extension : version_it.second.second) {
info = GetInfo(extension);
assert(info.state);
if (info.state) this->*(info.state) = kEnabledByApiLevel;
}
}
}
// CreateInfo takes precedence over promoted
if (pCreateInfo && pCreateInfo->ppEnabledExtensionNames) {
for (uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; i++) {
if (!pCreateInfo->ppEnabledExtensionNames[i]) continue;
vvl::Extension extension = GetExtension(pCreateInfo->ppEnabledExtensionNames[i]);
auto info = GetInfo(extension);
if (info.state) this->*(info.state) = kEnabledByCreateinfo;
}
}
// Workaround for functions being introduced by multiple extensions, until the layer is fixed to handle this correctly
// See https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/5579 and
// https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/5600
{
constexpr std::array shader_object_interactions = {
vvl::Extension::_VK_EXT_extended_dynamic_state,
vvl::Extension::_VK_EXT_extended_dynamic_state2,
vvl::Extension::_VK_EXT_extended_dynamic_state3,
vvl::Extension::_VK_EXT_vertex_input_dynamic_state,
};
auto info = GetInfo(vvl::Extension::_VK_EXT_shader_object);
if (info.state) {
if (this->*(info.state) != kNotEnabled) {
for (auto interaction_ext : shader_object_interactions) {
info = GetInfo(interaction_ext);
assert(info.state);
if (this->*(info.state) != kEnabledByCreateinfo) {
this->*(info.state) = kEnabledByInteraction;
}
}
}
}
}
return api_version;
}
''')
self.write(''.join(out))