/* * * Copyright (C) 2015 Valve Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * * Author: Chia-I Wu * Author: Chris Forbes */ #include #include #include #include #include #include #include #include #include #include #include "vk_loader_platform.h" #include "vk_dispatch_table_helper.h" #include "vulkan/vk_layer.h" #include "vk_layer_utils.h" #include "vk_layer_config.h" #include "vk_layer_table.h" #include "vk_enum_string_helper.h" #include "shader_checker.h" #include "vk_layer_extension_utils.h" #include // fwd decls struct shader_module; struct render_pass; struct layer_data { debug_report_data *report_data; std::vector logging_callback; VkLayerDispatchTable* device_dispatch_table; VkLayerInstanceDispatchTable* instance_dispatch_table; std::unordered_map shader_module_map; std::unordered_map*> descriptor_set_layout_map; std::unordered_map*>*> pipeline_layout_map; std::unordered_map render_pass_map; layer_data() : report_data(nullptr), device_dispatch_table(nullptr), instance_dispatch_table(nullptr) {}; }; static void build_type_def_index(std::vector const &words, std::unordered_map &type_def_index); struct shader_module { /* the spirv image itself */ std::vector words; /* a mapping of to the first word of its def. this is useful because walking type * trees requires jumping all over the instruction stream. */ std::unordered_map type_def_index; shader_module(VkShaderModuleCreateInfo const *pCreateInfo) : words((uint32_t *)pCreateInfo->pCode, (uint32_t *)pCreateInfo->pCode + pCreateInfo->codeSize / sizeof(uint32_t)), type_def_index() { build_type_def_index(words, type_def_index); } }; struct render_pass { std::vector> subpass_color_formats; render_pass(VkRenderPassCreateInfo const *pCreateInfo) { uint32_t i; subpass_color_formats.reserve(pCreateInfo->subpassCount); for (i = 0; i < pCreateInfo->subpassCount; i++) { const VkSubpassDescription *subpass = &pCreateInfo->pSubpasses[i]; std::vector color_formats; uint32_t j; color_formats.reserve(subpass->colorAttachmentCount); for (j = 0; j < subpass->colorAttachmentCount; j++) { const uint32_t att = subpass->pColorAttachments[j].attachment; const VkFormat format = pCreateInfo->pAttachments[att].format; color_formats.push_back(pCreateInfo->pAttachments[att].format); } subpass_color_formats.push_back(color_formats); } } }; static std::unordered_map layer_data_map; template layer_data *get_my_data_ptr( void *data_key, std::unordered_map &data_map); static LOADER_PLATFORM_THREAD_ONCE_DECLARATION(g_initOnce); // TODO : This can be much smarter, using separate locks for separate global data static int globalLockInitialized = 0; static loader_platform_thread_mutex globalLock; VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkCreateDescriptorSetLayout( VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorSetLayout* pSetLayout) { layer_data *my_data = get_my_data_ptr(get_dispatch_key(device), layer_data_map); /* stash a copy of the layout bindings */ VkResult result = my_data->device_dispatch_table->CreateDescriptorSetLayout(device, pCreateInfo, pAllocator, pSetLayout); if (VK_SUCCESS == result) { loader_platform_thread_lock_mutex(&globalLock); auto& bindings = my_data->descriptor_set_layout_map[*pSetLayout]; bindings = new std::unordered_set(); bindings->reserve(pCreateInfo->bindingCount); for (uint32_t i = 0; i < pCreateInfo->bindingCount; i++) bindings->insert(pCreateInfo->pBinding[i].binding); loader_platform_thread_unlock_mutex(&globalLock); } return result; } VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkCreatePipelineLayout( VkDevice device, const VkPipelineLayoutCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPipelineLayout* pPipelineLayout) { layer_data *my_data = get_my_data_ptr(get_dispatch_key(device), layer_data_map); VkResult result = my_data->device_dispatch_table->CreatePipelineLayout(device, pCreateInfo, pAllocator, pPipelineLayout); if (VK_SUCCESS == result) { loader_platform_thread_lock_mutex(&globalLock); auto& layouts = my_data->pipeline_layout_map[*pPipelineLayout]; layouts = new std::vector*>(); layouts->reserve(pCreateInfo->setLayoutCount); for (unsigned i = 0; i < pCreateInfo->setLayoutCount; i++) { layouts->push_back(my_data->descriptor_set_layout_map[pCreateInfo->pSetLayouts[i]]); } loader_platform_thread_unlock_mutex(&globalLock); } return result; } static void build_type_def_index(std::vector const &words, std::unordered_map &type_def_index) { unsigned int const *code = (unsigned int const *)&words[0]; size_t size = words.size(); unsigned word = 5; while (word < size) { unsigned opcode = code[word] & 0x0ffffu; unsigned oplen = (code[word] & 0xffff0000u) >> 16; switch (opcode) { case spv::OpTypeVoid: case spv::OpTypeBool: case spv::OpTypeInt: case spv::OpTypeFloat: case spv::OpTypeVector: case spv::OpTypeMatrix: case spv::OpTypeImage: case spv::OpTypeSampler: case spv::OpTypeSampledImage: case spv::OpTypeArray: case spv::OpTypeRuntimeArray: case spv::OpTypeStruct: case spv::OpTypeOpaque: case spv::OpTypePointer: case spv::OpTypeFunction: case spv::OpTypeEvent: case spv::OpTypeDeviceEvent: case spv::OpTypeReserveId: case spv::OpTypeQueue: case spv::OpTypePipe: type_def_index[code[word+1]] = word; break; default: /* We only care about type definitions */ break; } word += oplen; } } bool shader_is_spirv(VkShaderModuleCreateInfo const *pCreateInfo) { uint32_t *words = (uint32_t *)pCreateInfo->pCode; size_t sizeInWords = pCreateInfo->codeSize / sizeof(uint32_t); /* Just validate that the header makes sense. */ return sizeInWords >= 5 && words[0] == spv::MagicNumber && words[1] == spv::Version; } static void init_shader_checker(layer_data *my_data) { uint32_t report_flags = 0; uint32_t debug_action = 0; FILE *log_output = NULL; const char *option_str; VkDbgMsgCallback callback; // initialize ShaderChecker options report_flags = getLayerOptionFlags("ShaderCheckerReportFlags", 0); getLayerOptionEnum("ShaderCheckerDebugAction", (uint32_t *) &debug_action); if (debug_action & VK_DBG_LAYER_ACTION_LOG_MSG) { option_str = getLayerOption("ShaderCheckerLogFilename"); log_output = getLayerLogOutput(option_str, "ShaderChecker"); layer_create_msg_callback(my_data->report_data, report_flags, log_callback, (void *) log_output, &callback); my_data->logging_callback.push_back(callback); } if (debug_action & VK_DBG_LAYER_ACTION_DEBUG_OUTPUT) { layer_create_msg_callback(my_data->report_data, report_flags, win32_debug_output_msg, NULL, &callback); my_data->logging_callback.push_back(callback); } if (!globalLockInitialized) { loader_platform_thread_create_mutex(&globalLock); globalLockInitialized = 1; } } static const VkLayerProperties shader_checker_global_layers[] = { { "ShaderChecker", VK_API_VERSION, VK_MAKE_VERSION(0, 1, 0), "Validation layer: ShaderChecker", } }; VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties( const char *pLayerName, uint32_t *pCount, VkExtensionProperties* pProperties) { /* shader checker does not have any global extensions */ return util_GetExtensionProperties(0, NULL, pCount, pProperties); } VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties( uint32_t *pCount, VkLayerProperties* pProperties) { return util_GetLayerProperties(ARRAY_SIZE(shader_checker_global_layers), shader_checker_global_layers, pCount, pProperties); } VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties( VkPhysicalDevice physicalDevice, const char* pLayerName, uint32_t* pCount, VkExtensionProperties* pProperties) { /* Shader checker does not have any physical device extensions */ if (pLayerName == NULL) { dispatch_key key = get_dispatch_key(physicalDevice); layer_data *my_data = get_my_data_ptr(key, layer_data_map); return my_data->instance_dispatch_table->EnumerateDeviceExtensionProperties( physicalDevice, NULL, pCount, pProperties); } else { return util_GetExtensionProperties(0, NULL, pCount, pProperties); } } VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties( VkPhysicalDevice physicalDevice, uint32_t* pCount, VkLayerProperties* pProperties) { /* Shader checker physical device layers are the same as global */ return util_GetLayerProperties(ARRAY_SIZE(shader_checker_global_layers), shader_checker_global_layers, pCount, pProperties); } static char const * storage_class_name(unsigned sc) { switch (sc) { case spv::StorageClassInput: return "input"; case spv::StorageClassOutput: return "output"; case spv::StorageClassUniformConstant: return "const uniform"; case spv::StorageClassUniform: return "uniform"; case spv::StorageClassWorkgroup: return "workgroup local"; case spv::StorageClassCrossWorkgroup: return "workgroup global"; case spv::StorageClassPrivate: return "private global"; case spv::StorageClassFunction: return "function"; case spv::StorageClassGeneric: return "generic"; case spv::StorageClassAtomicCounter: return "atomic counter"; case spv::StorageClassImage: return "image"; default: return "unknown"; } } /* returns ptr to null terminator */ static char * describe_type(char *dst, shader_module const *src, unsigned type) { auto type_def_it = src->type_def_index.find(type); if (type_def_it == src->type_def_index.end()) { return dst + sprintf(dst, "undef"); } unsigned int const *code = (unsigned int const *)&src->words[type_def_it->second]; unsigned opcode = code[0] & 0x0ffffu; switch (opcode) { case spv::OpTypeBool: return dst + sprintf(dst, "bool"); case spv::OpTypeInt: return dst + sprintf(dst, "%cint%d", code[3] ? 's' : 'u', code[2]); case spv::OpTypeFloat: return dst + sprintf(dst, "float%d", code[2]); case spv::OpTypeVector: dst += sprintf(dst, "vec%d of ", code[3]); return describe_type(dst, src, code[2]); case spv::OpTypeMatrix: dst += sprintf(dst, "mat%d of ", code[3]); return describe_type(dst, src, code[2]); case spv::OpTypeArray: dst += sprintf(dst, "arr[%d] of ", code[3]); return describe_type(dst, src, code[2]); case spv::OpTypePointer: dst += sprintf(dst, "ptr to %s ", storage_class_name(code[2])); return describe_type(dst, src, code[3]); case spv::OpTypeStruct: { unsigned oplen = code[0] >> 16; dst += sprintf(dst, "struct of ("); for (unsigned i = 2; i < oplen; i++) { dst = describe_type(dst, src, code[i]); dst += sprintf(dst, i == oplen-1 ? ")" : ", "); } return dst; } case spv::OpTypeSampler: return dst + sprintf(dst, "sampler"); default: return dst + sprintf(dst, "oddtype"); } } static bool types_match(shader_module const *a, shader_module const *b, unsigned a_type, unsigned b_type, bool b_arrayed) { auto a_type_def_it = a->type_def_index.find(a_type); auto b_type_def_it = b->type_def_index.find(b_type); if (a_type_def_it == a->type_def_index.end()) { return false; } if (b_type_def_it == b->type_def_index.end()) { return false; } /* walk two type trees together, and complain about differences */ unsigned int const *a_code = (unsigned int const *)&a->words[a_type_def_it->second]; unsigned int const *b_code = (unsigned int const *)&b->words[b_type_def_it->second]; unsigned a_opcode = a_code[0] & 0x0ffffu; unsigned b_opcode = b_code[0] & 0x0ffffu; if (b_arrayed && b_opcode == spv::OpTypeArray) { /* we probably just found the extra level of arrayness in b_type: compare the type inside it to a_type */ return types_match(a, b, a_type, b_code[2], false); } if (a_opcode != b_opcode) { return false; } switch (a_opcode) { /* if b_arrayed and we hit a leaf type, then we can't match -- there's nowhere for the extra OpTypeArray to be! */ case spv::OpTypeBool: return true && !b_arrayed; case spv::OpTypeInt: /* match on width, signedness */ return a_code[2] == b_code[2] && a_code[3] == b_code[3] && !b_arrayed; case spv::OpTypeFloat: /* match on width */ return a_code[2] == b_code[2] && !b_arrayed; case spv::OpTypeVector: case spv::OpTypeMatrix: case spv::OpTypeArray: /* match on element type, count. these all have the same layout. we don't get here if * b_arrayed -- that is handled above. */ return !b_arrayed && types_match(a, b, a_code[2], b_code[2], b_arrayed) && a_code[3] == b_code[3]; case spv::OpTypeStruct: /* match on all element types */ { if (b_arrayed) { /* for the purposes of matching different levels of arrayness, structs are leaves. */ return false; } unsigned a_len = a_code[0] >> 16; unsigned b_len = b_code[0] >> 16; if (a_len != b_len) { return false; /* structs cannot match if member counts differ */ } for (unsigned i = 2; i < a_len; i++) { if (!types_match(a, b, a_code[i], b_code[i], b_arrayed)) { return false; } } return true; } case spv::OpTypePointer: /* match on pointee type. storage class is expected to differ */ return types_match(a, b, a_code[3], b_code[3], b_arrayed); default: /* remaining types are CLisms, or may not appear in the interfaces we * are interested in. Just claim no match. */ return false; } } static int value_or_default(std::unordered_map const &map, unsigned id, int def) { auto it = map.find(id); if (it == map.end()) return def; else return it->second; } static unsigned get_locations_consumed_by_type(shader_module const *src, unsigned type, bool strip_array_level) { auto type_def_it = src->type_def_index.find(type); if (type_def_it == src->type_def_index.end()) { return 1; /* This is actually broken SPIR-V... */ } unsigned int const *code = (unsigned int const *)&src->words[type_def_it->second]; unsigned opcode = code[0] & 0x0ffffu; switch (opcode) { case spv::OpTypePointer: /* see through the ptr -- this is only ever at the toplevel for graphics shaders; * we're never actually passing pointers around. */ return get_locations_consumed_by_type(src, code[3], strip_array_level); case spv::OpTypeArray: if (strip_array_level) { return get_locations_consumed_by_type(src, code[2], false); } else { return code[3] * get_locations_consumed_by_type(src, code[2], false); } case spv::OpTypeMatrix: /* num locations is the dimension * element size */ return code[3] * get_locations_consumed_by_type(src, code[2], false); default: /* everything else is just 1. */ return 1; /* TODO: extend to handle 64bit scalar types, whose vectors may need * multiple locations. */ } } struct interface_var { uint32_t id; uint32_t type_id; uint32_t offset; /* TODO: collect the name, too? Isn't required to be present. */ }; static void collect_interface_by_location(layer_data *my_data, VkDevice dev, shader_module const *src, spv::StorageClass sinterface, std::map &out, std::map &builtins_out, bool is_array_of_verts) { unsigned int const *code = (unsigned int const *)&src->words[0]; size_t size = src->words.size(); std::unordered_map var_locations; std::unordered_map var_builtins; unsigned word = 5; while (word < size) { unsigned opcode = code[word] & 0x0ffffu; unsigned oplen = (code[word] & 0xffff0000u) >> 16; /* We consider two interface models: SSO rendezvous-by-location, and * builtins. Complain about anything that fits neither model. */ if (opcode == spv::OpDecorate) { if (code[word+2] == spv::DecorationLocation) { var_locations[code[word+1]] = code[word+3]; } if (code[word+2] == spv::DecorationBuiltIn) { var_builtins[code[word+1]] = code[word+3]; } } /* TODO: handle grouped decorations */ /* TODO: handle index=1 dual source outputs from FS -- two vars will * have the same location, and we DONT want to clobber. */ if (opcode == spv::OpVariable && code[word+3] == sinterface) { unsigned id = code[word+2]; unsigned type = code[word+1]; int location = value_or_default(var_locations, code[word+2], -1); int builtin = value_or_default(var_builtins, code[word+2], -1); if (location == -1 && builtin == -1) { /* No location defined, and not bound to an API builtin. * The spec says nothing about how this case works (or doesn't) * for interface matching. */ log_msg(my_data->report_data, VK_DBG_REPORT_WARN_BIT, VK_OBJECT_TYPE_DEVICE, /*dev*/0, 0, SHADER_CHECKER_INCONSISTENT_SPIRV, "SC", "var %d (type %d) in %s interface has no Location or Builtin decoration", code[word+2], code[word+1], storage_class_name(sinterface)); } else if (location != -1) { /* A user-defined interface variable, with a location. Where a variable * occupied multiple locations, emit one result for each. */ unsigned num_locations = get_locations_consumed_by_type(src, type, is_array_of_verts); for (int offset = 0; offset < num_locations; offset++) { interface_var v; v.id = id; v.type_id = type; v.offset = offset; out[location + offset] = v; } } else { /* A builtin interface variable */ /* Note that since builtin interface variables do not consume numbered * locations, there is no larger-than-vec4 consideration as above */ interface_var v; v.id = id; v.type_id = type; v.offset = 0; builtins_out[builtin] = v; } } word += oplen; } } static void collect_interface_by_descriptor_slot(layer_data *my_data, VkDevice dev, shader_module const *src, spv::StorageClass sinterface, std::map, interface_var> &out) { unsigned int const *code = (unsigned int const *)&src->words[0]; size_t size = src->words.size(); std::unordered_map var_sets; std::unordered_map var_bindings; unsigned word = 5; while (word < size) { unsigned opcode = code[word] & 0x0ffffu; unsigned oplen = (code[word] & 0xffff0000u) >> 16; /* All variables in the Uniform or UniformConstant storage classes are required to be decorated with both * DecorationDescriptorSet and DecorationBinding. */ if (opcode == spv::OpDecorate) { if (code[word+2] == spv::DecorationDescriptorSet) { var_sets[code[word+1]] = code[word+3]; } if (code[word+2] == spv::DecorationBinding) { var_bindings[code[word+1]] = code[word+3]; } } if (opcode == spv::OpVariable && (code[word+3] == spv::StorageClassUniform || code[word+3] == spv::StorageClassUniformConstant)) { unsigned set = value_or_default(var_sets, code[word+2], 0); unsigned binding = value_or_default(var_bindings, code[word+2], 0); auto existing_it = out.find(std::make_pair(set, binding)); if (existing_it != out.end()) { /* conflict within spv image */ log_msg(my_data->report_data, VK_DBG_REPORT_ERROR_BIT, VK_OBJECT_TYPE_DEVICE, /*dev*/0, 0, SHADER_CHECKER_INCONSISTENT_SPIRV, "SC", "var %d (type %d) in %s interface in descriptor slot (%u,%u) conflicts with existing definition", code[word+2], code[word+1], storage_class_name(sinterface), existing_it->first.first, existing_it->first.second); } interface_var v; v.id = code[word+2]; v.type_id = code[word+1]; out[std::make_pair(set, binding)] = v; } word += oplen; } } VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkCreateShaderModule( VkDevice device, const VkShaderModuleCreateInfo *pCreateInfo, const VkAllocationCallbacks* pAllocator, VkShaderModule *pShaderModule) { layer_data *my_data = get_my_data_ptr(get_dispatch_key(device), layer_data_map); bool skip_call = false; if (!shader_is_spirv(pCreateInfo)) { skip_call |= log_msg(my_data->report_data, VK_DBG_REPORT_ERROR_BIT, VK_OBJECT_TYPE_DEVICE, /* dev */ 0, 0, SHADER_CHECKER_NON_SPIRV_SHADER, "SC", "Shader is not SPIR-V"); } if (skip_call) return VK_ERROR_VALIDATION_FAILED; VkResult res = my_data->device_dispatch_table->CreateShaderModule(device, pCreateInfo, pAllocator, pShaderModule); if (res == VK_SUCCESS) { loader_platform_thread_lock_mutex(&globalLock); my_data->shader_module_map[*pShaderModule] = new shader_module(pCreateInfo); loader_platform_thread_unlock_mutex(&globalLock); } return res; } VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkCreateRenderPass( VkDevice device, const VkRenderPassCreateInfo *pCreateInfo, const VkAllocationCallbacks* pAllocator, VkRenderPass *pRenderPass) { layer_data *my_data = get_my_data_ptr(get_dispatch_key(device), layer_data_map); VkResult res = my_data->device_dispatch_table->CreateRenderPass(device, pCreateInfo, pAllocator, pRenderPass); loader_platform_thread_lock_mutex(&globalLock); my_data->render_pass_map[*pRenderPass] = new render_pass(pCreateInfo); loader_platform_thread_unlock_mutex(&globalLock); return res; } static bool validate_interface_between_stages(layer_data *my_data, VkDevice dev, shader_module const *producer, char const *producer_name, shader_module const *consumer, char const *consumer_name, bool consumer_arrayed_input) { std::map outputs; std::map inputs; std::map builtin_outputs; std::map builtin_inputs; bool pass = true; collect_interface_by_location(my_data, dev, producer, spv::StorageClassOutput, outputs, builtin_outputs, false); collect_interface_by_location(my_data, dev, consumer, spv::StorageClassInput, inputs, builtin_inputs, consumer_arrayed_input); auto a_it = outputs.begin(); auto b_it = inputs.begin(); /* maps sorted by key (location); walk them together to find mismatches */ while ((outputs.size() > 0 && a_it != outputs.end()) || ( inputs.size() && b_it != inputs.end())) { bool a_at_end = outputs.size() == 0 || a_it == outputs.end(); bool b_at_end = inputs.size() == 0 || b_it == inputs.end(); auto a_first = a_at_end ? 0 : a_it->first; auto b_first = b_at_end ? 0 : b_it->first; if (b_at_end || ((!a_at_end) && (a_first < b_first))) { if (log_msg(my_data->report_data, VK_DBG_REPORT_PERF_WARN_BIT, VK_OBJECT_TYPE_DEVICE, /*dev*/0, 0, SHADER_CHECKER_OUTPUT_NOT_CONSUMED, "SC", "%s writes to output location %d which is not consumed by %s", producer_name, a_first, consumer_name)) { pass = false; } a_it++; } else if (a_at_end || a_first > b_first) { if (log_msg(my_data->report_data, VK_DBG_REPORT_ERROR_BIT, VK_OBJECT_TYPE_DEVICE, /*dev*/0, 0, SHADER_CHECKER_INPUT_NOT_PRODUCED, "SC", "%s consumes input location %d which is not written by %s", consumer_name, b_first, producer_name)) { pass = false; } b_it++; } else { if (types_match(producer, consumer, a_it->second.type_id, b_it->second.type_id, consumer_arrayed_input)) { /* OK! */ } else { char producer_type[1024]; char consumer_type[1024]; describe_type(producer_type, producer, a_it->second.type_id); describe_type(consumer_type, consumer, b_it->second.type_id); if (log_msg(my_data->report_data, VK_DBG_REPORT_ERROR_BIT, VK_OBJECT_TYPE_DEVICE, /*dev*/0, 0, SHADER_CHECKER_INTERFACE_TYPE_MISMATCH, "SC", "Type mismatch on location %d: '%s' vs '%s'", a_it->first, producer_type, consumer_type)) { pass = false; } } a_it++; b_it++; } } return pass; } enum FORMAT_TYPE { FORMAT_TYPE_UNDEFINED, FORMAT_TYPE_FLOAT, /* UNORM, SNORM, FLOAT, USCALED, SSCALED, SRGB -- anything we consider float in the shader */ FORMAT_TYPE_SINT, FORMAT_TYPE_UINT, }; static unsigned get_format_type(VkFormat fmt) { switch (fmt) { case VK_FORMAT_UNDEFINED: return FORMAT_TYPE_UNDEFINED; case VK_FORMAT_R8_SINT: case VK_FORMAT_R8G8_SINT: case VK_FORMAT_R8G8B8_SINT: case VK_FORMAT_R8G8B8A8_SINT: case VK_FORMAT_R16_SINT: case VK_FORMAT_R16G16_SINT: case VK_FORMAT_R16G16B16_SINT: case VK_FORMAT_R16G16B16A16_SINT: case VK_FORMAT_R32_SINT: case VK_FORMAT_R32G32_SINT: case VK_FORMAT_R32G32B32_SINT: case VK_FORMAT_R32G32B32A32_SINT: case VK_FORMAT_B8G8R8_SINT: case VK_FORMAT_B8G8R8A8_SINT: case VK_FORMAT_A2B10G10R10_SINT_PACK32: case VK_FORMAT_A2R10G10B10_SINT_PACK32: return FORMAT_TYPE_SINT; case VK_FORMAT_R8_UINT: case VK_FORMAT_R8G8_UINT: case VK_FORMAT_R8G8B8_UINT: case VK_FORMAT_R8G8B8A8_UINT: case VK_FORMAT_R16_UINT: case VK_FORMAT_R16G16_UINT: case VK_FORMAT_R16G16B16_UINT: case VK_FORMAT_R16G16B16A16_UINT: case VK_FORMAT_R32_UINT: case VK_FORMAT_R32G32_UINT: case VK_FORMAT_R32G32B32_UINT: case VK_FORMAT_R32G32B32A32_UINT: case VK_FORMAT_B8G8R8_UINT: case VK_FORMAT_B8G8R8A8_UINT: case VK_FORMAT_A2B10G10R10_UINT_PACK32: case VK_FORMAT_A2R10G10B10_UINT_PACK32: return FORMAT_TYPE_UINT; default: return FORMAT_TYPE_FLOAT; } } /* characterizes a SPIR-V type appearing in an interface to a FF stage, * for comparison to a VkFormat's characterization above. */ static unsigned get_fundamental_type(shader_module const *src, unsigned type) { auto type_def_it = src->type_def_index.find(type); if (type_def_it == src->type_def_index.end()) { return FORMAT_TYPE_UNDEFINED; } unsigned int const *code = (unsigned int const *)&src->words[type_def_it->second]; unsigned opcode = code[0] & 0x0ffffu; switch (opcode) { case spv::OpTypeInt: return code[3] ? FORMAT_TYPE_SINT : FORMAT_TYPE_UINT; case spv::OpTypeFloat: return FORMAT_TYPE_FLOAT; case spv::OpTypeVector: return get_fundamental_type(src, code[2]); case spv::OpTypeMatrix: return get_fundamental_type(src, code[2]); case spv::OpTypeArray: return get_fundamental_type(src, code[2]); case spv::OpTypePointer: return get_fundamental_type(src, code[3]); default: return FORMAT_TYPE_UNDEFINED; } } static bool validate_vi_consistency(layer_data *my_data, VkDevice dev, VkPipelineVertexInputStateCreateInfo const *vi) { /* walk the binding descriptions, which describe the step rate and stride of each vertex buffer. * each binding should be specified only once. */ std::unordered_map bindings; bool pass = true; for (unsigned i = 0; i < vi->vertexBindingDescriptionCount; i++) { auto desc = &vi->pVertexBindingDescriptions[i]; auto & binding = bindings[desc->binding]; if (binding) { if (log_msg(my_data->report_data, VK_DBG_REPORT_ERROR_BIT, VK_OBJECT_TYPE_DEVICE, /*dev*/0, 0, SHADER_CHECKER_INCONSISTENT_VI, "SC", "Duplicate vertex input binding descriptions for binding %d", desc->binding)) { pass = false; } } else { binding = desc; } } return pass; } static bool validate_vi_against_vs_inputs(layer_data *my_data, VkDevice dev, VkPipelineVertexInputStateCreateInfo const *vi, shader_module const *vs) { std::map inputs; /* we collect builtin inputs, but they will never appear in the VI state -- * the vs builtin inputs are generated in the pipeline, not sourced from buffers (VertexID, etc) */ std::map builtin_inputs; bool pass = true; collect_interface_by_location(my_data, dev, vs, spv::StorageClassInput, inputs, builtin_inputs, false); /* Build index by location */ std::map attribs; if (vi) { for (unsigned i = 0; i < vi->vertexAttributeDescriptionCount; i++) attribs[vi->pVertexAttributeDescriptions[i].location] = &vi->pVertexAttributeDescriptions[i]; } auto it_a = attribs.begin(); auto it_b = inputs.begin(); while ((attribs.size() > 0 && it_a != attribs.end()) || (inputs.size() > 0 && it_b != inputs.end())) { bool a_at_end = attribs.size() == 0 || it_a == attribs.end(); bool b_at_end = inputs.size() == 0 || it_b == inputs.end(); auto a_first = a_at_end ? 0 : it_a->first; auto b_first = b_at_end ? 0 : it_b->first; if (b_at_end || a_first < b_first) { if (log_msg(my_data->report_data, VK_DBG_REPORT_PERF_WARN_BIT, VK_OBJECT_TYPE_DEVICE, /*dev*/0, 0, SHADER_CHECKER_OUTPUT_NOT_CONSUMED, "SC", "Vertex attribute at location %d not consumed by VS", a_first)) { pass = false; } it_a++; } else if (a_at_end || b_first < a_first) { if (log_msg(my_data->report_data, VK_DBG_REPORT_ERROR_BIT, VK_OBJECT_TYPE_DEVICE, /*dev*/0, 0, SHADER_CHECKER_INPUT_NOT_PRODUCED, "SC", "VS consumes input at location %d but not provided", b_first)) { pass = false; } it_b++; } else { unsigned attrib_type = get_format_type(it_a->second->format); unsigned input_type = get_fundamental_type(vs, it_b->second.type_id); /* type checking */ if (attrib_type != FORMAT_TYPE_UNDEFINED && input_type != FORMAT_TYPE_UNDEFINED && attrib_type != input_type) { char vs_type[1024]; describe_type(vs_type, vs, it_b->second.type_id); if (log_msg(my_data->report_data, VK_DBG_REPORT_ERROR_BIT, VK_OBJECT_TYPE_DEVICE, /*dev*/0, 0, SHADER_CHECKER_INTERFACE_TYPE_MISMATCH, "SC", "Attribute type of `%s` at location %d does not match VS input type of `%s`", string_VkFormat(it_a->second->format), a_first, vs_type)) { pass = false; } } /* OK! */ it_a++; it_b++; } } return pass; } static bool validate_fs_outputs_against_render_pass(layer_data *my_data, VkDevice dev, shader_module const *fs, render_pass const *rp, uint32_t subpass) { const std::vector &color_formats = rp->subpass_color_formats[subpass]; std::map outputs; std::map builtin_outputs; bool pass = true; /* TODO: dual source blend index (spv::DecIndex, zero if not provided) */ collect_interface_by_location(my_data, dev, fs, spv::StorageClassOutput, outputs, builtin_outputs, false); auto it = outputs.begin(); uint32_t attachment = 0; /* Walk attachment list and outputs together -- this is a little overpowered since attachments * are currently dense, but the parallel with matching between shader stages is nice. */ /* TODO: Figure out compile error with cb->attachmentCount */ while ((outputs.size() > 0 && it != outputs.end()) || attachment < color_formats.size()) { if (attachment == color_formats.size() || ( it != outputs.end() && it->first < attachment)) { if (log_msg(my_data->report_data, VK_DBG_REPORT_WARN_BIT, VK_OBJECT_TYPE_DEVICE, /*dev*/0, 0, SHADER_CHECKER_OUTPUT_NOT_CONSUMED, "SC", "FS writes to output location %d with no matching attachment", it->first)) { pass = false; } it++; } else if (it == outputs.end() || it->first > attachment) { if (log_msg(my_data->report_data, VK_DBG_REPORT_ERROR_BIT, VK_OBJECT_TYPE_DEVICE, /*dev*/0, 0, SHADER_CHECKER_INPUT_NOT_PRODUCED, "SC", "Attachment %d not written by FS", attachment)) { pass = false; } attachment++; } else { unsigned output_type = get_fundamental_type(fs, it->second.type_id); unsigned att_type = get_format_type(color_formats[attachment]); /* type checking */ if (att_type != FORMAT_TYPE_UNDEFINED && output_type != FORMAT_TYPE_UNDEFINED && att_type != output_type) { char fs_type[1024]; describe_type(fs_type, fs, it->second.type_id); if (log_msg(my_data->report_data, VK_DBG_REPORT_ERROR_BIT, VK_OBJECT_TYPE_DEVICE, /*dev*/0, 0, SHADER_CHECKER_INTERFACE_TYPE_MISMATCH, "SC", "Attachment %d of type `%s` does not match FS output type of `%s`", attachment, string_VkFormat(color_formats[attachment]), fs_type)) { pass = false; } } /* OK! */ it++; attachment++; } } return pass; } struct shader_stage_attributes { char const * const name; bool arrayed_input; }; static shader_stage_attributes shader_stage_attribs[] = { { "vertex shader", false }, { "tessellation control shader", true }, { "tessellation evaluation shader", false }, { "geometry shader", true }, { "fragment shader", false }, }; static bool has_descriptor_binding(std::vector*>* layout, std::pair slot) { if (!layout) return false; if (slot.first >= layout->size()) return false; auto set = (*layout)[slot.first]; return (set->find(slot.second) != set->end()); } static uint32_t get_shader_stage_id(VkShaderStageFlagBits stage) { uint32_t bit_pos = u_ffs(stage); return bit_pos-1; } static bool validate_graphics_pipeline(layer_data *my_data, VkDevice dev, VkGraphicsPipelineCreateInfo const *pCreateInfo) { /* We seem to allow pipeline stages to be specified out of order, so collect and identify them * before trying to do anything more: */ int vertex_stage = get_shader_stage_id(VK_SHADER_STAGE_VERTEX_BIT); int geometry_stage = get_shader_stage_id(VK_SHADER_STAGE_GEOMETRY_BIT); int fragment_stage = get_shader_stage_id(VK_SHADER_STAGE_FRAGMENT_BIT); shader_module **shaders = new shader_module*[fragment_stage + 1]; /* exclude CS */ memset(shaders, 0, sizeof(shader_module *) * (fragment_stage +1)); render_pass const *rp = 0; VkPipelineVertexInputStateCreateInfo const *vi = 0; bool pass = true; loader_platform_thread_lock_mutex(&globalLock); for (uint32_t i = 0; i < pCreateInfo->stageCount; i++) { VkPipelineShaderStageCreateInfo const *pStage = &pCreateInfo->pStages[i]; if (pStage->sType == VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO) { if ((pStage->stage & (VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT)) == 0) { if (log_msg(my_data->report_data, VK_DBG_REPORT_WARN_BIT, VK_OBJECT_TYPE_DEVICE, /*dev*/0, 0, SHADER_CHECKER_UNKNOWN_STAGE, "SC", "Unknown shader stage %d", pStage->stage)) { pass = false; } } else { shader_module *module = my_data->shader_module_map[pStage->module]; shaders[get_shader_stage_id(pStage->stage)] = module; /* validate descriptor set layout against what the spirv module actually uses */ std::map, interface_var> descriptor_uses; collect_interface_by_descriptor_slot(my_data, dev, module, spv::StorageClassUniform, descriptor_uses); auto layout = pCreateInfo->layout != VK_NULL_HANDLE ? my_data->pipeline_layout_map[pCreateInfo->layout] : nullptr; for (auto it = descriptor_uses.begin(); it != descriptor_uses.end(); it++) { /* find the matching binding */ auto found = has_descriptor_binding(layout, it->first); if (!found) { char type_name[1024]; describe_type(type_name, module, it->second.type_id); if (log_msg(my_data->report_data, VK_DBG_REPORT_ERROR_BIT, VK_OBJECT_TYPE_DEVICE, /*dev*/0, 0, SHADER_CHECKER_MISSING_DESCRIPTOR, "SC", "Shader uses descriptor slot %u.%u (used as type `%s`) but not declared in pipeline layout", it->first.first, it->first.second, type_name)) { pass = false; } } } } } } if (pCreateInfo->renderPass != VK_NULL_HANDLE) rp = my_data->render_pass_map[pCreateInfo->renderPass]; vi = pCreateInfo->pVertexInputState; if (vi) { pass = validate_vi_consistency(my_data, dev, vi) && pass; } if (shaders[vertex_stage]) { pass = validate_vi_against_vs_inputs(my_data, dev, vi, shaders[vertex_stage]) && pass; } /* TODO: enforce rules about present combinations of shaders */ int producer = get_shader_stage_id(VK_SHADER_STAGE_VERTEX_BIT); int consumer = get_shader_stage_id(VK_SHADER_STAGE_GEOMETRY_BIT); while (!shaders[producer] && producer != fragment_stage) { producer++; consumer++; } for (; producer != fragment_stage && consumer <= fragment_stage; consumer++) { assert(shaders[producer]); if (shaders[consumer]) { pass = validate_interface_between_stages(my_data, dev, shaders[producer], shader_stage_attribs[producer].name, shaders[consumer], shader_stage_attribs[consumer].name, shader_stage_attribs[consumer].arrayed_input) && pass; producer = consumer; } } if (shaders[fragment_stage] && rp) { pass = validate_fs_outputs_against_render_pass(my_data, dev, shaders[fragment_stage], rp, pCreateInfo->subpass) && pass; } delete shaders; loader_platform_thread_unlock_mutex(&globalLock); return pass; } //TODO handle pipelineCache entry points VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkCreateGraphicsPipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t count, const VkGraphicsPipelineCreateInfo *pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline *pPipelines) { layer_data *my_data = get_my_data_ptr(get_dispatch_key(device), layer_data_map); bool pass = true; for (uint32_t i = 0; i < count; i++) { pass = validate_graphics_pipeline(my_data, device, &pCreateInfos[i]) && pass; } if (pass) { /* The driver is allowed to crash if passed junk. Only actually create the * pipeline if we didn't run into any showstoppers above. */ return my_data->device_dispatch_table->CreateGraphicsPipelines(device, pipelineCache, count, pCreateInfos, pAllocator, pPipelines); } else { return VK_ERROR_VALIDATION_FAILED; } } VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(VkPhysicalDevice gpu, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) { layer_data *my_device_data = get_my_data_ptr(get_dispatch_key(*pDevice), layer_data_map); VkResult result = my_device_data->device_dispatch_table->CreateDevice(gpu, pCreateInfo, pAllocator, pDevice); if (result == VK_SUCCESS) { layer_data *my_instance_data = get_my_data_ptr(get_dispatch_key(gpu), layer_data_map); my_device_data->report_data = layer_debug_report_create_device(my_instance_data->report_data, *pDevice); } return result; } /* hook DextroyDevice to remove tableMap entry */ VK_LAYER_EXPORT VKAPI_ATTR void VKAPI_CALL vkDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) { dispatch_key key = get_dispatch_key(device); layer_data *my_device_data = get_my_data_ptr(key, layer_data_map); my_device_data->device_dispatch_table->DestroyDevice(device, pAllocator); delete my_device_data->device_dispatch_table; layer_data_map.erase(key); } VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance( const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance) { layer_data *my_data = get_my_data_ptr(get_dispatch_key(*pInstance), layer_data_map); VkLayerInstanceDispatchTable *pTable = my_data->instance_dispatch_table; VkResult result = pTable->CreateInstance(pCreateInfo, pAllocator, pInstance); if (result == VK_SUCCESS) { layer_data *my_data = get_my_data_ptr(get_dispatch_key(*pInstance), layer_data_map); my_data->report_data = debug_report_create_instance( pTable, *pInstance, pCreateInfo->enabledExtensionNameCount, pCreateInfo->ppEnabledExtensionNames); init_shader_checker(my_data); } return result; } /* hook DestroyInstance to remove tableInstanceMap entry */ VK_LAYER_EXPORT VKAPI_ATTR void VKAPI_CALL vkDestroyInstance(VkInstance instance, const VkAllocationCallbacks* pAllocator) { dispatch_key key = get_dispatch_key(instance); layer_data *my_data = get_my_data_ptr(key, layer_data_map); my_data->instance_dispatch_table->DestroyInstance(instance, pAllocator); // Clean up logging callback, if any while (my_data->logging_callback.size() > 0) { VkDbgMsgCallback callback = my_data->logging_callback.back(); layer_destroy_msg_callback(my_data->report_data, callback); my_data->logging_callback.pop_back(); } layer_debug_report_destroy_instance(my_data->report_data); delete my_data->instance_dispatch_table; layer_data_map.erase(key); if (layer_data_map.empty()) { // Release mutex when destroying last instance. loader_platform_thread_delete_mutex(&globalLock); globalLockInitialized = 0; } } VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkDbgCreateMsgCallback( VkInstance instance, VkFlags msgFlags, const PFN_vkDbgMsgCallback pfnMsgCallback, void* pUserData, VkDbgMsgCallback* pMsgCallback) { layer_data *my_data = get_my_data_ptr(get_dispatch_key(instance), layer_data_map); VkResult res = my_data->instance_dispatch_table->DbgCreateMsgCallback(instance, msgFlags, pfnMsgCallback, pUserData, pMsgCallback); if (VK_SUCCESS == res) { res = layer_create_msg_callback(my_data->report_data, msgFlags, pfnMsgCallback, pUserData, pMsgCallback); } return res; } VK_LAYER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkDbgDestroyMsgCallback( VkInstance instance, VkDbgMsgCallback msgCallback) { layer_data *my_data = get_my_data_ptr(get_dispatch_key(instance), layer_data_map); VkResult res = my_data->instance_dispatch_table->DbgDestroyMsgCallback(instance, msgCallback); layer_destroy_msg_callback(my_data->report_data, msgCallback); return res; } VK_LAYER_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(VkDevice dev, const char* funcName) { if (dev == NULL) return NULL; layer_data *my_data; /* loader uses this to force layer initialization; device object is wrapped */ if (!strcmp("vkGetDeviceProcAddr", funcName)) { VkBaseLayerObject* wrapped_dev = (VkBaseLayerObject*) dev; my_data = get_my_data_ptr(get_dispatch_key(wrapped_dev->baseObject), layer_data_map); my_data->device_dispatch_table = new VkLayerDispatchTable; layer_initialize_dispatch_table(my_data->device_dispatch_table, wrapped_dev); return (PFN_vkVoidFunction) vkGetDeviceProcAddr; } my_data = get_my_data_ptr(get_dispatch_key(dev), layer_data_map); #define ADD_HOOK(fn) \ if (!strncmp(#fn, funcName, sizeof(#fn))) \ return (PFN_vkVoidFunction) fn ADD_HOOK(vkCreateDevice); ADD_HOOK(vkCreateShaderModule); ADD_HOOK(vkCreateRenderPass); ADD_HOOK(vkDestroyDevice); ADD_HOOK(vkCreateGraphicsPipelines); ADD_HOOK(vkCreateDescriptorSetLayout); ADD_HOOK(vkCreatePipelineLayout); #undef ADD_HOOK VkLayerDispatchTable* pTable = my_data->device_dispatch_table; { if (pTable->GetDeviceProcAddr == NULL) return NULL; return pTable->GetDeviceProcAddr(dev, funcName); } } VK_LAYER_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, const char* funcName) { PFN_vkVoidFunction fptr; if (instance == NULL) return NULL; layer_data *my_data; if (!strcmp("vkGetInstanceProcAddr", funcName)) { VkBaseLayerObject* wrapped_inst = (VkBaseLayerObject*) instance; my_data = get_my_data_ptr(get_dispatch_key(wrapped_inst->baseObject), layer_data_map); my_data->instance_dispatch_table = new VkLayerInstanceDispatchTable; layer_init_instance_dispatch_table(my_data->instance_dispatch_table, wrapped_inst); return (PFN_vkVoidFunction) vkGetInstanceProcAddr; } #define ADD_HOOK(fn) \ if (!strncmp(#fn, funcName, sizeof(#fn))) \ return (PFN_vkVoidFunction) fn ADD_HOOK(vkCreateInstance); ADD_HOOK(vkDestroyInstance); ADD_HOOK(vkEnumerateInstanceExtensionProperties); ADD_HOOK(vkEnumerateDeviceExtensionProperties); ADD_HOOK(vkEnumerateInstanceLayerProperties); ADD_HOOK(vkEnumerateDeviceLayerProperties); #undef ADD_HOOK my_data = get_my_data_ptr(get_dispatch_key(instance), layer_data_map); fptr = debug_report_get_instance_proc_addr(my_data->report_data, funcName); if (fptr) return fptr; { VkLayerInstanceDispatchTable* pTable = my_data->instance_dispatch_table; if (pTable->GetInstanceProcAddr == NULL) return NULL; return pTable->GetInstanceProcAddr(instance, funcName); } }