summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indra/llimage/llimagej2c.cpp54
-rw-r--r--indra/llimage/llimagej2c.h6
-rw-r--r--indra/llimagej2coj/llimagej2coj.cpp29
-rw-r--r--indra/llimagej2coj/llimagej2coj.h5
-rw-r--r--indra/newview/app_settings/settings.xml11
-rw-r--r--indra/newview/llviewertexture.cpp29
6 files changed, 102 insertions, 32 deletions
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<LLImageJ2CImpl> s_estimator(fallbackCreateLLImageJ2CImpl());
+ return s_estimator->estimateDataSize(w, h, comp, discard_level, rate);
+}
+
S32 LLImageJ2C::calcHeaderSize()
{
return calcHeaderSizeJ2C();
diff --git a/indra/llimage/llimagej2c.h b/indra/llimage/llimagej2c.h
index 19744a7f87..5173eed33b 100644
--- a/indra/llimage/llimagej2c.h
+++ b/indra/llimage/llimagej2c.h
@@ -106,6 +106,12 @@ class LLImageJ2CImpl
{
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;
protected:
// Find out the image size and number of channels.
// Return value:
diff --git a/indra/llimagej2coj/llimagej2coj.cpp b/indra/llimagej2coj/llimagej2coj.cpp
index 7cfadb889d..488c07e1c9 100644
--- a/indra/llimagej2coj/llimagej2coj.cpp
+++ b/indra/llimagej2coj/llimagej2coj.cpp
@@ -919,3 +919,32 @@ bool LLImageJ2COJ::getMetadata(LLImageJ2C &base)
base.setSize(width, height, components);
return true;
}
+
+
+// OpenJPEG-tuned byte estimator. Conservative pyramid walk that accounts for
+// OJ's whole-code-block decode behavior (even with strict mode off). Larger
+// images get a per-resolution multiplier so the byte range lands inside the
+// last needed code-block boundary.
+S32 LLImageJ2COJ::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;
+ S32 max_dimension = llmax(width, height);
+ S32 block_area = MAX_BLOCK_SIZE * MAX_BLOCK_SIZE;
+ S32 max_layers = (S32)llmax(llround(log2f((float)max_dimension) - log2f((float)MAX_BLOCK_SIZE)), 4);
+ block_area *= llmax(max_layers, 1);
+ S32 totalbytes = (S32)(MIN_LAYER_SIZE * max_components * precision);
+ S32 block_layers = 0;
+ while (block_layers <= max_layers)
+ {
+ if (block_layers <= (5 - discard_level))
+ totalbytes += (S32)(block_area * max_components * precision * rate);
+ block_layers++;
+ block_area *= 4;
+ }
+ totalbytes /= 8;
+ totalbytes += LLImageJ2C::calcHeaderSizeJ2C();
+ return totalbytes;
+}
diff --git a/indra/llimagej2coj/llimagej2coj.h b/indra/llimagej2coj/llimagej2coj.h
index da49597302..6c6b379e5f 100644
--- a/indra/llimagej2coj/llimagej2coj.h
+++ b/indra/llimagej2coj/llimagej2coj.h
@@ -44,6 +44,11 @@ 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:
+ // OpenJPEG decodes whole code-blocks even with strict mode off, so the
+ // lean packet-walk under-allocates and clips quality. Keep the older
+ // conservative pyramid-with-multiplier estimate here.
+ virtual S32 estimateDataSize(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) const override;
};
#endif
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index d33082b449..0b700c28ae 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -11871,6 +11871,17 @@
<key>Value</key>
<integer>0</integer>
</map>
+ <key>TextureMemoryHighWaterMark</key>
+ <map>
+ <key>Comment</key>
+ <string>Fraction of budget (0..1) at which the pressure controller bypasses smoothing and slams to cap. Last-ditch min-discard also creeps without waiting for mult_progress.</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.8</real>
+ </map>
<key>TextureMemoryPressureBackoffStart</key>
<map>
<key>Comment</key>
diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp
index e84481bade..82f4cc8341 100644
--- a/indra/newview/llviewertexture.cpp
+++ b/indra/newview/llviewertexture.cpp
@@ -602,10 +602,25 @@ void LLViewerTexture::updateClass()
- (F32)pending_bytes_decrease * BYTES_TO_USED_UNITS;
F32 predicted_over = predicted_used / llmax(backoff_target, 1.f);
+ // High water mark: when used crosses budget * high_water, skip the
+ // smoothed convergence and slam the controller into hard-cap state.
+ // Recovers the historical 90% behavior - immediate aggressive
+ // response instead of waiting for the lerp to chase the target.
+ static LLCachedControl<F32> high_water(gSavedSettings, "TextureMemoryHighWaterMark", 0.8f);
+ bool above_high_water = used >= budget * llclamp((F32)high_water, 0.5f, 1.f);
+
F32 target_mult = llclamp(powf(llmax(predicted_over, 1.f), llmax((F32)prediction_gain, 0.0001f)), 1.f, cap);
- // ~63% convergence in 1/smoothing_rate seconds (default 0.25s).
- F32 alpha = 1.f - expf(-llmax(dt, 0.f) * llmax((F32)smoothing_rate, 0.f));
- sMemoryPressureMultiplier += (target_mult - sMemoryPressureMultiplier) * alpha;
+ if (above_high_water)
+ {
+ target_mult = cap;
+ sMemoryPressureMultiplier = cap;
+ }
+ else
+ {
+ // ~63% convergence in 1/smoothing_rate seconds (default 0.25s).
+ F32 alpha = 1.f - expf(-llmax(dt, 0.f) * llmax((F32)smoothing_rate, 0.f));
+ sMemoryPressureMultiplier += (target_mult - sMemoryPressureMultiplier) * alpha;
+ }
sMemoryPressureMultiplier = llclamp(sMemoryPressureMultiplier, 1.f, cap);
F32 progress = getMemoryPressureProgress();
@@ -615,12 +630,14 @@ void LLViewerTexture::updateClass()
static LLCachedControl<F32> ld_ramp(gSavedSettings, "TextureLastDitchRampRate", 0.5f);
static LLCachedControl<F32> ld_decay(gSavedSettings, "TextureLastDitchDecayRate", 0.5f);
static LLCachedControl<F32> ld_max(gSavedSettings, "TextureLastDitchMinDiscardMax", 13.f);
- bool mult_saturated = progress >= llclampf((F32)ld_engage);
- if (mult_saturated && predicted_over > 1.f)
+ // Above the high water mark, last-ditch creeps regardless of
+ // mult_progress: by definition we are out of normal headroom.
+ bool engage = above_high_water || progress >= llclampf((F32)ld_engage);
+ if (engage && predicted_over > 1.f)
{
sLastDitchMinDiscard += llmax((F32)ld_ramp, 0.f) * dt;
}
- else if (predicted_over < 1.f)
+ else if (!above_high_water && predicted_over < 1.f)
{
sLastDitchMinDiscard -= llmax((F32)ld_decay, 0.f) * dt;
}