// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #include #include #include #include #include #include "common/container/unordered_map.h" #include "common/container/unordered_set.h" #include #include #include #include "common/assert.h" #include "common/fs/fs.h" #include "common/fs/path_util.h" #include "common/literals.h" #include #include "common/settings.h" #include "common/settings_enums.h" #include "video_core/vulkan_common/nsight_aftermath_tracker.h" #include "video_core/vulkan_common/vma.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/gpu_logging/gpu_logging.h" #ifdef EGLANTINE_STREAMLINE #include "video_core/vulkan_common/streamline.h" #endif #if defined(__ANDROID__) && defined(ARCHITECTURE_arm64) #include #include #endif namespace Vulkan { using namespace Common::Literals; namespace { namespace Alternatives { constexpr std::array STENCIL8_UINT{ VK_FORMAT_D16_UNORM_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_UNDEFINED, }; constexpr std::array DEPTH24_UNORM_STENCIL8_UINT{ VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D16_UNORM_S8_UINT, VK_FORMAT_UNDEFINED, }; constexpr std::array DEPTH24_UNORM_DONTCARE8{ VK_FORMAT_D32_SFLOAT, VK_FORMAT_D16_UNORM, VK_FORMAT_UNDEFINED, }; constexpr std::array DEPTH16_UNORM_STENCIL8_UINT{ VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_UNDEFINED, }; constexpr std::array B5G6R5_UNORM_PACK16{ VK_FORMAT_R5G6B5_UNORM_PACK16, VK_FORMAT_UNDEFINED, }; constexpr std::array R4G4_UNORM_PACK8{ VK_FORMAT_R8_UNORM, VK_FORMAT_UNDEFINED, }; constexpr std::array R16G16B16_SFLOAT{ VK_FORMAT_R16G16B16A16_SFLOAT, VK_FORMAT_UNDEFINED, }; constexpr std::array R16G16B16_SSCALED{ VK_FORMAT_R16G16B16A16_SSCALED, VK_FORMAT_UNDEFINED, }; constexpr std::array R8G8B8_SSCALED{ VK_FORMAT_R8G8B8A8_SSCALED, VK_FORMAT_UNDEFINED, }; constexpr std::array VK_FORMAT_R32G32B32_SFLOAT{ VK_FORMAT_R32G32B32A32_SFLOAT, VK_FORMAT_UNDEFINED, }; constexpr std::array VK_FORMAT_A4B4G4R4_UNORM_PACK16{ VK_FORMAT_R4G4B4A4_UNORM_PACK16, VK_FORMAT_UNDEFINED, }; constexpr std::array B10G11R11_UFLOAT_PACK32{ VK_FORMAT_R16G16B16A16_SFLOAT, VK_FORMAT_A8B8G8R8_SRGB_PACK32, VK_FORMAT_UNDEFINED, }; } // namespace Alternatives template void SetNext(void**& next, T& data) { *next = &data; next = &data.pNext; } constexpr const VkFormat* GetFormatAlternatives(VkFormat format) { switch (format) { case VK_FORMAT_S8_UINT: return Alternatives::STENCIL8_UINT.data(); case VK_FORMAT_D24_UNORM_S8_UINT: return Alternatives::DEPTH24_UNORM_STENCIL8_UINT.data(); case VK_FORMAT_X8_D24_UNORM_PACK32: return Alternatives::DEPTH24_UNORM_DONTCARE8.data(); case VK_FORMAT_D16_UNORM_S8_UINT: return Alternatives::DEPTH16_UNORM_STENCIL8_UINT.data(); case VK_FORMAT_B5G6R5_UNORM_PACK16: return Alternatives::B5G6R5_UNORM_PACK16.data(); case VK_FORMAT_R4G4_UNORM_PACK8: return Alternatives::R4G4_UNORM_PACK8.data(); case VK_FORMAT_R16G16B16_SFLOAT: return Alternatives::R16G16B16_SFLOAT.data(); case VK_FORMAT_R16G16B16_SSCALED: return Alternatives::R16G16B16_SSCALED.data(); case VK_FORMAT_R8G8B8_SSCALED: return Alternatives::R8G8B8_SSCALED.data(); case VK_FORMAT_R32G32B32_SFLOAT: return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data(); case VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT: return Alternatives::VK_FORMAT_A4B4G4R4_UNORM_PACK16.data(); case VK_FORMAT_B10G11R11_UFLOAT_PACK32: return Alternatives::B10G11R11_UFLOAT_PACK32.data(); default: return nullptr; } } VkFormatFeatureFlags GetFormatFeatures(VkFormatProperties properties, FormatType format_type) { switch (format_type) { case FormatType::Linear: return properties.linearTilingFeatures; case FormatType::Optimal: return properties.optimalTilingFeatures; case FormatType::Buffer: return properties.bufferFeatures; default: return {}; } } ::Common::unordered_map GetFormatProperties(vk::PhysicalDevice physical) { static constexpr std::array formats{ VK_FORMAT_A1R5G5B5_UNORM_PACK16, VK_FORMAT_A2B10G10R10_SINT_PACK32, VK_FORMAT_A2B10G10R10_SNORM_PACK32, VK_FORMAT_A2B10G10R10_SSCALED_PACK32, VK_FORMAT_A2B10G10R10_UINT_PACK32, VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_FORMAT_A2B10G10R10_USCALED_PACK32, VK_FORMAT_A2R10G10B10_UNORM_PACK32, VK_FORMAT_A8B8G8R8_SINT_PACK32, VK_FORMAT_A8B8G8R8_SNORM_PACK32, VK_FORMAT_A8B8G8R8_SRGB_PACK32, VK_FORMAT_A8B8G8R8_UINT_PACK32, VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_FORMAT_ASTC_10x10_SRGB_BLOCK, VK_FORMAT_ASTC_10x10_UNORM_BLOCK, VK_FORMAT_ASTC_10x5_SRGB_BLOCK, VK_FORMAT_ASTC_10x5_UNORM_BLOCK, VK_FORMAT_ASTC_10x6_SRGB_BLOCK, VK_FORMAT_ASTC_10x6_UNORM_BLOCK, VK_FORMAT_ASTC_10x8_SRGB_BLOCK, VK_FORMAT_ASTC_10x8_UNORM_BLOCK, VK_FORMAT_ASTC_12x10_SRGB_BLOCK, VK_FORMAT_ASTC_12x10_UNORM_BLOCK, VK_FORMAT_ASTC_12x12_SRGB_BLOCK, VK_FORMAT_ASTC_12x12_UNORM_BLOCK, VK_FORMAT_ASTC_4x4_SRGB_BLOCK, VK_FORMAT_ASTC_4x4_UNORM_BLOCK, VK_FORMAT_ASTC_5x4_SRGB_BLOCK, VK_FORMAT_ASTC_5x4_UNORM_BLOCK, VK_FORMAT_ASTC_5x5_SRGB_BLOCK, VK_FORMAT_ASTC_5x5_UNORM_BLOCK, VK_FORMAT_ASTC_6x5_SRGB_BLOCK, VK_FORMAT_ASTC_6x5_UNORM_BLOCK, VK_FORMAT_ASTC_6x6_SRGB_BLOCK, VK_FORMAT_ASTC_6x6_UNORM_BLOCK, VK_FORMAT_ASTC_8x5_SRGB_BLOCK, VK_FORMAT_ASTC_8x5_UNORM_BLOCK, VK_FORMAT_ASTC_8x6_SRGB_BLOCK, VK_FORMAT_ASTC_8x6_UNORM_BLOCK, VK_FORMAT_ASTC_8x8_SRGB_BLOCK, VK_FORMAT_ASTC_8x8_UNORM_BLOCK, VK_FORMAT_B10G11R11_UFLOAT_PACK32, VK_FORMAT_B4G4R4A4_UNORM_PACK16, VK_FORMAT_B5G5R5A1_UNORM_PACK16, VK_FORMAT_B5G6R5_UNORM_PACK16, VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_BC1_RGBA_SRGB_BLOCK, VK_FORMAT_BC1_RGBA_UNORM_BLOCK, VK_FORMAT_BC2_SRGB_BLOCK, VK_FORMAT_BC2_UNORM_BLOCK, VK_FORMAT_BC3_SRGB_BLOCK, VK_FORMAT_BC3_UNORM_BLOCK, VK_FORMAT_BC4_SNORM_BLOCK, VK_FORMAT_BC4_UNORM_BLOCK, VK_FORMAT_BC5_SNORM_BLOCK, VK_FORMAT_BC5_UNORM_BLOCK, VK_FORMAT_BC6H_SFLOAT_BLOCK, VK_FORMAT_BC6H_UFLOAT_BLOCK, VK_FORMAT_BC7_SRGB_BLOCK, VK_FORMAT_BC7_UNORM_BLOCK, VK_FORMAT_D16_UNORM, VK_FORMAT_D16_UNORM_S8_UINT, VK_FORMAT_X8_D24_UNORM_PACK32, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, VK_FORMAT_R16G16B16A16_SFLOAT, VK_FORMAT_R16G16B16A16_SINT, VK_FORMAT_R16G16B16A16_SNORM, VK_FORMAT_R16G16B16A16_SSCALED, VK_FORMAT_R16G16B16A16_UINT, VK_FORMAT_R16G16B16A16_UNORM, VK_FORMAT_R16G16B16A16_USCALED, VK_FORMAT_R16G16B16_SFLOAT, VK_FORMAT_R16G16B16_SINT, VK_FORMAT_R16G16B16_SNORM, VK_FORMAT_R16G16B16_SSCALED, VK_FORMAT_R16G16B16_UINT, VK_FORMAT_R16G16B16_UNORM, VK_FORMAT_R16G16B16_USCALED, VK_FORMAT_R16G16_SFLOAT, VK_FORMAT_R16G16_SINT, VK_FORMAT_R16G16_SNORM, VK_FORMAT_R16G16_SSCALED, VK_FORMAT_R16G16_UINT, VK_FORMAT_R16G16_UNORM, VK_FORMAT_R16G16_USCALED, VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16_SINT, VK_FORMAT_R16_SNORM, VK_FORMAT_R16_SSCALED, VK_FORMAT_R16_UINT, VK_FORMAT_R16_UNORM, VK_FORMAT_R16_USCALED, VK_FORMAT_R32G32B32A32_SFLOAT, VK_FORMAT_R32G32B32A32_SINT, VK_FORMAT_R32G32B32A32_UINT, VK_FORMAT_R32G32B32_SFLOAT, VK_FORMAT_R32G32B32_SINT, VK_FORMAT_R32G32B32_UINT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32_SINT, VK_FORMAT_R32G32_UINT, VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32_SINT, VK_FORMAT_R32_UINT, VK_FORMAT_R4G4B4A4_UNORM_PACK16, VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT, VK_FORMAT_R4G4_UNORM_PACK8, VK_FORMAT_R5G5B5A1_UNORM_PACK16, VK_FORMAT_R5G6B5_UNORM_PACK16, VK_FORMAT_R8G8B8A8_SINT, VK_FORMAT_R8G8B8A8_SNORM, VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_R8G8B8A8_SSCALED, VK_FORMAT_R8G8B8A8_UINT, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_USCALED, VK_FORMAT_R8G8B8_SINT, VK_FORMAT_R8G8B8_SNORM, VK_FORMAT_R8G8B8_SSCALED, VK_FORMAT_R8G8B8_UINT, VK_FORMAT_R8G8B8_UNORM, VK_FORMAT_R8G8B8_USCALED, VK_FORMAT_R8G8_SINT, VK_FORMAT_R8G8_SNORM, VK_FORMAT_R8G8_SSCALED, VK_FORMAT_R8G8_UINT, VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8_USCALED, VK_FORMAT_R8_SINT, VK_FORMAT_R8_SNORM, VK_FORMAT_R8_SSCALED, VK_FORMAT_R8_UINT, VK_FORMAT_R8_UNORM, VK_FORMAT_R8_USCALED, VK_FORMAT_S8_UINT, VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK, VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK, VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, VK_FORMAT_EAC_R11_UNORM_BLOCK, VK_FORMAT_EAC_R11_SNORM_BLOCK, VK_FORMAT_EAC_R11G11_UNORM_BLOCK, VK_FORMAT_EAC_R11G11_SNORM_BLOCK, }; ::Common::unordered_map format_properties; for (const auto format : formats) { format_properties.emplace(format, physical.GetFormatProperties(format)); } return format_properties; } #if defined(__ANDROID__) && defined(ARCHITECTURE_arm64) void OverrideBcnFormats(::Common::unordered_map& format_properties) { // These properties are extracted from Adreno driver 512.687.0 constexpr VkFormatFeatureFlags tiling_features{VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT}; constexpr VkFormatFeatureFlags buffer_features{VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT}; static constexpr std::array bcn_formats{ VK_FORMAT_BC1_RGBA_SRGB_BLOCK, VK_FORMAT_BC1_RGBA_UNORM_BLOCK, VK_FORMAT_BC2_SRGB_BLOCK, VK_FORMAT_BC2_UNORM_BLOCK, VK_FORMAT_BC3_SRGB_BLOCK, VK_FORMAT_BC3_UNORM_BLOCK, VK_FORMAT_BC4_SNORM_BLOCK, VK_FORMAT_BC4_UNORM_BLOCK, VK_FORMAT_BC5_SNORM_BLOCK, VK_FORMAT_BC5_UNORM_BLOCK, VK_FORMAT_BC6H_SFLOAT_BLOCK, VK_FORMAT_BC6H_UFLOAT_BLOCK, VK_FORMAT_BC7_SRGB_BLOCK, VK_FORMAT_BC7_UNORM_BLOCK, }; for (const auto format : bcn_formats) { format_properties[format].linearTilingFeatures = tiling_features; format_properties[format].optimalTilingFeatures = tiling_features; format_properties[format].bufferFeatures = buffer_features; } } #endif NvidiaArchitecture GetNvidiaArchitecture(vk::PhysicalDevice physical, const std::set>& exts) { VkPhysicalDeviceProperties2 physical_properties{}; physical_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; physical_properties.pNext = nullptr; if (exts.contains(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME)) { VkPhysicalDeviceFragmentShadingRatePropertiesKHR shading_rate_props{}; shading_rate_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR; physical_properties.pNext = &shading_rate_props; physical.GetProperties2(physical_properties); if (shading_rate_props.primitiveFragmentShadingRateWithMultipleViewports) { return NvidiaArchitecture::Arch_AmpereOrNewer; } return NvidiaArchitecture::Arch_Turing; } if (exts.contains(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME)) { VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT advanced_blending_props{}; advanced_blending_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_PROPERTIES_EXT; physical_properties.pNext = &advanced_blending_props; physical.GetProperties2(physical_properties); if (advanced_blending_props.advancedBlendMaxColorAttachments == 1) { return NvidiaArchitecture::Arch_Maxwell; } if (exts.contains(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME)) { VkPhysicalDeviceConservativeRasterizationPropertiesEXT conservative_raster_props{}; conservative_raster_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONSERVATIVE_RASTERIZATION_PROPERTIES_EXT; physical_properties.pNext = &conservative_raster_props; physical.GetProperties2(physical_properties); if (conservative_raster_props.degenerateLinesRasterized) { return NvidiaArchitecture::Arch_Volta; } return NvidiaArchitecture::Arch_Pascal; } } return NvidiaArchitecture::Arch_KeplerOrOlder; } std::vector ExtensionListForVulkan( const std::set>& extensions) { std::vector output; output.reserve(extensions.size()); for (const auto& extension : extensions) { output.push_back(extension.c_str()); } return output; } constexpr std::array STATIC_CACHE_MAGIC_NUMBER{'e', 'd', 'e', 'n', 's', 't', 'p', 'c'}; constexpr u32 STATIC_CACHE_VERSION = 1; std::filesystem::path StaticPipelineCacheFilename() { const auto shader_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir); if (!Common::FS::CreateDir(shader_dir)) { return {}; } return shader_dir / "vulkan_static_pipelines.bin"; } // What a heap's allocations use. The driver also counts the space in VMA's blocks no allocation // uses, but freeing an image gives its share back only once its whole block is empty: the caches, // collecting until the usage dropped, evicted thousands of textures in a row without lowering it // (title 1). u64 AllocatedUsage(VkDeviceSize driver_usage, const VmaBudget& vma_budget) { const VmaStatistics& statistics = vma_budget.statistics; const u64 unused = statistics.blockBytes - statistics.allocationBytes; return driver_usage - (std::min)(driver_usage, unused); } } // Anonymous namespace void Device::RemoveExtension(bool& extension, const std::string& extension_name) { extension = false; loaded_extensions.erase(extension_name); } void Device::RemoveExtensionIfUnsuitable(bool is_suitable, const std::string& extension_name) { if (loaded_extensions.contains(extension_name) && !is_suitable) { LOG_WARNING(Render_Vulkan, "Removing unsuitable extension {}", extension_name); this->RemoveExtension(is_suitable, extension_name); } } template void Device::RemoveExtensionFeature(bool& extension, Feature& feature, const std::string& extension_name) { // Unload extension. this->RemoveExtension(extension, extension_name); // Save sType and pNext for chain. VkStructureType sType = feature.sType; void* pNext = feature.pNext; // Clear feature struct and restore chain. feature = {}; feature.sType = sType; feature.pNext = pNext; } template void Device::RemoveExtensionFeatureIfUnsuitable(bool is_suitable, Feature& feature, const std::string& extension_name) { if (loaded_extensions.contains(extension_name) && !is_suitable) { LOG_WARNING(Render_Vulkan, "Removing features for unsuitable extension {}", extension_name); this->RemoveExtensionFeature(is_suitable, feature, extension_name); } } Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR surface, const vk::InstanceDispatch& dld_) : instance{instance_}, dld{dld_}, physical{physical_}, format_properties(GetFormatProperties(physical)) { // Get suitability and device properties. const bool is_suitable = GetSuitability(surface != VkSurfaceKHR{}); const VkDriverId driver_id = properties.driver.driverID; const bool is_radv = driver_id == VK_DRIVER_ID_MESA_RADV; const bool is_amd_driver = driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE; const bool is_amd = is_amd_driver || is_radv; const bool is_intel_windows = driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS; const bool is_intel_anv = driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA; const bool is_nvidia = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY; const bool is_mvk = driver_id == VK_DRIVER_ID_MOLTENVK; const bool is_qualcomm = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY; const bool is_turnip = driver_id == VK_DRIVER_ID_MESA_TURNIP; if (!is_suitable) LOG_WARNING(Render_Vulkan, "Unsuitable driver - continuing anyways"); if (is_nvidia) { nvidia_arch = GetNvidiaArchitecture(physical, supported_extensions); } SetupFamilies(surface); const auto queue_cis = GetDeviceQueueCreateInfos(); // GetSuitability has already configured the linked list of features for us. // Reuse it here. const void* first_next = &features2; VkDeviceDiagnosticsConfigCreateInfoNV diagnostics_nv{}; const bool use_diagnostics_nv = Settings::values.enable_nsight_aftermath && extensions.device_diagnostics_config; if (use_diagnostics_nv) { nsight_aftermath_tracker = std::make_unique(); diagnostics_nv = { .sType = VK_STRUCTURE_TYPE_DEVICE_DIAGNOSTICS_CONFIG_CREATE_INFO_NV, .pNext = &features2, .flags = VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_DEBUG_INFO_BIT_NV | VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_RESOURCE_TRACKING_BIT_NV | VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_AUTOMATIC_CHECKPOINTS_BIT_NV, }; first_next = &diagnostics_nv; } is_blit_depth24_stencil8_supported = TestDepthStencilBlits(VK_FORMAT_D24_UNORM_S8_UINT); is_blit_depth32_stencil8_supported = TestDepthStencilBlits(VK_FORMAT_D32_SFLOAT_S8_UINT); is_optimal_astc_supported = ComputeIsOptimalAstcSupported(); is_warp_potentially_bigger = !extensions.subgroup_size_control || properties.subgroup_size_control.maxSubgroupSize > GuestWarpSize; is_integrated = properties.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; is_virtual = properties.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU; is_non_gpu = properties.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_OTHER || properties.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU; supports_d24_depth = IsFormatSupported(VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT, FormatType::Optimal); supports_conditional_barriers = !(is_intel_anv || is_intel_windows); CollectPhysicalMemoryInfo(); CollectToolingInfo(); if (is_qualcomm) { must_emulate_scaled_formats = true; LOG_WARNING(Render_Vulkan, "Qualcomm drivers require scaled vertex format emulation."); has_broken_descriptor_aliasing = true; LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken descriptor aliasing."); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken color write enable."); RemoveExtensionFeature(extensions.color_write_enable, features.color_write_enable, VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader atomic int64."); RemoveExtensionFeature(extensions.shader_atomic_int64, features.shader_atomic_int64, VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); features.shader_atomic_int64.shaderBufferInt64Atomics = false; features.shader_atomic_int64.shaderSharedInt64Atomics = false; features.features.shaderInt64 = false; LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken workgroup memory explicit layout."); RemoveExtensionFeature(extensions.workgroup_memory_explicit_layout, features.workgroup_memory_explicit_layout, VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME); #if defined(__ANDROID__) && defined(ARCHITECTURE_arm64) // BCn patching only safe on Android 9+ (API 28+). Older versions crash on driver load. const auto major = (properties.properties.driverVersion >> 24) << 2; const auto minor = (properties.properties.driverVersion >> 12) & 0xFFFU; const auto vendor = properties.properties.vendorID; const auto patch_status = adrenotools_get_bcn_type(major, minor, vendor); const int api_level = android_get_device_api_level(); bool should_patch_bcn = api_level >= 28; const bool bcn_debug_override = Settings::values.patch_old_qcom_drivers.GetValue(); if (bcn_debug_override != should_patch_bcn) { LOG_WARNING(Render_Vulkan, "BCn patch debug override active: {} (auto-detected: {})", bcn_debug_override, should_patch_bcn); should_patch_bcn = bcn_debug_override; } if (patch_status == ADRENOTOOLS_BCN_PATCH) { if (should_patch_bcn) { LOG_INFO(Render_Vulkan, "Patching Adreno driver to support BCn texture formats " "(Android API {}, Driver {}.{})", api_level, major, minor); if (adrenotools_patch_bcn( reinterpret_cast(dld.vkGetPhysicalDeviceFormatProperties))) { OverrideBcnFormats(format_properties); } else { LOG_ERROR(Render_Vulkan, "BCn patch failed! Driver code may now crash"); } } else { LOG_WARNING(Render_Vulkan, "BCn texture patching skipped for stability (Android API {} < 28). " "Driver version {}.{} would support patching, but may crash on older Android.", api_level, major, minor); } } else if (patch_status == ADRENOTOOLS_BCN_BLOB) { LOG_INFO(Render_Vulkan, "Adreno driver supports BCn textures natively (no patch needed)"); } else { LOG_INFO(Render_Vulkan, "Adreno driver does not support BCn texture patching (Android API {}, Driver {}.{})", api_level, major, minor); } #endif } if (is_nvidia) { const auto arch = GetNvidiaArch(); if (arch >= NvidiaArchitecture::Arch_AmpereOrNewer) { LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math"); features.shader_float16_int8.shaderFloat16 = false; } // Use hardware depth/stencil blits instead when available if (!extensions.shader_stencil_export) { LOG_INFO(Render_Vulkan, "NVIDIA: VK_EXT_shader_stencil_export not supported, using hardware blits " "for depth/stencil operations"); LOG_INFO(Render_Vulkan, " D24S8 hardware blit support: {}", is_blit_depth24_stencil8_supported); LOG_INFO(Render_Vulkan, " D32S8 hardware blit support: {}", is_blit_depth32_stencil8_supported); if (!is_blit_depth24_stencil8_supported && !is_blit_depth32_stencil8_supported) { LOG_WARNING(Render_Vulkan, "NVIDIA: Neither shader export nor hardware blits available for " "depth/stencil. Performance may be degraded."); } } } sets_per_pool = 64; // AMD GCN4 and older are told apart by their lack of float16, read before float16 is turned // off for newer AMD drivers below: checked after that, every AMD GPU looked like GCN4 and lost // VK_EXT_sampler_filter_minmax (an RDNA3 Radeon 780M logged it). const bool amd_gcn4_or_older = is_amd && !features.shader_float16_int8.shaderFloat16; if (is_amd_driver) { // AMD drivers need a higher amount of Sets per Pool in certain circumstances like in XC2. sets_per_pool = 96; // Disable VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT on AMD GCN4 and lower as it is broken. if (amd_gcn4_or_older) { LOG_WARNING(Render_Vulkan, "AMD GCN4 and earlier have broken VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT"); has_broken_cube_compatibility = true; } // AMD drivers (2026+) have broken float16 math on DKCR if (features.shader_float16_int8.shaderFloat16) { LOG_WARNING(Render_Vulkan, "AMD drivers (2026+) have broken float16 math"); features.shader_float16_int8.shaderFloat16 = false; } } if (extensions.sampler_filter_minmax && is_amd) { // Disable ext_sampler_filter_minmax on AMD GCN4 and lower as it is broken. if (amd_gcn4_or_older) { LOG_WARNING(Render_Vulkan, "AMD GCN4 and earlier have broken VK_EXT_sampler_filter_minmax"); RemoveExtension(extensions.sampler_filter_minmax, VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME); } } if (features.shader_float16_int8.shaderFloat16 && is_intel_windows) { // Intel's compiler crashes when using fp16 on Astral Chain, disable it for the time being. LOG_WARNING(Render_Vulkan, "Intel has broken float16 math"); features.shader_float16_int8.shaderFloat16 = false; } has_broken_compute = CheckBrokenCompute(properties.driver.driverID, properties.properties.driverVersion) && !Settings::values.enable_compute_pipelines.GetValue(); if (is_mvk) { LOG_WARNING(Render_Vulkan, "MVK driver breaks when using more than 16 vertex attributes/bindings"); properties.properties.limits.maxVertexInputAttributes = (std::min)(properties.properties.limits.maxVertexInputAttributes, 16U); properties.properties.limits.maxVertexInputBindings = (std::min)(properties.properties.limits.maxVertexInputBindings, 16U); } if (is_turnip || is_qualcomm) { LOG_WARNING(Render_Vulkan, "Driver requires higher-than-reported binding limits"); properties.properties.limits.maxVertexInputBindings = 32; } const auto dyna_state = Settings::values.dyna_state.GetValue(); switch (dyna_state) { case Settings::ExtendedDynamicState::Disabled: // Level 0: Disable all extended dynamic state extensions RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2, VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3, VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); dynamic_state3_blending = false; dynamic_state3_enables = false; break; case Settings::ExtendedDynamicState::EDS1: // Level 1: Enable EDS1, disable EDS2 and EDS3 RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2, VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3, VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); dynamic_state3_blending = false; dynamic_state3_enables = false; break; case Settings::ExtendedDynamicState::EDS2: // Level 2: Enable EDS1 + EDS2, disable EDS3 RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3, VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); dynamic_state3_blending = false; dynamic_state3_enables = false; break; case Settings::ExtendedDynamicState::EDS3: default: // Level 3: Enable all (EDS1 + EDS2 + EDS3) break; } // VK_EXT_vertex_input_dynamic_state if (!Settings::values.vertex_input_dynamic_state.GetValue()) { RemoveExtensionFeature(extensions.vertex_input_dynamic_state, features.vertex_input_dynamic_state, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); } // Descriptors feature list { auto& descriptor_indexing = features.descriptor_indexing; descriptor_indexing.shaderInputAttachmentArrayDynamicIndexing = false; descriptor_indexing.shaderUniformTexelBufferArrayDynamicIndexing = false; descriptor_indexing.shaderStorageTexelBufferArrayDynamicIndexing = false; descriptor_indexing.shaderUniformBufferArrayNonUniformIndexing = false; descriptor_indexing.shaderStorageBufferArrayNonUniformIndexing = false; descriptor_indexing.shaderInputAttachmentArrayNonUniformIndexing = false; descriptor_indexing.descriptorBindingUniformBufferUpdateAfterBind = false; descriptor_indexing.descriptorBindingSampledImageUpdateAfterBind = false; descriptor_indexing.descriptorBindingStorageImageUpdateAfterBind = false; descriptor_indexing.descriptorBindingStorageBufferUpdateAfterBind = false; descriptor_indexing.descriptorBindingUniformTexelBufferUpdateAfterBind = false; descriptor_indexing.descriptorBindingStorageTexelBufferUpdateAfterBind = false; descriptor_indexing.descriptorBindingUpdateUnusedWhilePending = false; descriptor_indexing.descriptorBindingVariableDescriptorCount = false; descriptor_indexing.runtimeDescriptorArray = false; } // VK_EXT_descriptor_buffer requires VK_KHR_buffer_device_address if (extensions.descriptor_buffer && !features.buffer_device_address.bufferDeviceAddress) { LOG_WARNING(Render_Vulkan, "Descriptor buffer needs buffer device address, disabling."); RemoveExtensionFeature(extensions.descriptor_buffer, features.descriptor_buffer, VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME); } #ifdef EGLANTINE_STREAMLINE // NVIDIA's DLSS needs buffer device addresses: keep the feature enabled for // it, but leave Eglantine's own buffers as they are without descriptor buffers. const bool dlss_wants_device_address = streamline_extensions && features.buffer_device_address.bufferDeviceAddress; if (!extensions.descriptor_buffer && dlss_wants_device_address) { extensions.buffer_device_address = false; } else #endif if (!extensions.descriptor_buffer) { RemoveExtensionFeature(extensions.buffer_device_address, features.buffer_device_address, VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); } logical = vk::Device::Create(physical, queue_cis, ExtensionListForVulkan(loaded_extensions), first_next, dld); graphics_queue = logical.GetQueue(graphics_family); present_queue = logical.GetQueue(present_family); #ifdef EGLANTINE_STREAMLINE if (streamline_extensions) { streamline_attached = Streamline::AttachDevice(instance, physical, *logical, graphics_family, dld); } else if (Streamline::WantsDevice()) { Streamline::Stop(); } #endif VmaVulkanFunctions functions{}; functions.vkGetInstanceProcAddr = dld.vkGetInstanceProcAddr; functions.vkGetDeviceProcAddr = dld.vkGetDeviceProcAddr; VmaAllocatorCreateFlags flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT; if (extensions.memory_budget) { flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT; } if (extensions.buffer_device_address) { flags |= VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; } // EDEN_NX2_VRAM_LIMIT_MIB: VMA fails allocations past the limit, like a smaller card. std::array heap_size_limits; heap_size_limits.fill(VK_WHOLE_SIZE); if (heap_size_limit != 0) { const auto memory_properties = physical.GetMemoryProperties().memoryProperties; for (u32 heap = 0; heap < memory_properties.memoryHeapCount; ++heap) { if (memory_properties.memoryHeaps[heap].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) { heap_size_limits[heap] = heap_size_limit; } } } const VmaAllocatorCreateInfo allocator_info{ .flags = flags, .physicalDevice = physical, .device = *logical, // 16 MiB blocks on discrete GPUs too: with 256 MiB ones, Switch 2 titles' mix of // texture sizes left ~38% of the image blocks empty (2 GB of an 8 GB card in title // 1), which the texture cache counted as used and kept evicting textures for. With // 64 MiB ones textures created and deleted by the thousand still left ~1.7 GB of holes // after ten minutes, with 32 MiB ones ~0.5 GB: a block drains only once all of its // images are gone, and fewer per block drain sooner. VMA gives allocations over half a // block (8 MiB) their own memory, which goes back to the driver when they are freed. .preferredLargeHeapBlockSize = 16u * 1024u * 1024u, .pAllocationCallbacks = nullptr, .pDeviceMemoryCallbacks = nullptr, .pHeapSizeLimit = heap_size_limit != 0 ? heap_size_limits.data() : nullptr, .pVulkanFunctions = &functions, .instance = instance, .vulkanApiVersion = ApiVersion(), .pTypeExternalMemoryHandleTypes = nullptr, }; vk::Check(vmaCreateAllocator(&allocator_info, &allocator)); owns_static_pipeline_cache = surface != VkSurfaceKHR{}; LoadStaticPipelineCache(); // Initialize GPU logging if enabled InitializeGPULogging(); } Device::~Device() { #ifdef EGLANTINE_STREAMLINE if (streamline_attached) { Streamline::Stop(); } #endif SaveStaticPipelineCache(); ShutdownGPULogging(); for (auto& pools : separate_pools) { for (VmaPool pool : pools) { if (pool != VK_NULL_HANDLE) { vmaDestroyPool(allocator, pool); } } } vmaDestroyAllocator(allocator); } VmaPool Device::GetSeparatePool(SeparatePool kind, u32 memory_type) const { std::scoped_lock lk{separate_pools_mutex}; VmaPool& pool = separate_pools[static_cast(kind)][memory_type]; if (pool == VK_NULL_HANDLE) { const VmaPoolCreateInfo ci{ .memoryTypeIndex = memory_type, .flags = 0, .blockSize = SeparatePoolBlockSize, .minBlockCount = 0, .maxBlockCount = 0, .priority = 0.f, .minAllocationAlignment = 0, .pMemoryAllocateNext = nullptr, }; if (vmaCreatePool(allocator, &ci, &pool) != VK_SUCCESS) { pool = VK_NULL_HANDLE; } } return pool; } void Device::LoadStaticPipelineCache() { const auto create = [this](size_t size, const void* data) { static_pipeline_cache = logical.CreatePipelineCache({ .sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, .pNext = nullptr, .flags = 0, .initialDataSize = size, .pInitialData = data, }); }; if (!owns_static_pipeline_cache) { create(0, nullptr); return; } const auto filename = StaticPipelineCacheFilename(); if (filename.empty()) { create(0, nullptr); return; } std::vector data; try { std::ifstream file(filename, std::ios::binary | std::ios::ate); if (!file.is_open()) { create(0, nullptr); return; } file.exceptions(std::ifstream::failbit | std::ifstream::badbit); const size_t total = static_cast(file.tellg()); file.seekg(0, std::ios::beg); std::array magic{}; u32 version{}; if (total < magic.size() + sizeof(version)) { create(0, nullptr); return; } file.read(magic.data(), magic.size()) .read(reinterpret_cast(&version), sizeof(version)); if (magic != STATIC_CACHE_MAGIC_NUMBER || version != STATIC_CACHE_VERSION) { create(0, nullptr); return; } data.resize(total - magic.size() - sizeof(version)); file.read(data.data(), static_cast(data.size())); } catch (const std::ios_base::failure& e) { create(0, nullptr); return; } create(data.size(), data.empty() ? nullptr : data.data()); } void Device::SaveStaticPipelineCache() const { if (!owns_static_pipeline_cache || !static_pipeline_cache) { return; } const auto filename = StaticPipelineCacheFilename(); if (filename.empty()) { return; } size_t size = 0; std::vector data; static_pipeline_cache.Read(&size, nullptr); if (size == 0) { return; } data.resize(size); static_pipeline_cache.Read(&size, data.data()); try { std::ofstream file(filename, std::ios::binary | std::ios::trunc); file.exceptions(std::ofstream::failbit); if (!file.is_open()) { return; } file.write(STATIC_CACHE_MAGIC_NUMBER.data(), STATIC_CACHE_MAGIC_NUMBER.size()) .write(reinterpret_cast(&STATIC_CACHE_VERSION), sizeof(STATIC_CACHE_VERSION)) .write(data.data(), static_cast(size)); } catch (const std::ios_base::failure& e) { Common::FS::RemoveFile(filename); } } VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage, FormatType format_type) const { if (IsFormatSupported(wanted_format, wanted_usage, format_type)) { return wanted_format; } // The wanted format is not supported by hardware, search for alternatives const VkFormat* alternatives = GetFormatAlternatives(wanted_format); if (alternatives == nullptr) { LOG_ERROR(Render_Vulkan, "Format={} with usage={} and type={} has no defined alternatives and host " "hardware does not support it", wanted_format, wanted_usage, format_type); return wanted_format; } std::size_t i = 0; for (VkFormat alternative = *alternatives; alternative; alternative = alternatives[++i]) { if (!IsFormatSupported(alternative, wanted_usage, format_type)) { continue; } LOG_DEBUG(Render_Vulkan, "Emulating format={} with alternative format={} with usage={} and type={}", wanted_format, alternative, wanted_usage, format_type); return alternative; } // No alternatives found, panic LOG_ERROR(Render_Vulkan, "Format={} with usage={} and type={} is not supported by the host hardware and " "doesn't support any of the alternatives", wanted_format, wanted_usage, format_type); return wanted_format; } void Device::ReportLoss() const { LOG_CRITICAL(Render_Vulkan, "Device loss occurred!"); // Wait for the log to flush and for Nsight Aftermath to dump the results std::this_thread::sleep_for(std::chrono::seconds{15}); } void Device::SaveShader(std::span spirv) const { if (nsight_aftermath_tracker) { nsight_aftermath_tracker->SaveShader(spirv); } } bool Device::ComputeIsOptimalAstcSupported() const { static constexpr std::array astc_formats = { VK_FORMAT_ASTC_4x4_UNORM_BLOCK, VK_FORMAT_ASTC_4x4_SRGB_BLOCK, VK_FORMAT_ASTC_5x4_UNORM_BLOCK, VK_FORMAT_ASTC_5x4_SRGB_BLOCK, VK_FORMAT_ASTC_5x5_UNORM_BLOCK, VK_FORMAT_ASTC_5x5_SRGB_BLOCK, VK_FORMAT_ASTC_6x5_UNORM_BLOCK, VK_FORMAT_ASTC_6x5_SRGB_BLOCK, VK_FORMAT_ASTC_6x6_UNORM_BLOCK, VK_FORMAT_ASTC_6x6_SRGB_BLOCK, VK_FORMAT_ASTC_8x5_UNORM_BLOCK, VK_FORMAT_ASTC_8x5_SRGB_BLOCK, VK_FORMAT_ASTC_8x6_UNORM_BLOCK, VK_FORMAT_ASTC_8x6_SRGB_BLOCK, VK_FORMAT_ASTC_8x8_UNORM_BLOCK, VK_FORMAT_ASTC_8x8_SRGB_BLOCK, VK_FORMAT_ASTC_10x5_UNORM_BLOCK, VK_FORMAT_ASTC_10x5_SRGB_BLOCK, VK_FORMAT_ASTC_10x6_UNORM_BLOCK, VK_FORMAT_ASTC_10x6_SRGB_BLOCK, VK_FORMAT_ASTC_10x8_UNORM_BLOCK, VK_FORMAT_ASTC_10x8_SRGB_BLOCK, VK_FORMAT_ASTC_10x10_UNORM_BLOCK, VK_FORMAT_ASTC_10x10_SRGB_BLOCK, VK_FORMAT_ASTC_12x10_UNORM_BLOCK, VK_FORMAT_ASTC_12x10_SRGB_BLOCK, VK_FORMAT_ASTC_12x12_UNORM_BLOCK, VK_FORMAT_ASTC_12x12_SRGB_BLOCK, }; if (!features.features.textureCompressionASTC_LDR) { return false; } const VkFormatFeatureFlags format_feature_usage{ VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT}; for (const auto format : astc_formats) { const auto physical_format_properties{physical.GetFormatProperties(format)}; if ((physical_format_properties.optimalTilingFeatures & format_feature_usage) != format_feature_usage) { return false; } } return true; } bool Device::TestDepthStencilBlits(VkFormat format) const { static constexpr VkFormatFeatureFlags required_features = VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT; const auto test_features = [](VkFormatProperties props) { return (props.optimalTilingFeatures & required_features) == required_features; }; return test_features(format_properties.at(format)); } bool Device::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage, FormatType format_type) const { const auto it = format_properties.find(wanted_format); if (it == format_properties.end()) { UNIMPLEMENTED_MSG("Unimplemented format query={}", wanted_format); return true; } const auto supported_usage = GetFormatFeatures(it->second, format_type); return (supported_usage & wanted_usage) == wanted_usage; } std::string Device::GetDriverName() const { return vk::GetDriverName(properties.driver); } bool Device::ShouldBoostClocks() const { const auto driver_id = properties.driver.driverID; const auto vendor_id = properties.properties.vendorID; const auto device_id = properties.properties.deviceID; const bool validated_driver = driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE || driver_id == VK_DRIVER_ID_MESA_RADV || driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY || driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS || driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA || driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP || driver_id == VK_DRIVER_ID_SAMSUNG_PROPRIETARY; const bool is_steam_deck = (vendor_id == 0x1002 && device_id == 0x163F) || (vendor_id == 0x1002 && device_id == 0x1435); const bool is_debugging = this->HasDebuggingToolAttached(); return validated_driver && !is_steam_deck && !is_debugging; } bool Device::HasTimelineSemaphore() const { if (GetDriverID() == VK_DRIVER_ID_MESA_TURNIP) { return false; } return features.timeline_semaphore.timelineSemaphore; } bool Device::MustEmulateBGR565() const { return Settings::values.emulate_bgr565.GetValue(); } bool Device::GetSuitability(bool requires_swapchain) { // Assume we will be suitable. bool suitable = true; // Configure properties. VkPhysicalDeviceVulkan12Features features_1_2{}; VkPhysicalDeviceVulkan13Features features_1_3{}; // Configure properties. properties.properties = physical.GetProperties(); // Set instance version. instance_version = properties.properties.apiVersion; // Minimum of API version 1.1 is required. (This is well-supported.) ASSERT(instance_version >= VK_API_VERSION_1_1); // Get available extensions. auto extension_properties = physical.EnumerateDeviceExtensionProperties(); // Get the set of supported extensions. supported_extensions.clear(); for (const VkExtensionProperties& property : extension_properties) { supported_extensions.insert(property.extensionName); } // Generate list of extensions to load. loaded_extensions.clear(); #define EXTENSION(prefix, macro_name, var_name) \ if (supported_extensions.contains(VK_##prefix##_##macro_name##_EXTENSION_NAME)) { \ loaded_extensions.insert(VK_##prefix##_##macro_name##_EXTENSION_NAME); \ extensions.var_name = true; \ } #define FEATURE_EXTENSION(prefix, struct_name, macro_name, var_name) \ if (supported_extensions.contains(VK_##prefix##_##macro_name##_EXTENSION_NAME)) { \ loaded_extensions.insert(VK_##prefix##_##macro_name##_EXTENSION_NAME); \ extensions.var_name = true; \ } if (instance_version < VK_API_VERSION_1_2) { FOR_EACH_VK_FEATURE_1_2(FEATURE_EXTENSION); } if (instance_version < VK_API_VERSION_1_3) { FOR_EACH_VK_FEATURE_1_3(FEATURE_EXTENSION); } FOR_EACH_VK_FEATURE_EXT(FEATURE_EXTENSION); FOR_EACH_VK_EXTENSION(EXTENSION); extensions.depth_stencil_resolve = extensions.depth_stencil_resolve && (instance_version >= VK_API_VERSION_1_2 || extensions.create_renderpass2); RemoveExtensionIfUnsuitable(extensions.depth_stencil_resolve, VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME); if (supported_extensions.contains(VK_KHR_ROBUSTNESS_2_EXTENSION_NAME)) { loaded_extensions.erase(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME); loaded_extensions.insert(VK_KHR_ROBUSTNESS_2_EXTENSION_NAME); extensions.robustness_2 = true; } else if (supported_extensions.contains(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME)) { loaded_extensions.insert(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME); extensions.robustness_2 = true; } else { extensions.robustness_2 = false; } #ifdef EGLANTINE_STREAMLINE // The extensions NVIDIA's DLSS asks for, where the device has them all. if (Streamline::WantsDevice()) { const std::vector wanted = Streamline::DeviceExtensions(); const bool all = !wanted.empty() && std::ranges::all_of(wanted, [&](const std::string& name) { return supported_extensions.contains(name); }); std::string names; for (const std::string& name : wanted) { if (all) { loaded_extensions.insert(name); } names += (names.empty() ? "" : ", ") + name; } streamline_extensions = all; LOG_INFO(Render_Vulkan, "NVIDIA DLSS device extensions: {} ({})", all ? "enabled" : "unavailable", names); } #endif #undef FEATURE_EXTENSION #undef EXTENSION // Some extensions are mandatory. Check those. #define CHECK_EXTENSION(extension_name) \ if (!loaded_extensions.contains(extension_name)) { \ LOG_ERROR(Render_Vulkan, "Missing required extension {}", extension_name); \ suitable = false; \ } #define LOG_EXTENSION(extension_name) \ if (!loaded_extensions.contains(extension_name)) { \ LOG_INFO(Render_Vulkan, "Device doesn't support extension {}", extension_name); \ } FOR_EACH_VK_RECOMMENDED_EXTENSION(LOG_EXTENSION); FOR_EACH_VK_MANDATORY_EXTENSION(CHECK_EXTENSION); if (requires_swapchain) { CHECK_EXTENSION(VK_KHR_SWAPCHAIN_EXTENSION_NAME); } if (instance_version < VK_API_VERSION_1_2) { CHECK_EXTENSION(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME); } #undef LOG_EXTENSION #undef CHECK_EXTENSION // Generate the linked list of features to test. features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; // Set next pointer. void** next = &features2.pNext; // Vulkan 1.2 and 1.3 features if (instance_version >= VK_API_VERSION_1_2) { features_1_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; features_1_3.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; features_1_2.pNext = &features_1_3; *next = &features_1_2; } // Test all features we know about. If the feature is not available in core at our // current API version, and was not enabled by an extension, skip testing the feature. // We set the structure sType explicitly here as it is zeroed by the constructor. #define FEATURE(prefix, struct_name, macro_name, var_name) \ features.var_name.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_##macro_name##_FEATURES; \ SetNext(next, features.var_name); #define EXT_FEATURE(prefix, struct_name, macro_name, var_name) \ if (extensions.var_name) { \ features.var_name.sType = \ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_##macro_name##_FEATURES_##prefix; \ SetNext(next, features.var_name); \ } FOR_EACH_VK_FEATURE_1_1(FEATURE); FOR_EACH_VK_FEATURE_EXT(EXT_FEATURE); if (instance_version >= VK_API_VERSION_1_2) { FOR_EACH_VK_FEATURE_1_2(FEATURE); } else { FOR_EACH_VK_FEATURE_1_2(EXT_FEATURE); } if (instance_version >= VK_API_VERSION_1_3) { FOR_EACH_VK_FEATURE_1_3(FEATURE); } else { FOR_EACH_VK_FEATURE_1_3(EXT_FEATURE); } #undef EXT_FEATURE #undef FEATURE // Perform the feature test. physical.GetFeatures2(features2); // Base Vulkan 1.0 features are always valid regardless of instance version. features.features = features2.features; // Some features are mandatory. Check those. #define CHECK_FEATURE(feature, name) \ if (!features.feature.name) { \ if (IsMoltenVK() && (strcmp(#name, "geometryShader") == 0 || \ strcmp(#name, "logicOp") == 0 || \ strcmp(#name, "shaderCullDistance") == 0 || \ strcmp(#name, "wideLines") == 0)) { \ LOG_INFO(Render_Vulkan, "MoltenVK missing feature {} - using fallback", #name); \ } else { \ LOG_ERROR(Render_Vulkan, "Missing required feature {}", #name); \ suitable = false; \ } \ } #define LOG_FEATURE(feature, name) \ if (!features.feature.name) { \ LOG_INFO(Render_Vulkan, "Device doesn't support feature {}", #name); \ } // Optional features are enabled silently without any logging #define OPTIONAL_FEATURE(feature, name) (void)features.feature.name; FOR_EACH_VK_OPTIONAL_FEATURE(OPTIONAL_FEATURE); FOR_EACH_VK_RECOMMENDED_FEATURE(LOG_FEATURE); FOR_EACH_VK_MANDATORY_FEATURE(CHECK_FEATURE); #undef OPTIONAL_FEATURE #undef LOG_FEATURE #undef CHECK_FEATURE // Generate linked list of properties. properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; // Set next pointer. next = &properties2.pNext; // Get driver info. properties.driver.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES; SetNext(next, properties.driver); // Retrieve subgroup properties. properties.subgroup_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES; SetNext(next, properties.subgroup_properties); // Retrieve relevant extension properties. if (extensions.shader_float_controls) { properties.float_controls.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT_CONTROLS_PROPERTIES; SetNext(next, properties.float_controls); } if (extensions.push_descriptor) { properties.push_descriptor.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR; SetNext(next, properties.push_descriptor); } if (extensions.depth_stencil_resolve || instance_version >= VK_API_VERSION_1_2) { properties.depth_stencil_resolve.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES; SetNext(next, properties.depth_stencil_resolve); } if (extensions.descriptor_buffer) { properties.descriptor_buffer.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_PROPERTIES_EXT; SetNext(next, properties.descriptor_buffer); } if (extensions.subgroup_size_control || features.subgroup_size_control.subgroupSizeControl) { properties.subgroup_size_control.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_PROPERTIES; SetNext(next, properties.subgroup_size_control); } if (extensions.transform_feedback) { properties.transform_feedback.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_PROPERTIES_EXT; SetNext(next, properties.transform_feedback); } if (extensions.maintenance5) { properties.maintenance5.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_PROPERTIES_KHR; SetNext(next, properties.maintenance5); } if (extensions.custom_border_color) { properties.custom_border_color.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_PROPERTIES_EXT; SetNext(next, properties.custom_border_color); } // Perform the property fetch. physical.GetProperties2(properties2); // Store base properties properties.properties = properties2.properties; // Unload extensions if feature support is insufficient. RemoveUnsuitableExtensions(); // Check limits. struct Limit { u32 minimum; u32 value; const char* name; }; const VkPhysicalDeviceLimits& limits{properties.properties.limits}; const std::array limits_report{ Limit{65536, limits.maxUniformBufferRange, "maxUniformBufferRange"}, Limit{16, limits.maxViewports, "maxViewports"}, Limit{8, limits.maxColorAttachments, "maxColorAttachments"}, Limit{8, limits.maxClipDistances, "maxClipDistances"}, }; for (const auto& [min, value, name] : limits_report) { if (value < min) { LOG_ERROR(Render_Vulkan, "{} has to be {} or greater but it is {}", name, min, value); suitable = false; } } // VK_DYNAMIC_STATE // Driver detection variables for workarounds in GetSuitability const VkDriverId driver_id = properties.driver.driverID; // VK_EXT_extended_dynamic_state2 below this will appear drivers that need workarounds. // VK_EXT_extended_dynamic_state3 below this will appear drivers that need workarounds. // Samsung: Broken extendedDynamicState3ColorBlendEquation // Disable blend equation dynamic state, force static pipeline state if (extensions.extended_dynamic_state3 && (driver_id == VK_DRIVER_ID_SAMSUNG_PROPRIETARY)) { LOG_WARNING(Render_Vulkan, "Samsung: Disabling broken extendedDynamicState3ColorBlendEquation"); features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = false; features.extended_dynamic_state3.extendedDynamicState3ColorBlendEquation = false; } // Intel Windows < 27.20.100.0: Broken VertexInputDynamicState // Same for NVIDIA Proprietary < 580.119.02, unknown when VIDS was first NOT broken // Disable VertexInputDynamicState on old Intel Windows drivers if (extensions.vertex_input_dynamic_state) { const u32 version = (properties.properties.driverVersion << 3) >> 3; if ((driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS && version < VK_MAKE_API_VERSION(27, 20, 100, 0)) || (driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY && version < VK_MAKE_API_VERSION(580, 119, 02, 0))) { LOG_WARNING(Render_Vulkan, "Disabling broken VK_EXT_vertex_input_dynamic_state"); RemoveExtensionFeature(extensions.vertex_input_dynamic_state, features.vertex_input_dynamic_state, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); } } if (u32(Settings::values.dyna_state.GetValue()) == 0) { LOG_INFO(Render_Vulkan, "Extended Dynamic State disabled by user setting, clearing all EDS features"); features.extended_dynamic_state.extendedDynamicState = false; features.extended_dynamic_state2.extendedDynamicState2 = false; features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = false; features.extended_dynamic_state3.extendedDynamicState3ColorBlendEquation = false; features.extended_dynamic_state3.extendedDynamicState3ColorWriteMask = false; features.extended_dynamic_state3.extendedDynamicState3DepthClampEnable = false; features.extended_dynamic_state3.extendedDynamicState3LogicOpEnable = false; } // Return whether we were suitable. return suitable; } void Device::RemoveUnsuitableExtensions() { // VK_EXT_color_write_enable extensions.color_write_enable = features.color_write_enable.colorWriteEnable; RemoveExtensionFeatureIfUnsuitable(extensions.color_write_enable, features.color_write_enable, VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME); // VK_EXT_custom_border_color if (extensions.custom_border_color) { extensions.custom_border_color = features.custom_border_color.customBorderColors && features.custom_border_color.customBorderColorWithoutFormat; } RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); // VK_EXT_border_color_swizzle if (extensions.border_color_swizzle) { extensions.border_color_swizzle = extensions.custom_border_color && features.border_color_swizzle.borderColorSwizzle; } RemoveExtensionFeatureIfUnsuitable(extensions.border_color_swizzle, features.border_color_swizzle, VK_EXT_BORDER_COLOR_SWIZZLE_EXTENSION_NAME); // VK_EXT_depth_bias_control extensions.depth_bias_control = features.depth_bias_control.depthBiasControl && features.depth_bias_control.leastRepresentableValueForceUnormRepresentation; RemoveExtensionFeatureIfUnsuitable(extensions.depth_bias_control, features.depth_bias_control, VK_EXT_DEPTH_BIAS_CONTROL_EXTENSION_NAME); // VK_EXT_depth_clip_control extensions.depth_clip_control = features.depth_clip_control.depthClipControl; RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control, VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME); // VK_EXT_descriptor_buffer extensions.descriptor_buffer = features.descriptor_buffer.descriptorBuffer; RemoveExtensionFeatureIfUnsuitable(extensions.descriptor_buffer, features.descriptor_buffer, VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME); // VK_EXT_extended_dynamic_state extensions.extended_dynamic_state = features.extended_dynamic_state.extendedDynamicState; RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state, features.extended_dynamic_state, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); // VK_EXT_extended_dynamic_state2 extensions.extended_dynamic_state2 = features.extended_dynamic_state2.extendedDynamicState2; RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state2, features.extended_dynamic_state2, VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); // VK_EXT_extended_dynamic_state3 const bool supports_color_blend_enable = features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable; const bool supports_color_blend_equation = features.extended_dynamic_state3.extendedDynamicState3ColorBlendEquation; const bool supports_color_write_mask = features.extended_dynamic_state3.extendedDynamicState3ColorWriteMask; dynamic_state3_blending = supports_color_blend_enable && supports_color_blend_equation && supports_color_write_mask; const bool supports_depth_clamp_enable = features.extended_dynamic_state3.extendedDynamicState3DepthClampEnable; const bool supports_logic_op_enable = features.extended_dynamic_state3.extendedDynamicState3LogicOpEnable; const bool supports_line_raster_mode = features.extended_dynamic_state3.extendedDynamicState3LineRasterizationMode && extensions.line_rasterization && features.line_rasterization.rectangularLines; const bool supports_conservative_raster_mode = features.extended_dynamic_state3.extendedDynamicState3ConservativeRasterizationMode && extensions.conservative_rasterization; const bool supports_line_stipple_enable = features.extended_dynamic_state3.extendedDynamicState3LineStippleEnable && extensions.line_rasterization && features.line_rasterization.stippledRectangularLines; const bool supports_alpha_to_coverage = features.extended_dynamic_state3.extendedDynamicState3AlphaToCoverageEnable; const bool supports_alpha_to_one = features.extended_dynamic_state3.extendedDynamicState3AlphaToOneEnable && features.features.alphaToOne; dynamic_state3_depth_clamp_enable = supports_depth_clamp_enable; dynamic_state3_logic_op_enable = supports_logic_op_enable; dynamic_state3_line_raster_mode = supports_line_raster_mode; dynamic_state3_conservative_raster_mode = supports_conservative_raster_mode; dynamic_state3_line_stipple_enable = supports_line_stipple_enable; dynamic_state3_alpha_to_coverage = supports_alpha_to_coverage; dynamic_state3_alpha_to_one = supports_alpha_to_one; dynamic_state3_enables = dynamic_state3_depth_clamp_enable || dynamic_state3_logic_op_enable || dynamic_state3_line_raster_mode || dynamic_state3_conservative_raster_mode || dynamic_state3_line_stipple_enable || dynamic_state3_alpha_to_coverage || dynamic_state3_alpha_to_one; extensions.extended_dynamic_state3 = dynamic_state3_blending || dynamic_state3_enables; if (!extensions.extended_dynamic_state3) { dynamic_state3_blending = false; dynamic_state3_enables = false; dynamic_state3_depth_clamp_enable = false; dynamic_state3_logic_op_enable = false; dynamic_state3_line_raster_mode = false; dynamic_state3_conservative_raster_mode = false; dynamic_state3_line_stipple_enable = false; dynamic_state3_alpha_to_coverage = false; dynamic_state3_alpha_to_one = false; } RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state3, features.extended_dynamic_state3, VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); // VK_EXT_robustness2 features.robustness2.robustBufferAccess2 = VK_FALSE; features.robustness2.robustImageAccess2 = VK_FALSE; extensions.robustness_2 = features.robustness2.nullDescriptor; const char* robustness2_extension_name = loaded_extensions.contains(VK_KHR_ROBUSTNESS_2_EXTENSION_NAME) ? VK_KHR_ROBUSTNESS_2_EXTENSION_NAME : VK_EXT_ROBUSTNESS_2_EXTENSION_NAME; RemoveExtensionFeatureIfUnsuitable(extensions.robustness_2, features.robustness2, robustness2_extension_name); // Image robustness extensions.robust_image_access = features.robust_image_access.robustImageAccess; RemoveExtensionFeatureIfUnsuitable(extensions.robust_image_access, features.robust_image_access, VK_EXT_IMAGE_ROBUSTNESS_EXTENSION_NAME); // VK_KHR_shader_atomic_int64 extensions.shader_atomic_int64 = features.shader_atomic_int64.shaderBufferInt64Atomics && features.shader_atomic_int64.shaderSharedInt64Atomics; RemoveExtensionFeatureIfUnsuitable(extensions.shader_atomic_int64, features.shader_atomic_int64, VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); // VK_EXT_shader_demote_to_helper_invocation extensions.shader_demote_to_helper_invocation = features.shader_demote_to_helper_invocation.shaderDemoteToHelperInvocation; RemoveExtensionFeatureIfUnsuitable(extensions.shader_demote_to_helper_invocation, features.shader_demote_to_helper_invocation, VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME); // VK_EXT_subgroup_size_control extensions.subgroup_size_control = features.subgroup_size_control.subgroupSizeControl && properties.subgroup_size_control.minSubgroupSize <= GuestWarpSize && properties.subgroup_size_control.maxSubgroupSize >= GuestWarpSize; RemoveExtensionFeatureIfUnsuitable(extensions.subgroup_size_control, features.subgroup_size_control, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME); // VK_EXT_transform_feedback extensions.transform_feedback = features.transform_feedback.transformFeedback && properties.transform_feedback.maxTransformFeedbackBuffers > 0; RemoveExtensionFeatureIfUnsuitable(extensions.transform_feedback, features.transform_feedback, VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME); // VK_EXT_vertex_input_dynamic_state extensions.vertex_input_dynamic_state = features.vertex_input_dynamic_state.vertexInputDynamicState; RemoveExtensionFeatureIfUnsuitable(extensions.vertex_input_dynamic_state, features.vertex_input_dynamic_state, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); // VK_KHR_pipeline_executable_properties if (Settings::values.renderer_shader_feedback.GetValue()) { extensions.pipeline_executable_properties = features.pipeline_executable_properties.pipelineExecutableInfo; RemoveExtensionFeatureIfUnsuitable(extensions.pipeline_executable_properties, features.pipeline_executable_properties, VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); } else { RemoveExtensionFeature(extensions.pipeline_executable_properties, features.pipeline_executable_properties, VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); } // VK_EXT_mesh_shader: Switch 2 titles draw some geometry with task and mesh programs. Only // the two stages are used; the multiview, shading rate and query features need more. features.mesh_shader.multiviewMeshShader = VK_FALSE; features.mesh_shader.primitiveFragmentShadingRateMeshShader = VK_FALSE; features.mesh_shader.meshShaderQueries = VK_FALSE; extensions.mesh_shader = features.mesh_shader.taskShader && features.mesh_shader.meshShader; RemoveExtensionFeatureIfUnsuitable(extensions.mesh_shader, features.mesh_shader, VK_EXT_MESH_SHADER_EXTENSION_NAME); // VK_KHR_shader_quad_control extensions.shader_quad_control = features.shader_quad_control.shaderQuadControl; RemoveExtensionFeatureIfUnsuitable(extensions.shader_quad_control, features.shader_quad_control, VK_KHR_SHADER_QUAD_CONTROL_EXTENSION_NAME); // VK_KHR_workgroup_memory_explicit_layout extensions.workgroup_memory_explicit_layout = features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout && features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayoutScalarBlockLayout; RemoveExtensionFeatureIfUnsuitable(extensions.workgroup_memory_explicit_layout, features.workgroup_memory_explicit_layout, VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME); // VK_KHR_maintenance1 extensions.maintenance1 = loaded_extensions.contains(VK_KHR_MAINTENANCE_1_EXTENSION_NAME); RemoveExtensionIfUnsuitable(extensions.maintenance1, VK_KHR_MAINTENANCE_1_EXTENSION_NAME); // VK_KHR_maintenance2 extensions.maintenance2 = loaded_extensions.contains(VK_KHR_MAINTENANCE_2_EXTENSION_NAME); RemoveExtensionIfUnsuitable(extensions.maintenance2, VK_KHR_MAINTENANCE_2_EXTENSION_NAME); // VK_KHR_maintenance3 extensions.maintenance3 = loaded_extensions.contains(VK_KHR_MAINTENANCE_3_EXTENSION_NAME); RemoveExtensionIfUnsuitable(extensions.maintenance3, VK_KHR_MAINTENANCE_3_EXTENSION_NAME); // VK_KHR_maintenance4 extensions.maintenance4 = features.maintenance4.maintenance4; RemoveExtensionFeatureIfUnsuitable(extensions.maintenance4, features.maintenance4, VK_KHR_MAINTENANCE_4_EXTENSION_NAME); // VK_KHR_maintenance5 extensions.maintenance5 = features.maintenance5.maintenance5; RemoveExtensionFeatureIfUnsuitable(extensions.maintenance5, features.maintenance5, VK_KHR_MAINTENANCE_5_EXTENSION_NAME); // VK_KHR_maintenance6 extensions.maintenance6 = features.maintenance6.maintenance6; RemoveExtensionFeatureIfUnsuitable(extensions.maintenance6, features.maintenance6, VK_KHR_MAINTENANCE_6_EXTENSION_NAME); // VK_KHR_maintenance7 extensions.maintenance7 = loaded_extensions.contains(VK_KHR_MAINTENANCE_7_EXTENSION_NAME); RemoveExtensionIfUnsuitable(extensions.maintenance7, VK_KHR_MAINTENANCE_7_EXTENSION_NAME); // VK_KHR_maintenance8 extensions.maintenance8 = loaded_extensions.contains(VK_KHR_MAINTENANCE_8_EXTENSION_NAME); RemoveExtensionIfUnsuitable(extensions.maintenance8, VK_KHR_MAINTENANCE_8_EXTENSION_NAME); // VK_KHR_synchronization2 extensions.synchronization2 = features.synchronization2.synchronization2; RemoveExtensionFeatureIfUnsuitable(extensions.synchronization2, features.synchronization2, VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); } void Device::SetupFamilies(VkSurfaceKHR surface) { const std::vector queue_family_properties = physical.GetQueueFamilyProperties(); std::optional graphics; std::optional present; for (u32 index = 0; index < static_cast(queue_family_properties.size()); ++index) { if (graphics && (present || !surface)) { break; } const VkQueueFamilyProperties& queue_family = queue_family_properties[index]; if (queue_family.queueCount == 0) { continue; } if (queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) { graphics = index; } if (surface && physical.GetSurfaceSupportKHR(index, surface)) { present = index; } } if (!graphics) { LOG_ERROR(Render_Vulkan, "Device lacks a graphics queue"); throw vk::Exception(VK_ERROR_FEATURE_NOT_PRESENT); } if (surface && !present) { LOG_ERROR(Render_Vulkan, "Device lacks a present queue"); throw vk::Exception(VK_ERROR_FEATURE_NOT_PRESENT); } if (graphics) { graphics_family = *graphics; graphics_family_sparse_binding = (queue_family_properties[*graphics].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0; } if (present) { present_family = *present; } } bool Device::TryReserveCustomBorderColorSamplers(size_t count) const { const size_t limit = properties.custom_border_color.maxCustomBorderColorSamplers; if (limit == 0) { return true; } size_t used = custom_border_color_samplers_used.load(std::memory_order_relaxed); while (used + count <= limit) { if (custom_border_color_samplers_used.compare_exchange_weak( used, used + count, std::memory_order_relaxed, std::memory_order_relaxed)) { return true; } } return false; } void Device::ReleaseCustomBorderColorSamplers(size_t count) const { if (count == 0) { return; } custom_border_color_samplers_used.fetch_sub(count, std::memory_order_relaxed); } u64 Device::GetDeviceMemoryUsage() const { VkPhysicalDeviceMemoryBudgetPropertiesEXT budget{}; budget.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT; physical.GetMemoryProperties(&budget); std::array heap_budgets{}; if (allocator != nullptr) { vmaGetHeapBudgets(allocator, heap_budgets.data()); } u64 result{}; for (const size_t heap : valid_heap_memory) { result += AllocatedUsage(budget.heapUsage[heap], heap_budgets[heap]); } return result; } u64 Device::GetDeviceMemorySlack() const { if (allocator == nullptr) { return 0; } std::array heap_budgets{}; vmaGetHeapBudgets(allocator, heap_budgets.data()); const VmaStatistics& statistics = heap_budgets[main_heap].statistics; return statistics.blockBytes - (std::min)(statistics.blockBytes, statistics.allocationBytes); } u64 Device::GetDeviceMemoryBudget() const { if (!extensions.memory_budget) { return 0; } VkPhysicalDeviceMemoryBudgetPropertiesEXT budget{}; budget.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT; physical.GetMemoryProperties(&budget); std::array heap_budgets{}; if (allocator != nullptr) { vmaGetHeapBudgets(allocator, heap_budgets.data()); } // Only the main heap's budget: summing every heap's counted VRAM twice on AMD (an 8 GB card // reported 8.5 GB) and kept the collector's thresholds above the heap the images fill. What // the other heaps use is added the way GetDeviceMemoryUsage() counts it, so the usage reaches // this when the main heap's does its budget. u64 result = budget.heapBudget[main_heap]; if (heap_size_limit != 0) { result = (std::min)(result, heap_size_limit); } for (const size_t heap : valid_heap_memory) { if (heap != main_heap) { result += AllocatedUsage(budget.heapUsage[heap], heap_budgets[heap]); } } if (is_integrated) { // An integrated GPU's device-local heap is system memory (a Radeon 780M reported a 21 GB // budget): following it let the texture cache fill that much RAM before collecting, and // kept ASTC uncompressed. Its budget is at most the share CollectPhysicalMemoryInfo // gives it (4 GiB, 6 GiB with aggressive VRAM use). result = (std::min)(result, device_access_memory); } return result; } void Device::CollectPhysicalMemoryInfo() { // Calculate limits using memory budget VkPhysicalDeviceMemoryBudgetPropertiesEXT budget{}; budget.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT; const auto mem_info = physical.GetMemoryProperties(extensions.memory_budget ? &budget : nullptr); const auto& mem_properties = mem_info.memoryProperties; const size_t num_properties = mem_properties.memoryHeapCount; device_access_memory = 0; u64 device_initial_usage = 0; u64 local_memory = 0; // EDEN_NX2_VRAM_LIMIT_MIB=: treat every device-local heap as n MiB (small card tests). if (const char* value = std::getenv("EDEN_NX2_VRAM_LIMIT_MIB")) { heap_size_limit = std::strtoull(value, nullptr, 10) << 20; if (heap_size_limit != 0) { LOG_INFO(Render_Vulkan, "EDEN_NX2_VRAM_LIMIT_MIB: device-local heaps limited to {} MiB", heap_size_limit >> 20); } else { LOG_WARNING(Render_Vulkan, "EDEN_NX2_VRAM_LIMIT_MIB={} is not a size in MiB, ignored", value); } } const auto limit = [this](u64 size) { return heap_size_limit != 0 ? (std::min)(size, heap_size_limit) : size; }; for (size_t element = 0; element < num_properties; ++element) { const bool is_heap_local = (mem_properties.memoryHeaps[element].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0; if (!is_integrated && !is_heap_local) { continue; } valid_heap_memory.push_back(element); const VkMemoryHeap& main = mem_properties.memoryHeaps[main_heap]; u64 heap_size = mem_properties.memoryHeaps[element].size; if (is_heap_local) { if ((main.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) == 0 || heap_size > main.size) { main_heap = element; } heap_size = limit(heap_size); local_memory += heap_size; } if (extensions.memory_budget) { device_initial_usage += budget.heapUsage[element]; device_access_memory += is_heap_local ? limit(budget.heapBudget[element]) : budget.heapBudget[element]; continue; } device_access_memory += heap_size; } if (is_integrated) { const s64 available_memory = static_cast(device_access_memory - device_initial_usage); const u64 memory_size = Settings::values.vram_usage_mode.GetValue() == Settings::VramUsageMode::Aggressive ? 6_GiB : 4_GiB; device_access_memory = static_cast(std::max(std::min(available_memory - 8_GiB, memory_size), std::min(local_memory, memory_size))); } else { const u64 reserve_memory = std::min(device_access_memory / 8, 1_GiB); device_access_memory -= reserve_memory; if (Settings::values.vram_usage_mode.GetValue() != Settings::VramUsageMode::Aggressive) { // Account for resolution scaling in memory limits const size_t normal_memory = 6_GiB; const size_t scaler_memory = 1_GiB * Settings::values.resolution_info.ScaleUp(1); device_access_memory = std::min(device_access_memory, normal_memory + scaler_memory); } } } void Device::CollectToolingInfo() { if (!extensions.tooling_info) { return; } auto tools{physical.GetPhysicalDeviceToolProperties()}; for (const VkPhysicalDeviceToolProperties& tool : tools) { const std::string_view name = tool.name; LOG_INFO(Render_Vulkan, "Attached debugging tool: {}", name); has_renderdoc = has_renderdoc || name == "RenderDoc"; has_nsight_graphics = has_nsight_graphics || name == "NVIDIA Nsight Graphics"; has_radeon_gpu_profiler = has_radeon_gpu_profiler || name == "Radeon GPU Profiler"; } #ifdef _WIN32 if (has_renderdoc) { LOG_INFO(Render_Vulkan, "Windows default RenderDoc output folder: %LOCALAPPDATA%\\Temp\\RenderDoc"); } #endif } std::vector Device::GetDeviceQueueCreateInfos() const { static constexpr float QUEUE_PRIORITY = 1.0f; ::Common::unordered_set unique_queue_families{graphics_family, present_family}; std::vector queue_cis; queue_cis.reserve(unique_queue_families.size()); for (const u32 queue_family : unique_queue_families) { auto& ci = queue_cis.emplace_back(VkDeviceQueueCreateInfo{ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .pNext = nullptr, .flags = 0, .queueFamilyIndex = queue_family, .queueCount = 1, .pQueuePriorities = nullptr, }); ci.pQueuePriorities = &QUEUE_PRIORITY; } return queue_cis; } void Device::InitializeGPULogging() { // Get log level from settings - Off is the disable. const auto log_level = static_cast( static_cast(Settings::values.gpu_log_level.GetValue())); if (log_level == GPU::Logging::LogLevel::Off) { return; } // Detect driver type const auto driver_id = GetDriverID(); GPU::Logging::DriverType detected_driver = GPU::Logging::DriverType::Unknown; if (driver_id == VK_DRIVER_ID_MESA_TURNIP) { detected_driver = GPU::Logging::DriverType::Turnip; } else if (driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) { detected_driver = GPU::Logging::DriverType::Qualcomm; } // Initialize GPU logger GPU::Logging::GPULogger::GetInstance().Initialize(log_level, detected_driver); // Configure feature flags GPU::Logging::GPULogger::GetInstance().EnableVulkanCallTracking( Settings::values.gpu_log_vulkan_calls.GetValue()); GPU::Logging::GPULogger::GetInstance().EnableMemoryTracking( Settings::values.gpu_log_memory_tracking.GetValue()); GPU::Logging::GPULogger::GetInstance().EnableDriverDebugInfo( Settings::values.gpu_log_driver_debug.GetValue()); GPU::Logging::GPULogger::GetInstance().SetRingBufferSize( Settings::values.gpu_log_ring_buffer_size.GetValue()); // Log comprehensive driver and extension information if (Settings::values.gpu_log_driver_debug.GetValue()) { std::string driver_info; // Device information const auto& props = properties.properties; driver_info += fmt::format("Device: {}\n", props.deviceName); driver_info += fmt::format("Driver Name: {}\n", properties.driver.driverName); driver_info += fmt::format("Driver Info: {}\n", properties.driver.driverInfo); // Version information const u32 driver_version = props.driverVersion; const u32 api_version = props.apiVersion; driver_info += fmt::format("Driver Version: {}.{}.{}\n", VK_API_VERSION_MAJOR(driver_version), VK_API_VERSION_MINOR(driver_version), VK_API_VERSION_PATCH(driver_version)); driver_info += fmt::format("Vulkan API Version: {}.{}.{}\n", VK_API_VERSION_MAJOR(api_version), VK_API_VERSION_MINOR(api_version), VK_API_VERSION_PATCH(api_version)); driver_info += fmt::format("Driver ID: {}\n", static_cast(driver_id)); // Vendor and device IDs driver_info += fmt::format("Vendor ID: {:#04x}\n", props.vendorID); driver_info += fmt::format("Device ID: {:#04x}\n", props.deviceID); // Extensions - separate QCOM extensions from others driver_info += "\n=== Loaded Vulkan Extensions ===\n"; std::vector qcom_exts; std::vector other_exts; for (const auto& ext : loaded_extensions) { if (ext.find("QCOM") != std::string::npos || ext.find("qcom") != std::string::npos) { qcom_exts.push_back(ext); } else { other_exts.push_back(ext); } } // Log QCOM extensions first if (!qcom_exts.empty()) { driver_info += "\nQualcomm Proprietary Extensions:\n"; for (const auto& ext : qcom_exts) { driver_info += fmt::format(" - {}\n", ext); } } // Log other extensions if (!other_exts.empty()) { driver_info += "\nStandard Extensions:\n"; for (const auto& ext : other_exts) { driver_info += fmt::format(" - {}\n", ext); } } driver_info += fmt::format("\nTotal Extensions Loaded: {}\n", loaded_extensions.size()); GPU::Logging::GPULogger::GetInstance().LogDriverDebugInfo(driver_info); } } void Device::ShutdownGPULogging() { if (GPU::Logging::GPULogger::GetInstance().IsInitialized()) { GPU::Logging::GPULogger::GetInstance().Shutdown(); } } } // namespace Vulkan