Device
- Device::Initialize(required, optional) takes DeviceFeatures, the five
core feature structs vulkan10 to vulkan14. The library enables only what
its own shaders and code use. Device::enabledFeatures says what was
enabled, supportedFeatures what the GPU has, apiVersion the version in
use.
- shaderInt16, shaderFloat16, storageBuffer16BitAccess, scalarBlockLayout
and the uniform-buffer, storage-buffer and storage-image non-uniform
indexing bits are no longer enabled for everyone. An application whose
shaders take uint16_t spec constants passes
{.vulkan10 = {.shaderInt16 = VK_TRUE}}. scalarBlockLayout stays on with
the NVIDIA workaround, whose TLAS address may sit in a struct's tail
padding.
- Every GPU is weighed before one is chosen: Vulkan 1.3, the swapchain and,
below 1.4, maintenance5 extensions, the library's needs, the required
features. One that can't run the application is skipped, saying why when
it ranks higher. A CPU device runs it only when nothing else is listed,
and CRAFTER_GRAPHICS_DEVICE never falls back. A GPU two drivers both list
is tried through each.
- When no GPU qualifies, Initialize throws naming each GPU's gaps. It
destroys the instance it made, before any Wayland connection, so it can
be called again asking for less.
- maintenance5 is vulkan14's on a 1.4 GPU and VK_KHR_maintenance5's below.
CRAFTER_GRAPHICS_NO_VULKAN_1_4 runs as a 1.3 application, to exercise
that path on a 1.4 GPU.
- VK_KHR_spirv_1_4 and VK_KHR_shader_float_controls, core since 1.2, are no
longer enabled, nor VK_EXT_shader_atomic_float, which was enabled without
its feature struct. memoryDecompression's feature bit is queried before
it is enabled.
Shaders
- VulkanShader checks a module's capabilities, declared directly or
implicitly, against the enabled core features, the GPU's subgroup
operations and the optional groups, and throws naming the field to pass,
or saying the GPU lacks it. The tables are generated from vk.xml and
SPIR-V's grammar.
- WorkaroundNvidiaAS::Patch sized a row-major matrix push member by its
columns, so the TLAS address could land inside it.
Window
- The swapchain format is one whose own feature flags allow storage reads
and writes without a format, which since Vulkan 1.3 each format says for
itself; shaderStorageImageRead/WriteWithoutFormat are no longer required.
Tests
- DeviceFeatures: ResolveFeatures, ChooseDevice and CheckShaderCapabilities,
without a GPU.
- FeatureRequests: required and optional features end to end, the 1.3
path, and a retry after a refusal.
- PushConstantRewrite: a row-major mat3x4 push member.
- HeapASRead, HeapDynamicIndexHit, RTPassPushData and the ray-tracing
examples ask for the features their shaders use.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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);
|
|
}
|
|
}
|
|
}
|
|
}
|