Browse Source

engine: common: imagelib: add KTX2 support (#1455)

* engine: common: imagelib: add KTX2 support

Adds basic KTX2 support for a few compressed formats. KTX2 essentially
is a Vulkan-centric texture format that supports literally hundreds of
pixel formats.
For now only support for these is added:
- `VK_FORMAT_BC4_UNORM_BLOCK`
- `VK_FORMAT_BC4_SNORM_BLOCK`
- `VK_FORMAT_BC5_UNORM_BLOCK`
- `VK_FORMAT_BC5_SNORM_BLOCK`
- `VK_FORMAT_BC6H_UFLOAT_BLOCK`
- `VK_FORMAT_BC6H_SFLOAT_BLOCK`
- `VK_FORMAT_BC7_UNORM_BLOCK`
- `VK_FORMAT_BC7_SRGB_BLOCK`

Adding more formats is relatively straightforward:
- Copy format definition from `VkFormat` enum in `vulkan_core.h`
- Add a new definition into `pixformat_t` enum.
- Add format size calculation into `Image_ComputeSize()`

While we're at it, also adds a few new formats to DDS:
- BC4_UNORM -- PF_BC4_UNSIGNED
- BC4_SNORM -- PF_BC4_SIGNED
- BC5_UNORM -- PF_BC5_UNSIGNED
- BC5_SNORM -- PF_BC5_SIGNED
- BC7 is expanded into BC7_UNORM and BC7_SRGB

ref_gl and ref_soft code is updated where it made sense. But not tested
really. Support for these formats has been tested with ref_vk.

* address spaces-vs-parentheses formatting where noticed

* parenthesize sizeofs

* move ktx2.h to imagelib as img_ktx2.h; massage it a bit

* use SetBits() instead of |=

* remove stale TODO comments
pull/2/head
Ivan Avdeev 1 year ago committed by GitHub
parent
commit
a251600c8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      common/com_image.h
  2. 2
      engine/common/imagelib/imagelib.h
  3. 66
      engine/common/imagelib/img_dds.c
  4. 3
      engine/common/imagelib/img_dds.h
  5. 204
      engine/common/imagelib/img_ktx2.c
  6. 79
      engine/common/imagelib/img_ktx2.h
  7. 34
      engine/common/imagelib/img_utils.c
  8. 22
      ref/gl/gl_image.c
  9. 12
      ref/soft/r_image.c

24
common/com_image.h

@ -9,7 +9,20 @@ 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 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 ImageCompressed( type ) \
( type == PF_DXT1 \
|| type == PF_DXT3 \
|| type == PF_DXT5 \
|| type == PF_ATI2 \
|| type == PF_BC4_SIGNED \
|| type == PF_BC4_UNSIGNED \
|| type == PF_BC5_SIGNED \
|| type == PF_BC5_UNSIGNED \
|| type == PF_BC6H_SIGNED \
|| type == PF_BC6H_UNSIGNED \
|| type == PF_BC7_UNORM \
|| type == PF_BC7_SRGB \
|| type == PF_KTX2_RAW )
typedef enum typedef enum
{ {
@ -25,9 +38,15 @@ typedef enum
PF_DXT3, // s3tc DXT3/BC2 format PF_DXT3, // s3tc DXT3/BC2 format
PF_DXT5, // s3tc DXT5/BC3 format PF_DXT5, // s3tc DXT5/BC3 format
PF_ATI2, // latc ATI2N/BC5 format PF_ATI2, // latc ATI2N/BC5 format
PF_BC4_SIGNED,
PF_BC4_UNSIGNED,
PF_BC5_SIGNED,
PF_BC5_UNSIGNED,
PF_BC6H_SIGNED, // bptc BC6H signed FP16 format PF_BC6H_SIGNED, // bptc BC6H signed FP16 format
PF_BC6H_UNSIGNED, // bptc BC6H unsigned FP16 format PF_BC6H_UNSIGNED, // bptc BC6H unsigned FP16 format
PF_BC7, // bptc BC7 format PF_BC7_UNORM, // bptc BC7 format
PF_BC7_SRGB,
PF_KTX2_RAW, // Raw KTX2 data, used for yet unsupported KTX2 subformats
PF_TOTALCOUNT, // must be last PF_TOTALCOUNT, // must be last
} pixformat_t; } pixformat_t;
@ -50,6 +69,7 @@ typedef enum
IL_LOAD_DECAL = BIT(5), // special mode for load gradient decals IL_LOAD_DECAL = BIT(5), // special mode for load gradient decals
IL_OVERVIEW = BIT(6), // overview required some unque operations IL_OVERVIEW = BIT(6), // overview required some unque operations
IL_LOAD_PLAYER_DECAL = BIT(7), // special mode for player decals IL_LOAD_PLAYER_DECAL = BIT(7), // special mode for player decals
IL_KTX2_RAW = BIT(8), // renderer can consume raw KTX2 files (e.g. ref_vk)
} ilFlags_t; } ilFlags_t;
// goes into rgbdata_t->encode // goes into rgbdata_t->encode

2
engine/common/imagelib/imagelib.h

@ -142,6 +142,7 @@ void Image_CopyPalette32bit( void );
void Image_SetPixelFormat( void ); void Image_SetPixelFormat( void );
void Image_GetPaletteQ1( void ); void Image_GetPaletteQ1( void );
void Image_GetPaletteHL( void ); void Image_GetPaletteHL( void );
size_t Image_ComputeSize( int type, int width, int height, int depth );
// //
// formats load // formats load
@ -156,6 +157,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_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_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_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 // formats save

66
engine/common/imagelib/img_dds.c

@ -104,6 +104,13 @@ void Image_DXTGetPixelFormat( dds_t *hdr, dds_header_dxt10_t *headerExt )
{ {
switch( headerExt->dxgiFormat ) switch( headerExt->dxgiFormat )
{ {
case DXGI_FORMAT_BC4_TYPELESS:
case DXGI_FORMAT_BC4_UNORM:
image.type = PF_BC4_UNSIGNED;
break;
case DXGI_FORMAT_BC4_SNORM:
image.type = PF_BC4_SIGNED;
break;
case DXGI_FORMAT_BC6H_SF16: case DXGI_FORMAT_BC6H_SF16:
image.type = PF_BC6H_SIGNED; image.type = PF_BC6H_SIGNED;
break; break;
@ -112,9 +119,20 @@ void Image_DXTGetPixelFormat( dds_t *hdr, dds_header_dxt10_t *headerExt )
image.type = PF_BC6H_UNSIGNED; image.type = PF_BC6H_UNSIGNED;
break; break;
case DXGI_FORMAT_BC7_UNORM: case DXGI_FORMAT_BC7_UNORM:
case DXGI_FORMAT_BC7_UNORM_SRGB:
case DXGI_FORMAT_BC7_TYPELESS: case DXGI_FORMAT_BC7_TYPELESS:
image.type = PF_BC7; image.type = PF_BC7_UNORM;
break;
case DXGI_FORMAT_BC7_UNORM_SRGB:
image.type = PF_BC7_SRGB;
break;
case DXGI_FORMAT_BC5_TYPELESS:
image.type = PF_ATI2;
break;
case DXGI_FORMAT_BC5_UNORM:
image.type = PF_BC5_UNSIGNED;
break;
case DXGI_FORMAT_BC5_SNORM:
image.type = PF_BC5_SIGNED;
break; break;
default: default:
image.type = PF_UNKNOWN; image.type = PF_UNKNOWN;
@ -143,6 +161,15 @@ void Image_DXTGetPixelFormat( dds_t *hdr, dds_header_dxt10_t *headerExt )
case TYPE_ATI2: case TYPE_ATI2:
image.type = PF_ATI2; image.type = PF_ATI2;
break; break;
case TYPE_BC5S:
image.type = PF_BC5_SIGNED;
break;
case TYPE_BC4S:
image.type = PF_BC4_SIGNED;
break;
case TYPE_BC4U:
image.type = PF_BC4_UNSIGNED;
break;
default: default:
image.type = PF_UNKNOWN; // assume error image.type = PF_UNKNOWN; // assume error
break; break;
@ -188,27 +215,6 @@ void Image_DXTGetPixelFormat( dds_t *hdr, dds_header_dxt10_t *headerExt )
image.num_mips = hdr->dwMipMapCount; // get actual mip count image.num_mips = hdr->dwMipMapCount; // get actual mip count
} }
size_t Image_DXTGetLinearSize( int type, int width, int height, int depth )
{
switch( type )
{
case PF_DXT1: return ((( width + 3 ) / 4 ) * (( height + 3 ) / 4 ) * depth * 8 );
case PF_DXT3:
case PF_DXT5:
case PF_BC6H_SIGNED:
case PF_BC6H_UNSIGNED:
case PF_BC7:
case PF_ATI2: return ((( width + 3 ) / 4 ) * (( height + 3 ) / 4 ) * depth * 16 );
case PF_LUMINANCE: return (width * height * depth);
case PF_BGR_24:
case PF_RGB_24: return (width * height * depth * 3);
case PF_BGRA_32:
case PF_RGBA_32: return (width * height * depth * 4);
}
return 0;
}
size_t Image_DXTCalcMipmapSize( dds_t *hdr ) size_t Image_DXTCalcMipmapSize( dds_t *hdr )
{ {
size_t buffsize = 0; size_t buffsize = 0;
@ -219,7 +225,7 @@ size_t Image_DXTCalcMipmapSize( dds_t *hdr )
{ {
width = Q_max( 1, ( hdr->dwWidth >> i )); width = Q_max( 1, ( hdr->dwWidth >> i ));
height = Q_max( 1, ( hdr->dwHeight >> i )); height = Q_max( 1, ( hdr->dwHeight >> i ));
buffsize += Image_DXTGetLinearSize( image.type, width, height, image.depth ); buffsize += Image_ComputeSize( image.type, width, height, image.depth );
} }
return buffsize; return buffsize;
@ -255,7 +261,7 @@ uint Image_DXTCalcSize( const char *name, dds_t *hdr, size_t filesize )
if( filesize != buffsize ) // main check 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 ) if( buffsize > filesize )
return false; return false;
} }
@ -268,7 +274,7 @@ void Image_DXTAdjustVolume( dds_t *hdr )
if( hdr->dwDepth <= 1 ) if( hdr->dwDepth <= 1 )
return; return;
hdr->dwLinearSize = Image_DXTGetLinearSize( image.type, hdr->dwWidth, hdr->dwHeight, hdr->dwDepth ); hdr->dwLinearSize = Image_ComputeSize( image.type, hdr->dwWidth, hdr->dwHeight, hdr->dwDepth );
hdr->dwFlags |= DDS_LINEARSIZE; hdr->dwFlags |= DDS_LINEARSIZE;
} }
@ -323,7 +329,7 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi
Image_DXTGetPixelFormat( &header, &header2 ); // and image type too :) Image_DXTGetPixelFormat( &header, &header2 ); // and image type too :)
Image_DXTAdjustVolume( &header ); Image_DXTAdjustVolume( &header );
if( !Image_CheckFlag( IL_DDS_HARDWARE ) && ImageDXT( image.type )) if( !Image_CheckFlag( IL_DDS_HARDWARE ) && ImageCompressed( image.type ))
return false; // silently rejected return false; // silently rejected
if( image.type == PF_UNKNOWN ) if( image.type == PF_UNKNOWN )
@ -356,8 +362,10 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi
SetBits( image.flags, IMAGE_HAS_ALPHA ); SetBits( image.flags, IMAGE_HAS_ALPHA );
else if( image.type == PF_DXT5 && Image_CheckDXT5Alpha( &header, fin )) else if( image.type == PF_DXT5 && Image_CheckDXT5Alpha( &header, fin ))
SetBits( image.flags, IMAGE_HAS_ALPHA ); SetBits( image.flags, IMAGE_HAS_ALPHA );
else if ( image.type == PF_BC7 ) else if( image.type == PF_BC5_SIGNED || image.type == PF_BC5_UNSIGNED )
SetBits(image.flags, IMAGE_HAS_ALPHA); SetBits( image.flags, IMAGE_HAS_ALPHA );
else if( image.type == PF_BC7_UNORM || image.type == PF_BC7_SRGB )
SetBits( image.flags, IMAGE_HAS_ALPHA );
if( !FBitSet( header.dsPixelFormat.dwFlags, DDS_LUMINANCE )) if( !FBitSet( header.dsPixelFormat.dwFlags, DDS_LUMINANCE ))
SetBits( image.flags, IMAGE_HAS_COLOR ); SetBits( image.flags, IMAGE_HAS_COLOR );
break; break;

3
engine/common/imagelib/img_dds.h

@ -32,6 +32,9 @@ GNU General Public License for more details.
#define TYPE_DX10 (('0'<<24)+('1'<<16)+('X'<<8)+'D') // little-endian "DX10" #define TYPE_DX10 (('0'<<24)+('1'<<16)+('X'<<8)+'D') // little-endian "DX10"
#define TYPE_ATI1 (('1'<<24)+('I'<<16)+('T'<<8)+'A') // little-endian "ATI1" #define TYPE_ATI1 (('1'<<24)+('I'<<16)+('T'<<8)+'A') // little-endian "ATI1"
#define TYPE_ATI2 (('2'<<24)+('I'<<16)+('T'<<8)+'A') // little-endian "ATI2" #define TYPE_ATI2 (('2'<<24)+('I'<<16)+('T'<<8)+'A') // little-endian "ATI2"
#define TYPE_BC5S (('S'<<24)+('5'<<16)+('C'<<8)+'B') // little-endian "BC5S"
#define TYPE_BC4S (('S'<<24)+('4'<<16)+('C'<<8)+'B') // little-endian "BC4S"
#define TYPE_BC4U (('U'<<24)+('4'<<16)+('C'<<8)+'B') // little-endian "BC4U"
#define TYPE_RXGB (('B'<<24)+('G'<<16)+('X'<<8)+'R') // little-endian "RXGB" doom3 normalmaps #define TYPE_RXGB (('B'<<24)+('G'<<16)+('X'<<8)+'R') // little-endian "RXGB" doom3 normalmaps
#define TYPE_$ (('\0'<<24)+('\0'<<16)+('\0'<<8)+'$') // little-endian "$" #define TYPE_$ (('\0'<<24)+('\0'<<16)+('\0'<<8)+'$') // little-endian "$"
#define TYPE_o (('\0'<<24)+('\0'<<16)+('\0'<<8)+'o') // little-endian "o" #define TYPE_o (('\0'<<24)+('\0'<<16)+('\0'<<8)+'o') // little-endian "o"

204
engine/common/imagelib/img_ktx2.c

@ -0,0 +1,204 @@
/*
img_ktx2.c - ktx2 format load
Copyright (C) 2023 Provod
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 "img_ktx2.h"
static void Image_KTX2Format( uint32_t ktx2_format )
{
switch( ktx2_format )
{
case KTX2_FORMAT_BC4_UNORM_BLOCK:
image.type = PF_BC4_UNSIGNED;
// 1 component for ref_gl
break;
case KTX2_FORMAT_BC4_SNORM_BLOCK:
image.type = PF_BC4_SIGNED;
// 1 component for ref_gl
break;
case KTX2_FORMAT_BC5_UNORM_BLOCK:
image.type = PF_BC5_UNSIGNED;
// 2 components for ref_gl
SetBits( image.flags, IMAGE_HAS_ALPHA );
break;
case KTX2_FORMAT_BC5_SNORM_BLOCK:
image.type = PF_BC5_SIGNED;
// 2 components for ref_gl
SetBits( image.flags, IMAGE_HAS_ALPHA );
break;
case KTX2_FORMAT_BC6H_UFLOAT_BLOCK:
image.type = PF_BC6H_UNSIGNED;
// 3 components for ref_gl
SetBits( image.flags, IMAGE_HAS_COLOR );
break;
case KTX2_FORMAT_BC6H_SFLOAT_BLOCK:
image.type = PF_BC6H_SIGNED;
// 3 components for ref_gl
SetBits( image.flags, IMAGE_HAS_COLOR );
break;
case KTX2_FORMAT_BC7_UNORM_BLOCK:
image.type = PF_BC7_UNORM;
// 4 components for ref_gl
SetBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA );
break;
case KTX2_FORMAT_BC7_SRGB_BLOCK:
image.type = PF_BC7_SRGB;
// 4 components for ref_gl
SetBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA );
break;
default:
image.type = PF_UNKNOWN;
break;
}
}
static qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer, fs_offset_t filesize )
{
ktx2_index_t index;
size_t total_size = 0;
size_t max_offset = 0;
const byte *const levels_begin = buffer + KTX2_LEVELS_OFFSET;
// Sets image.type and image.flags
Image_KTX2Format( header->vkFormat );
if( image.type == PF_UNKNOWN )
{
Con_DPrintf( S_ERROR "%s: unsupported KTX2 format %d\n", __FUNCTION__, header->vkFormat );
return false;
}
if( !Image_CheckFlag( IL_DDS_HARDWARE ) && ImageCompressed( image.type ))
{
Con_DPrintf( S_WARN "%s: has compressed format, but support is not advertized\n", __FUNCTION__ );
return false;
}
if( header->pixelDepth > 1 )
{
Con_DPrintf( S_ERROR "%s: unsupported KTX2 pixelDepth %d\n", __FUNCTION__, header->pixelDepth );
return false;
}
if( header->faceCount > 1 )
{
Con_DPrintf( S_ERROR "%s: unsupported KTX2 faceCount %d\n", __FUNCTION__, header->faceCount );
return false;
}
if( header->layerCount > 1 )
{
Con_DPrintf( S_ERROR "%s: unsupported KTX2 layerCount %d\n", __FUNCTION__, header->layerCount );
return false;
}
if( header->supercompressionScheme != 0 )
{
Con_DPrintf( S_ERROR "%s: unsupported KTX2 supercompressionScheme %d\n", __FUNCTION__, header->supercompressionScheme );
return false;
}
if( header->levelCount * sizeof( ktx2_level_t ) + KTX2_LEVELS_OFFSET > filesize )
{
Con_DPrintf( S_ERROR "%s: file abruptly ends\n", __FUNCTION__ );
return false;
}
memcpy( &index, buffer + KTX2_IDENTIFIER_SIZE + sizeof( ktx2_header_t ), sizeof( index ));
for( int mip = 0; mip < header->levelCount; ++mip )
{
const uint32_t width = Q_max( 1, ( header->pixelWidth >> mip ));
const uint32_t height = Q_max( 1, ( header->pixelHeight >> mip ));
const uint32_t mip_size = Image_ComputeSize( image.type, width, height, image.depth );
ktx2_level_t level;
memcpy( &level, levels_begin + mip * sizeof( level ), sizeof( level ));
if( mip_size != level.byteLength )
{
Con_DPrintf( S_ERROR "%s: mip=%d size mismatch read=%d, but computed=%d\n",
__FUNCTION__, mip, (int)level.byteLength, mip_size );
return false;
}
total_size += level.byteLength;
max_offset = Q_max( max_offset, level.byteLength + level.byteOffset );
}
if( max_offset > filesize )
return false;
image.size = total_size;
image.num_mips = header->levelCount;
image.rgba = Mem_Malloc( host.imagepool, image.size );
memcpy( image.rgba, buffer, image.size );
for( int mip = 0, cursor = 0; mip < header->levelCount; ++mip )
{
ktx2_level_t level;
memcpy( &level, levels_begin + mip * sizeof( level ), sizeof( level ));
memcpy( image.rgba + cursor, buffer + level.byteOffset, level.byteLength );
cursor += level.byteLength;
}
return true;
}
qboolean Image_LoadKTX2( const char *name, const byte *buffer, fs_offset_t filesize )
{
ktx2_header_t header;
if( filesize < KTX2_MINIMAL_HEADER_SIZE )
return false;
if( memcmp( buffer, KTX2_IDENTIFIER, KTX2_IDENTIFIER_SIZE ) != 0 )
{
Con_DPrintf( S_ERROR "%s: (%s) has invalid identifier\n", __FUNCTION__, name );
return false;
}
memcpy( &header, buffer + KTX2_IDENTIFIER_SIZE, sizeof( header ));
image.width = header.pixelWidth;
image.height = header.pixelHeight;
image.depth = Q_max( 1, header.pixelDepth );
image.num_mips = 1;
ClearBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA | IMAGE_HAS_LUMA );
if( !Image_KTX2Parse( &header, buffer, filesize ))
{
if( !Image_CheckFlag( IL_KTX2_RAW ))
return false;
// If KTX2 to imagelib conversion failed, try passing the file as raw data.
// This is useful for ref_vk which can directly support hundreds of formats which we don't convert to pixformat_t here
Con_DPrintf( S_WARN "%s: (%s) could not be converted to supported imagelib format, passing as raw KTX2 data\n", __FUNCTION__, name );
// 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?
image.rgba = Mem_Malloc( host.imagepool, image.size );
memcpy( image.rgba, buffer, image.size );
}
return true;
}

79
engine/common/imagelib/img_ktx2.h

@ -0,0 +1,79 @@
/*
img_ktx2.h - ktx2 format reference
Copyright (C) 2023 Provod
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.
*/
#ifndef IMG_KTX2_H
#define IMG_KTX2_H
#include <stdint.h>
#define KTX2_IDENTIFIER_SIZE 12
#define KTX2_IDENTIFIER "\xABKTX 20\xBB\r\n\x1A\n"
/*
static const char k_ktx2_identifier[KTX2_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;
} ktx2_header_t;
typedef struct
{
uint32_t dfdByteOffset;
uint32_t dfdByteLength;
uint32_t kvdByteOffset;
uint32_t kvdByteLength;
uint64_t sgdByteOffset;
uint64_t sgdByteLength;
} ktx2_index_t;
typedef struct
{
uint64_t byteOffset;
uint64_t byteLength;
uint64_t uncompressedByteLength;
} ktx2_level_t;
#define KTX2_LEVELS_OFFSET ( KTX2_IDENTIFIER_SIZE + sizeof( ktx2_header_t ) + sizeof( ktx2_index_t ))
#define KTX2_MINIMAL_HEADER_SIZE ( KTX2_LEVELS_OFFSET + sizeof( ktx2_level_t ))
// These have the same values as enum VkFormat in vulkan_core.h
// There are hundreds of formats which can be contained in KTX2.
// Below are listed the ones which are supported here. This list can be extended.
typedef enum
{
KTX2_FORMAT_BC4_UNORM_BLOCK = 139,
KTX2_FORMAT_BC4_SNORM_BLOCK = 140,
KTX2_FORMAT_BC5_UNORM_BLOCK = 141,
KTX2_FORMAT_BC5_SNORM_BLOCK = 142,
KTX2_FORMAT_BC6H_UFLOAT_BLOCK = 143,
KTX2_FORMAT_BC6H_SFLOAT_BLOCK = 144,
KTX2_FORMAT_BC7_UNORM_BLOCK = 145,
KTX2_FORMAT_BC7_SRGB_BLOCK = 146,
} ktx2_format_t;
#endif // IMG_KTX2_H

34
engine/common/imagelib/img_utils.c

@ -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", "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", "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", "pal", Image_LoadPAL, IL_HINT_NO }, // install studio\sprite palette
{ "%s%s.%s", "ktx2", Image_LoadKTX2, IL_HINT_NO }, // ktx2 for world and studio models
{ NULL, NULL, NULL, IL_HINT_NO } { NULL, NULL, NULL, IL_HINT_NO }
}; };
@ -1446,3 +1447,36 @@ qboolean Image_Process(rgbdata_t **pix, int width, int height, uint flags, float
return result; return result;
} }
// This codebase has too many copies of this function:
// - ref_gl has one
// - ref_vk has one
// - ref_soft has one
// - many more places probably have one too
// TODO figure out how to make it available for ref_*
size_t Image_ComputeSize( int type, int width, int height, int depth )
{
switch( type )
{
case PF_DXT1:
case PF_BC4_SIGNED:
case PF_BC4_UNSIGNED:
return ((( width + 3 ) / 4 ) * (( height + 3 ) / 4 ) * depth * 8 );
case PF_DXT3:
case PF_DXT5:
case PF_ATI2:
case PF_BC5_UNSIGNED:
case PF_BC5_SIGNED:
case PF_BC6H_SIGNED:
case PF_BC6H_UNSIGNED:
case PF_BC7_UNORM:
case PF_BC7_SRGB: return ((( width + 3 ) / 4 ) * (( height + 3 ) / 4 ) * depth * 16 );
case PF_LUMINANCE: return ( width * height * depth );
case PF_BGR_24:
case PF_RGB_24: return ( width * height * depth * 3 );
case PF_BGRA_32:
case PF_RGBA_32: return ( width * height * depth * 4 );
}
return 0;
}

22
ref/gl/gl_image.c

@ -388,7 +388,8 @@ static size_t GL_CalcImageSize( pixformat_t format, int width, int height, int d
case PF_DXT5: case PF_DXT5:
case PF_BC6H_SIGNED: case PF_BC6H_SIGNED:
case PF_BC6H_UNSIGNED: case PF_BC6H_UNSIGNED:
case PF_BC7: case PF_BC7_UNORM:
case PF_BC7_SRGB:
case PF_ATI2: case PF_ATI2:
size = (((width + 3) >> 2) * ((height + 3) >> 2) * 16) * depth; size = (((width + 3) >> 2) * ((height + 3) >> 2) * 16) * depth;
break; break;
@ -694,7 +695,7 @@ static void GL_SetTextureFormat( gl_texture_t *tex, pixformat_t format, int chan
Assert( tex != NULL ); Assert( tex != NULL );
if( ImageDXT( format )) if( ImageCompressed( format ))
{ {
switch( format ) switch( format )
{ {
@ -703,7 +704,8 @@ static void GL_SetTextureFormat( gl_texture_t *tex, pixformat_t format, int chan
case PF_DXT5: tex->format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; case PF_DXT5: tex->format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break;
case PF_BC6H_SIGNED: tex->format = GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB; break; case PF_BC6H_SIGNED: tex->format = GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB; break;
case PF_BC6H_UNSIGNED: tex->format = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB; break; case PF_BC6H_UNSIGNED: tex->format = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB; break;
case PF_BC7: tex->format = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; break; case PF_BC7_SRGB:
case PF_BC7_UNORM: tex->format = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; break;
case PF_ATI2: case PF_ATI2:
if( glConfig.hardware_type == GLHW_RADEON ) if( glConfig.hardware_type == GLHW_RADEON )
tex->format = GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI; tex->format = GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI;
@ -1094,7 +1096,7 @@ static void GL_TextureImageRAW( gl_texture_t *tex, GLint side, GLint level, GLin
} }
} }
static void GL_TextureImageDXT( gl_texture_t *tex, GLint side, GLint level, GLint width, GLint height, GLint depth, size_t size, const void *data ) static void GL_TextureImageCompressed( gl_texture_t *tex, GLint side, GLint level, GLint width, GLint height, GLint depth, size_t size, const void *data )
{ {
GLuint cubeTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB; GLuint cubeTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB;
qboolean subImage = FBitSet( tex->flags, TF_IMG_UPLOADED ); qboolean subImage = FBitSet( tex->flags, TF_IMG_UPLOADED );
@ -1176,7 +1178,7 @@ static qboolean GL_UploadTexture( gl_texture_t *tex, rgbdata_t *pic )
return false; return false;
} }
if( pic->type == PF_BC6H_SIGNED || pic->type == PF_BC6H_UNSIGNED || pic->type == PF_BC7 ) if( pic->type == PF_BC6H_SIGNED || pic->type == PF_BC6H_UNSIGNED || pic->type == PF_BC7_UNORM || pic->type == PF_BC7_SRGB )
{ {
if( !GL_Support( GL_ARB_TEXTURE_COMPRESSION_BPTC )) if( !GL_Support( GL_ARB_TEXTURE_COMPRESSION_BPTC ))
{ {
@ -1217,7 +1219,7 @@ static qboolean GL_UploadTexture( gl_texture_t *tex, rgbdata_t *pic )
if( buf != NULL && buf >= bufend ) if( buf != NULL && buf >= bufend )
gEngfuncs.Host_Error( "GL_UploadTexture: %s image buffer overflow\n", tex->name ); gEngfuncs.Host_Error( "GL_UploadTexture: %s image buffer overflow\n", tex->name );
if( ImageDXT( pic->type )) if( ImageCompressed( pic->type ))
{ {
for( j = 0; j < Q_max( 1, pic->numMips ); j++ ) for( j = 0; j < Q_max( 1, pic->numMips ); j++ )
{ {
@ -1225,7 +1227,7 @@ static qboolean GL_UploadTexture( gl_texture_t *tex, rgbdata_t *pic )
height = Q_max( 1, ( tex->height >> j )); height = Q_max( 1, ( tex->height >> j ));
texsize = GL_CalcTextureSize( tex->format, width, height, tex->depth ); texsize = GL_CalcTextureSize( tex->format, width, height, tex->depth );
size = GL_CalcImageSize( pic->type, width, height, tex->depth ); size = GL_CalcImageSize( pic->type, width, height, tex->depth );
GL_TextureImageDXT( tex, i, j, width, height, tex->depth, size, buf ); GL_TextureImageCompressed( tex, i, j, width, height, tex->depth, size, buf );
tex->size += texsize; tex->size += texsize;
buf += size; // move pointer buf += size; // move pointer
tex->numMips++; tex->numMips++;
@ -1259,7 +1261,7 @@ static qboolean GL_UploadTexture( gl_texture_t *tex, rgbdata_t *pic )
data = GL_ResampleTexture( buf, pic->width, pic->height, tex->width, tex->height, normalMap ); data = GL_ResampleTexture( buf, pic->width, pic->height, tex->width, tex->height, normalMap );
else data = buf; else data = buf;
if( !ImageDXT( pic->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA )) if( !ImageCompressed( pic->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA ))
data = GL_ApplyFilter( data, tex->width, tex->height ); data = GL_ApplyFilter( data, tex->width, tex->height );
// mips will be auto-generated if desired // mips will be auto-generated if desired
@ -1307,7 +1309,7 @@ static void GL_ProcessImage( gl_texture_t *tex, rgbdata_t *pic )
tex->encode = pic->encode; // share encode method tex->encode = pic->encode; // share encode method
if( ImageDXT( pic->type )) if( ImageCompressed( pic->type ))
{ {
if( !pic->numMips ) if( !pic->numMips )
tex->flags |= TF_NOMIPMAP; // disable mipmapping by user request tex->flags |= TF_NOMIPMAP; // disable mipmapping by user request
@ -1922,7 +1924,7 @@ void GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor )
return; return;
} }
if( ImageDXT( image->original->type )) if( ImageCompressed( image->original->type ))
{ {
gEngfuncs.Con_Printf( S_ERROR "GL_ProcessTexture: can't process compressed texture %s\n", image->name ); gEngfuncs.Con_Printf( S_ERROR "GL_ProcessTexture: can't process compressed texture %s\n", image->name );
return; return;

12
ref/soft/r_image.c

@ -557,7 +557,7 @@ static qboolean GL_UploadTexture( image_t *tex, rgbdata_t *pic )
data = GL_ResampleTexture( buf, pic->width, pic->height, tex->width, tex->height, normalMap ); data = GL_ResampleTexture( buf, pic->width, pic->height, tex->width, tex->height, normalMap );
else data = buf; else data = buf;
//if( !ImageDXT( pic->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA )) //if( !ImageCompressed( pic->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA ))
// data = GL_ApplyFilter( data, tex->width, tex->height ); // data = GL_ApplyFilter( data, tex->width, tex->height );
// mips will be auto-generated if desired // mips will be auto-generated if desired
@ -663,7 +663,7 @@ static qboolean GL_UploadTexture( image_t *tex, rgbdata_t *pic )
if( buf != NULL && buf >= bufend ) if( buf != NULL && buf >= bufend )
gEngfuncs.Host_Error( "GL_UploadTexture: %s image buffer overflow\n", tex->name ); gEngfuncs.Host_Error( "GL_UploadTexture: %s image buffer overflow\n", tex->name );
if( ImageDXT( pic->type )) if( ImageCompressed( pic->type ))
{ {
for( j = 0; j < Q_max( 1, pic->numMips ); j++ ) for( j = 0; j < Q_max( 1, pic->numMips ); j++ )
{ {
@ -671,7 +671,7 @@ static qboolean GL_UploadTexture( image_t *tex, rgbdata_t *pic )
height = Q_max( 1, ( tex->height >> j )); height = Q_max( 1, ( tex->height >> j ));
texsize = GL_CalcTextureSize( tex->format, width, height, tex->depth ); texsize = GL_CalcTextureSize( tex->format, width, height, tex->depth );
size = GL_CalcImageSize( pic->type, width, height, tex->depth ); size = GL_CalcImageSize( pic->type, width, height, tex->depth );
GL_TextureImageDXT( tex, i, j, width, height, tex->depth, size, buf ); GL_TextureImageCompressed( tex, i, j, width, height, tex->depth, size, buf );
tex->size += texsize; tex->size += texsize;
buf += size; // move pointer buf += size; // move pointer
tex->numMips++; tex->numMips++;
@ -705,7 +705,7 @@ static qboolean GL_UploadTexture( image_t *tex, rgbdata_t *pic )
data = GL_ResampleTexture( buf, pic->width, pic->height, tex->width, tex->height, normalMap ); data = GL_ResampleTexture( buf, pic->width, pic->height, tex->width, tex->height, normalMap );
else data = buf; else data = buf;
if( !ImageDXT( pic->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA )) if( !ImageCompressed( pic->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA ))
data = GL_ApplyFilter( data, tex->width, tex->height ); data = GL_ApplyFilter( data, tex->width, tex->height );
// mips will be auto-generated if desired // mips will be auto-generated if desired
@ -753,7 +753,7 @@ static void GL_ProcessImage( image_t *tex, rgbdata_t *pic )
if( tex->flags & TF_FORCE_COLOR ) pic->flags |= IMAGE_HAS_COLOR; if( tex->flags & TF_FORCE_COLOR ) pic->flags |= IMAGE_HAS_COLOR;
if( pic->flags & IMAGE_HAS_ALPHA ) tex->flags |= TF_HAS_ALPHA; if( pic->flags & IMAGE_HAS_ALPHA ) tex->flags |= TF_HAS_ALPHA;
if( ImageDXT( pic->type )) if( ImageCompressed( pic->type ))
{ {
if( !pic->numMips ) if( !pic->numMips )
tex->flags |= TF_NOMIPMAP; // disable mipmapping by user request tex->flags |= TF_NOMIPMAP; // disable mipmapping by user request
@ -1178,7 +1178,7 @@ void GAME_EXPORT GL_ProcessTexture( int texnum, float gamma, int topColor, int b
return; return;
} }
if( ImageDXT( image->original->type )) if( ImageCompressed( image->original->type ))
{ {
gEngfuncs.Con_Printf( S_ERROR "GL_ProcessTexture: can't process compressed texture %s\n", image->name ); gEngfuncs.Con_Printf( S_ERROR "GL_ProcessTexture: can't process compressed texture %s\n", image->name );
return; return;

Loading…
Cancel
Save