/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "stdafx.h" #include "rendering/vulkan14/vulkan14capabilities.h" #include #include namespace vk14 { namespace { // one VkBool32 in the chain; without captures, so it turns into a plain function pointer and the table stays constant #define VK14_FIELD(member, field) [](feature_chain &chain) -> VkBool32 & { return chain.member.field; } #define VK14_CORE(field) VK14_FIELD(features2.features, field) // columns: structure, name, required, guaranteed by vulkan 1.4, purpose. // // beyond what vulkan 1.4 requires of every device the renderer needs multiDrawIndirect, shaderDrawParameters, // textureCompressionBC and geometryShader, so a gpu without one of them is turned down by its name rather than failing later. // "guaranteed" follows the minimum requirements of vulkan 1.2-1.4 (VP_LUNARG_minimum_requirements_1_4 of the vulkan sdk) std::vector const g_feature_table = { // VkPhysicalDeviceFeatures (1.0) {"VkPhysicalDeviceFeatures", "geometryShader", true, false, "map icons and cube shadow maps of the lamps until multiview takes them over", VK14_CORE(geometryShader)}, {"VkPhysicalDeviceFeatures", "multiDrawIndirect", true, false, "multi draw indirect of the gpu culling", VK14_CORE(multiDrawIndirect)}, {"VkPhysicalDeviceFeatures", "textureCompressionBC", true, false, "bc1-bc7 compressed dds textures", VK14_CORE(textureCompressionBC)}, {"VkPhysicalDeviceFeatures", "drawIndirectFirstInstance", true, true, "instanced indirect draws", VK14_CORE(drawIndirectFirstInstance)}, {"VkPhysicalDeviceFeatures", "fullDrawIndexUint32", true, true, "32 bit index buffers", VK14_CORE(fullDrawIndexUint32)}, {"VkPhysicalDeviceFeatures", "imageCubeArray", true, true, "environment map and cube shadow maps", VK14_CORE(imageCubeArray)}, {"VkPhysicalDeviceFeatures", "independentBlend", true, true, "blending per attachment", VK14_CORE(independentBlend)}, {"VkPhysicalDeviceFeatures", "sampleRateShading", true, true, "multisampling", VK14_CORE(sampleRateShading)}, {"VkPhysicalDeviceFeatures", "depthClamp", true, true, "shadow maps", VK14_CORE(depthClamp)}, {"VkPhysicalDeviceFeatures", "depthBiasClamp", true, true, "shadow maps", VK14_CORE(depthBiasClamp)}, {"VkPhysicalDeviceFeatures", "samplerAnisotropy", true, true, "anisotropic texture filtering", VK14_CORE(samplerAnisotropy)}, {"VkPhysicalDeviceFeatures", "largePoints", true, true, "points larger than a pixel", VK14_CORE(largePoints)}, {"VkPhysicalDeviceFeatures", "fragmentStoresAndAtomics", true, true, "clustered lights and post-processing", VK14_CORE(fragmentStoresAndAtomics)}, {"VkPhysicalDeviceFeatures", "shaderImageGatherExtended", true, true, "shadow filtering", VK14_CORE(shaderImageGatherExtended)}, {"VkPhysicalDeviceFeatures", "shaderStorageImageExtendedFormats", true, true, "compute passes", VK14_CORE(shaderStorageImageExtendedFormats)}, {"VkPhysicalDeviceFeatures", "fillModeNonSolid", false, false, "wireframe", VK14_CORE(fillModeNonSolid)}, {"VkPhysicalDeviceFeatures", "pipelineStatisticsQuery", false, false, "draw statistics", VK14_CORE(pipelineStatisticsQuery)}, // VkPhysicalDeviceVulkan11Features {"VkPhysicalDeviceVulkan11Features", "shaderDrawParameters", true, false, "gl_DrawID and gl_BaseInstance of multi draws", VK14_FIELD(v11, shaderDrawParameters)}, {"VkPhysicalDeviceVulkan11Features", "multiview", true, true, "cube shadow maps of the lamps in one pass", VK14_FIELD(v11, multiview)}, // VkPhysicalDeviceVulkan12Features {"VkPhysicalDeviceVulkan12Features", "timelineSemaphore", true, true, "synchronization of the frames", VK14_FIELD(v12, timelineSemaphore)}, {"VkPhysicalDeviceVulkan12Features", "scalarBlockLayout", true, true, "buffer layouts shared with the c++ code", VK14_FIELD(v12, scalarBlockLayout)}, {"VkPhysicalDeviceVulkan12Features", "uniformBufferStandardLayout", true, true, "std430 uniform buffers", VK14_FIELD(v12, uniformBufferStandardLayout)}, {"VkPhysicalDeviceVulkan12Features", "hostQueryReset", true, true, "gpu times of the passes", VK14_FIELD(v12, hostQueryReset)}, {"VkPhysicalDeviceVulkan12Features", "separateDepthStencilLayouts", true, true, "depth read while the stencil is written", VK14_FIELD(v12, separateDepthStencilLayouts)}, {"VkPhysicalDeviceVulkan12Features", "drawIndirectCount", false, false, "draw counts made on the gpu", VK14_FIELD(v12, drawIndirectCount)}, {"VkPhysicalDeviceVulkan12Features", "samplerFilterMinmax", false, false, "min/max reduction of the depth pyramid", VK14_FIELD(v12, samplerFilterMinmax)}, {"VkPhysicalDeviceVulkan12Features", "shaderOutputLayer", false, false, "layered rendering without a geometry shader", VK14_FIELD(v12, shaderOutputLayer)}, // VkPhysicalDeviceVulkan13Features {"VkPhysicalDeviceVulkan13Features", "dynamicRendering", true, true, "passes without VkRenderPass and VkFramebuffer", VK14_FIELD(v13, dynamicRendering)}, {"VkPhysicalDeviceVulkan13Features", "synchronization2", true, true, "barriers and submits", VK14_FIELD(v13, synchronization2)}, {"VkPhysicalDeviceVulkan13Features", "maintenance4", true, true, "pipeline layouts and buffer sizes", VK14_FIELD(v13, maintenance4)}, {"VkPhysicalDeviceVulkan13Features", "shaderDemoteToHelperInvocation", true, true, "alpha tested geometry", VK14_FIELD(v13, shaderDemoteToHelperInvocation)}, {"VkPhysicalDeviceVulkan13Features", "pipelineCreationCacheControl", true, true, "pipelines made on worker threads", VK14_FIELD(v13, pipelineCreationCacheControl)}, // VkPhysicalDeviceVulkan14Features {"VkPhysicalDeviceVulkan14Features", "pushDescriptor", true, true, "descriptors of materials and compute passes without pools", VK14_FIELD(v14, pushDescriptor)}, {"VkPhysicalDeviceVulkan14Features", "maintenance5", true, true, "pipelines without shader modules", VK14_FIELD(v14, maintenance5)}, {"VkPhysicalDeviceVulkan14Features", "maintenance6", true, true, "descriptor and push constant calls of 1.4", VK14_FIELD(v14, maintenance6)}, {"VkPhysicalDeviceVulkan14Features", "hostImageCopy", false, false, "texture uploads without command buffers", VK14_FIELD(v14, hostImageCopy)}, }; #undef VK14_CORE #undef VK14_FIELD } // namespace char const *device_type_string(VkPhysicalDeviceType Type) { switch (Type) { case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: return "integrated gpu"; case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: return "discrete gpu"; case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: return "virtual gpu"; case VK_PHYSICAL_DEVICE_TYPE_CPU: return "cpu"; default: break; } return "other"; } std::string vendor_string(std::uint32_t VendorId) { switch (VendorId) { case 0x1002: return "AMD"; case 0x106B: return "Apple"; case 0x10DE: return "NVIDIA"; case 0x13B5: return "ARM"; case 0x1414: return "Microsoft"; case 0x5143: return "Qualcomm"; case 0x8086: return "Intel"; case 0x10005: return "Mesa"; default: break; } char buffer[16] = {}; std::snprintf(buffer, sizeof(buffer), "0x%04X", VendorId); return std::string(buffer); } feature_chain::feature_chain() { reset(); } void feature_chain::reset() { features2 = {}; features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; v11 = {}; v11.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; v12 = {}; v12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; v13 = {}; v13.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; v14 = {}; v14.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_FEATURES; features2.pNext = &v11; v11.pNext = &v12; v12.pNext = &v13; v13.pNext = &v14; v14.pNext = nullptr; } std::vector const &feature_table() { return g_feature_table; } void query_capabilities(VkPhysicalDevice Physical, feature_chain &Queried, capabilities &Caps) { Caps = capabilities(); // extensions std::uint32_t extensioncount{0}; std::vector extensions; if (VK14_VERIFY(vkEnumerateDeviceExtensionProperties(Physical, nullptr, &extensioncount, nullptr)) && (extensioncount > 0)) { extensions.resize(extensioncount); if (!VK14_VERIFY(vkEnumerateDeviceExtensionProperties(Physical, nullptr, &extensioncount, extensions.data()))) { extensions.clear(); } } auto const hasextension = [&extensions](char const *Name) { return std::any_of(extensions.begin(), extensions.end(), [Name](VkExtensionProperties const &Entry) { return std::strcmp(Entry.extensionName, Name) == 0; }); }; Caps.ext_swapchain = hasextension(VK_KHR_SWAPCHAIN_EXTENSION_NAME); Caps.ext_memory_budget = hasextension(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME); Caps.ext_swapchain_maintenance1 = hasextension(VK_KHR_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME) || hasextension(VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME); Caps.ext_extended_dynamic_state3 = hasextension(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME); // properties VkPhysicalDeviceVulkan12Properties properties12{}; properties12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES; VkPhysicalDeviceProperties2 properties{}; properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; properties.pNext = &properties12; vkGetPhysicalDeviceProperties2(Physical, &properties); Caps.device_name = properties.properties.deviceName; Caps.device_type = properties.properties.deviceType; Caps.api_version = properties.properties.apiVersion; Caps.driver_version = properties.properties.driverVersion; Caps.vendor_id = properties.properties.vendorID; Caps.device_id = properties.properties.deviceID; Caps.driver_name = properties12.driverName; Caps.driver_info = properties12.driverInfo; Caps.timestamp_period = properties.properties.limits.timestampPeriod; Caps.limits = properties.properties.limits; std::copy(std::begin(properties.properties.pipelineCacheUUID), std::end(properties.properties.pipelineCacheUUID), Caps.pipeline_cache_uuid.begin()); VkPhysicalDeviceMemoryProperties memory{}; vkGetPhysicalDeviceMemoryProperties(Physical, &memory); for (std::uint32_t i = 0; i < memory.memoryHeapCount; ++i) { if ((memory.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0) { Caps.device_local_memory += memory.memoryHeaps[i].size; } } // the version first: the structures of 1.4 mean nothing to an older driver if (Caps.api_version < VK_API_VERSION_1_4) { Caps.missing.push_back("the driver gives vulkan " + version_string(Caps.api_version) + ", the renderer needs 1.4"); return; } // the properties of 1.4, asked for once the driver is known to have them { VkPhysicalDeviceVulkan14Properties properties14{}; properties14.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_PROPERTIES; VkPhysicalDeviceProperties2 properties2{}; properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; properties2.pNext = &properties14; vkGetPhysicalDeviceProperties2(Physical, &properties2); Caps.max_push_descriptors = properties14.maxPushDescriptors; // the layouts the host can fill images in (hostImageCopy): the textures are filled straight to the one they're sampled in std::vector copylayouts(properties14.copyDstLayoutCount); if (false == copylayouts.empty()) { properties14.pCopyDstLayouts = copylayouts.data(); properties14.copySrcLayoutCount = 0; properties14.pCopySrcLayouts = nullptr; vkGetPhysicalDeviceProperties2(Physical, &properties2); copylayouts.resize(properties14.copyDstLayoutCount); Caps.host_copy_sampled = (std::find(copylayouts.begin(), copylayouts.end(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) != copylayouts.end()); } } // features Queried.reset(); vkGetPhysicalDeviceFeatures2(Physical, Queried.head()); Caps.draw_indirect_count = (Queried.v12.drawIndirectCount == VK_TRUE); Caps.host_image_copy = (Queried.v14.hostImageCopy == VK_TRUE); Caps.fill_mode_non_solid = (Queried.features2.features.fillModeNonSolid == VK_TRUE); Caps.pipeline_statistics_query = (Queried.features2.features.pipelineStatisticsQuery == VK_TRUE); Caps.sampler_filter_minmax = (Queried.v12.samplerFilterMinmax == VK_TRUE); Caps.shader_output_layer = (Queried.v12.shaderOutputLayer == VK_TRUE); // the ray queries: the structures of the extensions are asked about only where the device has the extensions if (hasextension(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME) && hasextension(VK_KHR_RAY_QUERY_EXTENSION_NAME) && hasextension(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME)) { VkPhysicalDeviceAccelerationStructureFeaturesKHR acceleration{}; acceleration.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR; VkPhysicalDeviceRayQueryFeaturesKHR rayquery{}; rayquery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR; acceleration.pNext = &rayquery; VkPhysicalDeviceFeatures2 features{}; features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; features.pNext = &acceleration; vkGetPhysicalDeviceFeatures2(Physical, &features); VkPhysicalDeviceAccelerationStructurePropertiesKHR accelerationproperties{}; accelerationproperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_PROPERTIES_KHR; VkPhysicalDeviceVulkan12Properties indexing{}; indexing.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES; indexing.pNext = &accelerationproperties; VkPhysicalDeviceProperties2 properties2{}; properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; properties2.pNext = &indexing; vkGetPhysicalDeviceProperties2(Physical, &properties2); Caps.scratch_alignment = std::max(accelerationproperties.minAccelerationStructureScratchOffsetAlignment, 1); // the textures of the scenery the reflections are traced through, beside the other bindings of the stage // (combined image samplers: they count as samplers too) auto const textures{std::min({Caps.limits.maxPerStageDescriptorSampledImages, Caps.limits.maxDescriptorSetSampledImages, Caps.limits.maxPerStageDescriptorSamplers, Caps.limits.maxDescriptorSetSamplers, Caps.limits.maxPerStageResources})}; Caps.max_indexed_textures = std::min(textures > 64 ? textures - 64 : 0, 65536); Caps.ray_query = (acceleration.accelerationStructure == VK_TRUE) && (rayquery.rayQuery == VK_TRUE) && (Queried.v12.bufferDeviceAddress == VK_TRUE) && (Queried.v12.runtimeDescriptorArray == VK_TRUE) && (Queried.v12.descriptorBindingPartiallyBound == VK_TRUE) && (Queried.v12.descriptorBindingVariableDescriptorCount == VK_TRUE) && (Queried.v12.shaderSampledImageArrayNonUniformIndexing == VK_TRUE) && (Caps.max_indexed_textures >= 256); } // can the renderer run on it if (!Caps.ext_swapchain) { Caps.missing.push_back(std::string(VK_KHR_SWAPCHAIN_EXTENSION_NAME) + " isn't supported, the device can't show images in a window"); } if (properties.properties.limits.timestampComputeAndGraphics == VK_FALSE) { Caps.missing.push_back("timestampComputeAndGraphics isn't supported (needed for the gpu times of the passes)"); } for (auto const &entry : g_feature_table) { if ((false == entry.required) || (entry.field(Queried) == VK_TRUE)) { continue; } auto message{std::string(entry.structure) + "::" + entry.name + " isn't supported (needed for " + entry.purpose + ")"}; if (entry.guaranteed) { message += "; vulkan 1.4 requires it, the driver doesn't conform"; } Caps.missing.push_back(message); } } void build_enabled_features(feature_chain &Queried, feature_chain &Enabled) { Enabled.reset(); for (auto const &entry : g_feature_table) { if (entry.field(Queried) == VK_TRUE) { entry.field(Enabled) = VK_TRUE; } } } std::string capability_report(capabilities const &Caps) { std::string report{"gpu capabilities\n"}; auto const line = [&report](std::string const &Text) { report += " " + Text + "\n"; }; auto const flag = [](bool const Value) { return Value ? "yes" : "no"; }; line("device " + Caps.device_name + " (" + vendor_string(Caps.vendor_id) + ", " + device_type_string(Caps.device_type) + ")"); line("api " + version_string(Caps.api_version)); line("driver " + Caps.driver_name + " " + Caps.driver_info); line("device memory " + memory_size_string(Caps.device_local_memory)); line(std::string("draw indirect count ") + flag(Caps.draw_indirect_count)); line(std::string("host image copy ") + flag(Caps.host_image_copy) + (Caps.host_image_copy ? (Caps.host_copy_sampled ? " (to the sampled layout)" : " (not to the sampled layout)") : "")); line(std::string("extended dynamic state3 ") + flag(Caps.ext_extended_dynamic_state3)); line(std::string("memory budget ") + flag(Caps.ext_memory_budget)); line(std::string("swapchain maintenance1 ") + flag(Caps.ext_swapchain_maintenance1)); line(std::string("sampler filter minmax ") + flag(Caps.sampler_filter_minmax)); line(std::string("shader output layer ") + flag(Caps.shader_output_layer)); line(std::string("fill mode non solid ") + flag(Caps.fill_mode_non_solid)); line(std::string("pipeline statistics ") + flag(Caps.pipeline_statistics_query)); line(std::string("ray queries ") + flag(Caps.ray_query)); return report; } } // namespace vk14