1768 lines
52 KiB
C++
Executable File
1768 lines
52 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "platform/platformAssert.h"
|
|
#include "platform/platformGL.h"
|
|
#include "platform/platform.h"
|
|
#include "core/tVector.h"
|
|
#include "core/resManager.h"
|
|
#include "dgl/gBitmap.h"
|
|
#include "dgl/gPalette.h"
|
|
#include "dgl/gTexManager.h"
|
|
#include "console/console.h"
|
|
#include "console/consoleInternal.h"
|
|
#include "console/consoleTypes.h"
|
|
#include "dgl/gChunkedTexManager.h"
|
|
#include "util/safeDelete.h"
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
bool gDGLRender = true;
|
|
|
|
bool sgResurrect = false;
|
|
bool sgForcePalettedTexture = false;
|
|
bool sgForce16BitTexture = false;
|
|
|
|
|
|
#define ENABLE_HOLDING 1
|
|
|
|
#ifdef TORQUE_GATHER_METRICS
|
|
U32 TextureManager::smTextureSpaceLoaded = 0;
|
|
U32 TextureManager::smTextureCacheMisses = 0;
|
|
#endif
|
|
|
|
bool TextureManager::smUseSmallTextures = false;
|
|
bool TextureManager::smIsZombie = false;
|
|
bool TextureManager::smTextureManagerActive = false;
|
|
|
|
//--------------------------------------------------------------------------
|
|
//-------------------------------------- Texture detailing control variables
|
|
// 0: Highest
|
|
// 1: ...
|
|
// 2: ...
|
|
// 3: Lowest
|
|
|
|
namespace {
|
|
|
|
struct Forced16BitMapping
|
|
{
|
|
GLenum wanted;
|
|
GLenum forced;
|
|
bool end;
|
|
};
|
|
|
|
Forced16BitMapping sg16BitMappings[] =
|
|
{
|
|
{ GL_RGB, GL_RGB5, false },
|
|
{ GL_RGBA, GL_RGBA4, false },
|
|
{ 0, 0, true }
|
|
};
|
|
|
|
|
|
U32 sgTextureDetailLevel = 0;
|
|
U32 sgSkyTextureDetailLevel = 0;
|
|
U32 sgInteriorTextureDetailLevel = 0;
|
|
bool sgAllowTexCompression = false;
|
|
GLenum sgCompressionHint = GL_FASTEST;
|
|
F32 sgTextureAnisotropy = 0.0; // default aniso, when available. not sure why prefs.cs isn't setting.
|
|
bool sgDisableSubImage = false;
|
|
bool sgTextureTrilinear = false;
|
|
|
|
// valid texture extensions
|
|
#define EXT_ARRAY_SIZE 6
|
|
static const char* extArray[EXT_ARRAY_SIZE] = { "", ".jpg", ".png", ".gif", ".bmp", "" };
|
|
static const char* extArray_8[EXT_ARRAY_SIZE] = { "", ".bm8", ".bmp", ".jpg", ".png", ".gif" };
|
|
|
|
ConsoleFunctionGroupBegin( OpenGLTex, "Functions controlling OpenGL parameters.");
|
|
|
|
ConsoleFunction(setOpenGLMipReduction, void, 2, 2, "( n ) Sets mipmap reduction level, n ranges from 0-5.")
|
|
{
|
|
argc;
|
|
S32 val = dAtoi(argv[1]);
|
|
if (val < 0)
|
|
val = 0;
|
|
else if (val > 5)
|
|
val = 5;
|
|
|
|
sgTextureDetailLevel = val;
|
|
}
|
|
|
|
ConsoleFunction(setOpenGLSkyMipReduction, void, 2, 2, "setOpenGLSkyMipReduction(0-5);")
|
|
{
|
|
argc;
|
|
S32 val = dAtoi(argv[1]);
|
|
if (val < 0)
|
|
val = 0;
|
|
else if (val > 5)
|
|
val = 5;
|
|
|
|
sgSkyTextureDetailLevel = val;
|
|
}
|
|
|
|
ConsoleFunction(setOpenGLInteriorMipReduction, void, 2, 2, "setOpenGLInteriorMipReduction(0-5);")
|
|
{
|
|
argc;
|
|
S32 val = dAtoi(argv[1]);
|
|
if (val < 0)
|
|
val = 0;
|
|
else if (val > 5)
|
|
val = 5;
|
|
|
|
sgInteriorTextureDetailLevel = val;
|
|
}
|
|
|
|
ConsoleFunction(setOpenGLTextureCompressionHint, void, 2, 2, "setTextureCompressionHint(GL_DONT_CARE|GL_FASTEST|GL_NICEST);")
|
|
{
|
|
argc;
|
|
|
|
GLenum newHint = GL_DONT_CARE;
|
|
const char* newString = "GL_DONT_CARE";
|
|
|
|
if (!dStricmp(argv[1], "GL_FASTEST"))
|
|
{
|
|
newHint = GL_FASTEST;
|
|
newString = "GL_FASTEST";
|
|
}
|
|
else if (!dStricmp(argv[1], "GL_NICEST"))
|
|
{
|
|
newHint = GL_NICEST;
|
|
newString = "GL_NICEST";
|
|
}
|
|
|
|
sgCompressionHint = newHint;
|
|
|
|
if (dglDoesSupportTextureCompression())
|
|
glHint(GL_TEXTURE_COMPRESSION_HINT_ARB, sgCompressionHint);
|
|
}
|
|
|
|
ConsoleFunction(setOpenGLAnisotropy, void, 2, 2, "setOpenGLAnisotropy(0-1);")
|
|
{
|
|
argc;
|
|
F32 val = dAtof(argv[1]);
|
|
if (val < 0.0)
|
|
val = 0.0;
|
|
if (val > dglGetMaxAnisotropy())
|
|
val = dglGetMaxAnisotropy();
|
|
sgTextureAnisotropy = val;
|
|
|
|
if(dglDoesSupportTexAnisotropy())
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, sgTextureAnisotropy * dglGetMaxAnisotropy());
|
|
}
|
|
|
|
ConsoleFunctionGroupEnd( OpenGLTex );
|
|
|
|
} // namespace {}
|
|
|
|
|
|
//--------------------------------------
|
|
struct TextureDictionary
|
|
{
|
|
static TextureObject **smTable;
|
|
static TextureObject *smTOList;
|
|
static U32 smHashTableSize;
|
|
|
|
static void create();
|
|
static void preDestroy();
|
|
static void destroy();
|
|
|
|
static void insert(TextureObject *object);
|
|
static TextureObject *find(StringTableEntry name, TextureHandleType type, bool clamp);
|
|
static void remove(TextureObject *object);
|
|
static S32 clearHolds();
|
|
};
|
|
|
|
TextureObject **TextureDictionary::smTable = NULL;
|
|
TextureObject *TextureDictionary::smTOList = NULL;
|
|
U32 TextureDictionary::smHashTableSize = 0;
|
|
|
|
//--------------------------------------
|
|
void TextureDictionary::create()
|
|
{
|
|
smTOList = NULL;
|
|
smHashTableSize = 1023;
|
|
smTable = new TextureObject *[smHashTableSize];
|
|
for(U32 i = 0; i < smHashTableSize; i++)
|
|
smTable[i] = NULL;
|
|
|
|
Con::addVariable("$pref::OpenGL::force16BitTexture", TypeBool, &sgForce16BitTexture);
|
|
Con::addVariable("$pref::OpenGL::forcePalettedTexture", TypeBool, &sgForcePalettedTexture);
|
|
Con::addVariable("$pref::OpenGL::allowCompression", TypeBool, &sgAllowTexCompression);
|
|
Con::addVariable("$pref::OpenGL::disableSubImage", TypeBool, &sgDisableSubImage);
|
|
|
|
Con::addVariable("$pref::OpenGL::textureTrilinear", TypeBool, &sgTextureTrilinear);
|
|
Con::addVariable("$pref::OpenGL::textureAnisotropy", TypeF32, &sgTextureAnisotropy);
|
|
}
|
|
|
|
|
|
//--------------------------------------
|
|
TextureObject *TextureDictionary::find(StringTableEntry name, TextureHandleType type, bool clamp)
|
|
{
|
|
U32 key = HashPointer(name) % smHashTableSize;
|
|
TextureObject *walk = smTable[key];
|
|
for(; walk; walk = walk->hashNext)
|
|
if(walk->texFileName == name && walk->type == type && walk->clamp == clamp)
|
|
break;
|
|
return walk;
|
|
}
|
|
|
|
|
|
//--------------------------------------
|
|
void TextureDictionary::remove(TextureObject *object)
|
|
{
|
|
if(object->next)
|
|
object->next->prev = object->prev;
|
|
|
|
if(object->prev)
|
|
object->prev->next = object->next;
|
|
else
|
|
smTOList = object->next;
|
|
|
|
if(!object->texFileName)
|
|
return;
|
|
|
|
U32 key = HashPointer(object->texFileName) % smHashTableSize;
|
|
TextureObject **walk = &smTable[key];
|
|
while(*walk)
|
|
{
|
|
if(*walk == object)
|
|
{
|
|
*walk = object->hashNext;
|
|
break;
|
|
}
|
|
walk = &((*walk)->hashNext);
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------
|
|
void TextureDictionary::insert(TextureObject *object)
|
|
{
|
|
object->next = smTOList;
|
|
object->prev = NULL;
|
|
if(smTOList)
|
|
smTOList->prev = object;
|
|
smTOList = object;
|
|
|
|
if(object->texFileName)
|
|
{
|
|
U32 key = HashPointer(object->texFileName) % smHashTableSize;
|
|
|
|
object->hashNext = smTable[key];
|
|
smTable[key] = object;
|
|
}
|
|
}
|
|
|
|
//--------------------------------------
|
|
void TextureDictionary::preDestroy()
|
|
{
|
|
// This is a horrid hack, but it will have to do for now. (DMM, aided
|
|
// and abetted by MF.)
|
|
TextureObject* walk = smTOList;
|
|
while (walk)
|
|
{
|
|
if((gDGLRender || sgResurrect) && walk->texGLName)
|
|
glDeleteTextures(1, (const GLuint*)&walk->texGLName);
|
|
if((gDGLRender || sgResurrect) && walk->smallTexGLName)
|
|
glDeleteTextures(1, (const GLuint*)&walk->smallTexGLName);
|
|
//delete walk->bitmap;
|
|
walk->texGLName = 0;
|
|
walk->smallTexGLName = 0;
|
|
//walk->bitmap = NULL;
|
|
|
|
walk = walk->next;
|
|
}
|
|
}
|
|
|
|
//--------------------------------------
|
|
void TextureDictionary::destroy()
|
|
{
|
|
// This is a horrid hack, but it will have to do for now. (DMM, aided
|
|
// and abetted by MF.)
|
|
while(smTOList)
|
|
TextureManager::freeTexture(smTOList);
|
|
delete[] smTable;
|
|
}
|
|
|
|
//--------------------------------------
|
|
S32 TextureDictionary::clearHolds()
|
|
{
|
|
Vector<TextureObject *> holds;
|
|
|
|
// Find held textures to delete. Clear holding flag too so they're free
|
|
// to go away.
|
|
for (TextureObject * walk = smTOList; walk; walk = walk->next)
|
|
{
|
|
if (walk->holding)
|
|
{
|
|
if (!walk->refCount)
|
|
holds.push_back(walk);
|
|
else
|
|
walk->holding = false;
|
|
}
|
|
}
|
|
|
|
// Remove them-
|
|
for (S32 i = 0; i < holds.size(); i++)
|
|
TextureManager::freeTexture(holds[i]);
|
|
|
|
return holds.size();
|
|
}
|
|
|
|
ConsoleFunction(clearTextureHolds, S32, 1, 1, "clearTextureHolds();")
|
|
{
|
|
argc; argv;
|
|
return TextureDictionary::clearHolds();
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
//--------------------------------------
|
|
//
|
|
struct EventCallbackEntry
|
|
{
|
|
TextureEventCallback callback;
|
|
void * userData;
|
|
U32 key;
|
|
};
|
|
static U32 sgCurrCallbackKey = 0;
|
|
static Vector<EventCallbackEntry> sgEventCallbacks(__FILE__, __LINE__);
|
|
|
|
U32 TextureManager::registerEventCallback(TextureEventCallback callback, void *userData)
|
|
{
|
|
sgEventCallbacks.increment();
|
|
sgEventCallbacks.last().callback = callback;
|
|
sgEventCallbacks.last().userData = userData;
|
|
sgEventCallbacks.last().key = sgCurrCallbackKey++;
|
|
|
|
return sgEventCallbacks.last().key;
|
|
}
|
|
|
|
void TextureManager::unregisterEventCallback(const U32 callbackKey)
|
|
{
|
|
for (S32 i = 0; i < sgEventCallbacks.size(); i++)
|
|
if (sgEventCallbacks[i].key == callbackKey) {
|
|
sgEventCallbacks.erase(i);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void TextureManager::postTextureEvent(const U32 eventCode)
|
|
{
|
|
for (S32 i = 0; i < sgEventCallbacks.size(); i++)
|
|
(sgEventCallbacks[i].callback)(eventCode, sgEventCallbacks[i].userData);
|
|
}
|
|
|
|
|
|
void TextureManager::create()
|
|
{
|
|
AssertISV(!smTextureManagerActive, "TextureManager::create - already created!");
|
|
|
|
TextureDictionary::create();
|
|
smTextureManagerActive = true;
|
|
}
|
|
|
|
void TextureManager::preDestroy()
|
|
{
|
|
AssertISV(smTextureManagerActive, "TextureManager::preDestroy - nothing to destroy!");
|
|
|
|
TextureDictionary::preDestroy();
|
|
}
|
|
|
|
void TextureManager::destroy()
|
|
{
|
|
AssertISV(smTextureManagerActive, "TextureManager::destroy - nothing to destroy!");
|
|
|
|
TextureDictionary::destroy();
|
|
|
|
AssertFatal(sgEventCallbacks.size() == 0,
|
|
"Error, some object didn't unregister it's texture event callback function!");
|
|
|
|
smTextureManagerActive = false;
|
|
}
|
|
|
|
|
|
//--------------------------------------
|
|
void TextureManager::makeZombie()
|
|
{
|
|
if (smIsZombie == true)
|
|
return;
|
|
smIsZombie = true;
|
|
|
|
postTextureEvent(BeginZombification);
|
|
ChunkedTextureManager::makeZombie();
|
|
|
|
// Publish flush event?
|
|
|
|
Vector<GLuint> deleteNames(4096);
|
|
|
|
TextureObject* probe = TextureDictionary::smTOList;
|
|
while (probe)
|
|
{
|
|
AssertFatal(probe->type != TerrainTexture, "Error, all the terrain textureobjects should be gone by now!");
|
|
|
|
if (probe->type == BitmapNoDownloadTexture)
|
|
{
|
|
probe = probe->next;
|
|
continue;
|
|
}
|
|
|
|
if (probe->texGLName != 0)
|
|
deleteNames.push_back(probe->texGLName);
|
|
if (probe->smallTexGLName != 0)
|
|
deleteNames.push_back(probe->smallTexGLName);
|
|
|
|
#ifdef TORQUE_GATHER_METRICS
|
|
AssertFatal(probe->textureSpace <= smTextureSpaceLoaded, "Error, that shouldn't happen!");
|
|
smTextureSpaceLoaded -= probe->textureSpace;
|
|
probe->textureSpace = 0;
|
|
#endif
|
|
|
|
probe->texGLName = 0;
|
|
probe->smallTexGLName = 0;
|
|
|
|
probe = probe->next;
|
|
}
|
|
|
|
glDeleteTextures(deleteNames.size(), deleteNames.address());
|
|
}
|
|
|
|
void TextureManager::resurrect()
|
|
{
|
|
if (smIsZombie == false)
|
|
return;
|
|
smIsZombie = false;
|
|
|
|
sgResurrect = true;
|
|
|
|
// Get rid of any chunked textures created while the app was inactive
|
|
ChunkedTextureManager::makeZombie();
|
|
|
|
// Reload textures...
|
|
TextureObject* probe = TextureDictionary::smTOList;
|
|
while (probe)
|
|
{
|
|
// reload texture...
|
|
AssertFatal(probe->type != TerrainTexture, "Error, all the terrain textureobjects should be gone by now!");
|
|
|
|
if (probe->type == BitmapNoDownloadTexture)
|
|
{
|
|
probe = probe->next;
|
|
continue;
|
|
}
|
|
|
|
if (probe->bitmap != NULL)
|
|
{
|
|
if(probe->type == BitmapKeepTexture)
|
|
{
|
|
delete probe->bitmap;
|
|
probe->bitmap = NULL;
|
|
}
|
|
else
|
|
{
|
|
if (probe->type == RegisteredTexture)
|
|
{
|
|
createGLName(probe->bitmap, probe->clamp, 0, probe->type, probe);
|
|
}
|
|
else
|
|
{
|
|
TextureObject* refreshed = registerTexture(probe->texFileName, probe->bitmap,
|
|
probe->type, probe->clamp);
|
|
AssertFatal(refreshed == probe, "Error, new texture object returned. This should not happen in resurrect");
|
|
}
|
|
|
|
probe = probe->next;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Ok, what we have here is the object, with the right name, we need to load the
|
|
// bitmap, and register the texture
|
|
GBitmap *bmp = loadBitmapInstance(probe->texFileName);
|
|
AssertISV(bmp != NULL, "Error resurrecting the texture cache.\n"
|
|
"Possible cause: a bitmap was deleted during the course of gameplay.");
|
|
|
|
TextureObject* refreshed = registerTexture(probe->texFileName, bmp,
|
|
probe->type, probe->clamp);
|
|
AssertFatal(refreshed == probe, "Error, new texture object returned. This should not happen in resurrect");
|
|
|
|
probe = probe->next;
|
|
}
|
|
|
|
ChunkedTextureManager::resurrect();
|
|
postTextureEvent(CacheResurrected);
|
|
|
|
sgResurrect = false;
|
|
}
|
|
|
|
void TextureManager::flush()
|
|
{
|
|
makeZombie();
|
|
resurrect();
|
|
}
|
|
|
|
ConsoleFunction( flushTextureCache, void, 1, 1, "Flush the texture cache.")
|
|
{
|
|
TextureManager::flush();
|
|
}
|
|
|
|
#ifdef TORQUE_GATHER_METRICS
|
|
void TextureManager::dumpStats()
|
|
{
|
|
TextureObject* probe = TextureDictionary::smTOList;
|
|
|
|
Con::errorf("aaa Texture dump");
|
|
while (probe)
|
|
{
|
|
Con::errorf("aaa %d: (%d, %s) %d (%s)", probe->type, probe->refCount, probe->holding ? "yes" : "no", probe->textureSpace, probe->texFileName ? probe->texFileName : "nil");
|
|
probe = probe->next;
|
|
}
|
|
}
|
|
|
|
ConsoleFunction(dumpTextureStats, void, 1, 1, "Dump texture manager statistics. Debug only!")
|
|
{
|
|
TextureManager::dumpStats();
|
|
}
|
|
#endif
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
GBitmap* TextureManager::createPaddedBitmap(GBitmap* pBitmap)
|
|
{
|
|
if (isPow2(pBitmap->getWidth()) && isPow2(pBitmap->getHeight()))
|
|
return pBitmap;
|
|
|
|
AssertFatal(pBitmap->getNumMipLevels() == 1,
|
|
"Cannot have non-pow2 bitmap with miplevels");
|
|
|
|
U32 width = pBitmap->getWidth();
|
|
U32 height = pBitmap->getHeight();
|
|
|
|
U32 newWidth = getNextPow2(pBitmap->getWidth());
|
|
U32 newHeight = getNextPow2(pBitmap->getHeight());
|
|
|
|
GBitmap* pReturn = new GBitmap(newWidth, newHeight, false, pBitmap->getFormat());
|
|
|
|
for (U32 i = 0; i < height; i++)
|
|
{
|
|
U8* pDest = (U8*)pReturn->getAddress(0, i);
|
|
const U8* pSrc = (const U8*)pBitmap->getAddress(0, i);
|
|
|
|
dMemcpy(pDest, pSrc, width * pBitmap->bytesPerPixel);
|
|
|
|
pDest += width * pBitmap->bytesPerPixel;
|
|
// set the src pixel to the last pixel in the row
|
|
const U8 *pSrcPixel = pDest - pBitmap->bytesPerPixel;
|
|
|
|
for(U32 j = width; j < newWidth; j++)
|
|
for(U32 k = 0; k < pBitmap->bytesPerPixel; k++)
|
|
*pDest++ = pSrcPixel[k];
|
|
}
|
|
|
|
for(U32 i = height; i < newHeight; i++)
|
|
{
|
|
U8* pDest = (U8*)pReturn->getAddress(0, i);
|
|
U8* pSrc = (U8*)pReturn->getAddress(0, height-1);
|
|
dMemcpy(pDest, pSrc, newWidth * pBitmap->bytesPerPixel);
|
|
}
|
|
|
|
if (pBitmap->getFormat() == GBitmap::Palettized)
|
|
{
|
|
pReturn->pPalette = new GPalette;
|
|
dMemcpy(pReturn->pPalette->getColors(), pBitmap->pPalette->getColors(), sizeof(ColorI) * 256);
|
|
pReturn->pPalette->setPaletteType(pBitmap->pPalette->getPaletteType());
|
|
}
|
|
|
|
return pReturn;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
GBitmap* TextureManager::createMipBitmap(const GBitmap* pBitmap)
|
|
{
|
|
AssertFatal(pBitmap != NULL, "Error, no bitmap");
|
|
AssertFatal(pBitmap->getNumMipLevels() != 1, "Error, no mips to maintain");
|
|
|
|
GBitmap* pRetBitmap = new GBitmap(pBitmap->getWidth(1),
|
|
pBitmap->getHeight(1),
|
|
true,
|
|
pBitmap->getFormat());
|
|
|
|
for (U32 i = 1; i < pBitmap->getNumMipLevels(); i++)
|
|
{
|
|
void* pDest = pRetBitmap->getWritableBits(i - 1);
|
|
const void* pSrc = pBitmap->getBits(i);
|
|
|
|
dMemcpy(pDest, pSrc, (pBitmap->getWidth(i) *
|
|
pBitmap->getHeight(i) *
|
|
pBitmap->bytesPerPixel));
|
|
}
|
|
|
|
return pRetBitmap;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
void TextureManager::freeTexture(TextureObject *to)
|
|
{
|
|
#ifdef TORQUE_GATHER_METRICS
|
|
AssertFatal(to->textureSpace <= smTextureSpaceLoaded, "Error, that shouldn't happen!");
|
|
smTextureSpaceLoaded -= to->textureSpace;
|
|
#endif
|
|
|
|
if((gDGLRender || sgResurrect) && to->texGLName)
|
|
glDeleteTextures(1, (const GLuint*)&to->texGLName);
|
|
if((gDGLRender || sgResurrect) && to->smallTexGLName)
|
|
glDeleteTextures(1, (const GLuint*)&to->smallTexGLName);
|
|
|
|
SAFE_DELETE( to->bitmap );
|
|
TextureDictionary::remove(to);
|
|
SAFE_DELETE( to );
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
static void getSourceDestByteFormat(GBitmap *pBitmap, U32 *sourceFormat, U32 *destFormat, U32 *byteFormat)
|
|
{
|
|
*byteFormat = GL_UNSIGNED_BYTE;
|
|
|
|
switch(pBitmap->getFormat())
|
|
{
|
|
case GBitmap::Intensity:
|
|
*sourceFormat = GL_INTENSITY;
|
|
break;
|
|
|
|
case GBitmap::Palettized:
|
|
*sourceFormat = GL_COLOR_INDEX;
|
|
break;
|
|
|
|
case GBitmap::Luminance:
|
|
*sourceFormat = GL_LUMINANCE;
|
|
break;
|
|
case GBitmap::RGB:
|
|
*sourceFormat = GL_RGB;
|
|
break;
|
|
case GBitmap::RGBA:
|
|
*sourceFormat = GL_RGBA;
|
|
break;
|
|
case GBitmap::Alpha:
|
|
*sourceFormat = GL_ALPHA;
|
|
break;
|
|
|
|
case GBitmap::RGB565:
|
|
case GBitmap::RGB5551:
|
|
#if defined(TORQUE_OS_MAC)
|
|
*sourceFormat = GL_BGRA_EXT;
|
|
*byteFormat = GL_UNSIGNED_SHORT_1_5_5_5_REV;
|
|
#else
|
|
*sourceFormat = GL_RGBA;
|
|
*byteFormat = GL_UNSIGNED_SHORT_5_5_5_1;
|
|
#endif
|
|
break;
|
|
};
|
|
|
|
if(*byteFormat == GL_UNSIGNED_BYTE)
|
|
{
|
|
if (*sourceFormat != GL_COLOR_INDEX)
|
|
*destFormat = *sourceFormat;
|
|
else
|
|
*destFormat = GL_COLOR_INDEX8_EXT;
|
|
|
|
if (pBitmap->getNumMipLevels() > 1 &&
|
|
pBitmap->getFormat() != GBitmap::Palettized &&
|
|
(sgAllowTexCompression && dglDoesSupportTextureCompression()))
|
|
{
|
|
if (*sourceFormat == GL_RGB)
|
|
*destFormat = GL_COMPRESSED_RGB_ARB;
|
|
else if (*sourceFormat == GL_RGBA)
|
|
*destFormat = GL_COMPRESSED_RGBA_ARB;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*destFormat = GL_RGB5_A1;
|
|
}
|
|
|
|
if (sgForce16BitTexture)
|
|
{
|
|
for (U32 i = 0; sg16BitMappings[i].end != true; i++)
|
|
{
|
|
if (*destFormat == sg16BitMappings[i].wanted)
|
|
{
|
|
*destFormat = sg16BitMappings[i].forced;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(*destFormat == GL_RGB)
|
|
*destFormat = GL_RGB8;
|
|
else if(*destFormat == GL_RGBA)
|
|
*destFormat = GL_RGBA8;
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------
|
|
void TextureManager::refresh(TextureObject *to)
|
|
{
|
|
if (!(gDGLRender || sgResurrect))
|
|
return;
|
|
|
|
U32 sourceFormat, destFormat, byteFormat;
|
|
GBitmap *pBitmap = to->bitmap;
|
|
|
|
getSourceDestByteFormat(pBitmap, &sourceFormat, &destFormat, &byteFormat);
|
|
|
|
if (!to->texGLName)
|
|
glGenTextures(1,&to->texGLName);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, to->texGLName);
|
|
GBitmap *pDL = createPaddedBitmap(pBitmap);
|
|
|
|
U32 maxDownloadMip = pDL->getNumMipLevels();
|
|
if (to->type == BitmapTexture ||
|
|
to->type == BitmapKeepTexture ||
|
|
to->type == BitmapNoDownloadTexture)
|
|
{
|
|
maxDownloadMip = 1;
|
|
}
|
|
|
|
if (pDL->getFormat() == GBitmap::Palettized)
|
|
{
|
|
glColorTableEXT(GL_TEXTURE_2D,
|
|
pDL->getPalette()->getPaletteType() == GPalette::RGB ? GL_RGB : GL_RGBA,
|
|
256,
|
|
GL_RGBA,
|
|
GL_UNSIGNED_BYTE,
|
|
pDL->getPalette()->getColors());
|
|
}
|
|
|
|
if (sgDisableSubImage)
|
|
{
|
|
for (U32 i = 0; i < maxDownloadMip; i++)
|
|
{
|
|
glTexImage2D(GL_TEXTURE_2D,
|
|
i,
|
|
destFormat,
|
|
pDL->getWidth(i), pDL->getHeight(i),
|
|
0,
|
|
sourceFormat,
|
|
byteFormat,
|
|
pDL->getBits(i));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (U32 i = 0; i < maxDownloadMip; i++)
|
|
{
|
|
glTexSubImage2D(GL_TEXTURE_2D,
|
|
i,
|
|
0, 0,
|
|
pDL->getWidth(i), pDL->getHeight(i),
|
|
sourceFormat,
|
|
byteFormat,
|
|
pDL->getBits(i));
|
|
}
|
|
}
|
|
|
|
if ((to->type == InteriorTexture || to->type == MeshTexture) &&
|
|
pDL->getNumMipLevels() > 4)
|
|
{
|
|
//
|
|
if (!to->smallTexGLName)
|
|
glGenTextures(1,&to->smallTexGLName);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, to->smallTexGLName);
|
|
|
|
if (pDL->getFormat() == GBitmap::Palettized)
|
|
{
|
|
glColorTableEXT(GL_TEXTURE_2D,
|
|
pDL->getPalette()->getPaletteType() == GPalette::RGB ? GL_RGB : GL_RGBA,
|
|
256,
|
|
GL_RGBA,
|
|
GL_UNSIGNED_BYTE,
|
|
pDL->getPalette()->getColors());
|
|
}
|
|
|
|
if (sgDisableSubImage)
|
|
{
|
|
for (U32 i = 4; i < maxDownloadMip; i++)
|
|
{
|
|
glTexImage2D(GL_TEXTURE_2D,
|
|
i - 4,
|
|
destFormat,
|
|
pDL->getWidth(i), pDL->getHeight(i),
|
|
0,
|
|
sourceFormat,
|
|
byteFormat,
|
|
pDL->getBits(i));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (U32 i = 4; i < maxDownloadMip; i++)
|
|
{
|
|
glTexSubImage2D(GL_TEXTURE_2D,
|
|
i - 4,
|
|
0, 0,
|
|
pDL->getWidth(i), pDL->getHeight(i),
|
|
sourceFormat,
|
|
byteFormat,
|
|
pDL->getBits(i));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (to->smallTexGLName != 0)
|
|
glDeleteTextures(1, &to->smallTexGLName);
|
|
to->smallTexGLName = 0;
|
|
}
|
|
|
|
if(pDL != pBitmap)
|
|
delete pDL;
|
|
}
|
|
|
|
void TextureManager::refresh(TextureObject *to, GBitmap* bmp)
|
|
{
|
|
if (!(gDGLRender || sgResurrect)) return;
|
|
|
|
U32 sourceFormat, destFormat, byteFormat;
|
|
GBitmap* pBitmap = bmp;
|
|
|
|
getSourceDestByteFormat(pBitmap, &sourceFormat, &destFormat, &byteFormat);
|
|
|
|
if (!to->texGLName)
|
|
glGenTextures(1,&to->texGLName);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, to->texGLName);
|
|
GBitmap* pDL = createPaddedBitmap(pBitmap);
|
|
|
|
U32 maxDownloadMip = pDL->getNumMipLevels();
|
|
if (to->type == BitmapTexture ||
|
|
to->type == BitmapKeepTexture ||
|
|
to->type == BitmapNoDownloadTexture)
|
|
{
|
|
maxDownloadMip = 1;
|
|
}
|
|
|
|
if (pDL->getFormat() == GBitmap::Palettized)
|
|
{
|
|
glColorTableEXT(GL_TEXTURE_2D,
|
|
pDL->getPalette()->getPaletteType() == GPalette::RGB ? GL_RGB : GL_RGBA,
|
|
256,
|
|
GL_RGBA,
|
|
GL_UNSIGNED_BYTE,
|
|
pDL->getPalette()->getColors());
|
|
}
|
|
|
|
if (sgDisableSubImage)
|
|
{
|
|
for (U32 i = 0; i < maxDownloadMip; i++)
|
|
{
|
|
glTexImage2D(GL_TEXTURE_2D,
|
|
i,
|
|
destFormat,
|
|
pDL->getWidth(i), pDL->getHeight(i),
|
|
0,
|
|
sourceFormat,
|
|
byteFormat,
|
|
pDL->getBits(i));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (U32 i = 0; i < maxDownloadMip; i++)
|
|
{
|
|
glTexSubImage2D(GL_TEXTURE_2D,
|
|
i,
|
|
0, 0,
|
|
pDL->getWidth(i), pDL->getHeight(i),
|
|
sourceFormat,
|
|
byteFormat,
|
|
pDL->getBits(i));
|
|
}
|
|
}
|
|
|
|
if ((to->type == InteriorTexture || to->type == MeshTexture) &&
|
|
pDL->getNumMipLevels() > 4)
|
|
{
|
|
//
|
|
if (!to->smallTexGLName)
|
|
glGenTextures(1,&to->smallTexGLName);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, to->smallTexGLName);
|
|
glBindTexture(GL_TEXTURE_2D, to->smallTexGLName);
|
|
if (pDL->getFormat() == GBitmap::Palettized)
|
|
{
|
|
glColorTableEXT(GL_TEXTURE_2D,
|
|
pDL->getPalette()->getPaletteType() == GPalette::RGB ? GL_RGB : GL_RGBA,
|
|
256,
|
|
GL_RGBA,
|
|
GL_UNSIGNED_BYTE,
|
|
pDL->getPalette()->getColors());
|
|
}
|
|
|
|
if (sgDisableSubImage)
|
|
{
|
|
for (U32 i = 4; i < maxDownloadMip; i++)
|
|
{
|
|
glTexImage2D(GL_TEXTURE_2D,
|
|
i - 4,
|
|
destFormat,
|
|
pDL->getWidth(i), pDL->getHeight(i),
|
|
0,
|
|
sourceFormat,
|
|
byteFormat,
|
|
pDL->getBits(i));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (U32 i = 4; i < maxDownloadMip; i++)
|
|
{
|
|
glTexSubImage2D(GL_TEXTURE_2D,
|
|
i - 4,
|
|
0, 0,
|
|
pDL->getWidth(i), pDL->getHeight(i),
|
|
sourceFormat,
|
|
byteFormat,
|
|
pDL->getBits(i));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (to->smallTexGLName != 0)
|
|
glDeleteTextures(1, &to->smallTexGLName);
|
|
to->smallTexGLName = 0;
|
|
}
|
|
|
|
if(pDL != pBitmap)
|
|
delete pDL;
|
|
}
|
|
|
|
//--------------------------------------
|
|
bool TextureManager::createGLName(GBitmap* pBitmap,
|
|
bool clampToEdge,
|
|
U32 firstMip,
|
|
TextureHandleType type,
|
|
TextureObject* to)
|
|
{
|
|
if (!(gDGLRender || sgResurrect))
|
|
return 0;
|
|
|
|
glGenTextures(1, &to->texGLName);
|
|
glBindTexture(GL_TEXTURE_2D, to->texGLName);
|
|
|
|
U32 sourceFormat, destFormat, byteFormat;
|
|
|
|
getSourceDestByteFormat(pBitmap, &sourceFormat, &destFormat, &byteFormat);
|
|
|
|
GBitmap *pDL = createPaddedBitmap(pBitmap);
|
|
|
|
U32 maxDownloadMip = pDL->getNumMipLevels();
|
|
if (type == BitmapTexture ||
|
|
type == BitmapKeepTexture ||
|
|
type == BitmapNoDownloadTexture)
|
|
{
|
|
maxDownloadMip = firstMip + 1;
|
|
}
|
|
|
|
if (pDL->getFormat() == GBitmap::Palettized)
|
|
{
|
|
glColorTableEXT(GL_TEXTURE_2D,
|
|
pDL->getPalette()->getPaletteType() == GPalette::RGB ? GL_RGB : GL_RGBA,
|
|
256,
|
|
GL_RGBA,
|
|
GL_UNSIGNED_BYTE,
|
|
pDL->getPalette()->getColors());
|
|
}
|
|
|
|
for (U32 i = firstMip; i < maxDownloadMip; i++)
|
|
{
|
|
glTexImage2D(GL_TEXTURE_2D,
|
|
i - firstMip,
|
|
destFormat,
|
|
pDL->getWidth(i), pDL->getHeight(i),
|
|
0,
|
|
sourceFormat,
|
|
byteFormat,
|
|
pDL->getBits(i));
|
|
}
|
|
|
|
if(to->filterNearest)
|
|
{
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
}
|
|
else
|
|
{
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
if(pBitmap->getNumMipLevels() != 1 &&
|
|
type != BitmapTexture &&
|
|
type != BitmapKeepTexture &&
|
|
type != BitmapNoDownloadTexture)
|
|
{
|
|
if (sgTextureTrilinear || type == BumpTexture || type == InvertedBumpTexture)
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
|
else
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
|
|
|
|
if (dglDoesSupportTexAnisotropy())
|
|
{
|
|
F32 val = 1.0f + sgTextureAnisotropy * dglGetMaxAnisotropy();
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, val);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
}
|
|
}
|
|
|
|
U32 clamp = GL_REPEAT;
|
|
if (clampToEdge)
|
|
clamp = dglDoesSupportEdgeClamp() ? GL_CLAMP_TO_EDGE : GL_CLAMP;
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp);
|
|
|
|
if ((type == InteriorTexture || type == MeshTexture) &&
|
|
(pDL->getNumMipLevels() - firstMip) > 4)
|
|
{
|
|
glGenTextures(1, &to->smallTexGLName);
|
|
glBindTexture(GL_TEXTURE_2D, to->smallTexGLName);
|
|
|
|
if (pDL->getFormat() == GBitmap::Palettized)
|
|
{
|
|
glColorTableEXT(GL_TEXTURE_2D,
|
|
pDL->getPalette()->getPaletteType() == GPalette::RGB ? GL_RGB : GL_RGBA,
|
|
256,
|
|
GL_RGBA,
|
|
GL_UNSIGNED_BYTE,
|
|
pDL->getPalette()->getColors());
|
|
}
|
|
|
|
for (U32 i = firstMip + 4; i < maxDownloadMip; i++)
|
|
{
|
|
glTexImage2D(GL_TEXTURE_2D,
|
|
i - (firstMip + 4),
|
|
destFormat,
|
|
pDL->getWidth(i), pDL->getHeight(i),
|
|
0,
|
|
sourceFormat,
|
|
byteFormat,
|
|
pDL->getBits(i));
|
|
}
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
if (sgTextureTrilinear)
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
|
else
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
|
|
|
|
if (dglDoesSupportTexAnisotropy())
|
|
{
|
|
F32 val = 1.0f + sgTextureAnisotropy * dglGetMaxAnisotropy();
|
|
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, val);
|
|
}
|
|
|
|
U32 clamp = GL_REPEAT;
|
|
if (clampToEdge)
|
|
clamp = dglDoesSupportEdgeClamp() ? GL_CLAMP_TO_EDGE : GL_CLAMP;
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp);
|
|
}
|
|
|
|
if(pDL != pBitmap)
|
|
delete pDL;
|
|
|
|
return to->texGLName != 0;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
//------------------------------------------------------------------------------
|
|
TextureObject* TextureManager::registerTexture(const char* textureName, const GBitmap* data, bool clampToEdge)
|
|
{
|
|
//WARNING: since there's no texture type here, there's no way to tell
|
|
//if it is an inverted bump texture, which would cause it NOT to invert!
|
|
//but, this appears to only be used for RegisteredTextures
|
|
//so it shouldn't make a difference.
|
|
|
|
// if there is no textureName, it isn't inserted into the hash
|
|
// table... merely tracked by the texture manager
|
|
|
|
TextureObject *ret = NULL;
|
|
|
|
if(textureName)
|
|
{
|
|
textureName = StringTable->insert(textureName);
|
|
ret = TextureDictionary::find(textureName, RegisteredTexture, clampToEdge);
|
|
}
|
|
|
|
if(ret)
|
|
{
|
|
// Crucial conditionals for the flush case...
|
|
if (ret->bitmap != data)
|
|
delete ret->bitmap;
|
|
if (ret->texGLName)
|
|
glDeleteTextures(1, (const GLuint*)&ret->texGLName);
|
|
if (ret->smallTexGLName)
|
|
glDeleteTextures(1, (const GLuint*)&ret->smallTexGLName);
|
|
|
|
ret->texGLName = 0;
|
|
ret->smallTexGLName = 0;
|
|
|
|
#ifdef TORQUE_GATHER_METRICS
|
|
AssertFatal(ret->textureSpace <= smTextureSpaceLoaded, "Error, that shouldn't happen!");
|
|
smTextureSpaceLoaded -= ret->textureSpace;
|
|
ret->textureSpace = 0;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
ret = new TextureObject;
|
|
ret->texFileName = textureName;
|
|
ret->texGLName = 0;
|
|
ret->smallTexGLName = 0;
|
|
ret->refCount = 0;
|
|
ret->type = RegisteredTexture;
|
|
ret->holding = false;
|
|
ret->filterNearest = false;
|
|
|
|
TextureDictionary::insert(ret);
|
|
}
|
|
|
|
ret->bitmap = (GBitmap *) data;
|
|
ret->bitmapWidth = data->getWidth();
|
|
ret->bitmapHeight = data->getHeight();
|
|
ret->texWidth = getNextPow2(ret->bitmapWidth);
|
|
ret->texHeight = getNextPow2(ret->bitmapHeight);
|
|
ret->downloadedWidth = ret->texWidth;
|
|
ret->downloadedHeight = ret->texHeight;
|
|
ret->clamp = clampToEdge;
|
|
|
|
#ifdef TORQUE_GATHER_METRICS
|
|
ret->textureSpace = ret->downloadedWidth * ret->downloadedHeight;
|
|
smTextureSpaceLoaded += ret->textureSpace;
|
|
#endif
|
|
|
|
createGLName(ret->bitmap, clampToEdge, 0, ret->type, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
//--------------------------------------
|
|
TextureObject* TextureManager::registerTexture(const char* textureName, GBitmap* bmp, TextureHandleType type, bool clampToEdge)
|
|
{
|
|
//Get this done and out of the way first - if it's an inverted texture,
|
|
//then invert it! Do it in this function because resurrect() calls this directly
|
|
if( type == InvertedBumpTexture )
|
|
{
|
|
// Get pixel address (0,0) of bitmap data for mip level 0.
|
|
U8* pImageBits = bmp->getAddress(0,0,0);
|
|
|
|
// Fetch total pixel count for bitmap.
|
|
U32 PixelCount = bmp->getWidth(0) * bmp->getHeight(0) * bmp->bytesPerPixel;
|
|
|
|
// Invert only the RGB components of the bitmap (leave alpha alone).
|
|
for (U32 index = 0; index < PixelCount; ++index)
|
|
pImageBits[index] ^= 0x00ffffff;
|
|
}
|
|
|
|
// In order to get the right blending, all of the pixels have to be at half intensity.
|
|
// Would normally do this using glPixelTransferf(), but that function is not
|
|
// supported in the D3D wrapper.
|
|
// BUG: detect the case where we're using the D3D wrapper, and use this slower code only in that case.
|
|
if (type == BumpTexture || type == InvertedBumpTexture)
|
|
{
|
|
U8* pImageBits = bmp->getAddress(0,0,0);
|
|
|
|
U32 PixelCount = bmp->getWidth(0) * bmp->getHeight(0) * bmp->bytesPerPixel;
|
|
|
|
for (U32 index = 0; index < PixelCount; index++)
|
|
pImageBits[index] /= 2;
|
|
}
|
|
|
|
TextureObject *ret = NULL;
|
|
if(textureName)
|
|
{
|
|
textureName = StringTable->insert(textureName);
|
|
ret = TextureDictionary::find(textureName, type, clampToEdge);
|
|
}
|
|
|
|
if(ret)
|
|
{
|
|
// Crucial conditionals for the flush case...
|
|
if (ret->bitmap != bmp)
|
|
delete ret->bitmap;
|
|
if (ret->texGLName)
|
|
glDeleteTextures(1, (const GLuint*)&ret->texGLName);
|
|
if (ret->smallTexGLName)
|
|
glDeleteTextures(1, (const GLuint*)&ret->smallTexGLName);
|
|
ret->texGLName = 0;
|
|
ret->smallTexGLName = 0;
|
|
|
|
#ifdef TORQUE_GATHER_METRICS
|
|
AssertFatal(ret->textureSpace <= smTextureSpaceLoaded, "Error, that shouldn't happen!");
|
|
smTextureSpaceLoaded -= ret->textureSpace;
|
|
ret->textureSpace = 0;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
ret = new TextureObject;
|
|
ret->texFileName = textureName;
|
|
ret->texGLName = 0;
|
|
ret->smallTexGLName = 0;
|
|
ret->refCount = 0;
|
|
ret->type = type;
|
|
ret->filterNearest= false;
|
|
|
|
TextureDictionary::insert(ret);
|
|
}
|
|
|
|
ret->bitmap = bmp;
|
|
ret->bitmapWidth = bmp->getWidth();
|
|
ret->bitmapHeight = bmp->getHeight();
|
|
ret->texWidth = getNextPow2(ret->bitmapWidth);
|
|
ret->texHeight = getNextPow2(ret->bitmapHeight);
|
|
ret->clamp = clampToEdge;
|
|
ret->holding = (type == MeshTexture) && ENABLE_HOLDING;
|
|
|
|
if ((ret->type == DetailTexture || ret->type == BumpTexture || ret->type == InvertedBumpTexture) &&
|
|
bmp->getFormat() != GBitmap::Palettized)
|
|
bmp->extrudeMipLevels();
|
|
else if (ret->type != TerrainTexture &&
|
|
ret->type != BitmapTexture &&
|
|
ret->type != BitmapKeepTexture &&
|
|
ret->type != BitmapNoDownloadTexture &&
|
|
bmp->getFormat() != GBitmap::Palettized)
|
|
bmp->extrudeMipLevels(ret->type==ZeroBorderTexture);
|
|
|
|
if(!ret->texGLName)
|
|
{
|
|
U32 firstMip = 0;
|
|
if (ret->bitmap->getNumMipLevels() > 1 &&
|
|
type != DetailTexture &&
|
|
type != BumpTexture &&
|
|
type != InvertedBumpTexture &&
|
|
type != TerrainTexture &&
|
|
type != BitmapTexture &&
|
|
type != BitmapKeepTexture &&
|
|
type != BitmapNoDownloadTexture)
|
|
{
|
|
if (type == SkyTexture)
|
|
{
|
|
firstMip = getMin(sgSkyTextureDetailLevel, ret->bitmap->getNumMipLevels() - 1);
|
|
}
|
|
else if (type == InteriorTexture)
|
|
{
|
|
firstMip = getMin(sgInteriorTextureDetailLevel, ret->bitmap->getNumMipLevels() - 1);
|
|
}
|
|
else
|
|
{
|
|
firstMip = getMin(sgTextureDetailLevel, ret->bitmap->getNumMipLevels() - 1);
|
|
}
|
|
}
|
|
|
|
ret->downloadedWidth = ret->bitmapWidth >> firstMip;
|
|
ret->downloadedHeight = ret->bitmapHeight >> firstMip;
|
|
if (ret->downloadedWidth == 0) ret->downloadedWidth = 1;
|
|
if (ret->downloadedHeight == 0) ret->downloadedHeight = 1;
|
|
|
|
#ifdef TORQUE_GATHER_METRICS
|
|
ret->textureSpace = 0;
|
|
for (U32 i = firstMip; i < ret->bitmap->getNumMipLevels(); i++)
|
|
ret->textureSpace += ret->bitmap->getWidth(i) * ret->bitmap->getHeight(i);
|
|
smTextureSpaceLoaded += ret->textureSpace;
|
|
#endif
|
|
|
|
if(ret->type != BitmapNoDownloadTexture)
|
|
createGLName(bmp, clampToEdge, firstMip, ret->type, ret);
|
|
}
|
|
|
|
if (ret->type == BitmapKeepTexture || ret->type == BitmapNoDownloadTexture)
|
|
{
|
|
// do nothing
|
|
}
|
|
else if (ret->type == TerrainTexture)
|
|
{
|
|
// Don't delete the bitmap
|
|
ret->bitmap = NULL;
|
|
}
|
|
else
|
|
{
|
|
delete ret->bitmap;
|
|
ret->bitmap = NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
//--------------------------------------
|
|
GBitmap *TextureManager::loadBitmapInstance(const char *textureName, bool recurse /* = true */)
|
|
{
|
|
char fileNameBuffer[512];
|
|
dStrcpy(fileNameBuffer, textureName);
|
|
GBitmap *bmp = NULL;
|
|
|
|
// Loop through the supported extensions to find the file.
|
|
U32 len = dStrlen(fileNameBuffer);
|
|
for (U32 i = 0; i < EXT_ARRAY_SIZE && bmp == NULL; i++)
|
|
{
|
|
|
|
if (sgForcePalettedTexture == true && dglDoesSupportPalettedTexture())
|
|
dStrcpy(fileNameBuffer + len, extArray_8[i]);
|
|
else
|
|
dStrcpy(fileNameBuffer + len, extArray[i]);
|
|
|
|
bmp = (GBitmap*)ResourceManager->loadInstance(fileNameBuffer);
|
|
|
|
// CAF: if a jpg, and RGB, look for file.alpha.jpg as alpha channel
|
|
if ( (!sgForcePalettedTexture || !dglDoesSupportPalettedTexture()) && !dStricmp(extArray[i],".jpg") && bmp && bmp->getFormat()==GBitmap::RGB)
|
|
{
|
|
dStrcpy(fileNameBuffer + len, ".alpha.jpg");
|
|
GBitmap * bmpAlpha = (GBitmap*)ResourceManager->loadInstance(fileNameBuffer);
|
|
S32 w = bmp->getWidth();
|
|
S32 h = bmp->getHeight();
|
|
if (bmpAlpha && bmpAlpha->getWidth() == w && bmpAlpha->getHeight() == h && bmpAlpha->bytesPerPixel==1)
|
|
{
|
|
GBitmap * bmp2 = new GBitmap(w,h,false,GBitmap::RGBA);
|
|
U8 * rgbBits = bmp->getWritableBits();
|
|
U8 * alphaBits = bmpAlpha->getWritableBits();
|
|
U8 * bmpBits = bmp2->getWritableBits();
|
|
for (S32 wi=0; wi<w; wi++)
|
|
{
|
|
for (S32 hi=0; hi<h; hi++)
|
|
{
|
|
bmpBits[wi*4 + hi*4*w + 0] = rgbBits[wi*3 + hi*3*w + 0];
|
|
bmpBits[wi*4 + hi*4*w + 1] = rgbBits[wi*3 + hi*3*w + 1];
|
|
bmpBits[wi*4 + hi*4*w + 2] = rgbBits[wi*3 + hi*3*w + 2];
|
|
bmpBits[wi*4 + hi*4*w + 3] = alphaBits[wi + hi*w];
|
|
}
|
|
}
|
|
delete bmpAlpha;
|
|
delete bmp;
|
|
bmp = bmp2;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If unable to load texture in current directory
|
|
// look in the parent directory. But never look in the root.
|
|
fileNameBuffer[len] = 0;
|
|
if (!bmp && recurse)
|
|
{
|
|
char *name = dStrrchr(fileNameBuffer, '/');
|
|
if (name)
|
|
{
|
|
*name++ = 0;
|
|
char *parent = dStrrchr(fileNameBuffer, '/');
|
|
if (parent)
|
|
{
|
|
parent[1] = 0;
|
|
dStrcat(fileNameBuffer, name);
|
|
return loadBitmapInstance(fileNameBuffer);
|
|
}
|
|
}
|
|
}
|
|
return bmp;
|
|
}
|
|
|
|
//--------------------------------------
|
|
|
|
TextureObject *TextureManager::loadTexture(const char* textureName, TextureHandleType type, bool clampToEdge, bool checkOnly /* = false */)
|
|
{
|
|
// Catch if we're trying to load a blank texture...
|
|
if(!textureName || dStrlen(textureName) == 0)
|
|
return NULL;
|
|
|
|
textureName = StringTable->insert(textureName);
|
|
|
|
TextureObject *ret = TextureDictionary::find(textureName, type, clampToEdge);
|
|
|
|
GBitmap *bmp = NULL;
|
|
|
|
if(!ret)
|
|
{
|
|
// Ok, no hit - is it in the current dir? If so then let's grab it
|
|
// and use it.
|
|
bmp = loadBitmapInstance(textureName, false);
|
|
|
|
if(bmp)
|
|
return registerTexture(textureName, bmp, type, clampToEdge);
|
|
|
|
// Otherwise...
|
|
// We want to check for previously loaded textures with the same
|
|
// name in higher directories. loadBitmapInstance does this too
|
|
// but we want to reuse the actual texture object, if possible,
|
|
// not just load things many times into video memory!
|
|
char fileNameBuffer[512];
|
|
dStrcpy(fileNameBuffer, textureName);
|
|
|
|
// If unable to load texture in current directory
|
|
// look in the parent directory. But never look in the root.
|
|
char *name = dStrrchr(fileNameBuffer, '/');
|
|
if (name)
|
|
{
|
|
*name++ = 0;
|
|
char *parent = dStrrchr(fileNameBuffer, '/');
|
|
if (parent)
|
|
{
|
|
parent[1] = 0;
|
|
dStrcat(fileNameBuffer, name);
|
|
ret = loadTexture(fileNameBuffer, type, clampToEdge, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ret)
|
|
return ret;
|
|
|
|
// If we're just checking, fail out so we eventually get around to
|
|
// loading a real bitmap.
|
|
if(checkOnly)
|
|
return NULL;
|
|
|
|
// Ok, no success so let's try actually loading a texture.
|
|
bmp = loadBitmapInstance(textureName);
|
|
|
|
if(!bmp)
|
|
{
|
|
Con::warnf("Could not locate texture: %s", textureName);
|
|
return NULL;
|
|
}
|
|
|
|
return registerTexture(textureName, bmp, type, clampToEdge);
|
|
}
|
|
|
|
|
|
//--------------------------------------
|
|
|
|
void TextureHandle::setFilterNearest()
|
|
{
|
|
if (object)
|
|
{
|
|
object->filterNearest = true;
|
|
|
|
if(object->texGLName != 0)
|
|
{
|
|
glBindTexture(GL_TEXTURE_2D, object->texGLName);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TextureHandle::setClamp(const bool c)
|
|
{
|
|
if (object)
|
|
{
|
|
object->clamp = c;
|
|
if (object->texGLName != 0)
|
|
{
|
|
glBindTexture(GL_TEXTURE_2D, object->texGLName);
|
|
GLenum clamp;
|
|
if (c)
|
|
clamp = dglDoesSupportEdgeClamp() ? GL_CLAMP_TO_EDGE : GL_CLAMP;
|
|
else
|
|
clamp = GL_REPEAT;
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp);
|
|
}
|
|
|
|
if (object->smallTexGLName != 0)
|
|
{
|
|
glBindTexture(GL_TEXTURE_2D, object->smallTexGLName);
|
|
|
|
GLenum clamp;
|
|
if (c)
|
|
clamp = dglDoesSupportEdgeClamp() ? GL_CLAMP_TO_EDGE : GL_CLAMP;
|
|
else
|
|
clamp = GL_REPEAT;
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(TORQUE_DEBUG)
|
|
// The non-debug version of this is inlined in the header file
|
|
|
|
#define MAX_THOBJECT 1024
|
|
static TextureObject *thobject[MAX_THOBJECT];
|
|
static int htracked = 0;
|
|
|
|
void TextureHandle::lock()
|
|
{
|
|
AssertFatal(TextureManager::isActive(), "TextureHandle::lock - TextureManager must be active to lock a texture.");
|
|
|
|
if(object)
|
|
{
|
|
if (object->refCount>10000 || object->refCount<0) // some reasonable numbers.
|
|
{
|
|
for (int i=0; i<htracked; i++)
|
|
{
|
|
if (thobject[i]==object)
|
|
return;
|
|
}
|
|
Con::warnf(ConsoleLogEntry::Assert, "Texture refcount out of range: tex<%p> cnt<%d>", object, object->refCount);
|
|
if (htracked<MAX_THOBJECT)
|
|
{
|
|
thobject[htracked] = object;
|
|
htracked++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
object->refCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TextureHandle::unlock()
|
|
{
|
|
// Do nothing if the manager isn't active.
|
|
if(!TextureManager::isActive())
|
|
return;
|
|
|
|
if(object)
|
|
{
|
|
object->refCount--;
|
|
if (object->holding == false)
|
|
{
|
|
if(!object->refCount)
|
|
TextureManager::freeTexture(object);
|
|
}
|
|
else
|
|
{
|
|
// dc - try outputting this error to console instead of assert box, and keep safe to continue.
|
|
if (object->refCount<0)
|
|
{
|
|
Con::warnf(ConsoleLogEntry::Assert, "Texture holding out of balance: %d (0x%x)",
|
|
object->refCount, object->refCount);
|
|
object->refCount++; // put back for safety, in case we did something we shouldn't have.
|
|
}
|
|
}
|
|
|
|
object = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef TORQUE_GATHER_METRICS
|
|
F32 TextureManager::getResidentFraction()
|
|
{
|
|
U32 resident = 0;
|
|
U32 total = 0;
|
|
|
|
Vector<GLuint> names;
|
|
|
|
TextureObject* pProbe = TextureDictionary::smTOList;
|
|
while (pProbe != NULL)
|
|
{
|
|
if (pProbe->texGLName != 0)
|
|
{
|
|
total++;
|
|
names.push_back(pProbe->texGLName);
|
|
}
|
|
|
|
pProbe = pProbe->next;
|
|
}
|
|
|
|
if (total == 0)
|
|
return 1.0f;
|
|
|
|
Vector<GLboolean> isResident;
|
|
isResident.setSize(names.size());
|
|
|
|
glAreTexturesResident(names.size(), names.address(), isResident.address());
|
|
for (U32 i = 0; i < names.size(); i++)
|
|
if (isResident[i] == GL_TRUE)
|
|
resident++;
|
|
|
|
return (F32(resident) / F32(total));
|
|
}
|
|
#endif
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
ChunkedTextureObject *gChunkedTextureList = NULL;
|
|
|
|
ChunkedTextureObject* ChunkedTextureManager::loadTexture(const char *textureName)
|
|
{
|
|
if(!textureName)
|
|
return NULL;
|
|
StringTableEntry tName = StringTable->insert(textureName);
|
|
|
|
for(ChunkedTextureObject *walk = gChunkedTextureList; walk; walk = walk->next)
|
|
if(walk->texFileName == tName)
|
|
return walk;
|
|
GBitmap *bmp = TextureManager::loadBitmapInstance(textureName);
|
|
if(!bmp)
|
|
return NULL;
|
|
return registerTexture(textureName, bmp, false);
|
|
}
|
|
|
|
ChunkedTextureObject* ChunkedTextureManager::registerTexture(const char *textureName, GBitmap *data, bool keep)
|
|
{
|
|
ChunkedTextureObject *ret = NULL;
|
|
StringTableEntry tName = NULL;
|
|
|
|
if(textureName)
|
|
{
|
|
tName = StringTable->insert(textureName);
|
|
for(ChunkedTextureObject *walk = gChunkedTextureList; walk; walk = walk->next)
|
|
{
|
|
if(walk->texFileName == tName)
|
|
{
|
|
ret = walk;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ret && ret->bitmap)
|
|
{
|
|
delete ret->bitmap;
|
|
ret->bitmap = data;
|
|
}
|
|
else
|
|
{
|
|
ret = new ChunkedTextureObject;
|
|
ret->bitmap = data;
|
|
ret->texFileName = tName;
|
|
ret->next = gChunkedTextureList;
|
|
gChunkedTextureList = ret;
|
|
ret->texWidthCount = (data->getWidth() + 255) >> 8;
|
|
ret->texHeightCount = (data->getHeight() + 255) >> 8;
|
|
ret->width = data->getWidth();
|
|
ret->height = data->getHeight();
|
|
ret->textureHandles = NULL;
|
|
ret->refCount = 0;
|
|
}
|
|
|
|
refresh(ret);
|
|
|
|
if(!keep)
|
|
{
|
|
delete ret->bitmap;
|
|
ret->bitmap = NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void ChunkedTextureManager::freeTexture(ChunkedTextureObject *to)
|
|
{
|
|
// remove it from the linked list
|
|
|
|
for(ChunkedTextureObject **walk = &gChunkedTextureList; *walk; walk = &((*walk)->next))
|
|
{
|
|
if(*walk == to)
|
|
{
|
|
*walk = to->next;
|
|
delete[] to->textureHandles;
|
|
delete to->bitmap;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ChunkedTextureManager::refresh(ChunkedTextureObject *to)
|
|
{
|
|
if(!to->bitmap)
|
|
return;
|
|
|
|
if(to->textureHandles)
|
|
{
|
|
delete[] to->textureHandles;
|
|
to->textureHandles = NULL;
|
|
}
|
|
|
|
to->textureHandles = new TextureHandle[to->texWidthCount * to->texHeightCount];
|
|
|
|
for(U32 j = 0; j < to->texHeightCount; j++)
|
|
{
|
|
U32 y = j * 256;
|
|
U32 height = getMin(to->bitmap->getHeight() - y, U32(256));
|
|
|
|
for(U32 i = 0; i < to->texWidthCount; i++)
|
|
{
|
|
U32 index = j * to->texWidthCount + i;
|
|
U32 x = i * 256;
|
|
U32 width = getMin(to->bitmap->getWidth() - x, U32(256));
|
|
|
|
GBitmap *tempBitmap = new GBitmap(width, height, false, to->bitmap->getFormat());
|
|
for(U32 lp = 0; lp < height; lp++)
|
|
{
|
|
const U8 *src = to->bitmap->getAddress(x, y + lp);
|
|
U8 *dest = tempBitmap->getAddress(0, lp);
|
|
dMemcpy(dest, src, width * to->bitmap->bytesPerPixel);
|
|
}
|
|
to->textureHandles[index] = TextureHandle(NULL, tempBitmap, BitmapTexture, true);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void ChunkedTextureManager::makeZombie()
|
|
{
|
|
for(ChunkedTextureObject *walk = gChunkedTextureList; walk; walk = walk->next)
|
|
{
|
|
delete[] walk->textureHandles;
|
|
walk->textureHandles = NULL;
|
|
}
|
|
}
|
|
|
|
void ChunkedTextureManager::resurrect()
|
|
{
|
|
for(ChunkedTextureObject *walk = gChunkedTextureList; walk; walk = walk->next)
|
|
{
|
|
GBitmap *bmp = walk->bitmap;
|
|
if(!bmp)
|
|
walk->bitmap = TextureManager::loadBitmapInstance(walk->texFileName);
|
|
|
|
refresh(walk);
|
|
|
|
if(!bmp)
|
|
{
|
|
delete walk->bitmap;
|
|
walk->bitmap = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
TextureHandle ChunkedTextureHandle::getSubTexture(U32 x, U32 y)
|
|
{
|
|
if(!object || !object->textureHandles)
|
|
return NULL;
|
|
return object->textureHandles[x + y * object->texWidthCount];
|
|
}
|
|
|
|
|
|
void ChunkedTextureHandle::lock()
|
|
{
|
|
if(object)
|
|
object->refCount++;
|
|
}
|
|
|
|
void ChunkedTextureHandle::unlock()
|
|
{
|
|
if(object)
|
|
{
|
|
object->refCount--;
|
|
if(object->refCount == 0)
|
|
ChunkedTextureManager::freeTexture(object);
|
|
}
|
|
}
|