/* 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 <cody@lunarg.com> * Author: Mike Stroyan <mike@LunarG.com> */ #ifndef THREADING_H #define THREADING_H #include <condition_variable> #include <mutex> #include <vector> #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 <typename T> class counter { public: const char *typeName; VkDebugReportObjectTypeEXT objectType; std::unordered_map<T, object_use_data> uses; std::mutex counter_lock; std::condition_variable counter_condition; void startWrite(debug_report_data *report_data, T object) { bool skipCall = false; loader_platform_thread_id tid = loader_platform_get_thread_id(); std::unique_lock<std::mutex> 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), /*location*/ 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 *use_data = &uses[object]; use_data->thread = tid; use_data->reader_count = 0; 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), /*location*/ 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 *use_data = &uses[object]; use_data->thread = tid; use_data->reader_count = 0; 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) { // Object is no longer in use std::unique_lock<std::mutex> 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) { bool skipCall = false; loader_platform_thread_id tid = loader_platform_get_thread_id(); std::unique_lock<std::mutex> 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), /*location*/ 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) { std::unique_lock<std::mutex> 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<VkDebugReportCallbackEXT> 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<VkCommandBuffer> c_VkCommandBuffer; counter<VkDevice> c_VkDevice; counter<VkInstance> c_VkInstance; counter<VkQueue> c_VkQueue; #ifdef DISTINCT_NONDISPATCHABLE_HANDLES counter<VkBuffer> c_VkBuffer; counter<VkBufferView> c_VkBufferView; counter<VkCommandPool> c_VkCommandPool; counter<VkDescriptorPool> c_VkDescriptorPool; counter<VkDescriptorSet> c_VkDescriptorSet; counter<VkDescriptorSetLayout> c_VkDescriptorSetLayout; counter<VkDeviceMemory> c_VkDeviceMemory; counter<VkEvent> c_VkEvent; counter<VkFence> c_VkFence; counter<VkFramebuffer> c_VkFramebuffer; counter<VkImage> c_VkImage; counter<VkImageView> c_VkImageView; counter<VkPipeline> c_VkPipeline; counter<VkPipelineCache> c_VkPipelineCache; counter<VkPipelineLayout> c_VkPipelineLayout; counter<VkQueryPool> c_VkQueryPool; counter<VkRenderPass> c_VkRenderPass; counter<VkSampler> c_VkSampler; counter<VkSemaphore> c_VkSemaphore; counter<VkShaderModule> c_VkShaderModule; counter<VkDebugReportCallbackEXT> c_VkDebugReportCallbackEXT; #else // DISTINCT_NONDISPATCHABLE_HANDLES counter<uint64_t> 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) #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) #else // DISTINCT_NONDISPATCHABLE_HANDLES WRAPPER(uint64_t) #endif // DISTINCT_NONDISPATCHABLE_HANDLES static std::unordered_map<void *, layer_data *> layer_data_map; static std::mutex command_pool_lock; static std::unordered_map<VkCommandBuffer, VkCommandPool> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(command_pool_lock); VkCommandPool pool = command_pool_map[object]; lock.unlock(); finishReadObject(my_data, pool); } #endif // THREADING_H