From bcffcde5f347a758d213db589ce8807a8f6f19d5 Mon Sep 17 00:00:00 2001 From: Aitor Camacho Date: Wed, 15 Jan 2025 14:35:45 +0900 Subject: [PATCH 1/3] layers: VK_EXT_external_memory_metal generated files --- layers/vulkan/generated/feature_requirements_helper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layers/vulkan/generated/feature_requirements_helper.cpp b/layers/vulkan/generated/feature_requirements_helper.cpp index 2c991b188cc..3728710a3a1 100644 --- a/layers/vulkan/generated/feature_requirements_helper.cpp +++ b/layers/vulkan/generated/feature_requirements_helper.cpp @@ -4388,7 +4388,7 @@ FeatureAndName AddFeature(APIVersion api_version, vkt::Feature feature, void **i } #ifdef VK_ENABLE_BETA_EXTENSIONS - case Feature::constantAlphaColorBlendFactors: { + case Feature::constantAlphaColorBlendFactors: { auto vk_struct = const_cast( vku::FindStructInPNextChain(*inout_pnext_chain)); if (!vk_struct) { From fbc376c629e6d7caa2688426ffe4a79b45daef6c Mon Sep 17 00:00:00 2001 From: Aitor Camacho Date: Wed, 15 Jan 2025 15:04:59 +0900 Subject: [PATCH 2/3] layers: VK_EXT_external_memory_metal validation --- layers/core_checks/cc_device_memory.cpp | 17 ++- layers/core_checks/cc_external_object.cpp | 147 ++++++++++++++++++++++ layers/core_checks/core_validation.h | 11 ++ layers/stateless/sl_external_object.cpp | 6 + 4 files changed, 180 insertions(+), 1 deletion(-) diff --git a/layers/core_checks/cc_device_memory.cpp b/layers/core_checks/cc_device_memory.cpp index deb859dc79e..b2c68b081dd 100644 --- a/layers/core_checks/cc_device_memory.cpp +++ b/layers/core_checks/cc_device_memory.cpp @@ -342,7 +342,15 @@ bool CoreChecks::IgnoreAllocationSize(const VkMemoryAllocateInfo &allocate_info) if (import_memory_win32 && (import_memory_win32->handleType & ignored_allocation) != 0) { return true; } -#endif +#elif VK_USE_PLATFORM_METAL_EXT + const VkExternalMemoryHandleTypeFlags ignored_allocation = VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLBUFFER_BIT_EXT | + VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_EXT | + VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLHEAP_BIT_EXT; + const auto import_memory_metal = vku::FindStructInPNextChain(allocate_info.pNext); + if (import_memory_metal && (import_memory_metal->handleType & ignored_allocation) != 0) { + return true; + } +#endif // VK_USE_PLATFORM_METAL_EXT // Handles 01874 cases const auto export_info = vku::FindStructInPNextChain(allocate_info.pNext); if (export_info && (export_info->handleTypes & VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID)) { @@ -770,6 +778,13 @@ bool CoreChecks::PreCallValidateAllocateMemory(VkDevice device, const VkMemoryAl } } #endif + +#ifdef VK_USE_PLATFORM_METAL_EXT + if (IsExtEnabled(extensions.vk_ext_external_memory_metal)) { + skip |= ValidateAllocateMemoryMetal(*pAllocateInfo, dedicated_allocate_info, allocate_info_loc); + } +#endif // VK_USE_PLATFORM_METAL_EXT + return skip; } diff --git a/layers/core_checks/cc_external_object.cpp b/layers/core_checks/cc_external_object.cpp index 50e11925f39..ab8f3b60ad3 100644 --- a/layers/core_checks/cc_external_object.cpp +++ b/layers/core_checks/cc_external_object.cpp @@ -556,3 +556,150 @@ bool CoreChecks::PreCallValidateExportMetalObjectsEXT(VkDevice device, VkExportM return skip; } #endif // VK_USE_PLATFORM_METAL_EXT + +bool CoreChecks::ValidateAllocateMemoryMetal(const VkMemoryAllocateInfo &allocate_info, + const VkMemoryDedicatedAllocateInfo *dedicated_allocation_info, + const Location &allocate_info_loc) const { + bool skip = false; + +#if VK_USE_PLATFORM_METAL_EXT + // When dealing with Metal external memory, we can have the following 3 scenarios: + // 1. Allocation will be exported. Contains VkExportMemoryAllocateInfo + // 2. Allocation is being imported. Contains VkImportMemoryMetalHandleInfoEXT + // 3. Previous 2 combined + // Whenever the memory will be used for an image, VK_EXT_external_memory_metal requires that the allocation + // is dedicated to that single resource. + + // Case 1 and partially 3 + // Since we know Metal textures required to be imported as a dedicated allocation, we can do the check directly here instead + // of deferring to the binding call. + const auto export_memory_info = vku::FindStructInPNextChain(allocate_info.pNext); + const auto export_memory_info_nv = vku::FindStructInPNextChain(allocate_info.pNext); + if (export_memory_info || export_memory_info_nv) { + VkExternalMemoryHandleTypeFlags handles = + export_memory_info ? export_memory_info->handleTypes : export_memory_info_nv->handleTypes; + if ((handles == VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_EXT) && dedicated_allocation_info == nullptr) { + skip |= LogError("VUID-VkMemoryAllocateInfo-pNext-00639", device, allocate_info_loc.dot(Field::pNext), + "VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_EXT requires textures to be imported as a dedicated " + "allocation."); + } + } + + // Case 2 and remaining 3 + const auto import_memory_metal_info = vku::FindStructInPNextChain(allocate_info.pNext); + if (import_memory_metal_info == nullptr) { + return skip; + } + + // Ensure user is importing correct type before checking any other VUID since we will assume correct type + if ((import_memory_metal_info->handleType != VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLBUFFER_BIT_EXT) && + (import_memory_metal_info->handleType != VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_EXT) && + (import_memory_metal_info->handleType != VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLHEAP_BIT_EXT)) { + skip |= LogError("VUID-VkImportMemoryMetalHandleInfoEXT-handleType-10410", device, + allocate_info_loc.pNext(Struct::VkImportMemoryMetalHandleInfoEXT, Field::handleType), "current value is %s", + string_VkExternalMemoryHandleTypeFlagBits(import_memory_metal_info->handleType)); + return skip; + } + + // Only images require to be created as a dedicated allocation. This allows us to check if the format + // used by the image allows for importing such images from external memory. We cannot do that with + // buffers since we lack the usage, so it's not possible to know if the device allows import + // operations on buffers. + if (import_memory_metal_info->handleType == VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_EXT) { + if (allocate_info.allocationSize != 0) { + skip |= LogError("VUID-VkMemoryAllocateInfo-pNext-10397", device, + allocate_info_loc.dot(Field::allocationSize), "is %" PRId64, + allocate_info.allocationSize); + } + + if (dedicated_allocation_info == nullptr) { + skip |= LogError("VUID-VkMemoryAllocateInfo-pNext-10395", device, + allocate_info_loc.dot(Field::pNext), + "VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_EXT requires textures to be imported as a dedicated" + "allocation."); + // Early out since the image comes from VkMemoryDedicatedAllocateInfoKHR and there's none. + return skip; + } + + // Unsure if there should be a VUID that enforces image to not be NULL when importing a MTLTEXTURE type + if (dedicated_allocation_info->image == VK_NULL_HANDLE) { + skip |= LogError("VUID-VkMemoryAllocateInfo-pNext-10395", device, + allocate_info_loc.dot(Struct::VkMemoryDedicatedAllocateInfo, Field::image), + "must be a valid image handle."); + // Early out since there's no image in VkMemoryDedicatedAllocateInfoKHR. + return skip; + } + + // We can only validate images since we lack information to do the buffer call for the properties + auto image_state_ptr = Get(dedicated_allocation_info->image); + VkFormat image_format = image_state_ptr->create_info.format; + VkPhysicalDeviceExternalImageFormatInfo external_info = vku::InitStructHelper(); + external_info.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_EXT; + VkPhysicalDeviceImageFormatInfo2 format_info = vku::InitStructHelper(&external_info); + format_info.format = image_state_ptr->safe_create_info.format; + format_info.tiling = image_state_ptr->safe_create_info.tiling; + format_info.type = image_state_ptr->safe_create_info.imageType; + format_info.usage = image_state_ptr->safe_create_info.usage; + VkExternalImageFormatProperties external_image_format_properties = vku::InitStructHelper(); + VkImageFormatProperties2 format_properties = vku::InitStructHelper(&external_image_format_properties); + DispatchGetPhysicalDeviceImageFormatProperties2Helper(physical_device, &format_info, &format_properties); + + if ((external_image_format_properties.externalMemoryProperties.externalMemoryFeatures & + VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT) == 0u) { + skip |= LogError("VUID-VkImportMemoryMetalHandleInfoEXT-handleType-10408", device, + allocate_info_loc.dot(Struct::VkImportMemoryMetalHandleInfoEXT, Field::handleType), + "does not support importing image with format %s", string_VkFormat(image_format)); + } + } +#endif // VK_USE_PLATFORM_METAL_EXT + + return skip; +} + +#ifdef VK_USE_PLATFORM_METAL_EXT +bool CoreChecks::PreCallValidateGetMemoryMetalHandleEXT(VkDevice device, const VkMemoryGetMetalHandleInfoEXT *pGetMetalHandleInfo, + void **pHandle, const ErrorObject &error_obj) const { + bool skip = false; + const Location get_metal_handle_info = error_obj.location.dot(Field::pGetMetalHandleInfo); + auto memory = Get(pGetMetalHandleInfo->memory); + ASSERT_AND_RETURN_SKIP(memory); + auto export_memory_allocate_info = vku::FindStructInPNextChain(memory->safe_allocate_info.pNext); + if (export_memory_allocate_info == nullptr) { + skip |= LogError("VUID-VkMemoryGetMetalHandleInfoEXT-memory-10413", device, + get_metal_handle_info.dot(Field::memory).dot(Field::pNext), + "device memory missing VkExportMemoryAllocateInfo at creation"); + } else if ((export_memory_allocate_info->handleTypes & pGetMetalHandleInfo->handleType) == 0u) { + skip |= LogError("VUID-VkMemoryGetMetalHandleInfoEXT-handleType-10414", device, + get_metal_handle_info.dot(Field::handleType), + "device memory was created with (%s) handle types. Missing %s type", + string_VkExternalMemoryHandleTypeFlags(export_memory_allocate_info->handleTypes).c_str(), + string_VkExternalMemoryHandleTypeFlagBits(pGetMetalHandleInfo->handleType)); + } + + if ((pGetMetalHandleInfo->handleType != VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLBUFFER_BIT_EXT) && + (pGetMetalHandleInfo->handleType != VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_EXT) && + (pGetMetalHandleInfo->handleType != VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLHEAP_BIT_EXT)) { + skip |= LogError("VUID-VkMemoryGetMetalHandleInfoEXT-handleType-10415", device, + get_metal_handle_info.dot(Field::handleType), "current value is %s", + string_VkExternalMemoryHandleTypeFlagBits(pGetMetalHandleInfo->handleType)); + } + return skip; +} + +bool CoreChecks::PreCallValidateGetMemoryMetalHandlePropertiesEXT(VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, + const void *handle, + VkMemoryMetalHandlePropertiesEXT *pMemoryMetalHandleProperties, + const ErrorObject &error_obj) const { + bool skip = false; + + if ((handleType != VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLBUFFER_BIT_EXT) && + (handleType != VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_EXT) && + (handleType != VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLHEAP_BIT_EXT)) { + skip |= LogError("VUID-vkGetMemoryMetalHandlePropertiesEXT-handleType-10417", device, + error_obj.location.dot(Field::handleType), "current value is %s", + string_VkExternalMemoryHandleTypeFlagBits(handleType)); + } + + return skip; +} +#endif // VK_USE_PLATFORM_METAL_EXT diff --git a/layers/core_checks/core_validation.h b/layers/core_checks/core_validation.h index b7b767261e4..cfcca75955e 100644 --- a/layers/core_checks/core_validation.h +++ b/layers/core_checks/core_validation.h @@ -1372,6 +1372,9 @@ class CoreChecks : public ValidationStateTracker { bool ValidatePhysicalDeviceQueueFamilies(uint32_t queue_family_count, const uint32_t* queue_families, const Location& loc, const char* vuid) const; bool ValidateAllocateMemoryANDROID(const VkMemoryAllocateInfo& allocate_info, const Location& allocate_info_loc) const; + bool ValidateAllocateMemoryMetal(const VkMemoryAllocateInfo& allocate_info, + const VkMemoryDedicatedAllocateInfo* dedicated_allocation_info, + const Location& allocate_info_loc) const; bool ValidateGetImageMemoryRequirementsANDROID(const VkImage image, const Location& loc) const; bool ValidateBufferImportedHandleANDROID(VkExternalMemoryHandleTypeFlags handle_types, VkDeviceMemory memory, VkBuffer buffer, const Location& loc) const; @@ -2061,6 +2064,14 @@ class CoreChecks : public ValidationStateTracker { const VkSemaphoreGetZirconHandleInfoFUCHSIA* pGetZirconHandleInfo, zx_handle_t* pZirconHandle, const RecordObject& record_obj) override; #endif +#ifdef VK_USE_PLATFORM_METAL_EXT + bool PreCallValidateGetMemoryMetalHandleEXT(VkDevice device, const VkMemoryGetMetalHandleInfoEXT* pGetMetalHandleInfo, + void** pHandle, const ErrorObject& error_obj) const override; + bool PreCallValidateGetMemoryMetalHandlePropertiesEXT(VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, + const void* handle, + VkMemoryMetalHandlePropertiesEXT* pMemoryMetalHandleProperties, + const ErrorObject& error_obj) const override; +#endif // VK_USE_PLATFORM_METAL_EXT #ifdef VK_USE_PLATFORM_WIN32_KHR bool PreCallValidateGetMemoryWin32HandleKHR(VkDevice device, const VkMemoryGetWin32HandleInfoKHR* pGetWin32HandleInfo, HANDLE* pHandle, const ErrorObject& error_obj) const override; diff --git a/layers/stateless/sl_external_object.cpp b/layers/stateless/sl_external_object.cpp index a78af8777a0..953f99dceb7 100644 --- a/layers/stateless/sl_external_object.cpp +++ b/layers/stateless/sl_external_object.cpp @@ -371,6 +371,12 @@ ExternalOperationsInfo GetExternalOperationsInfo(const void *pNext) { ext.total_import_ops += (import_info_qnx && import_info_qnx->buffer); #endif // VK_USE_PLATFORM_SCREEN_QNX +#ifdef VK_USE_PLATFORM_METAL_EXT + // VK_EXT_external_memory_metal + auto import_info_metal = vku::FindStructInPNextChain(pNext); + ext.total_import_ops += (import_info_metal && import_info_metal->handle); +#endif // VK_USE_PLATFORM_METAL_EXT + return ext; } } // namespace From f9449e3dc7e2c7368e1dd3b38c766fe49252d763 Mon Sep 17 00:00:00 2001 From: Aitor Camacho Date: Wed, 15 Jan 2025 15:20:20 +0900 Subject: [PATCH 3/3] tests: VK_EXT_external_memory_metal tests --- tests/CMakeLists.txt | 1 + tests/unit/external_memory_metal.cpp | 426 +++++++++++++++++++++++++++ 2 files changed, 427 insertions(+) create mode 100644 tests/unit/external_memory_metal.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d45c190c1da..d556201af54 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -97,6 +97,7 @@ target_sources(vk_layer_validation_tests PRIVATE unit/dynamic_rendering_local_read_positive.cpp unit/dynamic_state.cpp unit/dynamic_state_positive.cpp + unit/external_memory_metal.cpp unit/external_memory_sync.cpp unit/external_memory_sync_positive.cpp unit/fragment_shading_rate.cpp diff --git a/tests/unit/external_memory_metal.cpp b/tests/unit/external_memory_metal.cpp new file mode 100644 index 00000000000..c530bf2926d --- /dev/null +++ b/tests/unit/external_memory_metal.cpp @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2015-2025 The Khronos Group Inc. + * Copyright (c) 2015-2025 Valve Corporation + * Copyright (c) 2015-2025 LunarG, Inc. + * Copyright (c) 2015-2025 Google, 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 + */ + +#include "utils/vk_layer_utils.h" +#include "../framework/layer_validation_tests.h" +#include "../framework/external_memory_sync.h" + +#ifdef VK_USE_PLATFORM_METAL_EXT +// We need these instead of using FindSupportedExternalMemoryHandleTypes because otherwise we'll get +// VUID-VkPhysicalDeviceExternalImageFormatInfo-handleType-parameter due to sending flags that are not supported due to extensions +// not being present +VkExternalMemoryHandleTypeFlags FindSupportedExternalMemoryHandleTypesMetal(VkPhysicalDevice gpu, + const VkBufferCreateInfo& buffer_create_info, + VkExternalMemoryFeatureFlags requested_features) { + VkPhysicalDeviceExternalBufferInfo external_info = vku::InitStructHelper(); + external_info.flags = buffer_create_info.flags; + external_info.usage = buffer_create_info.usage; + const VkExternalMemoryHandleTypeFlags all_metal_buffer_types = + VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLBUFFER_BIT_EXT | VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLHEAP_BIT_EXT; + + VkExternalMemoryHandleTypeFlags supported_types = 0; + IterateFlags(all_metal_buffer_types, [&](VkExternalMemoryHandleTypeFlagBits flag) { + external_info.handleType = flag; + VkExternalBufferProperties external_properties = vku::InitStructHelper(); + vk::GetPhysicalDeviceExternalBufferProperties(gpu, &external_info, &external_properties); + const auto external_features = external_properties.externalMemoryProperties.externalMemoryFeatures; + if ((external_features & requested_features) == requested_features) { + supported_types |= flag; + } + }); + return supported_types; +} + +VkExternalMemoryHandleTypeFlags FindSupportedExternalMemoryHandleTypesMetal(VkPhysicalDevice gpu, + const VkImageCreateInfo& image_create_info, + VkExternalMemoryFeatureFlags requested_features) { + VkPhysicalDeviceExternalImageFormatInfo external_info = vku::InitStructHelper(); + VkPhysicalDeviceImageFormatInfo2 image_info = vku::InitStructHelper(&external_info); + image_info.format = image_create_info.format; + image_info.type = image_create_info.imageType; + image_info.tiling = image_create_info.tiling; + image_info.usage = image_create_info.usage; + image_info.flags = image_create_info.flags; + const VkExternalMemoryHandleTypeFlags all_metal_image_types = + VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_EXT | VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLHEAP_BIT_EXT; + + VkExternalMemoryHandleTypeFlags supported_types = 0; + IterateFlags(all_metal_image_types, [&](VkExternalMemoryHandleTypeFlagBits flag) { + external_info.handleType = flag; + VkExternalImageFormatProperties external_properties = vku::InitStructHelper(); + VkImageFormatProperties2 image_properties = vku::InitStructHelper(&external_properties); + VkResult result = vk::GetPhysicalDeviceImageFormatProperties2(gpu, &image_info, &image_properties); + const auto external_features = external_properties.externalMemoryProperties.externalMemoryFeatures; + if (result == VK_SUCCESS && (external_features & requested_features) == requested_features) { + supported_types |= flag; + } + }); + return supported_types; +} + +class NegativeExternalMemoryMetal : public VkLayerTest {}; + +TEST_F(NegativeExternalMemoryMetal, AllocateExportableImageWithoutDedicatedAllocationInPNext) { + TEST_DESCRIPTION("Allocate exportable metal texture without dedicated allocation in pNext chain"); + + AddRequiredExtensions(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); + AddRequiredExtensions(VK_EXT_EXTERNAL_MEMORY_METAL_EXTENSION_NAME); + RETURN_IF_SKIP(Init()); + + // Create export image + VkExternalMemoryImageCreateInfo external_image_info = vku::InitStructHelper(); + VkImageCreateInfo image_info = vku::InitStructHelper(&external_image_info); + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.arrayLayers = 1; + image_info.extent = {64, 64, 1}; + image_info.format = VK_FORMAT_R8G8B8A8_UNORM; + image_info.mipLevels = 1; + image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + image_info.samples = VK_SAMPLE_COUNT_1_BIT; + image_info.tiling = VK_IMAGE_TILING_OPTIMAL; + image_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + + auto exportable_types = + FindSupportedExternalMemoryHandleTypesMetal(Gpu(), image_info, VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT); + + VkExternalMemoryHandleTypeFlagBits metal_texture_type = VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_EXT; + if ((exportable_types & metal_texture_type) == 0u) { + GTEST_SKIP() << "Unable to find exportable metal texture handle type"; + } + external_image_info.handleTypes = metal_texture_type; + vkt::Image image(*m_device, image_info, vkt::no_mem); + + VkExportMemoryAllocateInfo export_memory_info = vku::InitStructHelper(nullptr); + export_memory_info.handleTypes = metal_texture_type; + + auto alloc_info = vkt::DeviceMemory::GetResourceAllocInfo(*m_device, image.MemoryRequirements(), + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &export_memory_info); + m_errorMonitor->SetDesiredError("VUID-VkMemoryAllocateInfo-pNext-00639"); + image.Memory().TryInit(*m_device, alloc_info); + m_errorMonitor->VerifyFound(); +} + +TEST_F(NegativeExternalMemoryMetal, AllocateImportableImageWithoutDedicatedAllocationInPNext) { + TEST_DESCRIPTION("Import metal texture at allocation without dedicated allocation in pNext chain"); + + AddRequiredExtensions(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); + AddRequiredExtensions(VK_EXT_EXTERNAL_MEMORY_METAL_EXTENSION_NAME); + RETURN_IF_SKIP(Init()); + + // Create export image + VkExternalMemoryImageCreateInfo external_image_info = vku::InitStructHelper(); + VkImageCreateInfo image_info = vku::InitStructHelper(&external_image_info); + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.arrayLayers = 1; + image_info.extent = {64, 64, 1}; + image_info.format = VK_FORMAT_R8G8B8A8_UNORM; + image_info.mipLevels = 1; + image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + image_info.samples = VK_SAMPLE_COUNT_1_BIT; + image_info.tiling = VK_IMAGE_TILING_OPTIMAL; + image_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + + auto exportable_types = FindSupportedExternalMemoryHandleTypesMetal( + Gpu(), image_info, VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT | VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT); + + VkExternalMemoryHandleTypeFlagBits metal_texture_type = VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_EXT; + if ((exportable_types & metal_texture_type) == 0u) { + GTEST_SKIP() << "Unable to find exportable metal texture handle type"; + } + external_image_info.handleTypes = metal_texture_type; + vkt::Image image(*m_device, image_info, vkt::no_mem); + + VkMemoryDedicatedAllocateInfo dedicated_info = vku::InitStructHelper(); + dedicated_info.image = image.handle(); + VkExportMemoryAllocateInfo export_memory_info = vku::InitStructHelper(&dedicated_info); + export_memory_info.handleTypes = metal_texture_type; + image.AllocateAndBindMemory(*m_device, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &export_memory_info); + + VkMemoryGetMetalHandleInfoEXT get_handle_info = vku::InitStructHelper(); + get_handle_info.handleType = metal_texture_type; + get_handle_info.memory = image.Memory().handle(); + void* import_handle = nullptr; + vk::GetMemoryMetalHandleEXT(*m_device, &get_handle_info, &import_handle); + + VkImportMemoryMetalHandleInfoEXT metal_import_info = vku::InitStructHelper(); + metal_import_info.handleType = metal_texture_type; + metal_import_info.handle = import_handle; + VkMemoryAllocateInfo import_allocate_info = vku::InitStructHelper(&metal_import_info); + VkDeviceMemory device_memory = VK_NULL_HANDLE; + m_errorMonitor->SetDesiredError("VUID-VkMemoryAllocateInfo-pNext-10395"); + vk::AllocateMemory(*m_device, &import_allocate_info, nullptr, &device_memory); + m_errorMonitor->VerifyFound(); +} + +TEST_F(NegativeExternalMemoryMetal, AllocateImportableImageWithoutDedicatedImageAllocation) { + TEST_DESCRIPTION("Import invalid metal texture without dedicated image specified"); + + AddRequiredExtensions(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); + AddRequiredExtensions(VK_EXT_EXTERNAL_MEMORY_METAL_EXTENSION_NAME); + RETURN_IF_SKIP(Init()); + + // Create export image + VkExternalMemoryImageCreateInfo external_image_info = vku::InitStructHelper(); + VkImageCreateInfo image_info = vku::InitStructHelper(&external_image_info); + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.arrayLayers = 1; + image_info.extent = {64, 64, 1}; + image_info.format = VK_FORMAT_R8G8B8A8_UNORM; + image_info.mipLevels = 1; + image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + image_info.samples = VK_SAMPLE_COUNT_1_BIT; + image_info.tiling = VK_IMAGE_TILING_OPTIMAL; + image_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + + auto exportable_types = FindSupportedExternalMemoryHandleTypesMetal( + Gpu(), image_info, VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT | VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT); + + VkExternalMemoryHandleTypeFlagBits metal_texture_type = VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_EXT; + if ((exportable_types & metal_texture_type) == 0u) { + GTEST_SKIP() << "Unable to find exportable metal texture handle type"; + } + external_image_info.handleTypes = metal_texture_type; + vkt::Image image(*m_device, image_info, vkt::no_mem); + + VkMemoryDedicatedAllocateInfo dedicated_info = vku::InitStructHelper(); + dedicated_info.image = image.handle(); + VkExportMemoryAllocateInfo export_memory_info = vku::InitStructHelper(&dedicated_info); + export_memory_info.handleTypes = metal_texture_type; + image.AllocateAndBindMemory(*m_device, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &export_memory_info); + + VkMemoryGetMetalHandleInfoEXT get_handle_info = vku::InitStructHelper(); + get_handle_info.handleType = metal_texture_type; + get_handle_info.memory = image.Memory().handle(); + void* import_handle = nullptr; + vk::GetMemoryMetalHandleEXT(*m_device, &get_handle_info, &import_handle); + + dedicated_info.image = VK_NULL_HANDLE; // This is what makes it illegal + VkImportMemoryMetalHandleInfoEXT metal_import_info = vku::InitStructHelper(&dedicated_info); + metal_import_info.handleType = metal_texture_type; + metal_import_info.handle = import_handle; + VkMemoryAllocateInfo import_allocate_info = vku::InitStructHelper(&metal_import_info); + VkDeviceMemory device_memory = VK_NULL_HANDLE; + m_errorMonitor->SetDesiredError("VUID-VkMemoryAllocateInfo-pNext-10395"); + vk::AllocateMemory(*m_device, &import_allocate_info, nullptr, &device_memory); + m_errorMonitor->VerifyFound(); +} + +TEST_F(NegativeExternalMemoryMetal, AllocateImportableImageWithAllocationSizeNot0) { + TEST_DESCRIPTION("Import invalid metal texture with allocation size being non-0"); + + AddRequiredExtensions(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); + AddRequiredExtensions(VK_EXT_EXTERNAL_MEMORY_METAL_EXTENSION_NAME); + RETURN_IF_SKIP(Init()); + + // Create export image + VkExternalMemoryImageCreateInfo external_image_info = vku::InitStructHelper(); + VkImageCreateInfo image_info = vku::InitStructHelper(&external_image_info); + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.arrayLayers = 1; + image_info.extent = {64, 64, 1}; + image_info.format = VK_FORMAT_R8G8B8A8_UNORM; + image_info.mipLevels = 1; + image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + image_info.samples = VK_SAMPLE_COUNT_1_BIT; + image_info.tiling = VK_IMAGE_TILING_OPTIMAL; + image_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + + auto exportable_types = FindSupportedExternalMemoryHandleTypesMetal( + Gpu(), image_info, VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT | VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT); + + VkExternalMemoryHandleTypeFlagBits metal_texture_type = VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_EXT; + if ((exportable_types & metal_texture_type) == 0u) { + GTEST_SKIP() << "Unable to find exportable metal texture handle type"; + } + external_image_info.handleTypes = metal_texture_type; + vkt::Image image(*m_device, image_info, vkt::no_mem); + + VkMemoryDedicatedAllocateInfo dedicated_info = vku::InitStructHelper(); + dedicated_info.image = image.handle(); + VkExportMemoryAllocateInfo export_memory_info = vku::InitStructHelper(&dedicated_info); + export_memory_info.handleTypes = metal_texture_type; + image.AllocateAndBindMemory(*m_device, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &export_memory_info); + + VkMemoryGetMetalHandleInfoEXT get_handle_info = vku::InitStructHelper(); + get_handle_info.handleType = metal_texture_type; + get_handle_info.memory = image.Memory().handle(); + void* import_handle = nullptr; + vk::GetMemoryMetalHandleEXT(*m_device, &get_handle_info, &import_handle); + + VkImportMemoryMetalHandleInfoEXT metal_import_info = vku::InitStructHelper(&dedicated_info); + metal_import_info.handleType = metal_texture_type; + metal_import_info.handle = import_handle; + VkMemoryAllocateInfo import_allocate_info = vku::InitStructHelper(&metal_import_info); + import_allocate_info.allocationSize = 100; // This is what makes it illegal + VkDeviceMemory device_memory = VK_NULL_HANDLE; + m_errorMonitor->SetDesiredError("VUID-VkMemoryAllocateInfo-pNext-10397"); + vk::AllocateMemory(*m_device, &import_allocate_info, nullptr, &device_memory); + m_errorMonitor->VerifyFound(); +} + +TEST_F(NegativeExternalMemoryMetal, GetResourceHandleWithoutExportStructAtCreation) { + TEST_DESCRIPTION("Get resource handle that was created without the export struct at creation"); + + AddRequiredExtensions(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); + AddRequiredExtensions(VK_EXT_EXTERNAL_MEMORY_METAL_EXTENSION_NAME); + RETURN_IF_SKIP(Init()); + + VkExternalMemoryBufferCreateInfo external_buffer_info = vku::InitStructHelper(); + const auto buffer_info = vkt::Buffer::CreateInfo(4096, VK_BUFFER_USAGE_TRANSFER_DST_BIT, {}, &external_buffer_info); + const auto exportable_types = + FindSupportedExternalMemoryHandleTypesMetal(Gpu(), buffer_info, VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT); + + auto metal_types = static_cast(VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLBUFFER_BIT_EXT | + VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_EXT); + auto valid_types = static_cast(exportable_types & metal_types); + if (valid_types == 0u) { + GTEST_SKIP() << "Unable to find exportable metal handle type"; + } + const auto handle_type = LeastSignificantFlag(valid_types); + external_buffer_info.handleTypes = handle_type; + vkt::Buffer buffer(*m_device, buffer_info, vkt::no_mem); + + // Check if dedicated allocation is required + bool dedicated_allocation = false; + IterateFlags(exportable_types, [&](VkExternalMemoryHandleTypeFlagBits handle_type) { + if (HandleTypeNeedsDedicatedAllocation(Gpu(), buffer_info, handle_type)) { + dedicated_allocation = true; + } + }); + VkMemoryDedicatedAllocateInfo dedicated_info = vku::InitStructHelper(); + dedicated_info.buffer = buffer; + + auto alloc_info = + vkt::DeviceMemory::GetResourceAllocInfo(*m_device, buffer.MemoryRequirements(), VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + dedicated_allocation ? &dedicated_info : nullptr); + VkResult result = buffer.Memory().TryInit(*m_device, alloc_info); + if (result != VK_SUCCESS) { + GTEST_SKIP() << "vkAllocateMemory failed. Unable to continue test to check for incorrect import handle type."; + } + + void* handle = nullptr; + VkMemoryGetMetalHandleInfoEXT get_handle_info = vku::InitStructHelper(); + get_handle_info.memory = buffer.Memory().handle(); + get_handle_info.handleType = handle_type; + m_errorMonitor->SetDesiredError("VUID-VkMemoryGetMetalHandleInfoEXT-memory-10413"); + vk::GetMemoryMetalHandleEXT(*m_device, &get_handle_info, &handle); + m_errorMonitor->VerifyFound(); +} + +TEST_F(NegativeExternalMemoryMetal, GetResourceHandleWithIncorrectHandleType) { + TEST_DESCRIPTION("Get resource handle that was created with a different external handle type"); + + AddRequiredExtensions(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); + AddRequiredExtensions(VK_EXT_EXTERNAL_MEMORY_METAL_EXTENSION_NAME); + RETURN_IF_SKIP(Init()); + + VkExternalMemoryBufferCreateInfo external_buffer_info = vku::InitStructHelper(); + const auto buffer_info = vkt::Buffer::CreateInfo(4096, VK_BUFFER_USAGE_TRANSFER_DST_BIT, {}, &external_buffer_info); + const auto exportable_types = + FindSupportedExternalMemoryHandleTypesMetal(Gpu(), buffer_info, VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT); + + auto metal_types = static_cast(VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLBUFFER_BIT_EXT | + VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_EXT); + auto valid_types = static_cast(exportable_types & metal_types); + if (valid_types == 0u) { + GTEST_SKIP() << "Unable to find exportable metal handle type"; + } + const auto handle_type = LeastSignificantFlag(valid_types); + external_buffer_info.handleTypes = handle_type; + vkt::Buffer buffer(*m_device, buffer_info, vkt::no_mem); + + // Check if dedicated allocation is required + bool dedicated_allocation = false; + IterateFlags(exportable_types, [&](VkExternalMemoryHandleTypeFlagBits handle_type) { + if (HandleTypeNeedsDedicatedAllocation(Gpu(), buffer_info, handle_type)) { + dedicated_allocation = true; + } + }); + VkMemoryDedicatedAllocateInfo dedicated_info = vku::InitStructHelper(); + dedicated_info.buffer = buffer; + + VkExportMemoryAllocateInfo export_memory_info = vku::InitStructHelper(dedicated_allocation ? &dedicated_info : nullptr); + export_memory_info.handleTypes = handle_type; + + auto alloc_info = vkt::DeviceMemory::GetResourceAllocInfo(*m_device, buffer.MemoryRequirements(), + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &export_memory_info); + VkResult result = buffer.Memory().TryInit(*m_device, alloc_info); + if (result != VK_SUCCESS) { + GTEST_SKIP() << "vkAllocateMemory failed. Unable to continue test to check for incorrect import handle type."; + } + + void* handle = nullptr; + VkMemoryGetMetalHandleInfoEXT get_handle_info = vku::InitStructHelper(); + get_handle_info.memory = buffer.Memory().handle(); + // Get the other handle type that was not used to allocate the memory + get_handle_info.handleType = static_cast(handle_type ^ metal_types); + m_errorMonitor->SetDesiredError("VUID-VkMemoryGetMetalHandleInfoEXT-handleType-10414"); + vk::GetMemoryMetalHandleEXT(*m_device, &get_handle_info, &handle); + m_errorMonitor->VerifyFound(); +} + +TEST_F(NegativeExternalMemoryMetal, GetResourceHandleWithNonMetalHandle) { + TEST_DESCRIPTION("Get resource handle as non metal handle"); + + AddRequiredExtensions(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); + AddRequiredExtensions(VK_EXT_EXTERNAL_MEMORY_METAL_EXTENSION_NAME); + RETURN_IF_SKIP(Init()); + + VkExternalMemoryBufferCreateInfo external_buffer_info = vku::InitStructHelper(); + const auto buffer_info = vkt::Buffer::CreateInfo(4096, VK_BUFFER_USAGE_TRANSFER_DST_BIT, {}, &external_buffer_info); + const auto exportable_types = + FindSupportedExternalMemoryHandleTypesMetal(Gpu(), buffer_info, VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT); + + auto metal_types = static_cast(VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLBUFFER_BIT_EXT | + VK_EXTERNAL_MEMORY_HANDLE_TYPE_MTLTEXTURE_BIT_EXT); + auto valid_types = static_cast(exportable_types & metal_types); + if (valid_types == 0u) { + GTEST_SKIP() << "Unable to find exportable metal handle type"; + } + const auto handle_type = LeastSignificantFlag(valid_types); + external_buffer_info.handleTypes = handle_type; + vkt::Buffer buffer(*m_device, buffer_info, vkt::no_mem); + + // Check if dedicated allocation is required + bool dedicated_allocation = false; + IterateFlags(exportable_types, [&](VkExternalMemoryHandleTypeFlagBits handle_type) { + if (HandleTypeNeedsDedicatedAllocation(Gpu(), buffer_info, handle_type)) { + dedicated_allocation = true; + } + }); + VkMemoryDedicatedAllocateInfo dedicated_info = vku::InitStructHelper(); + dedicated_info.buffer = buffer; + + VkExportMemoryAllocateInfo export_memory_info = vku::InitStructHelper(dedicated_allocation ? &dedicated_info : nullptr); + export_memory_info.handleTypes = handle_type; + + auto alloc_info = vkt::DeviceMemory::GetResourceAllocInfo(*m_device, buffer.MemoryRequirements(), + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &export_memory_info); + VkResult result = buffer.Memory().TryInit(*m_device, alloc_info); + if (result != VK_SUCCESS) { + GTEST_SKIP() << "vkAllocateMemory failed. Unable to continue test to check for incorrect import handle type."; + } + + void* handle = nullptr; + VkMemoryGetMetalHandleInfoEXT get_handle_info = vku::InitStructHelper(); + get_handle_info.memory = buffer.Memory().handle(); + // Get the other handle type that was not used to allocate the memory + get_handle_info.handleType = LeastSignificantFlag(~metal_types); + m_errorMonitor->SetDesiredError("VUID-VkMemoryGetMetalHandleInfoEXT-handleType-10415"); + // We are trying to get the handle using a type that was not specified at creation, so we also get this one + m_errorMonitor->SetAllowedFailureMsg("VUID-VkMemoryGetMetalHandleInfoEXT-handleType-10414"); + vk::GetMemoryMetalHandleEXT(*m_device, &get_handle_info, &handle); + m_errorMonitor->VerifyFound(); +} + +#endif // VK_USE_PLATFORM_METAL_EXT