blob: e7040b0019d90de5753e8834df1560ba6d0d26ad [file]
/* Copyright (c) 2026 The Khronos Group Inc.
* Copyright (c) 2026 Valve Corporation
* Copyright (c) 2026 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.
*/
#include "gpuav/core/gpuav.h"
#include "generated/dispatch_functions.h"
namespace gpuav {
static std::vector<VkExtensionProperties> GetAvailableExtensions(VkPhysicalDevice physical_device) {
VkResult err;
uint32_t extension_count = 512;
std::vector<VkExtensionProperties> extensions(extension_count);
for (;;) {
err = DispatchEnumerateDeviceExtensionProperties(physical_device, nullptr, &extension_count, extensions.data());
if (err == VK_SUCCESS) {
extensions.resize(extension_count);
return extensions;
} else if (err == VK_INCOMPLETE) {
extension_count *= 2; // wasn't enough space, increase it
extensions.resize(extension_count);
} else {
return {};
}
}
}
static bool IsExtensionAvailable(const char* extension_name, const std::vector<VkExtensionProperties>& available_extensions) {
for (const VkExtensionProperties& ext : available_extensions) {
if (strncmp(extension_name, ext.extensionName, VK_MAX_EXTENSION_NAME_SIZE) == 0) {
return true;
}
}
return false;
}
// We end up printing lots of warning for GPU-AV that really have no use for any object.
// Instead of spamming with a VkInstance, just explicitly don't print anything
static const VulkanTypedHandle kNoObjects;
// In PreCallRecord we try to turn on as many features as possible on behalf of the app (and warn the user if we are doing it).
// Later after device creation, we can decide if the required Vulkan feature for each GPU-AV setting is found and report errors
void Instance::AddFeatures(VkPhysicalDevice physical_device, vku::safe_VkDeviceCreateInfo* modified_create_info,
const Location& loc) {
// Query things here to make sure we don't attempt to add a feature this is just not supported
VkPhysicalDeviceCooperativeMatrixFeaturesKHR supported_coop_mat_feature = vku::InitStructHelper();
VkPhysicalDeviceRobustness2FeaturesKHR supported_robustness2_feature = vku::InitStructHelper(&supported_coop_mat_feature);
VkPhysicalDeviceShaderFloat16Int8Features supported_shader_float16_int8_feature =
vku::InitStructHelper(&supported_robustness2_feature);
VkPhysicalDevice8BitStorageFeatures supported_8bit_feature = vku::InitStructHelper(&supported_shader_float16_int8_feature);
VkPhysicalDevice16BitStorageFeatures supported_16bit_feature = vku::InitStructHelper(&supported_8bit_feature);
VkPhysicalDeviceBufferDeviceAddressFeatures supported_bda_feature = vku::InitStructHelper(&supported_16bit_feature);
VkPhysicalDeviceScalarBlockLayoutFeatures supported_scalar_feature = vku::InitStructHelper(&supported_bda_feature);
VkPhysicalDeviceVulkanMemoryModelFeatures supported_memory_model_feature = vku::InitStructHelper(&supported_scalar_feature);
VkPhysicalDeviceTimelineSemaphoreFeatures supported_timeline_feature = vku::InitStructHelper(&supported_memory_model_feature);
VkPhysicalDeviceFeatures2 features_2 = vku::InitStructHelper(&supported_timeline_feature);
DispatchGetPhysicalDeviceFeatures2(physical_device, &features_2);
std::string adjustment_warnings;
// First core features
{
const VkPhysicalDeviceFeatures& supported_features = features_2.features;
VkPhysicalDeviceFeatures* modified_features = const_cast<VkPhysicalDeviceFeatures*>(modified_create_info->pEnabledFeatures);
if (!modified_features) {
if (auto* modified_features_2 = const_cast<VkPhysicalDeviceFeatures2*>(
vku::FindStructInPNextChain<VkPhysicalDeviceFeatures2>(modified_create_info->pNext))) {
modified_features = &modified_features_2->features;
} else {
// The user has no VkPhysicalDeviceFeatures, so we are adding it for them
modified_features = new VkPhysicalDeviceFeatures;
memset(modified_features, 0, sizeof(VkPhysicalDeviceFeatures));
modified_create_info->pEnabledFeatures = modified_features;
}
}
if (modified_features) {
if (supported_features.fragmentStoresAndAtomics && !modified_features->fragmentStoresAndAtomics) {
adjustment_warnings += "\tForcing fragmentStoresAndAtomics to VK_TRUE\n";
modified_features->fragmentStoresAndAtomics = VK_TRUE;
}
if (supported_features.vertexPipelineStoresAndAtomics && !modified_features->vertexPipelineStoresAndAtomics) {
adjustment_warnings += "\tForcing vertexPipelineStoresAndAtomics to VK_TRUE\n";
modified_features->vertexPipelineStoresAndAtomics = VK_TRUE;
}
if (supported_features.shaderInt64 && !modified_features->shaderInt64) {
adjustment_warnings += "\tForcing shaderInt64 to VK_TRUE\n";
modified_features->shaderInt64 = VK_TRUE;
}
if (gpuav_settings.force_on_robustness && supported_features.robustBufferAccess &&
!modified_features->robustBufferAccess) {
adjustment_warnings += "\tForcing robustBufferAccess to VK_TRUE\n";
modified_features->robustBufferAccess = VK_TRUE;
}
if (supported_features.shaderInt16 && !modified_features->shaderInt16) {
adjustment_warnings += "\tForcing shaderInt16 to VK_TRUE\n";
modified_features->shaderInt16 = VK_TRUE;
}
}
}
// Build extension list once
std::vector<VkExtensionProperties> available_extensions = GetAvailableExtensions(physical_device);
if (supported_timeline_feature.timelineSemaphore) {
auto add_timeline_semaphore = [modified_create_info, &adjustment_warnings]() {
if (auto* ts_features = const_cast<VkPhysicalDeviceTimelineSemaphoreFeatures*>(
vku::FindStructInPNextChain<VkPhysicalDeviceTimelineSemaphoreFeatures>(modified_create_info))) {
if (ts_features->timelineSemaphore == VK_FALSE) {
adjustment_warnings += "\tForcing VkPhysicalDeviceTimelineSemaphoreFeatures::timelineSemaphore to VK_TRUE\n";
ts_features->timelineSemaphore = VK_TRUE;
}
} else {
adjustment_warnings +=
"\tAdding a VkPhysicalDeviceTimelineSemaphoreFeatures to pNext with timelineSemaphore set to VK_TRUE\n";
VkPhysicalDeviceTimelineSemaphoreFeatures new_ts_features = vku::InitStructHelper();
new_ts_features.timelineSemaphore = VK_TRUE;
vku::AddToPnext(*modified_create_info, new_ts_features);
}
};
if (api_version > VK_API_VERSION_1_1) {
if (auto* features12 = const_cast<VkPhysicalDeviceVulkan12Features*>(
vku::FindStructInPNextChain<VkPhysicalDeviceVulkan12Features>(modified_create_info->pNext))) {
if (features12->timelineSemaphore == VK_FALSE) {
adjustment_warnings += "\tForcing VkPhysicalDeviceVulkan12Features::timelineSemaphore to VK_TRUE\n";
features12->timelineSemaphore = VK_TRUE;
}
} else {
add_timeline_semaphore();
}
} else if (IsExtensionAvailable(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME, available_extensions)) {
// Only adds if not found already
vku::AddExtension(*modified_create_info, VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME);
add_timeline_semaphore();
timeline_khr_ = true;
}
}
if (supported_memory_model_feature.vulkanMemoryModel) {
auto add_memory_model = [modified_create_info, &adjustment_warnings]() {
if (auto* mm_features = const_cast<VkPhysicalDeviceVulkanMemoryModelFeatures*>(
vku::FindStructInPNextChain<VkPhysicalDeviceVulkanMemoryModelFeatures>(modified_create_info))) {
if (mm_features->vulkanMemoryModel == VK_FALSE) {
adjustment_warnings += "\tForcing VkPhysicalDeviceVulkanMemoryModelFeatures::vulkanMemoryModel to VK_TRUE\n";
mm_features->vulkanMemoryModel = VK_TRUE;
}
if (mm_features->vulkanMemoryModelDeviceScope == VK_FALSE) {
adjustment_warnings +=
"\tForcing VkPhysicalDeviceVulkanMemoryModelFeatures::vulkanMemoryModelDeviceScope to VK_TRUE\n";
mm_features->vulkanMemoryModelDeviceScope = VK_TRUE;
}
} else {
adjustment_warnings +=
"\tAdding a VkPhysicalDeviceVulkanMemoryModelFeatures to pNext with vulkanMemoryModel and "
"vulkanMemoryModelDeviceScope set to VK_TRUE\n";
VkPhysicalDeviceVulkanMemoryModelFeatures new_mm_features = vku::InitStructHelper();
new_mm_features.vulkanMemoryModel = VK_TRUE;
new_mm_features.vulkanMemoryModelDeviceScope = VK_TRUE;
vku::AddToPnext(*modified_create_info, new_mm_features);
}
};
if (api_version > VK_API_VERSION_1_1) {
if (auto* features12 = const_cast<VkPhysicalDeviceVulkan12Features*>(
vku::FindStructInPNextChain<VkPhysicalDeviceVulkan12Features>(modified_create_info->pNext))) {
if (features12->vulkanMemoryModel == VK_FALSE) {
adjustment_warnings += "\tForcing VkPhysicalDeviceVulkan12Features::vulkanMemoryModel to VK_TRUE\n";
features12->vulkanMemoryModel = VK_TRUE;
}
if (features12->vulkanMemoryModelDeviceScope == VK_FALSE) {
adjustment_warnings += "\tForcing VkPhysicalDeviceVulkan12Features::vulkanMemoryModelDeviceScope to VK_TRUE\n";
features12->vulkanMemoryModelDeviceScope = VK_TRUE;
}
} else {
add_memory_model();
}
} else if (IsExtensionAvailable(VK_KHR_VULKAN_MEMORY_MODEL_EXTENSION_NAME, available_extensions)) {
// Only adds if not found already
vku::AddExtension(*modified_create_info, VK_KHR_VULKAN_MEMORY_MODEL_EXTENSION_NAME);
add_memory_model();
}
}
if (supported_bda_feature.bufferDeviceAddress) {
auto add_bda = [modified_create_info, &adjustment_warnings]() {
// Add buffer device address feature
if (auto* bda_features = const_cast<VkPhysicalDeviceBufferDeviceAddressFeatures*>(
vku::FindStructInPNextChain<VkPhysicalDeviceBufferDeviceAddressFeatures>(modified_create_info))) {
if (!bda_features->bufferDeviceAddress) {
adjustment_warnings +=
"\tForcing VkPhysicalDeviceBufferDeviceAddressFeatures::bufferDeviceAddress to VK_TRUE\n";
bda_features->bufferDeviceAddress = VK_TRUE;
}
} else {
adjustment_warnings +=
"\tAdding a VkPhysicalDeviceBufferDeviceAddressFeatures to pNext with bufferDeviceAddress set to VK_TRUE\n";
VkPhysicalDeviceBufferDeviceAddressFeatures new_bda_features = vku::InitStructHelper();
new_bda_features.bufferDeviceAddress = VK_TRUE;
vku::AddToPnext(*modified_create_info, new_bda_features);
}
};
if (api_version >= VK_API_VERSION_1_2) {
if (auto* features12 = const_cast<VkPhysicalDeviceVulkan12Features*>(
vku::FindStructInPNextChain<VkPhysicalDeviceVulkan12Features>(modified_create_info->pNext))) {
if (!features12->bufferDeviceAddress) {
adjustment_warnings += "\tForcing VkPhysicalDeviceVulkan12Features::bufferDeviceAddress to VK_TRUE\n";
features12->bufferDeviceAddress = VK_TRUE;
}
} else {
add_bda();
}
} else if (IsExtensionAvailable(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, available_extensions)) {
// Only adds if not found already
vku::AddExtension(*modified_create_info, VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
add_bda();
}
}
if (supported_scalar_feature.scalarBlockLayout) {
auto add_scalar = [modified_create_info, &adjustment_warnings]() {
if (auto* bda_features = const_cast<VkPhysicalDeviceScalarBlockLayoutFeatures*>(
vku::FindStructInPNextChain<VkPhysicalDeviceScalarBlockLayoutFeatures>(modified_create_info))) {
if (!bda_features->scalarBlockLayout) {
adjustment_warnings += "\tForcing VkPhysicalDeviceScalarBlockLayoutFeatures::scalarBlockLayout to VK_TRUE\n";
bda_features->scalarBlockLayout = VK_TRUE;
}
} else {
adjustment_warnings +=
"\tAdding a VkPhysicalDeviceScalarBlockLayoutFeatures to pNext with scalarBlockLayout set to VK_TRUE\n";
VkPhysicalDeviceScalarBlockLayoutFeatures new_bda_features = vku::InitStructHelper();
new_bda_features.scalarBlockLayout = VK_TRUE;
vku::AddToPnext(*modified_create_info, new_bda_features);
}
};
if (api_version >= VK_API_VERSION_1_2) {
if (auto* features12 = const_cast<VkPhysicalDeviceVulkan12Features*>(
vku::FindStructInPNextChain<VkPhysicalDeviceVulkan12Features>(modified_create_info->pNext))) {
if (!features12->scalarBlockLayout) {
adjustment_warnings += "\tForcing VkPhysicalDeviceVulkan12Features::scalarBlockLayout to VK_TRUE\n";
features12->scalarBlockLayout = VK_TRUE;
}
} else {
add_scalar();
}
} else if (IsExtensionAvailable(VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME, available_extensions)) {
// Only adds if not found already
vku::AddExtension(*modified_create_info, VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME);
add_scalar();
}
}
if (supported_8bit_feature.storageBuffer8BitAccess) {
auto add_8bit_access = [modified_create_info, &adjustment_warnings]() {
// Add storageBuffer8BitAccess feature
if (auto* eight_bit_access_feature = const_cast<VkPhysicalDevice8BitStorageFeatures*>(
vku::FindStructInPNextChain<VkPhysicalDevice8BitStorageFeatures>(modified_create_info))) {
if (!eight_bit_access_feature->storageBuffer8BitAccess) {
adjustment_warnings += "\tForcing VkPhysicalDevice8BitStorageFeatures::storageBuffer8BitAccess to VK_TRUE\n";
eight_bit_access_feature->storageBuffer8BitAccess = VK_TRUE;
}
} else {
adjustment_warnings +=
"\tAdding a VkPhysicalDevice8BitStorageFeatures to pNext with storageBuffer8BitAccess "
"set to VK_TRUE\n";
VkPhysicalDevice8BitStorageFeatures new_8bit_features = vku::InitStructHelper();
new_8bit_features.storageBuffer8BitAccess = VK_TRUE;
vku::AddToPnext(*modified_create_info, new_8bit_features);
}
};
if (api_version >= VK_API_VERSION_1_2) {
if (auto* features12 = const_cast<VkPhysicalDeviceVulkan12Features*>(
vku::FindStructInPNextChain<VkPhysicalDeviceVulkan12Features>(modified_create_info->pNext))) {
if (!features12->storageBuffer8BitAccess) {
adjustment_warnings += "\tForcing VkPhysicalDeviceVulkan12Features::storageBuffer8BitAccess to VK_TRUE\n";
features12->storageBuffer8BitAccess = VK_TRUE;
}
} else {
add_8bit_access();
}
} else if (IsExtensionAvailable(VK_KHR_8BIT_STORAGE_EXTENSION_NAME, available_extensions)) {
// Only adds if not found already
vku::AddExtension(*modified_create_info, VK_KHR_8BIT_STORAGE_EXTENSION_NAME);
add_8bit_access();
}
}
if (supported_shader_float16_int8_feature.shaderInt8) {
auto add_int8 = [modified_create_info, &adjustment_warnings]() {
// Add shaderInt8 feature
if (auto* sixteen_bit_access_feature = const_cast<VkPhysicalDeviceShaderFloat16Int8Features*>(
vku::FindStructInPNextChain<VkPhysicalDeviceShaderFloat16Int8Features>(modified_create_info))) {
if (!sixteen_bit_access_feature->shaderInt8) {
adjustment_warnings += "\tForcing VkPhysicalDeviceShaderFloat16Int8Features::shaderInt8 to VK_TRUE\n";
sixteen_bit_access_feature->shaderInt8 = VK_TRUE;
}
} else {
adjustment_warnings +=
"\tAdding a VkPhysicalDeviceShaderFloat16Int8Features to pNext with shaderInt8 "
"set to VK_TRUE\n";
VkPhysicalDeviceShaderFloat16Int8Features new_16bit_features = vku::InitStructHelper();
new_16bit_features.shaderInt8 = VK_TRUE;
vku::AddToPnext(*modified_create_info, new_16bit_features);
}
};
if (api_version >= VK_API_VERSION_1_2) {
if (auto* features12 = const_cast<VkPhysicalDeviceVulkan12Features*>(
vku::FindStructInPNextChain<VkPhysicalDeviceVulkan12Features>(modified_create_info->pNext))) {
if (!features12->shaderInt8) {
adjustment_warnings += "\tForcing VkPhysicalDeviceVulkan12Features::shaderInt8 to VK_TRUE\n";
features12->shaderInt8 = VK_TRUE;
}
} else {
add_int8();
}
} else if (IsExtensionAvailable(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, available_extensions)) {
// Only adds if not found already
vku::AddExtension(*modified_create_info, VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME);
add_int8();
}
}
if (supported_16bit_feature.storageBuffer16BitAccess) {
auto add_16bit_access = [modified_create_info, &adjustment_warnings]() {
// Add storageBuffer16BitAccess feature
if (auto* sixteen_bit_access_feature = const_cast<VkPhysicalDevice16BitStorageFeatures*>(
vku::FindStructInPNextChain<VkPhysicalDevice16BitStorageFeatures>(modified_create_info))) {
if (!sixteen_bit_access_feature->storageBuffer16BitAccess) {
adjustment_warnings += "\tForcing VkPhysicalDevice16BitStorageFeatures::storageBuffer16BitAccess to VK_TRUE\n";
sixteen_bit_access_feature->storageBuffer16BitAccess = VK_TRUE;
}
} else {
adjustment_warnings +=
"\tAdding a VkPhysicalDevice16BitStorageFeatures to pNext with storageBuffer16BitAccess "
"set to VK_TRUE\n";
VkPhysicalDevice16BitStorageFeatures new_16bit_features = vku::InitStructHelper();
new_16bit_features.storageBuffer16BitAccess = VK_TRUE;
vku::AddToPnext(*modified_create_info, new_16bit_features);
}
};
if (api_version >= VK_API_VERSION_1_1) {
if (auto* features11 = const_cast<VkPhysicalDeviceVulkan11Features*>(
vku::FindStructInPNextChain<VkPhysicalDeviceVulkan11Features>(modified_create_info->pNext))) {
if (!features11->storageBuffer16BitAccess) {
adjustment_warnings += "\tForcing VkPhysicalDeviceVulkan11Features::storageBuffer16BitAccess to VK_TRUE\n";
features11->storageBuffer16BitAccess = VK_TRUE;
}
} else {
add_16bit_access();
}
} else if (IsExtensionAvailable(VK_KHR_16BIT_STORAGE_EXTENSION_NAME, available_extensions)) {
// Only adds if not found already
vku::AddExtension(*modified_create_info, VK_KHR_16BIT_STORAGE_EXTENSION_NAME);
add_16bit_access();
}
}
if (gpuav_settings.debug_printf_enabled || gpuav_settings.debug_descriptor_enabled) {
if (!IsExtensionAvailable(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME, available_extensions)) {
adjustment_warnings +=
"\tVK_KHR_shader_non_semantic_info is not available on selected device, DebugPrintf/DebugDescriptor may produce "
"SPIR-V "
"that could fail to compile the shader\n";
} else {
vku::AddExtension(*modified_create_info, VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME);
}
}
if (gpuav_settings.force_on_robustness &&
(supported_robustness2_feature.robustBufferAccess2 || supported_robustness2_feature.robustImageAccess2)) {
const bool has_ext = IsExtensionAvailable(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME, available_extensions);
const bool has_khr = IsExtensionAvailable(VK_KHR_ROBUSTNESS_2_EXTENSION_NAME, available_extensions);
if (has_ext || has_khr) {
// Only adds if not found already
if (has_khr) {
vku::AddExtension(*modified_create_info, VK_KHR_ROBUSTNESS_2_EXTENSION_NAME);
} else {
vku::AddExtension(*modified_create_info, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
}
if (auto* robust_buffer_2_feature = const_cast<VkPhysicalDeviceRobustness2FeaturesKHR*>(
vku::FindStructInPNextChain<VkPhysicalDeviceRobustness2FeaturesKHR>(modified_create_info))) {
if (!robust_buffer_2_feature->robustBufferAccess2 && supported_robustness2_feature.robustBufferAccess2) {
adjustment_warnings += "\tForcing VkPhysicalDeviceRobustness2FeaturesKHR::robustBufferAccess2 to VK_TRUE\n";
robust_buffer_2_feature->robustBufferAccess2 = VK_TRUE;
}
if (!robust_buffer_2_feature->robustImageAccess2 && supported_robustness2_feature.robustImageAccess2) {
adjustment_warnings += "\tForcing VkPhysicalDeviceRobustness2FeaturesKHR::robustImageAccess2 to VK_TRUE\n";
robust_buffer_2_feature->robustImageAccess2 = VK_TRUE;
}
} else {
VkPhysicalDeviceRobustness2FeaturesKHR new_robust_buffer_2_feature = vku::InitStructHelper();
if (supported_robustness2_feature.robustBufferAccess2) {
adjustment_warnings +=
"\tAdding a VkPhysicalDeviceRobustness2FeaturesKHR to pNext with robustBufferAccess2 set to VK_TRUE\n";
new_robust_buffer_2_feature.robustBufferAccess2 = VK_TRUE;
}
if (supported_robustness2_feature.robustImageAccess2) {
adjustment_warnings +=
"\tAdding a VkPhysicalDeviceRobustness2FeaturesKHR to pNext with robustImageAccess2 set to VK_TRUE\n";
new_robust_buffer_2_feature.robustImageAccess2 = VK_TRUE;
}
vku::AddToPnext(*modified_create_info, new_robust_buffer_2_feature);
}
}
}
if (gpuav_settings.force_on_robustness && supported_coop_mat_feature.cooperativeMatrixRobustBufferAccess) {
// Because they need to have VkPhysicalDeviceCooperativeMatrixFeaturesKHR::cooperativeMatrix to even use this extensions, we
// don't add it, we only need to update the other feature bit for them
if (auto* coop_mat_feature = const_cast<VkPhysicalDeviceCooperativeMatrixFeaturesKHR*>(
vku::FindStructInPNextChain<VkPhysicalDeviceCooperativeMatrixFeaturesKHR>(modified_create_info))) {
if (!coop_mat_feature->cooperativeMatrixRobustBufferAccess) {
adjustment_warnings +=
"\tForcing VkPhysicalDeviceCooperativeMatrixFeaturesKHR::cooperativeMatrixRobustBufferAccess to VK_TRUE\n";
coop_mat_feature->cooperativeMatrixRobustBufferAccess = VK_TRUE;
}
}
}
if (!adjustment_warnings.empty()) {
AdjustmentWarning(kNoObjects, loc, adjustment_warnings.c_str());
}
}
} // namespace gpuav