imagelib: add rudimentary KTX2 support

It only does a very basic header validation, and passes the entire file
as PF_KTX2_RAW format. This is to simplify KTX2 reading in ref_vk and
trying out different formats. KTX2 has support for >200 format, and
passing all of them through PF_ types is a non-starter.

The plan is to figure out which formats we want to support, and add
their support to imagelib/ktx2 incrementally, leaving the rest as
PF_KTX2_RAW, so ref_vk still can use them.
This commit is contained in:
Ivan Avdeev 2023-10-12 12:38:35 -04:00
parent 726fcee3f7
commit 90119ae84a
7 changed files with 126 additions and 94 deletions

View File

@ -9,7 +9,7 @@ NOTE: number at end of pixelformat name it's a total bitscount e.g. PF_RGB_24 ==
========================================================================
*/
#define ImageRAW( type ) (type == PF_RGBA_32 || type == PF_BGRA_32 || type == PF_RGB_24 || type == PF_BGR_24 || type == PF_LUMINANCE)
#define ImageDXT( type ) (type == PF_DXT1 || type == PF_DXT3 || type == PF_DXT5 || type == PF_ATI2 || type == PF_BC6H_SIGNED || type == PF_BC6H_UNSIGNED || type == PF_BC7)
#define ImageDXT( type ) (type == PF_DXT1 || type == PF_DXT3 || type == PF_DXT5 || type == PF_ATI2 || type == PF_BC6H_SIGNED || type == PF_BC6H_UNSIGNED || type == PF_BC7 || type == PF_KTX2_RAW)
typedef enum
{
@ -28,6 +28,7 @@ typedef enum
PF_BC6H_SIGNED, // bptc BC6H signed FP16 format
PF_BC6H_UNSIGNED, // bptc BC6H unsigned FP16 format
PF_BC7, // bptc BC7 format
PF_KTX2_RAW, // Raw KTX2 data, used for yet unsupported KTX2 subformats
PF_TOTALCOUNT, // must be last
} pixformat_t;

View File

@ -156,6 +156,7 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi
qboolean Image_LoadFNT( const char *name, const byte *buffer, fs_offset_t filesize );
qboolean Image_LoadLMP( const char *name, const byte *buffer, fs_offset_t filesize );
qboolean Image_LoadPAL( const char *name, const byte *buffer, fs_offset_t filesize );
qboolean Image_LoadKTX2( const char *name, const byte *buffer, fs_offset_t filesize );
//
// formats save

View File

@ -255,7 +255,7 @@ uint Image_DXTCalcSize( const char *name, dds_t *hdr, size_t filesize )
if( filesize != buffsize ) // main check
{
Con_DPrintf( S_WARN "Image_LoadDDS: (%s) probably corrupted (%i should be %lu)\n", name, buffsize, filesize );
Con_DPrintf( S_WARN "Image_LoadDDS: (%s) probably corrupted (%zu should be %lu)\n", name, buffsize, filesize );
if( buffsize > filesize )
return false;
}

View File

@ -0,0 +1,55 @@
/*
img_dds.c - dds format load
Copyright (C) 2015 Uncle Mike
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.
*/
#include "imagelib.h"
#include "xash3d_mathlib.h"
#include "ktx2.h"
qboolean Image_LoadKTX2( const char *name, const byte *buffer, fs_offset_t filesize ) {
if( filesize < KTX_MINIMAL_HEADER_SIZE )
return false;
if ( memcmp(buffer, KTX_IDENTIFIER, KTX_IDENTIFIER_SIZE) != 0) {
Con_DPrintf( S_ERROR "%s: (%s) has invalid identifier\n", __FUNCTION__, name );
return false;
}
ktx_header_t header;
memcpy(&header, buffer + KTX_IDENTIFIER_SIZE, sizeof header);
/* ktx_index_t index; */
/* memcpy(&header, buffer + KTX_IDENTIFIER_SIZE + sizeof header, sizeof index); */
image.width = header.pixelWidth;
image.height = header.pixelHeight;
image.depth = Q_max(1, header.pixelDepth);
// Just pass file contents as rgba data directly
// TODO support various formats individually, for other renders to be able to consume them too
// This is a catch-all for ref_vk, which can do this format directly and natively
image.type = PF_KTX2_RAW;
image.size = filesize;
//image.encode = TODO custom encode type?
// FIXME format-dependent
image.flags = IMAGE_HAS_COLOR; // | IMAGE_HAS_ALPHA
image.rgba = Mem_Malloc( host.imagepool, image.size);
memcpy(image.rgba, buffer, image.size);
return true;
}

View File

@ -105,6 +105,7 @@ static const loadpixformat_t load_game[] =
{ "%s%s.%s", "lmp", Image_LoadLMP, IL_HINT_NO }, // hl menu images (cached.wad etc)
{ "%s%s.%s", "fnt", Image_LoadFNT, IL_HINT_HL }, // hl console font (fonts.wad etc)
{ "%s%s.%s", "pal", Image_LoadPAL, IL_HINT_NO }, // install studio\sprite palette
{ "%s%s.%s", "ktx2", Image_LoadKTX2, IL_HINT_NO }, // dds for world and studio models
{ NULL, NULL, NULL, IL_HINT_NO }
};

41
public/ktx2.h Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#include <stdint.h>
#define KTX_IDENTIFIER_SIZE 12
#define KTX_IDENTIFIER "\xABKTX 20\xBB\r\n\x1A\n"
/*
static const char k_ktx2_identifier[KTX_IDENTIFIER_SIZE] = {
'\xAB', 'K', 'T', 'X', ' ', '2', '0', '\xBB', '\r', '\n', '\x1A', '\n'
};
*/
typedef struct {
uint32_t vkFormat;
uint32_t typeSize;
uint32_t pixelWidth;
uint32_t pixelHeight;
uint32_t pixelDepth;
uint32_t layerCount;
uint32_t faceCount;
uint32_t levelCount;
uint32_t supercompressionScheme;
} ktx_header_t;
typedef struct {
uint32_t dfdByteOffset;
uint32_t dfdByteLength;
uint32_t kvdByteOffset;
uint32_t kvdByteLength;
uint64_t sgdByteOffset;
uint64_t sgdByteLength;
} ktx_index_t;
typedef struct {
uint64_t byteOffset;
uint64_t byteLength;
uint64_t uncompressedByteLength;
} ktx_level_t;
#define KTX_MINIMAL_HEADER_SIZE (KTX_IDENTIFIER_SIZE + sizeof(ktx_header_t) + sizeof(ktx_index_t) + sizeof(ktx_level_t))

View File

@ -15,6 +15,7 @@
#include "crclib.h"
#include "com_strings.h"
#include "eiface.h"
#include "ktx2.h"
#define PCG_IMPLEMENT
#include "pcg.h"
@ -638,7 +639,13 @@ static VkSampler pickSamplerForFlags( texFlags_t flags ) {
return tglob.default_sampler_fixme;
}
static qboolean loadKtx2Raw( vk_texture_t *tex, const rgbdata_t* pic );
static qboolean uploadTexture(vk_texture_t *tex, rgbdata_t *const *const layers, int num_layers, qboolean cubemap, colorspace_hint_e colorspace_hint) {
if (num_layers == 1 && layers[0]->type == PF_KTX2_RAW)
return loadKtx2Raw(tex, layers[0]);
const VkFormat format = VK_GetFormat(layers[0]->type, colorspace_hint);
int mipCount = 0;
@ -896,68 +903,18 @@ const byte* VK_TextureData( unsigned int texnum )
return NULL;
}
#define KTX_IDENTIFIER_SIZE 12
static const char k_ktx2_identifier[KTX_IDENTIFIER_SIZE] = {
'\xAB', 'K', 'T', 'X', ' ', '2', '0', '\xBB', '\r', '\n', '\x1A', '\n'
};
typedef struct {
uint32_t vkFormat;
uint32_t typeSize;
uint32_t pixelWidth;
uint32_t pixelHeight;
uint32_t pixelDepth;
uint32_t layerCount;
uint32_t faceCount;
uint32_t levelCount;
uint32_t supercompressionScheme;
} ktx_header_t;
typedef struct {
uint32_t dfdByteOffset;
uint32_t dfdByteLength;
uint32_t kvdByteOffset;
uint32_t kvdByteLength;
uint64_t sgdByteOffset;
uint64_t sgdByteLength;
} ktx_index_t;
typedef struct {
uint64_t byteOffset;
uint64_t byteLength;
uint64_t uncompressedByteLength;
} ktx_level_t;
static int loadKtx2( const char *name ) {
fs_offset_t size = 0;
byte *data = gEngine.fsapi->LoadFile( name, &size, false );
DEBUG("Loading KTX2 file \"%s\", exists=%d", name, data != 0);
if ( !data )
return 0;
static qboolean loadKtx2Raw( vk_texture_t *tex, const rgbdata_t* pic ) {
const byte *const data = pic->buffer;
const int size = pic->size;
const ktx_header_t* header;
const ktx_index_t* index;
const ktx_level_t* levels;
vk_texture_t* tex = NULL;
if (size < (sizeof k_ktx2_identifier + sizeof(ktx_header_t) + sizeof(ktx_index_t) + sizeof(ktx_level_t))) {
ERR("KTX2 file \"%s\" seems truncated", name);
goto fail;
}
header = (const ktx_header_t*)(data + KTX_IDENTIFIER_SIZE);
index = (const ktx_index_t*)(data + KTX_IDENTIFIER_SIZE + sizeof(ktx_header_t));
levels = (const ktx_level_t*)(data + KTX_IDENTIFIER_SIZE + sizeof(ktx_header_t) + sizeof(ktx_index_t));
if (memcmp(data, k_ktx2_identifier, sizeof k_ktx2_identifier) != 0) {
ERR("KTX2 file \"%s\" identifier is invalid", name);
goto fail;
}
header = (const ktx_header_t*)(data + sizeof k_ktx2_identifier);
index = (const ktx_index_t*)(data + sizeof k_ktx2_identifier + sizeof(ktx_header_t));
levels = (const ktx_level_t*)(data + sizeof k_ktx2_identifier + sizeof(ktx_header_t) + sizeof(ktx_index_t));
DEBUG("KTX2 file \"%s\"", name);
DEBUG(" header:");
#define X(field) DEBUG(" " # field "=%d", header->field);
DEBUG(" vkFormat = %s(%d)", R_VkFormatName(header->vkFormat), header->vkFormat);
@ -988,13 +945,6 @@ static int loadKtx2( const char *name ) {
DEBUG(" uncompressedByteLength=%llu", (unsigned long long)level->uncompressedByteLength);
}
{
const uint32_t flags = 0;
tex = Common_AllocTexture( name, flags );
if (!tex)
goto fail;
}
// FIXME check that format is supported
// FIXME layers == 0
// FIXME has_alpha
@ -1147,18 +1097,20 @@ static int loadKtx2( const char *name ) {
tex->width = header->pixelWidth;
tex->height = header->pixelHeight;
goto finalize;
fail:
if (tex)
memset( tex, 0, sizeof( vk_texture_t ));
finalize:
Mem_Free( data );
return (tex - vk_textures);
return true;
}
static int loadTextureUsingEngine( const char *name, const byte *buf, size_t size, int flags, colorspace_hint_e colorspace_hint ) {
static int loadTextureInternal( const char *name, const byte *buf, size_t size, int flags, colorspace_hint_e colorspace_hint ) {
if( !Common_CheckTexName( name ))
return 0;
// see if already loaded
{
const vk_texture_t *const tex = Common_TextureForName( name );
if( tex )
return (tex - vk_textures);
}
uint picFlags = 0;
if( FBitSet( flags, TF_NOFLIP_TGA ))
@ -1195,25 +1147,6 @@ static int loadTextureUsingEngine( const char *name, const byte *buf, size_t siz
return tex - vk_textures;
}
static int loadTextureInternal( const char *name, const byte *buf, size_t size, int flags, colorspace_hint_e colorspace_hint ) {
if( !Common_CheckTexName( name ))
return 0;
// see if already loaded
vk_texture_t *tex = Common_TextureForName( name );
if( tex )
return (tex - vk_textures);
{
const char *ext = Q_strrchr(name, '.');
if (Q_strcmp(ext, ".ktx2") == 0) {
return loadKtx2(name);
}
}
return loadTextureUsingEngine(name, buf, size, flags, colorspace_hint);
}
int VK_LoadTextureExternal( const char *name, const byte *buf, size_t size, int flags ) {
return loadTextureInternal(name, buf, size, flags, kColorspaceGamma);
}