layers: Add VK_EXT_validation_cache implementation for shaders
authorChris Forbes <chrisforbes@google.com>
Mon, 24 Jul 2017 22:35:29 +0000 (15:35 -0700)
committerChris Forbes <chrisf@ijw.co.nz>
Wed, 29 Nov 2017 00:27:21 +0000 (16:27 -0800)
layers/core_validation.cpp
layers/shader_validation.cpp
layers/shader_validation.h

index c3431c6..4fcb268 100644 (file)
@@ -195,6 +195,10 @@ static const VkLayerProperties global_layer = {
     "VK_LAYER_LUNARG_core_validation", VK_LAYER_API_VERSION, 1, "LunarG Validation Layer",
 };
 
+static const VkExtensionProperties device_extensions[] = {
+  { VK_EXT_VALIDATION_CACHE_EXTENSION_NAME, VK_EXT_VALIDATION_CACHE_SPEC_VERSION },
+};
+
 template <class TCreateInfo>
 void ValidateLayerOrdering(const TCreateInfo &createInfo) {
     bool foundLayer = false;
@@ -4492,6 +4496,37 @@ VKAPI_ATTR VkResult VKAPI_CALL MergePipelineCaches(VkDevice device, VkPipelineCa
     return result;
 }
 
+// Validation cache:
+// CV is the bottommost implementor of this extension. Don't pass calls down.
+VKAPI_ATTR VkResult VKAPI_CALL CreateValidationCacheEXT(VkDevice device, const VkValidationCacheCreateInfoEXT *pCreateInfo,
+                                                   const VkAllocationCallbacks *pAllocator, VkValidationCacheEXT *pValidationCache) {
+    *pValidationCache = ValidationCache::Create(pCreateInfo);
+    return *pValidationCache ? VK_SUCCESS : VK_ERROR_INITIALIZATION_FAILED;
+}
+
+VKAPI_ATTR void VKAPI_CALL DestroyValidationCacheEXT(VkDevice device, VkValidationCacheEXT validationCache,
+                                                const VkAllocationCallbacks *pAllocator) {
+    delete (ValidationCache *)validationCache;
+}
+
+VKAPI_ATTR VkResult VKAPI_CALL GetValidationCacheDataEXT(VkDevice device, VkValidationCacheEXT validationCache, size_t *pDataSize,
+                                                    void *pData) {
+    size_t inSize = *pDataSize;
+    ((ValidationCache *)validationCache)->Write(pDataSize, pData);
+    return (pData && *pDataSize != inSize) ? VK_INCOMPLETE : VK_SUCCESS;
+}
+
+VKAPI_ATTR VkResult VKAPI_CALL MergeValidationCachesEXT(VkDevice device, VkValidationCacheEXT dstCache, uint32_t srcCacheCount,
+                                                   const VkValidationCacheEXT *pSrcCaches) {
+    auto dst = (ValidationCache *)dstCache;
+    auto src = (ValidationCache const * const *)pSrcCaches;
+
+    for (uint32_t i = 0; i < srcCacheCount; i++)
+        dst->Merge(src[i]);
+
+    return VK_SUCCESS;
+}
+
 // utility function to set collective state for pipeline
 void set_pipeline_state(PIPELINE_STATE *pPipe) {
     // If any attachment used by this pipeline has blendEnable, set top-level blendEnable
@@ -10639,7 +10674,7 @@ VKAPI_ATTR VkResult VKAPI_CALL EnumerateInstanceExtensionProperties(const char *
 
 VKAPI_ATTR VkResult VKAPI_CALL EnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, const char *pLayerName,
                                                                   uint32_t *pCount, VkExtensionProperties *pProperties) {
-    if (pLayerName && !strcmp(pLayerName, global_layer.layerName)) return util_GetExtensionProperties(0, NULL, pCount, pProperties);
+    if (pLayerName && !strcmp(pLayerName, global_layer.layerName)) return util_GetExtensionProperties(1, device_extensions, pCount, pProperties);
 
     assert(physicalDevice);
 
@@ -11092,6 +11127,10 @@ static const std::unordered_map<std::string, void *> name_to_funcptr_map = {
     {"vkGetSemaphoreFdKHR", (void*)GetSemaphoreFdKHR},
     {"vkImportFenceFdKHR", (void*)ImportFenceFdKHR},
     {"vkGetFenceFdKHR", (void*)GetFenceFdKHR},
+    {"vkCreateValidationCacheEXT", (void*)CreateValidationCacheEXT},
+    {"vkDestroyValidationCacheEXT", (void*)DestroyValidationCacheEXT},
+    {"vkGetValidationCacheDataEXT", (void*)GetValidationCacheDataEXT},
+    {"vkMergeValidationCachesEXT", (void*)MergeValidationCachesEXT},
 };
 
 VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetDeviceProcAddr(VkDevice device, const char *funcName) {
index 11fcbdc..aa215fa 100644 (file)
@@ -35,6 +35,7 @@
 #include "core_validation_types.h"
 #include "shader_validation.h"
 #include "spirv-tools/libspirv.h"
+#include "xxhash.h"
 
 enum FORMAT_TYPE {
     FORMAT_TYPE_FLOAT = 1,  // UNORM, SNORM, FLOAT, USCALED, SSCALED, SRGB -- anything we consider float in the shader
@@ -1517,6 +1518,20 @@ bool validate_compute_pipeline(layer_data *dev_data, PIPELINE_STATE *pipeline) {
     return validate_pipeline_shader_stage(dev_data, &pCreateInfo->stage, pipeline, &module, &entrypoint);
 }
 
+uint32_t ValidationCache::MakeShaderHash(VkShaderModuleCreateInfo const *smci) {
+        return XXH32(smci->pCode, smci->codeSize * sizeof(uint32_t), 0);
+}
+
+static ValidationCache *GetValidationCacheInfo(
+    VkShaderModuleCreateInfo const *pCreateInfo) {
+    while ((pCreateInfo = (VkShaderModuleCreateInfo const *)pCreateInfo->pNext) != nullptr) {
+        if (pCreateInfo->sType == VK_STRUCTURE_TYPE_SHADER_MODULE_VALIDATION_CACHE_CREATE_INFO_EXT)
+            return (ValidationCache *)((VkShaderModuleValidationCacheCreateInfoEXT const *)pCreateInfo)->validationCache;
+    }
+
+    return nullptr;
+}
+
 bool PreCallValidateCreateShaderModule(layer_data *dev_data, VkShaderModuleCreateInfo const *pCreateInfo, bool *spirv_valid) {
     bool skip = false;
     spv_result_t spv_valid = SPV_SUCCESS;
@@ -1534,6 +1549,14 @@ bool PreCallValidateCreateShaderModule(layer_data *dev_data, VkShaderModuleCreat
                         "SPIR-V module not valid: Codesize must be a multiple of 4 but is " PRINTF_SIZE_T_SPECIFIER ". %s",
                         pCreateInfo->codeSize, validation_error_map[VALIDATION_ERROR_12a00ac0]);
     } else {
+        auto cache = GetValidationCacheInfo(pCreateInfo);
+        uint32_t hash = 0;
+        if (cache) {
+            hash = ValidationCache::MakeShaderHash(pCreateInfo);
+            if (cache->Contains(hash))
+                return false;
+        }
+
         // Use SPIRV-Tools validator to try and catch any issues with the module itself
         spv_context ctx = spvContextCreate(SPV_ENV_VULKAN_1_0);
         spv_const_binary_t binary{ pCreateInfo->pCode, pCreateInfo->codeSize / sizeof(uint32_t) };
@@ -1547,6 +1570,10 @@ bool PreCallValidateCreateShaderModule(layer_data *dev_data, VkShaderModuleCreat
                                 VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, 0, __LINE__, SHADER_CHECKER_INCONSISTENT_SPIRV, "SC",
                                 "SPIR-V module not valid: %s", diag && diag->error ? diag->error : "(no error text)");
             }
+        } else {
+            if (cache) {
+                cache->Insert(hash);
+            }
         }
 
         spvDiagnosticDestroy(diag);
index a6eecff..99a784c 100644 (file)
@@ -100,6 +100,93 @@ struct shader_module {
     void build_def_index();
 };
 
+// TODO: Wire this up to SPIRV-Tools commit hash
+#define VALIDATION_CACHE_VERSION  1
+
+class ValidationCache {
+    // 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.
+    std::unordered_set<uint32_t> good_shader_hashes;
+    ValidationCache() {}
+
+public:
+    static VkValidationCacheEXT Create(VkValidationCacheCreateInfoEXT const *pCreateInfo) {
+        auto cache = new ValidationCache();
+        cache->Load(pCreateInfo);
+        return VkValidationCacheEXT(cache);
+    }
+
+    void Load(VkValidationCacheCreateInfoEXT const *pCreateInfo) {
+        auto size = 8u + VK_UUID_SIZE;
+        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;
+        if (data[2] != VALIDATION_CACHE_VERSION || data[3] || data[4] || data[5])
+            return;   // different version
+
+        data += 6;
+
+        for (;size < pCreateInfo->initialDataSize;
+             data++, size += sizeof(uint32_t)) {
+            good_shader_hashes.insert(*data);
+        }
+    }
+
+    void Write(size_t *pDataSize, void *pData) {
+        auto headerSize = 8u + VK_UUID_SIZE;
+        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;
+        *out++ = VALIDATION_CACHE_VERSION;
+        *out++ = 0;
+        *out++ = 0;
+        *out++ = 0;
+
+        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) {
+        for (auto h : other->good_shader_hashes)
+            good_shader_hashes.insert(h);
+    }
+
+    static uint32_t MakeShaderHash(VkShaderModuleCreateInfo const *smci);
+
+    bool Contains(uint32_t hash) {
+        return good_shader_hashes.count(hash) != 0;
+    }
+
+    void Insert(uint32_t hash) {
+        good_shader_hashes.insert(hash);
+    }
+};
+
 bool validate_and_capture_pipeline_shader_state(layer_data *dev_data, PIPELINE_STATE *pPipeline);
 bool validate_compute_pipeline(layer_data *dev_data, PIPELINE_STATE *pPipeline);
 typedef std::pair<unsigned, unsigned> descriptor_slot_t;