551 lines
37 KiB
C++
551 lines
37 KiB
C++
|
|
//SPDX-License-Identifier: LGPL-3.0-only
|
||
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||
|
|
|
||
|
|
// The parts of device bring-up that are pure logic, kept apart from the
|
||
|
|
// Vulkan calls so tests/DeviceFeatures can drive them without a GPU: which
|
||
|
|
// features to enable, which GPU to use, and whether a SPIR-V module only
|
||
|
|
// declares capabilities the device was created for.
|
||
|
|
|
||
|
|
module;
|
||
|
|
|
||
|
|
#include <stddef.h>
|
||
|
|
#include "vulkan/vulkan.h"
|
||
|
|
#include "vulkan/vk_enum_string_helper.h"
|
||
|
|
|
||
|
|
module Crafter.Graphics:Device_features_impl;
|
||
|
|
import :Device;
|
||
|
|
import std;
|
||
|
|
using namespace Crafter;
|
||
|
|
|
||
|
|
std::string DeviceDetail::Join(std::span<const std::string> parts, std::string_view separator) {
|
||
|
|
std::string joined;
|
||
|
|
for (const std::string& part : parts) {
|
||
|
|
if (!joined.empty()) {
|
||
|
|
joined += separator;
|
||
|
|
}
|
||
|
|
joined += part;
|
||
|
|
}
|
||
|
|
return joined;
|
||
|
|
}
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
// One VkBool32 of DeviceFeatures: where it is, and the name an
|
||
|
|
// application writes it by.
|
||
|
|
struct FeatureField {
|
||
|
|
std::size_t offset;
|
||
|
|
std::string_view name;
|
||
|
|
};
|
||
|
|
|
||
|
|
#define CRAFTER_FIELD_OF(version, type, field) \
|
||
|
|
FeatureField { offsetof(DeviceFeatures, version) + offsetof(type, field), #version "." #field }
|
||
|
|
|
||
|
|
// Every VkBool32 member of the five core feature structs, in declaration
|
||
|
|
// order. Generated once from vk.xml: a core feature struct never changes
|
||
|
|
// after its version ships.
|
||
|
|
#define CRAFTER_DEVICE_FEATURES(X) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, robustBufferAccess) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, fullDrawIndexUint32) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, imageCubeArray) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, independentBlend) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, geometryShader) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, tessellationShader) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, sampleRateShading) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, dualSrcBlend) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, logicOp) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, multiDrawIndirect) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, drawIndirectFirstInstance) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, depthClamp) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, depthBiasClamp) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, fillModeNonSolid) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, depthBounds) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, wideLines) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, largePoints) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, alphaToOne) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, multiViewport) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, samplerAnisotropy) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, textureCompressionETC2) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, textureCompressionASTC_LDR) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, textureCompressionBC) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, occlusionQueryPrecise) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, pipelineStatisticsQuery) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, vertexPipelineStoresAndAtomics) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, fragmentStoresAndAtomics) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, shaderTessellationAndGeometryPointSize) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, shaderImageGatherExtended) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, shaderStorageImageExtendedFormats) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, shaderStorageImageMultisample) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, shaderStorageImageReadWithoutFormat) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, shaderStorageImageWriteWithoutFormat) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, shaderUniformBufferArrayDynamicIndexing) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, shaderSampledImageArrayDynamicIndexing) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, shaderStorageBufferArrayDynamicIndexing) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, shaderStorageImageArrayDynamicIndexing) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, shaderClipDistance) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, shaderCullDistance) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, shaderFloat64) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, shaderInt64) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, shaderInt16) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, shaderResourceResidency) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, shaderResourceMinLod) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, sparseBinding) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, sparseResidencyBuffer) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, sparseResidencyImage2D) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, sparseResidencyImage3D) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, sparseResidency2Samples) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, sparseResidency4Samples) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, sparseResidency8Samples) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, sparseResidency16Samples) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, sparseResidencyAliased) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, variableMultisampleRate) \
|
||
|
|
X(vulkan10, VkPhysicalDeviceFeatures, inheritedQueries) \
|
||
|
|
X(vulkan11, VkPhysicalDeviceVulkan11Features, storageBuffer16BitAccess) \
|
||
|
|
X(vulkan11, VkPhysicalDeviceVulkan11Features, uniformAndStorageBuffer16BitAccess) \
|
||
|
|
X(vulkan11, VkPhysicalDeviceVulkan11Features, storagePushConstant16) \
|
||
|
|
X(vulkan11, VkPhysicalDeviceVulkan11Features, storageInputOutput16) \
|
||
|
|
X(vulkan11, VkPhysicalDeviceVulkan11Features, multiview) \
|
||
|
|
X(vulkan11, VkPhysicalDeviceVulkan11Features, multiviewGeometryShader) \
|
||
|
|
X(vulkan11, VkPhysicalDeviceVulkan11Features, multiviewTessellationShader) \
|
||
|
|
X(vulkan11, VkPhysicalDeviceVulkan11Features, variablePointersStorageBuffer) \
|
||
|
|
X(vulkan11, VkPhysicalDeviceVulkan11Features, variablePointers) \
|
||
|
|
X(vulkan11, VkPhysicalDeviceVulkan11Features, protectedMemory) \
|
||
|
|
X(vulkan11, VkPhysicalDeviceVulkan11Features, samplerYcbcrConversion) \
|
||
|
|
X(vulkan11, VkPhysicalDeviceVulkan11Features, shaderDrawParameters) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, samplerMirrorClampToEdge) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, drawIndirectCount) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, storageBuffer8BitAccess) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, uniformAndStorageBuffer8BitAccess) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, storagePushConstant8) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, shaderBufferInt64Atomics) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, shaderSharedInt64Atomics) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, shaderFloat16) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, shaderInt8) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, descriptorIndexing) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, shaderInputAttachmentArrayDynamicIndexing) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, shaderUniformTexelBufferArrayDynamicIndexing) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, shaderStorageTexelBufferArrayDynamicIndexing) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, shaderUniformBufferArrayNonUniformIndexing) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, shaderSampledImageArrayNonUniformIndexing) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, shaderStorageBufferArrayNonUniformIndexing) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, shaderStorageImageArrayNonUniformIndexing) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, shaderInputAttachmentArrayNonUniformIndexing) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, shaderUniformTexelBufferArrayNonUniformIndexing) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, shaderStorageTexelBufferArrayNonUniformIndexing) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, descriptorBindingUniformBufferUpdateAfterBind) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, descriptorBindingSampledImageUpdateAfterBind) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, descriptorBindingStorageImageUpdateAfterBind) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, descriptorBindingStorageBufferUpdateAfterBind) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, descriptorBindingUniformTexelBufferUpdateAfterBind) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, descriptorBindingStorageTexelBufferUpdateAfterBind) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, descriptorBindingUpdateUnusedWhilePending) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, descriptorBindingPartiallyBound) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, descriptorBindingVariableDescriptorCount) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, runtimeDescriptorArray) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, samplerFilterMinmax) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, scalarBlockLayout) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, imagelessFramebuffer) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, uniformBufferStandardLayout) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, shaderSubgroupExtendedTypes) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, separateDepthStencilLayouts) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, hostQueryReset) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, timelineSemaphore) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, bufferDeviceAddress) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, bufferDeviceAddressCaptureReplay) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, bufferDeviceAddressMultiDevice) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, vulkanMemoryModel) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, vulkanMemoryModelDeviceScope) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, vulkanMemoryModelAvailabilityVisibilityChains) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, shaderOutputViewportIndex) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, shaderOutputLayer) \
|
||
|
|
X(vulkan12, VkPhysicalDeviceVulkan12Features, subgroupBroadcastDynamicId) \
|
||
|
|
X(vulkan13, VkPhysicalDeviceVulkan13Features, robustImageAccess) \
|
||
|
|
X(vulkan13, VkPhysicalDeviceVulkan13Features, inlineUniformBlock) \
|
||
|
|
X(vulkan13, VkPhysicalDeviceVulkan13Features, descriptorBindingInlineUniformBlockUpdateAfterBind) \
|
||
|
|
X(vulkan13, VkPhysicalDeviceVulkan13Features, pipelineCreationCacheControl) \
|
||
|
|
X(vulkan13, VkPhysicalDeviceVulkan13Features, privateData) \
|
||
|
|
X(vulkan13, VkPhysicalDeviceVulkan13Features, shaderDemoteToHelperInvocation) \
|
||
|
|
X(vulkan13, VkPhysicalDeviceVulkan13Features, shaderTerminateInvocation) \
|
||
|
|
X(vulkan13, VkPhysicalDeviceVulkan13Features, subgroupSizeControl) \
|
||
|
|
X(vulkan13, VkPhysicalDeviceVulkan13Features, computeFullSubgroups) \
|
||
|
|
X(vulkan13, VkPhysicalDeviceVulkan13Features, synchronization2) \
|
||
|
|
X(vulkan13, VkPhysicalDeviceVulkan13Features, textureCompressionASTC_HDR) \
|
||
|
|
X(vulkan13, VkPhysicalDeviceVulkan13Features, shaderZeroInitializeWorkgroupMemory) \
|
||
|
|
X(vulkan13, VkPhysicalDeviceVulkan13Features, dynamicRendering) \
|
||
|
|
X(vulkan13, VkPhysicalDeviceVulkan13Features, shaderIntegerDotProduct) \
|
||
|
|
X(vulkan13, VkPhysicalDeviceVulkan13Features, maintenance4) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, globalPriorityQuery) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, shaderSubgroupRotate) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, shaderSubgroupRotateClustered) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, shaderFloatControls2) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, shaderExpectAssume) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, rectangularLines) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, bresenhamLines) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, smoothLines) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, stippledRectangularLines) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, stippledBresenhamLines) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, stippledSmoothLines) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, vertexAttributeInstanceRateDivisor) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, vertexAttributeInstanceRateZeroDivisor) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, indexTypeUint8) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, dynamicRenderingLocalRead) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, maintenance5) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, maintenance6) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, pipelineProtectedAccess) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, pipelineRobustness) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, hostImageCopy) \
|
||
|
|
X(vulkan14, VkPhysicalDeviceVulkan14Features, pushDescriptor)
|
||
|
|
|
||
|
|
#define CRAFTER_FIELD(version, type, field) CRAFTER_FIELD_OF(version, type, field),
|
||
|
|
constexpr FeatureField featureFields[] = { CRAFTER_DEVICE_FEATURES(CRAFTER_FIELD) };
|
||
|
|
#undef CRAFTER_FIELD
|
||
|
|
#undef CRAFTER_DEVICE_FEATURES
|
||
|
|
|
||
|
|
// featureFields[begin, begin + count) are all of S's VkBool32 members, in
|
||
|
|
// order: one after another from S's first, nothing but padding after the
|
||
|
|
// last. With the counts below, a member missing from the list fails here.
|
||
|
|
template <typename S>
|
||
|
|
consteval bool Covers(std::size_t begin, std::size_t count, std::size_t structOffset) {
|
||
|
|
std::size_t header = 0; // VkPhysicalDeviceFeatures starts with a VkBool32
|
||
|
|
if constexpr (!std::is_same_v<S, VkPhysicalDeviceFeatures>) {
|
||
|
|
header = offsetof(S, pNext) + sizeof(void*);
|
||
|
|
}
|
||
|
|
for (std::size_t i = 0; i < count; ++i) {
|
||
|
|
if (featureFields[begin + i].offset != structOffset + header + i * sizeof(VkBool32)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return sizeof(S) - (header + count * sizeof(VkBool32)) < alignof(S);
|
||
|
|
}
|
||
|
|
static_assert(std::size(featureFields) == 55 + 12 + 47 + 15 + 21);
|
||
|
|
static_assert(Covers<VkPhysicalDeviceFeatures>(0, 55, offsetof(DeviceFeatures, vulkan10)));
|
||
|
|
static_assert(Covers<VkPhysicalDeviceVulkan11Features>(55, 12, offsetof(DeviceFeatures, vulkan11)));
|
||
|
|
static_assert(Covers<VkPhysicalDeviceVulkan12Features>(67, 47, offsetof(DeviceFeatures, vulkan12)));
|
||
|
|
static_assert(Covers<VkPhysicalDeviceVulkan13Features>(114, 15, offsetof(DeviceFeatures, vulkan13)));
|
||
|
|
static_assert(Covers<VkPhysicalDeviceVulkan14Features>(129, 21, offsetof(DeviceFeatures, vulkan14)));
|
||
|
|
|
||
|
|
bool Get(const DeviceFeatures& features, const FeatureField& field) {
|
||
|
|
VkBool32 value;
|
||
|
|
std::memcpy(&value, reinterpret_cast<const std::byte*>(&features) + field.offset, sizeof(value));
|
||
|
|
return value != VK_FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
void Enable(DeviceFeatures& features, const FeatureField& field) {
|
||
|
|
const VkBool32 value = VK_TRUE;
|
||
|
|
std::memcpy(reinterpret_cast<std::byte*>(&features) + field.offset, &value, sizeof(value));
|
||
|
|
}
|
||
|
|
|
||
|
|
// A SPIR-V capability that Vulkan gates by a core feature, a subgroup
|
||
|
|
// operation, or an extension of one of the library's optional groups,
|
||
|
|
// from vk.xml's <spirvcapabilities>: the ones with no other way to be
|
||
|
|
// enabled. One that a core version enables isn't listed, nor is one only
|
||
|
|
// an extension the library never enables can: those are left to the
|
||
|
|
// driver and the validation layer.
|
||
|
|
struct CapabilityRule {
|
||
|
|
std::uint32_t capability;
|
||
|
|
std::string_view name;
|
||
|
|
// Any of these enables it. An empty name ends the list.
|
||
|
|
std::array<FeatureField, 2> features;
|
||
|
|
// Nonzero: enabled instead by this bit of subgroupSupportedOperations.
|
||
|
|
VkSubgroupFeatureFlags subgroupOperation;
|
||
|
|
// Non-null: enabled instead by the optional group this Device flag
|
||
|
|
// says is on.
|
||
|
|
const bool* group;
|
||
|
|
};
|
||
|
|
|
||
|
|
// Generated once from vk.xml, with the values from SPIRV-Headers'
|
||
|
|
// spirv.core.grammar.json.
|
||
|
|
constexpr CapabilityRule capabilityRules[] = {
|
||
|
|
{ 2, "Geometry", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, geometryShader) }, 0, nullptr },
|
||
|
|
{ 3, "Tessellation", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, tessellationShader) }, 0, nullptr },
|
||
|
|
{ 9, "Float16", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, shaderFloat16) }, 0, nullptr },
|
||
|
|
{ 10, "Float64", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, shaderFloat64) }, 0, nullptr },
|
||
|
|
{ 11, "Int64", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, shaderInt64) }, 0, nullptr },
|
||
|
|
{ 12, "Int64Atomics", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, shaderBufferInt64Atomics), CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, shaderSharedInt64Atomics) }, 0, nullptr },
|
||
|
|
{ 22, "Int16", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, shaderInt16) }, 0, nullptr },
|
||
|
|
{ 23, "TessellationPointSize", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, shaderTessellationAndGeometryPointSize) }, 0, nullptr },
|
||
|
|
{ 24, "GeometryPointSize", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, shaderTessellationAndGeometryPointSize) }, 0, nullptr },
|
||
|
|
{ 25, "ImageGatherExtended", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, shaderImageGatherExtended) }, 0, nullptr },
|
||
|
|
{ 27, "StorageImageMultisample", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, shaderStorageImageMultisample) }, 0, nullptr },
|
||
|
|
{ 28, "UniformBufferArrayDynamicIndexing", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, shaderUniformBufferArrayDynamicIndexing) }, 0, nullptr },
|
||
|
|
{ 29, "SampledImageArrayDynamicIndexing", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, shaderSampledImageArrayDynamicIndexing) }, 0, nullptr },
|
||
|
|
{ 30, "StorageBufferArrayDynamicIndexing", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, shaderStorageBufferArrayDynamicIndexing) }, 0, nullptr },
|
||
|
|
{ 31, "StorageImageArrayDynamicIndexing", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, shaderStorageImageArrayDynamicIndexing) }, 0, nullptr },
|
||
|
|
{ 32, "ClipDistance", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, shaderClipDistance) }, 0, nullptr },
|
||
|
|
{ 33, "CullDistance", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, shaderCullDistance) }, 0, nullptr },
|
||
|
|
{ 34, "ImageCubeArray", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, imageCubeArray) }, 0, nullptr },
|
||
|
|
{ 35, "SampleRateShading", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, sampleRateShading) }, 0, nullptr },
|
||
|
|
{ 39, "Int8", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, shaderInt8) }, 0, nullptr },
|
||
|
|
{ 41, "SparseResidency", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, shaderResourceResidency) }, 0, nullptr },
|
||
|
|
{ 42, "MinLod", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, shaderResourceMinLod) }, 0, nullptr },
|
||
|
|
{ 45, "SampledCubeArray", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, imageCubeArray) }, 0, nullptr },
|
||
|
|
{ 48, "ImageMSArray", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, shaderStorageImageMultisample) }, 0, nullptr },
|
||
|
|
{ 52, "InterpolationFunction", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, sampleRateShading) }, 0, nullptr },
|
||
|
|
{ 57, "MultiViewport", { CRAFTER_FIELD_OF(vulkan10, VkPhysicalDeviceFeatures, multiViewport) }, 0, nullptr },
|
||
|
|
{ 61, "GroupNonUniform", { }, VK_SUBGROUP_FEATURE_BASIC_BIT, nullptr },
|
||
|
|
{ 62, "GroupNonUniformVote", { }, VK_SUBGROUP_FEATURE_VOTE_BIT, nullptr },
|
||
|
|
{ 63, "GroupNonUniformArithmetic", { }, VK_SUBGROUP_FEATURE_ARITHMETIC_BIT, nullptr },
|
||
|
|
{ 64, "GroupNonUniformBallot", { }, VK_SUBGROUP_FEATURE_BALLOT_BIT, nullptr },
|
||
|
|
{ 65, "GroupNonUniformShuffle", { }, VK_SUBGROUP_FEATURE_SHUFFLE_BIT, nullptr },
|
||
|
|
{ 66, "GroupNonUniformShuffleRelative", { }, VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT, nullptr },
|
||
|
|
{ 67, "GroupNonUniformClustered", { }, VK_SUBGROUP_FEATURE_CLUSTERED_BIT, nullptr },
|
||
|
|
{ 68, "GroupNonUniformQuad", { }, VK_SUBGROUP_FEATURE_QUAD_BIT, nullptr },
|
||
|
|
{ 69, "ShaderLayer", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, shaderOutputLayer) }, 0, nullptr },
|
||
|
|
{ 70, "ShaderViewportIndex", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, shaderOutputViewportIndex) }, 0, nullptr },
|
||
|
|
{ 4427, "DrawParameters", { CRAFTER_FIELD_OF(vulkan11, VkPhysicalDeviceVulkan11Features, shaderDrawParameters) }, 0, nullptr },
|
||
|
|
{ 4433, "StorageBuffer16BitAccess", { CRAFTER_FIELD_OF(vulkan11, VkPhysicalDeviceVulkan11Features, storageBuffer16BitAccess) }, 0, nullptr },
|
||
|
|
{ 4434, "UniformAndStorageBuffer16BitAccess", { CRAFTER_FIELD_OF(vulkan11, VkPhysicalDeviceVulkan11Features, uniformAndStorageBuffer16BitAccess) }, 0, nullptr },
|
||
|
|
{ 4435, "StoragePushConstant16", { CRAFTER_FIELD_OF(vulkan11, VkPhysicalDeviceVulkan11Features, storagePushConstant16) }, 0, nullptr },
|
||
|
|
{ 4436, "StorageInputOutput16", { CRAFTER_FIELD_OF(vulkan11, VkPhysicalDeviceVulkan11Features, storageInputOutput16) }, 0, nullptr },
|
||
|
|
{ 4439, "MultiView", { CRAFTER_FIELD_OF(vulkan11, VkPhysicalDeviceVulkan11Features, multiview) }, 0, nullptr },
|
||
|
|
{ 4441, "VariablePointersStorageBuffer", { CRAFTER_FIELD_OF(vulkan11, VkPhysicalDeviceVulkan11Features, variablePointersStorageBuffer) }, 0, nullptr },
|
||
|
|
{ 4442, "VariablePointers", { CRAFTER_FIELD_OF(vulkan11, VkPhysicalDeviceVulkan11Features, variablePointers) }, 0, nullptr },
|
||
|
|
{ 4448, "StorageBuffer8BitAccess", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, storageBuffer8BitAccess) }, 0, nullptr },
|
||
|
|
{ 4449, "UniformAndStorageBuffer8BitAccess", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, uniformAndStorageBuffer8BitAccess) }, 0, nullptr },
|
||
|
|
{ 4450, "StoragePushConstant8", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, storagePushConstant8) }, 0, nullptr },
|
||
|
|
{ 4472, "RayQueryKHR", { }, 0, &Device::rayTracingSupported },
|
||
|
|
{ 4473, "UntypedPointersKHR", { }, 0, &Device::descriptorHeapSupported },
|
||
|
|
{ 4478, "RayTraversalPrimitiveCullingKHR", { }, 0, &Device::rayTracingSupported },
|
||
|
|
{ 4479, "RayTracingKHR", { }, 0, &Device::rayTracingSupported },
|
||
|
|
{ 5128, "DescriptorHeapEXT", { }, 0, &Device::descriptorHeapSupported },
|
||
|
|
{ 5302, "RuntimeDescriptorArray", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, runtimeDescriptorArray) }, 0, nullptr },
|
||
|
|
{ 5303, "InputAttachmentArrayDynamicIndexing", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, shaderInputAttachmentArrayDynamicIndexing) }, 0, nullptr },
|
||
|
|
{ 5304, "UniformTexelBufferArrayDynamicIndexing", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, shaderUniformTexelBufferArrayDynamicIndexing) }, 0, nullptr },
|
||
|
|
{ 5305, "StorageTexelBufferArrayDynamicIndexing", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, shaderStorageTexelBufferArrayDynamicIndexing) }, 0, nullptr },
|
||
|
|
{ 5306, "UniformBufferArrayNonUniformIndexing", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, shaderUniformBufferArrayNonUniformIndexing) }, 0, nullptr },
|
||
|
|
{ 5307, "SampledImageArrayNonUniformIndexing", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, shaderSampledImageArrayNonUniformIndexing) }, 0, nullptr },
|
||
|
|
{ 5308, "StorageBufferArrayNonUniformIndexing", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, shaderStorageBufferArrayNonUniformIndexing) }, 0, nullptr },
|
||
|
|
{ 5309, "StorageImageArrayNonUniformIndexing", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, shaderStorageImageArrayNonUniformIndexing) }, 0, nullptr },
|
||
|
|
{ 5310, "InputAttachmentArrayNonUniformIndexing", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, shaderInputAttachmentArrayNonUniformIndexing) }, 0, nullptr },
|
||
|
|
{ 5311, "UniformTexelBufferArrayNonUniformIndexing", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, shaderUniformTexelBufferArrayNonUniformIndexing) }, 0, nullptr },
|
||
|
|
{ 5312, "StorageTexelBufferArrayNonUniformIndexing", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, shaderStorageTexelBufferArrayNonUniformIndexing) }, 0, nullptr },
|
||
|
|
{ 5345, "VulkanMemoryModel", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, vulkanMemoryModel) }, 0, nullptr },
|
||
|
|
{ 5346, "VulkanMemoryModelDeviceScope", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, vulkanMemoryModelDeviceScope) }, 0, nullptr },
|
||
|
|
{ 5347, "PhysicalStorageBufferAddresses", { CRAFTER_FIELD_OF(vulkan12, VkPhysicalDeviceVulkan12Features, bufferDeviceAddress) }, 0, nullptr },
|
||
|
|
{ 5379, "DemoteToHelperInvocation", { CRAFTER_FIELD_OF(vulkan13, VkPhysicalDeviceVulkan13Features, shaderDemoteToHelperInvocation) }, 0, nullptr },
|
||
|
|
{ 5629, "ExpectAssumeKHR", { CRAFTER_FIELD_OF(vulkan14, VkPhysicalDeviceVulkan14Features, shaderExpectAssume) }, 0, nullptr },
|
||
|
|
{ 6016, "DotProductInputAll", { CRAFTER_FIELD_OF(vulkan13, VkPhysicalDeviceVulkan13Features, shaderIntegerDotProduct) }, 0, nullptr },
|
||
|
|
{ 6017, "DotProductInput4x8Bit", { CRAFTER_FIELD_OF(vulkan13, VkPhysicalDeviceVulkan13Features, shaderIntegerDotProduct) }, 0, nullptr },
|
||
|
|
{ 6018, "DotProductInput4x8BitPacked", { CRAFTER_FIELD_OF(vulkan13, VkPhysicalDeviceVulkan13Features, shaderIntegerDotProduct) }, 0, nullptr },
|
||
|
|
{ 6019, "DotProduct", { CRAFTER_FIELD_OF(vulkan13, VkPhysicalDeviceVulkan13Features, shaderIntegerDotProduct) }, 0, nullptr },
|
||
|
|
{ 6026, "GroupNonUniformRotateKHR", { CRAFTER_FIELD_OF(vulkan14, VkPhysicalDeviceVulkan14Features, shaderSubgroupRotate) }, 0, nullptr },
|
||
|
|
{ 6029, "FloatControls2", { CRAFTER_FIELD_OF(vulkan14, VkPhysicalDeviceVulkan14Features, shaderFloatControls2) }, 0, nullptr },
|
||
|
|
};
|
||
|
|
#undef CRAFTER_FIELD_OF
|
||
|
|
|
||
|
|
// The listed capabilities that declaring one implicitly declares, as
|
||
|
|
// SPIR-V's grammar has it ("Int64Atomics" depends on "Int64"): a module
|
||
|
|
// may declare Int64Atomics alone and still use 64-bit integers. Generated
|
||
|
|
// with capabilityRules, keeping only dependencies that have a rule.
|
||
|
|
struct ImpliedCapabilities {
|
||
|
|
std::uint32_t capability;
|
||
|
|
std::string_view name;
|
||
|
|
std::uint32_t count;
|
||
|
|
std::array<std::uint32_t, 2> implied;
|
||
|
|
};
|
||
|
|
constexpr ImpliedCapabilities impliedCapabilities[] = {
|
||
|
|
{ 12, "Int64Atomics", 1, { 11 } }, // Int64
|
||
|
|
{ 23, "TessellationPointSize", 1, { 3 } }, // Tessellation
|
||
|
|
{ 24, "GeometryPointSize", 1, { 2 } }, // Geometry
|
||
|
|
{ 34, "ImageCubeArray", 1, { 45 } }, // SampledCubeArray
|
||
|
|
{ 54, "GeometryStreams", 1, { 2 } }, // Geometry
|
||
|
|
{ 57, "MultiViewport", 1, { 2 } }, // Geometry
|
||
|
|
{ 62, "GroupNonUniformVote", 1, { 61 } }, // GroupNonUniform
|
||
|
|
{ 63, "GroupNonUniformArithmetic", 1, { 61 } }, // GroupNonUniform
|
||
|
|
{ 64, "GroupNonUniformBallot", 1, { 61 } }, // GroupNonUniform
|
||
|
|
{ 65, "GroupNonUniformShuffle", 1, { 61 } }, // GroupNonUniform
|
||
|
|
{ 66, "GroupNonUniformShuffleRelative", 1, { 61 } }, // GroupNonUniform
|
||
|
|
{ 67, "GroupNonUniformClustered", 1, { 61 } }, // GroupNonUniform
|
||
|
|
{ 68, "GroupNonUniformQuad", 1, { 61 } }, // GroupNonUniform
|
||
|
|
{ 4434, "UniformAndStorageBuffer16BitAccess", 1, { 4433 } }, // StorageBuffer16BitAccess
|
||
|
|
{ 4442, "VariablePointers", 1, { 4441 } }, // VariablePointersStorageBuffer
|
||
|
|
{ 4449, "UniformAndStorageBuffer8BitAccess", 1, { 4448 } }, // StorageBuffer8BitAccess
|
||
|
|
{ 4478, "RayTraversalPrimitiveCullingKHR", 2, { 4472, 4479 } }, // RayQueryKHR, RayTracingKHR
|
||
|
|
{ 5016, "Int64ImageEXT", 1, { 11 } }, // Int64
|
||
|
|
{ 5128, "DescriptorHeapEXT", 1, { 4473 } }, // UntypedPointersKHR
|
||
|
|
{ 5249, "SampleMaskOverrideCoverageNV", 1, { 35 } }, // SampleRateShading
|
||
|
|
{ 5251, "GeometryShaderPassthroughNV", 1, { 2 } }, // Geometry
|
||
|
|
{ 5254, "ShaderViewportIndexLayerEXT", 2, { 2, 57 } }, // Geometry, MultiViewport
|
||
|
|
{ 5255, "ShaderViewportMaskNV", 2, { 2, 57 } }, // Geometry, MultiViewport
|
||
|
|
{ 5259, "ShaderStereoViewNV", 2, { 2, 57 } }, // Geometry, MultiViewport
|
||
|
|
{ 5260, "PerViewAttributesNV", 1, { 4439 } }, // MultiView
|
||
|
|
{ 5383, "ShaderInvocationReorderNV", 1, { 4479 } }, // RayTracingKHR
|
||
|
|
{ 5388, "ShaderInvocationReorderEXT", 1, { 4479 } }, // RayTracingKHR
|
||
|
|
{ 5409, "RayTracingDisplacementMicromapNV", 1, { 4479 } }, // RayTracingKHR
|
||
|
|
{ 5437, "RayTracingClusterAccelerationStructureNV", 1, { 4479 } }, // RayTracingKHR
|
||
|
|
{ 6017, "DotProductInput4x8Bit", 1, { 39 } }, // Int8
|
||
|
|
{ 6026, "GroupNonUniformRotateKHR", 1, { 61 } }, // GroupNonUniform
|
||
|
|
{ 6243, "UntypedVariableLengthArrayINTEL", 1, { 4473 } }, // UntypedPointersKHR
|
||
|
|
{ 6912, "DotProductFloat16AccFloat32VALVE", 1, { 9 } }, // Float16
|
||
|
|
{ 6913, "DotProductFloat16AccFloat16VALVE", 1, { 9 } }, // Float16
|
||
|
|
};
|
||
|
|
|
||
|
|
std::string_view GroupName(const bool* group) {
|
||
|
|
return group == &Device::rayTracingSupported ? "ray tracing (Device::rayTracingSupported)"
|
||
|
|
: group == &Device::descriptorHeapSupported ? "the descriptor heap (Device::descriptorHeapSupported)"
|
||
|
|
: group == &Device::deviceFaultSupported ? "device fault reports (Device::deviceFaultSupported)"
|
||
|
|
: "GPU memory decompression (Device::memoryDecompressionSupported)";
|
||
|
|
}
|
||
|
|
|
||
|
|
// Device types by preference. Zero: never chosen.
|
||
|
|
int Rank(VkPhysicalDeviceType type) {
|
||
|
|
switch (type) {
|
||
|
|
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: return 5;
|
||
|
|
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: return 4;
|
||
|
|
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: return 3;
|
||
|
|
case VK_PHYSICAL_DEVICE_TYPE_CPU: return 2;
|
||
|
|
case VK_PHYSICAL_DEVICE_TYPE_OTHER: return 1;
|
||
|
|
default: return 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
DeviceFeatures Device::ResolveFeatures(const DeviceFeatures& library, const DeviceFeatures& required,
|
||
|
|
const DeviceFeatures& optional, const DeviceFeatures& supported,
|
||
|
|
std::uint32_t apiVersion, FeatureGaps& gaps) {
|
||
|
|
DeviceFeatures enabled;
|
||
|
|
for (const FeatureField& field : featureFields) {
|
||
|
|
const bool byLibrary = Get(library, field);
|
||
|
|
const bool needed = byLibrary || Get(required, field);
|
||
|
|
if (Get(supported, field)) {
|
||
|
|
if (needed || Get(optional, field)) {
|
||
|
|
Enable(enabled, field);
|
||
|
|
}
|
||
|
|
} else if (needed) {
|
||
|
|
std::string gap(field.name);
|
||
|
|
if (apiVersion < VK_API_VERSION_1_4 && field.name == "vulkan14.maintenance5") {
|
||
|
|
gap += " (VK_KHR_maintenance5 reports it unsupported)";
|
||
|
|
} else if (apiVersion < VK_API_VERSION_1_4 && field.name.starts_with("vulkan14.")) {
|
||
|
|
gap += " (Vulkan 1.4 only)";
|
||
|
|
}
|
||
|
|
(byLibrary ? gaps.library : gaps.application).push_back(std::move(gap));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return enabled;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::size_t Device::ChooseDevice(std::span<const DeviceChoice> choices) {
|
||
|
|
// A CPU device (llvmpipe) runs the application only when nothing else is
|
||
|
|
// listed: a GPU that lacks something is a reason to say so, not to fall
|
||
|
|
// back to rendering in software.
|
||
|
|
const bool gpuListed = std::ranges::any_of(choices, [](const DeviceChoice& choice) {
|
||
|
|
return Rank(choice.type) > 0 && choice.type != VK_PHYSICAL_DEVICE_TYPE_CPU;
|
||
|
|
});
|
||
|
|
std::optional<std::size_t> best;
|
||
|
|
for (std::size_t i = 0; i < choices.size(); ++i) {
|
||
|
|
const DeviceChoice& choice = choices[i];
|
||
|
|
if (!choice.gaps.empty() || Rank(choice.type) == 0
|
||
|
|
|| (gpuListed && choice.type == VK_PHYSICAL_DEVICE_TYPE_CPU)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (!best || Rank(choice.type) > Rank(choices[*best].type)) {
|
||
|
|
best = i;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!best) {
|
||
|
|
std::string message = "Crafter.Graphics: no GPU can run this application";
|
||
|
|
if (choices.empty()) {
|
||
|
|
message += ": Vulkan lists none";
|
||
|
|
}
|
||
|
|
for (const DeviceChoice& choice : choices) {
|
||
|
|
std::string why = !choice.gaps.empty() ? DeviceDetail::Join(choice.gaps, "; ")
|
||
|
|
: choice.type == VK_PHYSICAL_DEVICE_TYPE_CPU
|
||
|
|
? "a CPU device, used only when no GPU is listed (pick it with CRAFTER_GRAPHICS_DEVICE)"
|
||
|
|
: "a device of unknown type";
|
||
|
|
message += std::format("\n {}: {}", choice.name, why);
|
||
|
|
}
|
||
|
|
throw std::runtime_error(message);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Say why each GPU that would have come first was passed over.
|
||
|
|
for (std::size_t i = 0; i < choices.size(); ++i) {
|
||
|
|
const DeviceChoice& choice = choices[i];
|
||
|
|
const int rank = Rank(choice.type);
|
||
|
|
const int bestRank = Rank(choices[*best].type);
|
||
|
|
if (!choice.gaps.empty() && (rank > bestRank || (rank == bestRank && i < *best))) {
|
||
|
|
std::println(stderr, "Crafter.Graphics: skipping {}: {}", choice.name, DeviceDetail::Join(choice.gaps, "; "));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return *best;
|
||
|
|
}
|
||
|
|
|
||
|
|
void Device::CheckShaderCapabilities(std::span<const std::uint32_t> spirv, const std::filesystem::path& file) {
|
||
|
|
constexpr std::uint32_t opCapability = 17;
|
||
|
|
constexpr std::uint32_t capabilityInt64 = 11;
|
||
|
|
|
||
|
|
// Throws when `capability` has a rule nothing satisfies. `declaredBy` is
|
||
|
|
// the capability the module named, when it implies this one.
|
||
|
|
const auto check = [&](std::uint32_t capability, const ImpliedCapabilities* declaredBy) {
|
||
|
|
const CapabilityRule* rule = std::ranges::find(capabilityRules, capability, &CapabilityRule::capability);
|
||
|
|
if (rule == std::ranges::end(capabilityRules)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const std::string what = declaredBy != nullptr
|
||
|
|
? std::format("{} declares SPIR-V capability {}, which implicitly declares {}", file.string(), declaredBy->name, rule->name)
|
||
|
|
: std::format("{} declares SPIR-V capability {}", file.string(), rule->name);
|
||
|
|
|
||
|
|
if (rule->subgroupOperation != 0) {
|
||
|
|
if ((vulkan11Properties.subgroupSupportedOperations & rule->subgroupOperation) == 0) {
|
||
|
|
throw std::runtime_error(std::format("{}, but this GPU has no {}", what,
|
||
|
|
string_VkSubgroupFeatureFlagBits(static_cast<VkSubgroupFeatureFlagBits>(rule->subgroupOperation))));
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (rule->group != nullptr) {
|
||
|
|
if (!*rule->group) {
|
||
|
|
throw std::runtime_error(std::format("{}, which needs {}, off on this GPU", what, GroupName(rule->group)));
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool enabled = false;
|
||
|
|
bool supported = false;
|
||
|
|
bool onlyVulkan14 = true;
|
||
|
|
std::string needs;
|
||
|
|
for (const FeatureField& field : rule->features) {
|
||
|
|
if (field.name.empty()) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
enabled = enabled || Get(enabledFeatures, field);
|
||
|
|
supported = supported || Get(supportedFeatures, field);
|
||
|
|
onlyVulkan14 = onlyVulkan14 && field.name.starts_with("vulkan14.");
|
||
|
|
needs += needs.empty() ? std::string(field.name) : std::format(" or {}", field.name);
|
||
|
|
}
|
||
|
|
// The NVIDIA workaround's own shaderInt64 doesn't count: the shader
|
||
|
|
// would run on NVIDIA and nowhere else.
|
||
|
|
if (capability == capabilityInt64 && shaderInt64ForWorkaroundOnly) {
|
||
|
|
throw std::runtime_error(std::format(
|
||
|
|
"{}, which needs {}: pass it to Device::Initialize (the NVIDIA workaround enables it for itself only)",
|
||
|
|
what, needs));
|
||
|
|
}
|
||
|
|
if (enabled) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (supported) {
|
||
|
|
throw std::runtime_error(std::format("{}, which needs {}: pass it to Device::Initialize", what, needs));
|
||
|
|
}
|
||
|
|
if (onlyVulkan14 && apiVersion < VK_API_VERSION_1_4) {
|
||
|
|
throw std::runtime_error(std::format("{}, which needs {}, a Vulkan 1.4 feature, and this GPU runs Vulkan {}.{}",
|
||
|
|
what, needs, VK_API_VERSION_MAJOR(apiVersion), VK_API_VERSION_MINOR(apiVersion)));
|
||
|
|
}
|
||
|
|
throw std::runtime_error(std::format("{}, which needs {}, and this GPU lacks it", what, needs));
|
||
|
|
};
|
||
|
|
|
||
|
|
// A module's OpCapability instructions come first, right after its
|
||
|
|
// five-word header (the logical layout in SPIR-V's section 2.4).
|
||
|
|
for (std::size_t i = 5; i < spirv.size();) {
|
||
|
|
const std::uint32_t length = spirv[i] >> 16;
|
||
|
|
if ((spirv[i] & 0xFFFFu) != opCapability || length < 2 || length > spirv.size() - i) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
const std::uint32_t capability = spirv[i + 1];
|
||
|
|
i += length;
|
||
|
|
|
||
|
|
check(capability, nullptr);
|
||
|
|
const ImpliedCapabilities* implied = std::ranges::find(impliedCapabilities, capability, &ImpliedCapabilities::capability);
|
||
|
|
if (implied != std::ranges::end(impliedCapabilities)) {
|
||
|
|
for (std::uint32_t k = 0; k < implied->count; ++k) {
|
||
|
|
check(implied->implied[k], implied);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|