singe/src/ktx2Basis.cpp
2026-09-22 18:32:07 -05:00

149 lines
4.7 KiB
C++

/*
*
* Singe 3
* Copyright (C) 2006-2026 Scott Duensing <scott@kangaroopunch.com>
*
* 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 <stdint.h>
#include <string.h>
#include <SDL3/SDL.h>
#include <basisu_transcoder.h>
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;
}