blob: a4cf7ea033bc3c355ff101161b3ba597cb60fdea [file] [log] [blame]
/* Copyright (c) 2015-2023 The Khronos Group Inc.
* Copyright (c) 2015-2023 Valve Corporation
* Copyright (c) 2015-2023 LunarG, Inc.
* Copyright (C) 2015-2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* The Shader Validation file is in charge of taking the Shader Module data and validating it
*/
#pragma once
#include "vulkan/vulkan.h"
#include "utils/vk_layer_utils.h"
#include "generated/spirv_tools_commit_id.h"
#include <spirv/unified1/spirv.hpp>
#include <spirv-tools/libspirv.h>
#include <spirv-tools/optimizer.hpp>
struct DeviceFeatures;
struct DeviceExtensions;
class APIVersion;
enum DescriptorReqBits {
DESCRIPTOR_REQ_VIEW_TYPE_1D = 1 << VK_IMAGE_VIEW_TYPE_1D,
DESCRIPTOR_REQ_VIEW_TYPE_1D_ARRAY = 1 << VK_IMAGE_VIEW_TYPE_1D_ARRAY,
DESCRIPTOR_REQ_VIEW_TYPE_2D = 1 << VK_IMAGE_VIEW_TYPE_2D,
DESCRIPTOR_REQ_VIEW_TYPE_2D_ARRAY = 1 << VK_IMAGE_VIEW_TYPE_2D_ARRAY,
DESCRIPTOR_REQ_VIEW_TYPE_3D = 1 << VK_IMAGE_VIEW_TYPE_3D,
DESCRIPTOR_REQ_VIEW_TYPE_CUBE = 1 << VK_IMAGE_VIEW_TYPE_CUBE,
DESCRIPTOR_REQ_VIEW_TYPE_CUBE_ARRAY = 1 << VK_IMAGE_VIEW_TYPE_CUBE_ARRAY,
DESCRIPTOR_REQ_ALL_VIEW_TYPE_BITS = (1 << (VK_IMAGE_VIEW_TYPE_CUBE_ARRAY + 1)) - 1,
DESCRIPTOR_REQ_SINGLE_SAMPLE = 2 << VK_IMAGE_VIEW_TYPE_CUBE_ARRAY,
DESCRIPTOR_REQ_MULTI_SAMPLE = DESCRIPTOR_REQ_SINGLE_SAMPLE << 1,
DESCRIPTOR_REQ_COMPONENT_TYPE_FLOAT = DESCRIPTOR_REQ_MULTI_SAMPLE << 1,
DESCRIPTOR_REQ_COMPONENT_TYPE_SINT = DESCRIPTOR_REQ_COMPONENT_TYPE_FLOAT << 1,
DESCRIPTOR_REQ_COMPONENT_TYPE_UINT = DESCRIPTOR_REQ_COMPONENT_TYPE_SINT << 1,
DESCRIPTOR_REQ_VIEW_ATOMIC_OPERATION = DESCRIPTOR_REQ_COMPONENT_TYPE_UINT << 1,
DESCRIPTOR_REQ_SAMPLER_SAMPLED = DESCRIPTOR_REQ_VIEW_ATOMIC_OPERATION << 1,
DESCRIPTOR_REQ_SAMPLER_IMPLICITLOD_DREF_PROJ = DESCRIPTOR_REQ_SAMPLER_SAMPLED << 1,
DESCRIPTOR_REQ_SAMPLER_BIAS_OFFSET = DESCRIPTOR_REQ_SAMPLER_IMPLICITLOD_DREF_PROJ << 1,
DESCRIPTOR_REQ_IMAGE_READ_WITHOUT_FORMAT = DESCRIPTOR_REQ_SAMPLER_BIAS_OFFSET << 1,
DESCRIPTOR_REQ_IMAGE_WRITE_WITHOUT_FORMAT = DESCRIPTOR_REQ_IMAGE_READ_WITHOUT_FORMAT << 1,
DESCRIPTOR_REQ_IMAGE_DREF = DESCRIPTOR_REQ_IMAGE_WRITE_WITHOUT_FORMAT << 1,
};
typedef uint32_t DescriptorReqFlags;
struct ResourceInterfaceVariable;
struct DescriptorRequirement {
DescriptorReqFlags reqs;
const ResourceInterfaceVariable *variable;
DescriptorRequirement() : reqs(0) {}
};
enum class ShaderObjectStage : uint32_t {
VERTEX = 0u,
TESSELLATION_CONTROL,
TESSELLATION_EVALUATION,
GEOMETRY,
FRAGMENT,
COMPUTE,
TASK,
MESH,
LAST = 8u,
};
constexpr uint32_t SHADER_OBJECT_STAGE_COUNT = 8u;
inline ShaderObjectStage VkShaderStageToShaderObjectStage(VkShaderStageFlagBits stage) {
switch (stage) {
case VK_SHADER_STAGE_VERTEX_BIT:
return ShaderObjectStage::VERTEX;
case VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT:
return ShaderObjectStage::TESSELLATION_CONTROL;
case VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT:
return ShaderObjectStage::TESSELLATION_EVALUATION;
case VK_SHADER_STAGE_GEOMETRY_BIT:
return ShaderObjectStage::GEOMETRY;
case VK_SHADER_STAGE_FRAGMENT_BIT:
return ShaderObjectStage::FRAGMENT;
case VK_SHADER_STAGE_COMPUTE_BIT:
return ShaderObjectStage::COMPUTE;
case VK_SHADER_STAGE_TASK_BIT_EXT:
return ShaderObjectStage::TASK;
case VK_SHADER_STAGE_MESH_BIT_EXT:
return ShaderObjectStage::MESH;
default:
break;
}
return ShaderObjectStage::LAST;
}
inline bool operator==(const DescriptorRequirement &a, const DescriptorRequirement &b) noexcept { return a.reqs == b.reqs; }
inline bool operator<(const DescriptorRequirement &a, const DescriptorRequirement &b) noexcept { return a.reqs < b.reqs; }
// < binding index (of descriptor set) : meta data >
typedef std::map<uint32_t, DescriptorRequirement> BindingVariableMap;
// Capture which slots (set#->bindings) are actually used by the shaders of this pipeline
using ActiveSlotMap = vvl::unordered_map<uint32_t, BindingVariableMap>;
struct EntryPoint;
struct SHADER_MODULE_STATE;
struct SPIRV_MODULE_STATE;
struct safe_VkPipelineShaderStageCreateInfo;
struct safe_VkShaderCreateInfoEXT;
struct safe_VkSpecializationInfo;
struct PipelineStageState {
// We use this over a SPIRV_MODULE_STATE because there are times we need to create empty objects
std::shared_ptr<const SHADER_MODULE_STATE> module_state;
std::shared_ptr<const SPIRV_MODULE_STATE> spirv_state;
const safe_VkPipelineShaderStageCreateInfo *pipeline_create_info;
const safe_VkShaderCreateInfoEXT *shader_object_create_info;
// If null, means it is an empty object, no SPIR-V backing it
std::shared_ptr<const EntryPoint> entrypoint;
PipelineStageState(const safe_VkPipelineShaderStageCreateInfo *pipeline_create_info,
const safe_VkShaderCreateInfoEXT *shader_object_create_info,
std::shared_ptr<const SHADER_MODULE_STATE> module_state,
std::shared_ptr<const SPIRV_MODULE_STATE> spirv_state);
const char *GetPName() const;
VkShaderStageFlagBits GetStage() const;
safe_VkSpecializationInfo *GetSpecializationInfo() const;
const void *GetPNext() const;
};
using StageStateVec = std::vector<PipelineStageState>;
class ValidationCache {
public:
static VkValidationCacheEXT Create(VkValidationCacheCreateInfoEXT const *pCreateInfo) {
auto cache = new ValidationCache();
cache->Load(pCreateInfo);
return VkValidationCacheEXT(cache);
}
void Load(VkValidationCacheCreateInfoEXT const *pCreateInfo) {
const auto headerSize = 2 * sizeof(uint32_t) + VK_UUID_SIZE;
auto size = headerSize;
if (!pCreateInfo->pInitialData || pCreateInfo->initialDataSize < size) return;
uint32_t const *data = (uint32_t const *)pCreateInfo->pInitialData;
if (data[0] != size) return;
if (data[1] != VK_VALIDATION_CACHE_HEADER_VERSION_ONE_EXT) return;
uint8_t expected_uuid[VK_UUID_SIZE];
Sha1ToVkUuid(SPIRV_TOOLS_COMMIT_ID, expected_uuid);
if (memcmp(&data[2], expected_uuid, VK_UUID_SIZE) != 0) return; // different version
data = (uint32_t const *)(reinterpret_cast<uint8_t const *>(data) + headerSize);
auto guard = WriteLock();
for (; size < pCreateInfo->initialDataSize; data++, size += sizeof(uint32_t)) {
good_shader_hashes_.insert(*data);
}
}
void Write(size_t *pDataSize, void *pData) {
const auto headerSize = 2 * sizeof(uint32_t) + VK_UUID_SIZE; // 4 bytes for header size + 4 bytes for version number + UUID
if (!pData) {
*pDataSize = headerSize + good_shader_hashes_.size() * sizeof(uint32_t);
return;
}
if (*pDataSize < headerSize) {
*pDataSize = 0;
return; // Too small for even the header!
}
uint32_t *out = (uint32_t *)pData;
size_t actualSize = headerSize;
// Write the header
*out++ = headerSize;
*out++ = VK_VALIDATION_CACHE_HEADER_VERSION_ONE_EXT;
Sha1ToVkUuid(SPIRV_TOOLS_COMMIT_ID, reinterpret_cast<uint8_t *>(out));
out = (uint32_t *)(reinterpret_cast<uint8_t *>(out) + VK_UUID_SIZE);
{
auto guard = ReadLock();
for (auto it = good_shader_hashes_.begin(); it != good_shader_hashes_.end() && actualSize < *pDataSize;
it++, out++, actualSize += sizeof(uint32_t)) {
*out = *it;
}
}
*pDataSize = actualSize;
}
void Merge(ValidationCache const *other) {
// self-merging is invalid, but avoid deadlock below just in case.
if (other == this) {
return;
}
auto other_guard = other->ReadLock();
auto guard = WriteLock();
good_shader_hashes_.reserve(good_shader_hashes_.size() + other->good_shader_hashes_.size());
for (auto h : other->good_shader_hashes_) good_shader_hashes_.insert(h);
}
static uint32_t MakeShaderHash(const void *pCode, const size_t codeSize);
bool Contains(uint32_t hash) {
auto guard = ReadLock();
return good_shader_hashes_.count(hash) != 0;
}
void Insert(uint32_t hash) {
auto guard = WriteLock();
good_shader_hashes_.insert(hash);
}
private:
ValidationCache() {}
ReadLockGuard ReadLock() const { return ReadLockGuard(lock_); }
WriteLockGuard WriteLock() { return WriteLockGuard(lock_); }
void Sha1ToVkUuid(const char *sha1_str, uint8_t *uuid) {
// Convert sha1_str from a hex string to binary. We only need VK_UUID_SIZE bytes of
// output, so pad with zeroes if the input string is shorter than that, and truncate
// if it's longer.
#if defined(__GNUC__) && (__GNUC__ > 8)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-truncation"
#endif
char padded_sha1_str[2 * VK_UUID_SIZE + 1] = {}; // 2 hex digits == 1 byte
std::strncpy(padded_sha1_str, sha1_str, 2 * VK_UUID_SIZE);
#if defined(__GNUC__) && (__GNUC__ > 8)
#pragma GCC diagnostic pop
#endif
for (uint32_t i = 0; i < VK_UUID_SIZE; ++i) {
const char byte_str[] = {padded_sha1_str[2 * i + 0], padded_sha1_str[2 * i + 1], '\0'};
uuid[i] = static_cast<uint8_t>(std::strtoul(byte_str, nullptr, 16));
}
}
// hashes of shaders that have passed validation before, and can be skipped.
// we don't store negative results, as we would have to also store what was
// wrong with them; also, we expect they will get fixed, so we're less
// likely to see them again.
vvl::unordered_set<uint32_t> good_shader_hashes_;
mutable std::shared_mutex lock_;
};
spv_target_env PickSpirvEnv(const APIVersion &api_version, bool spirv_1_4);
void AdjustValidatorOptions(const DeviceExtensions &device_extensions, const DeviceFeatures &enabled_features,
spvtools::ValidatorOptions &options);
void GetActiveSlots(ActiveSlotMap &active_slots, const std::shared_ptr<const EntryPoint> &entrypoint);
ActiveSlotMap GetActiveSlots(const StageStateVec &stage_states);
ActiveSlotMap GetActiveSlots(const std::shared_ptr<const EntryPoint> &entrypoint);
uint32_t GetMaxActiveSlot(const ActiveSlotMap &active_slots);