/* Copyright (c) 2015-2016 The Khronos Group Inc. * Copyright (c) 2015-2016 Valve Corporation * Copyright (c) 2015-2016 LunarG, 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. * * Author: Cody Northrop * Author: Mike Stroyan */ #ifndef THREADING_H #define THREADING_H #include #include #include #include "vk_layer_config.h" #include "vk_layer_logging.h" #if defined(__LP64__) || defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || \ defined(__aarch64__) || defined(__powerpc64__) // If pointers are 64-bit, then there can be separate counters for each // NONDISPATCHABLE_HANDLE type. Otherwise they are all typedef uint64_t. #define DISTINCT_NONDISPATCHABLE_HANDLES #endif // Draw State ERROR codes enum THREADING_CHECKER_ERROR { THREADING_CHECKER_NONE, // Used for INFO & other non-error messages THREADING_CHECKER_MULTIPLE_THREADS, // Object used simultaneously by multiple threads THREADING_CHECKER_SINGLE_THREAD_REUSE, // Object used simultaneously by recursion in single thread }; struct object_use_data { loader_platform_thread_id thread; int reader_count; int writer_count; }; struct layer_data; namespace threading { volatile bool vulkan_in_use = false; volatile bool vulkan_multi_threaded = false; // starting check if an application is using vulkan from multiple threads. inline bool startMultiThread() { if (vulkan_multi_threaded) { return true; } if (vulkan_in_use) { vulkan_multi_threaded = true; return true; } vulkan_in_use = true; return false; } // finishing check if an application is using vulkan from multiple threads. inline void finishMultiThread() { vulkan_in_use = false; } } // namespace threading template class counter { public: const char *typeName; VkDebugReportObjectTypeEXT objectType; std::unordered_map uses; std::mutex counter_lock; std::condition_variable counter_condition; void startWrite(debug_report_data *report_data, T object) { if (object == VK_NULL_HANDLE) { return; } bool skipCall = false; loader_platform_thread_id tid = loader_platform_get_thread_id(); std::unique_lock lock(counter_lock); if (uses.find(object) == uses.end()) { // There is no current use of the object. Record writer thread. struct object_use_data *use_data = &uses[object]; use_data->reader_count = 0; use_data->writer_count = 1; use_data->thread = tid; } else { struct object_use_data *use_data = &uses[object]; if (use_data->reader_count == 0) { // There are no readers. Two writers just collided. if (use_data->thread != tid) { skipCall |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, objectType, (uint64_t)(object), 0, THREADING_CHECKER_MULTIPLE_THREADS, "THREADING", "THREADING ERROR : object of type %s is simultaneously used in thread %ld and thread %ld", typeName, use_data->thread, tid); if (skipCall) { // Wait for thread-safe access to object instead of skipping call. while (uses.find(object) != uses.end()) { counter_condition.wait(lock); } // There is now no current use of the object. Record writer thread. struct object_use_data *new_use_data = &uses[object]; new_use_data->thread = tid; new_use_data->reader_count = 0; new_use_data->writer_count = 1; } else { // Continue with an unsafe use of the object. use_data->thread = tid; use_data->writer_count += 1; } } else { // This is either safe multiple use in one call, or recursive use. // There is no way to make recursion safe. Just forge ahead. use_data->writer_count += 1; } } else { // There are readers. This writer collided with them. if (use_data->thread != tid) { skipCall |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, objectType, (uint64_t)(object), 0, THREADING_CHECKER_MULTIPLE_THREADS, "THREADING", "THREADING ERROR : object of type %s is simultaneously used in thread %ld and thread %ld", typeName, use_data->thread, tid); if (skipCall) { // Wait for thread-safe access to object instead of skipping call. while (uses.find(object) != uses.end()) { counter_condition.wait(lock); } // There is now no current use of the object. Record writer thread. struct object_use_data *new_use_data = &uses[object]; new_use_data->thread = tid; new_use_data->reader_count = 0; new_use_data->writer_count = 1; } else { // Continue with an unsafe use of the object. use_data->thread = tid; use_data->writer_count += 1; } } else { // This is either safe multiple use in one call, or recursive use. // There is no way to make recursion safe. Just forge ahead. use_data->writer_count += 1; } } } } void finishWrite(T object) { if (object == VK_NULL_HANDLE) { return; } // Object is no longer in use std::unique_lock lock(counter_lock); uses[object].writer_count -= 1; if ((uses[object].reader_count == 0) && (uses[object].writer_count == 0)) { uses.erase(object); } // Notify any waiting threads that this object may be safe to use lock.unlock(); counter_condition.notify_all(); } void startRead(debug_report_data *report_data, T object) { if (object == VK_NULL_HANDLE) { return; } bool skipCall = false; loader_platform_thread_id tid = loader_platform_get_thread_id(); std::unique_lock lock(counter_lock); if (uses.find(object) == uses.end()) { // There is no current use of the object. Record reader count struct object_use_data *use_data = &uses[object]; use_data->reader_count = 1; use_data->writer_count = 0; use_data->thread = tid; } else if (uses[object].writer_count > 0 && uses[object].thread != tid) { // There is a writer of the object. skipCall |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, objectType, (uint64_t)(object), 0, THREADING_CHECKER_MULTIPLE_THREADS, "THREADING", "THREADING ERROR : object of type %s is simultaneously used in thread %ld and thread %ld", typeName, uses[object].thread, tid); if (skipCall) { // Wait for thread-safe access to object instead of skipping call. while (uses.find(object) != uses.end()) { counter_condition.wait(lock); } // There is no current use of the object. Record reader count struct object_use_data *use_data = &uses[object]; use_data->reader_count = 1; use_data->writer_count = 0; use_data->thread = tid; } else { uses[object].reader_count += 1; } } else { // There are other readers of the object. Increase reader count uses[object].reader_count += 1; } } void finishRead(T object) { if (object == VK_NULL_HANDLE) { return; } std::unique_lock lock(counter_lock); uses[object].reader_count -= 1; if ((uses[object].reader_count == 0) && (uses[object].writer_count == 0)) { uses.erase(object); } // Notify any waiting threads that this object may be safe to use lock.unlock(); counter_condition.notify_all(); } counter(const char *name = "", VkDebugReportObjectTypeEXT type = VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT) { typeName = name; objectType = type; } }; struct layer_data { VkInstance instance; debug_report_data *report_data; std::vector logging_callback; VkLayerDispatchTable *device_dispatch_table; VkLayerInstanceDispatchTable *instance_dispatch_table; // The following are for keeping track of the temporary callbacks that can // be used in vkCreateInstance and vkDestroyInstance: uint32_t num_tmp_callbacks; VkDebugReportCallbackCreateInfoEXT *tmp_dbg_create_infos; VkDebugReportCallbackEXT *tmp_callbacks; counter c_VkCommandBuffer; counter c_VkDevice; counter c_VkInstance; counter c_VkQueue; #ifdef DISTINCT_NONDISPATCHABLE_HANDLES counter c_VkBuffer; counter c_VkBufferView; counter c_VkCommandPool; counter c_VkDescriptorPool; counter c_VkDescriptorSet; counter c_VkDescriptorSetLayout; counter c_VkDeviceMemory; counter c_VkEvent; counter c_VkFence; counter c_VkFramebuffer; counter c_VkImage; counter c_VkImageView; counter c_VkPipeline; counter c_VkPipelineCache; counter c_VkPipelineLayout; counter c_VkQueryPool; counter c_VkRenderPass; counter c_VkSampler; counter c_VkSemaphore; counter c_VkShaderModule; counter c_VkDebugReportCallbackEXT; counter c_VkObjectTableNVX; counter c_VkIndirectCommandsLayoutNVX; counter c_VkDisplayKHR; counter c_VkDisplayModeKHR; counter c_VkSurfaceKHR; counter c_VkSwapchainKHR; counter c_VkDescriptorUpdateTemplateKHR; counter c_VkValidationCacheEXT; counter c_VkSamplerYcbcrConversionKHR; #else // DISTINCT_NONDISPATCHABLE_HANDLES counter c_uint64_t; #endif // DISTINCT_NONDISPATCHABLE_HANDLES layer_data() : report_data(nullptr), num_tmp_callbacks(0), tmp_dbg_create_infos(nullptr), tmp_callbacks(nullptr), c_VkCommandBuffer("VkCommandBuffer", VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT), c_VkDevice("VkDevice", VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT), c_VkInstance("VkInstance", VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT), c_VkQueue("VkQueue", VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT), #ifdef DISTINCT_NONDISPATCHABLE_HANDLES c_VkBuffer("VkBuffer", VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT), c_VkBufferView("VkBufferView", VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_VIEW_EXT), c_VkCommandPool("VkCommandPool", VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT), c_VkDescriptorPool("VkDescriptorPool", VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_POOL_EXT), c_VkDescriptorSet("VkDescriptorSet", VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT), c_VkDescriptorSetLayout("VkDescriptorSetLayout", VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT), c_VkDeviceMemory("VkDeviceMemory", VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT), c_VkEvent("VkEvent", VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT), c_VkFence("VkFence", VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT), c_VkFramebuffer("VkFramebuffer", VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT), c_VkImage("VkImage", VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT), c_VkImageView("VkImageView", VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_VIEW_EXT), c_VkPipeline("VkPipeline", VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT), c_VkPipelineCache("VkPipelineCache", VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_CACHE_EXT), c_VkPipelineLayout("VkPipelineLayout", VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT), c_VkQueryPool("VkQueryPool", VK_DEBUG_REPORT_OBJECT_TYPE_QUERY_POOL_EXT), c_VkRenderPass("VkRenderPass", VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT), c_VkSampler("VkSampler", VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT), c_VkSemaphore("VkSemaphore", VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT), c_VkShaderModule("VkShaderModule", VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT), c_VkDebugReportCallbackEXT("VkDebugReportCallbackEXT", VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_EXT), c_VkObjectTableNVX("VkObjectTableNVX", VK_DEBUG_REPORT_OBJECT_TYPE_OBJECT_TABLE_NVX_EXT), c_VkIndirectCommandsLayoutNVX("VkIndirectCommandsLayoutNVX", VK_DEBUG_REPORT_OBJECT_TYPE_INDIRECT_COMMANDS_LAYOUT_NVX_EXT), c_VkDisplayKHR("VkDisplayKHR", VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_KHR_EXT), c_VkDisplayModeKHR("VkDisplayModeKHR", VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_MODE_KHR_EXT), c_VkSurfaceKHR("VkSurfaceKHR", VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT), c_VkSwapchainKHR("VkSwapchainKHR", VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT), c_VkDescriptorUpdateTemplateKHR("VkDescriptorUpdateTemplateKHR", VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_KHR_EXT), c_VkSamplerYcbcrConversionKHR("VkSamplerYcbcrConversionKHR", VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_KHR_EXT) #else // DISTINCT_NONDISPATCHABLE_HANDLES c_uint64_t("NON_DISPATCHABLE_HANDLE", VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT) #endif // DISTINCT_NONDISPATCHABLE_HANDLES {}; }; #define WRAPPER(type) \ static void startWriteObject(struct layer_data *my_data, type object) { \ my_data->c_##type.startWrite(my_data->report_data, object); \ } \ static void finishWriteObject(struct layer_data *my_data, type object) { my_data->c_##type.finishWrite(object); } \ static void startReadObject(struct layer_data *my_data, type object) { \ my_data->c_##type.startRead(my_data->report_data, object); \ } \ static void finishReadObject(struct layer_data *my_data, type object) { my_data->c_##type.finishRead(object); } WRAPPER(VkDevice) WRAPPER(VkInstance) WRAPPER(VkQueue) #ifdef DISTINCT_NONDISPATCHABLE_HANDLES WRAPPER(VkBuffer) WRAPPER(VkBufferView) WRAPPER(VkCommandPool) WRAPPER(VkDescriptorPool) WRAPPER(VkDescriptorSet) WRAPPER(VkDescriptorSetLayout) WRAPPER(VkDeviceMemory) WRAPPER(VkEvent) WRAPPER(VkFence) WRAPPER(VkFramebuffer) WRAPPER(VkImage) WRAPPER(VkImageView) WRAPPER(VkPipeline) WRAPPER(VkPipelineCache) WRAPPER(VkPipelineLayout) WRAPPER(VkQueryPool) WRAPPER(VkRenderPass) WRAPPER(VkSampler) WRAPPER(VkSemaphore) WRAPPER(VkShaderModule) WRAPPER(VkDebugReportCallbackEXT) WRAPPER(VkObjectTableNVX) WRAPPER(VkIndirectCommandsLayoutNVX) WRAPPER(VkDisplayKHR) WRAPPER(VkDisplayModeKHR) WRAPPER(VkSurfaceKHR) WRAPPER(VkSwapchainKHR) WRAPPER(VkDescriptorUpdateTemplateKHR) WRAPPER(VkValidationCacheEXT) WRAPPER(VkSamplerYcbcrConversionKHR) #else // DISTINCT_NONDISPATCHABLE_HANDLES WRAPPER(uint64_t) #endif // DISTINCT_NONDISPATCHABLE_HANDLES static std::unordered_map layer_data_map; static std::mutex command_pool_lock; static std::unordered_map command_pool_map; // VkCommandBuffer needs check for implicit use of command pool static void startWriteObject(struct layer_data *my_data, VkCommandBuffer object, bool lockPool = true) { if (lockPool) { std::unique_lock lock(command_pool_lock); VkCommandPool pool = command_pool_map[object]; lock.unlock(); startWriteObject(my_data, pool); } my_data->c_VkCommandBuffer.startWrite(my_data->report_data, object); } static void finishWriteObject(struct layer_data *my_data, VkCommandBuffer object, bool lockPool = true) { my_data->c_VkCommandBuffer.finishWrite(object); if (lockPool) { std::unique_lock lock(command_pool_lock); VkCommandPool pool = command_pool_map[object]; lock.unlock(); finishWriteObject(my_data, pool); } } static void startReadObject(struct layer_data *my_data, VkCommandBuffer object) { std::unique_lock lock(command_pool_lock); VkCommandPool pool = command_pool_map[object]; lock.unlock(); startReadObject(my_data, pool); my_data->c_VkCommandBuffer.startRead(my_data->report_data, object); } static void finishReadObject(struct layer_data *my_data, VkCommandBuffer object) { my_data->c_VkCommandBuffer.finishRead(object); std::unique_lock lock(command_pool_lock); VkCommandPool pool = command_pool_map[object]; lock.unlock(); finishReadObject(my_data, pool); } #endif // THREADING_H