From 7f9438ed2b1e91aff615673ca2d97a3f300910f6 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Thu, 14 Dec 2023 23:25:11 +0100 Subject: SL-3508 Crash in LLKDUDecodeState::processTileDecode --- indra/llimage/llimagej2c.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'indra/llimage/llimagej2c.cpp') diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp index 8dba1641a6..a4957466d4 100644 --- a/indra/llimage/llimagej2c.cpp +++ b/indra/llimage/llimagej2c.cpp @@ -157,10 +157,10 @@ bool LLImageJ2C::decodeChannels(LLImageRaw *raw_imagep, F32 decode_time, S32 fir LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; LLTimer elapsed; - bool res = true; - resetLastError(); + mDecoding = true; + bool res; // Check to make sure that this instance has been initialized with data if (!getData() || (getDataSize() < 16)) { @@ -171,7 +171,6 @@ bool LLImageJ2C::decodeChannels(LLImageRaw *raw_imagep, F32 decode_time, S32 fir { // Update the raw discard level updateRawDiscardLevel(); - mDecoding = true; res = mImpl->decodeImpl(*this, *raw_imagep, decode_time, first_channel, max_channel_count); } @@ -181,12 +180,21 @@ bool LLImageJ2C::decodeChannels(LLImageRaw *raw_imagep, F32 decode_time, S32 fir { // Failed raw_imagep->deleteData(); + res = false; } else { mDecoding = false; } } + else + { + if (mDecoding) + { + LL_WARNS() << "decodeImpl failed but mDecoding is TRUE" << LL_ENDL; + mDecoding = false; + } + } if (!mLastError.empty()) { -- cgit v1.2.3 From 74c8b028d42a8c5b080bb861e427f38cedd4ad7c Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Fri, 15 Dec 2023 18:26:14 +0100 Subject: SL-20743 Use LLMutex in LLImageBase for internal data thread-safety --- indra/llimage/llimagej2c.cpp | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) (limited to 'indra/llimage/llimagej2c.cpp') diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp index a4957466d4..a06c461107 100644 --- a/indra/llimage/llimagej2c.cpp +++ b/indra/llimage/llimagej2c.cpp @@ -107,6 +107,8 @@ bool LLImageJ2C::updateData() bool res = true; resetLastError(); + LLImageDataLock lock(this); + // Check to make sure that this instance has been initialized with data if (!getData() || (getDataSize() < 16)) { @@ -158,22 +160,26 @@ bool LLImageJ2C::decodeChannels(LLImageRaw *raw_imagep, F32 decode_time, S32 fir LLTimer elapsed; resetLastError(); - mDecoding = true; bool res; - // Check to make sure that this instance has been initialized with data - if (!getData() || (getDataSize() < 16)) { - setLastError("LLImageJ2C uninitialized"); - res = true; // done - } - else - { - // Update the raw discard level - updateRawDiscardLevel(); - res = mImpl->decodeImpl(*this, *raw_imagep, decode_time, first_channel, max_channel_count); + LLImageDataLock lock(this); + + mDecoding = true; + // Check to make sure that this instance has been initialized with data + if (!getData() || (getDataSize() < 16)) + { + setLastError("LLImageJ2C uninitialized"); + res = true; // done + } + else + { + // Update the raw discard level + updateRawDiscardLevel(); + res = mImpl->decodeImpl(*this, *raw_imagep, decode_time, first_channel, max_channel_count); + } } - + if (res) { if (!mDecoding) @@ -414,9 +420,10 @@ bool LLImageJ2C::loadAndValidate(const std::string &filename) bool LLImageJ2C::validate(U8 *data, U32 file_size) { - resetLastError(); - + + LLImageDataLock lock(this); + setData(data, file_size); bool res = updateData(); -- cgit v1.2.3 From e2e37cced861b98de8c1a7c9c0d3a50d2d90e433 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 22 May 2024 21:25:21 +0200 Subject: Fix line endlings --- indra/llimage/llimagej2c.cpp | 1186 +++++++++++++++++++++--------------------- 1 file changed, 593 insertions(+), 593 deletions(-) (limited to 'indra/llimage/llimagej2c.cpp') diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp index 9593c6b972..0058b91b0f 100644 --- a/indra/llimage/llimagej2c.cpp +++ b/indra/llimage/llimagej2c.cpp @@ -1,593 +1,593 @@ -/** - * @file llimagej2c.cpp - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#include "linden_common.h" - -#include "llapr.h" -#include "lldir.h" -#include "llimagej2c.h" -#include "lltimer.h" -#include "llmath.h" -#include "llmemory.h" -#include "llsd.h" -#include - -// Declare the prototype for this factory function here. It is implemented in -// other files which define a LLImageJ2CImpl subclass, but only ONE static -// library which has the implementation for this function should ever be -// linked. -LLImageJ2CImpl* fallbackCreateLLImageJ2CImpl(); - -// Test data gathering handle -LLImageCompressionTester* LLImageJ2C::sTesterp = NULL ; -const std::string sTesterName("ImageCompressionTester"); - -//static -std::string LLImageJ2C::getEngineInfo() -{ - // All known LLImageJ2CImpl implementation subclasses are cheap to - // construct. - std::unique_ptr impl(fallbackCreateLLImageJ2CImpl()); - return impl->getEngineInfo(); -} - -LLImageJ2C::LLImageJ2C() : LLImageFormatted(IMG_CODEC_J2C), - mMaxBytes(0), - mRawDiscardLevel(-1), - mRate(DEFAULT_COMPRESSION_RATE), - mReversible(false), - mAreaUsedForDataSizeCalcs(0) -{ - mImpl.reset(fallbackCreateLLImageJ2CImpl()); - - // Clear data size table - for( S32 i = 0; i <= MAX_DISCARD_LEVEL; i++) - { // Array size is MAX_DISCARD_LEVEL+1 - mDataSizes[i] = 0; - } - - // If that test log has ben requested but not yet created, create it - if (LLMetricPerformanceTesterBasic::isMetricLogRequested(sTesterName) && !LLMetricPerformanceTesterBasic::getTester(sTesterName)) - { - sTesterp = new LLImageCompressionTester() ; - if (!sTesterp->isValid()) - { - delete sTesterp; - sTesterp = NULL; - } - } -} - -// virtual -LLImageJ2C::~LLImageJ2C() {} - -// virtual -void LLImageJ2C::resetLastError() -{ - mLastError.clear(); -} - -//virtual -void LLImageJ2C::setLastError(const std::string& message, const std::string& filename) -{ - mLastError = message; - if (!filename.empty()) - mLastError += std::string(" FILE: ") + filename; -} - -// virtual -S8 LLImageJ2C::getRawDiscardLevel() -{ - return mRawDiscardLevel; -} - -bool LLImageJ2C::updateData() -{ - bool res = true; - resetLastError(); - - LLImageDataLock lock(this); - - // Check to make sure that this instance has been initialized with data - if (!getData() || (getDataSize() < 16)) - { - setLastError("LLImageJ2C uninitialized"); - res = false; - } - else - { - res = mImpl->getMetadata(*this); - } - - if (res) - { - // SJB: override discard based on mMaxBytes elsewhere - S32 max_bytes = getDataSize(); // mMaxBytes ? mMaxBytes : getDataSize(); - S32 discard = calcDiscardLevelBytes(max_bytes); - setDiscardLevel(discard); - } - - if (!mLastError.empty()) - { - LLImage::setLastError(mLastError); - } - return res; -} - -bool LLImageJ2C::initDecode(LLImageRaw &raw_image, int discard_level, int* region) -{ - setDiscardLevel(discard_level != -1 ? discard_level : 0); - return mImpl->initDecode(*this,raw_image,discard_level,region); -} - -bool LLImageJ2C::initEncode(LLImageRaw &raw_image, int blocks_size, int precincts_size, int levels) -{ - return mImpl->initEncode(*this,raw_image,blocks_size,precincts_size,levels); -} - -bool LLImageJ2C::decode(LLImageRaw *raw_imagep, F32 decode_time) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - return decodeChannels(raw_imagep, decode_time, 0, 4); -} - - -// Returns true to mean done, whether successful or not. -bool LLImageJ2C::decodeChannels(LLImageRaw *raw_imagep, F32 decode_time, S32 first_channel, S32 max_channel_count ) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - LLTimer elapsed; - - resetLastError(); - - bool res; - { - LLImageDataLock lock(this); - - mDecoding = true; - // Check to make sure that this instance has been initialized with data - if (!getData() || (getDataSize() < 16)) - { - setLastError("LLImageJ2C uninitialized"); - res = true; // done - } - else - { - // Update the raw discard level - updateRawDiscardLevel(); - res = mImpl->decodeImpl(*this, *raw_imagep, decode_time, first_channel, max_channel_count); - } - } - - if (res) - { - if (!mDecoding) - { - // Failed - raw_imagep->deleteData(); - res = false; - } - else - { - mDecoding = false; - } - } - else - { - if (mDecoding) - { - LL_WARNS() << "decodeImpl failed but mDecoding is true" << LL_ENDL; - mDecoding = false; - } - } - - if (!mLastError.empty()) - { - LLImage::setLastError(mLastError); - } - - LLImageCompressionTester* tester = (LLImageCompressionTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); - if (tester) - { - // Decompression stat gathering - // Note that we *do not* take into account the decompression failures data so we might overestimate the time spent processing - - // Always add the decompression time to the stat - tester->updateDecompressionStats(elapsed.getElapsedTimeF32()) ; - if (res) - { - // The whole data stream is finally decompressed when res is returned as true - tester->updateDecompressionStats(this->getDataSize(), raw_imagep->getDataSize()) ; - } - } - - return res; -} - - -bool LLImageJ2C::encode(const LLImageRaw *raw_imagep, F32 encode_time) -{ - return encode(raw_imagep, NULL, encode_time); -} - - -bool LLImageJ2C::encode(const LLImageRaw *raw_imagep, const char* comment_text, F32 encode_time) -{ - LLTimer elapsed; - resetLastError(); - bool res = mImpl->encodeImpl(*this, *raw_imagep, comment_text, encode_time, mReversible); - if (!mLastError.empty()) - { - LLImage::setLastError(mLastError); - } - - LLImageCompressionTester* tester = (LLImageCompressionTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); - if (tester) - { - // Compression stat gathering - // Note that we *do not* take into account the compression failures cases so we night overestimate the time spent processing - - // Always add the compression time to the stat - tester->updateCompressionStats(elapsed.getElapsedTimeF32()) ; - if (res) - { - // The whole data stream is finally compressed when res is returned as true - tester->updateCompressionStats(this->getDataSize(), raw_imagep->getDataSize()) ; - } - } - - return res; -} - -//static -S32 LLImageJ2C::calcHeaderSizeJ2C() -{ - return FIRST_PACKET_SIZE; // Hack. just needs to be >= actual header size... -} - -//static -S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) -{ - // Note: This provides an estimation for the first to last quality layer of a given discard level - // This is however an efficient approximation, as the true discard level boundary would be - // in general too big for fast fetching. - // For details about the equation used here, see https://wiki.lindenlab.com/wiki/THX1138_KDU_Improvements#Byte_Range_Study - - // Estimate the number of layers. This is consistent with what's done for j2c encoding in LLImageJ2CKDU::encodeImpl(). - S32 nb_layers = 1; - S32 surface = w*h; - S32 s = 64*64; - while (surface > s) - { - nb_layers++; - s *= 4; - } - F32 layer_factor = 3.0f * (7 - llclamp(nb_layers,1,6)); - - // Compute w/pow(2,discard_level) and h/pow(2,discard_level) - w >>= discard_level; - h >>= discard_level; - w = llmax(w, 1); - h = llmax(h, 1); - - // Temporary: compute both new and old range and pick one according to the settings TextureNewByteRange - // *TODO: Take the old code out once we have enough tests done - S32 bytes; - S32 new_bytes = (S32) (sqrt((F32)(w*h))*(F32)(comp)*rate*1000.f/layer_factor); - S32 old_bytes = (S32)((F32)(w*h*comp)*rate); - bytes = (LLImage::useNewByteRange() && (new_bytes < old_bytes) ? new_bytes : old_bytes); - bytes = llmax(bytes, calcHeaderSizeJ2C()); - return bytes; -} - -S32 LLImageJ2C::calcHeaderSize() -{ - return calcHeaderSizeJ2C(); -} - -// calcDataSize() returns how many bytes to read to load discard_level (including header) -S32 LLImageJ2C::calcDataSize(S32 discard_level) -{ - discard_level = llclamp(discard_level, 0, MAX_DISCARD_LEVEL); - if ( mAreaUsedForDataSizeCalcs != (getHeight() * getWidth()) - || (mDataSizes[0] == 0)) - { - mAreaUsedForDataSizeCalcs = getHeight() * getWidth(); - - S32 level = MAX_DISCARD_LEVEL; // Start at the highest discard - while ( level >= 0 ) - { - mDataSizes[level] = calcDataSizeJ2C(getWidth(), getHeight(), getComponents(), level, mRate); - level--; - } - } - return mDataSizes[discard_level]; -} - -S32 LLImageJ2C::calcDiscardLevelBytes(S32 bytes) -{ - llassert(bytes >= 0); - S32 discard_level = 0; - if (bytes == 0) - { - return MAX_DISCARD_LEVEL; - } - while (1) - { - S32 bytes_needed = calcDataSize(discard_level); - // Use TextureReverseByteRange percent (see settings.xml) of the optimal size to qualify as correct rendering for the given discard level - if (bytes >= (bytes_needed*LLImage::getReverseByteRangePercent()/100)) - { - break; - } - discard_level++; - if (discard_level >= MAX_DISCARD_LEVEL) - { - break; - } - } - return discard_level; -} - -void LLImageJ2C::setMaxBytes(S32 max_bytes) -{ - mMaxBytes = max_bytes; -} - -void LLImageJ2C::setReversible(const bool reversible) -{ - mReversible = reversible; -} - - -bool LLImageJ2C::loadAndValidate(const std::string &filename) -{ - bool res = true; - - resetLastError(); - - S32 file_size = 0; - LLAPRFile infile ; - infile.open(filename, LL_APR_RB, NULL, &file_size); - apr_file_t* apr_file = infile.getFileHandle() ; - if (!apr_file) - { - setLastError("Unable to open file for reading", filename); - res = false; - } - else if (file_size == 0) - { - setLastError("File is empty",filename); - res = false; - } - else - { - U8 *data = (U8*)ll_aligned_malloc_16(file_size); - if (!data) - { - infile.close(); - setLastError("Out of memory", filename); - res = false; - } - else - { - apr_size_t bytes_read = file_size; - apr_status_t s = apr_file_read(apr_file, data, &bytes_read); // modifies bytes_read - infile.close(); - - if (s != APR_SUCCESS || (S32)bytes_read != file_size) - { - ll_aligned_free_16(data); - setLastError("Unable to read entire file"); - res = false; - } - else - { - res = validate(data, file_size); - } - } - } - - if (!mLastError.empty()) - { - LLImage::setLastError(mLastError); - } - - return res; -} - - -bool LLImageJ2C::validate(U8 *data, U32 file_size) -{ - resetLastError(); - - LLImageDataLock lock(this); - - setData(data, file_size); - - bool res = updateData(); - if ( res ) - { - // Check to make sure that this instance has been initialized with data - if (!getData() || (0 == getDataSize())) - { - setLastError("LLImageJ2C uninitialized"); - res = false; - } - else - { - res = mImpl->getMetadata(*this); - } - } - - if (!mLastError.empty()) - { - LLImage::setLastError(mLastError); - } - return res; -} - -void LLImageJ2C::decodeFailed() -{ - mDecoding = false; -} - -void LLImageJ2C::updateRawDiscardLevel() -{ - mRawDiscardLevel = mMaxBytes ? calcDiscardLevelBytes(mMaxBytes) : mDiscardLevel; -} - -LLImageJ2CImpl::~LLImageJ2CImpl() -{ -} - -//---------------------------------------------------------------------------------------------- -// Start of LLImageCompressionTester -//---------------------------------------------------------------------------------------------- -LLImageCompressionTester::LLImageCompressionTester() : LLMetricPerformanceTesterBasic(sTesterName) -{ - addMetric("Time Decompression (s)"); - addMetric("Volume In Decompression (kB)"); - addMetric("Volume Out Decompression (kB)"); - addMetric("Decompression Ratio (x:1)"); - addMetric("Perf Decompression (kB/s)"); - - addMetric("Time Compression (s)"); - addMetric("Volume In Compression (kB)"); - addMetric("Volume Out Compression (kB)"); - addMetric("Compression Ratio (x:1)"); - addMetric("Perf Compression (kB/s)"); - - mRunBytesInDecompression = 0; - mRunBytesOutDecompression = 0; - mRunBytesInCompression = 0; - - mTotalBytesInDecompression = 0; - mTotalBytesOutDecompression = 0; - mTotalBytesInCompression = 0; - mTotalBytesOutCompression = 0; - - mTotalTimeDecompression = 0.0f; - mTotalTimeCompression = 0.0f; - mRunTimeDecompression = 0.0f; -} - -LLImageCompressionTester::~LLImageCompressionTester() -{ - outputTestResults(); - LLImageJ2C::sTesterp = NULL; -} - -//virtual -void LLImageCompressionTester::outputTestRecord(LLSD *sd) -{ - std::string currentLabel = getCurrentLabelName(); - - F32 decompressionPerf = 0.0f; - F32 compressionPerf = 0.0f; - F32 decompressionRate = 0.0f; - F32 compressionRate = 0.0f; - - F32 totalkBInDecompression = (F32)(mTotalBytesInDecompression) / 1000.f; - F32 totalkBOutDecompression = (F32)(mTotalBytesOutDecompression) / 1000.f; - F32 totalkBInCompression = (F32)(mTotalBytesInCompression) / 1000.f; - F32 totalkBOutCompression = (F32)(mTotalBytesOutCompression) / 1000.f; - - if (!is_approx_zero(mTotalTimeDecompression)) - { - decompressionPerf = totalkBInDecompression / mTotalTimeDecompression; - } - if (!is_approx_zero(totalkBInDecompression)) - { - decompressionRate = totalkBOutDecompression / totalkBInDecompression; - } - if (!is_approx_zero(mTotalTimeCompression)) - { - compressionPerf = totalkBInCompression / mTotalTimeCompression; - } - if (!is_approx_zero(totalkBOutCompression)) - { - compressionRate = totalkBInCompression / totalkBOutCompression; - } - - (*sd)[currentLabel]["Time Decompression (s)"] = (LLSD::Real)mTotalTimeDecompression; - (*sd)[currentLabel]["Volume In Decompression (kB)"] = (LLSD::Real)totalkBInDecompression; - (*sd)[currentLabel]["Volume Out Decompression (kB)"]= (LLSD::Real)totalkBOutDecompression; - (*sd)[currentLabel]["Decompression Ratio (x:1)"] = (LLSD::Real)decompressionRate; - (*sd)[currentLabel]["Perf Decompression (kB/s)"] = (LLSD::Real)decompressionPerf; - - (*sd)[currentLabel]["Time Compression (s)"] = (LLSD::Real)mTotalTimeCompression; - (*sd)[currentLabel]["Volume In Compression (kB)"] = (LLSD::Real)totalkBInCompression; - (*sd)[currentLabel]["Volume Out Compression (kB)"] = (LLSD::Real)totalkBOutCompression; - (*sd)[currentLabel]["Compression Ratio (x:1)"] = (LLSD::Real)compressionRate; - (*sd)[currentLabel]["Perf Compression (kB/s)"] = (LLSD::Real)compressionPerf; -} - -void LLImageCompressionTester::updateCompressionStats(const F32 deltaTime) -{ - mTotalTimeCompression += deltaTime; -} - -void LLImageCompressionTester::updateCompressionStats(const S32 bytesCompress, const S32 bytesRaw) -{ - mTotalBytesInCompression += bytesRaw; - mRunBytesInCompression += bytesRaw; - mTotalBytesOutCompression += bytesCompress; - if (mRunBytesInCompression > (1000000)) - { - // Output everything - outputTestResults(); - // Reset the compression data of the run - mRunBytesInCompression = 0; - } -} - -void LLImageCompressionTester::updateDecompressionStats(const F32 deltaTime) -{ - mTotalTimeDecompression += deltaTime; -} - -void LLImageCompressionTester::updateDecompressionStats(const S32 bytesIn, const S32 bytesOut) -{ - mTotalBytesInDecompression += bytesIn; - mRunBytesInDecompression += bytesIn; - mTotalBytesOutDecompression += bytesOut; - mRunBytesOutDecompression += bytesOut; - //if (mRunBytesInDecompression > (1000000)) - if (mRunBytesOutDecompression > (10000000)) - //if ((mTotalTimeDecompression - mRunTimeDecompression) >= (5.0f)) - { - // Output everything - outputTestResults(); - // Reset the decompression data of the run - mRunBytesInDecompression = 0; - mRunBytesOutDecompression = 0; - mRunTimeDecompression = mTotalTimeDecompression; - } -} - -//---------------------------------------------------------------------------------------------- -// End of LLTexturePipelineTester -//---------------------------------------------------------------------------------------------- - +/** + * @file llimagej2c.cpp + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#include "linden_common.h" + +#include "llapr.h" +#include "lldir.h" +#include "llimagej2c.h" +#include "lltimer.h" +#include "llmath.h" +#include "llmemory.h" +#include "llsd.h" +#include + +// Declare the prototype for this factory function here. It is implemented in +// other files which define a LLImageJ2CImpl subclass, but only ONE static +// library which has the implementation for this function should ever be +// linked. +LLImageJ2CImpl* fallbackCreateLLImageJ2CImpl(); + +// Test data gathering handle +LLImageCompressionTester* LLImageJ2C::sTesterp = NULL ; +const std::string sTesterName("ImageCompressionTester"); + +//static +std::string LLImageJ2C::getEngineInfo() +{ + // All known LLImageJ2CImpl implementation subclasses are cheap to + // construct. + std::unique_ptr impl(fallbackCreateLLImageJ2CImpl()); + return impl->getEngineInfo(); +} + +LLImageJ2C::LLImageJ2C() : LLImageFormatted(IMG_CODEC_J2C), + mMaxBytes(0), + mRawDiscardLevel(-1), + mRate(DEFAULT_COMPRESSION_RATE), + mReversible(false), + mAreaUsedForDataSizeCalcs(0) +{ + mImpl.reset(fallbackCreateLLImageJ2CImpl()); + + // Clear data size table + for( S32 i = 0; i <= MAX_DISCARD_LEVEL; i++) + { // Array size is MAX_DISCARD_LEVEL+1 + mDataSizes[i] = 0; + } + + // If that test log has ben requested but not yet created, create it + if (LLMetricPerformanceTesterBasic::isMetricLogRequested(sTesterName) && !LLMetricPerformanceTesterBasic::getTester(sTesterName)) + { + sTesterp = new LLImageCompressionTester() ; + if (!sTesterp->isValid()) + { + delete sTesterp; + sTesterp = NULL; + } + } +} + +// virtual +LLImageJ2C::~LLImageJ2C() {} + +// virtual +void LLImageJ2C::resetLastError() +{ + mLastError.clear(); +} + +//virtual +void LLImageJ2C::setLastError(const std::string& message, const std::string& filename) +{ + mLastError = message; + if (!filename.empty()) + mLastError += std::string(" FILE: ") + filename; +} + +// virtual +S8 LLImageJ2C::getRawDiscardLevel() +{ + return mRawDiscardLevel; +} + +bool LLImageJ2C::updateData() +{ + bool res = true; + resetLastError(); + + LLImageDataLock lock(this); + + // Check to make sure that this instance has been initialized with data + if (!getData() || (getDataSize() < 16)) + { + setLastError("LLImageJ2C uninitialized"); + res = false; + } + else + { + res = mImpl->getMetadata(*this); + } + + if (res) + { + // SJB: override discard based on mMaxBytes elsewhere + S32 max_bytes = getDataSize(); // mMaxBytes ? mMaxBytes : getDataSize(); + S32 discard = calcDiscardLevelBytes(max_bytes); + setDiscardLevel(discard); + } + + if (!mLastError.empty()) + { + LLImage::setLastError(mLastError); + } + return res; +} + +bool LLImageJ2C::initDecode(LLImageRaw &raw_image, int discard_level, int* region) +{ + setDiscardLevel(discard_level != -1 ? discard_level : 0); + return mImpl->initDecode(*this,raw_image,discard_level,region); +} + +bool LLImageJ2C::initEncode(LLImageRaw &raw_image, int blocks_size, int precincts_size, int levels) +{ + return mImpl->initEncode(*this,raw_image,blocks_size,precincts_size,levels); +} + +bool LLImageJ2C::decode(LLImageRaw *raw_imagep, F32 decode_time) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + return decodeChannels(raw_imagep, decode_time, 0, 4); +} + + +// Returns true to mean done, whether successful or not. +bool LLImageJ2C::decodeChannels(LLImageRaw *raw_imagep, F32 decode_time, S32 first_channel, S32 max_channel_count ) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + LLTimer elapsed; + + resetLastError(); + + bool res; + { + LLImageDataLock lock(this); + + mDecoding = true; + // Check to make sure that this instance has been initialized with data + if (!getData() || (getDataSize() < 16)) + { + setLastError("LLImageJ2C uninitialized"); + res = true; // done + } + else + { + // Update the raw discard level + updateRawDiscardLevel(); + res = mImpl->decodeImpl(*this, *raw_imagep, decode_time, first_channel, max_channel_count); + } + } + + if (res) + { + if (!mDecoding) + { + // Failed + raw_imagep->deleteData(); + res = false; + } + else + { + mDecoding = false; + } + } + else + { + if (mDecoding) + { + LL_WARNS() << "decodeImpl failed but mDecoding is true" << LL_ENDL; + mDecoding = false; + } + } + + if (!mLastError.empty()) + { + LLImage::setLastError(mLastError); + } + + LLImageCompressionTester* tester = (LLImageCompressionTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); + if (tester) + { + // Decompression stat gathering + // Note that we *do not* take into account the decompression failures data so we might overestimate the time spent processing + + // Always add the decompression time to the stat + tester->updateDecompressionStats(elapsed.getElapsedTimeF32()) ; + if (res) + { + // The whole data stream is finally decompressed when res is returned as true + tester->updateDecompressionStats(this->getDataSize(), raw_imagep->getDataSize()) ; + } + } + + return res; +} + + +bool LLImageJ2C::encode(const LLImageRaw *raw_imagep, F32 encode_time) +{ + return encode(raw_imagep, NULL, encode_time); +} + + +bool LLImageJ2C::encode(const LLImageRaw *raw_imagep, const char* comment_text, F32 encode_time) +{ + LLTimer elapsed; + resetLastError(); + bool res = mImpl->encodeImpl(*this, *raw_imagep, comment_text, encode_time, mReversible); + if (!mLastError.empty()) + { + LLImage::setLastError(mLastError); + } + + LLImageCompressionTester* tester = (LLImageCompressionTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); + if (tester) + { + // Compression stat gathering + // Note that we *do not* take into account the compression failures cases so we night overestimate the time spent processing + + // Always add the compression time to the stat + tester->updateCompressionStats(elapsed.getElapsedTimeF32()) ; + if (res) + { + // The whole data stream is finally compressed when res is returned as true + tester->updateCompressionStats(this->getDataSize(), raw_imagep->getDataSize()) ; + } + } + + return res; +} + +//static +S32 LLImageJ2C::calcHeaderSizeJ2C() +{ + return FIRST_PACKET_SIZE; // Hack. just needs to be >= actual header size... +} + +//static +S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) +{ + // Note: This provides an estimation for the first to last quality layer of a given discard level + // This is however an efficient approximation, as the true discard level boundary would be + // in general too big for fast fetching. + // For details about the equation used here, see https://wiki.lindenlab.com/wiki/THX1138_KDU_Improvements#Byte_Range_Study + + // Estimate the number of layers. This is consistent with what's done for j2c encoding in LLImageJ2CKDU::encodeImpl(). + S32 nb_layers = 1; + S32 surface = w*h; + S32 s = 64*64; + while (surface > s) + { + nb_layers++; + s *= 4; + } + F32 layer_factor = 3.0f * (7 - llclamp(nb_layers,1,6)); + + // Compute w/pow(2,discard_level) and h/pow(2,discard_level) + w >>= discard_level; + h >>= discard_level; + w = llmax(w, 1); + h = llmax(h, 1); + + // Temporary: compute both new and old range and pick one according to the settings TextureNewByteRange + // *TODO: Take the old code out once we have enough tests done + S32 bytes; + S32 new_bytes = (S32) (sqrt((F32)(w*h))*(F32)(comp)*rate*1000.f/layer_factor); + S32 old_bytes = (S32)((F32)(w*h*comp)*rate); + bytes = (LLImage::useNewByteRange() && (new_bytes < old_bytes) ? new_bytes : old_bytes); + bytes = llmax(bytes, calcHeaderSizeJ2C()); + return bytes; +} + +S32 LLImageJ2C::calcHeaderSize() +{ + return calcHeaderSizeJ2C(); +} + +// calcDataSize() returns how many bytes to read to load discard_level (including header) +S32 LLImageJ2C::calcDataSize(S32 discard_level) +{ + discard_level = llclamp(discard_level, 0, MAX_DISCARD_LEVEL); + if ( mAreaUsedForDataSizeCalcs != (getHeight() * getWidth()) + || (mDataSizes[0] == 0)) + { + mAreaUsedForDataSizeCalcs = getHeight() * getWidth(); + + S32 level = MAX_DISCARD_LEVEL; // Start at the highest discard + while ( level >= 0 ) + { + mDataSizes[level] = calcDataSizeJ2C(getWidth(), getHeight(), getComponents(), level, mRate); + level--; + } + } + return mDataSizes[discard_level]; +} + +S32 LLImageJ2C::calcDiscardLevelBytes(S32 bytes) +{ + llassert(bytes >= 0); + S32 discard_level = 0; + if (bytes == 0) + { + return MAX_DISCARD_LEVEL; + } + while (1) + { + S32 bytes_needed = calcDataSize(discard_level); + // Use TextureReverseByteRange percent (see settings.xml) of the optimal size to qualify as correct rendering for the given discard level + if (bytes >= (bytes_needed*LLImage::getReverseByteRangePercent()/100)) + { + break; + } + discard_level++; + if (discard_level >= MAX_DISCARD_LEVEL) + { + break; + } + } + return discard_level; +} + +void LLImageJ2C::setMaxBytes(S32 max_bytes) +{ + mMaxBytes = max_bytes; +} + +void LLImageJ2C::setReversible(const bool reversible) +{ + mReversible = reversible; +} + + +bool LLImageJ2C::loadAndValidate(const std::string &filename) +{ + bool res = true; + + resetLastError(); + + S32 file_size = 0; + LLAPRFile infile ; + infile.open(filename, LL_APR_RB, NULL, &file_size); + apr_file_t* apr_file = infile.getFileHandle() ; + if (!apr_file) + { + setLastError("Unable to open file for reading", filename); + res = false; + } + else if (file_size == 0) + { + setLastError("File is empty",filename); + res = false; + } + else + { + U8 *data = (U8*)ll_aligned_malloc_16(file_size); + if (!data) + { + infile.close(); + setLastError("Out of memory", filename); + res = false; + } + else + { + apr_size_t bytes_read = file_size; + apr_status_t s = apr_file_read(apr_file, data, &bytes_read); // modifies bytes_read + infile.close(); + + if (s != APR_SUCCESS || (S32)bytes_read != file_size) + { + ll_aligned_free_16(data); + setLastError("Unable to read entire file"); + res = false; + } + else + { + res = validate(data, file_size); + } + } + } + + if (!mLastError.empty()) + { + LLImage::setLastError(mLastError); + } + + return res; +} + + +bool LLImageJ2C::validate(U8 *data, U32 file_size) +{ + resetLastError(); + + LLImageDataLock lock(this); + + setData(data, file_size); + + bool res = updateData(); + if ( res ) + { + // Check to make sure that this instance has been initialized with data + if (!getData() || (0 == getDataSize())) + { + setLastError("LLImageJ2C uninitialized"); + res = false; + } + else + { + res = mImpl->getMetadata(*this); + } + } + + if (!mLastError.empty()) + { + LLImage::setLastError(mLastError); + } + return res; +} + +void LLImageJ2C::decodeFailed() +{ + mDecoding = false; +} + +void LLImageJ2C::updateRawDiscardLevel() +{ + mRawDiscardLevel = mMaxBytes ? calcDiscardLevelBytes(mMaxBytes) : mDiscardLevel; +} + +LLImageJ2CImpl::~LLImageJ2CImpl() +{ +} + +//---------------------------------------------------------------------------------------------- +// Start of LLImageCompressionTester +//---------------------------------------------------------------------------------------------- +LLImageCompressionTester::LLImageCompressionTester() : LLMetricPerformanceTesterBasic(sTesterName) +{ + addMetric("Time Decompression (s)"); + addMetric("Volume In Decompression (kB)"); + addMetric("Volume Out Decompression (kB)"); + addMetric("Decompression Ratio (x:1)"); + addMetric("Perf Decompression (kB/s)"); + + addMetric("Time Compression (s)"); + addMetric("Volume In Compression (kB)"); + addMetric("Volume Out Compression (kB)"); + addMetric("Compression Ratio (x:1)"); + addMetric("Perf Compression (kB/s)"); + + mRunBytesInDecompression = 0; + mRunBytesOutDecompression = 0; + mRunBytesInCompression = 0; + + mTotalBytesInDecompression = 0; + mTotalBytesOutDecompression = 0; + mTotalBytesInCompression = 0; + mTotalBytesOutCompression = 0; + + mTotalTimeDecompression = 0.0f; + mTotalTimeCompression = 0.0f; + mRunTimeDecompression = 0.0f; +} + +LLImageCompressionTester::~LLImageCompressionTester() +{ + outputTestResults(); + LLImageJ2C::sTesterp = NULL; +} + +//virtual +void LLImageCompressionTester::outputTestRecord(LLSD *sd) +{ + std::string currentLabel = getCurrentLabelName(); + + F32 decompressionPerf = 0.0f; + F32 compressionPerf = 0.0f; + F32 decompressionRate = 0.0f; + F32 compressionRate = 0.0f; + + F32 totalkBInDecompression = (F32)(mTotalBytesInDecompression) / 1000.f; + F32 totalkBOutDecompression = (F32)(mTotalBytesOutDecompression) / 1000.f; + F32 totalkBInCompression = (F32)(mTotalBytesInCompression) / 1000.f; + F32 totalkBOutCompression = (F32)(mTotalBytesOutCompression) / 1000.f; + + if (!is_approx_zero(mTotalTimeDecompression)) + { + decompressionPerf = totalkBInDecompression / mTotalTimeDecompression; + } + if (!is_approx_zero(totalkBInDecompression)) + { + decompressionRate = totalkBOutDecompression / totalkBInDecompression; + } + if (!is_approx_zero(mTotalTimeCompression)) + { + compressionPerf = totalkBInCompression / mTotalTimeCompression; + } + if (!is_approx_zero(totalkBOutCompression)) + { + compressionRate = totalkBInCompression / totalkBOutCompression; + } + + (*sd)[currentLabel]["Time Decompression (s)"] = (LLSD::Real)mTotalTimeDecompression; + (*sd)[currentLabel]["Volume In Decompression (kB)"] = (LLSD::Real)totalkBInDecompression; + (*sd)[currentLabel]["Volume Out Decompression (kB)"]= (LLSD::Real)totalkBOutDecompression; + (*sd)[currentLabel]["Decompression Ratio (x:1)"] = (LLSD::Real)decompressionRate; + (*sd)[currentLabel]["Perf Decompression (kB/s)"] = (LLSD::Real)decompressionPerf; + + (*sd)[currentLabel]["Time Compression (s)"] = (LLSD::Real)mTotalTimeCompression; + (*sd)[currentLabel]["Volume In Compression (kB)"] = (LLSD::Real)totalkBInCompression; + (*sd)[currentLabel]["Volume Out Compression (kB)"] = (LLSD::Real)totalkBOutCompression; + (*sd)[currentLabel]["Compression Ratio (x:1)"] = (LLSD::Real)compressionRate; + (*sd)[currentLabel]["Perf Compression (kB/s)"] = (LLSD::Real)compressionPerf; +} + +void LLImageCompressionTester::updateCompressionStats(const F32 deltaTime) +{ + mTotalTimeCompression += deltaTime; +} + +void LLImageCompressionTester::updateCompressionStats(const S32 bytesCompress, const S32 bytesRaw) +{ + mTotalBytesInCompression += bytesRaw; + mRunBytesInCompression += bytesRaw; + mTotalBytesOutCompression += bytesCompress; + if (mRunBytesInCompression > (1000000)) + { + // Output everything + outputTestResults(); + // Reset the compression data of the run + mRunBytesInCompression = 0; + } +} + +void LLImageCompressionTester::updateDecompressionStats(const F32 deltaTime) +{ + mTotalTimeDecompression += deltaTime; +} + +void LLImageCompressionTester::updateDecompressionStats(const S32 bytesIn, const S32 bytesOut) +{ + mTotalBytesInDecompression += bytesIn; + mRunBytesInDecompression += bytesIn; + mTotalBytesOutDecompression += bytesOut; + mRunBytesOutDecompression += bytesOut; + //if (mRunBytesInDecompression > (1000000)) + if (mRunBytesOutDecompression > (10000000)) + //if ((mTotalTimeDecompression - mRunTimeDecompression) >= (5.0f)) + { + // Output everything + outputTestResults(); + // Reset the decompression data of the run + mRunBytesInDecompression = 0; + mRunBytesOutDecompression = 0; + mRunTimeDecompression = mTotalTimeDecompression; + } +} + +//---------------------------------------------------------------------------------------------- +// End of LLTexturePipelineTester +//---------------------------------------------------------------------------------------------- + -- cgit v1.2.3 From 2f83b0aed2ad123b86faad5b4cb1b55abc0a3a85 Mon Sep 17 00:00:00 2001 From: TommyTheTerrible <81168766+TommyTheTerrible@users.noreply.github.com> Date: Tue, 16 Jul 2024 21:02:57 -0400 Subject: Fix: Update calcDataSizeJ2C to pyramid-base file size estimation (#2032) * Fix: Update calcDataSizeJ2C to pyramid-base file size estimation Used the loop from the previous LayerFactored method to create a more accurate file size estimation by walking up the pyramid tiles. Sizes are much larger in many cases and eliminate partial decoder issues with OpenJPEG. KDU not tested but expected to produce better files as well. Should also stop decode failures on tiny or very rectangular dimensions. --------- Co-authored-by: Andrey Lihatskiy --- indra/llimage/llimagej2c.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'indra/llimage/llimagej2c.cpp') diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp index 0058b91b0f..5dfd8cd947 100644 --- a/indra/llimage/llimagej2c.cpp +++ b/indra/llimage/llimagej2c.cpp @@ -278,13 +278,20 @@ S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 r S32 nb_layers = 1; S32 surface = w*h; S32 s = 64*64; + S32 precision = 8; // assumed bitrate per component channel, might change in future for HDR support + S32 totalbytes = (S32)(s * comp * precision * rate); // first level computed before loop while (surface > s) { + if (nb_layers <= (5 - discard_level)) + totalbytes += (S32)(s * comp * precision * rate); nb_layers++; s *= 4; } F32 layer_factor = 3.0f * (7 - llclamp(nb_layers,1,6)); + totalbytes /= 8; // to bytes + totalbytes += calcHeaderSizeJ2C(); // header + // Compute w/pow(2,discard_level) and h/pow(2,discard_level) w >>= discard_level; h >>= discard_level; @@ -297,7 +304,9 @@ S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 r S32 new_bytes = (S32) (sqrt((F32)(w*h))*(F32)(comp)*rate*1000.f/layer_factor); S32 old_bytes = (S32)((F32)(w*h*comp)*rate); bytes = (LLImage::useNewByteRange() && (new_bytes < old_bytes) ? new_bytes : old_bytes); - bytes = llmax(bytes, calcHeaderSizeJ2C()); + bytes = llmax(totalbytes, calcHeaderSizeJ2C()); + //LL_WARNS() << "calcDataSizeJ2C w-h-c-d-p " << w << "-" << h << "-" << comp << "-" << discard_level << "-" << precision + // << " Pyramid: " << (S32)totalbytes << " LayerFactored: " << new_bytes << " WJCR: " << old_bytes << LL_ENDL; return bytes; } -- cgit v1.2.3 From 6535ce51fd1e3f2b0efdc650310ec75a7638f6f9 Mon Sep 17 00:00:00 2001 From: Ansariel Hiller Date: Thu, 18 Jul 2024 09:48:24 +0200 Subject: Remove unnecessary code and (re-)add some more compile time constants (#2057) --- indra/llimage/llimagej2c.cpp | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) (limited to 'indra/llimage/llimagej2c.cpp') diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp index 5dfd8cd947..29449a5d2e 100644 --- a/indra/llimage/llimagej2c.cpp +++ b/indra/llimage/llimagej2c.cpp @@ -275,10 +275,10 @@ S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 r // For details about the equation used here, see https://wiki.lindenlab.com/wiki/THX1138_KDU_Improvements#Byte_Range_Study // Estimate the number of layers. This is consistent with what's done for j2c encoding in LLImageJ2CKDU::encodeImpl(). + constexpr S32 precision = 8; // assumed bitrate per component channel, might change in future for HDR support S32 nb_layers = 1; - S32 surface = w*h; + const S32 surface = w*h; S32 s = 64*64; - S32 precision = 8; // assumed bitrate per component channel, might change in future for HDR support S32 totalbytes = (S32)(s * comp * precision * rate); // first level computed before loop while (surface > s) { @@ -287,27 +287,11 @@ S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 r nb_layers++; s *= 4; } - F32 layer_factor = 3.0f * (7 - llclamp(nb_layers,1,6)); totalbytes /= 8; // to bytes totalbytes += calcHeaderSizeJ2C(); // header - // Compute w/pow(2,discard_level) and h/pow(2,discard_level) - w >>= discard_level; - h >>= discard_level; - w = llmax(w, 1); - h = llmax(h, 1); - - // Temporary: compute both new and old range and pick one according to the settings TextureNewByteRange - // *TODO: Take the old code out once we have enough tests done - S32 bytes; - S32 new_bytes = (S32) (sqrt((F32)(w*h))*(F32)(comp)*rate*1000.f/layer_factor); - S32 old_bytes = (S32)((F32)(w*h*comp)*rate); - bytes = (LLImage::useNewByteRange() && (new_bytes < old_bytes) ? new_bytes : old_bytes); - bytes = llmax(totalbytes, calcHeaderSizeJ2C()); - //LL_WARNS() << "calcDataSizeJ2C w-h-c-d-p " << w << "-" << h << "-" << comp << "-" << discard_level << "-" << precision - // << " Pyramid: " << (S32)totalbytes << " LayerFactored: " << new_bytes << " WJCR: " << old_bytes << LL_ENDL; - return bytes; + return totalbytes; } S32 LLImageJ2C::calcHeaderSize() -- cgit v1.2.3 From bffd4a12b8e6677d8cd8bec2e38909e5200b69dd Mon Sep 17 00:00:00 2001 From: TommyTheTerrible <81168766+TommyTheTerrible@users.noreply.github.com> Date: Sat, 20 Jul 2024 06:37:23 -0400 Subject: calcDataSizeJ2C adjusted to use maximum possible components (#2073) Previous pyramid walking calculation (#2032) assumed the incoming components variable can be accurate but unfortunately the needs_aux is only set to true if the face has an alpha mask setting. Without this information we must assume the J2C files have the maximum component size of four so that alpha channels are found when decoding both the color and aux textures. --- indra/llimage/llimagej2c.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'indra/llimage/llimagej2c.cpp') diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp index 29449a5d2e..42f3e92257 100644 --- a/indra/llimage/llimagej2c.cpp +++ b/indra/llimage/llimagej2c.cpp @@ -276,14 +276,15 @@ S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 r // Estimate the number of layers. This is consistent with what's done for j2c encoding in LLImageJ2CKDU::encodeImpl(). constexpr S32 precision = 8; // assumed bitrate per component channel, might change in future for HDR support + constexpr S32 max_components = 4; // assumed the file has four components; three color and alpha S32 nb_layers = 1; const S32 surface = w*h; S32 s = 64*64; - S32 totalbytes = (S32)(s * comp * precision * rate); // first level computed before loop + S32 totalbytes = (S32)(s * max_components * precision * rate); // first level computed before loop while (surface > s) { if (nb_layers <= (5 - discard_level)) - totalbytes += (S32)(s * comp * precision * rate); + totalbytes += (S32)(s * max_components * precision * rate); nb_layers++; s *= 4; } -- cgit v1.2.3