init
@@ -0,0 +1,30 @@
|
||||
# Copyright (c) 2024-2025, Arm Limited and Contributors
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 the "License";
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Arm"
|
||||
NAME "Image Compression Control"
|
||||
DESCRIPTION "Save memory footprint and bandwidth with visually lossless compression"
|
||||
SHADER_FILES_GLSL
|
||||
"postprocessing/postprocessing.vert"
|
||||
"postprocessing/chromatic_aberration.frag")
|
||||
@@ -0,0 +1,291 @@
|
||||
////
|
||||
- Copyright (c) 2024, The Khronos Group
|
||||
- Copyright (c) 2024, Arm Limited and Contributors
|
||||
-
|
||||
- SPDX-License-Identifier: Apache-2.0
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 the "License";
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-
|
||||
////
|
||||
|
||||
= Image Compression Control
|
||||
|
||||
////
|
||||
The following block adds linkage to this repo in the Vulkan docs site project. It's only visible if the file is viewed via the Antora framework.
|
||||
////
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/performance/image_compression_control[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
== Overview
|
||||
|
||||
This sample shows how a Vulkan application can control the compression of https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImage.html[`VkImage`] elements, in particular a framebuffer attachment and the swapchain.
|
||||
This requires enabling and using the extensions https://docs.vulkan.org/spec/latest/appendices/extensions.html#VK_EXT_image_compression_control[`VK_EXT_image_compression_control`] and https://docs.vulkan.org/spec/latest/appendices/extensions.html#VK_EXT_image_compression_control_swapchain[`VK_EXT_image_compression_control_swapchain`], respectively.
|
||||
Applications that use compression generally perform better thanks to the reduced memory footprint and bandwidth.
|
||||
|
||||
In this sample, the use-case is a simple post-processing effect (https://en.wikipedia.org/wiki/Chromatic_aberration[chromatic aberration]) applied to the output color of the forward-rendering pass.
|
||||
Since the color output needs to be saved to main memory and read again by the post-processing pass, this an opportunity to improve performance by using image compression.
|
||||
|
||||
The sample allows toggling between:
|
||||
|
||||
* "Default": lossless compression (e.g. xref:/samples/performance/afbc/README.adoc[AFBC]).
|
||||
|
||||
* "Fixed-rate": visually lossless compression (e.g. https://learn.arm.com/learning-paths/smartphones-and-mobile/afrc/[AFRC]).
|
||||
A low/high level of compression can be specified in this case.
|
||||
|
||||
* "None": disable all compression, which is usually not recommended except for debugging purposes.
|
||||
|
||||
The compression settings will be applied to both the color attachment and the swapchain, if the extensions are supported.
|
||||
The on-screen hardware counters show the impact each option has on bandwidth and on the memory footprint.
|
||||
|
||||
image::./images/image_compression_control.png[Image Compression Control sample, 900, align="center"]
|
||||
|
||||
[.text-center]
|
||||
__Sponza scene with default (lossless) compression__
|
||||
|
||||
=== Default compression [[default_compression]]
|
||||
|
||||
This is lossless compression that devices can enable transparently to save performance where possible.
|
||||
Vulkan applications do not need to explicitly enable this sort of compression.
|
||||
|
||||
Devices with Arm GPUs implement Arm Frame Buffer Compression (AFBC), which uses variable bitrate encoding: the image is compressed in blocks (e.g. 4x4 pixels) and, depending on their composition, a different bitrate will be used.
|
||||
This means that the bandwidth savings depend on the image being compressed, and the compression ratio applied to each block.
|
||||
On average, high compression ratios are often obtained, as shown in the xref:/samples/performance/afbc/README.adoc[AFBC sample].
|
||||
|
||||
=== Fixed-rate compression [[fixed_rate_compression]]
|
||||
|
||||
On the other hand, a compression scheme may use a constant bitrate for all blocks, and in this case the compression is defined as fixed-rate.
|
||||
This means that in some cases a block will lose some information, and thus the compression is lossy.
|
||||
Therefore the device cannot enable it automatically, and the developer must opt-in using the Vulkan image compression control extensions.
|
||||
|
||||
Recent devices with Arm GPUs support Arm Fixed Rate Compression (AFRC), which achieve high quality results even with the highest compression ratios.
|
||||
For instance, see below for images saved from a Pixel 8 device:
|
||||
|
||||
[.center, cols="a,a,a"]
|
||||
|===
|
||||
| Default compression
|
||||
| 2BPC Fixed-rate compression
|
||||
| Pixel difference
|
||||
| image::./images/default.png[Default compression, 400]
|
||||
| image::./images/fixed_rate_2BPC.png[Fixed-rate compression, 400]
|
||||
| image::./images/compare.png[Pixel difference, 400]
|
||||
|===
|
||||
[.text-center]
|
||||
__Space Module scene compression comparison__
|
||||
|
||||
Since the difference is not noticeable with the naked eye, this is sometimes referred to as "visually lossless" compression.
|
||||
Software like https://imagemagick.org/script/compare.php[imagemagick allows to compare] the images and obtain a https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio[PSNR] quality estimation, in this case with the high value of 49.8dB:
|
||||
|
||||
[,shell]
|
||||
----
|
||||
$ magick compare -metric PSNR default.png fixed_rate_2BPC.png compare.png
|
||||
49.8487 (0.498487)
|
||||
----
|
||||
|
||||
There are some performance benefits associated with fixed-rate compression, as described below.
|
||||
|
||||
=== Memory footprint savings
|
||||
|
||||
Images compressed with a fixed-rate will always consume less memory.
|
||||
In this case, an image compressed with a 2BPC bitrate results in a 65% reduction compared to uncompressed.
|
||||
|
||||
image::./images/footprint.png[Memory footprint savings, 700, align="center"]
|
||||
|
||||
In this case, the slightly larger size of images compressed with AFBC is expected, as variable bitrates require enough space for the worse case (uncompressed) as well as some extra storage for compression-related metadata.
|
||||
|
||||
=== Bandwidth savings
|
||||
|
||||
The sample allows to observe an estimate of bytes being written out to main memory.
|
||||
On this device the write bandwidth difference between uncompressed and fixed-rate compression is approximately 38%:
|
||||
|
||||
image::./images/bandwidth.png[Bandwidth savings, 700, align="center"]
|
||||
|
||||
Bandwidth savings coming from image compression depend on the pixels being compressed.
|
||||
Moving the camera and showing different distribution of colors in the frame changes the results.
|
||||
Be sure to profile your application and verify which compression scheme is optimal in each case.
|
||||
For instance, images with a high proportion of solid color (e.g. normals or material properties) may be more optimally compressed with variable bitrates than with fixed-rate.
|
||||
This is the case for the Space Module scene shown above.
|
||||
|
||||
== VK_EXT_image_compression_control
|
||||
|
||||
This sample enables the https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_image_compression_control.html[`VK_EXT_image_compression_control`] extension and requests the relevant device feature, https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceImageCompressionControlFeaturesEXT.html[`imageCompressionControl`]
|
||||
This extension abstracts how applications choose a fixed compression rate, in terms of "minimum number of bits per component (BPC)".
|
||||
|
||||
=== Query for image compression support
|
||||
|
||||
To query if a particular image supports fixed-rate compression, add a https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCompressionPropertiesEXT.html[`VkImageCompressionPropertiesEXT`] to the `pNext` chain of https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageFormatProperties2KHR.html[`VkImageFormatProperties2`], and call https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkGetPhysicalDeviceImageFormatProperties2KHR.html[`vkGetPhysicalDeviceImageFormatProperties2KHR`]:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkImageCompressionPropertiesEXT supported_compression_properties{VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_PROPERTIES_EXT};
|
||||
|
||||
VkImageCompressionControlEXT compression_control{VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT};
|
||||
compression_control.flags = VK_IMAGE_COMPRESSION_FIXED_RATE_DEFAULT_EXT;
|
||||
|
||||
VkPhysicalDeviceImageFormatInfo2 image_format_info{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2};
|
||||
image_format_info.format = VK_FORMAT_R8G8B8_UNORM;
|
||||
image_format_info.type = VK_IMAGE_TYPE_2D;
|
||||
image_format_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
image_format_info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
image_format_info.pNext = &compression_control;
|
||||
|
||||
VkImageFormatProperties2 image_format_properties{VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2};
|
||||
image_format_properties.pNext = &supported_compression_properties;
|
||||
|
||||
vkGetPhysicalDeviceImageFormatProperties2KHR(device.get_gpu().get_handle(), &image_format_info, &image_format_properties);
|
||||
----
|
||||
|
||||
In the Vulkan Samples framework, this happens in the https://github.com/KhronosGroup/Vulkan-Samples/blob/main/framework/common/vk_common.cpp[`vkb::query_supported_fixed_rate_compression`] function.
|
||||
|
||||
Then inspect the values written to the `imageCompressionFixedRateFlags` component of https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCompressionPropertiesEXT.html[`VkImageCompressionPropertiesEXT`].
|
||||
If fixed-rate compression is supported, the flags will indicate which levels may be selected for this image, for instance https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCompressionFixedRateFlagBitsEXT.html[`VK_IMAGE_COMPRESSION_FIXED_RATE_2BPC_BIT_EXT`] or https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCompressionFixedRateFlagBitsEXT.html[`VK_IMAGE_COMPRESSION_FIXED_RATE_5BPC_BIT_EXT`].
|
||||
The sample will use the minimum BPC available for its high compression setting, and the maximum BPC available for its low compression setting.
|
||||
|
||||
image::./images/fixed_rate_levels.png[Image Compression Control sample, 900, align="center"]
|
||||
|
||||
[.text-center]
|
||||
__Fixed-rate options__
|
||||
|
||||
=== Request image compression
|
||||
|
||||
To request fixed-rate compression, provide a https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCompressionControlEXT.html[`VkImageCompressionControlEXT`] to the `pNext` chain of https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCreateInfo.html[`VkImageCreateInfo`]:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkImageCompressionFixedRateFlagsEXT fixed_rate_flags_array[1] = {VK_IMAGE_COMPRESSION_FIXED_RATE_2BPC_BIT_EXT};
|
||||
|
||||
VkImageCompressionControlEXT compression_control{VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT};
|
||||
compression_control.flags = VK_IMAGE_COMPRESSION_FIXED_RATE_EXPLICIT_EXT;
|
||||
compression_control.compressionControlPlaneCount = 1;
|
||||
compression_control.pFixedRateFlags = &fixed_rate_flags_array[0];
|
||||
|
||||
VkImageCreateInfo image_info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||||
image_info.format = VK_FORMAT_R8G8B8_UNORM;
|
||||
image_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
image_info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
image_info.pNext = &compression_control;
|
||||
|
||||
vkCreateImage(device, &image_info, nullptr, &new_image);
|
||||
----
|
||||
|
||||
Note that, instead of using https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCompressionFlagBitsEXT.html[`VK_IMAGE_COMPRESSION_FIXED_RATE_EXPLICIT_EXT`], one may use https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCompressionFlagBitsEXT.html[`VK_IMAGE_COMPRESSION_FIXED_RATE_DEFAULT_EXT`], and in that case it would not be necessary to provide a specific set of `pFixedRateFlags`.
|
||||
|
||||
In the Vulkan Samples framework, this happens in the https://github.com/KhronosGroup/Vulkan-Samples/blob/main/framework/core/image.cpp[`core::Image`] constructor.
|
||||
|
||||
=== Verify image compression [[verify_image_compression]]
|
||||
|
||||
To query which compression was applied, if any, once a https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImage.html[`VkImage`] has been created, add a https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCompressionPropertiesEXT.html[`VkImageCompressionPropertiesEXT`] to the `pNext` chain of https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageSubresource2EXT.html[`VkImageSubresource2EXT`], and call https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkGetImageSubresourceLayout2EXT.html[`vkGetImageSubresourceLayout2EXT`]:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkImageCompressionPropertiesEXT compression_properties{VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_PROPERTIES_EXT};
|
||||
|
||||
VkSubresourceLayout2EXT subresource_layout{VK_STRUCTURE_TYPE_SUBRESOURCE_LAYOUT_2_KHR};
|
||||
subresource_layout.pNext = &compression_properties;
|
||||
|
||||
VkImageSubresource2EXT image_subresource{VK_STRUCTURE_TYPE_IMAGE_SUBRESOURCE_2_KHR};
|
||||
image_subresource.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
image_subresource.imageSubresource.mipLevel = 0;
|
||||
image_subresource.imageSubresource.arrayLayer = 0;
|
||||
|
||||
vkGetImageSubresourceLayout2EXT(device, image, &image_subresource, &subresource_layout);
|
||||
----
|
||||
|
||||
Then inspect the values written to the `imageCompressionFlags` and `imageCompressionFixedRateFlags` components of https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCompressionPropertiesEXT.html[`VkImageCompressionPropertiesEXT`].
|
||||
In the Vulkan Samples framework, this happens in the https://github.com/KhronosGroup/Vulkan-Samples/blob/main/framework/core/image.cpp[`core::Image::query_applied_compression`] function.
|
||||
|
||||
|
||||
== VK_EXT_image_compression_control_swapchain
|
||||
|
||||
Compression control for swapchain images is similar, but it requires the https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_image_compression_control_swapchain.html[`VK_EXT_image_compression_control_swapchain`] extension and the https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceImageCompressionControlSwapchainFeaturesEXT.html[`imageCompressionControlSwapchain`] device feature to be enabled.
|
||||
These depend on the https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_image_compression_control.html[`VK_EXT_image_compression_control`] being available and enabled too.
|
||||
|
||||
=== Query for surface compression support
|
||||
|
||||
To query if the surface supports fixed-rate compression, add a https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCompressionPropertiesEXT.html[`VkImageCompressionPropertiesEXT`] to the `pNext` chain of https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageFormatProperties2KHR.html[`VkImageFormatProperties2`], and call https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkGetPhysicalDeviceImageFormatProperties2KHR.html[`vkGetPhysicalDeviceImageFormatProperties2KHR`]:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkPhysicalDeviceSurfaceInfo2KHR surface_info{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR};
|
||||
surface_info.surface = surface;
|
||||
|
||||
uint32_t surface_format_count{0U};
|
||||
|
||||
vkGetPhysicalDeviceSurfaceFormats2KHR(device, &surface_info, &surface_format_count, nullptr);
|
||||
|
||||
std::vector<VkSurfaceFormat2KHR> surface_formats;
|
||||
surface_formats.resize(surface_format_count, {VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR});
|
||||
|
||||
std::vector<VkImageCompressionPropertiesEXT> compression_properties;
|
||||
compression_properties.resize(surface_format_count, {VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_PROPERTIES_EXT});
|
||||
|
||||
for (uint32_t i = 0; i < surface_format_count; i++)
|
||||
{
|
||||
surface_formats[i].pNext = &compression_properties[i];
|
||||
}
|
||||
|
||||
vkGetPhysicalDeviceSurfaceFormats2KHR(device, &surface_info, &surface_format_count, surface_formats.data());
|
||||
----
|
||||
|
||||
Then inspect the values written to the `imageCompressionFixedRateFlags` component of https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCompressionPropertiesEXT.html[`VkImageCompressionPropertiesEXT`], associated to a particular https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkSurfaceFormat2KHR.html[`VkSurfaceFormat2KHR`].
|
||||
In the Vulkan Samples framework, this happens in the https://github.com/KhronosGroup/Vulkan-Samples/blob/main/framework/core/swapchain.cpp[`Swapchain::query_supported_fixed_rate_compression`] function.
|
||||
|
||||
=== Request surface compression
|
||||
|
||||
To request fixed-rate compression, provide a https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCompressionControlEXT.html[`VkImageCompressionControlEXT`] to the `pNext` chain of https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkSwapchainCreateInfoKHR.html[`VkSwapchainCreateInfoKHR`]:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkImageCompressionFixedRateFlagsEXT fixed_rate_flags_array[1] = {VK_IMAGE_COMPRESSION_FIXED_RATE_2BPC_BIT_EXT};
|
||||
|
||||
VkImageCompressionControlEXT compression_control{VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT};
|
||||
compression_control.flags = VK_IMAGE_COMPRESSION_FIXED_RATE_EXPLICIT_EXT;
|
||||
compression_control.compressionControlPlaneCount = 1;
|
||||
compression_control.pFixedRateFlags = &fixed_rate_flags_array[0];
|
||||
|
||||
VkSwapchainCreateInfoKHR create_info{VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR};
|
||||
create_info.surface = surface;
|
||||
create_info.pNext = &compression_control;
|
||||
|
||||
vkCreateSwapchainKHR(device, &create_info, nullptr, &new_swapchain);
|
||||
----
|
||||
|
||||
Similarly to regular images, https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCompressionFlagBitsEXT.html[`VK_IMAGE_COMPRESSION_FIXED_RATE_DEFAULT_EXT`] may be used instead.
|
||||
|
||||
In the Vulkan Samples framework, this happens in the https://github.com/KhronosGroup/Vulkan-Samples/blob/main/framework/core/swapchain.cpp[`Swapchain`] constructor.
|
||||
|
||||
=== Verify surface compression
|
||||
|
||||
To verify that compression was applied to the swapchain images, use the same method as described for a regular https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImage.html[`VkImage`] in <<verify_image_compression>>.
|
||||
No need to enable https://docs.vulkan.org/spec/latest/appendices/extensions.html#VK_EXT_image_compression_control_swapchain[`VK_EXT_image_compression_control_swapchain`] for this.
|
||||
|
||||
In the Vulkan Samples framework, this happens in the https://github.com/KhronosGroup/Vulkan-Samples/blob/main/framework/core/swapchain.cpp[`Swapchain::get_applied_compression`] function.
|
||||
|
||||
Note that even if the surface supports fixed-rate compression and the extensions are enabled, the surface might not be compressed.
|
||||
The most likely reason is that, even though the GPU supports it, other IP components in the system (e.g. the Display) do not support it, and therefore images are not compressed.
|
||||
|
||||
== Disabling fixed-rate compression
|
||||
|
||||
As explained above, the `flags` in https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCompressionControlEXT.html[`VkImageCompressionControlEXT`] control the compression scheme selection for images.
|
||||
Take care not to accidentally disable <<default_compression>> when disabling <<fixed_rate_compression>>.
|
||||
That is, ensure that https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCompressionFlagBitsEXT.html[`VK_IMAGE_COMPRESSION_DEFAULT_EXT`] is used by default, rather than https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImageCompressionFlagBitsEXT.html[`VK_IMAGE_COMPRESSION_DISABLED_EXT`], which disables all compression, negatively impacting performance.
|
||||
|
||||
== Conclusion
|
||||
|
||||
https://docs.vulkan.org/spec/latest/appendices/extensions.html#VK_EXT_image_compression_control[`VK_EXT_image_compression_control`] allows applications to check if default compression is enabled.
|
||||
It also provides the mechanism to request lossy (fixed-rate) compression where appropriate (https://docs.vulkan.org/spec/latest/appendices/extensions.html#VK_EXT_image_compression_control_swapchain[`VK_EXT_image_compression_control_swapchain`] is required for swapchain images).
|
||||
|
||||
Fixed-rate compression guarantees the most efficient memory footprint and can result in substantially reduced memory bandwidth, without sacrificing image quality.
|
||||
Bandwidth reductions can in turn result in performance improvements and power savings.
|
||||
@@ -0,0 +1,584 @@
|
||||
/* Copyright (c) 2024-2025, Arm Limited and Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 the "License";
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "image_compression_control.h"
|
||||
|
||||
#include "common/vk_common.h"
|
||||
#include "gltf_loader.h"
|
||||
#include "gui.h"
|
||||
#include "platform/platform.h"
|
||||
#include "rendering/postprocessing_renderpass.h"
|
||||
#include "rendering/subpasses/forward_subpass.h"
|
||||
#include "stats/stats.h"
|
||||
|
||||
ImageCompressionControlSample::ImageCompressionControlSample()
|
||||
{
|
||||
// Extensions of interest in this sample (optional)
|
||||
add_device_extension(VK_EXT_IMAGE_COMPRESSION_CONTROL_EXTENSION_NAME, true);
|
||||
add_device_extension(VK_EXT_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_EXTENSION_NAME, true);
|
||||
|
||||
// Extension dependency requirements (given that instance API version is 1.0.0)
|
||||
add_instance_extension(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, true);
|
||||
|
||||
auto &config = get_configuration();
|
||||
|
||||
// Batch mode will test the toggle between different compression modes
|
||||
config.insert<vkb::IntSetting>(0, static_cast<int>(gui_target_compression), 0);
|
||||
config.insert<vkb::IntSetting>(1, static_cast<int>(gui_target_compression), 1);
|
||||
config.insert<vkb::IntSetting>(2, static_cast<int>(gui_target_compression), 2);
|
||||
}
|
||||
|
||||
void ImageCompressionControlSample::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
if (gpu.is_extension_supported(VK_EXT_IMAGE_COMPRESSION_CONTROL_EXTENSION_NAME))
|
||||
{
|
||||
REQUEST_REQUIRED_FEATURE(gpu,
|
||||
VkPhysicalDeviceImageCompressionControlFeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_FEATURES_EXT,
|
||||
imageCompressionControl);
|
||||
}
|
||||
|
||||
if (gpu.is_extension_supported(VK_EXT_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_EXTENSION_NAME))
|
||||
{
|
||||
REQUEST_OPTIONAL_FEATURE(gpu,
|
||||
VkPhysicalDeviceImageCompressionControlSwapchainFeaturesEXT,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_FEATURES_EXT,
|
||||
imageCompressionControlSwapchain);
|
||||
}
|
||||
}
|
||||
|
||||
bool ImageCompressionControlSample::prepare(const vkb::ApplicationOptions &options)
|
||||
{
|
||||
if (!VulkanSample::prepare(options))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
load_scene("scenes/sponza/Sponza01.gltf");
|
||||
|
||||
auto &camera_node = vkb::add_free_camera(get_scene(), "main_camera", get_render_context().get_surface_extent());
|
||||
camera = dynamic_cast<vkb::sg::PerspectiveCamera *>(&camera_node.get_component<vkb::sg::Camera>());
|
||||
|
||||
vkb::ShaderSource scene_vs("base.vert.spv");
|
||||
vkb::ShaderSource scene_fs("base.frag.spv");
|
||||
auto scene_subpass = std::make_unique<vkb::ForwardSubpass>(get_render_context(), std::move(scene_vs), std::move(scene_fs), get_scene(), *camera);
|
||||
scene_subpass->set_output_attachments({static_cast<int>(Attachments::Color)});
|
||||
|
||||
// Forward rendering pass
|
||||
auto render_pipeline = std::make_unique<vkb::RenderPipeline>();
|
||||
render_pipeline->add_subpass(std::move(scene_subpass));
|
||||
render_pipeline->set_load_store(scene_load_store);
|
||||
set_render_pipeline(std::move(render_pipeline));
|
||||
|
||||
// Post-processing pass (chromatic aberration)
|
||||
vkb::ShaderSource postprocessing_vs("postprocessing/postprocessing.vert.spv");
|
||||
postprocessing_pipeline = std::make_unique<vkb::PostProcessingPipeline>(get_render_context(), std::move(postprocessing_vs));
|
||||
postprocessing_pipeline->add_pass().add_subpass(vkb::ShaderSource("postprocessing/chromatic_aberration.frag.spv"));
|
||||
|
||||
// Trigger recreation of Swapchain and render targets, with initial compression parameters
|
||||
update_render_targets();
|
||||
|
||||
get_stats().request_stats({vkb::StatIndex::frame_times,
|
||||
vkb::StatIndex::gpu_ext_write_bytes});
|
||||
|
||||
create_gui(*window, &get_stats());
|
||||
|
||||
// Hide GUI compression options other than default if the required extension is not supported
|
||||
if (!get_device().is_extension_enabled(VK_EXT_IMAGE_COMPRESSION_CONTROL_EXTENSION_NAME))
|
||||
{
|
||||
for (int i = 0; i < static_cast<int>(TargetCompression::Count); i++)
|
||||
{
|
||||
if (static_cast<TargetCompression>(i) != TargetCompression::Default)
|
||||
{
|
||||
gui_skip_compression_values.insert(static_cast<TargetCompression>(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImageCompressionControlSample::create_render_context()
|
||||
{
|
||||
/**
|
||||
* The framework expects a prioritized list of surface formats. For this sample, include
|
||||
* only those that can be compressed.
|
||||
*/
|
||||
std::vector<VkSurfaceFormatKHR> surface_formats_that_support_compression;
|
||||
|
||||
/**
|
||||
* To query for compression support, VK_EXT_image_compression_control_swapchain allows to
|
||||
* add a VkImageCompressionPropertiesEXT to the pNext chain of VkSurfaceFormat2KHR when
|
||||
* calling vkGetPhysicalDeviceSurfaceFormats2KHR.
|
||||
* See the implementation of this helper function in vkb::Swapchain.
|
||||
*/
|
||||
auto surface_compression_properties_list = vkb::Swapchain::query_supported_fixed_rate_compression(get_device(), get_surface());
|
||||
|
||||
LOGI("The following surface formats support compression:");
|
||||
for (auto &surface_compression_properties : surface_compression_properties_list)
|
||||
{
|
||||
if (surface_compression_properties.compression_properties.imageCompressionFixedRateFlags != VK_IMAGE_COMPRESSION_FIXED_RATE_NONE_EXT)
|
||||
{
|
||||
VkSurfaceFormatKHR surface_format = {surface_compression_properties.surface_format.surfaceFormat.format,
|
||||
surface_compression_properties.surface_format.surfaceFormat.colorSpace};
|
||||
|
||||
LOGI(" \t{}:\t{}",
|
||||
vkb::to_string(surface_format),
|
||||
vkb::image_compression_fixed_rate_flags_to_string(surface_compression_properties.compression_properties.imageCompressionFixedRateFlags));
|
||||
|
||||
surface_formats_that_support_compression.push_back(surface_format);
|
||||
}
|
||||
}
|
||||
|
||||
if (surface_formats_that_support_compression.empty())
|
||||
{
|
||||
LOGI(" \tNo surface formats support fixed-rate compression");
|
||||
|
||||
// Fall-back to default surface format priority list
|
||||
VulkanSample::create_render_context();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Filter default list to those formats that support compression
|
||||
std::vector<VkSurfaceFormatKHR> new_surface_priority_list;
|
||||
for (auto const &surface_priority : get_surface_priority_list())
|
||||
{
|
||||
auto it = std::ranges::find_if(surface_formats_that_support_compression,
|
||||
[&](VkSurfaceFormatKHR &sf) { return surface_priority.format == sf.format &&
|
||||
surface_priority.colorSpace == sf.colorSpace; });
|
||||
if (it != surface_formats_that_support_compression.end())
|
||||
{
|
||||
new_surface_priority_list.push_back(*it);
|
||||
|
||||
surface_formats_that_support_compression.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
// In case there is no overlap, append any formats that support compression but were not in the default list
|
||||
for (auto &remaining_format : surface_formats_that_support_compression)
|
||||
{
|
||||
new_surface_priority_list.push_back(remaining_format);
|
||||
}
|
||||
|
||||
vkb::VulkanSampleC::create_render_context(new_surface_priority_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* At this point, a Swapchain has been created using the first supported format in the list above. Save a list of
|
||||
* its corresponding supported compression rates (if any).
|
||||
*/
|
||||
const auto &selected_surface_format = get_render_context().get_swapchain().get_surface_format();
|
||||
for (size_t i = 0; i < surface_compression_properties_list.size(); i++)
|
||||
{
|
||||
if (selected_surface_format.format == surface_compression_properties_list[i].surface_format.surfaceFormat.format &&
|
||||
selected_surface_format.colorSpace == surface_compression_properties_list[i].surface_format.surfaceFormat.colorSpace)
|
||||
{
|
||||
supported_fixed_rate_flags_swapchain = vkb::fixed_rate_compression_flags_to_vector(
|
||||
surface_compression_properties_list[i].compression_properties.imageCompressionFixedRateFlags);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImageCompressionControlSample::prepare_render_context()
|
||||
{
|
||||
get_render_context().prepare(1, std::bind(&ImageCompressionControlSample::create_render_target, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::RenderTarget> ImageCompressionControlSample::create_render_target(vkb::core::Image &&swapchain_image)
|
||||
{
|
||||
/**
|
||||
* The render passes will use 3 attachments: Color, Depth and Swapchain.
|
||||
* This sample allows to control the compression of the color and Swapchain attachments.
|
||||
* The Swapchain has already been created by the RenderContext. Here, create color and depth images.
|
||||
*/
|
||||
color_image_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
color_image_info.extent = swapchain_image.get_extent();
|
||||
color_image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
color_image_info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
|
||||
// The first time this function is called, choose a compressible format for the color attachment.
|
||||
if (VK_FORMAT_UNDEFINED == color_image_info.format)
|
||||
{
|
||||
// Query fixed-rate compression support for a few candidate formats
|
||||
std::vector<VkFormat> format_list{VK_FORMAT_R8G8B8_UNORM, VK_FORMAT_R8G8B8_SNORM, VK_FORMAT_R8G8B8_SRGB,
|
||||
VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_B8G8R8_SNORM, VK_FORMAT_B8G8R8_SRGB,
|
||||
VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_SNORM, VK_FORMAT_R8G8B8A8_SRGB,
|
||||
VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SNORM, VK_FORMAT_B8G8R8A8_SRGB};
|
||||
|
||||
VkFormat chosen_format{VK_FORMAT_UNDEFINED};
|
||||
if (get_device().is_extension_enabled(VK_EXT_IMAGE_COMPRESSION_CONTROL_EXTENSION_NAME))
|
||||
{
|
||||
for (auto &candidate_format : format_list)
|
||||
{
|
||||
color_image_info.format = candidate_format;
|
||||
|
||||
/**
|
||||
* To query for compression support, VK_EXT_image_compression_control allows to
|
||||
* add a VkImageCompressionPropertiesEXT to the pNext chain of VkImageFormatProperties2 when
|
||||
* calling vkGetPhysicalDeviceImageFormatProperties2KHR.
|
||||
* See the implementation of this helper function in vkb::core::Image.
|
||||
*/
|
||||
VkImageCompressionPropertiesEXT supported_compression_properties = vkb::query_supported_fixed_rate_compression(get_device().get_gpu().get_handle(), color_image_info);
|
||||
|
||||
auto fixed_rate_flags = vkb::fixed_rate_compression_flags_to_vector(supported_compression_properties.imageCompressionFixedRateFlags);
|
||||
|
||||
VkImageFormatProperties format_properties;
|
||||
auto result = vkGetPhysicalDeviceImageFormatProperties(get_device().get_gpu().get_handle(),
|
||||
color_image_info.format,
|
||||
color_image_info.imageType,
|
||||
color_image_info.tiling,
|
||||
color_image_info.usage,
|
||||
0, // no create flags
|
||||
&format_properties);
|
||||
|
||||
// Pick the first format that supports at least 2 or more levels of fixed-rate compression, otherwise
|
||||
// pick the first format that supports at least 1 level
|
||||
if (result != VK_ERROR_FORMAT_NOT_SUPPORTED &&
|
||||
fixed_rate_flags.size() > 0)
|
||||
{
|
||||
if ((VK_FORMAT_UNDEFINED == chosen_format) || (fixed_rate_flags.size() > 1))
|
||||
{
|
||||
chosen_format = candidate_format;
|
||||
supported_fixed_rate_flags_color = fixed_rate_flags;
|
||||
}
|
||||
|
||||
if (fixed_rate_flags.size() > 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback if no fixed-rate compressible format was found
|
||||
color_image_info.format = (VK_FORMAT_UNDEFINED != chosen_format) ? chosen_format : swapchain_image.get_format();
|
||||
LOGI("Chosen color format: {}", vkb::to_string(color_image_info.format));
|
||||
|
||||
// Hide GUI fixed-rate compression option if chosen format does not support it
|
||||
if (supported_fixed_rate_flags_color.empty())
|
||||
{
|
||||
gui_skip_compression_values.insert(TargetCompression::FixedRate);
|
||||
|
||||
LOGW("Color image does not support fixed-rate compression. Possible reasons:");
|
||||
LOGW("\t- Its format may not be supported (format = {})", vkb::to_string(color_image_info.format));
|
||||
if (color_image_info.usage & VK_IMAGE_USAGE_STORAGE_BIT)
|
||||
{
|
||||
LOGW("\t- It is a storage image (usage = {})", vkb::image_usage_to_string(color_image_info.usage));
|
||||
}
|
||||
if (color_image_info.samples > 1)
|
||||
{
|
||||
LOGW("\t- It is a multi-sampled image (sample count = {})", vkb::to_string(color_image_info.samples));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vkb::core::Image depth_image{get_device(),
|
||||
vkb::core::ImageBuilder(color_image_info.extent)
|
||||
.with_format(vkb::get_suitable_depth_format(get_device().get_gpu().get_handle()))
|
||||
.with_usage(VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT)
|
||||
.with_vma_usage(VMA_MEMORY_USAGE_GPU_ONLY)};
|
||||
|
||||
vkb::core::ImageBuilder color_image_builder(color_image_info.extent);
|
||||
color_image_builder.with_format(color_image_info.format)
|
||||
.with_usage(color_image_info.usage)
|
||||
.with_tiling(color_image_info.tiling)
|
||||
.with_vma_usage(VMA_MEMORY_USAGE_GPU_ONLY);
|
||||
|
||||
// Prepare compression control structure
|
||||
VkImageCompressionControlEXT color_compression_control{VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT};
|
||||
|
||||
if (VK_IMAGE_COMPRESSION_DEFAULT_EXT != compression_flag)
|
||||
{
|
||||
color_compression_control.flags = compression_flag;
|
||||
|
||||
if (VK_IMAGE_COMPRESSION_FIXED_RATE_EXPLICIT_EXT == compression_flag &&
|
||||
VK_IMAGE_COMPRESSION_FIXED_RATE_NONE_EXT != compression_fixed_rate_flag_color)
|
||||
{
|
||||
color_compression_control.compressionControlPlaneCount = 1;
|
||||
color_compression_control.pFixedRateFlags = &compression_fixed_rate_flag_color;
|
||||
}
|
||||
|
||||
color_image_builder.with_extension<VkImageCompressionControlEXT>(color_compression_control);
|
||||
}
|
||||
|
||||
vkb::core::Image color_image{get_device(), color_image_builder};
|
||||
|
||||
if (VK_IMAGE_COMPRESSION_FIXED_RATE_EXPLICIT_EXT == compression_flag)
|
||||
{
|
||||
/**
|
||||
* To verify that the requested compression was indeed applied, the framework core::Image class
|
||||
* provides a helper function that uses the new structures introduced by VK_EXT_image_compression_control.
|
||||
* It calls vkGetImageSubresourceLayout2EXT with a VkImageSubresource2EXT that includes a
|
||||
* VkImageCompressionPropertiesEXT structure in its pNext chain.
|
||||
* This structure is then filled with the applied compression flags.
|
||||
*/
|
||||
LOGI("Applied fixed-rate compression for color ({}): {}", vkb::to_string(color_image_info.format),
|
||||
vkb::image_compression_fixed_rate_flags_to_string(color_image.get_applied_compression().imageCompressionFixedRateFlags));
|
||||
}
|
||||
|
||||
// Update memory footprint values in GUI (displayed in MB)
|
||||
const float bytes_in_mb = 1024 * 1024;
|
||||
footprint_swapchain = swapchain_image.get_image_required_size() / bytes_in_mb;
|
||||
footprint_color = color_image.get_image_required_size() / bytes_in_mb;
|
||||
|
||||
scene_load_store.clear();
|
||||
std::vector<vkb::core::Image> images;
|
||||
|
||||
// Attachment 0 - Swapchain - Not used in the scene render pass, output of postprocessing
|
||||
images.push_back(std::move(swapchain_image));
|
||||
scene_load_store.push_back({VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_DONT_CARE});
|
||||
|
||||
// Attachment 1 - Attachments::Depth - Transient, used only in the scene render pass
|
||||
images.push_back(std::move(depth_image));
|
||||
scene_load_store.push_back({VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_DONT_CARE});
|
||||
|
||||
// Attachment 2 - Attachments::Color - Output of the scene render pass, input of postprocessing
|
||||
images.push_back(std::move(color_image));
|
||||
scene_load_store.push_back({VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE});
|
||||
|
||||
return std::make_unique<vkb::RenderTarget>(std::move(images));
|
||||
}
|
||||
|
||||
void ImageCompressionControlSample::update(float delta_time)
|
||||
{
|
||||
elapsed_time += delta_time;
|
||||
|
||||
if ((gui_target_compression != last_gui_target_compression) ||
|
||||
(gui_fixed_rate_compression_level != last_gui_fixed_rate_compression_level))
|
||||
{
|
||||
update_render_targets();
|
||||
|
||||
last_gui_target_compression = gui_target_compression;
|
||||
last_gui_fixed_rate_compression_level = gui_fixed_rate_compression_level;
|
||||
}
|
||||
|
||||
VulkanSample::update(delta_time);
|
||||
}
|
||||
|
||||
void ImageCompressionControlSample::update_render_targets()
|
||||
{
|
||||
/**
|
||||
* Define the compression flags that will be used to select the compression
|
||||
* level of the color and Swapchain images. In the framework, this will be
|
||||
* handled by the vkb::core::Image and vkb::Swapchain constructors.
|
||||
* The extensions introduce a new VkImageCompressionControlEXT struct, which
|
||||
* collects compression settings, and that can be provided to the pNext
|
||||
* list of VkImageCreateInfo and VkSwapchainCreateInfoKHR.
|
||||
*/
|
||||
switch (gui_target_compression)
|
||||
{
|
||||
case TargetCompression::FixedRate:
|
||||
{
|
||||
compression_flag = VK_IMAGE_COMPRESSION_FIXED_RATE_EXPLICIT_EXT;
|
||||
break;
|
||||
}
|
||||
case TargetCompression::None:
|
||||
{
|
||||
compression_flag = VK_IMAGE_COMPRESSION_DISABLED_EXT;
|
||||
break;
|
||||
}
|
||||
case TargetCompression::Default:
|
||||
default:
|
||||
{
|
||||
compression_flag = VK_IMAGE_COMPRESSION_DEFAULT_EXT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the minimum (higher compression) or maximum (lower compression) bitrate supported,
|
||||
* which might be different for the color and the Swapchain.
|
||||
*/
|
||||
auto fixed_rate_compression_level = static_cast<FixedRateCompressionLevel>(gui_fixed_rate_compression_level);
|
||||
compression_fixed_rate_flag_color = select_fixed_rate_compression_flag(supported_fixed_rate_flags_color, fixed_rate_compression_level);
|
||||
compression_fixed_rate_flag_swapchain = select_fixed_rate_compression_flag(supported_fixed_rate_flags_swapchain, fixed_rate_compression_level);
|
||||
|
||||
// Recreate the Swapchain, which also triggers recreation of render targets
|
||||
get_device().wait_idle();
|
||||
get_render_context().update_swapchain(compression_flag, compression_fixed_rate_flag_swapchain);
|
||||
}
|
||||
|
||||
VkImageCompressionFixedRateFlagBitsEXT ImageCompressionControlSample::select_fixed_rate_compression_flag(
|
||||
std::vector<VkImageCompressionFixedRateFlagBitsEXT> &supported_fixed_rate_flags,
|
||||
FixedRateCompressionLevel compression_level)
|
||||
{
|
||||
if (!supported_fixed_rate_flags.empty())
|
||||
{
|
||||
switch (compression_level)
|
||||
{
|
||||
case FixedRateCompressionLevel::High:
|
||||
{
|
||||
return supported_fixed_rate_flags.front();
|
||||
}
|
||||
case FixedRateCompressionLevel::Low:
|
||||
{
|
||||
return supported_fixed_rate_flags.back();
|
||||
}
|
||||
default:
|
||||
{
|
||||
LOGW("Unknown fixed-rate compression level {}", static_cast<int>(compression_level));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return VK_IMAGE_COMPRESSION_FIXED_RATE_NONE_EXT;
|
||||
}
|
||||
|
||||
void ImageCompressionControlSample::render(vkb::core::CommandBufferC &command_buffer)
|
||||
{
|
||||
// Scene (forward rendering) pass
|
||||
VulkanSample::render(command_buffer);
|
||||
|
||||
command_buffer.end_render_pass();
|
||||
|
||||
/**
|
||||
* Post processing pass, which applies a simple chromatic aberration effect.
|
||||
* The effect is animated, using elapsed time, for two reasons:
|
||||
* 1. Allows to visualize the scene with and without the effect.
|
||||
* 2. Reduces the effect of transaction elimination, a useful feature that
|
||||
* reduces bandwidth but may hide the bandwidth benefits of compression,
|
||||
* the focus of this sample.
|
||||
*/
|
||||
auto &postprocessing_pass = postprocessing_pipeline->get_pass(0);
|
||||
postprocessing_pass.set_uniform_data(sin(elapsed_time));
|
||||
|
||||
auto &postprocessing_subpass = postprocessing_pass.get_subpass(0);
|
||||
postprocessing_subpass.bind_sampled_image("color_sampler", static_cast<int>(Attachments::Color));
|
||||
|
||||
postprocessing_pipeline->draw(command_buffer, get_render_context().get_active_frame().get_render_target());
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Helper function to generate a GUI drop-down options menu
|
||||
*/
|
||||
template <typename T>
|
||||
inline T generate_combo(T current_value, const char *combo_label, const std::unordered_map<T, std::string> &enum_to_string, float item_width, const std::set<T> *skip_values = nullptr)
|
||||
{
|
||||
ImGui::PushItemWidth(item_width);
|
||||
|
||||
T new_enum_value = current_value;
|
||||
|
||||
const auto &search_it = enum_to_string.find(current_value);
|
||||
const char *selected_value = search_it != enum_to_string.end() ? search_it->second.c_str() : "";
|
||||
|
||||
if (ImGui::BeginCombo(combo_label, selected_value))
|
||||
{
|
||||
for (const auto &it : enum_to_string)
|
||||
{
|
||||
if (skip_values && std::ranges::find(*skip_values, it.first) != skip_values->end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool is_selected = it.first == current_value;
|
||||
|
||||
if (ImGui::Selectable(it.second.c_str(), is_selected))
|
||||
{
|
||||
new_enum_value = it.first;
|
||||
}
|
||||
|
||||
if (is_selected)
|
||||
{
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
return new_enum_value;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ImageCompressionControlSample::draw_gui()
|
||||
{
|
||||
const bool landscape = camera->get_aspect_ratio() > 1.0f;
|
||||
uint32_t lines = 3;
|
||||
|
||||
if (landscape)
|
||||
{
|
||||
lines--;
|
||||
}
|
||||
|
||||
get_gui().show_options_window(
|
||||
[&]() {
|
||||
const auto window_width = ImGui::GetWindowWidth();
|
||||
|
||||
/**
|
||||
* Select compression scheme from those available. Some options may be hidden if the extension(s) are not supported,
|
||||
* or if the chosen color format does not support fixed-rate compression.
|
||||
*/
|
||||
ImGui::Text("Compression:");
|
||||
ImGui::SameLine();
|
||||
|
||||
const TargetCompression compression = generate_combo(gui_target_compression, "##compression",
|
||||
{{TargetCompression::FixedRate, "Fixed-rate"}, {TargetCompression::None, "None"}, {TargetCompression::Default, "Default"}},
|
||||
window_width * 0.2f,
|
||||
&gui_skip_compression_values);
|
||||
|
||||
gui_target_compression = compression;
|
||||
|
||||
if (compression == TargetCompression::FixedRate && supported_fixed_rate_flags_color.size() > 1)
|
||||
{
|
||||
/**
|
||||
* Select level of fixed-rate compression from those available. It is assumed that the Swapchain can only be compressed
|
||||
* if the color attachment can be compressed too, given that we select similar formats and the Swapchain compression control
|
||||
* extension depends on the general image compression control extension.
|
||||
*/
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Level:");
|
||||
|
||||
ImGui::SameLine();
|
||||
const FixedRateCompressionLevel compression_level = generate_combo(gui_fixed_rate_compression_level, "##compression-level",
|
||||
{{FixedRateCompressionLevel::High, "High"}, {FixedRateCompressionLevel::Low, "Low"}},
|
||||
window_width * 0.2f);
|
||||
|
||||
gui_fixed_rate_compression_level = compression_level;
|
||||
}
|
||||
|
||||
if (landscape)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
if (gui_skip_compression_values.size() >= static_cast<int>(TargetCompression::Count) - 1)
|
||||
{
|
||||
// Single or no compression options available on this device
|
||||
ImGui::Text("(Extensions are not supported)");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Indicate if the Swapchain compression matches that of the color attachment
|
||||
ImGui::Text("(Swapchain is %s affected)", get_render_context().get_swapchain().get_applied_compression() == compression_flag ? "also" : "not");
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the memory footprint of the configurable targets, which will be lower if fixed-rate compression is selected.
|
||||
*/
|
||||
ImGui::Text("Color attachment (%.1f MB), Swapchain (%.1f MB)", footprint_color, footprint_swapchain);
|
||||
},
|
||||
lines);
|
||||
}
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_image_compression_control()
|
||||
{
|
||||
return std::make_unique<ImageCompressionControlSample>();
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/* Copyright (c) 2024-2025, Arm Limited and Contributors
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 the "License";
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rendering/postprocessing_pipeline.h"
|
||||
#include "rendering/render_pipeline.h"
|
||||
#include "scene_graph/components/camera.h"
|
||||
#include "scene_graph/components/perspective_camera.h"
|
||||
#include "vulkan_sample.h"
|
||||
|
||||
/**
|
||||
* @brief Image Compression Control Sample
|
||||
*
|
||||
* This sample shows how to use the extensions VK_EXT_image_compression_control
|
||||
* and VK_EXT_image_compression_control_swapchain to select between
|
||||
* different levels of image compression.
|
||||
*
|
||||
* The UI shows the impact compression has on image size and bandwidth,
|
||||
* illustrating the benefits of fixed-rate (visually lossless) compression.
|
||||
*/
|
||||
class ImageCompressionControlSample : public vkb::VulkanSampleC
|
||||
{
|
||||
public:
|
||||
ImageCompressionControlSample();
|
||||
|
||||
virtual bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
|
||||
virtual void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
|
||||
virtual ~ImageCompressionControlSample() = default;
|
||||
|
||||
virtual void update(float delta_time) override;
|
||||
|
||||
virtual void render(vkb::core::CommandBufferC &command_buffer) override;
|
||||
|
||||
void draw_gui() override;
|
||||
|
||||
private:
|
||||
vkb::sg::PerspectiveCamera *camera{nullptr};
|
||||
|
||||
virtual void prepare_render_context() override;
|
||||
|
||||
virtual void create_render_context() override;
|
||||
|
||||
std::unique_ptr<vkb::RenderTarget> create_render_target(vkb::core::Image &&swapchain_image);
|
||||
|
||||
enum class Attachments : int
|
||||
{
|
||||
Swapchain = 0,
|
||||
Depth = 1,
|
||||
Color = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Postprocessing pipeline
|
||||
* In this sample, a postprocessing pass applies a simple chromatic aberration effect
|
||||
* to the output of the forward rendering pass. Since this color output needs to
|
||||
* be saved to main memory, it is a simple use case for compressing the image and
|
||||
* saving bandwidth and memory footprint, as illustrated in the sample.
|
||||
*/
|
||||
std::unique_ptr<vkb::PostProcessingPipeline> postprocessing_pipeline{};
|
||||
|
||||
/**
|
||||
* @brief Load/store operations of the forward rendering pass attachments
|
||||
* Used to specify that the color output must be stored to main memory.
|
||||
*/
|
||||
std::vector<vkb::LoadStoreInfo> scene_load_store{};
|
||||
|
||||
/**
|
||||
* @brief Color image parameters
|
||||
* These are the properties of the color output image.
|
||||
*/
|
||||
VkImageCreateInfo color_image_info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||||
|
||||
/**
|
||||
* @brief Compression scheme
|
||||
* By default, many implementations support lossless compression.
|
||||
* Some also support fixed-rate compression, which is generally visually lossless.
|
||||
* This flag can also be used to disable all compression, which is not recommended.
|
||||
*/
|
||||
VkImageCompressionFlagsEXT compression_flag{VK_IMAGE_COMPRESSION_DEFAULT_EXT};
|
||||
|
||||
/**
|
||||
* @brief Possible compression schemes
|
||||
*/
|
||||
enum class TargetCompression : int
|
||||
{
|
||||
Default = 0,
|
||||
FixedRate = 1,
|
||||
None = 2,
|
||||
|
||||
// Enum value count
|
||||
Count = 3
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Compression rate for the color image
|
||||
* If fixed-rate compression is selected, this specifies the bitrate per component.
|
||||
*/
|
||||
VkImageCompressionFixedRateFlagsEXT compression_fixed_rate_flag_color{};
|
||||
|
||||
/**
|
||||
* @brief Compression rate for the Swapchain image
|
||||
*/
|
||||
VkImageCompressionFixedRateFlagsEXT compression_fixed_rate_flag_swapchain{};
|
||||
|
||||
/**
|
||||
* @brief Supported values for compression rate
|
||||
* These depend on the properties (format and usage) of the color image.
|
||||
*/
|
||||
std::vector<VkImageCompressionFixedRateFlagBitsEXT> supported_fixed_rate_flags_color{};
|
||||
|
||||
/**
|
||||
* @brief Supported values for compression rate
|
||||
* These depend on the properties (format and usage) of the Swapchain image.
|
||||
*/
|
||||
std::vector<VkImageCompressionFixedRateFlagBitsEXT> supported_fixed_rate_flags_swapchain{};
|
||||
|
||||
/**
|
||||
* @brief Possible fixed-rate compression levels
|
||||
*/
|
||||
enum FixedRateCompressionLevel : int
|
||||
{
|
||||
High = 0, // Highest compression available (smallest bitrate)
|
||||
Low = 1, // Lowest compression available (largest bitrate)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Selects the compression bitrate given a list of supported values and a compression level
|
||||
*/
|
||||
VkImageCompressionFixedRateFlagBitsEXT select_fixed_rate_compression_flag(std::vector<VkImageCompressionFixedRateFlagBitsEXT> &supported_fixed_rate_flags,
|
||||
FixedRateCompressionLevel compression_level);
|
||||
|
||||
/**
|
||||
* @brief Size (in MB) of the color image (output of the forward rendering pass)
|
||||
*/
|
||||
float footprint_color{};
|
||||
|
||||
/**
|
||||
* @brief Size (in MB) of the Swapchain image (output of the postprocessing pass)
|
||||
*/
|
||||
float footprint_swapchain{};
|
||||
|
||||
/**
|
||||
* @brief Time
|
||||
* Used to animate the chromatic aberration effect.
|
||||
*/
|
||||
float elapsed_time{};
|
||||
|
||||
/**
|
||||
* @brief Updates compression settings based on user GUI input.
|
||||
* Triggers recreation of the Swapchain and other render targets.
|
||||
*/
|
||||
void update_render_targets();
|
||||
|
||||
/**
|
||||
* @brief Unsupported compression schemes
|
||||
* Used to reduce the GUI controls if, for instance, the required
|
||||
* extensions are not supported by the device.
|
||||
*/
|
||||
std::set<TargetCompression> gui_skip_compression_values;
|
||||
|
||||
/* Helpers for managing GUI input */
|
||||
|
||||
TargetCompression gui_target_compression{TargetCompression::Default};
|
||||
|
||||
TargetCompression last_gui_target_compression{gui_target_compression};
|
||||
|
||||
FixedRateCompressionLevel gui_fixed_rate_compression_level{FixedRateCompressionLevel::High};
|
||||
|
||||
FixedRateCompressionLevel last_gui_fixed_rate_compression_level{gui_fixed_rate_compression_level};
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_image_compression_control();
|
||||
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 4.2 MiB |