/* * * Singe 3 * Copyright (C) 2006-2026 Scott Duensing * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * */ // Singe - KTX2 textures on the Basis Universal transcoder, behind the C interface in ktx2.h. #include #include #include #include extern "C" { #include "ktx2.h" } #define KTX2_IDENTIFIER_BYTES 12 static bool _ready = false; static const uint8_t _identifier[KTX2_IDENTIFIER_BYTES] = { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; static basist::transcoder_texture_format _target(Ktx2FormatE format); static basist::transcoder_texture_format _target(Ktx2FormatE format) { switch (format) { case KTX2_BC7: return basist::transcoder_texture_format::cTFBC7_RGBA; case KTX2_ASTC: return basist::transcoder_texture_format::cTFASTC_4x4_RGBA; case KTX2_BC3: return basist::transcoder_texture_format::cTFBC3_RGBA; case KTX2_ETC2: return basist::transcoder_texture_format::cTFETC2_RGBA; default: return basist::transcoder_texture_format::cTFRGBA32; } } // ===== Public ===== void ktx2Free(Ktx2ImageT *image) { int32_t x; if (image->levels != NULL) { for (x = 0; x < image->levelCount; x++) { SDL_free(image->levels[x].data); } SDL_free(image->levels); } memset(image, 0, sizeof(*image)); } bool ktx2Is(const void *bytes, size_t size) { return (size >= KTX2_IDENTIFIER_BYTES) && (memcmp(bytes, _identifier, KTX2_IDENTIFIER_BYTES) == 0); } // Every mip level of the file's first image transcoded to the wanted format (2D, one layer, one face). bool ktx2Transcode(const void *bytes, size_t size, Ktx2FormatE wanted, Ktx2ImageT *out) { basist::ktx2_transcoder transcoder; basist::transcoder_texture_format format = _target(wanted); uint32_t levels; uint32_t level; memset(out, 0, sizeof(*out)); if (!_ready) { basist::basisu_transcoder_init(); _ready = true; } if ((size > UINT32_MAX) || !transcoder.init(bytes, (uint32_t)size) || !transcoder.start_transcoding()) { SDL_SetError("Not a Basis Universal KTX2 file."); return false; } levels = transcoder.get_levels(); if (levels == 0) { SDL_SetError("KTX2 file has no images."); return false; } out->format = wanted; out->width = (int32_t)transcoder.get_width(); out->height = (int32_t)transcoder.get_height(); out->srgb = transcoder.is_srgb(); out->levelCount = (int32_t)levels; out->levels = (Ktx2LevelT *)SDL_calloc(levels, sizeof(Ktx2LevelT)); if (out->levels == NULL) { SDL_SetError("Out of memory transcoding a texture."); return false; } for (level = 0; level < levels; level++) { basist::ktx2_image_level_info info; Ktx2LevelT *dst = &out->levels[level]; size_t count; uint32_t pitch = 0; if (!transcoder.get_image_level_info(info, level, 0, 0)) { ktx2Free(out); SDL_SetError("KTX2 level %u is unreadable.", level); return false; } dst->width = (int32_t)info.m_orig_width; dst->height = (int32_t)info.m_orig_height; if (basist::basis_transcoder_format_is_uncompressed(format)) { count = (size_t)info.m_orig_width * (size_t)info.m_orig_height; dst->size = count * basist::basis_get_uncompressed_bytes_per_pixel(format); pitch = info.m_orig_width; } else { count = info.m_total_blocks; dst->size = count * basist::basis_get_bytes_per_block_or_pixel(format); } if (count > UINT32_MAX) { ktx2Free(out); SDL_SetError("KTX2 level %u is too large.", level); return false; } dst->data = (uint8_t *)SDL_malloc(dst->size); if (dst->data == NULL) { ktx2Free(out); SDL_SetError("Out of memory transcoding a texture."); return false; } if (!transcoder.transcode_image_level(level, 0, 0, dst->data, (uint32_t)count, format, 0, pitch, 0)) { ktx2Free(out); SDL_SetError("KTX2 level %u would not transcode.", level); return false; } } return true; }