diff --git a/layer_gpu_support/README_LAYER.md b/layer_gpu_support/README_LAYER.md index 4d60ead..8de583d 100644 --- a/layer_gpu_support/README_LAYER.md +++ b/layer_gpu_support/README_LAYER.md @@ -175,6 +175,13 @@ allocated and handled by the driver. per component setting. Images that do not support a fixed rate compression level that meets this bit rate requirement will be left at the original application setting. +* If `disable_external_compression` option is set to `1` , all the possible measures + are taken to ensure that external compression is disabled. In case is not possible to guarantee + that external compression is used, the layer will return `VK_ERROR_FEATURE_NOT_PRESENT`. + Option `2`, in addition to what happens with option `1`, enables a heuristic useful when the + application is not presenting; compression could be disabled accidentally on internal images, + and it may also miss external images if they lack `COLOR_ATTACHMENT_BIT`. + Feel free to tune the heuristic to your specific use case. #### Configuration options @@ -182,9 +189,14 @@ allocated and handled by the driver. "framebuffer": { "disable_compression": false, // Disable all use of compression "force_default_compression": false, // Force driver default compression - "force_fixed_rate_compression": 0 // Force use of fixed rate compression as close + "force_fixed_rate_compression": 0, // Force use of fixed rate compression as close // to this bits-per-channel as possible, but // no lower (0 = do not force) + "disable_external_compression": 0, // 0 = Perform no operation, passthrough + // 1 = Force disable external compression, requires image presentation + // 2 = Force disable external compression also without + // presentation, requires only the use of vkCreateImage + // WARNING! Currently implemented as an heuristic } ``` diff --git a/layer_gpu_support/layer_config.json b/layer_gpu_support/layer_config.json index 726e999..f99e88e 100644 --- a/layer_gpu_support/layer_config.json +++ b/layer_gpu_support/layer_config.json @@ -40,6 +40,7 @@ "framebuffer": { "disable_compression": false, "force_default_compression": false, - "force_fixed_rate_compression": 0 + "force_fixed_rate_compression": 0, + "disable_external_compression": 0 } } diff --git a/layer_gpu_support/source/CMakeLists.txt b/layer_gpu_support/source/CMakeLists.txt index 4b224de..7256f55 100644 --- a/layer_gpu_support/source/CMakeLists.txt +++ b/layer_gpu_support/source/CMakeLists.txt @@ -47,11 +47,14 @@ add_library( layer_config.cpp layer_device_functions_dispatch.cpp layer_device_functions_image.cpp + layer_device_functions_swapchain.cpp + layer_device_functions_allocate_memory.cpp layer_device_functions_pipelines.cpp layer_device_functions_queue.cpp layer_device_functions_render_pass.cpp layer_device_functions_trace_rays.cpp - layer_device_functions_transfer.cpp) + layer_device_functions_transfer.cpp + layer_instance_functions_get_physical_properties.cpp) target_include_directories( ${VK_LAYER} PRIVATE diff --git a/layer_gpu_support/source/layer_config.cpp b/layer_gpu_support/source/layer_config.cpp index a66c1c3..91b26af 100644 --- a/layer_gpu_support/source/layer_config.cpp +++ b/layer_gpu_support/source/layer_config.cpp @@ -167,6 +167,7 @@ void LayerConfig::parse_framebuffer_options(const json& config) bool disable_all_compression = framebuffer.at("disable_compression"); bool default_all_compression = framebuffer.at("force_default_compression"); uint64_t force_fixed_rate_compression = framebuffer.at("force_fixed_rate_compression"); + int disable_external_compression = framebuffer.at("disable_external_compression"); // Apply precedence ladder if (disable_all_compression) @@ -228,6 +229,7 @@ void LayerConfig::parse_framebuffer_options(const json& config) conf_framebuffer_disable_compression = disable_all_compression; conf_framebuffer_force_default_compression = default_all_compression; conf_framebuffer_force_fixed_rate_compression = fixed_rate_mask; + conf_disable_external_compression = disable_external_compression; LAYER_LOG("Layer framebuffer configuration"); LAYER_LOG("==============================="); @@ -235,6 +237,8 @@ void LayerConfig::parse_framebuffer_options(const json& config) LAYER_LOG(" - Force default framebuffer compression: %d", conf_framebuffer_force_default_compression); LAYER_LOG(" - Force fixed rate compression: %lu bpc", force_fixed_rate_compression); LAYER_LOG(" - Force fixed rate compression mask: %08x", conf_framebuffer_force_fixed_rate_compression); + LAYER_LOG(" - Force disable external compression: %d", conf_disable_external_compression); + } /* See header for documentation. */ @@ -430,3 +434,9 @@ uint32_t LayerConfig::framebuffer_force_fixed_rate_compression() const { return conf_framebuffer_force_fixed_rate_compression; } + +/* See header for documentation. */ +int LayerConfig::disable_external_compression() const +{ + return conf_disable_external_compression; +} diff --git a/layer_gpu_support/source/layer_config.hpp b/layer_gpu_support/source/layer_config.hpp index c1a0cee..8f24606 100644 --- a/layer_gpu_support/source/layer_config.hpp +++ b/layer_gpu_support/source/layer_config.hpp @@ -165,6 +165,15 @@ class LayerConfig */ uint32_t framebuffer_force_fixed_rate_compression() const; + + /** + * @brief External compression control for swapchains/images. + * 0 = passthrough (default), + * 1 = strip compression on external images, + * 2 = strip compression on external images even without presentation, using heuristic (no guarantee!) + */ + int disable_external_compression() const; + private: /** * @brief Parse the configuration options for the feature module. @@ -315,4 +324,17 @@ class LayerConfig * If zero, then no force is set and default compression will be used. */ uint32_t conf_framebuffer_force_fixed_rate_compression {0}; + + /** + * @brief Forces disabling external compression. + * + * 0 = Perform no operation, passthrough. + * 1 = Force disable external compression, requires image presentation. + * 2 = Force disable external compression also without. + * presentation, requires only the use of vkCreateImage. + * + * WARNING! Currently implemented as an heuristic. + */ + int conf_disable_external_compression {0}; + }; diff --git a/layer_gpu_support/source/layer_device_functions.hpp b/layer_gpu_support/source/layer_device_functions.hpp index 0343ff7..a1a6c4e 100644 --- a/layer_gpu_support/source/layer_device_functions.hpp +++ b/layer_gpu_support/source/layer_device_functions.hpp @@ -369,3 +369,21 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage(VkDevice device, const VkImageCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImage* pImage); + +// Functions for swapchains + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateSwapchainKHR(VkDevice device, + const VkSwapchainCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSwapchainKHR* pSwapchain); + +// Functions for external DMA-buf + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkAllocateMemory(VkDevice device, + const VkMemoryAllocateInfo* pAllocateInfo, + const VkAllocationCallbacks* pAllocator, + VkDeviceMemory* pMemory); \ No newline at end of file diff --git a/layer_gpu_support/source/layer_device_functions_allocate_memory.cpp b/layer_gpu_support/source/layer_device_functions_allocate_memory.cpp new file mode 100644 index 0000000..3aa0f50 --- /dev/null +++ b/layer_gpu_support/source/layer_device_functions_allocate_memory.cpp @@ -0,0 +1,114 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2025 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ +#include +#include + +#include "device.hpp" +#include "framework/device_dispatch_table.hpp" + +#include +#include + +extern std::mutex g_vulkanLock; + +/** + * @brief Intercept vkAllocateMemory and hard-fail on external memory imports + * that could mandate external compression; otherwise pass through to the driver. + * + * @details Scans @p pAllocateInfo->pNext for import structs. If it finds + * VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID or + * VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR with + * VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, it returns + * VK_ERROR_FEATURE_NOT_PRESENT to preserve the guarantee that + * external compression is not used. When no such imports are present, the + * call is forwarded unchanged. + * + * @param device Logical device allocating the memory. + * @param pAllocateInfo Allocation parameters; its pNext chain is inspected + * for import info. + * @param pAllocator Optional allocation callbacks. + * @param pMemory Receives the allocated device memory on success. + * + * @return VK_SUCCESS on success, or a VkResult propagated from the driver. + * Returns VK_ERROR_FEATURE_NOT_PRESENT when disallowed external + * import types are detected in the pNext chain. + * + * @note This is a strict policy check; additional import structures may be + * rejected in the future to maintain the compression-off guarantee. + */ + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkAllocateMemory( + VkDevice device, + const VkMemoryAllocateInfo* pAllocateInfo, + const VkAllocationCallbacks* pAllocator, + VkDeviceMemory* pMemory +) { + LAYER_TRACE(__func__); + + std::unique_lock lock{g_vulkanLock}; + auto* layer = Device::retrieve(device); + const auto& config = layer->instance->config; + + const int disable_external_compression = config.disable_external_compression(); + + // Absolute passthrough if feature is off + if (disable_external_compression == 0) + { + lock.unlock(); + return layer->driver.vkAllocateMemory(device, pAllocateInfo, pAllocator, pMemory); + } + + // Scan pNext for imports that we cannot sanitize -> hard-fail to keep the 100% guarantee that we can disable external compression + for (const auto* n = reinterpret_cast(pAllocateInfo->pNext); n; n = n->pNext) + { + + if (n->sType == VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID) + { + LAYER_LOG("[libGPULayers] vkAllocateMemory: AHardwareBuffer IMPORT detected. " + "Cannot guarantee external compression is disabled (buffer created outside Vulkan). Failing by policy."); + return VK_ERROR_FEATURE_NOT_PRESENT; + } + + if (n->sType == VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR) + { + const auto* fdInfo = reinterpret_cast(n); + + if (fdInfo->handleType == VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT) + { + LAYER_LOG("[libGPULayers] vkAllocateMemory: DMA-BUF memory IMPORT detected (fd=%d). " + "Cannot guarantee external compression (DRM modifier may imply compression). Failing by policy.", + fdInfo->fd); + return VK_ERROR_FEATURE_NOT_PRESENT; + } + } + + } + + // Release the lock to call into the driver + lock.unlock(); + return layer->driver.vkAllocateMemory(device, pAllocateInfo, pAllocator, pMemory); +} diff --git a/layer_gpu_support/source/layer_device_functions_image.cpp b/layer_gpu_support/source/layer_device_functions_image.cpp index e680e6d..629dee5 100644 --- a/layer_gpu_support/source/layer_device_functions_image.cpp +++ b/layer_gpu_support/source/layer_device_functions_image.cpp @@ -44,22 +44,25 @@ extern std::mutex g_vulkanLock; static VkImageCompressionFixedRateFlagsEXT getSupportedCompressionLevels(Device* layer, const VkImageCreateInfo* pCreateInfo) { - VkImageCompressionControlEXT compressionInfo { + VkImageCompressionControlEXT compressionInfo + { .sType = VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT, .pNext = nullptr, .flags = VK_IMAGE_COMPRESSION_FIXED_RATE_DEFAULT_EXT, .compressionControlPlaneCount = 0, - .pFixedRateFlags = 0, + .pFixedRateFlags = nullptr, }; - VkImageCompressionPropertiesEXT compressionProperties { + VkImageCompressionPropertiesEXT compressionProperties + { .sType = VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_PROPERTIES_EXT, .pNext = nullptr, .imageCompressionFlags = 0, .imageCompressionFixedRateFlags = 0, }; - VkPhysicalDeviceImageFormatInfo2 formatInfo { + VkPhysicalDeviceImageFormatInfo2 formatInfo + { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, .pNext = reinterpret_cast(&compressionInfo), .format = pCreateInfo->format, @@ -69,7 +72,8 @@ static VkImageCompressionFixedRateFlagsEXT getSupportedCompressionLevels(Device* .flags = pCreateInfo->flags, }; - VkImageFormatProperties2 formatProperties { + VkImageFormatProperties2 formatProperties + { .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, .pNext = reinterpret_cast(&compressionProperties), .imageFormatProperties = {}, @@ -83,6 +87,82 @@ static VkImageCompressionFixedRateFlagsEXT getSupportedCompressionLevels(Device* return compressionProperties.imageCompressionFixedRateFlags; } +static bool heuristic_is_present_like_image(const VkImageCreateInfo* ci) +{ + if (!ci) + { + return false; + } + + // Shape & sampling + + // Guaranteed for presentable images + if (ci->imageType != VK_IMAGE_TYPE_2D) + { + return false; + } + + // Guaranteed for presentable images + if (ci->mipLevels != 1) + { + return false; + } + + // Guaranteed for presentable images + if (ci->samples != VK_SAMPLE_COUNT_1_BIT) + { + return false; + } + + // Guaranteed for presentable images + if (ci->extent.depth != 1) + { + return false; + } + + // Might introduce false negative, stereo images are excluded + if (ci->arrayLayers != 1) + { + return false; + } + + // Prefer common present-ish color formats. + switch (ci->format) + { + case VK_FORMAT_B8G8R8A8_UNORM: + case VK_FORMAT_B8G8R8A8_SRGB: + case VK_FORMAT_R8G8B8A8_UNORM: + case VK_FORMAT_R8G8B8A8_SRGB: + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: + case VK_FORMAT_A2R10G10B10_UNORM_PACK32: + case VK_FORMAT_B10G11R11_UFLOAT_PACK32: + case VK_FORMAT_R16G16B16A16_UNORM: + case VK_FORMAT_R16G16B16A16_SFLOAT: // HDR paths + case VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16: // HDR paths + break; // allowed + default: + return false; + } + + // Square images are likely not for present + if (ci->extent.width == ci->extent.height) + { + return false; + } + + // Usage & tiling + const VkImageUsageFlags u = ci->usage; + // Color image + const bool color_rt = (u & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0; + // Needed for the final color image that gets blitted to the swapchain + const bool xfer_src = ((u & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) != 0) || ((u & VK_IMAGE_USAGE_SAMPLED_BIT) != 0); + const bool til_ok = (ci->tiling == VK_IMAGE_TILING_OPTIMAL) || + (ci->tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT); + + return color_rt && xfer_src && til_ok; +} + + /* See Vulkan API for documentation. */ template<> VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage(VkDevice device, @@ -93,80 +173,299 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage(VkDevice device, LAYER_TRACE(__func__); // Hold the lock to access layer-wide global store - std::unique_lock lock {g_vulkanLock}; + std::unique_lock lock{g_vulkanLock}; auto* layer = Device::retrieve(device); const auto& config = layer->instance->config; - bool forceDisable = config.framebuffer_disable_all_compression(); - bool forceDefault = config.framebuffer_force_default_compression(); - uint32_t allowedLevels = config.framebuffer_force_fixed_rate_compression(); + const bool forceDisable = config.framebuffer_disable_all_compression(); + const bool forceDefault = config.framebuffer_force_default_compression(); + const uint32_t allowedLevels = config.framebuffer_force_fixed_rate_compression(); - // Release the lock to call into the driver + const int disable_external_compression = config.disable_external_compression(); lock.unlock(); - // If we are forcing fixed rate then query the options available - // Images we cannot change are left unmodified + uint32_t selectedLevel = VK_IMAGE_COMPRESSION_FIXED_RATE_NONE_EXT; - if (allowedLevels) + if (allowedLevels) { auto compressionLevels = getSupportedCompressionLevels(layer, pCreateInfo); - uint32_t availableLevels = static_cast(compressionLevels); - uint32_t testableLevels = availableLevels & allowedLevels; + const uint32_t availableLevels = static_cast(compressionLevels); + const uint32_t testableLevels = availableLevels & allowedLevels; + if (testableLevels) + { + const auto zeros = std::countr_zero(testableLevels); + selectedLevel = (1u << zeros); // highest matching ratio + } + } + + // -------------------------------------------- + // Heuristic + external export intent detection + // -------------------------------------------- + const bool present_like = heuristic_is_present_like_image(pCreateInfo); - // Extract the highest matching compression ratio - if (testableLevels) + bool wants_dma_buf = false; + bool wants_ahb_export = false; + for (const VkBaseInStructure* n = reinterpret_cast(pCreateInfo->pNext); n; n = n->pNext) + { + if (n->sType == VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO) { - auto zeros = std::countr_zero(testableLevels); - selectedLevel = 1 << zeros; + const auto* ext = reinterpret_cast(n); + if (ext->handleTypes & VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT) + { + wants_dma_buf = true; + } + + if (ext->handleTypes & VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID) + { + wants_ahb_export = true; + } } } - // Create modifiable structures we can patch - vku::safe_VkImageCreateInfo newCreateInfoSafe(pCreateInfo); - auto* newCreateInfo = reinterpret_cast(&newCreateInfoSafe); - // We know we can const-cast here because this is a safe-struct clone - void* pNextBase = const_cast(newCreateInfoSafe.pNext); + // ------------------------------------------------- + // Rebuild the pNext chain (drop DRM modifier nodes) + // ------------------------------------------------- + VkImageCreateInfo local = *pCreateInfo; + + const VkBaseInStructure* src = reinterpret_cast(pCreateInfo->pNext); + VkBaseOutStructure* rebuilt_head = nullptr; + VkBaseOutStructure* rebuilt_tail = nullptr; + + auto append = [&](VkBaseOutStructure* n) + { + n->pNext = nullptr; + if (!rebuilt_head) + { + rebuilt_head = rebuilt_tail = n; + } + else + { + rebuilt_tail->pNext = n; + rebuilt_tail = n; + } + }; + + auto clone = [&](const VkBaseInStructure* n)->VkBaseOutStructure* + { + size_t sz = sizeof(VkBaseOutStructure); + switch (n->sType) + { + case VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO: sz = sizeof(VkImageFormatListCreateInfo); break; + case VK_STRUCTURE_TYPE_IMAGE_STENCIL_USAGE_CREATE_INFO: sz = sizeof(VkImageStencilUsageCreateInfo); break; + case VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO: sz = sizeof(VkImageViewUsageCreateInfo); break; + case VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO: sz = sizeof(VkExternalMemoryImageCreateInfo); break; + case VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT: sz = sizeof(VkImageDrmFormatModifierListCreateInfoEXT); break; + case VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT: sz = sizeof(VkImageDrmFormatModifierExplicitCreateInfoEXT); break; + case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: sz = sizeof(VkImageCompressionControlEXT); break; + default: break; + } + auto* out = static_cast(malloc(sz)); + memcpy(out, n, sz); + out->pNext = nullptr; + return out; + }; - // Create extra structures we can patch in - VkImageCompressionControlEXT newCompressionControl = vku::InitStructHelper(); - VkImageCompressionControlEXT* compressionControl = &newCompressionControl; + bool dropped_modifier = false; + VkImageCompressionControlEXT* userICCClone = nullptr; // capture cloned ICC if app provided it - auto* userCompressionControl = vku::FindStructInPNextChain(pNextBase); - if (userCompressionControl) + for (; src; src = src->pNext) { - compressionControl = userCompressionControl; + + bool drop = false; + + if (wants_dma_buf) + { + if (src->sType == VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT || + src->sType == VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT) + { + drop = true; + } + } + + if (drop) + { + dropped_modifier = true; + continue; + } + + VkBaseOutStructure* c = clone(src); + + if (c->sType == VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT) + { + userICCClone = reinterpret_cast(c); + } + append(c); + } + + if (dropped_modifier && local.tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT) + { + local.tiling = VK_IMAGE_TILING_OPTIMAL; } - bool patchNeeded = compressionControl != userCompressionControl; + // ----------------------------------------------------------------- + // Decide the final ICC state (apply BOTH policies in this order): + // 1) framebuffer_* policy (disable/default/fixed-rate) + // 2) disable_external_compression policy (may override to DISABLED) + // ----------------------------------------------------------------- + VkImageCompressionControlEXT injectedICC{}; // used only if we need to inject + VkImageCompressionControlEXT* icc = nullptr; - if (forceDisable) + // If app already supplied ICC, we’ll modify that one; otherwise we’ll inject ours if needed. + if (userICCClone) + { + icc = userICCClone; + } + else { - compressionControl->flags = VK_IMAGE_COMPRESSION_DISABLED_EXT; - compressionControl->compressionControlPlaneCount = 0; - compressionControl->pFixedRateFlags = nullptr; + // We'll only chain this if we end up needing ICC at all. + injectedICC.sType = VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT; + injectedICC.pNext = nullptr; + icc = &injectedICC; } - else if (forceDefault) + + bool needICC = false; + + // (1) High-priority framebuffer policy + if (forceDisable) + { + icc->flags = VK_IMAGE_COMPRESSION_DISABLED_EXT; + icc->compressionControlPlaneCount = 0; + icc->pFixedRateFlags = nullptr; + needICC = true; + } + else if (forceDefault) { - compressionControl->flags = VK_IMAGE_COMPRESSION_DEFAULT_EXT; - compressionControl->compressionControlPlaneCount = 0; - compressionControl->pFixedRateFlags = nullptr; + icc->flags = VK_IMAGE_COMPRESSION_DEFAULT_EXT; + icc->compressionControlPlaneCount = 0; + icc->pFixedRateFlags = nullptr; + needICC = true; + } + else if (selectedLevel != VK_IMAGE_COMPRESSION_FIXED_RATE_NONE_EXT) + { + icc->flags = VK_IMAGE_COMPRESSION_FIXED_RATE_EXPLICIT_EXT; + icc->compressionControlPlaneCount = 1; + icc->pFixedRateFlags = reinterpret_cast(&selectedLevel); + needICC = true; } - else if (selectedLevel) + + const bool need_icc_disable = + (disable_external_compression == 1 && wants_ahb_export) || + (disable_external_compression == 2 && (wants_ahb_export || present_like)); + + if (disable_external_compression == 0) { - compressionControl->flags = VK_IMAGE_COMPRESSION_FIXED_RATE_EXPLICIT_EXT; - compressionControl->compressionControlPlaneCount = 1; - compressionControl->pFixedRateFlags = reinterpret_cast(&selectedLevel); + // passthrough of external compression policy; nothing extra here + } + else if (need_icc_disable) + { + // For a guarantee, ensure the device supports VK_EXT_image_compression_control + bool have_icc_ext = false; + { + uint32_t extCount = 0; + vkEnumerateDeviceExtensionProperties(layer->physicalDevice, nullptr, &extCount, nullptr); + std::vector exts(extCount); + vkEnumerateDeviceExtensionProperties(layer->physicalDevice, nullptr, &extCount, exts.data()); + for (const auto& e : exts) + { + if (strcmp(e.extensionName, VK_EXT_IMAGE_COMPRESSION_CONTROL_EXTENSION_NAME) == 0) { + have_icc_ext = true; break; + } + } + } + + if (!have_icc_ext) + { + // Strict behavior to preserve the guarantee; free and fail. + while (rebuilt_head) + { + auto* next = reinterpret_cast(rebuilt_head->pNext); + free(rebuilt_head); + rebuilt_head = next; + } + + return VK_ERROR_FEATURE_NOT_PRESENT; + } + + // Override to DISABLED (this runs last by design) + icc->flags = VK_IMAGE_COMPRESSION_DISABLED_EXT; + icc->compressionControlPlaneCount = 0; + icc->pFixedRateFlags = nullptr; + needICC = true; } - else + + // If mode==1 and not an external export, leave original info (except DRM drop for DMA-BUF path which we didn’t do here) + // NOTE: Your original code only did the passthrough exception for mode==1 && !external. + if (disable_external_compression == 1 && !wants_dma_buf && !wants_ahb_export) { - patchNeeded = false; + if (needICC) + { + if (!userICCClone) + { + // Prepend our injected ICC ahead of the rebuilt chain + injectedICC.pNext = rebuilt_head; + local.pNext = &injectedICC; + } + else + { + // ICC already in rebuilt chain; just attach the rebuilt chain + local.pNext = rebuilt_head; + } + + VkResult r = layer->driver.vkCreateImage(device, &local, pAllocator, pImage); + + while (rebuilt_head) + { + auto* next = reinterpret_cast(rebuilt_head->pNext); + free(rebuilt_head); + rebuilt_head = next; + } + + return r; + } + + while (rebuilt_head) + { + auto* next = reinterpret_cast(rebuilt_head->pNext); + free(rebuilt_head); + rebuilt_head = next; + } + + return layer->driver.vkCreateImage(device, pCreateInfo, pAllocator, pImage); } - // Add a config if not already configured by the application - if (patchNeeded) + // --------------------------------------------- + // Stitch final pNext chain and call the driver + // --------------------------------------------- + if (needICC) + { + if (!userICCClone) + { + // Prepend our injected ICC ahead of the rebuilt chain + injectedICC.pNext = rebuilt_head; + local.pNext = &injectedICC; + } + else + { + // ICC already in rebuilt chain; just attach the rebuilt chain + local.pNext = rebuilt_head; + } + } + else { - vku::AddToPnext(newCreateInfoSafe, *compressionControl); + // No ICC needed; just attach the rebuilt chain (may be null) + local.pNext = rebuilt_head; } - return layer->driver.vkCreateImage(device, newCreateInfo, pAllocator, pImage); + VkResult r = layer->driver.vkCreateImage(device, &local, pAllocator, pImage); + + // Cleanup cloned nodes + while (rebuilt_head) + { + auto* next = reinterpret_cast(rebuilt_head->pNext); + free(rebuilt_head); + rebuilt_head = next; + } + + return r; } + diff --git a/layer_gpu_support/source/layer_device_functions_swapchain.cpp b/layer_gpu_support/source/layer_device_functions_swapchain.cpp new file mode 100644 index 0000000..140a1dc --- /dev/null +++ b/layer_gpu_support/source/layer_device_functions_swapchain.cpp @@ -0,0 +1,195 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2025 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ +#include +#include + +#include "device.hpp" +#include "framework/device_dispatch_table.hpp" + +#include +#include + +extern std::mutex g_vulkanLock; + + /** + * @brief Intercept vkCreateSwapchainKHR and, when configured, disable external + * compression by forcing uncompressed swapchain images; otherwise pass through. + * + * @param device Logical device creating the swapchain. + * @param pCreateInfo Swapchain creation parameters; the pNext chain is + * sanitized (DRM modifier structs rejected, any + * VkImageCompressionControlEXT removed) and augmented + * with VK_IMAGE_COMPRESSION_DISABLED_EXT when compression + * disablement is enabled in the layer config. + * @param pAllocator Optional allocation callbacks. + * @param pSwapchain Receives the created swapchain handle on success. + * + * @return VK_SUCCESS on success, or a VkResult propagated from the driver. + * Returns VK_ERROR_FEATURE_NOT_PRESENT if + * VK_EXT_image_compression_control is unavailable or if DRM modifier + * create-info is present in the pNext chain. + */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateSwapchainKHR( + VkDevice device, + const VkSwapchainCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSwapchainKHR* pSwapchain +) { + LAYER_TRACE(__func__); + + // --- Read config first, then IMMEDIATE passthrough if disabled --- + Device* layer = nullptr; + int disable_external_compression = 0; + std::unique_lock lock { g_vulkanLock }; + layer = Device::retrieve(device); + const auto& config = layer->instance->config; + disable_external_compression = config.disable_external_compression(); + lock.unlock(); // ensure no lock is held for any Vulkan/loader calls below + + // 0) Absolute passthrough if feature is off — do nothing else + if (disable_external_compression == 0) + { + VkResult ret = layer->driver.vkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain); + return ret; + } + + // 1) Require VK_EXT_image_compression_control (enumerate with DOWN-CHAIN dispatch; no lock held) + bool have_image_compression_control = false; + { + uint32_t extCount = 0; + layer->instance->driver.vkEnumerateDeviceExtensionProperties( + layer->physicalDevice, nullptr, &extCount, nullptr); + std::vector exts(extCount); + layer->instance->driver.vkEnumerateDeviceExtensionProperties( + layer->physicalDevice, nullptr, &extCount, exts.data()); + + for (const auto& e : exts) + { + if (strcmp(e.extensionName, VK_EXT_IMAGE_COMPRESSION_CONTROL_EXTENSION_NAME) == 0) + { + have_image_compression_control = true; break; + } + } + } + + if (!have_image_compression_control) + { + LAYER_LOG("[libGPULayers] layer_vkCreateSwapchainKHR ERROR: VK_EXT_image_compression_control not supported; returning VK_ERROR_FEATURE_NOT_PRESENT"); + return VK_ERROR_FEATURE_NOT_PRESENT; // no lock held here + } + + // 2) Refuse any DRM modifier nodes in pNext (they can mandate compression) + { + const VkBaseInStructure* node = reinterpret_cast(pCreateInfo->pNext); + + for (; node; node = node->pNext) + { + if (node->sType == VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT || + node->sType == VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT) + { + LAYER_LOG("[libGPULayers] layer_vkCreateSwapchainKHR ERROR: DRM modifier create-info present in swapchain pNext; returning VK_ERROR_FEATURE_NOT_PRESENT"); + return VK_ERROR_FEATURE_NOT_PRESENT; // no lock held here + } + } + } + + // 3) Rebuild pNext, filtering out any existing ImageCompressionControl nodes + VkSwapchainCreateInfoKHR local = *pCreateInfo; + const VkBaseInStructure* src = reinterpret_cast(pCreateInfo->pNext); + VkBaseOutStructure* head = nullptr; + VkBaseOutStructure* tail = nullptr; + + auto append = [&](VkBaseOutStructure* n) + { + n->pNext = nullptr; + + if (!head) + { + head = tail = n; + } + else + { + tail->pNext = n; + tail = n; + } + }; + + auto clone = [](const VkBaseInStructure* n)->VkBaseOutStructure* + { + size_t sz = sizeof(VkBaseOutStructure); + + if (n->sType == VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT) + { + sz = sizeof(VkImageDrmFormatModifierListCreateInfoEXT); + } + else if (n->sType == VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT) + { + sz = sizeof(VkImageDrmFormatModifierExplicitCreateInfoEXT); + } + else if (n->sType == VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT) + { + sz = sizeof(VkImageCompressionControlEXT); + } + + auto* out = reinterpret_cast(malloc(sz)); + memcpy(out, n, sz); + out->pNext = nullptr; + + return out; + }; + + for (; src; src = src->pNext) + { + const bool drop = (src->sType == VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT); + if (!drop) + { + append(clone(src)); + } + } + + VkImageCompressionControlEXT compression_ctl { + VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT, + head, // keep the rest of the chain + VK_IMAGE_COMPRESSION_DISABLED_EXT, // compression OFF for swapchain images + 0, + nullptr + }; + + local.pNext = &compression_ctl; + + // 4) Call down-chain create (no lock held) + VkResult r = layer->driver.vkCreateSwapchainKHR(device, &local, pAllocator, pSwapchain); + + // Free rebuilt nodes (do NOT free compression_ctl — it’s on the stack) + while (head) + { + auto* next = reinterpret_cast(head->pNext); + free(head); + head = next; + } + + return r; +} diff --git a/layer_gpu_support/source/layer_instance_functions.hpp b/layer_gpu_support/source/layer_instance_functions.hpp new file mode 100644 index 0000000..712d696 --- /dev/null +++ b/layer_gpu_support/source/layer_instance_functions.hpp @@ -0,0 +1,53 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2025 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ + +#pragma once + +#include + +#include "framework/instance_functions.hpp" +#include "framework/utils.hpp" + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkGetPhysicalDeviceImageFormatProperties(VkPhysicalDevice physicalDevice, + VkFormat format, + VkImageType type, + VkImageTiling tiling, + VkImageUsageFlags usage, + VkImageCreateFlags flags, + VkImageFormatProperties* pImageFormatProperties); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkGetPhysicalDeviceImageFormatProperties2(VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceImageFormatInfo2* pImageFormatInfo, + VkImageFormatProperties2* pImageFormatProperties); + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkGetPhysicalDeviceImageFormatProperties2KHR(VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceImageFormatInfo2* pImageFormatInfo, + VkImageFormatProperties2* pImageFormatProperties); \ No newline at end of file diff --git a/layer_gpu_support/source/layer_instance_functions_get_physical_properties.cpp b/layer_gpu_support/source/layer_instance_functions_get_physical_properties.cpp new file mode 100644 index 0000000..b7c3fe7 --- /dev/null +++ b/layer_gpu_support/source/layer_instance_functions_get_physical_properties.cpp @@ -0,0 +1,273 @@ +/* + * SPDX-License-Identifier: MIT + * ---------------------------------------------------------------------------- + * Copyright (c) 2025 Arm Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * ---------------------------------------------------------------------------- + */ +#include +#include + +#include "device.hpp" +#include "framework/device_dispatch_table.hpp" + +#include +#include + +extern std::mutex g_vulkanLock; + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkGetPhysicalDeviceImageFormatProperties( + VkPhysicalDevice physicalDevice, + VkFormat format, + VkImageType type, + VkImageTiling tiling, + VkImageUsageFlags usage, + VkImageCreateFlags flags, + VkImageFormatProperties* pImageFormatProperties +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Instance::retrieve(physicalDevice); + + // Release the lock to call into the driver + lock.unlock(); + return layer->driver.vkGetPhysicalDeviceImageFormatProperties(physicalDevice, format, type, tiling, usage, flags, pImageFormatProperties); +} + +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkGetPhysicalDeviceImageFormatProperties2( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceImageFormatInfo2* pImageFormatInfo, + VkImageFormatProperties2* pImageFormatProperties +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Instance::retrieve(physicalDevice); + const auto& config = layer->config; + + // Config gate + int disable_external_compression = config.disable_external_compression(); + + // Inspect query intent + VkExternalMemoryHandleTypeFlagBits handle{}; + bool has_external_info = false; + + for (const VkBaseInStructure* n = reinterpret_cast(pImageFormatInfo->pNext); n; n = n->pNext) + { + if (n->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO) + { + has_external_info = true; + const auto* ext = reinterpret_cast(n); + handle = static_cast(ext->handleType); + } + } + + // Only act when we’re asked about DMA-BUF AND policy is enabled + const bool wants_dma_buf = has_external_info && (handle == VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT); + + if (!(disable_external_compression == 0 && wants_dma_buf)) + { + // Pass-through (nothing to strip) + return layer->driver.vkGetPhysicalDeviceImageFormatProperties2(physicalDevice, pImageFormatInfo, pImageFormatProperties); + } + + // Build a filtered copy that removes modifier filters from the QUERY + VkPhysicalDeviceImageFormatInfo2 local = *pImageFormatInfo; + const VkBaseInStructure* src = reinterpret_cast(pImageFormatInfo->pNext); + VkBaseOutStructure* head = nullptr; + VkBaseOutStructure* tail = nullptr; + + auto append = [&](VkBaseOutStructure* node) + { + node->pNext = nullptr; + if (!head) { + head = node; + tail = node; + } + else + { + tail->pNext = node; + tail = node; + } + }; + + auto clone_base = [](const VkBaseInStructure* node)->VkBaseOutStructure* + { + VkBaseOutStructure* out = reinterpret_cast(malloc(sizeof(VkBaseOutStructure))); + memcpy(out, node, sizeof(VkBaseOutStructure)); + out->pNext = nullptr; + return out; + }; + + for (; src; src = src->pNext) + { + + bool drop = false; + + if (src->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT) + { + drop = true; + } + + if (!drop) + { + append(clone_base(src)); + } + + } + + if (local.tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT) + { + local.tiling = VK_IMAGE_TILING_OPTIMAL; + } + local.pNext = head; + + VkResult r = layer->driver.vkGetPhysicalDeviceImageFormatProperties2(physicalDevice, &local, pImageFormatProperties); + + while (head) + { + auto* next = reinterpret_cast(head->pNext); + free(head); + head = next; + } + + if (r != VK_SUCCESS) + { + LAYER_LOG("[libGPULayers] Driver returned %d after stripping modifiers.", r); + } + + lock.unlock(); + return r; +} + + +/* See Vulkan API for documentation. */ +template <> +VKAPI_ATTR VkResult VKAPI_CALL layer_vkGetPhysicalDeviceImageFormatProperties2KHR( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceImageFormatInfo2* pImageFormatInfo, + VkImageFormatProperties2* pImageFormatProperties +) { + LAYER_TRACE(__func__); + + // Hold the lock to access layer-wide global store + std::unique_lock lock { g_vulkanLock }; + auto* layer = Instance::retrieve(physicalDevice); + const auto& config = layer->config; + + // Config gate: only enforce policy for external exports when enabled. + int disable_external_compression = config.disable_external_compression(); + + // Inspect query intent + VkExternalMemoryHandleTypeFlagBits handle{}; + bool has_external_info = false; + + for (const VkBaseInStructure* n = reinterpret_cast(pImageFormatInfo->pNext); n; n = n->pNext) + { + if (n->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO) + { + has_external_info = true; + const auto* ext = reinterpret_cast(n); + handle = static_cast(ext->handleType); + } + } + + // Only act when we’re asked about DMA-BUF AND policy is enabled + const bool wants_dma_buf = has_external_info && (handle == VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT); + + if (!(disable_external_compression == 0 && wants_dma_buf)) + { + // Pass-through (nothing to strip) + return layer->driver.vkGetPhysicalDeviceImageFormatProperties2KHR(physicalDevice, pImageFormatInfo, pImageFormatProperties); + } + + // Build a filtered copy that removes modifier filters from the QUERY + VkPhysicalDeviceImageFormatInfo2 local = *pImageFormatInfo; + const VkBaseInStructure* src = reinterpret_cast(pImageFormatInfo->pNext); + VkBaseOutStructure* head = nullptr; + VkBaseOutStructure* tail = nullptr; + + auto append = [&](VkBaseOutStructure* node) + { + node->pNext = nullptr; + + if (!head) + { + head = node; + tail = node; + } else { + tail->pNext = node; + tail = node; + } + }; + + auto clone_base = [](const VkBaseInStructure* node)->VkBaseOutStructure* + { + VkBaseOutStructure* out = reinterpret_cast(malloc(sizeof(VkBaseOutStructure))); + memcpy(out, node, sizeof(VkBaseOutStructure)); + out->pNext = nullptr; + return out; + }; + + for (; src; src = src->pNext) + { + bool drop = false; + + if (src->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT) + { + drop = true; + } + + if (!drop) + { + append(clone_base(src)); + } + } + + if (local.tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT) + { + local.tiling = VK_IMAGE_TILING_OPTIMAL; + } + + local.pNext = head; + + VkResult r = layer->driver.vkGetPhysicalDeviceImageFormatProperties2KHR(physicalDevice, &local, pImageFormatProperties); + + while (head) + { + auto* next = reinterpret_cast(head->pNext); + free(head); + head = next; + } + + if (r != VK_SUCCESS) + { + LAYER_LOG("[libGPULayers] Driver (KHR) returned %d after stripping modifiers.", r); + } + + lock.unlock(); + return r; +} \ No newline at end of file