Files
Vulkan-Samples/framework/scene_graph/components/image/ktx.cpp
T
2025-09-04 10:54:47 +08:00

185 lines
5.7 KiB
C++

/* Copyright (c) 2019-2025, Arm Limited and Contributors
* Copyright (c) 2019-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.
*/
#include "scene_graph/components/image/ktx.h"
#include "common/error.h"
#include <ktx.h>
#include <ktxvulkan.h>
namespace vkb
{
namespace sg
{
struct CallbackData final
{
ktxTexture *texture;
std::vector<Mipmap> *mipmaps;
};
/// Row padding is different between KTX (pad to 4) and Vulkan (none).
/// Also region->bufferOffset, i.e. the start of each image, has
/// to be a multiple of 4 and also a multiple of the element size.
static ktx_error_code_e KTX_APIENTRY optimal_tiling_callback(int mip_level,
int face,
int width,
int height,
int depth,
ktx_uint64_t face_lod_size,
void *pixels,
void *user_data)
{
auto *callback_data = reinterpret_cast<CallbackData *>(user_data);
assert(static_cast<size_t>(mip_level) < callback_data->mipmaps->size() && "Not enough space in the mipmap vector");
ktx_size_t mipmap_offset = 0;
auto result = ktxTexture_GetImageOffset(callback_data->texture, mip_level, 0, face, &mipmap_offset);
if (result != KTX_SUCCESS)
{
return result;
}
auto &mipmap = callback_data->mipmaps->at(mip_level);
mipmap.level = mip_level;
mipmap.offset = to_u32(mipmap_offset);
mipmap.extent.width = width;
mipmap.extent.height = height;
mipmap.extent.depth = depth;
return KTX_SUCCESS;
}
Ktx::Ktx(const std::string &name, const std::vector<uint8_t> &data, ContentType content_type) :
Image{name}
{
auto data_buffer = reinterpret_cast<const ktx_uint8_t *>(data.data());
auto data_size = static_cast<ktx_size_t>(data.size());
ktxTexture *texture;
auto load_ktx_result = ktxTexture_CreateFromMemory(data_buffer,
data_size,
KTX_TEXTURE_CREATE_NO_FLAGS,
&texture);
if (load_ktx_result != KTX_SUCCESS)
{
throw std::runtime_error{"Error loading KTX texture: " + name};
}
if (texture->pData)
{
// Already loaded
set_data(texture->pData, texture->dataSize);
}
else
{
// Load
auto &mut_data = get_mut_data();
auto size = texture->dataSize;
mut_data.resize(size);
auto load_data_result = ktxTexture_LoadImageData(texture, mut_data.data(), size);
if (load_data_result != KTX_SUCCESS)
{
throw std::runtime_error{"Error loading KTX image data: " + name};
}
}
set_width(texture->baseWidth);
set_height(texture->baseHeight);
set_depth(texture->baseDepth);
set_layers(texture->numLayers);
update_hash();
bool cubemap = false;
// Use the faces if there are 6 (for cubemap)
if (texture->numLayers == 1 && texture->numFaces == 6)
{
cubemap = true;
set_layers(texture->numFaces);
}
auto updated_format = ktxTexture_GetVkFormat(texture);
set_format(updated_format);
if (texture->classId == ktxTexture1_c && content_type == Color)
{
// KTX-1 files don't contain color space information. Color data is normally
// in sRGB, but the format we get back won't report that, so this will adjust it
// if necessary.
coerce_format_to_srgb();
}
auto &mipmap_levels = get_mut_mipmaps();
mipmap_levels.resize(texture->numLevels);
CallbackData callback_data{};
callback_data.texture = texture;
callback_data.mipmaps = &mipmap_levels;
auto result = ktxTexture_IterateLevels(texture, optimal_tiling_callback, &callback_data);
if (result != KTX_SUCCESS)
{
throw std::runtime_error("Error loading KTX texture");
}
// If the texture contains more than one layer, then populate the offsets otherwise take the mipmap level offsets
if (texture->numLayers > 1 || cubemap)
{
uint32_t layer_count = cubemap ? texture->numFaces : texture->numLayers;
std::vector<std::vector<VkDeviceSize>> offsets;
for (uint32_t layer = 0; layer < layer_count; layer++)
{
std::vector<VkDeviceSize> layer_offsets{};
for (uint32_t level = 0; level < texture->numLevels; level++)
{
ktx_size_t offset;
KTX_error_code result;
if (cubemap)
{
result = ktxTexture_GetImageOffset(texture, level, 0, layer, &offset);
}
else
{
result = ktxTexture_GetImageOffset(texture, level, layer, 0, &offset);
}
layer_offsets.push_back(static_cast<VkDeviceSize>(offset));
}
offsets.push_back(layer_offsets);
}
set_offsets(offsets);
}
else
{
std::vector<std::vector<VkDeviceSize>> offsets{};
offsets.resize(1);
for (size_t level = 0; level < mipmap_levels.size(); level++)
{
offsets[0].push_back(static_cast<VkDeviceSize>(mipmap_levels[level].offset));
}
set_offsets(offsets);
}
ktxTexture_Destroy(texture);
}
} // namespace sg
} // namespace vkb