Added support to run the CTS with Vulkan validation layers (#2246)

Fixes #2116 according to task description.

As approved 25Mar25
diff --git a/test_conformance/common/vulkan_wrapper/opencl_vulkan_wrapper.cpp b/test_conformance/common/vulkan_wrapper/opencl_vulkan_wrapper.cpp
index ded1e70..36b94d9 100644
--- a/test_conformance/common/vulkan_wrapper/opencl_vulkan_wrapper.cpp
+++ b/test_conformance/common/vulkan_wrapper/opencl_vulkan_wrapper.cpp
@@ -1191,6 +1191,10 @@
 {
     switch (vkExternalMemoryHandleType)
     {
+        default:
+        case VULKAN_EXTERNAL_MEMORY_HANDLE_TYPE_NONE:
+            log_error("Unexpected external memory handle type\n");
+            return 0;
         case VULKAN_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD:
             return CL_EXTERNAL_MEMORY_HANDLE_OPAQUE_FD_KHR;
         case VULKAN_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_NT:
@@ -1198,7 +1202,6 @@
         case VULKAN_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT:
         case VULKAN_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_NT_KMT:
             return CL_EXTERNAL_MEMORY_HANDLE_OPAQUE_WIN32_KMT_KHR;
-        case VULKAN_EXTERNAL_MEMORY_HANDLE_TYPE_NONE: return 0;
     }
     return 0;
 }
diff --git a/test_conformance/common/vulkan_wrapper/vulkan_api_list.hpp b/test_conformance/common/vulkan_wrapper/vulkan_api_list.hpp
index f5e7437..a474102 100644
--- a/test_conformance/common/vulkan_wrapper/vulkan_api_list.hpp
+++ b/test_conformance/common/vulkan_wrapper/vulkan_api_list.hpp
@@ -102,6 +102,8 @@
     VK_FUNC_DECL(vkImportSemaphoreFdKHR)                                       \
     VK_FUNC_DECL(vkGetPhysicalDeviceExternalSemaphorePropertiesKHR)            \
     VK_FUNC_DECL(vkGetImageSubresourceLayout)                                  \
+    VK_FUNC_DECL(vkCreateDebugUtilsMessengerEXT)                               \
+    VK_FUNC_DECL(vkDestroyDebugUtilsMessengerEXT)                              \
     VK_FUNC_DECL(vkGetPhysicalDeviceExternalBufferProperties)
 #define VK_WINDOWS_FUNC_LIST                                                   \
     VK_FUNC_DECL(vkGetMemoryWin32HandleKHR)                                    \
@@ -203,6 +205,8 @@
 #define vkGetSemaphoreWin32HandleKHR _vkGetSemaphoreWin32HandleKHR
 #define vkImportSemaphoreWin32HandleKHR _vkImportSemaphoreWin32HandleKHR
 #define vkGetImageSubresourceLayout _vkGetImageSubresourceLayout
+#define vkCreateDebugUtilsMessengerEXT _vkCreateDebugUtilsMessengerEXT
+#define vkDestroyDebugUtilsMessengerEXT _vkDestroyDebugUtilsMessengerEXT
 #define vkGetPhysicalDeviceExternalBufferProperties                            \
     _vkGetPhysicalDeviceExternalBufferProperties
 
diff --git a/test_conformance/common/vulkan_wrapper/vulkan_utility.cpp b/test_conformance/common/vulkan_wrapper/vulkan_utility.cpp
index 6391d36..75aa536 100644
--- a/test_conformance/common/vulkan_wrapper/vulkan_utility.cpp
+++ b/test_conformance/common/vulkan_wrapper/vulkan_utility.cpp
@@ -32,13 +32,13 @@
 #define BUFFERSIZE 3000
 
 
-const VulkanInstance &getVulkanInstance()
+const VulkanInstance &getVulkanInstance(bool useValidationLayers)
 {
-    static VulkanInstance instance;
+    static VulkanInstance instance(useValidationLayers);
     return instance;
 }
 
-const VulkanPhysicalDevice &getVulkanPhysicalDevice()
+const VulkanPhysicalDevice &getVulkanPhysicalDevice(bool useValidationLayers)
 {
     size_t pdIdx = 0;
     cl_int errNum = 0;
@@ -47,7 +47,7 @@
     cl_uint num_devices = 0;
     cl_uint device_no = 0;
     const size_t bufsize = BUFFERSIZE;
-    const VulkanInstance &instance = getVulkanInstance();
+    const VulkanInstance &instance = getVulkanInstance(useValidationLayers);
     const VulkanPhysicalDeviceList &physicalDeviceList =
         instance.getPhysicalDeviceList();
 
@@ -112,12 +112,13 @@
 }
 
 const VulkanPhysicalDevice &
-getAssociatedVulkanPhysicalDevice(cl_device_id deviceId)
+getAssociatedVulkanPhysicalDevice(cl_device_id deviceId,
+                                  bool useValidationLayers)
 {
     size_t pdIdx;
     cl_int errNum = 0;
     cl_uchar uuid[CL_UUID_SIZE_KHR];
-    const VulkanInstance &instance = getVulkanInstance();
+    const VulkanInstance &instance = getVulkanInstance(useValidationLayers);
     const VulkanPhysicalDeviceList &physicalDeviceList =
         instance.getPhysicalDeviceList();
 
@@ -188,10 +189,10 @@
     return memoryTypeList[mtIdx];
 }
 
-bool checkVkSupport()
+bool checkVkSupport(bool useValidationLayers)
 {
     bool result = true;
-    const VulkanInstance &instance = getVulkanInstance();
+    const VulkanInstance &instance = getVulkanInstance(useValidationLayers);
     const VulkanPhysicalDeviceList &physicalDeviceList =
         instance.getPhysicalDeviceList();
     if (physicalDeviceList() == NULL)
@@ -711,6 +712,7 @@
 {
     switch (externalMemoryHandleType)
     {
+        default:
         case VULKAN_EXTERNAL_MEMORY_HANDLE_TYPE_NONE: return os << "None";
         case VULKAN_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD:
             return os << "Opaque file descriptor";
@@ -731,6 +733,7 @@
 {
     switch (externalSemaphoreHandleType)
     {
+        default:
         case VULKAN_EXTERNAL_SEMAPHORE_HANDLE_TYPE_NONE: return os << "None";
         case VULKAN_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD:
             return os << "Opaque file descriptor";
diff --git a/test_conformance/common/vulkan_wrapper/vulkan_utility.hpp b/test_conformance/common/vulkan_wrapper/vulkan_utility.hpp
index 8502866..cd9bf4e 100644
--- a/test_conformance/common/vulkan_wrapper/vulkan_utility.hpp
+++ b/test_conformance/common/vulkan_wrapper/vulkan_utility.hpp
@@ -30,10 +30,12 @@
 #define ROUND_UP(n, multiple)                                                  \
     (((n) + (multiple)-1) - ((((n) + (multiple)-1)) % (multiple)))
 
-const VulkanInstance& getVulkanInstance();
-const VulkanPhysicalDevice& getVulkanPhysicalDevice();
+const VulkanInstance& getVulkanInstance(bool useValidationLayers = false);
 const VulkanPhysicalDevice&
-getAssociatedVulkanPhysicalDevice(cl_device_id deviceId);
+getVulkanPhysicalDevice(bool useValidationLayers = false);
+const VulkanPhysicalDevice&
+getAssociatedVulkanPhysicalDevice(cl_device_id deviceId,
+                                  bool useValidationLayers = false);
 const VulkanQueueFamily& getVulkanQueueFamily(
     const VulkanPhysicalDevice& physicalDevice = getVulkanPhysicalDevice(),
     uint32_t queueFlags = VULKAN_QUEUE_FLAG_GRAPHICS
@@ -41,7 +43,7 @@
 const VulkanMemoryType&
 getVulkanMemoryType(const VulkanDevice& device,
                     VulkanMemoryTypeProperty memoryTypeProperty);
-bool checkVkSupport();
+bool checkVkSupport(bool useValidationLayers = false);
 const VulkanQueueFamilyList& getEmptyVulkanQueueFamilyList();
 const VulkanDescriptorSetLayoutList& getEmptyVulkanDescriptorSetLayoutList();
 const VulkanQueueFamilyToQueueCountMap&
diff --git a/test_conformance/common/vulkan_wrapper/vulkan_wrapper.cpp b/test_conformance/common/vulkan_wrapper/vulkan_wrapper.cpp
index 6c31a35..f41e196 100644
--- a/test_conformance/common/vulkan_wrapper/vulkan_wrapper.cpp
+++ b/test_conformance/common/vulkan_wrapper/vulkan_wrapper.cpp
@@ -48,16 +48,38 @@
 
 #define CHECK_VK(call)                                                         \
     if (call != VK_SUCCESS) return call;
+
+static VKAPI_ATTR VkBool32 VKAPI_CALL logCallback(
+    VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
+    VkDebugUtilsMessageTypeFlagsEXT messageType,
+    const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData, void *pUserData)
+{
+    switch (messageSeverity)
+    {
+        case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
+            log_error("Vulkan validation layer: %s\n", pCallbackData->pMessage);
+            break;
+        default:
+            log_info("Vulkan validation layer: %s\n", pCallbackData->pMessage);
+            break;
+    }
+
+    return VK_FALSE;
+}
+
 ///////////////////////////////////
 // VulkanInstance implementation //
 ///////////////////////////////////
 
 VulkanInstance::VulkanInstance(const VulkanInstance &instance)
     : m_vkInstance(instance.m_vkInstance),
-      m_physicalDeviceList(instance.m_physicalDeviceList)
+      m_physicalDeviceList(instance.m_physicalDeviceList),
+      m_useValidationLayers(instance.m_useValidationLayers),
+      m_validationLayers(instance.m_validationLayers)
 {}
 
-VulkanInstance::VulkanInstance(): m_vkInstance(VK_NULL_HANDLE)
+VulkanInstance::VulkanInstance(bool useValidationLayers)
+    : m_vkInstance(VK_NULL_HANDLE), m_useValidationLayers(useValidationLayers)
 {
 #if defined(__linux__) && !defined(__ANDROID__)
     char *glibcVersion = strdup(gnu_get_libc_version());
@@ -130,6 +152,35 @@
     VK_GET_NULL_INSTANCE_PROC_ADDR(vkCreateInstance);
 #undef VK_GET_NULL_INSTANCE_PROC_ADDR
 
+    if (m_useValidationLayers)
+    {
+        uint32_t layerCount = 0;
+        vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
+
+        std::vector<VkLayerProperties> layers(layerCount);
+        vkEnumerateInstanceLayerProperties(&layerCount, layers.data());
+
+        for (auto it = m_validationLayers.begin();
+             it != m_validationLayers.end();)
+        {
+            bool found = false;
+            for (const auto &layerProps : layers)
+                if (strcmp(*it, layerProps.layerName) == 0)
+                {
+                    found = true;
+                    break;
+                }
+            if (!found)
+            {
+                log_info("Vulkan layer not found: %s\n", *it);
+                it = m_validationLayers.erase(it);
+            }
+            else
+                ++it;
+        }
+        m_useValidationLayers = !m_validationLayers.empty();
+    }
+
     VkApplicationInfo vkApplicationInfo = {};
     vkApplicationInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
     vkApplicationInfo.pNext = NULL;
@@ -147,6 +198,9 @@
     enabledExtensionNameList.push_back(
         VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME);
 
+    if (m_useValidationLayers)
+        enabledExtensionNameList.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
+
     std::vector<VkExtensionProperties> vkExtensionPropertiesList(
         instanceExtensionPropertiesCount);
     vkEnumerateInstanceExtensionProperties(NULL,
@@ -174,18 +228,51 @@
 
     VkInstanceCreateInfo vkInstanceCreateInfo = {};
     vkInstanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
-    vkInstanceCreateInfo.pNext = NULL;
     vkInstanceCreateInfo.flags = 0;
     vkInstanceCreateInfo.pApplicationInfo = &vkApplicationInfo;
-    vkInstanceCreateInfo.enabledLayerCount = 0;
     vkInstanceCreateInfo.ppEnabledLayerNames = NULL;
     vkInstanceCreateInfo.enabledExtensionCount =
         (uint32_t)enabledExtensionNameList.size();
     vkInstanceCreateInfo.ppEnabledExtensionNames =
         enabledExtensionNameList.data();
+    vkInstanceCreateInfo.enabledLayerCount = 0;
+    vkInstanceCreateInfo.pNext = NULL;
+
+    VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{};
+    if (m_useValidationLayers)
+    {
+        vkInstanceCreateInfo.enabledLayerCount =
+            static_cast<uint32_t>(m_validationLayers.size());
+        vkInstanceCreateInfo.ppEnabledLayerNames = m_validationLayers.data();
+
+        debugCreateInfo.messageType =
+            VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
+            | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
+            | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
+
+        debugCreateInfo.sType =
+            VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
+        debugCreateInfo.messageSeverity =
+            VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
+            | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
+        debugCreateInfo.pfnUserCallback = logCallback;
+
+        vkInstanceCreateInfo.pNext =
+            (VkDebugUtilsMessengerCreateInfoEXT *)&debugCreateInfo;
+    }
 
     vkCreateInstance(&vkInstanceCreateInfo, NULL, &m_vkInstance);
 
+    if (m_useValidationLayers)
+    {
+        _vkCreateDebugUtilsMessengerEXT =
+            (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
+                m_vkInstance, "vkCreateDebugUtilsMessengerEXT");
+        if (_vkCreateDebugUtilsMessengerEXT != nullptr)
+            vkCreateDebugUtilsMessengerEXT(m_vkInstance, &debugCreateInfo,
+                                           nullptr, &m_debugMessenger);
+    }
+
 #define VK_FUNC_DECL(name)                                                     \
     _##name = (PFN_##name)vkGetInstanceProcAddr(m_vkInstance, #name);          \
     // ASSERT_NEQ((unsigned long long)name, 0ULL) << "Couldn't obtain address
@@ -228,6 +315,17 @@
             m_physicalDeviceList[pdIdx];
         delete &physicalDevice;
     }
+
+    _vkDestroyDebugUtilsMessengerEXT =
+        (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
+            m_vkInstance, "vkDestroyDebugUtilsMessengerEXT");
+
+    if (_vkDestroyDebugUtilsMessengerEXT != nullptr)
+    {
+        vkDestroyDebugUtilsMessengerEXT(m_vkInstance, m_debugMessenger,
+                                        nullptr);
+    }
+
     if (m_vkInstance)
     {
         vkDestroyInstance(m_vkInstance, NULL);
diff --git a/test_conformance/common/vulkan_wrapper/vulkan_wrapper.hpp b/test_conformance/common/vulkan_wrapper/vulkan_wrapper.hpp
index a536d14..04d74c5 100644
--- a/test_conformance/common/vulkan_wrapper/vulkan_wrapper.hpp
+++ b/test_conformance/common/vulkan_wrapper/vulkan_wrapper.hpp
@@ -25,14 +25,21 @@
 #include <cassert>
 
 class VulkanInstance {
-    friend const VulkanInstance &getVulkanInstance();
+    friend const VulkanInstance &getVulkanInstance(bool useValidationLayers);
 
 protected:
     VkInstance m_vkInstance;
     VulkanPhysicalDeviceList m_physicalDeviceList;
+    VkDebugUtilsMessengerEXT m_debugMessenger;
+    bool m_useValidationLayers;
+    std::vector<const char *> m_validationLayers = {
+        "VK_LAYER_KHRONOS_validation",
+    };
 
-    VulkanInstance();
     VulkanInstance(const VulkanInstance &);
+
+public:
+    VulkanInstance(bool useValidationLayers = false);
     virtual ~VulkanInstance();
 
 public:
diff --git a/test_conformance/vulkan/main.cpp b/test_conformance/vulkan/main.cpp
index d2d278d..fdc1e8f 100644
--- a/test_conformance/vulkan/main.cpp
+++ b/test_conformance/vulkan/main.cpp
@@ -31,6 +31,7 @@
 #endif
 
 #include "harness/testHarness.h"
+#include "harness/parseParameters.h"
 
 unsigned int numCQ;
 bool multiImport;
@@ -38,6 +39,7 @@
 bool debug_trace = false;
 bool useSingleImageKernel = false;
 bool useDeviceLocal = false;
+bool useValidationLayers = false;
 bool disableNTHandleType = false;
 bool enableOffset = false;
 
@@ -55,7 +57,15 @@
     log_info("\n");
     log_info("Options:\n");
     log_info("\t--debug_trace - Enables additional debug info logging\n");
-    log_info("\t--non_dedicated - Choose dedicated Vs. non_dedicated \n");
+    log_info("\t--useSingleImageKernel - Use the same image "
+             "(image_single_queue and image_multiple_queue tests)\n");
+    log_info("\t--useDeviceLocal - Skip tests that use images with local "
+             "memory type\n");
+    log_info("\t--disableNTHandleType - Skip tests that use win32 external "
+             "memory handle\n");
+    log_info("\t--useValidationLayers - Enables Vulkan validation layer "
+             "diagnostic output\n");
+    log_info("\t-h - Print test usage\n");
 }
 
 bool isDeviceSelection(const char *arg)
@@ -70,35 +80,47 @@
         || strcmp(arg, "CL_DEVICE_TYPE_DEFAULT") == 0;
 }
 
-size_t parseParams(int argc, const char *argv[], const char **argList)
+void parseParams(int &argc, const char *argv[])
 {
-    size_t argCount = 1;
-    for (int i = 1; i < argc; i++)
+    argc = parseCustomParam(argc, argv);
+
+    for (int i = 0; i < argc; ++i)
     {
+        int argsRemoveNum = 0;
+
         if (argv[i] == NULL) break;
         if (argv[i][0] == '-')
         {
             if (!strcmp(argv[i], "--debug_trace"))
             {
                 debug_trace = true;
+                argsRemoveNum = 1;
             }
             if (!strcmp(argv[i], "--useSingleImageKernel"))
             {
                 useSingleImageKernel = true;
+                argsRemoveNum = 1;
             }
             if (!strcmp(argv[i], "--useDeviceLocal"))
             {
                 useDeviceLocal = true;
+                argsRemoveNum = 1;
+            }
+            if (!strcmp(argv[i], "--useValidationLayers"))
+            {
+                useValidationLayers = true;
+                argsRemoveNum = 1;
             }
             if (!strcmp(argv[i], "--disableNTHandleType"))
             {
                 disableNTHandleType = true;
+                argsRemoveNum = 1;
             }
             if (strcmp(argv[i], "-h") == 0)
             {
                 printUsage(argv[0]);
-                argCount = 0; // Returning argCount=0 to assert error in main()
-                break;
+                argc = 0; // Returning argCount=0 to assert error in main()
+                return;
             }
         }
         else if (isDeviceSelection(argv[i]))
@@ -108,16 +130,20 @@
                 && strcmp(argv[i], "CL_DEVICE_TYPE_DEFAULT") != 0)
             {
                 log_info("Vulkan tests can only run on a GPU device.\n");
-                return 0;
+                argc = 0;
+                return;
             }
         }
-        else
+
+        if (argsRemoveNum > 0)
         {
-            argList[argCount] = argv[i];
-            argCount++;
+            for (int j = i; j < (argc - argsRemoveNum); ++j)
+                argv[j] = argv[j + argsRemoveNum];
+
+            argc -= argsRemoveNum;
+            --i;
         }
     }
-    return argCount;
 }
 
 int main(int argc, const char *argv[])
@@ -147,9 +173,9 @@
         return 0;
     }
 
-    const char **argList = (const char **)calloc(argc, sizeof(char *));
-    size_t argCount = parseParams(argc, argv, argList);
-    if (argCount == 0) return 0;
+    parseParams(argc, argv);
+
+    if (argc == 0) return 0;
 
     return runTestHarness(argc, argv, test_registry::getInstance().num_tests(),
                           test_registry::getInstance().definitions(), false, 0);
diff --git a/test_conformance/vulkan/vulkan_interop_common.hpp b/test_conformance/vulkan/vulkan_interop_common.hpp
index 6f4d915..4e28533 100644
--- a/test_conformance/vulkan/vulkan_interop_common.hpp
+++ b/test_conformance/vulkan/vulkan_interop_common.hpp
@@ -42,6 +42,7 @@
 
 extern bool useSingleImageKernel;
 extern bool useDeviceLocal;
+extern bool useValidationLayers;
 extern bool disableNTHandleType;
 
 #endif // _vulkan_interop_common_hpp_
diff --git a/test_conformance/vulkan/vulkan_test_base.h b/test_conformance/vulkan/vulkan_test_base.h
index 82b8e63..8c7b07c 100644
--- a/test_conformance/vulkan/vulkan_test_base.h
+++ b/test_conformance/vulkan/vulkan_test_base.h
@@ -40,8 +40,8 @@
                    cl_command_queue queue, cl_int nelems)
         : device(device), context(context), num_elems(nelems)
     {
-        vkDevice.reset(
-            new VulkanDevice(getAssociatedVulkanPhysicalDevice(device)));
+        vkDevice.reset(new VulkanDevice(
+            getAssociatedVulkanPhysicalDevice(device, useValidationLayers)));
 
         cl_platform_id platform;
         cl_int error = clGetDeviceInfo(device, CL_DEVICE_PLATFORM,
@@ -101,7 +101,7 @@
         return TEST_SKIPPED_ITSELF;
     }
 
-    if (!checkVkSupport())
+    if (!checkVkSupport(useValidationLayers))
     {
         log_info("Vulkan supported GPU not found \n");
         log_info("TEST SKIPPED \n");