From 1ee317bcb7a5988658612a69a7bd52fb1a9e37c7 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Tue, 19 May 2026 23:11:16 -0400 Subject: Bring back the pre-aug 2024 byte estimation for j2c streams, and add a configurable high water mark for VRAM. --- indra/llimage/llimagej2c.cpp | 54 +++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 26 deletions(-) (limited to 'indra/llimage/llimagej2c.cpp') diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp index 5a941dc958..512b370bae 100644 --- a/indra/llimage/llimagej2c.cpp +++ b/indra/llimage/llimagej2c.cpp @@ -265,40 +265,42 @@ 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) +// Lean pyramid-walk byte estimator suited to packet-by-packet decoders (KDU). +// Starts at one max-block, walks resolutions by doubling area, sums each +// layer's compressed-bytes contribution. +// Reference: https://wiki.lindenlab.com/wiki/THX1138_KDU_Improvements#Byte_Range_Study +S32 LLImageJ2CImpl::estimateDataSize(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) const { - // 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(). - 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 - // Use MAX_IMAGE_SIZE_DEFAULT (currently 2048) if either dimension is unknown (zero) + constexpr S32 precision = 8; + constexpr S32 max_components = 4; S32 width = (w > 0) ? w : 2048; S32 height = (h > 0) ? h : 2048; - S32 max_dimension = llmax(width, height); // Find largest dimension - S32 block_area = MAX_BLOCK_SIZE * MAX_BLOCK_SIZE; // Calculated initial block area from established max block size (currently 64) - S32 max_layers = (S32)llmax(llround(log2f((float)max_dimension) - log2f((float)MAX_BLOCK_SIZE)), 4); // Find number of powers of two between extents and block size to a minimum of 4 - block_area *= llmax(max_layers, 1); // Adjust initial block area by max number of layers - S32 totalbytes = (S32) (MIN_LAYER_SIZE * max_components * precision); // Start estimation with a minimum reasonable size - S32 block_layers = 0; - while (block_layers <= max_layers) // Walk the layers + const S32 surface = width * height; + S32 nb_layers = 1; + S32 s = MAX_BLOCK_SIZE * MAX_BLOCK_SIZE; + S32 totalbytes = (S32)(s * max_components * precision * rate); + while (surface > s) { - if (block_layers <= (5 - discard_level)) // Walk backwards from discard 5 to required discard layer. - totalbytes += (S32) (block_area * max_components * precision * rate); // Add each block layer reduced by assumed compression rate - block_layers++; // Move to next layer - block_area *= 4; // Increase block area by power of four + if (nb_layers <= (5 - discard_level)) + totalbytes += (S32)(s * max_components * precision * rate); + nb_layers++; + s *= 4; } - - totalbytes /= 8; // to bytes - totalbytes += calcHeaderSizeJ2C(); // header - + totalbytes /= 8; + totalbytes += LLImageJ2C::calcHeaderSizeJ2C(); return totalbytes; } +//static +S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) +{ + // Dispatch to the linked impl so OpenJPEG (block-aligned, needs + // over-allocation) and KDU (packet-aligned, lean) each return what + // their decoder actually needs. + static std::unique_ptr s_estimator(fallbackCreateLLImageJ2CImpl()); + return s_estimator->estimateDataSize(w, h, comp, discard_level, rate); +} + S32 LLImageJ2C::calcHeaderSize() { return calcHeaderSizeJ2C(); -- cgit v1.3 From 57048769db20f5e84834c8c6dd15466083bd7585 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 20 May 2026 10:20:54 -0400 Subject: Move the KDU data size estimate into j2ckdu, make the base pure virtual. --- indra/llimage/llimagej2c.cpp | 26 -------------------------- indra/llimage/llimagej2c.h | 7 +++---- indra/llkdu/llimagej2ckdu.cpp | 30 ++++++++++++++++++++++++++++++ indra/llkdu/llimagej2ckdu.h | 2 ++ indra/llkdu/tests/llimagej2ckdu_test.cpp | 1 - 5 files changed, 35 insertions(+), 31 deletions(-) (limited to 'indra/llimage/llimagej2c.cpp') diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp index 512b370bae..8099a18045 100644 --- a/indra/llimage/llimagej2c.cpp +++ b/indra/llimage/llimagej2c.cpp @@ -265,32 +265,6 @@ S32 LLImageJ2C::calcHeaderSizeJ2C() return FIRST_PACKET_SIZE; // Hack. just needs to be >= actual header size... } -// Lean pyramid-walk byte estimator suited to packet-by-packet decoders (KDU). -// Starts at one max-block, walks resolutions by doubling area, sums each -// layer's compressed-bytes contribution. -// Reference: https://wiki.lindenlab.com/wiki/THX1138_KDU_Improvements#Byte_Range_Study -S32 LLImageJ2CImpl::estimateDataSize(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) const -{ - constexpr S32 precision = 8; - constexpr S32 max_components = 4; - S32 width = (w > 0) ? w : 2048; - S32 height = (h > 0) ? h : 2048; - const S32 surface = width * height; - S32 nb_layers = 1; - S32 s = MAX_BLOCK_SIZE * MAX_BLOCK_SIZE; - S32 totalbytes = (S32)(s * max_components * precision * rate); - while (surface > s) - { - if (nb_layers <= (5 - discard_level)) - totalbytes += (S32)(s * max_components * precision * rate); - nb_layers++; - s *= 4; - } - totalbytes /= 8; - totalbytes += LLImageJ2C::calcHeaderSizeJ2C(); - return totalbytes; -} - //static S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) { diff --git a/indra/llimage/llimagej2c.h b/indra/llimage/llimagej2c.h index 5173eed33b..81b24cc0b3 100644 --- a/indra/llimage/llimagej2c.h +++ b/indra/llimage/llimagej2c.h @@ -108,10 +108,9 @@ public: virtual ~LLImageJ2CImpl(); // Estimate the byte size of a J2C codestream sufficient to decode the - // given discard level. KDU streams packet-by-packet and uses the lean - // pyramid-walk default. OpenJPEG needs over-allocation to land on - // code-block boundaries even with strict mode off, so it overrides. - virtual S32 estimateDataSize(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) const; + // given discard level. KDU uses a packet-by-packet impl; OpenJPEG + // overrides with a more conservative block-aligned estimate. + virtual S32 estimateDataSize(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) const = 0; protected: // Find out the image size and number of channels. // Return value: diff --git a/indra/llkdu/llimagej2ckdu.cpp b/indra/llkdu/llimagej2ckdu.cpp index e7ac6bdb31..4330b1e5b1 100644 --- a/indra/llkdu/llimagej2ckdu.cpp +++ b/indra/llkdu/llimagej2ckdu.cpp @@ -1513,3 +1513,33 @@ void kdc_flow_control::process_components() } } } + +// Layer-factored byte estimator. Walks the resolution pyramid to count +// layers, weights by layer_factor, then picks between a sqrt-based "new" +// estimate and a raw-dimensions "old" estimate per TextureNewByteRange. +// Reference: https://wiki.lindenlab.com/wiki/THX1138_KDU_Improvements#Byte_Range_Study +S32 LLImageJ2CKDU::estimateDataSize(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) const +{ + S32 width = (w > 0) ? w : 2048; + S32 height = (h > 0) ? h : 2048; + S32 nb_layers = 1; + S32 surface = width * height; + S32 s = MAX_BLOCK_SIZE * MAX_BLOCK_SIZE; + while (surface > s) + { + nb_layers++; + s *= 4; + } + F32 layer_factor = 3.0f * (7 - llclamp(nb_layers, 1, 6)); + + width >>= discard_level; + height >>= discard_level; + width = llmax(width, 1); + height = llmax(height, 1); + + S32 new_bytes = (S32)(sqrtf((F32)(width * height)) * (F32)comp * rate * 1000.f / layer_factor); + S32 old_bytes = (S32)((F32)(width * height * comp) * rate); + S32 bytes = (LLImage::useNewByteRange() && (new_bytes < old_bytes)) ? new_bytes : old_bytes; + bytes = llmax(bytes, LLImageJ2C::calcHeaderSizeJ2C()); + return bytes; +} diff --git a/indra/llkdu/llimagej2ckdu.h b/indra/llkdu/llimagej2ckdu.h index c9aa0c5250..6079585948 100644 --- a/indra/llkdu/llimagej2ckdu.h +++ b/indra/llkdu/llimagej2ckdu.h @@ -67,6 +67,8 @@ protected: virtual bool initDecode(LLImageJ2C &base, LLImageRaw &raw_image, int discard_level = -1, int* region = NULL); virtual bool initEncode(LLImageJ2C &base, LLImageRaw &raw_image, int blocks_size = -1, int precincts_size = -1, int levels = 0); virtual std::string getEngineInfo() const; +public: + virtual S32 estimateDataSize(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) const; private: bool initDecode(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, ECodeStreamMode mode, S32 first_channel, S32 max_channel_count, int discard_level = -1, int* region = NULL); diff --git a/indra/llkdu/tests/llimagej2ckdu_test.cpp b/indra/llkdu/tests/llimagej2ckdu_test.cpp index a016865fcd..bc52a15c4a 100644 --- a/indra/llkdu/tests/llimagej2ckdu_test.cpp +++ b/indra/llkdu/tests/llimagej2ckdu_test.cpp @@ -83,7 +83,6 @@ void LLImageBase::setSize(S32 , S32 , S32 ) { } bool LLImageBase::isBufferInvalid() const { return false; } LLImageJ2CImpl::~LLImageJ2CImpl() { } -S32 LLImageJ2CImpl::estimateDataSize(S32, S32, S32, S32, F32) const { return 0; } LLImageFormatted::LLImageFormatted(S8 ) { } LLImageFormatted::~LLImageFormatted() { } -- cgit v1.3