2020-11-17 11:16:16 +00:00
|
|
|
/*
|
|
|
|
Copyright (C) 1999-2006 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
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "textures.h"
|
|
|
|
|
|
|
|
#include "debugging/debugging.h"
|
|
|
|
#include "warnings.h"
|
|
|
|
|
|
|
|
#include "itextures.h"
|
|
|
|
#include "igl.h"
|
|
|
|
#include "preferencesystem.h"
|
|
|
|
#include "qgl.h"
|
|
|
|
|
|
|
|
#include "texturelib.h"
|
|
|
|
#include "container/hashfunc.h"
|
|
|
|
#include "container/cache.h"
|
|
|
|
#include "generic/callback.h"
|
|
|
|
#include "stringio.h"
|
|
|
|
|
|
|
|
#include "image.h"
|
|
|
|
#include "texmanip.h"
|
|
|
|
#include "preferences.h"
|
|
|
|
|
|
|
|
|
|
|
|
enum ETexturesMode {
|
|
|
|
eTextures_NEAREST = 0,
|
|
|
|
eTextures_NEAREST_MIPMAP_NEAREST = 1,
|
|
|
|
eTextures_NEAREST_MIPMAP_LINEAR = 2,
|
|
|
|
eTextures_LINEAR = 3,
|
|
|
|
eTextures_LINEAR_MIPMAP_NEAREST = 4,
|
|
|
|
eTextures_LINEAR_MIPMAP_LINEAR = 5,
|
|
|
|
eTextures_MAX_ANISOTROPY = 6,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum TextureCompressionFormat {
|
|
|
|
TEXTURECOMPRESSION_NONE = 0,
|
|
|
|
TEXTURECOMPRESSION_RGBA = 1,
|
|
|
|
TEXTURECOMPRESSION_RGBA_S3TC_DXT1 = 2,
|
|
|
|
TEXTURECOMPRESSION_RGBA_S3TC_DXT3 = 3,
|
|
|
|
TEXTURECOMPRESSION_RGBA_S3TC_DXT5 = 4,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct texture_globals_t {
|
|
|
|
// RIANT
|
|
|
|
// texture compression format
|
|
|
|
TextureCompressionFormat m_nTextureCompressionFormat;
|
|
|
|
|
|
|
|
float fGamma;
|
|
|
|
|
|
|
|
bool bTextureCompressionSupported; // is texture compression supported by hardware?
|
|
|
|
GLint texture_components;
|
|
|
|
|
|
|
|
// temporary values that should be initialised only once at run-time
|
|
|
|
bool m_bOpenGLCompressionSupported;
|
|
|
|
bool m_bS3CompressionSupported;
|
|
|
|
|
|
|
|
texture_globals_t(GLint components) :
|
|
|
|
m_nTextureCompressionFormat(TEXTURECOMPRESSION_NONE),
|
|
|
|
fGamma(1.0f),
|
|
|
|
bTextureCompressionSupported(false),
|
|
|
|
texture_components(components),
|
|
|
|
m_bOpenGLCompressionSupported(false),
|
|
|
|
m_bS3CompressionSupported(false)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
texture_globals_t g_texture_globals(GL_RGBA);
|
|
|
|
|
|
|
|
void SetTexParameters(ETexturesMode mode)
|
|
|
|
{
|
|
|
|
float maxAniso = QGL_maxTextureAnisotropy();
|
|
|
|
if (maxAniso > 1) {
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f);
|
|
|
|
} else if (mode == eTextures_MAX_ANISOTROPY) {
|
|
|
|
mode = eTextures_LINEAR_MIPMAP_LINEAR;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (mode) {
|
|
|
|
case eTextures_NEAREST:
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
|
|
break;
|
|
|
|
case eTextures_NEAREST_MIPMAP_NEAREST:
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
|
|
break;
|
|
|
|
case eTextures_NEAREST_MIPMAP_LINEAR:
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
|
|
break;
|
|
|
|
case eTextures_LINEAR:
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
|
|
break;
|
|
|
|
case eTextures_LINEAR_MIPMAP_NEAREST:
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
|
|
break;
|
|
|
|
case eTextures_LINEAR_MIPMAP_LINEAR:
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
|
|
break;
|
|
|
|
case eTextures_MAX_ANISOTROPY:
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAniso);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
globalOutputStream() << "invalid texture mode\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ETexturesMode g_texture_mode = eTextures_NEAREST_MIPMAP_NEAREST;
|
|
|
|
|
|
|
|
|
|
|
|
byte g_gammatable[256];
|
|
|
|
|
|
|
|
void ResampleGamma(float fGamma)
|
|
|
|
{
|
|
|
|
int i, inf;
|
|
|
|
if (fGamma == 1.0) {
|
|
|
|
for (i = 0; i < 256; i++) {
|
|
|
|
g_gammatable[i] = i;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (i = 0; i < 256; i++) {
|
|
|
|
inf = (int) (255 * pow(static_cast<double>((i + 0.5) / 255.5 ), static_cast<double>( fGamma )) + 0.5);
|
|
|
|
if (inf < 0) {
|
|
|
|
inf = 0;
|
|
|
|
}
|
|
|
|
if (inf > 255) {
|
|
|
|
inf = 255;
|
|
|
|
}
|
|
|
|
g_gammatable[i] = inf;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inline const int &min_int(const int &left, const int &right)
|
|
|
|
{
|
|
|
|
return std::min(left, right);
|
|
|
|
}
|
|
|
|
|
|
|
|
int max_tex_size = 0;
|
|
|
|
const int max_texture_quality = 3;
|
|
|
|
LatchedValue<int> g_Textures_textureQuality(3, "Texture Quality");
|
|
|
|
|
|
|
|
/// \brief This function does the actual processing of raw RGBA data into a GL texture.
|
|
|
|
/// It will also resample to power-of-two dimensions, generate the mipmaps and adjust gamma.
|
|
|
|
void LoadTextureRGBA(qtexture_t *q, unsigned char *pPixels, int nWidth, int nHeight)
|
|
|
|
{
|
|
|
|
static float fGamma = -1;
|
|
|
|
float total[3];
|
|
|
|
byte *outpixels = 0;
|
|
|
|
int nCount = nWidth * nHeight;
|
|
|
|
|
|
|
|
if (fGamma != g_texture_globals.fGamma) {
|
|
|
|
fGamma = g_texture_globals.fGamma;
|
|
|
|
ResampleGamma(fGamma);
|
|
|
|
}
|
|
|
|
|
|
|
|
q->width = nWidth;
|
|
|
|
q->height = nHeight;
|
|
|
|
|
|
|
|
total[0] = total[1] = total[2] = 0.0f;
|
|
|
|
|
|
|
|
// resample texture gamma according to user settings
|
|
|
|
for (int i = 0; i < (nCount * 4); i += 4) {
|
|
|
|
for (int j = 0; j < 3; j++) {
|
|
|
|
total[j] += (pPixels + i)[j];
|
|
|
|
byte b = (pPixels + i)[j];
|
|
|
|
(pPixels + i)[j] = g_gammatable[b];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
q->color[0] = total[0] / (nCount * 255);
|
|
|
|
q->color[1] = total[1] / (nCount * 255);
|
|
|
|
q->color[2] = total[2] / (nCount * 255);
|
|
|
|
|
|
|
|
glGenTextures(1, &q->texture_number);
|
|
|
|
|
|
|
|
glBindTexture(GL_TEXTURE_2D, q->texture_number);
|
|
|
|
|
|
|
|
SetTexParameters(g_texture_mode);
|
|
|
|
|
|
|
|
int gl_width = 1;
|
|
|
|
while (gl_width < nWidth) {
|
|
|
|
gl_width <<= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int gl_height = 1;
|
|
|
|
while (gl_height < nHeight) {
|
|
|
|
gl_height <<= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool resampled = false;
|
|
|
|
if (!(gl_width == nWidth && gl_height == nHeight)) {
|
|
|
|
resampled = true;
|
|
|
|
outpixels = (byte *) malloc(gl_width * gl_height * 4);
|
|
|
|
R_ResampleTexture(pPixels, nWidth, nHeight, outpixels, gl_width, gl_height, 4);
|
|
|
|
} else {
|
|
|
|
outpixels = pPixels;
|
|
|
|
}
|
|
|
|
|
|
|
|
int quality_reduction = max_texture_quality - g_Textures_textureQuality.m_value;
|
|
|
|
int target_width = min_int(gl_width >> quality_reduction, max_tex_size);
|
|
|
|
int target_height = min_int(gl_height >> quality_reduction, max_tex_size);
|
|
|
|
|
|
|
|
while (gl_width > target_width || gl_height > target_height) {
|
|
|
|
GL_MipReduce(outpixels, outpixels, gl_width, gl_height, target_width, target_height);
|
|
|
|
|
|
|
|
if (gl_width > target_width) {
|
|
|
|
gl_width >>= 1;
|
|
|
|
}
|
|
|
|
if (gl_height > target_height) {
|
|
|
|
gl_height >>= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int mip = 0;
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, mip++, g_texture_globals.texture_components, gl_width, gl_height, 0, GL_RGBA,
|
|
|
|
GL_UNSIGNED_BYTE, outpixels);
|
|
|
|
while (gl_width > 1 || gl_height > 1) {
|
|
|
|
GL_MipReduce(outpixels, outpixels, gl_width, gl_height, 1, 1);
|
|
|
|
|
|
|
|
if (gl_width > 1) {
|
|
|
|
gl_width >>= 1;
|
|
|
|
}
|
|
|
|
if (gl_height > 1) {
|
|
|
|
gl_height >>= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, mip++, g_texture_globals.texture_components, gl_width, gl_height, 0, GL_RGBA,
|
|
|
|
GL_UNSIGNED_BYTE, outpixels);
|
|
|
|
}
|
|
|
|
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
if (resampled) {
|
|
|
|
free(outpixels);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
/*
|
|
|
|
==============
|
|
|
|
Texture_InitPalette
|
|
|
|
==============
|
|
|
|
*/
|
|
|
|
void Texture_InitPalette( byte *pal ){
|
|
|
|
int r,g,b;
|
|
|
|
int i;
|
|
|
|
int inf;
|
|
|
|
byte gammatable[256];
|
|
|
|
float gamma;
|
|
|
|
|
|
|
|
gamma = g_texture_globals.fGamma;
|
|
|
|
|
|
|
|
if ( gamma == 1.0 ) {
|
|
|
|
for ( i = 0 ; i < 256 ; i++ )
|
|
|
|
gammatable[i] = i;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for ( i = 0 ; i < 256 ; i++ )
|
|
|
|
{
|
|
|
|
inf = (int)( 255 * pow( ( i + 0.5 ) / 255.5, gamma ) + 0.5 );
|
|
|
|
if ( inf < 0 ) {
|
|
|
|
inf = 0;
|
|
|
|
}
|
|
|
|
if ( inf > 255 ) {
|
|
|
|
inf = 255;
|
|
|
|
}
|
|
|
|
gammatable[i] = inf;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for ( i = 0 ; i < 256 ; i++ )
|
|
|
|
{
|
|
|
|
r = gammatable[pal[0]];
|
|
|
|
g = gammatable[pal[1]];
|
|
|
|
b = gammatable[pal[2]];
|
|
|
|
pal += 3;
|
|
|
|
|
|
|
|
//v = (r<<24) + (g<<16) + (b<<8) + 255;
|
|
|
|
//v = BigLong (v);
|
|
|
|
|
|
|
|
//tex_palette[i] = v;
|
|
|
|
tex_palette[i * 3 + 0] = r;
|
|
|
|
tex_palette[i * 3 + 1] = g;
|
|
|
|
tex_palette[i * 3 + 2] = b;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
class TestHashtable
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
TestHashtable(){
|
|
|
|
HashTable<CopiedString, CopiedString, HashStringNoCase, StringEqualNoCase> strings;
|
|
|
|
strings["Monkey"] = "bleh";
|
|
|
|
strings["MonkeY"] = "blah";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const TestHashtable g_testhashtable;
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
typedef std::pair<LoadImageCallback, CopiedString> TextureKey;
|
|
|
|
|
|
|
|
void qtexture_realise(qtexture_t &texture, const TextureKey &key)
|
|
|
|
{
|
|
|
|
texture.texture_number = 0;
|
|
|
|
if (!string_empty(key.second.c_str())) {
|
|
|
|
Image *image = key.first.loadImage(key.second.c_str());
|
|
|
|
if (image != 0) {
|
|
|
|
LoadTextureRGBA(&texture, image->getRGBAPixels(), image->getWidth(), image->getHeight());
|
|
|
|
texture.surfaceFlags = image->getSurfaceFlags();
|
|
|
|
texture.contentFlags = image->getContentFlags();
|
|
|
|
texture.value = image->getValue();
|
|
|
|
image->release();
|
2021-06-04 10:21:39 +00:00
|
|
|
// We only want to report when errors happen
|
|
|
|
//globalOutputStream() << "Loaded Texture: \"" << key.second.c_str() << "\"\n";
|
2020-11-17 11:16:16 +00:00
|
|
|
GlobalOpenGL_debugAssertNoErrors();
|
|
|
|
} else {
|
|
|
|
globalErrorStream() << "Texture load failed: \"" << key.second.c_str() << "\"\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void qtexture_unrealise(qtexture_t &texture)
|
|
|
|
{
|
|
|
|
if (GlobalOpenGL().contextValid && texture.texture_number != 0) {
|
|
|
|
glDeleteTextures(1, &texture.texture_number);
|
|
|
|
GlobalOpenGL_debugAssertNoErrors();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class TextureKeyEqualNoCase {
|
|
|
|
public:
|
|
|
|
bool operator()(const TextureKey &key, const TextureKey &other) const
|
|
|
|
{
|
|
|
|
return key.first == other.first && string_equal_nocase(key.second.c_str(), other.second.c_str());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class TextureKeyHashNoCase {
|
|
|
|
public:
|
|
|
|
typedef hash_t hash_type;
|
|
|
|
|
|
|
|
hash_t operator()(const TextureKey &key) const
|
|
|
|
{
|
|
|
|
return hash_combine(string_hash_nocase(key.second.c_str()), pod_hash(key.first));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
#define DEBUG_TEXTURES 0
|
|
|
|
|
|
|
|
class TexturesMap : public TexturesCache {
|
|
|
|
class TextureConstructor {
|
|
|
|
TexturesMap *m_cache;
|
|
|
|
public:
|
|
|
|
explicit TextureConstructor(TexturesMap *cache)
|
|
|
|
: m_cache(cache)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
qtexture_t *construct(const TextureKey &key)
|
|
|
|
{
|
|
|
|
qtexture_t *texture = new qtexture_t(key.first, key.second.c_str());
|
|
|
|
if (m_cache->realised()) {
|
|
|
|
qtexture_realise(*texture, key);
|
|
|
|
}
|
|
|
|
return texture;
|
|
|
|
}
|
|
|
|
|
|
|
|
void destroy(qtexture_t *texture)
|
|
|
|
{
|
|
|
|
if (m_cache->realised()) {
|
|
|
|
qtexture_unrealise(*texture);
|
|
|
|
}
|
|
|
|
delete texture;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef HashedCache<TextureKey, qtexture_t, TextureKeyHashNoCase, TextureKeyEqualNoCase, TextureConstructor> qtextures_t;
|
|
|
|
qtextures_t m_qtextures;
|
|
|
|
TexturesCacheObserver *m_observer;
|
|
|
|
std::size_t m_unrealised;
|
|
|
|
|
|
|
|
public:
|
|
|
|
virtual ~TexturesMap() = default;
|
|
|
|
|
|
|
|
TexturesMap() : m_qtextures(TextureConstructor(this)), m_observer(0), m_unrealised(1)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef qtextures_t::iterator iterator;
|
|
|
|
|
|
|
|
iterator begin()
|
|
|
|
{
|
|
|
|
return m_qtextures.begin();
|
|
|
|
}
|
|
|
|
|
|
|
|
iterator end()
|
|
|
|
{
|
|
|
|
return m_qtextures.end();
|
|
|
|
}
|
|
|
|
|
|
|
|
LoadImageCallback defaultLoader() const
|
|
|
|
{
|
|
|
|
return LoadImageCallback(0, QERApp_LoadImage);
|
|
|
|
}
|
|
|
|
|
|
|
|
Image *loadImage(const char *name)
|
|
|
|
{
|
|
|
|
return defaultLoader().loadImage(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
qtexture_t *capture(const char *name)
|
|
|
|
{
|
|
|
|
return capture(defaultLoader(), name);
|
|
|
|
}
|
|
|
|
|
|
|
|
qtexture_t *capture(const LoadImageCallback &loader, const char *name)
|
|
|
|
{
|
|
|
|
#if DEBUG_TEXTURES
|
|
|
|
globalOutputStream() << "textures capture: " << makeQuoted( name ) << '\n';
|
|
|
|
#endif
|
|
|
|
return m_qtextures.capture(TextureKey(loader, name)).get();
|
|
|
|
}
|
|
|
|
|
|
|
|
void release(qtexture_t *texture)
|
|
|
|
{
|
|
|
|
#if DEBUG_TEXTURES
|
|
|
|
globalOutputStream() << "textures release: " << makeQuoted( texture->name ) << '\n';
|
|
|
|
#endif
|
|
|
|
m_qtextures.release(TextureKey(texture->load, texture->name));
|
|
|
|
}
|
|
|
|
|
|
|
|
void attach(TexturesCacheObserver &observer)
|
|
|
|
{
|
|
|
|
ASSERT_MESSAGE(m_observer == 0, "TexturesMap::attach: cannot attach observer");
|
|
|
|
m_observer = &observer;
|
|
|
|
}
|
|
|
|
|
|
|
|
void detach(TexturesCacheObserver &observer)
|
|
|
|
{
|
|
|
|
ASSERT_MESSAGE(m_observer == &observer, "TexturesMap::detach: cannot detach observer");
|
|
|
|
m_observer = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void realise()
|
|
|
|
{
|
|
|
|
if (--m_unrealised == 0) {
|
|
|
|
g_texture_globals.bTextureCompressionSupported = false;
|
|
|
|
|
|
|
|
if (GlobalOpenGL().ARB_texture_compression()) {
|
|
|
|
g_texture_globals.bTextureCompressionSupported = true;
|
|
|
|
g_texture_globals.m_bOpenGLCompressionSupported = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (GlobalOpenGL().EXT_texture_compression_s3tc()) {
|
|
|
|
g_texture_globals.bTextureCompressionSupported = true;
|
|
|
|
g_texture_globals.m_bS3CompressionSupported = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (g_texture_globals.texture_components) {
|
|
|
|
case GL_RGBA:
|
|
|
|
break;
|
|
|
|
case GL_COMPRESSED_RGBA_ARB:
|
|
|
|
if (!g_texture_globals.m_bOpenGLCompressionSupported) {
|
|
|
|
globalOutputStream()
|
|
|
|
<< "OpenGL extension GL_ARB_texture_compression not supported by current graphics drivers\n";
|
|
|
|
g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE;
|
|
|
|
g_texture_globals.texture_components = GL_RGBA;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
|
|
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
|
|
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
|
|
|
|
if (!g_texture_globals.m_bS3CompressionSupported) {
|
|
|
|
globalOutputStream()
|
|
|
|
<< "OpenGL extension GL_EXT_texture_compression_s3tc not supported by current graphics drivers\n";
|
|
|
|
if (g_texture_globals.m_bOpenGLCompressionSupported) {
|
|
|
|
g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_RGBA;
|
|
|
|
g_texture_globals.texture_components = GL_COMPRESSED_RGBA_ARB;
|
|
|
|
} else {
|
|
|
|
g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE;
|
|
|
|
g_texture_globals.texture_components = GL_RGBA;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
globalOutputStream() << "Unknown texture compression selected, reverting\n";
|
|
|
|
g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE;
|
|
|
|
g_texture_globals.texture_components = GL_RGBA;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex_size);
|
|
|
|
if (max_tex_size == 0) {
|
|
|
|
max_tex_size = 1024;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (qtextures_t::iterator i = m_qtextures.begin(); i != m_qtextures.end(); ++i) {
|
|
|
|
if (!(*i).value.empty()) {
|
|
|
|
qtexture_realise(*(*i).value, (*i).key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (m_observer != 0) {
|
|
|
|
m_observer->realise();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void unrealise()
|
|
|
|
{
|
|
|
|
if (++m_unrealised == 1) {
|
|
|
|
if (m_observer != 0) {
|
|
|
|
m_observer->unrealise();
|
|
|
|
}
|
|
|
|
for (qtextures_t::iterator i = m_qtextures.begin(); i != m_qtextures.end(); ++i) {
|
|
|
|
if (!(*i).value.empty()) {
|
|
|
|
qtexture_unrealise(*(*i).value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool realised()
|
|
|
|
{
|
|
|
|
return m_unrealised == 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
TexturesMap *g_texturesmap;
|
|
|
|
|
|
|
|
TexturesCache &GetTexturesCache()
|
|
|
|
{
|
|
|
|
return *g_texturesmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Textures_Realise()
|
|
|
|
{
|
|
|
|
g_texturesmap->realise();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Textures_Unrealise()
|
|
|
|
{
|
|
|
|
g_texturesmap->unrealise();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Callback<void()> g_texturesModeChangedNotify;
|
|
|
|
|
|
|
|
void Textures_setModeChangedNotify(const Callback<void()> ¬ify)
|
|
|
|
{
|
|
|
|
g_texturesModeChangedNotify = notify;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Textures_ModeChanged()
|
|
|
|
{
|
|
|
|
if (g_texturesmap->realised()) {
|
|
|
|
SetTexParameters(g_texture_mode);
|
|
|
|
|
|
|
|
for (TexturesMap::iterator i = g_texturesmap->begin(); i != g_texturesmap->end(); ++i) {
|
|
|
|
glBindTexture(GL_TEXTURE_2D, (*i).value->texture_number);
|
|
|
|
SetTexParameters(g_texture_mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
}
|
|
|
|
g_texturesModeChangedNotify();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Textures_SetMode(ETexturesMode mode)
|
|
|
|
{
|
|
|
|
if (g_texture_mode != mode) {
|
|
|
|
g_texture_mode = mode;
|
|
|
|
|
|
|
|
Textures_ModeChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Textures_setTextureComponents(GLint texture_components)
|
|
|
|
{
|
|
|
|
if (g_texture_globals.texture_components != texture_components) {
|
|
|
|
Textures_Unrealise();
|
|
|
|
g_texture_globals.texture_components = texture_components;
|
|
|
|
Textures_Realise();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Textures_UpdateTextureCompressionFormat()
|
|
|
|
{
|
|
|
|
GLint texture_components = GL_RGBA;
|
|
|
|
|
|
|
|
switch (g_texture_globals.m_nTextureCompressionFormat) {
|
|
|
|
case (TEXTURECOMPRESSION_NONE): {
|
|
|
|
texture_components = GL_RGBA;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case (TEXTURECOMPRESSION_RGBA): {
|
|
|
|
texture_components = GL_COMPRESSED_RGBA_ARB;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case (TEXTURECOMPRESSION_RGBA_S3TC_DXT1): {
|
|
|
|
texture_components = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case (TEXTURECOMPRESSION_RGBA_S3TC_DXT3): {
|
|
|
|
texture_components = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case (TEXTURECOMPRESSION_RGBA_S3TC_DXT5): {
|
|
|
|
texture_components = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Textures_setTextureComponents(texture_components);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct TextureCompression {
|
|
|
|
static void Export(const TextureCompressionFormat &self, const Callback<void(int)> &returnz)
|
|
|
|
{
|
|
|
|
returnz(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void Import(TextureCompressionFormat &self, int value)
|
|
|
|
{
|
|
|
|
if (!g_texture_globals.m_bOpenGLCompressionSupported
|
|
|
|
&& g_texture_globals.m_bS3CompressionSupported
|
|
|
|
&& value >= 1) {
|
|
|
|
++value;
|
|
|
|
}
|
|
|
|
switch (value) {
|
|
|
|
case 0:
|
|
|
|
self = TEXTURECOMPRESSION_NONE;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
self = TEXTURECOMPRESSION_RGBA;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
self = TEXTURECOMPRESSION_RGBA_S3TC_DXT1;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
self = TEXTURECOMPRESSION_RGBA_S3TC_DXT3;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
self = TEXTURECOMPRESSION_RGBA_S3TC_DXT5;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Textures_UpdateTextureCompressionFormat();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct TextureGamma {
|
|
|
|
static void Export(const float &self, const Callback<void(float)> &returnz)
|
|
|
|
{
|
|
|
|
returnz(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void Import(float &self, float value)
|
|
|
|
{
|
|
|
|
if (value != self) {
|
|
|
|
Textures_Unrealise();
|
|
|
|
self = value;
|
|
|
|
Textures_Realise();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct TextureMode {
|
|
|
|
static void Export(const ETexturesMode &self, const Callback<void(int)> &returnz)
|
|
|
|
{
|
|
|
|
switch (self) {
|
|
|
|
case eTextures_NEAREST:
|
|
|
|
returnz(0);
|
|
|
|
break;
|
|
|
|
case eTextures_NEAREST_MIPMAP_NEAREST:
|
|
|
|
returnz(1);
|
|
|
|
break;
|
|
|
|
case eTextures_LINEAR:
|
|
|
|
returnz(2);
|
|
|
|
break;
|
|
|
|
case eTextures_NEAREST_MIPMAP_LINEAR:
|
|
|
|
returnz(3);
|
|
|
|
break;
|
|
|
|
case eTextures_LINEAR_MIPMAP_NEAREST:
|
|
|
|
returnz(4);
|
|
|
|
break;
|
|
|
|
case eTextures_LINEAR_MIPMAP_LINEAR:
|
|
|
|
returnz(5);
|
|
|
|
break;
|
|
|
|
case eTextures_MAX_ANISOTROPY:
|
|
|
|
returnz(6);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
returnz(4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void Import(ETexturesMode &self, int value)
|
|
|
|
{
|
|
|
|
switch (value) {
|
|
|
|
case 0:
|
|
|
|
Textures_SetMode(eTextures_NEAREST);
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
Textures_SetMode(eTextures_NEAREST_MIPMAP_NEAREST);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
Textures_SetMode(eTextures_LINEAR);
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
Textures_SetMode(eTextures_NEAREST_MIPMAP_LINEAR);
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
Textures_SetMode(eTextures_LINEAR_MIPMAP_NEAREST);
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
Textures_SetMode(eTextures_LINEAR_MIPMAP_LINEAR);
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
Textures_SetMode(eTextures_MAX_ANISOTROPY);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
void Textures_constructPreferences(PreferencesPage &page)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
const char *percentages[] = {"12.5%", "25%", "50%", "100%",};
|
|
|
|
page.appendRadio(
|
|
|
|
"Texture Quality",
|
|
|
|
STRING_ARRAY_RANGE(percentages),
|
|
|
|
make_property(g_Textures_textureQuality)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
page.appendSpinner(
|
|
|
|
"Texture Gamma",
|
|
|
|
1.0,
|
|
|
|
0.0,
|
|
|
|
1.0,
|
|
|
|
make_property<TextureGamma>(g_texture_globals.fGamma)
|
|
|
|
);
|
|
|
|
{
|
|
|
|
const char *texture_mode[] = {"Nearest", "Nearest Mipmap", "Linear", "Bilinear", "Bilinear Mipmap", "Trilinear",
|
|
|
|
"Anisotropy"};
|
|
|
|
page.appendCombo(
|
|
|
|
"Texture Render Mode",
|
|
|
|
STRING_ARRAY_RANGE(texture_mode),
|
|
|
|
make_property<TextureMode>(g_texture_mode)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
const char *compression_none[] = {"None"};
|
|
|
|
const char *compression_opengl[] = {"None", "OpenGL ARB"};
|
|
|
|
const char *compression_s3tc[] = {"None", "S3TC DXT1", "S3TC DXT3", "S3TC DXT5"};
|
|
|
|
const char *compression_opengl_s3tc[] = {"None", "OpenGL ARB", "S3TC DXT1", "S3TC DXT3", "S3TC DXT5"};
|
|
|
|
StringArrayRange compression(
|
|
|
|
(g_texture_globals.m_bOpenGLCompressionSupported)
|
|
|
|
? (g_texture_globals.m_bS3CompressionSupported)
|
|
|
|
? STRING_ARRAY_RANGE(compression_opengl_s3tc)
|
|
|
|
: STRING_ARRAY_RANGE(compression_opengl)
|
|
|
|
: (g_texture_globals.m_bS3CompressionSupported)
|
|
|
|
? STRING_ARRAY_RANGE(compression_s3tc)
|
|
|
|
: STRING_ARRAY_RANGE(compression_none)
|
|
|
|
);
|
|
|
|
page.appendCombo(
|
|
|
|
"Hardware Texture Compression",
|
|
|
|
compression,
|
|
|
|
make_property<TextureCompression>(g_texture_globals.m_nTextureCompressionFormat)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Textures_constructPage(PreferenceGroup &group)
|
|
|
|
{
|
|
|
|
PreferencesPage page(group.createPage("Textures", "Texture Settings"));
|
|
|
|
Textures_constructPreferences(page);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Textures_registerPreferencesPage()
|
|
|
|
{
|
|
|
|
PreferencesDialog_addDisplayPage(makeCallbackF(Textures_constructPage));
|
|
|
|
}
|
|
|
|
|
|
|
|
struct TextureCompressionPreference {
|
|
|
|
static void Export(const Callback<void(int)> &returnz)
|
|
|
|
{
|
|
|
|
returnz(g_texture_globals.m_nTextureCompressionFormat);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void Import(int value)
|
|
|
|
{
|
|
|
|
g_texture_globals.m_nTextureCompressionFormat = static_cast<TextureCompressionFormat>( value );
|
|
|
|
Textures_UpdateTextureCompressionFormat();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
void Textures_Construct()
|
|
|
|
{
|
|
|
|
g_texturesmap = new TexturesMap;
|
|
|
|
|
|
|
|
GlobalPreferenceSystem().registerPreference("TextureCompressionFormat",
|
|
|
|
make_property_string<TextureCompressionPreference>());
|
|
|
|
GlobalPreferenceSystem().registerPreference("TextureFiltering",
|
|
|
|
make_property_string(reinterpret_cast<int &>( g_texture_mode )));
|
|
|
|
GlobalPreferenceSystem().registerPreference("TextureQuality",
|
|
|
|
make_property_string(g_Textures_textureQuality.m_latched));
|
|
|
|
GlobalPreferenceSystem().registerPreference("SI_Gamma", make_property_string(g_texture_globals.fGamma));
|
|
|
|
|
|
|
|
g_Textures_textureQuality.useLatched();
|
|
|
|
|
|
|
|
Textures_registerPreferencesPage();
|
|
|
|
|
|
|
|
Textures_ModeChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Textures_Destroy()
|
|
|
|
{
|
|
|
|
delete g_texturesmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#include "modulesystem/modulesmap.h"
|
|
|
|
#include "modulesystem/singletonmodule.h"
|
|
|
|
#include "modulesystem/moduleregistry.h"
|
|
|
|
|
|
|
|
class TexturesDependencies :
|
|
|
|
public GlobalRadiantModuleRef,
|
|
|
|
public GlobalOpenGLModuleRef,
|
|
|
|
public GlobalPreferenceSystemModuleRef {
|
|
|
|
ImageModulesRef m_image_modules;
|
|
|
|
public:
|
|
|
|
TexturesDependencies() :
|
|
|
|
m_image_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("texturetypes"))
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ImageModules &getImageModules()
|
|
|
|
{
|
|
|
|
return m_image_modules.get();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class TexturesAPI {
|
|
|
|
TexturesCache *m_textures;
|
|
|
|
public:
|
|
|
|
typedef TexturesCache Type;
|
|
|
|
|
|
|
|
STRING_CONSTANT(Name, "*");
|
|
|
|
|
|
|
|
TexturesAPI()
|
|
|
|
{
|
|
|
|
Textures_Construct();
|
|
|
|
|
|
|
|
m_textures = &GetTexturesCache();
|
|
|
|
}
|
|
|
|
|
|
|
|
~TexturesAPI()
|
|
|
|
{
|
|
|
|
Textures_Destroy();
|
|
|
|
}
|
|
|
|
|
|
|
|
TexturesCache *getTable()
|
|
|
|
{
|
|
|
|
return m_textures;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef SingletonModule<TexturesAPI, TexturesDependencies> TexturesModule;
|
|
|
|
typedef Static<TexturesModule> StaticTexturesModule;
|
|
|
|
StaticRegisterModule staticRegisterTextures(StaticTexturesModule::instance());
|
|
|
|
|
|
|
|
ImageModules &Textures_getImageModules()
|
|
|
|
{
|
|
|
|
return StaticTexturesModule::instance().getDependencies().getImageModules();
|
|
|
|
}
|