init
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
////
|
||||
- Copyright (c) 2021-2025, Sascha Willems
|
||||
-
|
||||
- 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.
|
||||
-
|
||||
////
|
||||
= Using Basis Universal supercompressed GPU texture codec with Vulkan
|
||||
|
||||
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/texture_compression_basisu[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
|
||||
== Overview
|
||||
|
||||
This tutorial, along with the accompanying example code, demonstrates how to use Basis universal supercompressed GPU textures in a Vulkan application.
|
||||
|
||||
== The KTX2 format
|
||||
|
||||
https://www.khronos.org/ktx/[KTX] is a GPU texture *container format* for storing different texture types (2D, cubemap, etc.) and texture formats (uncompressed and compressed).
|
||||
Version 2.0 added support for Basis Universal supercompressed textures.
|
||||
|
||||
== Basis Universal
|
||||
|
||||
https://github.com/BinomialLLC/basis_universal[Basis Universal] is a supercompressed GPU texture data interchange system that implements the UASTC and ETC1S compressed formats that serve as *transmission formats*.
|
||||
Both can be quickly transcoded to a wide variety of GPU native compressed and uncompressed formats like RGB/RGBA, BCn, ETC1, ETC2, etc.
|
||||
This means that unlike a KTX 2.0 file storing a BC3 textures, the data needs to be transcoded at runtime.
|
||||
|
||||
image::./images/2021-ktx-universal-gpu-compressed-textures.png[KTX and BasisU]
|
||||
|
||||
== Libraries for loading KTX 2.0 files
|
||||
|
||||
This sample (as well as the repository) uses the _libktx_ library from the https://github.com/KhronosGroup/KTX-Software[official Khronos KTX Software Repository] for loading and transcoding the Basis Universal compressed KTX 2.0 textures.
|
||||
It's included in the framework via this link:../../../third_party/CMakeLists.txt[CMakeLists.txt] and also adds the Basis Universal transcoder:
|
||||
|
||||
[,CMake]
|
||||
----
|
||||
# libktx
|
||||
set(KTX_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ktx)
|
||||
|
||||
set(KTX_SOURCES
|
||||
...
|
||||
|
||||
# Basis Universal
|
||||
${KTX_DIR}/lib/basis_sgd.h
|
||||
${KTX_DIR}/lib/basis_transcode.cpp
|
||||
${KTX_DIR}/lib/basisu/transcoder/basisu_containers.h
|
||||
${KTX_DIR}/lib/basisu/transcoder/basisu_containers_impl.h
|
||||
${KTX_DIR}/lib/basisu/transcoder/basisu_file_headers.h
|
||||
${KTX_DIR}/lib/basisu/transcoder/basisu_global_selector_cb.h
|
||||
${KTX_DIR}/lib/basisu/transcoder/basisu_global_selector_palette.h
|
||||
${KTX_DIR}/lib/basisu/transcoder/basisu_transcoder_internal.h
|
||||
${KTX_DIR}/lib/basisu/transcoder/basisu_transcoder_uastc.h
|
||||
${KTX_DIR}/lib/basisu/transcoder/basisu_transcoder.cpp
|
||||
${KTX_DIR}/lib/basisu/transcoder/basisu_transcoder.h
|
||||
${KTX_DIR}/lib/basisu/transcoder/basisu.h
|
||||
${KTX_DIR}/lib/basisu/zstd/zstd.c
|
||||
|
||||
...
|
||||
# KTX2
|
||||
${KTX_DIR}/lib/texture2.c
|
||||
${KTX_DIR}/lib/texture2.h
|
||||
----
|
||||
|
||||
An alternative to including _libktx_ via CMake would be using the pre-built binaries provided in the https://github.com/KhronosGroup/KTX-Software/releases[KTX-Software repository].
|
||||
|
||||
For projects that just want to use KTX 2.0 and Basis Universal texture compression, a light-weight alternative to _libktx_ is the basisu _basist::ktx2_transcoder_.
|
||||
More information on how to use this can be found in Binomial's documentation, https://github.com/BinomialLLC/basis_universal/wiki/How-to-Use-and-Configure-the-Transcoder[How to configure and use the transcoder].
|
||||
|
||||
== Selecting a GPU native target format
|
||||
|
||||
As noted above, the KTX 2.0 files used in this sample store texture data in the Basis Universal ETC1S and UASTC transmission formats, which can't be natively used by the GPU.
|
||||
|
||||
So before transcoding the data to a native GPU format, we need to select a valid native GPU target format.
|
||||
In this sample, we use a simple mechanism solely based on the Vulkan formats supported by the GPU:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
void TextureCompressionBasisu::get_available_target_formats()
|
||||
{
|
||||
available_target_formats.clear();
|
||||
|
||||
VkPhysicalDeviceFeatures device_features = get_device().get_gpu().get_features();
|
||||
|
||||
// Block compression
|
||||
if (device_features.textureCompressionBC)
|
||||
{
|
||||
// BC7 is the preferred block compression if available
|
||||
if (format_supported(VK_FORMAT_BC7_SRGB_BLOCK))
|
||||
{
|
||||
// Target formats from the KTX library, and prefixed with KTX_
|
||||
available_target_formats.push_back(KTX_TTF_BC7_RGBA);
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
|
||||
// Adaptive scalable texture compression
|
||||
if (device_features.textureCompressionASTC_LDR)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
// Ericsson texture compression
|
||||
if (device_features.textureCompressionETC2)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
// Always add uncompressed RGBA as a valid target
|
||||
available_target_formats.push_back(KTX_TTF_RGBA32);
|
||||
available_target_formats_names.push_back("KTX_TTF_RGBA32");
|
||||
}
|
||||
----
|
||||
|
||||
This gives us a list of possible target formats for the Basis Universal transcoder (prefixed with `KTX_`) that we can later on transcode to from the ETC1S and UASTC transmission formats.
|
||||
|
||||
Transcode target format selection in a real world application will most probably be a bit more complex.
|
||||
A good reference for how to select target formats can be found in the https://github.com/KhronosGroup/3D-Formats-Guidelines/blob/main/KTXDeveloperGuide.md[KTX 2.0 / Basis Universal Textures -- Developer Guide].
|
||||
|
||||
== Loading the KTX 2.0 file
|
||||
|
||||
Loading and transcoding the KTX 2.0 texture image file handled by _libktx_ and done inside the `TextureCompressionBasisu::transcode_texture` function.
|
||||
|
||||
=== Loading the file from disk
|
||||
|
||||
Loading a KTX 2.0 file is the same as loading a KTX1.0 file, with the exception that we use the `ktxTexture2` class, which needs to be casted to `ktxTexture` in some function calls:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
// We are working with KTX 2.0 files, so we need to use the ktxTexture2 class
|
||||
ktxTexture2 *ktx_texture;
|
||||
// Load the KTX 2.0 file into memory. This is agnostic to the KTX version, so we cast the ktxTexture2 down to ktxTexture
|
||||
KTX_error_code result = ktxTexture_CreateFromNamedFile(file_name.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, (ktxTexture **) &ktx_texture);
|
||||
if (result != KTX_SUCCESS)
|
||||
{
|
||||
throw std::runtime_error("Could not load the requested image file.");
|
||||
}
|
||||
----
|
||||
|
||||
=== Transcoding into a native format
|
||||
|
||||
Once we have successfully loaded the file from disk, we can transcode it from ETCS1/UASTC to our desired target format from the list we created earlier.
|
||||
|
||||
We first check if the source KTX 2.0 file actually needs transcoding via `ktxTexture2_NeedsTranscoding`.
|
||||
This is always the case for all KTX 2.0 texture files used in this sample, but if a file would e.g.
|
||||
already contain a native format like BCn, then we wouldn't have to transcode it.
|
||||
|
||||
If the file needs transcoding, we then call the Basis Universal transcoder from the _libktx_ via `ktxTexture2_TranscodeBasis`.
|
||||
This will transcode the texture data into the GPU native target format:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
if (ktxTexture2_NeedsTranscoding(ktx_texture))
|
||||
{
|
||||
result = ktxTexture2_TranscodeBasis(ktx_texture, target_format, 0);
|
||||
if (result != KTX_SUCCESS)
|
||||
{
|
||||
throw std::runtime_error("Could not transcode the input texture to the selected target format.");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
If we e.g.
|
||||
select `KTX_TTF_BC7_RGBA` as the transcode target format for a UASTC compressed file, this will transcode the UASTC texture data to GPU native BC7 data.
|
||||
|
||||
=== Uploading the texture data
|
||||
|
||||
Once transcoded, the `ktxTexture` object contains the texture data in a native GPU format (e.g.
|
||||
BC7 in the above sample), which can then be directly uploaded to a GPU that supports BC7 texture compression.
|
||||
From this point on it's like working with regular textures.
|
||||
The native Vulkan format we can then use to create the Vulkan image from can be taken from the `ktxTexture` object:
|
||||
|
||||
[,cpp]
|
||||
----
|
||||
VkFormat format = (VkFormat)ktx_texture->vkFormat;
|
||||
|
||||
// Create a buffer to store the transcoded ktx texture data for staging to the GPU
|
||||
VkBufferCreateInfo buffer_create_info = vkb::initializers::buffer_create_info();
|
||||
buffer_create_info.size = ktx_texture->dataSize;
|
||||
|
||||
...
|
||||
|
||||
// Copy the ktx texture into the host local buffer
|
||||
uint8_t *data;
|
||||
vkMapMemory(get_device().get_handle(), staging_memory, 0, memory_requirements.size, 0, (void **) &data);
|
||||
memcpy(data, ktx_image_data, ktx_texture->dataSize);
|
||||
vkUnmapMemory(get_device().get_handle(), staging_memory);
|
||||
|
||||
// Setup buffer copy regions for each mip level
|
||||
std::vector<VkBufferImageCopy> buffer_copy_regions;
|
||||
for (uint32_t mip_level = 0; mip_level < texture.mip_levels; mip_level++)
|
||||
{
|
||||
ktx_size_t offset;
|
||||
KTX_error_code result = ktxTexture_GetImageOffset((ktxTexture *) ktx_texture, mip_level, 0, 0, &offset);
|
||||
VkBufferImageCopy buffer_copy_region = {};
|
||||
buffer_copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
buffer_copy_region.imageSubresource.mipLevel = mip_level;
|
||||
buffer_copy_region.imageSubresource.baseArrayLayer = 0;
|
||||
buffer_copy_region.imageSubresource.layerCount = 1;
|
||||
buffer_copy_region.imageExtent.width = ktx_texture->baseWidth >> mip_level;
|
||||
buffer_copy_region.imageExtent.height = ktx_texture->baseHeight >> mip_level;
|
||||
buffer_copy_region.imageExtent.depth = 1;
|
||||
buffer_copy_region.bufferOffset = offset;
|
||||
buffer_copy_regions.push_back(buffer_copy_region);
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
VkImageCreateInfo image_create_info = vkb::initializers::image_create_info();
|
||||
image_create_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
image_create_info.format = format;
|
||||
image_create_info.mipLevels = texture.mip_levels;
|
||||
image_create_info.arrayLayers = 1;
|
||||
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
image_create_info.extent = {texture.width, texture.height, 1};
|
||||
image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
vkCreateImage(get_device().get_handle(), &image_create_info, nullptr, &texture.image);
|
||||
|
||||
...
|
||||
|
||||
// Upload data to the Vulkan image using a command buffer
|
||||
|
||||
VkCommandBuffer copy_command = device->create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
...
|
||||
vkCmdCopyBufferToImage(
|
||||
copy_command,
|
||||
staging_buffer,
|
||||
texture.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
static_cast<uint32_t>(buffer_copy_regions.size()),
|
||||
buffer_copy_regions.data());
|
||||
...
|
||||
device->flush_command_buffer(copy_command, queue, true);
|
||||
----
|
||||
|
||||
== The sample
|
||||
|
||||
image::./images/texture_compression_basisu_sample.jpg[Sample image]
|
||||
|
||||
The sample allows transcoding of a fixed set of ETC1S/UASTC to supported native GPU target formats at runtime.
|
||||
The list of possible targets depends on the device's capabilities.
|
||||
You can also zoom in and rotate the image to see the effect of different input and target format combinations.
|
||||
|
||||
NOTE: Transcoding speed suffers a lot in debug builds.
|
||||
For best performance, running a release build is advised.
|
||||
Reference in New Issue
Block a user