blob: b112e33400bc5e7d2ce6cb439dc18a819f42cb79 [file] [log] [blame]
/* Copyright (c) 2015-2026 The Khronos Group Inc.
* Copyright (c) 2015-2026 Valve Corporation
* Copyright (c) 2015-2026 LunarG, Inc.
* Copyright (C) 2015-2026 Google Inc.
* Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved.
*
* 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.
*/
#include "shader_utils.h"
#include "generated/spirv_grammar_helper.h"
#include "generated/spirv_tools_commit_id.h"
#include "state_tracker/shader_module.h"
#include <cstring>
#include <fstream>
#include <sstream>
#include <string>
void ValidationCache::GetUUID(uint8_t *uuid) {
const char *sha1_str = SPIRV_TOOLS_COMMIT_ID;
// 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));
}
// Replace the last 4 bytes (likely padded with zero anyway)
std::memcpy(uuid + (VK_UUID_SIZE - sizeof(uint32_t)), &spirv_val_option_hash_, sizeof(uint32_t));
}
void ValidationCache::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];
GetUUID(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 ValidationCache::Write(size_t *pDataSize, void *pData) {
const auto header_size = 2 * sizeof(uint32_t) + VK_UUID_SIZE; // 4 bytes for header size + 4 bytes for version number + UUID
if (!pData) {
*pDataSize = header_size + good_shader_hashes_.size() * sizeof(uint32_t);
return;
}
if (*pDataSize < header_size) {
*pDataSize = 0;
return; // Too small for even the header!
}
uint32_t *out = (uint32_t *)pData;
size_t actual_size = header_size;
// Write the header
*out++ = header_size;
*out++ = VK_VALIDATION_CACHE_HEADER_VERSION_ONE_EXT;
GetUUID(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() && actual_size < *pDataSize;
it++, out++, actual_size += sizeof(uint32_t)) {
*out = *it;
}
}
*pDataSize = actual_size;
}
void ValidationCache::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);
}
VkShaderStageFlagBits ExecutionModelToShaderStageFlagBits(uint32_t mode) {
switch (mode) {
case spv::ExecutionModelVertex:
return VK_SHADER_STAGE_VERTEX_BIT;
case spv::ExecutionModelTessellationControl:
return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT;
case spv::ExecutionModelTessellationEvaluation:
return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
case spv::ExecutionModelGeometry:
return VK_SHADER_STAGE_GEOMETRY_BIT;
case spv::ExecutionModelFragment:
return VK_SHADER_STAGE_FRAGMENT_BIT;
case spv::ExecutionModelGLCompute:
return VK_SHADER_STAGE_COMPUTE_BIT;
case spv::ExecutionModelRayGenerationKHR:
return VK_SHADER_STAGE_RAYGEN_BIT_KHR;
case spv::ExecutionModelAnyHitKHR:
return VK_SHADER_STAGE_ANY_HIT_BIT_KHR;
case spv::ExecutionModelClosestHitKHR:
return VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR;
case spv::ExecutionModelMissKHR:
return VK_SHADER_STAGE_MISS_BIT_KHR;
case spv::ExecutionModelIntersectionKHR:
return VK_SHADER_STAGE_INTERSECTION_BIT_KHR;
case spv::ExecutionModelCallableKHR:
return VK_SHADER_STAGE_CALLABLE_BIT_KHR;
case spv::ExecutionModelTaskNV:
return VK_SHADER_STAGE_TASK_BIT_NV;
case spv::ExecutionModelMeshNV:
return VK_SHADER_STAGE_MESH_BIT_NV;
case spv::ExecutionModelTaskEXT:
return VK_SHADER_STAGE_TASK_BIT_EXT;
case spv::ExecutionModelMeshEXT:
return VK_SHADER_STAGE_MESH_BIT_EXT;
case spv::ExecutionModelKernel:
case spv::ExecutionModelMax:
break;
}
return VK_SHADER_STAGE_ALL;
}
// This is used to help dump SPIR-V while debugging intermediate phases of any altercations to the SPIR-V
void DumpSpirvToFile(const std::string &file_path, const uint32_t *spirv, size_t spirv_dwords_count) {
std::ofstream debug_file(file_path, std::ios::out | std::ios::binary);
debug_file.write(reinterpret_cast<const char *>(spirv), spirv_dwords_count * sizeof(uint32_t));
}
bool ResourceTypeMatchesBinding(VkSpirvResourceTypeFlagsEXT resource_type,
const spirv::ResourceInterfaceVariable& resource_variable) {
if (resource_type == VK_SPIRV_RESOURCE_TYPE_ALL_EXT) {
return true;
}
const uint32_t opcode = resource_variable.base_type.Opcode();
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT) != 0 && opcode == spv::OpTypeSampler) {
return true;
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT) != 0 && opcode == spv::OpTypeImage &&
resource_variable.base_type.Word(7) == 1) {
return true;
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_ONLY_IMAGE_BIT_EXT) != 0 && opcode == spv::OpTypeImage &&
resource_variable.base_type.Word(7) == 2 && resource_variable.decorations.Has(spirv::DecorationSet::nonwritable_bit)) {
return true;
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_WRITE_IMAGE_BIT_EXT) != 0 && opcode == spv::OpTypeImage &&
resource_variable.base_type.Word(7) == 2 && !resource_variable.decorations.Has(spirv::DecorationSet::nonwritable_bit)) {
return true;
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_COMBINED_SAMPLED_IMAGE_BIT_EXT) != 0 && resource_variable.is_type_sampled_image) {
return true;
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT) != 0 && resource_variable.is_uniform_buffer) {
return true;
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_ONLY_STORAGE_BUFFER_BIT_EXT) != 0 && resource_variable.is_storage_buffer &&
resource_variable.decorations.Has(spirv::DecorationSet::nonwritable_bit)) {
return true;
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_WRITE_STORAGE_BUFFER_BIT_EXT) != 0 && resource_variable.is_storage_buffer &&
!resource_variable.decorations.Has(spirv::DecorationSet::nonwritable_bit)) {
return true;
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_ACCELERATION_STRUCTURE_BIT_EXT) != 0 &&
opcode == spv::OpTypeAccelerationStructureKHR) {
return true;
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_TENSOR_BIT_ARM) != 0 && opcode == spv::OpTypeTensorARM) {
return true;
}
return false;
}
// Only currently want to report the first mismatch
std::string DescribeResourceTypeMismatch(VkSpirvResourceTypeFlagsEXT resource_type,
const spirv::ResourceInterfaceVariable& resource_variable) {
const uint32_t opcode = resource_variable.base_type.Opcode();
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT) != 0 && opcode != spv::OpTypeSampler) {
return "base type is " + std::string(string_SpvOpcode(opcode)) + ", not OpTypeSampler";
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT) != 0) {
if (opcode != spv::OpTypeImage) {
return "base type is " + std::string(string_SpvOpcode(opcode)) + ", not OpTypeImage";
} else if (resource_variable.base_type.Word(7) != 1) {
return "OpTypeImage Sampled is " + std::to_string(resource_variable.base_type.Word(7)) + ", not 1";
}
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_ONLY_IMAGE_BIT_EXT) != 0) {
if (opcode != spv::OpTypeImage) {
return "base type is " + std::string(string_SpvOpcode(opcode)) + ", not OpTypeImage";
} else if (resource_variable.base_type.Word(7) != 2) {
return "OpTypeImage Sampled is " + std::to_string(resource_variable.base_type.Word(7)) + ", not 2";
} else if (!resource_variable.decorations.Has(spirv::DecorationSet::nonwritable_bit)) {
return "is not decorated with NonWritable";
}
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_WRITE_IMAGE_BIT_EXT) != 0) {
if (opcode != spv::OpTypeImage) {
return "base type is " + std::string(string_SpvOpcode(opcode)) + ", not OpTypeImage";
} else if (resource_variable.base_type.Word(7) != 2) {
return "OpTypeImage Sampled is " + std::to_string(resource_variable.base_type.Word(7)) + ", not 2";
} else if (resource_variable.decorations.Has(spirv::DecorationSet::nonwritable_bit)) {
return "is decorated with NonWritable";
}
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_COMBINED_SAMPLED_IMAGE_BIT_EXT) != 0 && !resource_variable.is_type_sampled_image) {
return "is not OpTypeSampledImage";
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT) != 0 && !resource_variable.is_uniform_buffer) {
if (resource_variable.storage_class != spv::StorageClassUniform) {
return "is not Uniform StorageClass";
} else if (!resource_variable.type_struct_info) {
return "is not a OpTypeStruct";
} else if (!resource_variable.type_struct_info->decorations.Has(spirv::DecorationSet::block_bit)) {
return "is not decorated with Block";
}
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_ONLY_STORAGE_BUFFER_BIT_EXT) != 0) {
if (!resource_variable.decorations.Has(spirv::DecorationSet::nonwritable_bit)) {
return "is not decorated with NonWritable";
} else if (!resource_variable.type_struct_info) {
return "is not a OpTypeStruct";
} else if (!resource_variable.is_storage_buffer) {
return "is not a storage buffer"; // simplified
}
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_READ_WRITE_STORAGE_BUFFER_BIT_EXT) != 0) {
if (!resource_variable.decorations.Has(spirv::DecorationSet::nonwritable_bit)) {
return "is decorated with NonWritable";
} else if (!resource_variable.type_struct_info) {
return "is not a OpTypeStruct";
} else if (!resource_variable.is_storage_buffer) {
return "is not a storage buffer"; // simplified
}
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_ACCELERATION_STRUCTURE_BIT_EXT) != 0 &&
opcode == spv::OpTypeAccelerationStructureKHR) {
return "base type is " + std::string(string_SpvOpcode(opcode)) + ", not OpTypeAccelerationStructureKHR";
}
if ((resource_type & VK_SPIRV_RESOURCE_TYPE_TENSOR_BIT_ARM) != 0 && opcode == spv::OpTypeTensorARM) {
return "base type is " + std::string(string_SpvOpcode(opcode)) + ", not OpTypeTensorARM";
}
return "[UNKNOWN]";
}