blob: 1d6d779f7ce03d45aab0c54b99637f9d6dd0555e [file] [log] [blame]
#!/usr/bin/python3 -i
#
# Copyright (c) 2023-2025 The Khronos Group 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.
import os
import re
from base_generator import BaseGenerator
# For detecting VkPhysicalDevice*Features* structs
featuresStructPattern = re.compile(r'VkPhysicalDevice.*Features[A-Z0-9]*')
#
# DeviceFeaturesOutputGenerator - Generate helpers to discover enabled features.
class DeviceFeaturesOutputGenerator(BaseGenerator):
def __init__(self):
BaseGenerator.__init__(self)
# Features of the VkPhysicalDeviceBufferDeviceAddressFeaturesEXT have
# the same name as Vulkan 1.2 and
# VkPhysicalDeviceBufferDeviceAddressFeaturesKHR features, but are
# semantically different. They are given a suffix to be distinguished.
self.identical_but_different_features = {
'VkPhysicalDeviceBufferDeviceAddressFeaturesEXT': 'EXT',
'VkPhysicalDeviceDeviceGeneratedCommandsFeaturesNV': 'NV',
}
def generate(self):
self.write(f'''// *** THIS FILE IS GENERATED - DO NOT EDIT ***
// See {os.path.basename(__file__)} for modifications
/***************************************************************************
*
* Copyright (c) 2023-2025 Google Inc.
* Copyright (c) 2023-2025 LunarG, 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 == 'device_features.h':
self.generateHeader()
elif self.filename == 'device_features.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 addToDict(self, dictionary, name, value):
if name not in dictionary:
dictionary[name] = set()
dictionary[name].add(value)
def generateHeader(self):
# Map feature names to structs that have them
featureMap = dict()
for struct, info in self.vk.structs.items():
if featuresStructPattern.match(struct) is None:
continue
suffix = ''
if struct in self.identical_but_different_features:
suffix = self.identical_but_different_features[struct]
for feature in info.members:
if feature.type == 'VkBool32':
self.addToDict(featureMap, feature.name + suffix, struct)
# Generate a comment for every feature regarding where it may be coming from, then sort the
# features by that comment. That ensures features of the same struct end up together.
featuresAndOrigins = sorted([(', '.join(sorted(structs)), feature)
for feature, structs in featureMap.items()])
out = []
out.append('''
#pragma once
#include <vulkan/vulkan.h>
class APIVersion;
// Union of all features defined in VkPhysicalDevice*Features* structs
struct DeviceFeatures {
''')
for origins, feature in featuresAndOrigins:
out.append(f'// {origins}\n')
out.append(f'bool {feature};\n')
out.append('''};
void GetEnabledDeviceFeatures(const VkDeviceCreateInfo *pCreateInfo, DeviceFeatures *features, const APIVersion &api_version);
''')
self.write("".join(out))
def generateSource(self):
out = []
# Handle pCreateInfo->pEnabledFeatures first, it's the only one not necessarily found in the
# pNext chain.
out.append('''
#include "generated/device_features.h"
#include "generated/vk_api_version.h"
#include "generated/vk_extension_helper.h"
#include <vulkan/utility/vk_struct_helper.hpp>
void GetEnabledDeviceFeatures(const VkDeviceCreateInfo *pCreateInfo, DeviceFeatures *features, const APIVersion &api_version) {
// Initialize all to false
*features = {};
// handle VkPhysicalDeviceFeatures specially as it is not part of the pNext chain,
// and when it is (through VkPhysicalDeviceFeatures2), it requires an extra indirection.
const VkPhysicalDeviceFeatures *core_features = pCreateInfo->pEnabledFeatures;
if (core_features == nullptr) {
const VkPhysicalDeviceFeatures2 *features2 = vku::FindStructInPNextChain<VkPhysicalDeviceFeatures2>(pCreateInfo->pNext);
if (features2 != nullptr) {
core_features = &features2->features;
}
}
if (core_features != nullptr) {
''')
for member in self.vk.structs['VkPhysicalDeviceFeatures'].members:
if member.type == 'VkBool32':
out.append(f'features->{member.name} = core_features->{member.name} == VK_TRUE;\n')
out.append('}\n')
# Handle every features struct in the pnext chain.
out.append('''
for(const VkBaseInStructure *pNext = reinterpret_cast<const VkBaseInStructure *>(pCreateInfo->pNext);
pNext != nullptr; pNext = pNext->pNext) {
switch (pNext->sType) {
''')
for struct, info in self.vk.structs.items():
if featuresStructPattern.match(struct) is None:
continue
if info.sType is None:
# Only features struct without an sType is VkPhysicalDeviceFeatures
assert(struct == 'VkPhysicalDeviceFeatures')
continue
if struct == 'VkPhysicalDeviceFeatures2':
# VkPhysicalDeviceFeatures2 is already handled
continue
suffix = ''
if struct in self.identical_but_different_features:
suffix = self.identical_but_different_features[struct]
out.extend([f'#ifdef {info.protect}\n'] if info.protect else [])
out.append(f'case {info.sType}:')
out.append('{\n')
out.append(f'const {struct} *enabled = reinterpret_cast<const {struct} *>(pNext);\n')
for member in info.members:
if member.type == 'VkBool32':
feature = member.name
out.append(f'features->{feature}{suffix} |= enabled->{feature} == VK_TRUE;\n')
out.append('break;\n}\n')
out.extend([f'#endif //{info.protect}\n'] if info.protect else [])
out.append('''
default:
break;
}
}
''')
# Handle Extension Feature Aliases:
extension_feature_alises = {
'VK_KHR_shader_draw_parameters': ['shaderDrawParameters'],
'VK_KHR_draw_indirect_count': ['drawIndirectCount'],
'VK_KHR_sampler_mirror_clamp_to_edge': ['samplerMirrorClampToEdge'],
'VK_EXT_descriptor_indexing': ['descriptorIndexing'],
'VK_EXT_sampler_filter_minmax': ['samplerFilterMinmax'],
'VK_EXT_shader_viewport_index_layer': ['shaderOutputViewportIndex', 'shaderOutputLayer'],
}
out.append('''
// Some older extensions were made without features, but equivalent features were
// added to the core spec when they were promoted. When those extensions are
// enabled, treat validation rules as if the corresponding feature is enabled.
for (uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; i++) {
vvl::Extension extension = GetExtension(pCreateInfo->ppEnabledExtensionNames[i]);
''')
for ext, features in sorted(extension_feature_alises.items()):
out.append(f'if (extension == vvl::Extension::_{ext}) {{\n')
for feature in features:
out.append(f' features->{feature} = true;\n')
out.append('}\n')
out.append('}\n')
# Finally, handle oddities:
out.append('''
// texelBufferAlignment was not promoted to VkPhysicalDeviceVulkan13Features
// but the feature is automatically enabled.
// Setting the feature explicitly to 'false' doesn't change that
if (api_version >= VK_API_VERSION_1_3) {
features->texelBufferAlignment = true;
}
}
''')
self.write("".join(out))