Add KTX and ETC1 texture support by @Triang3l
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.
This commit is contained in:
parent
d1c3c838c8
commit
44907db895
8
Makefile
8
Makefile
|
|
@ -583,6 +583,7 @@ $(INSTALLDIR)/q3map2.$(EXE): \
|
|||
tools/quake3/q3map2/vis.o \
|
||||
tools/quake3/q3map2/writebsp.o \
|
||||
libddslib.$(A) \
|
||||
libetclib.$(A) \
|
||||
libfilematch.$(A) \
|
||||
libl_net.$(A) \
|
||||
$(if $(findstring Win32,$(OS)),icons/q3map2.o,) \
|
||||
|
|
@ -814,6 +815,10 @@ libddslib.$(A): CPPFLAGS_EXTRA := -Ilibs
|
|||
libddslib.$(A): \
|
||||
libs/ddslib/ddslib.o \
|
||||
|
||||
libetclib.$(A): CPPFLAGS_EXTRA := -Ilibs
|
||||
libetclib.$(A): \
|
||||
libs/etclib.o \
|
||||
|
||||
$(INSTALLDIR)/q3data.$(EXE): LIBS_EXTRA := $(LIBS_XML) $(LIBS_GLIB) $(LIBS_ZLIB)
|
||||
$(INSTALLDIR)/q3data.$(EXE): CPPFLAGS_EXTRA := $(CPPFLAGS_XML) $(CPPFLAGS_GLIB) $(CPPFLAGS_ZLIB) -Itools/quake3/common -Ilibs -Iinclude
|
||||
$(INSTALLDIR)/q3data.$(EXE): \
|
||||
|
|
@ -839,6 +844,7 @@ $(INSTALLDIR)/q3data.$(EXE): \
|
|||
tools/quake3/q3data/stripper.o \
|
||||
tools/quake3/q3data/video.o \
|
||||
libfilematch.$(A) \
|
||||
libetclib.$(A) \
|
||||
libl_net.$(A) \
|
||||
libmathlib.$(A) \
|
||||
$(if $(findstring Win32,$(OS)),icons/q3data.o,) \
|
||||
|
|
@ -1039,9 +1045,11 @@ $(INSTALLDIR)/modules/image.$(DLL): \
|
|||
plugins/image/dds.o \
|
||||
plugins/image/image.o \
|
||||
plugins/image/jpeg.o \
|
||||
plugins/image/ktx.o \
|
||||
plugins/image/pcx.o \
|
||||
plugins/image/tga.o \
|
||||
libddslib.$(A) \
|
||||
libetclib.$(A) \
|
||||
|
||||
$(INSTALLDIR)/modules/imageq2.$(DLL): CPPFLAGS_EXTRA := -Ilibs -Iinclude
|
||||
$(INSTALLDIR)/modules/imageq2.$(DLL): \
|
||||
|
|
|
|||
|
|
@ -82,6 +82,13 @@ inline int16_t istream_read_int16_le( InputStreamType& istream ){
|
|||
return value;
|
||||
}
|
||||
|
||||
template<typename InputStreamType>
|
||||
inline int16_t istream_read_int16_be( InputStreamType& istream ){
|
||||
int16_t value;
|
||||
istream_read_big_endian( istream, value );
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename InputStreamType>
|
||||
inline uint16_t istream_read_uint16_le( InputStreamType& istream ){
|
||||
uint16_t value;
|
||||
|
|
@ -89,6 +96,13 @@ inline uint16_t istream_read_uint16_le( InputStreamType& istream ){
|
|||
return value;
|
||||
}
|
||||
|
||||
template<typename InputStreamType>
|
||||
inline uint16_t istream_read_uint16_be( InputStreamType& istream ){
|
||||
uint16_t value;
|
||||
istream_read_big_endian( istream, value );
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename InputStreamType>
|
||||
inline int32_t istream_read_int32_le( InputStreamType& istream ){
|
||||
int32_t value;
|
||||
|
|
@ -96,6 +110,13 @@ inline int32_t istream_read_int32_le( InputStreamType& istream ){
|
|||
return value;
|
||||
}
|
||||
|
||||
template<typename InputStreamType>
|
||||
inline int32_t istream_read_int32_be( InputStreamType& istream ){
|
||||
int32_t value;
|
||||
istream_read_big_endian( istream, value );
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename InputStreamType>
|
||||
inline uint32_t istream_read_uint32_le( InputStreamType& istream ){
|
||||
uint32_t value;
|
||||
|
|
@ -103,6 +124,13 @@ inline uint32_t istream_read_uint32_le( InputStreamType& istream ){
|
|||
return value;
|
||||
}
|
||||
|
||||
template<typename InputStreamType>
|
||||
inline uint32_t istream_read_uint32_be( InputStreamType& istream ){
|
||||
uint32_t value;
|
||||
istream_read_big_endian( istream, value );
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename InputStreamType>
|
||||
inline float istream_read_float32_le( InputStreamType& istream ){
|
||||
float value;
|
||||
|
|
@ -110,6 +138,13 @@ inline float istream_read_float32_le( InputStreamType& istream ){
|
|||
return value;
|
||||
}
|
||||
|
||||
template<typename InputStreamType>
|
||||
inline float istream_read_float32_be( InputStreamType& istream ){
|
||||
float value;
|
||||
istream_read_big_endian( istream, value );
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename InputStreamType>
|
||||
inline typename InputStreamType::byte_type istream_read_byte( InputStreamType& istream ){
|
||||
typename InputStreamType::byte_type b;
|
||||
|
|
|
|||
114
libs/etclib.c
Normal file
114
libs/etclib.c
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
// Copyright 2009 Google Inc.
|
||||
//
|
||||
// Based on the code from Android ETC1Util.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "etclib.h"
|
||||
|
||||
static void ETC_DecodeETC1SubBlock( byte *out, bool outRGBA, int r, int g, int b, int tableIndex, unsigned int low, bool second, bool flipped ){
|
||||
int baseX = 0, baseY = 0;
|
||||
const int modifierTable[] = {
|
||||
2, 8, -2, -8,
|
||||
5, 17, -5, -17,
|
||||
9, 29, -9, -29,
|
||||
13, 42, -13, -42,
|
||||
18, 60, -18, -60,
|
||||
24, 80, -24, -80,
|
||||
33, 106, -33, -106,
|
||||
47, 183, -47, -183
|
||||
};
|
||||
const int *table = modifierTable + tableIndex * 4;
|
||||
int i;
|
||||
|
||||
if ( second ) {
|
||||
if ( flipped ) {
|
||||
baseY = 2;
|
||||
}
|
||||
else {
|
||||
baseX = 2;
|
||||
}
|
||||
}
|
||||
|
||||
for ( i = 0; i < 8; i++ )
|
||||
{
|
||||
int x, y, k, delta;
|
||||
int qr, qg, qb;
|
||||
byte *q;
|
||||
|
||||
if ( flipped ) {
|
||||
x = baseX + ( i >> 1 );
|
||||
y = baseY + ( i & 1 );
|
||||
}
|
||||
else {
|
||||
x = baseX + ( i >> 2 );
|
||||
y = baseY + ( i & 3 );
|
||||
}
|
||||
k = y + ( x * 4 );
|
||||
delta = table[( ( low >> k ) & 1 ) | ( ( low >> ( k + 15 ) ) & 2 )];
|
||||
|
||||
qr = r + delta;
|
||||
qg = g + delta;
|
||||
qb = b + delta;
|
||||
if ( outRGBA ) {
|
||||
q = out + 4 * ( x + 4 * y );
|
||||
}
|
||||
else {
|
||||
q = out + 3 * ( x + 4 * y );
|
||||
}
|
||||
*( q++ ) = ( ( qr > 0 ) ? ( ( qr < 255 ) ? qr : 255 ) : 0 );
|
||||
*( q++ ) = ( ( qg > 0 ) ? ( ( qg < 255 ) ? qg : 255 ) : 0 );
|
||||
*( q++ ) = ( ( qb > 0 ) ? ( ( qb < 255 ) ? qb : 255 ) : 0 );
|
||||
if ( outRGBA ) {
|
||||
*( q++ ) = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ETC_DecodeETC1Block( const byte* in, byte* out, bool outRGBA ){
|
||||
const unsigned int high = ( in[0] << 24 ) | ( in[1] << 16 ) | ( in[2] << 8 ) | in[3];
|
||||
const unsigned int low = ( in[4] << 24 ) | ( in[5] << 16 ) | ( in[6] << 8 ) | in[7];
|
||||
int r1, r2, g1, g2, b1, b2;
|
||||
const bool flipped = ( ( high & 1 ) != 0 );
|
||||
|
||||
if ( high & 2 ) {
|
||||
int rBase, gBase, bBase;
|
||||
const int lookup[] = { 0, 1, 2, 3, -4, -3, -2, -1 };
|
||||
|
||||
rBase = ( high >> 27 ) & 31;
|
||||
r1 = ( rBase << 3 ) | ( rBase >> 2 );
|
||||
rBase = ( rBase + ( lookup[( high >> 24 ) & 7] ) ) & 31;
|
||||
r2 = ( rBase << 3 ) | ( rBase >> 2 );
|
||||
|
||||
gBase = ( high >> 19 ) & 31;
|
||||
g1 = ( gBase << 3 ) | ( gBase >> 2 );
|
||||
gBase = ( gBase + ( lookup[( high >> 16 ) & 7] ) ) & 31;
|
||||
g2 = ( gBase << 3 ) | ( gBase >> 2 );
|
||||
|
||||
bBase = ( high >> 11 ) & 31;
|
||||
b1 = ( bBase << 3 ) | ( bBase >> 2 );
|
||||
bBase = ( bBase + ( lookup[( high >> 8 ) & 7] ) ) & 31;
|
||||
b2 = ( bBase << 3 ) | ( bBase >> 2 );
|
||||
}
|
||||
else {
|
||||
r1 = ( ( high >> 24 ) & 0xf0 ) | ( ( high >> 28 ) & 0xf );
|
||||
r2 = ( ( high >> 20 ) & 0xf0 ) | ( ( high >> 24 ) & 0xf );
|
||||
g1 = ( ( high >> 16 ) & 0xf0 ) | ( ( high >> 20 ) & 0xf );
|
||||
g2 = ( ( high >> 12 ) & 0xf0 ) | ( ( high >> 16 ) & 0xf );
|
||||
b1 = ( ( high >> 8 ) & 0xf0 ) | ( ( high >> 12 ) & 0xf );
|
||||
b2 = ( ( high >> 4 ) & 0xf0 ) | ( ( high >> 8 ) & 0xf );
|
||||
}
|
||||
|
||||
ETC_DecodeETC1SubBlock( out, outRGBA, r1, g1, b1, ( high >> 5 ) & 7, low, false, flipped );
|
||||
ETC_DecodeETC1SubBlock( out, outRGBA, r2, g2, b2, ( high >> 2 ) & 7, low, true, flipped );
|
||||
}
|
||||
33
libs/etclib.h
Normal file
33
libs/etclib.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2009 Google Inc.
|
||||
//
|
||||
// Based on the code from Android ETC1Util.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef INCLUDED_ETCLIB_H
|
||||
#define INCLUDED_ETCLIB_H
|
||||
|
||||
#include "bytebool.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
void ETC_DecodeETC1Block( const byte* in, byte* out, bool outRGBA );
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -29,6 +29,7 @@
|
|||
#include "bmp.h"
|
||||
#include "pcx.h"
|
||||
#include "dds.h"
|
||||
#include "ktx.h"
|
||||
|
||||
|
||||
#include "modulesystem/singletonmodule.h"
|
||||
|
|
@ -137,6 +138,26 @@ typedef SingletonModule<ImageDDSAPI, ImageDependencies> ImageDDSModule;
|
|||
ImageDDSModule g_ImageDDSModule;
|
||||
|
||||
|
||||
class ImageKTXAPI
|
||||
{
|
||||
_QERPlugImageTable m_imagektx;
|
||||
public:
|
||||
typedef _QERPlugImageTable Type;
|
||||
STRING_CONSTANT( Name, "ktx" );
|
||||
|
||||
ImageKTXAPI(){
|
||||
m_imagektx.loadImage = LoadKTX;
|
||||
}
|
||||
_QERPlugImageTable* getTable(){
|
||||
return &m_imagektx;
|
||||
}
|
||||
};
|
||||
|
||||
typedef SingletonModule<ImageKTXAPI, ImageDependencies> ImageKTXModule;
|
||||
|
||||
ImageKTXModule g_ImageKTXModule;
|
||||
|
||||
|
||||
extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules( ModuleServer& server ){
|
||||
initialiseModule( server );
|
||||
|
||||
|
|
@ -145,4 +166,5 @@ extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules( ModuleServer& server
|
|||
g_ImageBMPModule.selfRegister();
|
||||
g_ImagePCXModule.selfRegister();
|
||||
g_ImageDDSModule.selfRegister();
|
||||
g_ImageKTXModule.selfRegister();
|
||||
}
|
||||
|
|
|
|||
416
plugins/image/ktx.cpp
Normal file
416
plugins/image/ktx.cpp
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
/*
|
||||
Copyright (C) 2015, SiPlus, Chasseur de bots.
|
||||
All Rights Reserved.
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
#include "ktx.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "bytestreamutils.h"
|
||||
#include "etclib.h"
|
||||
#include "ifilesystem.h"
|
||||
#include "imagelib.h"
|
||||
|
||||
|
||||
const int KTX_TYPE_UNSIGNED_BYTE = 0x1401;
|
||||
const int KTX_TYPE_UNSIGNED_SHORT_4_4_4_4 = 0x8033;
|
||||
const int KTX_TYPE_UNSIGNED_SHORT_5_5_5_1 = 0x8034;
|
||||
const int KTX_TYPE_UNSIGNED_SHORT_5_6_5 = 0x8363;
|
||||
|
||||
const int KTX_FORMAT_ALPHA = 0x1906;
|
||||
const int KTX_FORMAT_RGB = 0x1907;
|
||||
const int KTX_FORMAT_RGBA = 0x1908;
|
||||
const int KTX_FORMAT_LUMINANCE = 0x1909;
|
||||
const int KTX_FORMAT_LUMINANCE_ALPHA = 0x190A;
|
||||
const int KTX_FORMAT_BGR = 0x80E0;
|
||||
const int KTX_FORMAT_BGRA = 0x80E1;
|
||||
|
||||
const int KTX_FORMAT_ETC1_RGB8 = 0x8D64;
|
||||
|
||||
class KTX_Decoder
|
||||
{
|
||||
public:
|
||||
virtual ~KTX_Decoder() = default;
|
||||
virtual void Decode( PointerInputStream& istream, byte* out ) const = 0;
|
||||
virtual unsigned int GetPixelSize() const = 0;
|
||||
};
|
||||
|
||||
class KTX_Decoder_A8 final : public KTX_Decoder
|
||||
{
|
||||
public:
|
||||
void Decode( PointerInputStream& istream, byte* out ) const override {
|
||||
out[0] = out[1] = out[2] = 0;
|
||||
out[3] = istream_read_byte( istream );
|
||||
}
|
||||
unsigned int GetPixelSize() const override {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
class KTX_Decoder_RGB8 final : public KTX_Decoder
|
||||
{
|
||||
public:
|
||||
void Decode( PointerInputStream& istream, byte* out ) const override {
|
||||
istream.read( out, 3 );
|
||||
out[3] = 255;
|
||||
}
|
||||
unsigned int GetPixelSize() const override {
|
||||
return 3;
|
||||
}
|
||||
};
|
||||
|
||||
class KTX_Decoder_RGBA8 final : public KTX_Decoder
|
||||
{
|
||||
public:
|
||||
void Decode( PointerInputStream& istream, byte* out ) const override {
|
||||
istream.read( out, 4 );
|
||||
}
|
||||
unsigned int GetPixelSize() const override {
|
||||
return 4;
|
||||
}
|
||||
};
|
||||
|
||||
class KTX_Decoder_L8 final : public KTX_Decoder
|
||||
{
|
||||
public:
|
||||
void Decode( PointerInputStream& istream, byte* out ) const override {
|
||||
byte l = istream_read_byte( istream );
|
||||
out[0] = out[1] = out[2] = l;
|
||||
out[3] = 255;
|
||||
}
|
||||
unsigned int GetPixelSize() const override {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
class KTX_Decoder_LA8 final : public KTX_Decoder
|
||||
{
|
||||
public:
|
||||
void Decode( PointerInputStream& istream, byte* out ) const override {
|
||||
byte la[2];
|
||||
istream.read( la, 2 );
|
||||
out[0] = out[1] = out[2] = la[0];
|
||||
out[3] = la[1];
|
||||
}
|
||||
unsigned int GetPixelSize() const override {
|
||||
return 2;
|
||||
}
|
||||
};
|
||||
|
||||
class KTX_Decoder_BGR8 final : public KTX_Decoder
|
||||
{
|
||||
public:
|
||||
void Decode( PointerInputStream& istream, byte* out ) const override {
|
||||
byte bgr[3];
|
||||
istream.read( bgr, 3 );
|
||||
out[0] = bgr[2];
|
||||
out[1] = bgr[1];
|
||||
out[2] = bgr[0];
|
||||
out[3] = 255;
|
||||
}
|
||||
unsigned int GetPixelSize() const override {
|
||||
return 3;
|
||||
}
|
||||
};
|
||||
|
||||
class KTX_Decoder_BGRA8 final : public KTX_Decoder
|
||||
{
|
||||
public:
|
||||
void Decode( PointerInputStream& istream, byte* out ) const override {
|
||||
byte bgra[4];
|
||||
istream.read( bgra, 4 );
|
||||
out[0] = bgra[2];
|
||||
out[1] = bgra[1];
|
||||
out[2] = bgra[0];
|
||||
out[3] = bgra[3];
|
||||
}
|
||||
unsigned int GetPixelSize() const override {
|
||||
return 4;
|
||||
}
|
||||
};
|
||||
|
||||
class KTX_Decoder_RGBA4 final : public KTX_Decoder
|
||||
{
|
||||
protected:
|
||||
const bool m_bigEndian;
|
||||
public:
|
||||
KTX_Decoder_RGBA4( bool bigEndian ) : m_bigEndian( bigEndian ){}
|
||||
void Decode( PointerInputStream& istream, byte* out ) const override {
|
||||
uint16_t rgba;
|
||||
if ( m_bigEndian ) {
|
||||
rgba = istream_read_uint16_be( istream );
|
||||
}
|
||||
else {
|
||||
rgba = istream_read_uint16_le( istream );
|
||||
}
|
||||
int r = ( rgba >> 12 ) & 0xf;
|
||||
int g = ( rgba >> 8 ) & 0xf;
|
||||
int b = ( rgba >> 4 ) & 0xf;
|
||||
int a = rgba & 0xf;
|
||||
out[0] = ( r << 4 ) | r;
|
||||
out[1] = ( g << 4 ) | g;
|
||||
out[2] = ( b << 4 ) | b;
|
||||
out[3] = ( a << 4 ) | a;
|
||||
}
|
||||
unsigned int GetPixelSize() const override {
|
||||
return 2;
|
||||
}
|
||||
};
|
||||
|
||||
class KTX_Decoder_RGBA5 final : public KTX_Decoder
|
||||
{
|
||||
protected:
|
||||
const bool m_bigEndian;
|
||||
public:
|
||||
KTX_Decoder_RGBA5( bool bigEndian ) : m_bigEndian( bigEndian ){}
|
||||
void Decode( PointerInputStream& istream, byte* out ) const override {
|
||||
uint16_t rgba;
|
||||
if ( m_bigEndian ) {
|
||||
rgba = istream_read_uint16_be( istream );
|
||||
}
|
||||
else {
|
||||
rgba = istream_read_uint16_le( istream );
|
||||
}
|
||||
int r = ( rgba >> 11 ) & 0x1f;
|
||||
int g = ( rgba >> 6 ) & 0x1f;
|
||||
int b = ( rgba >> 1 ) & 0x1f;
|
||||
out[0] = ( r << 3 ) | ( r >> 2 );
|
||||
out[1] = ( g << 3 ) | ( g >> 2 );
|
||||
out[2] = ( b << 3 ) | ( b >> 2 );
|
||||
out[3] = ( rgba & 1 ) * 255;
|
||||
}
|
||||
unsigned int GetPixelSize() const override {
|
||||
return 2;
|
||||
}
|
||||
};
|
||||
|
||||
class KTX_Decoder_RGB5 final : public KTX_Decoder
|
||||
{
|
||||
protected:
|
||||
const bool m_bigEndian;
|
||||
public:
|
||||
KTX_Decoder_RGB5( bool bigEndian ) : m_bigEndian( bigEndian ){}
|
||||
void Decode( PointerInputStream& istream, byte* out ) const override {
|
||||
uint16_t rgb;
|
||||
if ( m_bigEndian ) {
|
||||
rgb = istream_read_uint16_be( istream );
|
||||
}
|
||||
else {
|
||||
rgb = istream_read_uint16_le( istream );
|
||||
}
|
||||
int r = ( rgb >> 11 ) & 0x1f;
|
||||
int g = ( rgb >> 5 ) & 0x3f;
|
||||
int b = rgb & 0x1f;
|
||||
out[0] = ( r << 3 ) | ( r >> 2 );
|
||||
out[1] = ( g << 2 ) | ( g >> 4 );
|
||||
out[2] = ( b << 3 ) | ( b >> 2 );
|
||||
out[3] = 255;
|
||||
}
|
||||
unsigned int GetPixelSize() const override {
|
||||
return 2;
|
||||
}
|
||||
};
|
||||
|
||||
static void KTX_DecodeETC1( PointerInputStream& istream, Image& image ){
|
||||
unsigned int width = image.getWidth(), height = image.getHeight();
|
||||
unsigned int stride = width * 4;
|
||||
byte* pixbuf = image.getRGBAPixels();
|
||||
byte etc[8], rgba[64];
|
||||
|
||||
for ( unsigned int y = 0; y < height; y += 4, pixbuf += stride * 4 )
|
||||
{
|
||||
unsigned int blockrows = height - y;
|
||||
if ( blockrows > 4 ) {
|
||||
blockrows = 4;
|
||||
}
|
||||
|
||||
byte* p = pixbuf;
|
||||
for ( unsigned int x = 0; x < width; x += 4, p += 16 )
|
||||
{
|
||||
istream.read( etc, 8 );
|
||||
ETC_DecodeETC1Block( etc, rgba, true );
|
||||
|
||||
unsigned int blockrowsize = width - x;
|
||||
if ( blockrowsize > 4 ) {
|
||||
blockrowsize = 4;
|
||||
}
|
||||
blockrowsize *= 4;
|
||||
for ( unsigned int blockrow = 0; blockrow < blockrows; blockrow++ )
|
||||
{
|
||||
memcpy( p + blockrow * stride, rgba + blockrow * 16, blockrowsize );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Image* LoadKTXBuff( PointerInputStream& istream ){
|
||||
byte identifier[12];
|
||||
istream.read( identifier, 12 );
|
||||
if ( memcmp( identifier, "\xABKTX 11\xBB\r\n\x1A\n", 12 ) ) {
|
||||
globalErrorStream() << "LoadKTX: Image has the wrong identifier\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
const bool bigEndian = ( istream_read_uint32_le( istream ) == 0x01020304 );
|
||||
|
||||
unsigned int type;
|
||||
if ( bigEndian ) {
|
||||
type = istream_read_uint32_be( istream );
|
||||
}
|
||||
else {
|
||||
type = istream_read_uint32_le( istream );
|
||||
}
|
||||
|
||||
// For compressed textures, the format is in glInternalFormat.
|
||||
// For uncompressed textures, it's in glBaseInternalFormat.
|
||||
istream.seek( ( type ? 3 : 2 ) * sizeof( uint32_t ) );
|
||||
unsigned int format;
|
||||
if ( bigEndian ) {
|
||||
format = istream_read_uint32_be( istream );
|
||||
}
|
||||
else {
|
||||
format = istream_read_uint32_le( istream );
|
||||
}
|
||||
if ( !type ) {
|
||||
istream.seek( sizeof( uint32_t ) );
|
||||
}
|
||||
|
||||
unsigned int width, height;
|
||||
if ( bigEndian ) {
|
||||
width = istream_read_uint32_be( istream );
|
||||
height = istream_read_uint32_be( istream );
|
||||
}
|
||||
else {
|
||||
width = istream_read_uint32_le( istream );
|
||||
height = istream_read_uint32_le( istream );
|
||||
}
|
||||
if ( !width ) {
|
||||
globalErrorStream() << "LoadKTX: Image has zero width\n";
|
||||
return 0;
|
||||
}
|
||||
if ( !height ) {
|
||||
height = 1;
|
||||
}
|
||||
|
||||
// Skip the key/values and load the first 2D image in the texture.
|
||||
// Since KTXorientation is only a hint and has no effect on the texture data and coordinates, it must be ignored.
|
||||
istream.seek( 4 * sizeof( uint32_t ) );
|
||||
unsigned int bytesOfKeyValueData;
|
||||
if ( bigEndian ) {
|
||||
bytesOfKeyValueData = istream_read_uint32_be( istream );
|
||||
}
|
||||
else {
|
||||
bytesOfKeyValueData = istream_read_uint32_le( istream );
|
||||
}
|
||||
istream.seek( bytesOfKeyValueData + sizeof( uint32_t ) );
|
||||
|
||||
RGBAImage* image = new RGBAImage( width, height );
|
||||
|
||||
if ( type ) {
|
||||
KTX_Decoder* decoder = NULL;
|
||||
switch ( type )
|
||||
{
|
||||
case KTX_TYPE_UNSIGNED_BYTE:
|
||||
switch ( format )
|
||||
{
|
||||
case KTX_FORMAT_ALPHA:
|
||||
decoder = new KTX_Decoder_A8();
|
||||
break;
|
||||
case KTX_FORMAT_RGB:
|
||||
decoder = new KTX_Decoder_RGB8();
|
||||
break;
|
||||
case KTX_FORMAT_RGBA:
|
||||
decoder = new KTX_Decoder_RGBA8();
|
||||
break;
|
||||
case KTX_FORMAT_LUMINANCE:
|
||||
decoder = new KTX_Decoder_L8();
|
||||
break;
|
||||
case KTX_FORMAT_LUMINANCE_ALPHA:
|
||||
decoder = new KTX_Decoder_LA8();
|
||||
break;
|
||||
case KTX_FORMAT_BGR:
|
||||
decoder = new KTX_Decoder_BGR8();
|
||||
break;
|
||||
case KTX_FORMAT_BGRA:
|
||||
decoder = new KTX_Decoder_BGRA8();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case KTX_TYPE_UNSIGNED_SHORT_4_4_4_4:
|
||||
if ( format == KTX_FORMAT_RGBA ) {
|
||||
decoder = new KTX_Decoder_RGBA4( bigEndian );
|
||||
}
|
||||
break;
|
||||
case KTX_TYPE_UNSIGNED_SHORT_5_5_5_1:
|
||||
if ( format == KTX_FORMAT_RGBA ) {
|
||||
decoder = new KTX_Decoder_RGBA5( bigEndian );
|
||||
}
|
||||
break;
|
||||
case KTX_TYPE_UNSIGNED_SHORT_5_6_5:
|
||||
if ( format == KTX_FORMAT_RGB ) {
|
||||
decoder = new KTX_Decoder_RGB5( bigEndian );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if ( !decoder ) {
|
||||
globalErrorStream() << "LoadKTX: Image has an unsupported pixel type " << type << " or format " << format << "\n";
|
||||
image->release();
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int inRowLength = width * decoder->GetPixelSize();
|
||||
unsigned int inPadding = ( ( inRowLength + 3 ) & ~3 ) - inRowLength;
|
||||
byte* out = image->getRGBAPixels();
|
||||
for ( unsigned int y = 0; y < height; y++ )
|
||||
{
|
||||
for ( unsigned int x = 0; x < width; x++, out += 4 )
|
||||
{
|
||||
decoder->Decode( istream, out );
|
||||
}
|
||||
|
||||
if ( inPadding ) {
|
||||
istream.seek( inPadding );
|
||||
}
|
||||
}
|
||||
|
||||
delete decoder;
|
||||
}
|
||||
else {
|
||||
switch ( format )
|
||||
{
|
||||
case KTX_FORMAT_ETC1_RGB8:
|
||||
KTX_DecodeETC1( istream, *image );
|
||||
break;
|
||||
default:
|
||||
globalErrorStream() << "LoadKTX: Image has an unsupported compressed format " << format << "\n";
|
||||
image->release();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
Image* LoadKTX( ArchiveFile& file ){
|
||||
ScopedArchiveBuffer buffer( file );
|
||||
PointerInputStream istream( buffer.buffer );
|
||||
return LoadKTXBuff( istream );
|
||||
}
|
||||
30
plugins/image/ktx.h
Normal file
30
plugins/image/ktx.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
Copyright (C) 2015, SiPlus, Chasseur de bots.
|
||||
All Rights Reserved.
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
#if !defined( INCLUDED_KTX_H )
|
||||
#define INCLUDED_KTX_H
|
||||
|
||||
class Image;
|
||||
class ArchiveFile;
|
||||
|
||||
Image* LoadKTX( ArchiveFile& file );
|
||||
|
||||
#endif
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
#include "inout.h"
|
||||
#include "cmdlib.h"
|
||||
#include "etclib.h"
|
||||
#include "imagelib.h"
|
||||
#include "vfs.h"
|
||||
|
||||
|
|
@ -1220,3 +1221,300 @@ void Load32BitImage( const char *name, unsigned **pixels, int *width, int *heig
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
============================================================================
|
||||
KHRONOS TEXTURE
|
||||
============================================================================
|
||||
*/
|
||||
|
||||
|
||||
#define KTX_UINT32_LE( buf ) ( ( unsigned int )( (buf)[0] | ( (buf)[1] << 8 ) | ( (buf)[2] << 16 ) | ( (buf)[3] << 24 ) ) )
|
||||
#define KTX_UINT32_BE( buf ) ( ( unsigned int )( (buf)[3] | ( (buf)[2] << 8 ) | ( (buf)[1] << 16 ) | ( (buf)[0] << 24 ) ) )
|
||||
|
||||
#define KTX_TYPE_UNSIGNED_BYTE 0x1401
|
||||
#define KTX_TYPE_UNSIGNED_SHORT_4_4_4_4 0x8033
|
||||
#define KTX_TYPE_UNSIGNED_SHORT_5_5_5_1 0x8034
|
||||
#define KTX_TYPE_UNSIGNED_SHORT_5_6_5 0x8363
|
||||
|
||||
#define KTX_FORMAT_ALPHA 0x1906
|
||||
#define KTX_FORMAT_RGB 0x1907
|
||||
#define KTX_FORMAT_RGBA 0x1908
|
||||
#define KTX_FORMAT_LUMINANCE 0x1909
|
||||
#define KTX_FORMAT_LUMINANCE_ALPHA 0x190A
|
||||
#define KTX_FORMAT_BGR 0x80E0
|
||||
#define KTX_FORMAT_BGRA 0x80E1
|
||||
|
||||
#define KTX_FORMAT_ETC1_RGB8 0x8D64
|
||||
|
||||
static void KTX_DecodeA8( const byte *in, bool bigEndian, byte *out ){
|
||||
out[0] = out[1] = out[2] = 0;
|
||||
out[3] = in[0];
|
||||
}
|
||||
|
||||
static void KTX_DecodeRGB8( const byte *in, bool bigEndian, byte *out ){
|
||||
out[0] = in[0];
|
||||
out[1] = in[1];
|
||||
out[2] = in[2];
|
||||
out[3] = 255;
|
||||
}
|
||||
|
||||
static void KTX_DecodeRGBA8( const byte *in, bool bigEndian, byte *out ){
|
||||
out[0] = in[0];
|
||||
out[1] = in[1];
|
||||
out[2] = in[2];
|
||||
out[3] = in[3];
|
||||
}
|
||||
|
||||
static void KTX_DecodeL8( const byte *in, bool bigEndian, byte *out ){
|
||||
out[0] = out[1] = out[2] = in[0];
|
||||
out[3] = 255;
|
||||
}
|
||||
|
||||
static void KTX_DecodeLA8( const byte *in, bool bigEndian, byte *out ){
|
||||
out[0] = out[1] = out[2] = in[0];
|
||||
out[3] = in[1];
|
||||
}
|
||||
|
||||
static void KTX_DecodeBGR8( const byte *in, bool bigEndian, byte *out ){
|
||||
out[0] = in[2];
|
||||
out[1] = in[1];
|
||||
out[2] = in[0];
|
||||
out[3] = 255;
|
||||
}
|
||||
|
||||
static void KTX_DecodeBGRA8( const byte *in, bool bigEndian, byte *out ){
|
||||
out[0] = in[2];
|
||||
out[1] = in[1];
|
||||
out[2] = in[0];
|
||||
out[3] = in[3];
|
||||
}
|
||||
|
||||
static void KTX_DecodeRGBA4( const byte *in, bool bigEndian, byte *out ){
|
||||
unsigned short rgba;
|
||||
int r, g, b, a;
|
||||
|
||||
if ( bigEndian ) {
|
||||
rgba = ( in[0] << 8 ) | in[1];
|
||||
}
|
||||
else {
|
||||
rgba = ( in[1] << 8 ) | in[0];
|
||||
}
|
||||
|
||||
r = ( rgba >> 12 ) & 0xf;
|
||||
g = ( rgba >> 8 ) & 0xf;
|
||||
b = ( rgba >> 4 ) & 0xf;
|
||||
a = rgba & 0xf;
|
||||
out[0] = ( r << 4 ) | r;
|
||||
out[1] = ( g << 4 ) | g;
|
||||
out[2] = ( b << 4 ) | b;
|
||||
out[3] = ( a << 4 ) | a;
|
||||
}
|
||||
|
||||
static void KTX_DecodeRGBA5( const byte *in, bool bigEndian, byte *out ){
|
||||
unsigned short rgba;
|
||||
int r, g, b;
|
||||
|
||||
if ( bigEndian ) {
|
||||
rgba = ( in[0] << 8 ) | in[1];
|
||||
}
|
||||
else {
|
||||
rgba = ( in[1] << 8 ) | in[0];
|
||||
}
|
||||
|
||||
r = ( rgba >> 11 ) & 0x1f;
|
||||
g = ( rgba >> 6 ) & 0x1f;
|
||||
b = ( rgba >> 1 ) & 0x1f;
|
||||
out[0] = ( r << 3 ) | ( r >> 2 );
|
||||
out[1] = ( g << 3 ) | ( g >> 2 );
|
||||
out[2] = ( b << 3 ) | ( b >> 2 );
|
||||
out[3] = ( rgba & 1 ) * 255;
|
||||
}
|
||||
|
||||
static void KTX_DecodeRGB5( const byte *in, bool bigEndian, byte *out ){
|
||||
unsigned short rgba;
|
||||
int r, g, b;
|
||||
|
||||
if ( bigEndian ) {
|
||||
rgba = ( in[0] << 8 ) | in[1];
|
||||
}
|
||||
else {
|
||||
rgba = ( in[1] << 8 ) | in[0];
|
||||
}
|
||||
|
||||
r = ( rgba >> 11 ) & 0x1f;
|
||||
g = ( rgba >> 5 ) & 0x3f;
|
||||
b = rgba & 0x1f;
|
||||
out[0] = ( r << 3 ) | ( r >> 2 );
|
||||
out[1] = ( g << 2 ) | ( g >> 4 );
|
||||
out[2] = ( b << 3 ) | ( b >> 2 );
|
||||
out[3] = 255;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned int type;
|
||||
unsigned int format;
|
||||
unsigned int pixelSize;
|
||||
void ( *decode )( const byte *in, bool bigEndian, byte *out );
|
||||
} KTX_UncompressedFormat_t;
|
||||
|
||||
static const KTX_UncompressedFormat_t KTX_UncompressedFormats[] =
|
||||
{
|
||||
{ KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_ALPHA, 1, KTX_DecodeA8 },
|
||||
{ KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_RGB, 3, KTX_DecodeRGB8 },
|
||||
{ KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_RGBA, 4, KTX_DecodeRGBA8 },
|
||||
{ KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_LUMINANCE, 1, KTX_DecodeL8 },
|
||||
{ KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_LUMINANCE_ALPHA, 2, KTX_DecodeLA8 },
|
||||
{ KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_BGR, 3, KTX_DecodeBGR8 },
|
||||
{ KTX_TYPE_UNSIGNED_BYTE, KTX_FORMAT_BGRA, 4, KTX_DecodeBGRA8 },
|
||||
{ KTX_TYPE_UNSIGNED_SHORT_4_4_4_4, KTX_FORMAT_RGBA, 2, KTX_DecodeRGBA4 },
|
||||
{ KTX_TYPE_UNSIGNED_SHORT_5_5_5_1, KTX_FORMAT_RGBA, 2, KTX_DecodeRGBA5 },
|
||||
{ KTX_TYPE_UNSIGNED_SHORT_5_6_5, KTX_FORMAT_RGB, 2, KTX_DecodeRGB5 },
|
||||
{ 0, 0, 0, NULL }
|
||||
};
|
||||
|
||||
static bool KTX_DecodeETC1( const byte* in, size_t inSize, unsigned int width, unsigned int height, byte* out ){
|
||||
unsigned int y, stride = width * 4;
|
||||
byte rgba[64];
|
||||
|
||||
if ( inSize < ( ( ( ( width + 3 ) & ~3 ) * ( ( height + 3 ) & ~3 ) ) >> 1 ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for ( y = 0; y < height; y += 4, out += stride * 4 )
|
||||
{
|
||||
byte *p;
|
||||
unsigned int x, blockrows;
|
||||
|
||||
blockrows = height - y;
|
||||
if ( blockrows > 4 ) {
|
||||
blockrows = 4;
|
||||
}
|
||||
|
||||
p = out;
|
||||
for ( x = 0; x < width; x += 4, p += 16 )
|
||||
{
|
||||
unsigned int blockrowsize, blockrow;
|
||||
|
||||
ETC_DecodeETC1Block( in, rgba, true );
|
||||
in += 8;
|
||||
|
||||
blockrowsize = width - x;
|
||||
if ( blockrowsize > 4 ) {
|
||||
blockrowsize = 4;
|
||||
}
|
||||
blockrowsize *= 4;
|
||||
for ( blockrow = 0; blockrow < blockrows; blockrow++ )
|
||||
{
|
||||
memcpy( p + blockrow * stride, rgba + blockrow * 16, blockrowsize );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define KTX_HEADER_UINT32( buf ) ( bigEndian ? KTX_UINT32_BE( buf ) : KTX_UINT32_LE( buf ) )
|
||||
|
||||
void LoadKTXBufferFirstImage( const byte *buffer, size_t bufSize, byte **pic, int *picWidth, int *picHeight ){
|
||||
unsigned int type, format, width, height, imageOffset;
|
||||
byte *pixels;
|
||||
|
||||
if ( bufSize < 64 ) {
|
||||
Error( "LoadKTX: Image doesn't have a header" );
|
||||
}
|
||||
|
||||
if ( memcmp( buffer, "\xABKTX 11\xBB\r\n\x1A\n", 12 ) ) {
|
||||
Error( "LoadKTX: Image has the wrong identifier" );
|
||||
}
|
||||
|
||||
const bool bigEndian = ( buffer[4] == 4 );
|
||||
|
||||
type = KTX_HEADER_UINT32( buffer + 16 );
|
||||
if ( type ) {
|
||||
format = KTX_HEADER_UINT32( buffer + 32 );
|
||||
}
|
||||
else {
|
||||
format = KTX_HEADER_UINT32( buffer + 28 );
|
||||
}
|
||||
|
||||
width = KTX_HEADER_UINT32( buffer + 36 );
|
||||
height = KTX_HEADER_UINT32( buffer + 40 );
|
||||
if ( !width ) {
|
||||
Error( "LoadKTX: Image has zero width" );
|
||||
}
|
||||
if ( !height ) {
|
||||
height = 1;
|
||||
}
|
||||
if ( picWidth ) {
|
||||
*picWidth = width;
|
||||
}
|
||||
if ( picHeight ) {
|
||||
*picHeight = height;
|
||||
}
|
||||
|
||||
imageOffset = 64 + KTX_HEADER_UINT32( buffer + 60 ) + 4;
|
||||
if ( bufSize < imageOffset ) {
|
||||
Error( "LoadKTX: No image in the file" );
|
||||
}
|
||||
buffer += imageOffset;
|
||||
bufSize -= imageOffset;
|
||||
|
||||
pixels = safe_malloc( width * height * 4 );
|
||||
*pic = pixels;
|
||||
|
||||
if ( type ) {
|
||||
const KTX_UncompressedFormat_t *ktxFormat = KTX_UncompressedFormats;
|
||||
unsigned int pixelSize;
|
||||
unsigned int inRowLength, inPadding;
|
||||
unsigned int y;
|
||||
|
||||
while ( ktxFormat->type )
|
||||
{
|
||||
if ( ktxFormat->type == type && ktxFormat->format == format ) {
|
||||
break;
|
||||
}
|
||||
ktxFormat++;
|
||||
}
|
||||
if ( !ktxFormat->type ) {
|
||||
Error( "LoadKTX: Image has an unsupported pixel type 0x%X or format 0x%X", type, format );
|
||||
}
|
||||
|
||||
pixelSize = ktxFormat->pixelSize;
|
||||
inRowLength = width * pixelSize;
|
||||
inPadding = ( ( inRowLength + 3 ) & ~3 ) - inRowLength;
|
||||
|
||||
if ( bufSize < height * ( inRowLength + inPadding ) ) {
|
||||
Error( "LoadKTX: Image is truncated" );
|
||||
}
|
||||
|
||||
for ( y = 0; y < height; y++ )
|
||||
{
|
||||
unsigned int x;
|
||||
for ( x = 0; x < width; x++, buffer += pixelSize, pixels += 4 )
|
||||
{
|
||||
ktxFormat->decode( buffer, bigEndian, pixels );
|
||||
}
|
||||
buffer += inPadding;
|
||||
}
|
||||
}
|
||||
else {
|
||||
bool decoded = false;
|
||||
|
||||
switch ( format )
|
||||
{
|
||||
case KTX_FORMAT_ETC1_RGB8:
|
||||
decoded = KTX_DecodeETC1( buffer, bufSize, width, height, pixels );
|
||||
break;
|
||||
default:
|
||||
Error( "LoadKTX: Image has an unsupported compressed format format 0x%X", format );
|
||||
break;
|
||||
}
|
||||
|
||||
if ( !decoded ) {
|
||||
Error( "LoadKTX: Image is truncated" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,3 +43,5 @@ void WriteTGAGray( const char *filename, byte *data, int width, int height );
|
|||
int LoadJPGBuff( void *src_buffer, int src_size, unsigned char **pic, int *width, int *height );
|
||||
|
||||
void Load32BitImage( const char *name, unsigned **pixels, int *width, int *height );
|
||||
|
||||
void LoadKTXBufferFirstImage( const byte *buffer, size_t bufSize, byte **pic, int *picWidth, int *picHeight );
|
||||
|
|
|
|||
|
|
@ -397,6 +397,15 @@ image_t *ImageLoad( const char *filename ){
|
|||
}
|
||||
#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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user