https://github.com/xonotic/netradiant/pull/1 https://gitlab.com/xonotic/netradiant/-/issues/5 This pull request adds support for Khronos Textures to NetRadiant and Q3Map2, with OpenGL ES 2.0 formats, Ericsson Texture Compression version 1, and BGR/BGRA. The patent-free Ericsson Texture Compression format will be used in the next version of Warsow to significantly reduce VRAM usage on mobile GPUs and the integrated GPU of Intel Broadwell.
457 lines
11 KiB
C++
457 lines
11 KiB
C++
/* -------------------------------------------------------------------------------
|
|
|
|
Copyright (C) 1999-2007 id Software, Inc. and contributors.
|
|
For a list of contributors, see the accompanying CONTRIBUTORS file.
|
|
|
|
This file is part of GtkRadiant.
|
|
|
|
GtkRadiant 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 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
GtkRadiant 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 GtkRadiant; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
----------------------------------------------------------------------------------
|
|
|
|
This code has been altered significantly from its original form, to support
|
|
several games based on the Quake III Arena engine, in the form of "Q3Map2."
|
|
|
|
------------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
/* dependencies */
|
|
#include "q3map2.h"
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------------------
|
|
|
|
this file contains image pool management with reference counting. note: it isn't
|
|
reentrant, so only call it from init/shutdown code or wrap calls in a mutex
|
|
|
|
------------------------------------------------------------------------------- */
|
|
|
|
/*
|
|
LoadDDSBuffer()
|
|
loads a dxtc (1, 3, 5) dds buffer into a valid rgba image
|
|
*/
|
|
|
|
static void LoadDDSBuffer( byte *buffer, int size, byte **pixels, int *width, int *height ){
|
|
int w, h;
|
|
ddsPF_t pf;
|
|
|
|
|
|
/* dummy check */
|
|
if ( buffer == NULL || size <= 0 || pixels == NULL || width == NULL || height == NULL ) {
|
|
return;
|
|
}
|
|
|
|
/* null out */
|
|
*pixels = 0;
|
|
*width = 0;
|
|
*height = 0;
|
|
|
|
/* get dds info */
|
|
if ( DDSGetInfo( (ddsBuffer_t*) buffer, &w, &h, &pf ) ) {
|
|
Sys_Warning( "Invalid DDS texture\n" );
|
|
return;
|
|
}
|
|
|
|
/* only certain types of dds textures are supported */
|
|
if ( pf != DDS_PF_ARGB8888 && pf != DDS_PF_DXT1 && pf != DDS_PF_DXT3 && pf != DDS_PF_DXT5 ) {
|
|
Sys_Warning( "Only DDS texture formats ARGB8888, DXT1, DXT3, and DXT5 are supported (%d)\n", pf );
|
|
return;
|
|
}
|
|
|
|
/* create image pixel buffer */
|
|
*width = w;
|
|
*height = h;
|
|
*pixels = safe_malloc( w * h * 4 );
|
|
|
|
/* decompress the dds texture */
|
|
DDSDecompress( (ddsBuffer_t*) buffer, *pixels );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
PNGReadData()
|
|
callback function for libpng to read from a memory buffer
|
|
note: this function is a total hack, as it reads/writes the png struct directly!
|
|
*/
|
|
|
|
struct pngBuffer_t
|
|
{
|
|
byte *buffer;
|
|
png_size_t size, offset;
|
|
};
|
|
|
|
void PNGReadData( png_struct *png, png_byte *buffer, png_size_t size ){
|
|
pngBuffer_t *pb = (pngBuffer_t*) png_get_io_ptr( png );
|
|
|
|
|
|
if ( ( pb->offset + size ) > pb->size ) {
|
|
size = ( pb->size - pb->offset );
|
|
}
|
|
memcpy( buffer, &pb->buffer[ pb->offset ], size );
|
|
pb->offset += size;
|
|
//% Sys_Printf( "Copying %d bytes from 0x%08X to 0x%08X (offset: %d of %d)\n", size, &pb->buffer[ pb->offset ], buffer, pb->offset, pb->size );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
LoadPNGBuffer()
|
|
loads a png file buffer into a valid rgba image
|
|
*/
|
|
|
|
static void LoadPNGBuffer( byte *buffer, int size, byte **pixels, int *width, int *height ){
|
|
png_struct *png;
|
|
png_info *info, *end;
|
|
pngBuffer_t pb;
|
|
//pngBuffer_t *pb = (pngBuffer_t*) png_get_io_ptr( png );
|
|
int bitDepth, colorType;
|
|
png_uint_32 w, h, i;
|
|
byte **rowPointers;
|
|
|
|
/* dummy check */
|
|
if ( buffer == NULL || size <= 0 || pixels == NULL || width == NULL || height == NULL ) {
|
|
return;
|
|
}
|
|
|
|
/* null out */
|
|
*pixels = 0;
|
|
*width = 0;
|
|
*height = 0;
|
|
|
|
/* determine if this is a png file */
|
|
if ( png_sig_cmp( buffer, 0, 8 ) != 0 ) {
|
|
Sys_Warning( "Invalid PNG file\n" );
|
|
return;
|
|
}
|
|
|
|
/* create png structs */
|
|
png = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL );
|
|
if ( png == NULL ) {
|
|
Sys_Warning( "Unable to create PNG read struct\n" );
|
|
return;
|
|
}
|
|
|
|
info = png_create_info_struct( png );
|
|
if ( info == NULL ) {
|
|
Sys_Warning( "Unable to create PNG info struct\n" );
|
|
png_destroy_read_struct( &png, NULL, NULL );
|
|
return;
|
|
}
|
|
|
|
end = png_create_info_struct( png );
|
|
if ( end == NULL ) {
|
|
Sys_Warning( "Unable to create PNG end info struct\n" );
|
|
png_destroy_read_struct( &png, &info, NULL );
|
|
return;
|
|
}
|
|
|
|
/* set read callback */
|
|
pb.buffer = buffer;
|
|
pb.size = size;
|
|
pb.offset = 0;
|
|
png_set_read_fn( png, &pb, PNGReadData );
|
|
//png->io_ptr = &pb; /* hack! */
|
|
|
|
/* set error longjmp */
|
|
if ( setjmp( png_jmpbuf(png) ) ) {
|
|
Sys_Warning( "An error occurred reading PNG image\n" );
|
|
png_destroy_read_struct( &png, &info, &end );
|
|
return;
|
|
}
|
|
|
|
/* fixme: add proper i/o stuff here */
|
|
|
|
/* read png info */
|
|
png_read_info( png, info );
|
|
|
|
/* read image header chunk */
|
|
png_get_IHDR( png, info,
|
|
&w, &h, &bitDepth, &colorType, NULL, NULL, NULL );
|
|
|
|
/* the following will probably bork on certain types of png images, but hey... */
|
|
|
|
/* force indexed/gray/trans chunk to rgb */
|
|
if ( ( colorType == PNG_COLOR_TYPE_PALETTE && bitDepth <= 8 ) ||
|
|
( colorType == PNG_COLOR_TYPE_GRAY && bitDepth <= 8 ) ||
|
|
png_get_valid( png, info, PNG_INFO_tRNS ) ) {
|
|
png_set_expand( png );
|
|
}
|
|
|
|
/* strip 16bpc -> 8bpc */
|
|
if ( bitDepth == 16 ) {
|
|
png_set_strip_16( png );
|
|
}
|
|
|
|
/* pad rgb to rgba */
|
|
if ( bitDepth == 8 && colorType == PNG_COLOR_TYPE_RGB ) {
|
|
png_set_filler( png, 255, PNG_FILLER_AFTER );
|
|
}
|
|
|
|
/* create image pixel buffer */
|
|
*width = w;
|
|
*height = h;
|
|
*pixels = safe_malloc( w * h * 4 );
|
|
|
|
/* create row pointers */
|
|
rowPointers = safe_malloc( h * sizeof( byte* ) );
|
|
for ( i = 0; i < h; i++ )
|
|
rowPointers[ i ] = *pixels + ( i * w * 4 );
|
|
|
|
/* read the png */
|
|
png_read_image( png, rowPointers );
|
|
|
|
/* clean up */
|
|
free( rowPointers );
|
|
png_destroy_read_struct( &png, &info, &end );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
ImageInit()
|
|
implicitly called by every function to set up image list
|
|
*/
|
|
|
|
static void ImageInit( void ){
|
|
if ( numImages <= 0 ) {
|
|
/* clear images (fixme: this could theoretically leak) */
|
|
memset( images, 0, sizeof( images ) );
|
|
|
|
/* generate *bogus image */
|
|
images[ 0 ].name = copystring( DEFAULT_IMAGE );
|
|
images[ 0 ].filename = copystring( DEFAULT_IMAGE );
|
|
images[ 0 ].width = 64;
|
|
images[ 0 ].height = 64;
|
|
images[ 0 ].refCount = 1;
|
|
images[ 0 ].pixels = safe_malloc( 64 * 64 * 4 );
|
|
memset( images[ 0 ].pixels, 255, 64 * 64 * 4 );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
ImageFree()
|
|
frees an rgba image
|
|
*/
|
|
|
|
void ImageFree( image_t *image ){
|
|
/* dummy check */
|
|
if ( image == NULL ) {
|
|
return;
|
|
}
|
|
|
|
/* decrement refcount */
|
|
image->refCount--;
|
|
|
|
/* free? */
|
|
if ( image->refCount <= 0 ) {
|
|
free( image->name );
|
|
image->name = NULL;
|
|
free( image->filename );
|
|
image->filename = NULL;
|
|
free( image->pixels );
|
|
image->width = 0;
|
|
image->height = 0;
|
|
numImages--;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
ImageFind()
|
|
finds an existing rgba image and returns a pointer to the image_t struct or NULL if not found
|
|
name is name without extension, as in images[ i ].name
|
|
*/
|
|
|
|
image_t *ImageFind( const char *name ){
|
|
/* init */
|
|
ImageInit();
|
|
|
|
/* dummy check */
|
|
if ( strEmptyOrNull( name ) ) {
|
|
return NULL;
|
|
}
|
|
|
|
/* search list */
|
|
for ( int i = 0; i < MAX_IMAGES; ++i )
|
|
{
|
|
if ( images[ i ].name != NULL && strEqual( name, images[ i ].name ) ) {
|
|
return &images[ i ];
|
|
}
|
|
}
|
|
|
|
/* no matching image found */
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
ImageLoad()
|
|
loads an rgba image and returns a pointer to the image_t struct or NULL if not found
|
|
expects extensionless path as input
|
|
*/
|
|
|
|
image_t *ImageLoad( const char *filename ){
|
|
image_t *image;
|
|
char name[ 1024 ];
|
|
int size;
|
|
byte *buffer = NULL;
|
|
bool alphaHack = false;
|
|
|
|
|
|
/* init */
|
|
ImageInit();
|
|
|
|
/* dummy check */
|
|
if ( strEmptyOrNull( filename ) ) {
|
|
return NULL;
|
|
}
|
|
|
|
strcpy( name, filename );
|
|
|
|
/* try to find existing image */
|
|
image = ImageFind( name );
|
|
if ( image != NULL ) {
|
|
image->refCount++;
|
|
return image;
|
|
}
|
|
|
|
/* none found, so find first empty image slot */
|
|
for ( int i = 0; i < MAX_IMAGES; ++i )
|
|
{
|
|
if ( images[ i ].name == NULL ) {
|
|
image = &images[ i ];
|
|
image->name = copystring( name ); /* set it up */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* too many images? */
|
|
if ( image == NULL ) {
|
|
Error( "MAX_IMAGES (%d) exceeded, there are too many image files referenced by the map.", MAX_IMAGES );
|
|
}
|
|
|
|
/* attempt to load tga */
|
|
strcat( name, ".tga" ); // StripExtension( name ); already
|
|
size = vfsLoadFile( name, (void**) &buffer, 0 );
|
|
if ( size > 0 ) {
|
|
LoadTGABuffer( buffer, buffer + size, &image->pixels, &image->width, &image->height );
|
|
}
|
|
else
|
|
{
|
|
/* attempt to load png */
|
|
path_set_extension( name, ".png" );
|
|
size = vfsLoadFile( name, (void**) &buffer, 0 );
|
|
if ( size > 0 ) {
|
|
LoadPNGBuffer( buffer, size, &image->pixels, &image->width, &image->height );
|
|
}
|
|
else
|
|
{
|
|
/* attempt to load jpg */
|
|
path_set_extension( name, ".jpg" );
|
|
size = vfsLoadFile( name, (void**) &buffer, 0 );
|
|
if ( size > 0 ) {
|
|
if ( LoadJPGBuff( buffer, size, &image->pixels, &image->width, &image->height ) == -1 && image->pixels != NULL ) {
|
|
// On error, LoadJPGBuff might store a pointer to the error message in image->pixels
|
|
Sys_Warning( "LoadJPGBuff %s %s\n", name, (unsigned char*) image->pixels );
|
|
}
|
|
alphaHack = true;
|
|
}
|
|
else
|
|
{
|
|
/* attempt to load dds */
|
|
path_set_extension( name, ".dds" );
|
|
size = vfsLoadFile( name, (void**) &buffer, 0 );
|
|
if ( size > 0 ) {
|
|
LoadDDSBuffer( buffer, size, &image->pixels, &image->width, &image->height );
|
|
|
|
/* debug code */
|
|
#if 1
|
|
{
|
|
ddsPF_t pf;
|
|
DDSGetInfo( (ddsBuffer_t*) buffer, NULL, NULL, &pf );
|
|
Sys_Printf( "pf = %d\n", pf );
|
|
if ( image->width > 0 ) {
|
|
path_set_extension( name, "_converted.tga" );
|
|
WriteTGA( "C:\\games\\quake3\\baseq3\\textures\\rad\\dds_converted.tga", image->pixels, image->width, image->height );
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
/* attempt to load ktx */
|
|
path_set_extension( name, ".ktx" );
|
|
size = vfsLoadFile( name, (void**) &buffer, 0 );
|
|
if ( size > 0 ) {
|
|
LoadKTXBufferFirstImage( buffer, size, &image->pixels, &image->width, &image->height );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* free file buffer */
|
|
free( buffer );
|
|
|
|
/* make sure everything's kosher */
|
|
if ( size <= 0 || image->width <= 0 || image->height <= 0 || image->pixels == NULL ) {
|
|
//% Sys_Printf( "size = %d width = %d height = %d pixels = 0x%08x (%s)\n",
|
|
//% size, image->width, image->height, image->pixels, name );
|
|
free( image->name );
|
|
image->name = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
/* set filename */
|
|
image->filename = copystring( name );
|
|
|
|
/* set count */
|
|
image->refCount = 1;
|
|
numImages++;
|
|
|
|
if ( alphaHack ) {
|
|
path_set_extension( name, "_alpha.jpg" );
|
|
size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 );
|
|
if ( size > 0 ) {
|
|
unsigned char *pixels;
|
|
int width, height;
|
|
if ( LoadJPGBuff( buffer, size, &pixels, &width, &height ) == -1 ) {
|
|
if (pixels) {
|
|
// On error, LoadJPGBuff might store a pointer to the error message in pixels
|
|
Sys_Warning( "LoadJPGBuff %s %s\n", name, (unsigned char*) pixels );
|
|
}
|
|
} else {
|
|
if ( width == image->width && height == image->height ) {
|
|
for ( int i = 0; i < width * height; ++i )
|
|
image->pixels[4 * i + 3] = pixels[4 * i + 2]; // copy alpha from blue channel
|
|
}
|
|
free( pixels );
|
|
}
|
|
free( buffer );
|
|
}
|
|
}
|
|
|
|
/* return the image */
|
|
return image;
|
|
}
|