From 0af559ddfa44308de44fef083fa48f55a0b216af Mon Sep 17 00:00:00 2001 From: Marco Leonardi Date: Thu, 30 Oct 2025 17:38:57 +0100 Subject: [PATCH 1/3] Adding the possibility to disable external compression --- layer_gpu_support/README_LAYER.md | 14 +- layer_gpu_support/layer_config.json | 3 +- layer_gpu_support/source/CMakeLists.txt | 5 +- layer_gpu_support/source/layer_config.cpp | 10 + layer_gpu_support/source/layer_config.hpp | 22 ++ .../source/layer_device_functions.hpp | 20 ++ ...layer_device_functions_allocate_memory.cpp | 112 +++++++ .../source/layer_device_functions_image.cpp | 298 +++++++++++++++--- .../layer_device_functions_swapchain.cpp | 159 ++++++++++ .../source/layer_instance_functions.hpp | 59 ++++ ...ance_functions_get_physical_properties.cpp | 236 ++++++++++++++ 11 files changed, 883 insertions(+), 55 deletions(-) create mode 100644 layer_gpu_support/source/layer_device_functions_allocate_memory.cpp create mode 100644 layer_gpu_support/source/layer_device_functions_swapchain.cpp create mode 100644 layer_gpu_support/source/layer_instance_functions.hpp create mode 100644 layer_gpu_support/source/layer_instance_functions_get_physical_properties.cpp diff --git a/layer_gpu_support/README_LAYER.md b/layer_gpu_support/README_LAYER.md index 4d60ead..378c65a 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 so that external compression is disabled. In case is not possible to garantuee + that external compression is used, the layer will return VK_ERROR_FEATURE_NOT_PRESENT. + It should be set to `2` only if the application is not presenting, and is targeting vkCreateImage. + Option `2` is an heuristic, compression could be disabled accidentally on internal images, + and is also possible that it misses 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..85a475e 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 garantuee!) + */ + 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..ff1757e 100644 --- a/layer_gpu_support/source/layer_device_functions.hpp +++ b/layer_gpu_support/source/layer_device_functions.hpp @@ -369,3 +369,23 @@ 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 + +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..2aa9f6d --- /dev/null +++ b/layer_gpu_support/source/layer_device_functions_allocate_memory.cpp @@ -0,0 +1,112 @@ +/* + * 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__); + + //fprintf(stderr, "[libGPULayers] HIT %s\n", __func__); fflush(stderr); + + std::unique_lock lock { g_vulkanLock }; + auto* layer = Device::retrieve(device); + const auto& config = layer->instance->config; + + 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 VkBaseInStructure* n = (const VkBaseInStructure*)pAllocateInfo->pNext; n; n = n->pNext) { + + if (n->sType == VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID) { + printf("[libGPULayers] vkAllocateMemory: AHardwareBuffer IMPORT detected. " + "Cannot guarantee external compression is disabled (buffer created outside Vulkan). Failing by policy.\n"); + return VK_ERROR_FEATURE_NOT_PRESENT; + } + + if (n->sType == VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR) { + const VkImportMemoryFdInfoKHR* fdInfo = (const VkImportMemoryFdInfoKHR*)n; + if (fdInfo->handleType == VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT) { + printf("[libGPULayers] vkAllocateMemory: DMA-BUF memory IMPORT detected (fd=%d). " + "Cannot guarantee external compression (DRM modifier may imply compression). Failing by policy.\n", + 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..724fb90 100644 --- a/layer_gpu_support/source/layer_device_functions_image.cpp +++ b/layer_gpu_support/source/layer_device_functions_image.cpp @@ -83,6 +83,50 @@ static VkImageCompressionFixedRateFlagsEXT getSupportedCompressionLevels(Device* return compressionProperties.imageCompressionFixedRateFlags; } +static bool heuristic_is_present_like_image(const VkImageCreateInfo* ci) +{ + if (!ci) return false; + + // Shape & sampling + 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; // Guaranteed for presentable images + if (ci->arrayLayers != 1) return false; // Might introduce false negative, stereo images are excluded + + // 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; + const bool color_rt = (u & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0; // Color image + const bool xfer_src = (u & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) || (u & VK_IMAGE_USAGE_SAMPLED_BIT); // Needed for the final color image that gets blitted to the swapchain + 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 +137,230 @@ 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 + } + } - // Extract the highest matching compression ratio - if (testableLevels) - { - auto zeros = std::countr_zero(testableLevels); - selectedLevel = 1 << zeros; + // -------------------------------------------- + // Heuristic + external export intent detection + // -------------------------------------------- + const bool present_like = heuristic_is_present_like_image(pCreateInfo); + + 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) { + 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); + if (disable_external_compression == 2) { + + static int total_present_like_images = 0; + if (present_like) { + total_present_like_images++; + } + } + + // ------------------------------------------------- + // 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; } + }; - // Create extra structures we can patch in - VkImageCompressionControlEXT newCompressionControl = vku::InitStructHelper(); - VkImageCompressionControlEXT* compressionControl = &newCompressionControl; + 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; + }; - auto* userCompressionControl = vku::FindStructInPNextChain(pNextBase); - if (userCompressionControl) - { - compressionControl = userCompressionControl; + bool dropped_modifier = false; + VkImageCompressionControlEXT* userICCClone = nullptr; // capture cloned ICC if app provided it + for (; src; src = src->pNext) { + 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) - { - compressionControl->flags = VK_IMAGE_COMPRESSION_DISABLED_EXT; - compressionControl->compressionControlPlaneCount = 0; - compressionControl->pFixedRateFlags = nullptr; + // If app already supplied ICC, we’ll modify that one; otherwise we’ll inject ours if needed. + if (userICCClone) { + icc = userICCClone; + } else { + // 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) - { - compressionControl->flags = VK_IMAGE_COMPRESSION_DEFAULT_EXT; - compressionControl->compressionControlPlaneCount = 0; - compressionControl->pFixedRateFlags = nullptr; + + 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) { + 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( + &const_cast(selectedLevel)); + needICC = true; } - else if (selectedLevel) - { - compressionControl->flags = VK_IMAGE_COMPRESSION_FIXED_RATE_EXPLICIT_EXT; - compressionControl->compressionControlPlaneCount = 1; - compressionControl->pFixedRateFlags = reinterpret_cast(&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) { + // 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 = (VkBaseOutStructure*)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 - { - patchNeeded = false; + + // 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) { + 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 = (VkBaseOutStructure*)rebuilt_head->pNext; free(rebuilt_head); rebuilt_head = next; } + return r; + } + while (rebuilt_head) { auto* next = (VkBaseOutStructure*)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) - { - vku::AddToPnext(newCreateInfoSafe, *compressionControl); + // --------------------------------------------- + // 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 { + // 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 = (VkBaseOutStructure*)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..1c8c6de --- /dev/null +++ b/layer_gpu_support/source/layer_device_functions_swapchain.cpp @@ -0,0 +1,159 @@ +/* + * 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) { + fprintf(stderr, "[libGPULayers] layer_vkCreateSwapchainKHR ERROR: VK_EXT_image_compression_control not supported; returning VK_ERROR_FEATURE_NOT_PRESENT\n"); fflush(stderr); + 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) { + fprintf(stderr, "[libGPULayers] layer_vkCreateSwapchainKHR ERROR: DRM modifier create-info present in swapchain pNext; returning VK_ERROR_FEATURE_NOT_PRESENT\n"); fflush(stderr); + 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 = (VkBaseOutStructure*)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 = (VkBaseOutStructure*)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..3adcb42 --- /dev/null +++ b/layer_gpu_support/source/layer_instance_functions.hpp @@ -0,0 +1,59 @@ +/* + * 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..191e888 --- /dev/null +++ b/layer_gpu_support/source/layer_instance_functions_get_physical_properties.cpp @@ -0,0 +1,236 @@ +/* + * 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 = (VkExternalMemoryHandleTypeFlagBits)0; + bool has_external_info = false; + bool saw_drm_modifier_info = false; + + for (const VkBaseInStructure* n = (const VkBaseInStructure*)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 = (const VkPhysicalDeviceExternalImageFormatInfo*)n; + handle = (VkExternalMemoryHandleTypeFlagBits)ext->handleType; + } + if (n->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT) { + saw_drm_modifier_info = true; + } + } + + // 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 = (const VkBaseInStructure*)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 = (VkBaseOutStructure*)malloc(sizeof(VkBaseOutStructure)); + memcpy(out, node, sizeof(VkBaseOutStructure)); + out->pNext = nullptr; + return out; + }; + + bool dropped_modifier = false; + for (; src; src = src->pNext) { + bool drop = false; + if (src->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT) { + drop = true; dropped_modifier = 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 = (VkBaseOutStructure*)head->pNext; free(head); head = next; } + + if (r != VK_SUCCESS) { + fprintf(stderr, "[libGPULayers] Driver returned %d after stripping modifiers.\n", 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; + + //fprintf(stderr, "[libGPULayers] HIT %s (KHR alias)\n", __func__); fflush(stderr); + + // Config gate: only enforce policy for external exports when enabled. + int disable_external_compression = config.disable_external_compression(); + + // Inspect query intent + VkExternalMemoryHandleTypeFlagBits handle = (VkExternalMemoryHandleTypeFlagBits)0; + bool has_external_info = false; + bool saw_drm_modifier_info = false; + + for (const VkBaseInStructure* n = (const VkBaseInStructure*)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 = (const VkPhysicalDeviceExternalImageFormatInfo*)n; + handle = (VkExternalMemoryHandleTypeFlagBits)ext->handleType; + } + if (n->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT) { + saw_drm_modifier_info = true; + } + } + + if (has_external_info) { + fprintf(stderr, "[libGPULayers] vkGetPhysicalDeviceImageFormatProperties2KHR: external handleType=0x%x\n", (unsigned)handle); fflush(stderr); + } else { + //fprintf(stderr, "[libGPULayers] vkGetPhysicalDeviceImageFormatProperties2KHR: no ExternalImageFormatInfo in query.\n"); fflush(stderr); + } + if (saw_drm_modifier_info) { + fprintf(stderr, "[libGPULayers]Query includes PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT\n"); fflush(stderr); + } + + // 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 = (const VkBaseInStructure*)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 = (VkBaseOutStructure*)malloc(sizeof(VkBaseOutStructure)); + memcpy(out, node, sizeof(VkBaseOutStructure)); + out->pNext = nullptr; + return out; + }; + + bool dropped_modifier = false; + for (; src; src = src->pNext) { + bool drop = false; + if (src->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT) { + drop = true; dropped_modifier = 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 = (VkBaseOutStructure*)head->pNext; free(head); head = next; } + + if (r != VK_SUCCESS) { + fprintf(stderr, "[libGPULayers] Driver (KHR) returned %d after stripping modifiers.\n", r); fflush(stderr); + } + + lock.unlock(); + return r; + +} \ No newline at end of file From 35f2419c7972546c7d42b29c08a76835fcbf6a1f Mon Sep 17 00:00:00 2001 From: Marco Leonardi Date: Fri, 31 Oct 2025 13:59:00 +0100 Subject: [PATCH 2/3] Remove unused variables --- .../source/layer_device_functions_image.cpp | 8 ------- ...ance_functions_get_physical_properties.cpp | 23 ++----------------- 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/layer_gpu_support/source/layer_device_functions_image.cpp b/layer_gpu_support/source/layer_device_functions_image.cpp index 724fb90..089f2a6 100644 --- a/layer_gpu_support/source/layer_device_functions_image.cpp +++ b/layer_gpu_support/source/layer_device_functions_image.cpp @@ -178,14 +178,6 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage(VkDevice device, } } - if (disable_external_compression == 2) { - - static int total_present_like_images = 0; - if (present_like) { - total_present_like_images++; - } - } - // ------------------------------------------------- // Rebuild the pNext chain (drop DRM modifier nodes) // ------------------------------------------------- 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 index 191e888..7ac03b0 100644 --- a/layer_gpu_support/source/layer_instance_functions_get_physical_properties.cpp +++ b/layer_gpu_support/source/layer_instance_functions_get_physical_properties.cpp @@ -74,7 +74,6 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkGetPhysicalDeviceImageFormatProperties2pNext; n; n = n->pNext) { if (n->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO) { @@ -82,9 +81,6 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkGetPhysicalDeviceImageFormatProperties2handleType; } - if (n->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT) { - saw_drm_modifier_info = true; - } } // Only act when we’re asked about DMA-BUF AND policy is enabled @@ -112,11 +108,10 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkGetPhysicalDeviceImageFormatProperties2pNext) { bool drop = false; if (src->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT) { - drop = true; dropped_modifier = true; + drop = true; } if (!drop) append(clone_base(src)); } @@ -160,7 +155,6 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkGetPhysicalDeviceImageFormatProperties2KH // Inspect query intent VkExternalMemoryHandleTypeFlagBits handle = (VkExternalMemoryHandleTypeFlagBits)0; bool has_external_info = false; - bool saw_drm_modifier_info = false; for (const VkBaseInStructure* n = (const VkBaseInStructure*)pImageFormatInfo->pNext; n; n = n->pNext) { if (n->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO) { @@ -168,18 +162,6 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkGetPhysicalDeviceImageFormatProperties2KH const auto* ext = (const VkPhysicalDeviceExternalImageFormatInfo*)n; handle = (VkExternalMemoryHandleTypeFlagBits)ext->handleType; } - if (n->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT) { - saw_drm_modifier_info = true; - } - } - - if (has_external_info) { - fprintf(stderr, "[libGPULayers] vkGetPhysicalDeviceImageFormatProperties2KHR: external handleType=0x%x\n", (unsigned)handle); fflush(stderr); - } else { - //fprintf(stderr, "[libGPULayers] vkGetPhysicalDeviceImageFormatProperties2KHR: no ExternalImageFormatInfo in query.\n"); fflush(stderr); - } - if (saw_drm_modifier_info) { - fprintf(stderr, "[libGPULayers]Query includes PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT\n"); fflush(stderr); } // Only act when we’re asked about DMA-BUF AND policy is enabled @@ -208,11 +190,10 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkGetPhysicalDeviceImageFormatProperties2KH return out; }; - bool dropped_modifier = false; for (; src; src = src->pNext) { bool drop = false; if (src->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT) { - drop = true; dropped_modifier = true; + drop = true; } if (!drop) append(clone_base(src)); } From 93221b91b3625da7e6ce4c35e9a14aee11e31f7b Mon Sep 17 00:00:00 2001 From: Marco Leonardi Date: Mon, 3 Nov 2025 16:33:11 +0100 Subject: [PATCH 3/3] Various code formatting --- layer_gpu_support/README_LAYER.md | 10 +- layer_gpu_support/source/layer_config.hpp | 2 +- .../source/layer_device_functions.hpp | 8 +- ...layer_device_functions_allocate_memory.cpp | 40 +-- .../source/layer_device_functions_image.cpp | 233 +++++++++++++----- .../layer_device_functions_swapchain.cpp | 66 +++-- .../source/layer_instance_functions.hpp | 32 +-- ...ance_functions_get_physical_properties.cpp | 136 +++++++--- 8 files changed, 363 insertions(+), 164 deletions(-) diff --git a/layer_gpu_support/README_LAYER.md b/layer_gpu_support/README_LAYER.md index 378c65a..8de583d 100644 --- a/layer_gpu_support/README_LAYER.md +++ b/layer_gpu_support/README_LAYER.md @@ -176,11 +176,11 @@ allocated and handled by the driver. 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 so that external compression is disabled. In case is not possible to garantuee - that external compression is used, the layer will return VK_ERROR_FEATURE_NOT_PRESENT. - It should be set to `2` only if the application is not presenting, and is targeting vkCreateImage. - Option `2` is an heuristic, compression could be disabled accidentally on internal images, - and is also possible that it misses external images, if they lack COLOR_ATTACHMENT_BIT. + 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 diff --git a/layer_gpu_support/source/layer_config.hpp b/layer_gpu_support/source/layer_config.hpp index 85a475e..8f24606 100644 --- a/layer_gpu_support/source/layer_config.hpp +++ b/layer_gpu_support/source/layer_config.hpp @@ -170,7 +170,7 @@ class LayerConfig * @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 garantuee!) + * 2 = strip compression on external images even without presentation, using heuristic (no guarantee!) */ int disable_external_compression() const; diff --git a/layer_gpu_support/source/layer_device_functions.hpp b/layer_gpu_support/source/layer_device_functions.hpp index ff1757e..a1a6c4e 100644 --- a/layer_gpu_support/source/layer_device_functions.hpp +++ b/layer_gpu_support/source/layer_device_functions.hpp @@ -370,7 +370,6 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage(VkDevice device, const VkAllocationCallbacks* pAllocator, VkImage* pImage); - // Functions for swapchains /* See Vulkan API for documentation. */ @@ -378,14 +377,13 @@ template <> VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateSwapchainKHR(VkDevice device, const VkSwapchainCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, - VkSwapchainKHR* pSwapchain - ); + 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 + 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 index 2aa9f6d..3aa0f50 100644 --- a/layer_gpu_support/source/layer_device_functions_allocate_memory.cpp +++ b/layer_gpu_support/source/layer_device_functions_allocate_memory.cpp @@ -67,46 +67,48 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkAllocateMemory( const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory ) { - LAYER_TRACE(__func__); - - //fprintf(stderr, "[libGPULayers] HIT %s\n", __func__); fflush(stderr); - std::unique_lock lock { g_vulkanLock }; + std::unique_lock lock{g_vulkanLock}; auto* layer = Device::retrieve(device); const auto& config = layer->instance->config; - int disable_external_compression = config.disable_external_compression(); + const int disable_external_compression = config.disable_external_compression(); - //absolute passthrough if feature is off - if (disable_external_compression == 0) { + // 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 VkBaseInStructure* n = (const VkBaseInStructure*)pAllocateInfo->pNext; n; n = n->pNext) { + for (const auto* n = reinterpret_cast(pAllocateInfo->pNext); n; n = n->pNext) + { - if (n->sType == VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID) { - printf("[libGPULayers] vkAllocateMemory: AHardwareBuffer IMPORT detected. " - "Cannot guarantee external compression is disabled (buffer created outside Vulkan). Failing by policy.\n"); + 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 VkImportMemoryFdInfoKHR* fdInfo = (const VkImportMemoryFdInfoKHR*)n; - if (fdInfo->handleType == VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT) { - printf("[libGPULayers] vkAllocateMemory: DMA-BUF memory IMPORT detected (fd=%d). " - "Cannot guarantee external compression (DRM modifier may imply compression). Failing by policy.\n", - fdInfo->fd); + 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 089f2a6..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 = {}, @@ -85,17 +89,46 @@ static VkImageCompressionFixedRateFlagsEXT getSupportedCompressionLevels(Device* static bool heuristic_is_present_like_image(const VkImageCreateInfo* ci) { - if (!ci) return false; + if (!ci) + { + return false; + } // Shape & sampling - 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; // Guaranteed for presentable images - if (ci->arrayLayers != 1) return false; // Might introduce false negative, stereo images are excluded + + // 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) { + switch (ci->format) + { case VK_FORMAT_B8G8R8A8_UNORM: case VK_FORMAT_B8G8R8A8_SRGB: case VK_FORMAT_R8G8B8A8_UNORM: @@ -112,16 +145,19 @@ static bool heuristic_is_present_like_image(const VkImageCreateInfo* ci) } // Square images are likely not for present - if (ci->extent.width == ci->extent.height) { + if (ci->extent.width == ci->extent.height) + { return false; } // Usage & tiling const VkImageUsageFlags u = ci->usage; - const bool color_rt = (u & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0; // Color image - const bool xfer_src = (u & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) || (u & VK_IMAGE_USAGE_SAMPLED_BIT); // Needed for the final color image that gets blitted to the swapchain - const bool til_ok = (ci->tiling == VK_IMAGE_TILING_OPTIMAL) || - (ci->tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT); + // 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; } @@ -141,8 +177,8 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage(VkDevice device, auto* layer = Device::retrieve(device); const auto& config = layer->instance->config; - const bool forceDisable = config.framebuffer_disable_all_compression(); - const bool forceDefault = config.framebuffer_force_default_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(); const int disable_external_compression = config.disable_external_compression(); @@ -150,11 +186,13 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage(VkDevice device, uint32_t selectedLevel = VK_IMAGE_COMPRESSION_FIXED_RATE_NONE_EXT; - if (allowedLevels) { + if (allowedLevels) + { auto compressionLevels = getSupportedCompressionLevels(layer, pCreateInfo); const uint32_t availableLevels = static_cast(compressionLevels); const uint32_t testableLevels = availableLevels & allowedLevels; - if (testableLevels) { + if (testableLevels) + { const auto zeros = std::countr_zero(testableLevels); selectedLevel = (1u << zeros); // highest matching ratio } @@ -167,14 +205,20 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage(VkDevice device, 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) { + for (const VkBaseInStructure* n = reinterpret_cast(pCreateInfo->pNext); n; n = n->pNext) + { + if (n->sType == VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO) + { 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; + } } } @@ -187,15 +231,25 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage(VkDevice device, VkBaseOutStructure* rebuilt_head = nullptr; VkBaseOutStructure* rebuilt_tail = nullptr; - auto append = [&](VkBaseOutStructure* n){ + auto append = [&](VkBaseOutStructure* n) + { n->pNext = nullptr; - if (!rebuilt_head) rebuilt_head = rebuilt_tail = n; - else { rebuilt_tail->pNext = n; rebuilt_tail = n; } + if (!rebuilt_head) + { + rebuilt_head = rebuilt_tail = n; + } + else + { + rebuilt_tail->pNext = n; + rebuilt_tail = n; + } }; - auto clone = [&](const VkBaseInStructure* n)->VkBaseOutStructure*{ + auto clone = [&](const VkBaseInStructure* n)->VkBaseOutStructure* + { size_t sz = sizeof(VkBaseOutStructure); - switch (n->sType) { + 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; @@ -213,25 +267,38 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage(VkDevice device, bool dropped_modifier = false; VkImageCompressionControlEXT* userICCClone = nullptr; // capture cloned ICC if app provided it - for (; src; src = src->pNext) { + + for (; src; src = src->pNext) + { + bool drop = false; - if (wants_dma_buf) { + + 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) { + src->sType == VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT) + { drop = true; } } - if (drop) { + + if (drop) + { dropped_modifier = true; continue; } + VkBaseOutStructure* c = clone(src); - if (c->sType == VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT) { + + 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) { + + if (dropped_modifier && local.tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT) + { local.tiling = VK_IMAGE_TILING_OPTIMAL; } @@ -244,9 +311,12 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage(VkDevice device, VkImageCompressionControlEXT* icc = nullptr; // If app already supplied ICC, we’ll modify that one; otherwise we’ll inject ours if needed. - if (userICCClone) { + if (userICCClone) + { icc = userICCClone; - } else { + } + else + { // 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; @@ -256,21 +326,25 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage(VkDevice device, bool needICC = false; // (1) High-priority framebuffer policy - if (forceDisable) { + if (forceDisable) + { icc->flags = VK_IMAGE_COMPRESSION_DISABLED_EXT; icc->compressionControlPlaneCount = 0; icc->pFixedRateFlags = nullptr; needICC = true; - } else if (forceDefault) { + } + else if (forceDefault) + { 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) { + } + 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( - &const_cast(selectedLevel)); + icc->pFixedRateFlags = reinterpret_cast(&selectedLevel); needICC = true; } @@ -278,9 +352,12 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage(VkDevice device, (disable_external_compression == 1 && wants_ahb_export) || (disable_external_compression == 2 && (wants_ahb_export || present_like)); - if (disable_external_compression == 0) { + if (disable_external_compression == 0) + { // passthrough of external compression policy; nothing extra here - } else if (need_icc_disable) { + } + else if (need_icc_disable) + { // For a guarantee, ensure the device supports VK_EXT_image_compression_control bool have_icc_ext = false; { @@ -288,15 +365,24 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage(VkDevice device, vkEnumerateDeviceExtensionProperties(layer->physicalDevice, nullptr, &extCount, nullptr); std::vector exts(extCount); vkEnumerateDeviceExtensionProperties(layer->physicalDevice, nullptr, &extCount, exts.data()); - for (const auto& e : exts) { + 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) { + + if (!have_icc_ext) + { // Strict behavior to preserve the guarantee; free and fail. - while (rebuilt_head) { auto* next = (VkBaseOutStructure*)rebuilt_head->pNext; free(rebuilt_head); rebuilt_head = next; } + while (rebuilt_head) + { + auto* next = reinterpret_cast(rebuilt_head->pNext); + free(rebuilt_head); + rebuilt_head = next; + } + return VK_ERROR_FEATURE_NOT_PRESENT; } @@ -305,42 +391,67 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage(VkDevice device, icc->compressionControlPlaneCount = 0; icc->pFixedRateFlags = nullptr; needICC = true; - } // 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) { - if (needICC) { - if (!userICCClone) { + if (disable_external_compression == 1 && !wants_dma_buf && !wants_ahb_export) + { + if (needICC) + { + if (!userICCClone) + { // Prepend our injected ICC ahead of the rebuilt chain injectedICC.pNext = rebuilt_head; local.pNext = &injectedICC; - } else { + } + 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 = (VkBaseOutStructure*)rebuilt_head->pNext; free(rebuilt_head); rebuilt_head = next; } + + while (rebuilt_head) + { + auto* next = reinterpret_cast(rebuilt_head->pNext); + free(rebuilt_head); + rebuilt_head = next; + } + return r; } - while (rebuilt_head) { auto* next = (VkBaseOutStructure*)rebuilt_head->pNext; free(rebuilt_head); rebuilt_head = next; } + + while (rebuilt_head) + { + auto* next = reinterpret_cast(rebuilt_head->pNext); + free(rebuilt_head); + rebuilt_head = next; + } + return layer->driver.vkCreateImage(device, pCreateInfo, pAllocator, pImage); } // --------------------------------------------- // Stitch final pNext chain and call the driver // --------------------------------------------- - if (needICC) { - if (!userICCClone) { + if (needICC) + { + if (!userICCClone) + { // Prepend our injected ICC ahead of the rebuilt chain injectedICC.pNext = rebuilt_head; local.pNext = &injectedICC; - } else { + } + else + { // ICC already in rebuilt chain; just attach the rebuilt chain local.pNext = rebuilt_head; } - } else { + } + else + { // No ICC needed; just attach the rebuilt chain (may be null) local.pNext = rebuilt_head; } @@ -348,11 +459,13 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage(VkDevice device, VkResult r = layer->driver.vkCreateImage(device, &local, pAllocator, pImage); // Cleanup cloned nodes - while (rebuilt_head) { - auto* next = (VkBaseOutStructure*)rebuilt_head->pNext; + 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 index 1c8c6de..140a1dc 100644 --- a/layer_gpu_support/source/layer_device_functions_swapchain.cpp +++ b/layer_gpu_support/source/layer_device_functions_swapchain.cpp @@ -70,7 +70,8 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateSwapchainKHR( 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) { + if (disable_external_compression == 0) + { VkResult ret = layer->driver.vkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain); return ret; } @@ -85,25 +86,31 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateSwapchainKHR( 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) { + 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) { - fprintf(stderr, "[libGPULayers] layer_vkCreateSwapchainKHR ERROR: VK_EXT_image_compression_control not supported; returning VK_ERROR_FEATURE_NOT_PRESENT\n"); fflush(stderr); + 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) { + + 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) { - fprintf(stderr, "[libGPULayers] layer_vkCreateSwapchainKHR ERROR: DRM modifier create-info present in swapchain pNext; returning VK_ERROR_FEATURE_NOT_PRESENT\n"); fflush(stderr); + 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 } } @@ -115,30 +122,52 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateSwapchainKHR( VkBaseOutStructure* head = nullptr; VkBaseOutStructure* tail = nullptr; - auto append = [&](VkBaseOutStructure* n){ + auto append = [&](VkBaseOutStructure* n) + { n->pNext = nullptr; - if (!head) { head = tail = n; } else { tail->pNext = n; tail = n; } + + if (!head) + { + head = tail = n; + } + else + { + tail->pNext = n; + tail = n; + } }; - auto clone = [](const VkBaseInStructure* n)->VkBaseOutStructure* { + 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 = (VkBaseOutStructure*)malloc(sz); + auto* out = reinterpret_cast(malloc(sz)); memcpy(out, n, sz); out->pNext = nullptr; return out; }; - for (; src; src = src->pNext) { + for (; src; src = src->pNext) + { const bool drop = (src->sType == VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT); - if (!drop) append(clone(src)); + if (!drop) + { + append(clone(src)); + } } VkImageCompressionControlEXT compression_ctl { @@ -148,12 +177,19 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateSwapchainKHR( 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 = (VkBaseOutStructure*)head->pNext; free(head); head = next; } + 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 index 3adcb42..712d696 100644 --- a/layer_gpu_support/source/layer_instance_functions.hpp +++ b/layer_gpu_support/source/layer_instance_functions.hpp @@ -32,28 +32,22 @@ /* 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 -); +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 -); +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 +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 index 7ac03b0..b7c3fe7 100644 --- a/layer_gpu_support/source/layer_instance_functions_get_physical_properties.cpp +++ b/layer_gpu_support/source/layer_instance_functions_get_physical_properties.cpp @@ -72,60 +72,91 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkGetPhysicalDeviceImageFormatProperties2pNext; n; n = n->pNext) { - if (n->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO) { + 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 = (const VkPhysicalDeviceExternalImageFormatInfo*)n; - handle = (VkExternalMemoryHandleTypeFlagBits)ext->handleType; + 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)) { + 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 = (const VkBaseInStructure*)pImageFormatInfo->pNext; + const VkBaseInStructure* src = reinterpret_cast(pImageFormatInfo->pNext); VkBaseOutStructure* head = nullptr; VkBaseOutStructure* tail = nullptr; - auto append = [&](VkBaseOutStructure* node) { + auto append = [&](VkBaseOutStructure* node) + { node->pNext = nullptr; - if (!head) { head = node; tail = node; } else { tail->pNext = node; tail = node; } + if (!head) { + head = node; + tail = node; + } + else + { + tail->pNext = node; + tail = node; + } }; - auto clone_base = [](const VkBaseInStructure* node)->VkBaseOutStructure*{ - VkBaseOutStructure* out = (VkBaseOutStructure*)malloc(sizeof(VkBaseOutStructure)); + + 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) { + for (; src; src = src->pNext) + { + bool drop = false; - if (src->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT) { + + if (src->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT) + { drop = true; } - if (!drop) append(clone_base(src)); + + if (!drop) + { + append(clone_base(src)); + } + } - if (local.tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT) { + 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 = (VkBaseOutStructure*)head->pNext; free(head); head = next; } - if (r != VK_SUCCESS) { - fprintf(stderr, "[libGPULayers] Driver returned %d after stripping modifiers.\n", r); + 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(); @@ -147,71 +178,96 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkGetPhysicalDeviceImageFormatProperties2KH auto* layer = Instance::retrieve(physicalDevice); const auto& config = layer->config; - //fprintf(stderr, "[libGPULayers] HIT %s (KHR alias)\n", __func__); fflush(stderr); - // Config gate: only enforce policy for external exports when enabled. int disable_external_compression = config.disable_external_compression(); // Inspect query intent - VkExternalMemoryHandleTypeFlagBits handle = (VkExternalMemoryHandleTypeFlagBits)0; + VkExternalMemoryHandleTypeFlagBits handle{}; bool has_external_info = false; - for (const VkBaseInStructure* n = (const VkBaseInStructure*)pImageFormatInfo->pNext; n; n = n->pNext) { - if (n->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO) { + 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 = (const VkPhysicalDeviceExternalImageFormatInfo*)n; - handle = (VkExternalMemoryHandleTypeFlagBits)ext->handleType; + 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)) { + 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 = (const VkBaseInStructure*)pImageFormatInfo->pNext; + const VkBaseInStructure* src = reinterpret_cast(pImageFormatInfo->pNext); VkBaseOutStructure* head = nullptr; VkBaseOutStructure* tail = nullptr; - auto append = [&](VkBaseOutStructure* node) { + auto append = [&](VkBaseOutStructure* node) + { node->pNext = nullptr; - if (!head) { head = node; tail = node; } else { tail->pNext = node; tail = node; } + + if (!head) + { + head = node; + tail = node; + } else { + tail->pNext = node; + tail = node; + } }; - auto clone_base = [](const VkBaseInStructure* node)->VkBaseOutStructure*{ - VkBaseOutStructure* out = (VkBaseOutStructure*)malloc(sizeof(VkBaseOutStructure)); + + 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) { + for (; src; src = src->pNext) + { bool drop = false; - if (src->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT) { + + if (src->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT) + { drop = true; } - if (!drop) append(clone_base(src)); + + if (!drop) + { + append(clone_base(src)); + } } - if (local.tiling == VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT) { + 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 = (VkBaseOutStructure*)head->pNext; free(head); head = next; } + while (head) + { + auto* next = reinterpret_cast(head->pNext); + free(head); + head = next; + } - if (r != VK_SUCCESS) { - fprintf(stderr, "[libGPULayers] Driver (KHR) returned %d after stripping modifiers.\n", r); fflush(stderr); + 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