Merge pull request #1711 from marauder2k9-torque/GLFix-BlackTerrain-issue
Some checks failed
Linux Build / Ubuntu Latest GCC (push) Has been cancelled
MacOSX Build / MacOSX Latest Clang (push) Has been cancelled
Windows Build / Windows Latest MSVC (push) Has been cancelled

OPENGL: Terrain doesnt render correctly
This commit is contained in:
Brian Roberts 2026-04-11 18:12:37 -05:00 committed by GitHub
commit 12dddd07b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 447 additions and 549 deletions

View file

@ -1094,7 +1094,7 @@ void ScatterSky::_render( ObjectRenderInst *ri, SceneRenderState *state, BaseMat
} }
else else
{ {
GFX->setCubeTexture( 0, NULL ); GFX->setTexture( 0, NULL );
mShaderConsts->setSafe( mUseCubemapSC, 0.0f ); mShaderConsts->setSafe( mUseCubemapSC, 0.0f );
} }

View file

@ -41,15 +41,16 @@
#endif #endif
static bool sReadPNG(const Torque::Path& path, GBitmap* bitmap);
static bool sReadStreamPNG(Stream& stream, GBitmap* bitmap, U32 len);
/// Compression levels for PNGs range from 0-9. /// Compression levels for PNGs range from 0-9.
/// A value outside that range will cause the write routine to look for the best compression for a given PNG. This can be slow. /// A value outside that range will cause the write routine to look for the best compression for a given PNG. This can be slow.
static bool sReadPNG(const Torque::Path& path, GBitmap* bitmap);
static bool sReadStreamPNG(Stream& stream, GBitmap* bitmap, U32 len);
static bool sWritePNG(const Torque::Path& path, GBitmap* bitmap, U32 compressionLevel); static bool sWritePNG(const Torque::Path& path, GBitmap* bitmap, U32 compressionLevel);
static bool sWriteStreamPNG(const String& bmType, Stream& stream, GBitmap* bitmap, U32 compressionLevel); static bool sWriteStreamPNG(const String& bmType, Stream& stream, GBitmap* bitmap, U32 compressionLevel);
static bool _writePNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel, U32 strategy, U32 filter);
// Internal functions used.
static bool _readStreamPNG(Stream& stream, GBitmap* bitmap, U32 len, const char* filename);
static bool _writePNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel, U32 strategy, U32 filter, const char* filename = "unknown");
static struct _privateRegisterPNG static struct _privateRegisterPNG
{ {
@ -72,421 +73,390 @@ static struct _privateRegisterPNG
} }
} sStaticRegisterPNG; } sStaticRegisterPNG;
//-----------------------------------------------------------------------------
//-------------------------------------- Replacement I/O for standard LIBPng // PNG context passed to libpng for error reporting
// functions. we don't wanna use //-----------------------------------------------------------------------------
// FILE*'s... struct PngContext
static void pngReadDataFn(png_structp png_ptr,
png_bytep data,
png_size_t length)
{ {
AssertFatal(png_get_io_ptr(png_ptr) != NULL, "No stream?"); const char* filename;
explicit PngContext(const char* f) : filename(f) {}
};
//-----------------------------------------------------------------------------
// libpng callbacks
//-----------------------------------------------------------------------------
static void pngReadDataFn(png_structp png_ptr, png_bytep data, png_size_t length)
{
Stream* strm = (Stream*)png_get_io_ptr(png_ptr); Stream* strm = (Stream*)png_get_io_ptr(png_ptr);
bool success = strm->read(length, data); AssertFatal(strm, "pngReadDataFn - No stream.");
AssertFatal(success, "pngReadDataFn - failed to read from stream!"); if (!strm->read(length, data))
Con::errorf("pngReadDataFn - Failed to read %u bytes from stream.", (U32)length);
} }
static void pngWriteDataFn(png_structp png_ptr, png_bytep data, png_size_t length)
//--------------------------------------
static void pngWriteDataFn(png_structp png_ptr,
png_bytep data,
png_size_t length)
{ {
AssertFatal(png_get_io_ptr(png_ptr) != NULL, "No stream?");
Stream* strm = (Stream*)png_get_io_ptr(png_ptr); Stream* strm = (Stream*)png_get_io_ptr(png_ptr);
bool success = strm->write(length, data); AssertFatal(strm, "pngWriteDataFn - No stream.");
AssertFatal(success, "pngWriteDataFn - failed to write to stream!"); if (!strm->write(length, data))
Con::errorf("pngWriteDataFn - Failed to write %u bytes to stream.", (U32)length);
} }
static void pngFlushDataFn(png_structp /*png_ptr*/) {}
//-------------------------------------- static png_voidp pngMallocFn(png_structp /*png_ptr*/, png_size_t size) { return FrameAllocator::alloc(size); }
static void pngFlushDataFn(png_structp /*png_ptr*/) static void pngFreeFn(png_structp /*png_ptr*/, png_voidp /*mem*/) {}
static png_voidp pngRealMallocFn(png_structp /*png_ptr*/, png_size_t size) { return (png_voidp)dMalloc(size); }
static void pngRealFreeFn(png_structp /*png_ptr*/, png_voidp mem) { dFree(mem); }
static void pngFatalErrorFn(png_structp png_ptr, png_const_charp pMessage)
{ {
// const char* filename = "unknown";
if (png_ptr)
{
PngContext* ctx = (PngContext*)png_get_error_ptr(png_ptr);
if (ctx && ctx->filename)
filename = ctx->filename;
}
Con::errorf("pngFatalErrorFn - Fatal error in '%s': %s", filename, pMessage);
AssertISV(false, avar("libpng fatal error in '%s':\n%s", filename, pMessage));
} }
static png_voidp pngMallocFn(png_structp /*png_ptr*/, png_size_t size) static void pngWarningFn(png_structp png_ptr, png_const_charp pMessage)
{ {
return FrameAllocator::alloc(size); #if TORQUE_DEBUG
const char* filename = "unknown";
if (png_ptr)
{
PngContext* ctx = (PngContext*)png_get_error_ptr(png_ptr);
if (ctx && ctx->filename)
filename = ctx->filename;
}
Con::warnf("pngWarningFn - Warning in '%s': %s", filename, pMessage);
#endif
} }
static void pngFreeFn(png_structp /*png_ptr*/, png_voidp /*mem*/) //-----------------------------------------------------------------------------
// RAII guards for png read/write structs
//-----------------------------------------------------------------------------
struct PngReadGuard
{ {
} png_structp png_ptr = nullptr;
png_infop info_ptr = nullptr;
png_infop end_info = nullptr;
PngContext ctx;
static png_voidp pngRealMallocFn(png_structp /*png_ptr*/, png_size_t size) explicit PngReadGuard(const char* filename) : ctx(filename) {}
bool init()
{ {
return (png_voidp)dMalloc(size); png_ptr = png_create_read_struct_2(PNG_LIBPNG_VER_STRING,
} &ctx, pngFatalErrorFn, pngWarningFn,
NULL, pngRealMallocFn, pngRealFreeFn);
static void pngRealFreeFn(png_structp /*png_ptr*/, png_voidp mem) if (!png_ptr)
{ {
dFree(mem); Con::errorf("PngReadGuard - png_create_read_struct_2 failed for '%s'.", ctx.filename);
}
//--------------------------------------
static void pngFatalErrorFn(png_structp /*png_ptr*/,
png_const_charp pMessage)
{
AssertISV(false, avar("Error reading PNG file:\n %s", pMessage));
}
//--------------------------------------
static void pngWarningFn(png_structp, png_const_charp /*pMessage*/)
{
// AssertWarn(false, avar("Warning reading PNG file:\n %s", pMessage));
}
//--------------------------------------
bool sReadPNG(const Torque::Path& path, GBitmap* bitmap)
{
FileStream* readPng = new FileStream;
if (!readPng->open(path.getFullPath(), Torque::FS::File::Read))
{
Con::printf("Failed to open PNG :%s", path.getFullFileName().c_str());
return false; return false;
} }
if (!sReadStreamPNG(*readPng, bitmap, U32_MAX)) info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
{ {
Con::printf("Failed to read PNG :%s", path.getFullFileName().c_str()); Con::errorf("PngReadGuard - png_create_info_struct (info) failed for '%s'.", ctx.filename);
return false; return false;
} }
readPng->close(); end_info = png_create_info_struct(png_ptr);
if (!end_info)
{
Con::errorf("PngReadGuard - png_create_info_struct (end) failed for '%s'.", ctx.filename);
return false;
}
return true; return true;
} }
static bool sReadStreamPNG(Stream& stream, GBitmap* bitmap, U32 len) ~PngReadGuard()
{ {
PROFILE_SCOPE(sReadPNG); if (png_ptr)
static const U32 cs_headerBytesChecked = 8; png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
U8 header[cs_headerBytesChecked];
stream.read(cs_headerBytesChecked, header);
bool isPng = png_check_sig(header, cs_headerBytesChecked) != 0;
if (isPng == false)
{
AssertWarn(false, "GBitmap::readPNG: stream doesn't contain a PNG");
return false;
} }
};
U32 prevWaterMark = FrameAllocator::getWaterMark(); struct PngWriteGuard
png_structp png_ptr = png_create_read_struct_2(PNG_LIBPNG_VER_STRING, {
png_structp png_ptr = nullptr;
png_infop info_ptr = nullptr;
PngContext ctx;
explicit PngWriteGuard(const char* filename) : ctx(filename) {}
bool init(bool useFrameAllocator = true)
{
png_ptr = png_create_write_struct_2(PNG_LIBPNG_VER_STRING,
&ctx, pngFatalErrorFn, pngWarningFn,
NULL, NULL,
pngFatalErrorFn, useFrameAllocator ? pngMallocFn : pngRealMallocFn,
pngWarningFn, useFrameAllocator ? pngFreeFn : pngRealFreeFn);
NULL, if (!png_ptr)
pngRealMallocFn,
pngRealFreeFn);
if (png_ptr == NULL)
{ {
FrameAllocator::setWaterMark(prevWaterMark); Con::errorf("PngWriteGuard - png_create_write_struct_2 failed for '%s'.", ctx.filename);
return false; return false;
} }
png_infop info_ptr = png_create_info_struct(png_ptr); info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) if (!info_ptr)
{ {
png_destroy_read_struct(&png_ptr, Con::errorf("PngWriteGuard - png_create_info_struct failed for '%s'.", ctx.filename);
(png_infopp)NULL,
(png_infopp)NULL);
FrameAllocator::setWaterMark(prevWaterMark);
return false; return false;
} }
png_infop end_info = png_create_info_struct(png_ptr); return true;
if (end_info == NULL)
{
png_destroy_read_struct(&png_ptr,
&info_ptr,
(png_infopp)NULL);
FrameAllocator::setWaterMark(prevWaterMark);
return false;
} }
png_set_read_fn(png_ptr, &stream, pngReadDataFn); ~PngWriteGuard()
{
if (png_ptr)
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
}
};
// Read off the info on the image. //-----------------------------------------------------------------------------
png_set_sig_bytes(png_ptr, cs_headerBytesChecked); // Shared helpers
png_read_info(png_ptr, info_ptr); //-----------------------------------------------------------------------------
static GFXFormat _determineReadFormat(png_structp png_ptr, png_infop info_ptr, bool& transAlpha)
{
S32 bit_depth, color_type;
png_uint_32 width, height;
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);
// OK, at this point, if we have reached it ok, then we can reset the transAlpha = false;
// image to accept the new data...
//
bitmap->deleteImage();
png_uint_32 width;
png_uint_32 height;
S32 bit_depth;
S32 color_type;
png_get_IHDR(png_ptr, info_ptr,
&width, &height, // obv.
&bit_depth, &color_type, // obv.
NULL, // interlace
NULL, // compression_type
NULL); // filter_type
// First, handle the color transformations. We need this to read in the
// data as RGB or RGBA, _always_, with a maximal channel width of 8 bits.
//
bool transAlpha = false;
GFXFormat format = GFXFormatR8G8B8; GFXFormat format = GFXFormatR8G8B8;
// Strip off any 16 bit info
//
if (bit_depth == 16 && color_type != PNG_COLOR_TYPE_GRAY) if (bit_depth == 16 && color_type != PNG_COLOR_TYPE_GRAY)
{
png_set_strip_16(png_ptr); png_set_strip_16(png_ptr);
}
// Expand a transparency channel into a full alpha channel...
//
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
{ {
png_set_expand(png_ptr); png_set_expand(png_ptr);
transAlpha = true; transAlpha = true;
} }
if (color_type == PNG_COLOR_TYPE_PALETTE) switch (color_type)
{ {
case PNG_COLOR_TYPE_PALETTE:
png_set_expand(png_ptr); png_set_expand(png_ptr);
format = transAlpha ? GFXFormatR8G8B8A8 : GFXFormatR8G8B8; format = transAlpha ? GFXFormatR8G8B8A8 : GFXFormatR8G8B8;
} break;
else if (color_type == PNG_COLOR_TYPE_GRAY) case PNG_COLOR_TYPE_GRAY:
{
png_set_expand(png_ptr); png_set_expand(png_ptr);
format = (bit_depth == 16) ? GFXFormatR5G6B5 : GFXFormatA8;
if (bit_depth == 16) break;
format = GFXFormatR5G6B5; case PNG_COLOR_TYPE_GRAY_ALPHA:
else
format = GFXFormatA8;
}
else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
{
png_set_expand(png_ptr); png_set_expand(png_ptr);
png_set_gray_to_rgb(png_ptr); png_set_gray_to_rgb(png_ptr);
format = GFXFormatR8G8B8A8; format = GFXFormatR8G8B8A8;
} break;
else if (color_type == PNG_COLOR_TYPE_RGB) case PNG_COLOR_TYPE_RGB:
{
format = transAlpha ? GFXFormatR8G8B8A8 : GFXFormatR8G8B8;
png_set_expand(png_ptr); png_set_expand(png_ptr);
} format = transAlpha ? GFXFormatR8G8B8A8 : GFXFormatR8G8B8;
else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) break;
{ case PNG_COLOR_TYPE_RGB_ALPHA:
png_set_expand(png_ptr); png_set_expand(png_ptr);
format = GFXFormatR8G8B8A8; format = GFXFormatR8G8B8A8;
break;
default:
Con::errorf("_determineReadFormat - Unrecognised color_type %d.", color_type);
break;
} }
// Update the info pointer with the result of the transformations return format;
// above... }
png_read_update_info(png_ptr, info_ptr);
png_uint_32 rowBytes = png_get_rowbytes(png_ptr, info_ptr); static bool _validateRowBytes(const char* filename, GFXFormat format, U32 rowBytes, U32 width)
if (format == GFXFormatR8G8B8)
{ {
AssertFatal(rowBytes == width * 3, const U32 expected =
"Error, our rowbytes are incorrect for this transform... (3)"); (format == GFXFormatR8G8B8) ? width * 3 :
} (format == GFXFormatR8G8B8A8) ? width * 4 :
else if (format == GFXFormatR8G8B8A8) (format == GFXFormatR5G6B5) ? width * 2 : 0;
if (expected && rowBytes != expected)
{ {
AssertFatal(rowBytes == width * 4, Con::errorf("_validateRowBytes - '%s': rowBytes %d != expected %d for format %d.",
"Error, our rowbytes are incorrect for this transform... (4)"); filename, rowBytes, expected, format);
return false;
} }
else if (format == GFXFormatR5G6B5)
{
AssertFatal(rowBytes == width * 2,
"Error, our rowbytes are incorrect for this transform... (2)");
}
// actually allocate the bitmap space...
bitmap->allocateBitmap(width, height,
false, // don't extrude miplevels...
format); // use determined format...
// Set up the row pointers...
png_bytep* rowPointers = new png_bytep[ height ];
U8* pBase = (U8*)bitmap->getBits();
for (U32 i = 0; i < height; i++)
rowPointers[i] = pBase + (i * rowBytes);
// And actually read the image!
png_read_image(png_ptr, rowPointers);
// We're outta here, destroy the png structs, and release the lock
// as quickly as possible...
//png_read_end(png_ptr, end_info);
delete [] rowPointers;
png_read_end(png_ptr, NULL);
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
// Ok, the image is read in, now we need to finish up the initialization,
// which means: setting up the detailing members, init'ing the palette
// key, etc...
//
// actually, all of that was handled by allocateBitmap, so we're outta here
//
// Check this bitmap for transparency
bitmap->checkForTransparency();
FrameAllocator::setWaterMark(prevWaterMark);
return true; return true;
} }
//-------------------------------------------------------------------------- static void _applyWriteIHDR(png_structp png_ptr, png_infop info_ptr, GFXFormat format, U32 width, U32 height)
static bool _writePNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel, U32 strategy, U32 filter)
{ {
GFXFormat format = bitmap->getFormat(); switch (format)
// ONLY RGB bitmap writing supported at this time!
AssertFatal( format == GFXFormatR8G8B8 ||
format == GFXFormatR8G8B8A8 ||
format == GFXFormatR8G8B8X8 ||
format == GFXFormatA8 ||
format == GFXFormatR5G6B5 ||
format == GFXFormatR8G8B8A8_LINEAR_FORCE, "_writePNG: ONLY RGB bitmap writing supported at this time.");
if ( format != GFXFormatR8G8B8 &&
format != GFXFormatR8G8B8A8 &&
format != GFXFormatR8G8B8X8 &&
format != GFXFormatA8 &&
format != GFXFormatR5G6B5 && format != GFXFormatR8G8B8A8_LINEAR_FORCE)
return false;
png_structp png_ptr = png_create_write_struct_2(PNG_LIBPNG_VER_STRING,
NULL,
pngFatalErrorFn,
pngWarningFn,
NULL,
pngMallocFn,
pngFreeFn);
if (png_ptr == NULL)
return (false);
png_infop info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL)
{ {
png_destroy_write_struct(&png_ptr, (png_infopp)NULL); case GFXFormatR8G8B8:
return false; png_set_IHDR(png_ptr, info_ptr, width, height, 8,
} PNG_COLOR_TYPE_RGB, NULL, NULL, NULL);
break;
png_set_write_fn(png_ptr, &stream, pngWriteDataFn, pngFlushDataFn); case GFXFormatR8G8B8A8:
case GFXFormatR8G8B8X8:
// Set the compression level and image filters case GFXFormatR8G8B8A8_LINEAR_FORCE:
png_set_compression_window_bits(png_ptr, 15); png_set_IHDR(png_ptr, info_ptr, width, height, 8,
png_set_compression_level(png_ptr, compressionLevel); PNG_COLOR_TYPE_RGB_ALPHA, NULL, NULL, NULL);
png_set_filter(png_ptr, 0, filter); break;
case GFXFormatA8:
// Set the image information here. Width and height are up to 2^31, png_set_IHDR(png_ptr, info_ptr, width, height, 8,
// bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on PNG_COLOR_TYPE_GRAY, NULL, NULL, NULL);
// the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY, break;
// PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB, case GFXFormatR5G6B5:
// or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
// PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
// currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
U32 width = bitmap->getWidth();
U32 height = bitmap->getHeight();
if (format == GFXFormatR8G8B8)
{ {
png_set_IHDR(png_ptr, info_ptr, png_set_IHDR(png_ptr, info_ptr, width, height, 16,
width, height, // the width & height PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE,
8, PNG_COLOR_TYPE_RGB, // bit_depth, color_type, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
NULL, // no interlace
NULL, // compression type
NULL); // filter type
}
else if (format == GFXFormatR8G8B8A8 || format == GFXFormatR8G8B8X8 || format == GFXFormatR8G8B8A8_LINEAR_FORCE)
{
png_set_IHDR(png_ptr, info_ptr,
width, height, // the width & height
8, PNG_COLOR_TYPE_RGB_ALPHA, // bit_depth, color_type,
NULL, // no interlace
NULL, // compression type
NULL); // filter type
}
else if (format == GFXFormatA8)
{
png_set_IHDR(png_ptr, info_ptr,
width, height, // the width & height
8, PNG_COLOR_TYPE_GRAY, // bit_depth, color_type,
NULL, // no interlace
NULL, // compression type
NULL); // filter type
}
else if (format == GFXFormatR5G6B5)
{
png_set_IHDR(png_ptr, info_ptr,
width, height, // the width & height
16, PNG_COLOR_TYPE_GRAY, // bit_depth, color_type,
PNG_INTERLACE_NONE, // no interlace
PNG_COMPRESSION_TYPE_DEFAULT, // compression type
PNG_FILTER_TYPE_DEFAULT); // filter type
png_color_8_struct sigBit = { 0 }; png_color_8_struct sigBit = { 0 };
sigBit.gray = 16; sigBit.gray = 16;
png_set_sBIT(png_ptr, info_ptr, &sigBit); png_set_sBIT(png_ptr, info_ptr, &sigBit);
png_set_swap(png_ptr); png_set_swap(png_ptr);
break;
}
default:
break;
}
} }
png_write_info(png_ptr, info_ptr); static bool _isSupportedWriteFormat(GFXFormat format)
FrameAllocatorMarker marker; {
png_bytep* row_pointers = (png_bytep*)marker.alloc( height * sizeof( png_bytep ) ); switch (format)
{
case GFXFormatR8G8B8:
case GFXFormatR8G8B8A8:
case GFXFormatR8G8B8X8:
case GFXFormatR8G8B8A8_LINEAR_FORCE:
case GFXFormatA8:
case GFXFormatR5G6B5:
return true;
default:
return false;
}
}
//-----------------------------------------------------------------------------
// Core read / write
//-----------------------------------------------------------------------------
static bool _readStreamPNG(Stream& stream, GBitmap* bitmap, U32 /*len*/, const char* filename = "unknown")
{
PROFILE_SCOPE(sReadPNG);
static const U32 cs_headerBytesChecked = 8;
U8 header[cs_headerBytesChecked];
stream.read(cs_headerBytesChecked, header);
if (!png_check_sig(header, cs_headerBytesChecked))
{
Con::errorf("_readStreamPNG - '%s' does not have a valid PNG signature.", filename);
return false;
}
U32 prevWaterMark = FrameAllocator::getWaterMark();
PngReadGuard guard(filename);
if (!guard.init())
{
FrameAllocator::setWaterMark(prevWaterMark);
return false;
}
png_set_read_fn(guard.png_ptr, &stream, pngReadDataFn);
png_set_sig_bytes(guard.png_ptr, cs_headerBytesChecked);
png_read_info(guard.png_ptr, guard.info_ptr);
bool transAlpha = false;
GFXFormat format = _determineReadFormat(guard.png_ptr, guard.info_ptr, transAlpha);
png_uint_32 number_of_passes = png_set_interlace_handling(guard.png_ptr);
png_read_update_info(guard.png_ptr, guard.info_ptr);
png_uint_32 width = png_get_image_width(guard.png_ptr, guard.info_ptr);
png_uint_32 height = png_get_image_height(guard.png_ptr, guard.info_ptr);
png_uint_32 rowBytes = png_get_rowbytes(guard.png_ptr, guard.info_ptr);
if (!_validateRowBytes(filename, format, rowBytes, width))
{
FrameAllocator::setWaterMark(prevWaterMark);
return false;
}
bitmap->deleteImage();
bitmap->allocateBitmap(width, height, false, format);
png_bytep* rowPointers = new png_bytep[height];
U8* pBase = (U8*)bitmap->getBits();
for (U32 i = 0; i < height; i++) for (U32 i = 0; i < height; i++)
row_pointers[i] = const_cast<png_bytep>(bitmap->getAddress(0, i)); rowPointers[i] = pBase + (i * rowBytes);
png_write_image(png_ptr, row_pointers); png_read_image(guard.png_ptr, rowPointers);
delete[] rowPointers;
// Write S3TC data if present... png_read_end(guard.png_ptr, NULL);
// Write FXT1 data if present...
png_write_end(png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
bitmap->checkForTransparency();
FrameAllocator::setWaterMark(prevWaterMark);
return true; return true;
} }
static bool _writePNG(GBitmap* bitmap, Stream& stream, U32 compressionLevel, U32 strategy, U32 filter, const char* filename)
//--------------------------------------------------------------------------
bool sWritePNG(const Torque::Path& path, GBitmap* bitmap, U32 compressionLevel)
{ {
FileStream* writePng = new FileStream; GFXFormat format = bitmap->getFormat();
if (!writePng->open(path.getFullPath(), Torque::FS::File::Write)) if (!_isSupportedWriteFormat(format))
{ {
Con::printf("Failed to open PNG :%s", path.getFullFileName().c_str()); Con::errorf("_writePNG - '%s': Unsupported format %d.", filename, format);
return false; return false;
} }
if (!sWriteStreamPNG("png", *writePng, bitmap, compressionLevel)) PngWriteGuard guard(filename);
{ if (!guard.init(true))
Con::printf("Failed to write PNG :%s", path.getFullFileName().c_str());
return false; return false;
}
writePng->close(); png_set_write_fn(guard.png_ptr, &stream, pngWriteDataFn, pngFlushDataFn);
png_set_compression_window_bits(guard.png_ptr, 15);
png_set_compression_level(guard.png_ptr, compressionLevel);
png_set_filter(guard.png_ptr, 0, filter);
_applyWriteIHDR(guard.png_ptr, guard.info_ptr, format, bitmap->getWidth(), bitmap->getHeight());
png_write_info(guard.png_ptr, guard.info_ptr);
FrameAllocatorMarker marker;
const U32 height = bitmap->getHeight();
png_bytep* rowPointers = (png_bytep*)marker.alloc(height * sizeof(png_bytep));
for (U32 i = 0; i < height; i++)
rowPointers[i] = const_cast<png_bytep>(bitmap->getAddress(0, i));
png_write_image(guard.png_ptr, rowPointers);
png_write_end(guard.png_ptr, guard.info_ptr);
return true; return true;
} }
static bool sWriteStreamPNG(const String& bmType, Stream& stream, GBitmap* bitmap, U32 compressionLevel) //-----------------------------------------------------------------------------
// Public interface
//-----------------------------------------------------------------------------
static bool sReadPNG(const Torque::Path& path, GBitmap* bitmap)
{
FileStream readPng;
if (!readPng.open(path.getFullPath(), Torque::FS::File::Read))
{
Con::errorf("sReadPNG - Failed to open '%s'.", path.getFullPath().c_str());
return false;
}
bool result = _readStreamPNG(readPng, bitmap, U32_MAX, path.getFullPath().c_str());
if (!result)
Con::errorf("sReadPNG - Failed to decode '%s'.", path.getFullPath().c_str());
readPng.close();
return result;
}
static bool sWriteStreamPNG(const String& /*bmType*/, Stream& stream, GBitmap* bitmap, U32 compressionLevel)
{ {
U32 waterMark = FrameAllocator::getWaterMark(); U32 waterMark = FrameAllocator::getWaterMark();
@ -501,19 +471,11 @@ static bool sWriteStreamPNG(const String& bmType, Stream& stream, GBitmap* bitma
U8* buffer = new U8[1 << 22]; // 4 Megs. Should be enough... U8* buffer = new U8[1 << 22]; // 4 Megs. Should be enough...
MemStream* pMemStream = new MemStream(1 << 22, buffer, false, true); MemStream* pMemStream = new MemStream(1 << 22, buffer, false, true);
const U32 zStrategies[] = { Z_DEFAULT_STRATEGY, const U32 zStrategies[] = { Z_DEFAULT_STRATEGY, Z_FILTERED };
Z_FILTERED }; const U32 pngFilters[] = { PNG_FILTER_NONE, PNG_FILTER_SUB, PNG_FILTER_UP,
const U32 pngFilters[] = { PNG_FILTER_NONE, PNG_FILTER_AVG, PNG_FILTER_PAETH, PNG_ALL_FILTERS };
PNG_FILTER_SUB,
PNG_FILTER_UP,
PNG_FILTER_AVG,
PNG_FILTER_PAETH,
PNG_ALL_FILTERS };
U32 minSize = 0xFFFFFFFF; U32 minSize = U32_MAX, bestCLevel = 0, bestStrategy = 0, bestFilter = 0;
U32 bestStrategy = 0xFFFFFFFF;
U32 bestFilter = 0xFFFFFFFF;
U32 bestCLevel = 0xFFFFFFFF;
for (U32 cl = 0; cl <= 9; cl++) for (U32 cl = 0; cl <= 9; cl++)
{ {
@ -522,53 +484,78 @@ static bool sWriteStreamPNG(const String& bmType, Stream& stream, GBitmap* bitma
for (U32 pf = 0; pf < 6; pf++) for (U32 pf = 0; pf < 6; pf++)
{ {
pMemStream->setPosition(0); pMemStream->setPosition(0);
U32 waterMarkInner = FrameAllocator::getWaterMark(); U32 waterMarkInner = FrameAllocator::getWaterMark();
if (_writePNG(bitmap, *pMemStream, cl, zStrategies[zs], pngFilters[pf]) == false) if (!_writePNG(bitmap, *pMemStream, cl, zStrategies[zs], pngFilters[pf]))
AssertFatal(false, "Handle this error!"); {
Con::errorf("sWriteStreamPNG - Compression search failed at cl=%d zs=%d pf=%d.", cl, zs, pf);
FrameAllocator::setWaterMark(waterMarkInner);
continue;
}
FrameAllocator::setWaterMark(waterMarkInner); FrameAllocator::setWaterMark(waterMarkInner);
if (pMemStream->getPosition() < minSize) if (pMemStream->getPosition() < minSize)
{ {
minSize = pMemStream->getPosition(); minSize = pMemStream->getPosition();
bestStrategy = zs; bestCLevel = cl; bestStrategy = zs; bestFilter = pf;
bestFilter = pf;
bestCLevel = cl;
} }
} }
} }
} }
AssertFatal(minSize != 0xFFFFFFFF, "Error, no best found?");
delete pMemStream; delete pMemStream;
delete[] buffer; delete[] buffer;
if (minSize == U32_MAX)
bool retVal = _writePNG(bitmap, stream, {
bestCLevel, Con::errorf("sWriteStreamPNG - No valid compression result found.");
zStrategies[bestStrategy],
pngFilters[bestFilter]);
FrameAllocator::setWaterMark(waterMark); FrameAllocator::setWaterMark(waterMark);
return false;
}
bool retVal = _writePNG(bitmap, stream, bestCLevel, zStrategies[bestStrategy], pngFilters[bestFilter]);
FrameAllocator::setWaterMark(waterMark);
return retVal; return retVal;
} }
//-------------------------------------------------------------------------- static bool sWritePNG(const Torque::Path& path, GBitmap* bitmap, U32 compressionLevel)
// Stores PNG stream data
struct DeferredPNGWriterData {
png_structp png_ptr;
png_infop info_ptr;
U32 width;
U32 height;
};
DeferredPNGWriter::DeferredPNGWriter() :
mData( NULL ),
mActive(false)
{ {
mData = new DeferredPNGWriterData(); FileStream writePng;
if (!writePng.open(path.getFullPath(), Torque::FS::File::Write))
{
Con::errorf("sWritePNG - Failed to open '%s' for writing.", path.getFullPath().c_str());
return false;
} }
bool retVal = sWriteStreamPNG("png", writePng, bitmap, compressionLevel);
if (!retVal)
Con::errorf("sWritePNG - Failed to encode '%s'. Format: %d, Size: %dx%d.",
path.getFullPath().c_str(), bitmap->getFormat(), bitmap->getWidth(), bitmap->getHeight());
writePng.close();
return retVal;
}
static bool sReadStreamPNG(Stream& stream, GBitmap* bitmap, U32 len)
{
return _readStreamPNG(stream, bitmap, len, "unknown (stream only)");
}
//-----------------------------------------------------------------------------
// DeferredPNGWriter
//-----------------------------------------------------------------------------
struct DeferredPNGWriterData
{
PngWriteGuard guard;
U32 width = 0;
U32 height = 0;
explicit DeferredPNGWriterData(const char* filename) : guard(filename) {}
};
DeferredPNGWriter::DeferredPNGWriter() : mData(nullptr), mActive(false) {}
DeferredPNGWriter::~DeferredPNGWriter() DeferredPNGWriter::~DeferredPNGWriter()
{ {
delete mData; delete mData;
@ -576,120 +563,52 @@ DeferredPNGWriter::~DeferredPNGWriter()
bool DeferredPNGWriter::begin(GFXFormat format, S32 width, S32 height, Stream& stream, U32 compressionLevel) bool DeferredPNGWriter::begin(GFXFormat format, S32 width, S32 height, Stream& stream, U32 compressionLevel)
{ {
// ONLY RGB bitmap writing supported at this time! if (!_isSupportedWriteFormat(format))
AssertFatal( format == GFXFormatR8G8B8 ||
format == GFXFormatR8G8B8A8 ||
format == GFXFormatR8G8B8X8 ||
format == GFXFormatA8 ||
format == GFXFormatR5G6B5, "_writePNG: ONLY RGB bitmap writing supported at this time.");
if ( format != GFXFormatR8G8B8 &&
format != GFXFormatR8G8B8A8 &&
format != GFXFormatR8G8B8X8 &&
format != GFXFormatA8 &&
format != GFXFormatR5G6B5 )
return false;
mData->png_ptr = png_create_write_struct_2(PNG_LIBPNG_VER_STRING,
NULL,
pngFatalErrorFn,
pngWarningFn,
NULL,
pngRealMallocFn,
pngRealFreeFn);
if (mData->png_ptr == NULL)
return (false);
mData->info_ptr = png_create_info_struct(mData->png_ptr);
if (mData->info_ptr == NULL)
{ {
png_destroy_write_struct(&mData->png_ptr, (png_infopp)NULL); Con::errorf("DeferredPNGWriter::begin - Unsupported format %d.", format);
return false; return false;
} }
png_set_write_fn(mData->png_ptr, &stream, pngWriteDataFn, pngFlushDataFn); mData = new DeferredPNGWriterData("DeferredPNGWriter");
// Set the compression level and image filters if (!mData->guard.init(false))
png_set_compression_window_bits(mData->png_ptr, 15);
png_set_compression_level(mData->png_ptr, compressionLevel);
png_set_filter(mData->png_ptr, 0, PNG_ALL_FILTERS);
// Set the image information here. Width and height are up to 2^31,
// bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
// the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
// PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
// or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
// PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
// currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
if (format == GFXFormatR8G8B8)
{ {
png_set_IHDR(mData->png_ptr, mData->info_ptr, Con::errorf("DeferredPNGWriter::begin - Failed to init PNG write structs. Format: %d, Size: %dx%d.", format, width, height);
width, height, // the width & height return false;
8, PNG_COLOR_TYPE_RGB, // bit_depth, color_type,
NULL, // no interlace
NULL, // compression type
NULL); // filter type
}
else if (format == GFXFormatR8G8B8A8 || format == GFXFormatR8G8B8X8)
{
png_set_IHDR(mData->png_ptr, mData->info_ptr,
width, height, // the width & height
8, PNG_COLOR_TYPE_RGB_ALPHA, // bit_depth, color_type,
NULL, // no interlace
NULL, // compression type
NULL); // filter type
}
else if (format == GFXFormatA8)
{
png_set_IHDR(mData->png_ptr, mData->info_ptr,
width, height, // the width & height
8, PNG_COLOR_TYPE_GRAY, // bit_depth, color_type,
NULL, // no interlace
NULL, // compression type
NULL); // filter type
}
else if (format == GFXFormatR5G6B5)
{
png_set_IHDR(mData->png_ptr, mData->info_ptr,
width, height, // the width & height
16, PNG_COLOR_TYPE_GRAY, // bit_depth, color_type,
PNG_INTERLACE_NONE, // no interlace
PNG_COMPRESSION_TYPE_DEFAULT, // compression type
PNG_FILTER_TYPE_DEFAULT); // filter type
png_color_8_struct sigBit = { 0 };
sigBit.gray = 16;
png_set_sBIT(mData->png_ptr, mData->info_ptr, &sigBit );
png_set_swap( mData->png_ptr );
} }
png_write_info(mData->png_ptr, mData->info_ptr); mData->width = width;
mData->height = height;
png_set_write_fn(mData->guard.png_ptr, &stream, pngWriteDataFn, pngFlushDataFn);
png_set_compression_window_bits(mData->guard.png_ptr, 15);
png_set_compression_level(mData->guard.png_ptr, compressionLevel);
png_set_filter(mData->guard.png_ptr, 0, PNG_ALL_FILTERS);
_applyWriteIHDR(mData->guard.png_ptr, mData->guard.info_ptr, format, width, height);
png_write_info(mData->guard.png_ptr, mData->guard.info_ptr);
mActive = true; mActive = true;
return true; return true;
} }
void DeferredPNGWriter::append(GBitmap* bitmap, U32 rows) void DeferredPNGWriter::append(GBitmap* bitmap, U32 rows)
{ {
AssertFatal(mActive, "Cannot append to an inactive DeferredPNGWriter!"); AssertFatal(mActive, "DeferredPNGWriter::append - Writer is not active.");
U32 height = getMin(bitmap->getHeight(), rows); U32 height = getMin(bitmap->getHeight(), rows);
FrameAllocatorMarker marker; FrameAllocatorMarker marker;
png_bytep* row_pointers = (png_bytep*)marker.alloc(height * sizeof(png_bytep)); png_bytep* row_pointers = (png_bytep*)marker.alloc(height * sizeof(png_bytep));
for (U32 i = 0; i < height; i++) for (U32 i = 0; i < height; i++)
row_pointers[i] = const_cast<png_bytep>(bitmap->getAddress(0, i)); row_pointers[i] = const_cast<png_bytep>(bitmap->getAddress(0, i));
png_write_rows(mData->png_ptr, row_pointers, height); png_write_rows(mData->guard.png_ptr, row_pointers, height);
} }
void DeferredPNGWriter::end() void DeferredPNGWriter::end()
{ {
AssertFatal(mActive, "Cannot end an inactive DeferredPNGWriter!"); AssertFatal(mActive, "DeferredPNGWriter::end - Writer is not active.");
png_write_end(mData->png_ptr, mData->info_ptr);
png_destroy_write_struct(&mData->png_ptr, (png_infopp)NULL);
png_write_end(mData->guard.png_ptr, mData->guard.info_ptr);
mActive = false; mActive = false;
} }

View file

@ -57,6 +57,24 @@
#include "gfx/gl/tGL/tXGL.h" #include "gfx/gl/tGL/tXGL.h"
#endif #endif
#pragma region GL WARNINGS
// #131204 - Texture state usage warning: The texture object (0) bound to texture image unit 0
#define GL_LOW_WARN_TEXTURE_STATE 131204
// #131169 - Framebuffer detailed info: The driver allocated storage for renderbuffer 2. (severity: low)
#define GL_LOW_WARN_FRAMEBUFFER 131169
// #131185 - Buffer detailed info: Buffer object 1 (bound to GL_ELEMENT_ARRAY_BUFFER_ARB, usage hint is GL_ENUM_88e4)
// will use VIDEO memory as the source for buffer object operations. (severity: low)
#define GL_LOW_WARN_VIDEO_MEMORY 131185
// #131218 - Program/shader state performance warning: Vertex shader in program #
// is being recompiled based on GL state. (severity: medium)
#define GL_MED_WARN_PERFORMANCE_RECOMPILE 131218
#pragma endregion
GFXAdapter::CreateDeviceInstanceDelegate GFXGLDevice::mCreateDeviceInstance(GFXGLDevice::createInstance); GFXAdapter::CreateDeviceInstanceDelegate GFXGLDevice::mCreateDeviceInstance(GFXGLDevice::createInstance);
GFXDevice *GFXGLDevice::createInstance( U32 adapterIndex ) GFXDevice *GFXGLDevice::createInstance( U32 adapterIndex )
@ -104,6 +122,10 @@ void APIENTRY glDebugCallback(
if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) if (severity == GL_DEBUG_SEVERITY_NOTIFICATION)
return; return;
// Silence: Texture state usage warning: The texture object (0) bound to texture image unit 0
if (id == GL_LOW_WARN_TEXTURE_STATE)
return;
const char* srcStr = "UNKNOWN"; const char* srcStr = "UNKNOWN";
const char* typeStr = "UNKNOWN"; const char* typeStr = "UNKNOWN";
const char* sevStr = "UNKNOWN"; const char* sevStr = "UNKNOWN";

View file

@ -480,10 +480,9 @@ bool GFXGLTextureManager::_loadTexture(GFXTextureObject *aTexture, GBitmap *pDL)
} }
} }
if(!ImageUtil::isCompressedFormat(pDL->getFormat())) if (mipLevels > 1 && !ImageUtil::isCompressedFormat(pDL->getFormat()))
glGenerateMipmap(texture->getBinding()); glGenerateMipmap(texture->getBinding());
glBindTexture(target, 0);
return true; return true;
} }
@ -560,10 +559,9 @@ bool GFXGLTextureManager::_loadTexture(GFXTextureObject *aTexture, DDSFile *dds)
} }
} }
if (numMips != 1 && !isCompressed) if (numMips > 1 && !isCompressed)
glGenerateMipmap(texture->getBinding()); glGenerateMipmap(texture->getBinding());
glBindTexture(target, 0);
return true; return true;
} }
@ -608,7 +606,7 @@ bool GFXGLTextureManager::_refreshTexture(GFXTextureObject *texture)
_loadTexture(texture, texture->mBitmap); _loadTexture(texture, texture->mBitmap);
if(texture->mDDS) if(texture->mDDS)
return false; _loadTexture(texture, texture->mDDS);
usedStrategies++; usedStrategies++;
} }

View file

@ -105,76 +105,33 @@ void GFXGLTextureObject::unlock(U32 mipLevel /*= 0*/, U32 faceIndex /*= 0*/)
if (!mLockedRect.bits) if (!mLockedRect.bits)
return; return;
PROFILE_SCOPE(GFXGLTextureObject_unlock); // I know this is in unlock, but in GL we actually do our submission in unlock.
PROFILE_SCOPE(GFXGLTextureObject_lockRT);
PRESERVE_TEXTURE(mBinding); PRESERVE_TEXTURE(mBinding);
glBindTexture(mBinding, mHandle); glBindTexture(mBinding, mHandle);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mBuffer);
// --- Save pixel store state --- glBufferData(GL_PIXEL_UNPACK_BUFFER, (mLockedRectRect.extent.x + 1) * (mLockedRectRect.extent.y + 1) * mBytesPerTexel, mFrameAllocatorPtr, GL_STREAM_DRAW);
GLint prevUnpackAlign; S32 z = getDepth();
glGetIntegerv(GL_UNPACK_ALIGNMENT, &prevUnpackAlign);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
const U32 width = mLockedRectRect.extent.x;
const U32 height = mLockedRectRect.extent.y;
const U32 depth = getDepth();
if (mBinding == GL_TEXTURE_3D) if (mBinding == GL_TEXTURE_3D)
{ glTexSubImage3D(mBinding, mipLevel, mLockedRectRect.point.x, mLockedRectRect.point.y, z,
glTexSubImage3D( mLockedRectRect.extent.x, mLockedRectRect.extent.y, z, GFXGLTextureFormat[mFormat], GFXGLTextureType[mFormat], NULL);
mBinding,
mipLevel,
mLockedRectRect.point.x,
mLockedRectRect.point.y,
0,
width,
height,
depth,
GFXGLTextureFormat[mFormat],
GFXGLTextureType[mFormat],
mLockedRect.bits
);
}
else if (mBinding == GL_TEXTURE_2D) else if (mBinding == GL_TEXTURE_2D)
{ glTexSubImage2D(mBinding, mipLevel, mLockedRectRect.point.x, mLockedRectRect.point.y,
glTexSubImage2D( mLockedRectRect.extent.x, mLockedRectRect.extent.y, GFXGLTextureFormat[mFormat], GFXGLTextureType[mFormat], NULL);
mBinding,
mipLevel,
mLockedRectRect.point.x,
mLockedRectRect.point.y,
width,
height,
GFXGLTextureFormat[mFormat],
GFXGLTextureType[mFormat],
mLockedRect.bits
);
}
else if (mBinding == GL_TEXTURE_1D) else if (mBinding == GL_TEXTURE_1D)
{ glTexSubImage1D(mBinding, mipLevel, (mLockedRectRect.point.x > 1 ? mLockedRectRect.point.x : mLockedRectRect.point.y),
glTexSubImage1D( (mLockedRectRect.extent.x > 1 ? mLockedRectRect.extent.x : mLockedRectRect.extent.y), GFXGLTextureFormat[mFormat], GFXGLTextureType[mFormat], NULL);
mBinding,
mipLevel,
mLockedRectRect.point.x,
width,
GFXGLTextureFormat[mFormat],
GFXGLTextureType[mFormat],
mLockedRect.bits
);
}
// --- Restore state --- glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, prevUnpackAlign);
mLockedRect.bits = NULL; mLockedRect.bits = NULL;
#if TORQUE_DEBUG
AssertFatal(mFrameAllocatorMarkGuard == FrameAllocator::getWaterMark(), "");
#endif
FrameAllocator::setWaterMark(mFrameAllocatorMark); FrameAllocator::setWaterMark(mFrameAllocatorMark);
mFrameAllocatorMark = 0; mFrameAllocatorMark = 0;
mFrameAllocatorPtr = NULL; mFrameAllocatorPtr = NULL;
#ifdef TORQUE_DEBUG
glCheckErrors();
#endif
} }
void GFXGLTextureObject::release() void GFXGLTextureObject::release()
@ -281,7 +238,6 @@ bool GFXGLTextureObject::copyToBmp(GBitmap * bmp)
} // face } // face
} // mip } // mip
glBindTexture(mBinding, 0);
return true; return true;
} }
@ -298,6 +254,9 @@ void GFXGLTextureObject::updateTextureSlot(const GFXTexHandle& texHandle, const
const GLenum srcTarget = srcTex->getBinding(); // source binding const GLenum srcTarget = srcTex->getBinding(); // source binding
const bool srcIsCube = (srcTarget == GL_TEXTURE_CUBE_MAP || srcTarget == GL_TEXTURE_CUBE_MAP_ARRAY); const bool srcIsCube = (srcTarget == GL_TEXTURE_CUBE_MAP || srcTarget == GL_TEXTURE_CUBE_MAP_ARRAY);
PRESERVE_TEXTURE(srcTarget);
PRESERVE_TEXTURE(dstTarget);
// Determine list of faces to copy from source // Determine list of faces to copy from source
U32 firstFace = 0; U32 firstFace = 0;
U32 faceCount = 1; U32 faceCount = 1;
@ -435,9 +394,6 @@ void GFXGLTextureObject::updateTextureSlot(const GFXTexHandle& texHandle, const
GFXGLTextureFormat[mFormat], GFXGLTextureType[mFormat], buffer); GFXGLTextureFormat[mFormat], GFXGLTextureType[mFormat], buffer);
} }
} }
glBindTexture(dstTarget, 0);
glBindTexture(srcTarget, 0);
} }
} }
@ -462,6 +418,9 @@ void GFXGLTextureObject::initSamplerState(const GFXSamplerStateDesc &ssd)
void GFXGLTextureObject::bind(U32 textureUnit) void GFXGLTextureObject::bind(U32 textureUnit)
{ {
if (!mHandle || mIsZombie)
return;
glActiveTexture(GL_TEXTURE0 + textureUnit); glActiveTexture(GL_TEXTURE0 + textureUnit);
glBindTexture(mBinding, mHandle); glBindTexture(mBinding, mHandle);
GFXGL->getOpenglCache()->setCacheBindedTex(textureUnit, mBinding, mHandle); GFXGL->getOpenglCache()->setCacheBindedTex(textureUnit, mBinding, mHandle);