From 93b5a8d22eb26983bb4829bab42e9249610cbbe4 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 10 Apr 2026 06:42:45 +0100 Subject: [PATCH 1/5] Terrain doesnt render correctly The core of this issue was the unlock on the GFXGLTextureObject Few other bug fixes around setting a texture binding to 0, notice in clear function we now also clear the glprogram and the buffers. This seems to fix most of the warnings around id 131204 This warning was triggered every frame, now it just triggers when a shader expects a texture but none is active. --- Engine/source/environment/scatterSky.cpp | 2 +- Engine/source/gfx/gl/gfxGLDevice.cpp | 4 + Engine/source/gfx/gl/gfxGLTextureManager.cpp | 8 +- Engine/source/gfx/gl/gfxGLTextureObject.cpp | 103 ++++++------------- 4 files changed, 39 insertions(+), 78 deletions(-) diff --git a/Engine/source/environment/scatterSky.cpp b/Engine/source/environment/scatterSky.cpp index 40d0e5137..394b9769b 100644 --- a/Engine/source/environment/scatterSky.cpp +++ b/Engine/source/environment/scatterSky.cpp @@ -1094,7 +1094,7 @@ void ScatterSky::_render( ObjectRenderInst *ri, SceneRenderState *state, BaseMat } else { - GFX->setCubeTexture( 0, NULL ); + GFX->setTexture( 0, NULL ); mShaderConsts->setSafe( mUseCubemapSC, 0.0f ); } diff --git a/Engine/source/gfx/gl/gfxGLDevice.cpp b/Engine/source/gfx/gl/gfxGLDevice.cpp index 8ec644b8a..c5b166a0e 100644 --- a/Engine/source/gfx/gl/gfxGLDevice.cpp +++ b/Engine/source/gfx/gl/gfxGLDevice.cpp @@ -640,6 +640,10 @@ void GFXGLDevice::copyResource(GFXTextureObject* pDst, GFXCubemap* pSrc, const U void GFXGLDevice::clear(U32 flags, const LinearColorF& color, F32 z, U32 stencil) { + glUseProgram(0); + mCurrentShader = NULL; + mCurrentConstBuffer = NULL; + // Make sure we have flushed our render target state. _updateRenderTargets(); diff --git a/Engine/source/gfx/gl/gfxGLTextureManager.cpp b/Engine/source/gfx/gl/gfxGLTextureManager.cpp index c6c73fbee..5e769dbd7 100644 --- a/Engine/source/gfx/gl/gfxGLTextureManager.cpp +++ b/Engine/source/gfx/gl/gfxGLTextureManager.cpp @@ -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()); - glBindTexture(target, 0); return true; } @@ -560,10 +559,9 @@ bool GFXGLTextureManager::_loadTexture(GFXTextureObject *aTexture, DDSFile *dds) } } - if (numMips != 1 && !isCompressed) + if (numMips > 1 && !isCompressed) glGenerateMipmap(texture->getBinding()); - glBindTexture(target, 0); return true; } @@ -608,7 +606,7 @@ bool GFXGLTextureManager::_refreshTexture(GFXTextureObject *texture) _loadTexture(texture, texture->mBitmap); if(texture->mDDS) - return false; + _loadTexture(texture, texture->mDDS); usedStrategies++; } diff --git a/Engine/source/gfx/gl/gfxGLTextureObject.cpp b/Engine/source/gfx/gl/gfxGLTextureObject.cpp index 81ca6fa9d..0b58c19b6 100644 --- a/Engine/source/gfx/gl/gfxGLTextureObject.cpp +++ b/Engine/source/gfx/gl/gfxGLTextureObject.cpp @@ -102,79 +102,36 @@ GFXLockedRect* GFXGLTextureObject::lock(U32 mipLevel /*= 0*/, RectI* inRect /*= void GFXGLTextureObject::unlock(U32 mipLevel /*= 0*/, U32 faceIndex /*= 0*/) { - if (!mLockedRect.bits) - return; + if (!mLockedRect.bits) + 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); - glBindTexture(mBinding, mHandle); + PRESERVE_TEXTURE(mBinding); + glBindTexture(mBinding, mHandle); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mBuffer); + glBufferData(GL_PIXEL_UNPACK_BUFFER, (mLockedRectRect.extent.x + 1) * (mLockedRectRect.extent.y + 1) * mBytesPerTexel, mFrameAllocatorPtr, GL_STREAM_DRAW); + S32 z = getDepth(); + if (mBinding == GL_TEXTURE_3D) + glTexSubImage3D(mBinding, mipLevel, mLockedRectRect.point.x, mLockedRectRect.point.y, z, + mLockedRectRect.extent.x, mLockedRectRect.extent.y, z, GFXGLTextureFormat[mFormat], GFXGLTextureType[mFormat], NULL); + else if (mBinding == GL_TEXTURE_2D) + glTexSubImage2D(mBinding, mipLevel, mLockedRectRect.point.x, mLockedRectRect.point.y, + mLockedRectRect.extent.x, mLockedRectRect.extent.y, GFXGLTextureFormat[mFormat], GFXGLTextureType[mFormat], NULL); + else if (mBinding == GL_TEXTURE_1D) + glTexSubImage1D(mBinding, mipLevel, (mLockedRectRect.point.x > 1 ? mLockedRectRect.point.x : mLockedRectRect.point.y), + (mLockedRectRect.extent.x > 1 ? mLockedRectRect.extent.x : mLockedRectRect.extent.y), GFXGLTextureFormat[mFormat], GFXGLTextureType[mFormat], NULL); - // --- Save pixel store state --- - GLint prevUnpackAlign; - glGetIntegerv(GL_UNPACK_ALIGNMENT, &prevUnpackAlign); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - 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) - { - glTexSubImage3D( - mBinding, - mipLevel, - mLockedRectRect.point.x, - mLockedRectRect.point.y, - 0, - width, - height, - depth, - GFXGLTextureFormat[mFormat], - GFXGLTextureType[mFormat], - mLockedRect.bits - ); - } - else if (mBinding == GL_TEXTURE_2D) - { - glTexSubImage2D( - mBinding, - mipLevel, - mLockedRectRect.point.x, - mLockedRectRect.point.y, - width, - height, - GFXGLTextureFormat[mFormat], - GFXGLTextureType[mFormat], - mLockedRect.bits - ); - } - else if (mBinding == GL_TEXTURE_1D) - { - glTexSubImage1D( - mBinding, - mipLevel, - mLockedRectRect.point.x, - width, - GFXGLTextureFormat[mFormat], - GFXGLTextureType[mFormat], - mLockedRect.bits - ); - } - - // --- Restore state --- - glPixelStorei(GL_UNPACK_ALIGNMENT, prevUnpackAlign); - - mLockedRect.bits = NULL; - - FrameAllocator::setWaterMark(mFrameAllocatorMark); - mFrameAllocatorMark = 0; - mFrameAllocatorPtr = NULL; - -#ifdef TORQUE_DEBUG - glCheckErrors(); + mLockedRect.bits = NULL; +#if TORQUE_DEBUG + AssertFatal(mFrameAllocatorMarkGuard == FrameAllocator::getWaterMark(), ""); #endif + FrameAllocator::setWaterMark(mFrameAllocatorMark); + mFrameAllocatorMark = 0; + mFrameAllocatorPtr = NULL; } void GFXGLTextureObject::release() @@ -281,7 +238,6 @@ bool GFXGLTextureObject::copyToBmp(GBitmap * bmp) } // face } // mip - glBindTexture(mBinding, 0); return true; } @@ -298,6 +254,9 @@ void GFXGLTextureObject::updateTextureSlot(const GFXTexHandle& texHandle, const const GLenum srcTarget = srcTex->getBinding(); // source binding 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 U32 firstFace = 0; U32 faceCount = 1; @@ -435,9 +394,6 @@ void GFXGLTextureObject::updateTextureSlot(const GFXTexHandle& texHandle, const 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) { + if (!mHandle || mIsZombie) + return; + glActiveTexture(GL_TEXTURE0 + textureUnit); glBindTexture(mBinding, mHandle); GFXGL->getOpenglCache()->setCacheBindedTex(textureUnit, mBinding, mHandle); From 02b3f3d0a1b789293fdc6a88bb37b67cc8f644d2 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 10 Apr 2026 08:34:59 +0100 Subject: [PATCH 2/5] Update gfxGLDevice.cpp clearing the active program caused issues during a target bake as we call glClear after setting the shader etc. As this warning id 131204 seems to be a product of doing this, everything i read up on it says it is safe to silence it. --- Engine/source/gfx/gl/gfxGLDevice.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Engine/source/gfx/gl/gfxGLDevice.cpp b/Engine/source/gfx/gl/gfxGLDevice.cpp index c5b166a0e..efe84e873 100644 --- a/Engine/source/gfx/gl/gfxGLDevice.cpp +++ b/Engine/source/gfx/gl/gfxGLDevice.cpp @@ -104,6 +104,12 @@ void APIENTRY glDebugCallback( if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) return; + // This warning id appears safe to ignore, we clear states + // but leave programs active when rendering to a target + // this produces a warning during glClear + if (id == 131204) + return; + const char* srcStr = "UNKNOWN"; const char* typeStr = "UNKNOWN"; const char* sevStr = "UNKNOWN"; @@ -640,10 +646,6 @@ void GFXGLDevice::copyResource(GFXTextureObject* pDst, GFXCubemap* pSrc, const U void GFXGLDevice::clear(U32 flags, const LinearColorF& color, F32 z, U32 stencil) { - glUseProgram(0); - mCurrentShader = NULL; - mCurrentConstBuffer = NULL; - // Make sure we have flushed our render target state. _updateRenderTargets(); From dead75f45818806982bce350ba28558db9f159a1 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 10 Apr 2026 16:48:30 +0100 Subject: [PATCH 3/5] Update gfxGLDevice.cpp --- Engine/source/gfx/gl/gfxGLDevice.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Engine/source/gfx/gl/gfxGLDevice.cpp b/Engine/source/gfx/gl/gfxGLDevice.cpp index efe84e873..0dd26f436 100644 --- a/Engine/source/gfx/gl/gfxGLDevice.cpp +++ b/Engine/source/gfx/gl/gfxGLDevice.cpp @@ -57,6 +57,9 @@ #include "gfx/gl/tGL/tXGL.h" #endif +// #131204 - Texture state usage warning: The texture object (0) bound to texture image unit 0 +#define GL_LOW_WARN_TEXTURE_STATE 131204 + GFXAdapter::CreateDeviceInstanceDelegate GFXGLDevice::mCreateDeviceInstance(GFXGLDevice::createInstance); GFXDevice *GFXGLDevice::createInstance( U32 adapterIndex ) @@ -104,10 +107,8 @@ void APIENTRY glDebugCallback( if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) return; - // This warning id appears safe to ignore, we clear states - // but leave programs active when rendering to a target - // this produces a warning during glClear - if (id == 131204) + // 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"; From f622d97224a699693c22e757a59199e5030e193c Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 10 Apr 2026 17:00:58 +0100 Subject: [PATCH 4/5] Update gfxGLDevice.cpp --- Engine/source/gfx/gl/gfxGLDevice.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Engine/source/gfx/gl/gfxGLDevice.cpp b/Engine/source/gfx/gl/gfxGLDevice.cpp index 0dd26f436..a39d9bfd6 100644 --- a/Engine/source/gfx/gl/gfxGLDevice.cpp +++ b/Engine/source/gfx/gl/gfxGLDevice.cpp @@ -57,9 +57,24 @@ #include "gfx/gl/tGL/tXGL.h" #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); GFXDevice *GFXGLDevice::createInstance( U32 adapterIndex ) From a78c0b430923e07e65035978a49e342632e1a454 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 10 Apr 2026 17:41:27 +0100 Subject: [PATCH 5/5] Update bitmapPng.cpp update bitmapPng reading from libpng to have context in warnings and errors, also add a bit of a better structure around the read and write functions. Interlaced PNG's were never being accounted for before so add the png_set_interlace_handling for interlaced pngs --- .../source/gfx/bitmap/loaders/bitmapPng.cpp | 861 ++++++++---------- 1 file changed, 390 insertions(+), 471 deletions(-) diff --git a/Engine/source/gfx/bitmap/loaders/bitmapPng.cpp b/Engine/source/gfx/bitmap/loaders/bitmapPng.cpp index 60a5e5965..83b60edfb 100644 --- a/Engine/source/gfx/bitmap/loaders/bitmapPng.cpp +++ b/Engine/source/gfx/bitmap/loaders/bitmapPng.cpp @@ -41,15 +41,16 @@ #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. /// 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 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 { @@ -72,425 +73,394 @@ static struct _privateRegisterPNG } } sStaticRegisterPNG; - -//-------------------------------------- Replacement I/O for standard LIBPng -// functions. we don't wanna use -// FILE*'s... -static void pngReadDataFn(png_structp png_ptr, - png_bytep data, - png_size_t length) +//----------------------------------------------------------------------------- +// PNG context passed to libpng for error reporting +//----------------------------------------------------------------------------- +struct PngContext { - AssertFatal(png_get_io_ptr(png_ptr) != NULL, "No stream?"); + const char* filename; + explicit PngContext(const char* f) : filename(f) {} +}; - Stream *strm = (Stream*)png_get_io_ptr(png_ptr); - bool success = strm->read(length, data); - AssertFatal(success, "pngReadDataFn - failed to read from stream!"); +//----------------------------------------------------------------------------- +// 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); + AssertFatal(strm, "pngReadDataFn - No 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); - bool success = strm->write(length, data); - AssertFatal(success, "pngWriteDataFn - failed to write to stream!"); + Stream* strm = (Stream*)png_get_io_ptr(png_ptr); + AssertFatal(strm, "pngWriteDataFn - No 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 void pngFlushDataFn(png_structp /*png_ptr*/) +static png_voidp pngMallocFn(png_structp /*png_ptr*/, png_size_t size) { return FrameAllocator::alloc(size); } +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) { - // -} - -static png_voidp pngMallocFn(png_structp /*png_ptr*/, png_size_t size) -{ - return FrameAllocator::alloc(size); -} - -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) -{ - 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)) + const char* filename = "unknown"; + if (png_ptr) { - Con::printf("Failed to open PNG :%s", path.getFullFileName().c_str()); - return false; + 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 void pngWarningFn(png_structp png_ptr, png_const_charp pMessage) +{ +#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 +} + +//----------------------------------------------------------------------------- +// 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; + + explicit PngReadGuard(const char* filename) : ctx(filename) {} + + bool init() + { + png_ptr = png_create_read_struct_2(PNG_LIBPNG_VER_STRING, + &ctx, pngFatalErrorFn, pngWarningFn, + NULL, pngRealMallocFn, pngRealFreeFn); + if (!png_ptr) + { + Con::errorf("PngReadGuard - png_create_read_struct_2 failed for '%s'.", ctx.filename); + return false; + } + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + Con::errorf("PngReadGuard - png_create_info_struct (info) failed for '%s'.", ctx.filename); + return false; + } + + 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; } - if (!sReadStreamPNG(*readPng, bitmap, U32_MAX)) + ~PngReadGuard() { - Con::printf("Failed to read PNG :%s", path.getFullFileName().c_str()); - return false; + if (png_ptr) + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); } +}; - readPng->close(); - - return true; -} - -static bool sReadStreamPNG(Stream& stream, GBitmap* bitmap, U32 len) +struct PngWriteGuard { - PROFILE_SCOPE(sReadPNG); - static const U32 cs_headerBytesChecked = 8; + png_structp png_ptr = nullptr; + png_infop info_ptr = nullptr; + PngContext ctx; - U8 header[cs_headerBytesChecked]; - stream.read(cs_headerBytesChecked, header); + explicit PngWriteGuard(const char* filename) : ctx(filename) {} - bool isPng = png_check_sig(header, cs_headerBytesChecked) != 0; - if (isPng == false) + bool init(bool useFrameAllocator = true) { - AssertWarn(false, "GBitmap::readPNG: stream doesn't contain a PNG"); - return false; + png_ptr = png_create_write_struct_2(PNG_LIBPNG_VER_STRING, + &ctx, pngFatalErrorFn, pngWarningFn, + NULL, + useFrameAllocator ? pngMallocFn : pngRealMallocFn, + useFrameAllocator ? pngFreeFn : pngRealFreeFn); + if (!png_ptr) + { + Con::errorf("PngWriteGuard - png_create_write_struct_2 failed for '%s'.", ctx.filename); + return false; + } + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + Con::errorf("PngWriteGuard - png_create_info_struct failed for '%s'.", ctx.filename); + return false; + } + + return true; } - U32 prevWaterMark = FrameAllocator::getWaterMark(); - png_structp png_ptr = png_create_read_struct_2(PNG_LIBPNG_VER_STRING, - NULL, - pngFatalErrorFn, - pngWarningFn, - NULL, - pngRealMallocFn, - pngRealFreeFn); - - if (png_ptr == NULL) + ~PngWriteGuard() { - FrameAllocator::setWaterMark(prevWaterMark); - return false; + if (png_ptr) + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); } +}; - png_infop info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == NULL) - { - png_destroy_read_struct(&png_ptr, - (png_infopp)NULL, - (png_infopp)NULL); +//----------------------------------------------------------------------------- +// Shared helpers +//----------------------------------------------------------------------------- +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); - FrameAllocator::setWaterMark(prevWaterMark); - return false; - } - - png_infop end_info = png_create_info_struct(png_ptr); - 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); - - // Read off the info on the image. - png_set_sig_bytes(png_ptr, cs_headerBytesChecked); - png_read_info(png_ptr, info_ptr); - - // OK, at this point, if we have reached it ok, then we can reset the - // 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; + transAlpha = false; 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); - } - // 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); transAlpha = true; } - if (color_type == PNG_COLOR_TYPE_PALETTE) + switch (color_type) { + case PNG_COLOR_TYPE_PALETTE: png_set_expand(png_ptr); format = transAlpha ? GFXFormatR8G8B8A8 : GFXFormatR8G8B8; - } - else if (color_type == PNG_COLOR_TYPE_GRAY) - { + break; + case PNG_COLOR_TYPE_GRAY: png_set_expand(png_ptr); - - if (bit_depth == 16) - format = GFXFormatR5G6B5; - else - format = GFXFormatA8; - } - else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) - { + format = (bit_depth == 16) ? GFXFormatR5G6B5 : GFXFormatA8; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: png_set_expand(png_ptr); png_set_gray_to_rgb(png_ptr); format = GFXFormatR8G8B8A8; - } - else if (color_type == PNG_COLOR_TYPE_RGB) - { - format = transAlpha ? GFXFormatR8G8B8A8 : GFXFormatR8G8B8; + break; + case PNG_COLOR_TYPE_RGB: png_set_expand(png_ptr); - } - else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) - { + format = transAlpha ? GFXFormatR8G8B8A8 : GFXFormatR8G8B8; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: png_set_expand(png_ptr); format = GFXFormatR8G8B8A8; + break; + default: + Con::errorf("_determineReadFormat - Unrecognised color_type %d.", color_type); + break; } - // Update the info pointer with the result of the transformations - // above... - png_read_update_info(png_ptr, info_ptr); + return format; +} - png_uint_32 rowBytes = png_get_rowbytes(png_ptr, info_ptr); - if (format == GFXFormatR8G8B8) +static bool _validateRowBytes(const char* filename, GFXFormat format, U32 rowBytes, U32 width) +{ + const U32 expected = + (format == GFXFormatR8G8B8) ? width * 3 : + (format == GFXFormatR8G8B8A8) ? width * 4 : + (format == GFXFormatR5G6B5) ? width * 2 : 0; + + if (expected && rowBytes != expected) { - AssertFatal(rowBytes == width * 3, - "Error, our rowbytes are incorrect for this transform... (3)"); + Con::errorf("_validateRowBytes - '%s': rowBytes %d != expected %d for format %d.", + filename, rowBytes, expected, format); + return false; } - else if (format == GFXFormatR8G8B8A8) + return true; +} + +static void _applyWriteIHDR(png_structp png_ptr, png_infop info_ptr, GFXFormat format, U32 width, U32 height) +{ + switch (format) { - AssertFatal(rowBytes == width * 4, - "Error, our rowbytes are incorrect for this transform... (4)"); + case GFXFormatR8G8B8: + png_set_IHDR(png_ptr, info_ptr, width, height, 8, + PNG_COLOR_TYPE_RGB, NULL, NULL, NULL); + break; + case GFXFormatR8G8B8A8: + case GFXFormatR8G8B8X8: + case GFXFormatR8G8B8A8_LINEAR_FORCE: + png_set_IHDR(png_ptr, info_ptr, width, height, 8, + PNG_COLOR_TYPE_RGB_ALPHA, NULL, NULL, NULL); + break; + case GFXFormatA8: + png_set_IHDR(png_ptr, info_ptr, width, height, 8, + PNG_COLOR_TYPE_GRAY, NULL, NULL, NULL); + break; + case GFXFormatR5G6B5: + { + png_set_IHDR(png_ptr, info_ptr, width, height, 16, + PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_color_8_struct sigBit = { 0 }; + sigBit.gray = 16; + png_set_sBIT(png_ptr, info_ptr, &sigBit); + png_set_swap(png_ptr); + break; } - else if (format == GFXFormatR5G6B5) + default: + break; + } +} + +static bool _isSupportedWriteFormat(GFXFormat format) +{ + switch (format) { - AssertFatal(rowBytes == width * 2, - "Error, our rowbytes are incorrect for this transform... (2)"); + 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; } - // actually allocate the bitmap space... - bitmap->allocateBitmap(width, height, - false, // don't extrude miplevels... - format); // use determined format... + U32 prevWaterMark = FrameAllocator::getWaterMark(); - // Set up the row pointers... - png_bytep* rowPointers = new png_bytep[ height ]; + 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++) rowPointers[i] = pBase + (i * rowBytes); - // And actually read the image! - png_read_image(png_ptr, rowPointers); + png_read_image(guard.png_ptr, rowPointers); + delete[] 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); + png_read_end(guard.png_ptr, NULL); - // 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; } -//-------------------------------------------------------------------------- -static bool _writePNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel, U32 strategy, U32 filter) +static bool _writePNG(GBitmap* bitmap, Stream& stream, U32 compressionLevel, U32 strategy, U32 filter, const char* filename) { - GFXFormat format = bitmap->getFormat(); + GFXFormat format = bitmap->getFormat(); - // 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) + if (!_isSupportedWriteFormat(format)) { - png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + Con::errorf("_writePNG - '%s': Unsupported format %d.", filename, format); return false; } - png_set_write_fn(png_ptr, &stream, pngWriteDataFn, pngFlushDataFn); + PngWriteGuard guard(filename); + if (!guard.init(true)) + return false; - // Set the compression level and image filters - png_set_compression_window_bits(png_ptr, 15); - png_set_compression_level(png_ptr, compressionLevel); - png_set_filter(png_ptr, 0, filter); + 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); - // 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 + _applyWriteIHDR(guard.png_ptr, guard.info_ptr, format, bitmap->getWidth(), bitmap->getHeight()); + png_write_info(guard.png_ptr, guard.info_ptr); - U32 width = bitmap->getWidth(); - U32 height = bitmap->getHeight(); - - if (format == GFXFormatR8G8B8) - { - png_set_IHDR(png_ptr, info_ptr, - width, height, // the width & height - 8, PNG_COLOR_TYPE_RGB, // bit_depth, color_type, - 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 }; - sigBit.gray = 16; - png_set_sBIT(png_ptr, info_ptr, &sigBit ); - - png_set_swap( png_ptr ); - } - - png_write_info(png_ptr, info_ptr); FrameAllocatorMarker marker; - png_bytep* row_pointers = (png_bytep*)marker.alloc( height * sizeof( png_bytep ) ); - for (U32 i=0; i(bitmap->getAddress(0, i)); - - png_write_image(png_ptr, row_pointers); - - // Write S3TC data if present... - // Write FXT1 data if present... - - png_write_end(png_ptr, info_ptr); - png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + 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(bitmap->getAddress(0, i)); + png_write_image(guard.png_ptr, rowPointers); + png_write_end(guard.png_ptr, guard.info_ptr); return true; } - -//-------------------------------------------------------------------------- -bool sWritePNG(const Torque::Path& path, GBitmap* bitmap, U32 compressionLevel) +//----------------------------------------------------------------------------- +// Public interface +//----------------------------------------------------------------------------- +static bool sReadPNG(const Torque::Path& path, GBitmap* bitmap) { - FileStream* writePng = new FileStream; - - if (!writePng->open(path.getFullPath(), Torque::FS::File::Write)) + FileStream readPng; + if (!readPng.open(path.getFullPath(), Torque::FS::File::Read)) { - Con::printf("Failed to open PNG :%s", path.getFullFileName().c_str()); + Con::errorf("sReadPNG - Failed to open '%s'.", path.getFullPath().c_str()); return false; } - if (!sWriteStreamPNG("png", *writePng, bitmap, compressionLevel)) - { - Con::printf("Failed to write PNG :%s", path.getFullFileName().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()); - writePng->close(); - - return true; + readPng.close(); + return result; } -static bool sWriteStreamPNG(const String& bmType, Stream& stream, GBitmap* bitmap, U32 compressionLevel) +static bool sWriteStreamPNG(const String& /*bmType*/, Stream& stream, GBitmap* bitmap, U32 compressionLevel) { U32 waterMark = FrameAllocator::getWaterMark(); - if ( compressionLevel < 10 ) + if (compressionLevel < 10) { bool retVal = _writePNG(bitmap, stream, compressionLevel, 0, PNG_ALL_FILTERS); FrameAllocator::setWaterMark(waterMark); @@ -501,195 +471,144 @@ static bool sWriteStreamPNG(const String& bmType, Stream& stream, GBitmap* bitma U8* buffer = new U8[1 << 22]; // 4 Megs. Should be enough... MemStream* pMemStream = new MemStream(1 << 22, buffer, false, true); - const U32 zStrategies[] = { Z_DEFAULT_STRATEGY, - Z_FILTERED }; - const U32 pngFilters[] = { PNG_FILTER_NONE, - PNG_FILTER_SUB, - PNG_FILTER_UP, - PNG_FILTER_AVG, - PNG_FILTER_PAETH, - PNG_ALL_FILTERS }; + const U32 zStrategies[] = { Z_DEFAULT_STRATEGY, Z_FILTERED }; + const U32 pngFilters[] = { PNG_FILTER_NONE, PNG_FILTER_SUB, PNG_FILTER_UP, + PNG_FILTER_AVG, PNG_FILTER_PAETH, PNG_ALL_FILTERS }; - U32 minSize = 0xFFFFFFFF; - U32 bestStrategy = 0xFFFFFFFF; - U32 bestFilter = 0xFFFFFFFF; - U32 bestCLevel = 0xFFFFFFFF; + U32 minSize = U32_MAX, bestCLevel = 0, bestStrategy = 0, bestFilter = 0; - for (U32 cl = 0; cl <=9; cl++) + for (U32 cl = 0; cl <= 9; cl++) { - for (U32 zs = 0; zs < 2; zs++) + for (U32 zs = 0; zs < 2; zs++) { - for (U32 pf = 0; pf < 6; pf++) + for (U32 pf = 0; pf < 6; pf++) { pMemStream->setPosition(0); - U32 waterMarkInner = FrameAllocator::getWaterMark(); - if (_writePNG(bitmap, *pMemStream, cl, zStrategies[zs], pngFilters[pf]) == false) - AssertFatal(false, "Handle this error!"); + if (!_writePNG(bitmap, *pMemStream, cl, zStrategies[zs], pngFilters[pf])) + { + Con::errorf("sWriteStreamPNG - Compression search failed at cl=%d zs=%d pf=%d.", cl, zs, pf); + FrameAllocator::setWaterMark(waterMarkInner); + continue; + } FrameAllocator::setWaterMark(waterMarkInner); - if (pMemStream->getPosition() < minSize) + if (pMemStream->getPosition() < minSize) { minSize = pMemStream->getPosition(); - bestStrategy = zs; - bestFilter = pf; - bestCLevel = cl; + bestCLevel = cl; bestStrategy = zs; bestFilter = pf; } } } } - AssertFatal(minSize != 0xFFFFFFFF, "Error, no best found?"); delete pMemStream; - delete [] buffer; + delete[] buffer; + if (minSize == U32_MAX) + { + Con::errorf("sWriteStreamPNG - No valid compression result found."); + FrameAllocator::setWaterMark(waterMark); + return false; + } - bool retVal = _writePNG(bitmap, stream, - bestCLevel, - zStrategies[bestStrategy], - pngFilters[bestFilter]); + bool retVal = _writePNG(bitmap, stream, bestCLevel, zStrategies[bestStrategy], pngFilters[bestFilter]); FrameAllocator::setWaterMark(waterMark); - return retVal; } -//-------------------------------------------------------------------------- -// Stores PNG stream data -struct DeferredPNGWriterData { - png_structp png_ptr; - png_infop info_ptr; - U32 width; - U32 height; -}; -DeferredPNGWriter::DeferredPNGWriter() : - mData( NULL ), - mActive(false) +static bool sWritePNG(const Torque::Path& path, GBitmap* bitmap, U32 compressionLevel) { - 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() { delete mData; } -bool DeferredPNGWriter::begin( GFXFormat format, S32 width, S32 height, Stream &stream, U32 compressionLevel ) -{ - // ONLY RGB bitmap writing supported at this time! - 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) +bool DeferredPNGWriter::begin(GFXFormat format, S32 width, S32 height, Stream& stream, U32 compressionLevel) +{ + if (!_isSupportedWriteFormat(format)) { - png_destroy_write_struct(&mData->png_ptr, (png_infopp)NULL); + Con::errorf("DeferredPNGWriter::begin - Unsupported format %d.", format); return false; } - png_set_write_fn(mData->png_ptr, &stream, pngWriteDataFn, pngFlushDataFn); + mData = new DeferredPNGWriterData("DeferredPNGWriter"); - // Set the compression level and image filters - 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) + if (!mData->guard.init(false)) { - png_set_IHDR(mData->png_ptr, mData->info_ptr, - width, height, // the width & height - 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 ); + Con::errorf("DeferredPNGWriter::begin - Failed to init PNG write structs. Format: %d, Size: %dx%d.", format, width, height); + return false; } - 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; - return true; } -void DeferredPNGWriter::append( GBitmap* bitmap, U32 rows) + +void DeferredPNGWriter::append(GBitmap* bitmap, U32 rows) { - AssertFatal(mActive, "Cannot append to an inactive DeferredPNGWriter!"); - - U32 height = getMin( bitmap->getHeight(), rows); + AssertFatal(mActive, "DeferredPNGWriter::append - Writer is not active."); + U32 height = getMin(bitmap->getHeight(), rows); FrameAllocatorMarker marker; - png_bytep* row_pointers = (png_bytep*)marker.alloc( height * sizeof( png_bytep ) ); - for (U32 i=0; i(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() { - AssertFatal(mActive, "Cannot end an inactive DeferredPNGWriter!"); - - png_write_end(mData->png_ptr, mData->info_ptr); - png_destroy_write_struct(&mData->png_ptr, (png_infopp)NULL); + AssertFatal(mActive, "DeferredPNGWriter::end - Writer is not active."); + png_write_end(mData->guard.png_ptr, mData->guard.info_ptr); mActive = false; }