summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErik Kundiman <erik@megapahit.org>2025-08-16 06:39:13 +0800
committerErik Kundiman <erik@megapahit.org>2025-08-16 06:39:13 +0800
commit2d8948ef173c5bfc37834301f4aa4fb72e78f03c (patch)
tree68b8ce2297ec590ac3f5af2e4f34736f15d7e193
parent977b636f37cd4a53caa1db084c07294f67212c0e (diff)
parent37a04baf104aa394615d8e8286522988ba56c09d (diff)
Merge tag 'Second_Life_Release#37a04baf-2025.06' into 2025.06
-rw-r--r--indra/llimage/llimagebmp.cpp6
-rw-r--r--indra/llimage/llimagedxt.cpp6
-rw-r--r--indra/llimage/llimagejpeg.cpp6
-rw-r--r--indra/llimagej2coj/llimagej2coj.cpp6
-rw-r--r--indra/llkdu/llimagej2ckdu.cpp6
-rw-r--r--indra/newview/llfloaterimagepreview.cpp16
-rw-r--r--indra/newview/llfloatermodelpreview.cpp86
-rw-r--r--indra/newview/llfloatermodelpreview.h3
-rw-r--r--indra/newview/llmeshrepository.cpp15
-rw-r--r--indra/newview/llmeshrepository.h8
-rw-r--r--indra/newview/llviewerstats.cpp30
-rw-r--r--indra/newview/llviewerstats.h9
-rw-r--r--indra/newview/skins/default/xui/en/floater_stats.xml8
-rw-r--r--indra/newview/skins/default/xui/en/notifications.xml8
-rw-r--r--indra/newview/skins/default/xui/en/strings.xml1
15 files changed, 206 insertions, 8 deletions
diff --git a/indra/llimage/llimagebmp.cpp b/indra/llimage/llimagebmp.cpp
index 2a328675c2..c8f99380ea 100644
--- a/indra/llimage/llimagebmp.cpp
+++ b/indra/llimage/llimagebmp.cpp
@@ -558,6 +558,12 @@ bool LLImageBMP::encode(const LLImageRaw* raw_image, F32 encode_time)
LL_INFOS() << "Dropping alpha information during BMP encoding" << LL_ENDL;
}
+ if (raw_image->isBufferInvalid())
+ {
+ setLastError("Invalid input, no buffer");
+ return false;
+ }
+
setSize(raw_image->getWidth(), raw_image->getHeight(), dst_components);
U8 magic[14];
diff --git a/indra/llimage/llimagedxt.cpp b/indra/llimage/llimagedxt.cpp
index 6b960f9077..c3fd0c5aa8 100644
--- a/indra/llimage/llimagedxt.cpp
+++ b/indra/llimage/llimagedxt.cpp
@@ -329,6 +329,12 @@ bool LLImageDXT::encodeDXT(const LLImageRaw* raw_image, F32 time, bool explicit_
{
llassert_always(raw_image);
+ if (raw_image->isBufferInvalid())
+ {
+ setLastError("Invalid input, no buffer");
+ return false;
+ }
+
S32 ncomponents = raw_image->getComponents();
EFileFormat format;
switch (ncomponents)
diff --git a/indra/llimage/llimagejpeg.cpp b/indra/llimage/llimagejpeg.cpp
index dd3efb3459..71b139bc82 100644
--- a/indra/llimage/llimagejpeg.cpp
+++ b/indra/llimage/llimagejpeg.cpp
@@ -497,6 +497,12 @@ bool LLImageJPEG::encode( const LLImageRaw* raw_image, F32 encode_time )
resetLastError();
+ if (raw_image->isBufferInvalid())
+ {
+ setLastError("Invalid input, no buffer");
+ return false;
+ }
+
LLImageDataSharedLock lockIn(raw_image);
LLImageDataLock lockOut(this);
diff --git a/indra/llimagej2coj/llimagej2coj.cpp b/indra/llimagej2coj/llimagej2coj.cpp
index 717b0a6e86..6ebbe359b9 100644
--- a/indra/llimagej2coj/llimagej2coj.cpp
+++ b/indra/llimagej2coj/llimagej2coj.cpp
@@ -897,6 +897,12 @@ bool LLImageJ2COJ::decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decod
bool LLImageJ2COJ::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text, F32 encode_time, bool reversible)
{
+ if (raw_image.isBufferInvalid())
+ {
+ base.setLastError("Invalid input, no buffer");
+ return false;
+ }
+
JPEG2KEncode encode(comment_text, reversible);
bool encoded = encode.encode(raw_image, base);
if (!encoded)
diff --git a/indra/llkdu/llimagej2ckdu.cpp b/indra/llkdu/llimagej2ckdu.cpp
index 0d1f2b3006..b824fd8385 100644
--- a/indra/llkdu/llimagej2ckdu.cpp
+++ b/indra/llkdu/llimagej2ckdu.cpp
@@ -662,6 +662,12 @@ bool LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, co
bool vflip = true;
bool hflip = false;
+ if (raw_image.isBufferInvalid())
+ {
+ base.setLastError("Invalid input, no buffer");
+ return false;
+ }
+
try
{
// Set up input image files
diff --git a/indra/newview/llfloaterimagepreview.cpp b/indra/newview/llfloaterimagepreview.cpp
index 42a5df5d17..550c3adc27 100644
--- a/indra/newview/llfloaterimagepreview.cpp
+++ b/indra/newview/llfloaterimagepreview.cpp
@@ -288,7 +288,9 @@ void LLFloaterImagePreview::onBtnOK()
}
else
{
- LLNotificationsUtil::add("ErrorEncodingImage");
+ LLSD args;
+ args["REASON"] = LLImage::getLastThreadError();
+ LLNotificationsUtil::add("ErrorEncodingImage", args);
LL_WARNS() << "Error encoding image" << LL_ENDL;
}
}
@@ -423,6 +425,18 @@ bool LLFloaterImagePreview::loadImage(const std::string& src_filename)
return false;
}
+ // raw image is limited to 256MB so need at least some upper limit that fits into that
+ constexpr S32 MAX_IMAGE_AREA = 8096 * 8096;
+
+ if (image_info.getWidth() * image_info.getHeight() > MAX_IMAGE_AREA)
+ {
+ LLStringUtil::format_map_t args;
+ args["PIXELS"] = llformat("%dM", (S32)(MAX_IMAGE_AREA / 1000000));
+
+ mImageLoadError = LLTrans::getString("texture_load_area_error", args);
+ return false;
+ }
+
// Load the image
LLPointer<LLImageFormatted> image = LLImageFormatted::createFromType(codec);
if (image.isNull())
diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp
index 6a6766fb3f..08d3488ef2 100644
--- a/indra/newview/llfloatermodelpreview.cpp
+++ b/indra/newview/llfloatermodelpreview.cpp
@@ -503,7 +503,10 @@ void LLFloaterModelPreview::onClickCalculateBtn()
mUploadModelUrl.clear();
mModelPhysicsFee.clear();
- gMeshRepo.uploadModel(mModelPreview->mUploadData, mModelPreview->mPreviewScale,
+ lod_sources_map_t lod_sources;
+ fillLODSourceStatistics(lod_sources);
+
+ gMeshRepo.uploadModel(mModelPreview->mUploadData, lod_sources, mModelPreview->mPreviewScale,
childGetValue("upload_textures").asBoolean(),
upload_skinweights, upload_joint_positions, lock_scale_if_joint_position,
mUploadModelUrl, mDestinationFolderId, false,
@@ -1317,8 +1320,84 @@ void LLFloaterModelPreview::createSmoothComboBox(LLComboBox* combo_box, float mi
std::string label = (++ilabel == SMOOTH_VALUES_NUMBER) ? "10 (max)" : llformat("%.1d", ilabel);
combo_box->add(label, value, ADD_BOTTOM, true);
}
+}
+
+std::string get_source_file_extr(const std::string& filename)
+{
+ if (std::string::npos != filename.rfind(".gltf")
+ || std::string::npos != filename.rfind(".glb"))
+ {
+ return "gltf";
+ }
+ else if (std::string::npos != filename.rfind(".dae"))
+ {
+ return "dae";
+ }
+ else if (std::string::npos != filename.rfind(".slm"))
+ {
+ return "slm";
+ }
+ else
+ {
+ return "unknown file";
+ }
+}
+void LLFloaterModelPreview::fillLODSourceStatistics(LLFloaterModelPreview::lod_sources_map_t& lod_sources) const
+{
+ lod_sources.clear();
+ // This doesn't nessesarily reflect the actual source of meshes, just user choices,
+ // some meshes could have been matched from different lods, but should be good
+ // enough for statistics.
+ for (S32 lod = 0; lod <= LLModel::LOD_HIGH; ++lod)
+ {
+ const std::string &lod_string = lod_name[lod];
+ if (mLODMode[lod] == LLModelPreview::USE_LOD_ABOVE)
+ {
+ lod_sources[lod_string] = "lod above";
+ }
+ else if (mLODMode[lod] == LLModelPreview::MESH_OPTIMIZER_AUTO
+ || mLODMode[lod] == LLModelPreview::MESH_OPTIMIZER_PRECISE
+ || mLODMode[lod] == LLModelPreview::MESH_OPTIMIZER_SLOPPY)
+ {
+ lod_sources[lod_string] = "generated";
+ }
+ else if (mLODMode[lod] == LLModelPreview::LOD_FROM_FILE)
+ {
+ const std::string& file = mModelPreview->mLODFile[lod];
+ lod_sources[lod_string] = get_source_file_extr(file);
+ }
+ else
+ {
+ lod_sources[lod_string] = "unknown source";
+ }
+ }
+ if (mModelPreview->mLODFile[LLModel::LOD_PHYSICS].empty())
+ {
+ if (mModelPreview->mPhysicsSearchLOD >= 0 && mModelPreview->mPhysicsSearchLOD <= 3)
+ {
+ lod_sources["physics"] = lod_name[mModelPreview->mPhysicsSearchLOD];
+ }
+ else
+ {
+ lod_sources["physics"] = "none";
+ }
+ }
+ else
+ {
+ const std::string& file = mModelPreview->mLODFile[LLModel::LOD_PHYSICS];
+ if (std::string::npos == file.rfind("cube.dae"))
+ {
+ // There is a chance it will misfire if someone tries to upload a cube.dae mesh,
+ // but should be negligible enough.
+ lod_sources["physics"] = get_source_file_extr(file);
+ }
+ else
+ {
+ lod_sources["physics"] = "bounding box";
+ }
+ }
}
//-----------------------------------------------------------------------------
@@ -1656,7 +1735,10 @@ void LLFloaterModelPreview::onUpload(void* user_data)
mp->mModelPreview->saveUploadData(upload_skinweights, upload_joint_positions, lock_scale_if_joint_position);
}
- gMeshRepo.uploadModel(mp->mModelPreview->mUploadData, mp->mModelPreview->mPreviewScale,
+ lod_sources_map_t lod_sources;
+ mp->fillLODSourceStatistics(lod_sources);
+
+ gMeshRepo.uploadModel(mp->mModelPreview->mUploadData, lod_sources, mp->mModelPreview->mPreviewScale,
mp->childGetValue("upload_textures").asBoolean(),
upload_skinweights, upload_joint_positions, lock_scale_if_joint_position,
mp->mUploadModelUrl, mp->mDestinationFolderId,
diff --git a/indra/newview/llfloatermodelpreview.h b/indra/newview/llfloatermodelpreview.h
index 7b652a3613..982f36c46e 100644
--- a/indra/newview/llfloatermodelpreview.h
+++ b/indra/newview/llfloatermodelpreview.h
@@ -223,6 +223,9 @@ private:
void createSmoothComboBox(LLComboBox* combo_box, float min, float max);
+ typedef std::map<std::string, std::string> lod_sources_map_t;
+ void fillLODSourceStatistics(lod_sources_map_t& lod_sources) const;
+
LLUUID mDestinationFolderId;
LLButton* mUploadBtn;
LLButton* mCalculateBtn;
diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp
index 07d68fc3ec..fd3360b234 100644
--- a/indra/newview/llmeshrepository.cpp
+++ b/indra/newview/llmeshrepository.cpp
@@ -2569,7 +2569,8 @@ EMeshProcessingResult LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_
return MESH_OK;
}
-LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, LLVector3& scale, bool upload_textures,
+LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, const LLMeshUploadThread::lod_sources_map_t& sources_list,
+ LLVector3& scale, bool upload_textures,
bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position,
const std::string & upload_url, LLUUID destination_folder_id, bool do_upload,
LLHandle<LLWholeModelFeeObserver> fee_observer,
@@ -2584,6 +2585,7 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data,
mUploadObserverHandle(upload_observer)
{
mInstanceList = data;
+ mLodSources = sources_list;
mUploadTextures = upload_textures;
mUploadSkin = upload_skin;
mUploadJoints = upload_joints;
@@ -2721,6 +2723,12 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, std::vector<std::string>&
res["mesh_list"] = LLSD::emptyArray();
res["texture_list"] = LLSD::emptyArray();
res["instance_list"] = LLSD::emptyArray();
+ LLSD& lod_sources = res["source_format"];
+ lod_sources["high"] = 0;
+ for (auto &source : mLodSources)
+ {
+ lod_sources[source.first] = source.second;
+ }
S32 mesh_num = 0;
S32 texture_num = 0;
@@ -5026,12 +5034,13 @@ bool LLMeshRepoThread::hasHeader(const LLUUID& mesh_id) const
return iter != mMeshHeader.end();
}
-void LLMeshRepository::uploadModel(std::vector<LLModelInstance>& data, LLVector3& scale, bool upload_textures,
+void LLMeshRepository::uploadModel(std::vector<LLModelInstance>& data, const std::map<std::string, std::string> &lod_sources,
+ LLVector3& scale, bool upload_textures,
bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position,
std::string upload_url, const LLUUID& destination_folder_id, bool do_upload,
LLHandle<LLWholeModelFeeObserver> fee_observer, LLHandle<LLWholeModelUploadObserver> upload_observer)
{
- LLMeshUploadThread* thread = new LLMeshUploadThread(data, scale, upload_textures,
+ LLMeshUploadThread* thread = new LLMeshUploadThread(data, lod_sources, scale, upload_textures,
upload_skin, upload_joints, lock_scale_if_joint_position,
upload_url, destination_folder_id, do_upload, fee_observer, upload_observer);
mUploadWaitList.push_back(thread);
diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h
index 2b772f7803..ab17b921d6 100644
--- a/indra/newview/llmeshrepository.h
+++ b/indra/newview/llmeshrepository.h
@@ -691,6 +691,8 @@ public:
};
typedef std::map<LLPointer<LLModel>, instance_list, LLUploadModelInstanceLess> instance_map;
instance_map mInstance;
+ typedef std::map<std::string, std::string> lod_sources_map_t;
+ lod_sources_map_t mLodSources;
LLMutex* mMutex;
S32 mPendingUploads;
@@ -707,7 +709,8 @@ public:
std::string mWholeModelUploadURL;
LLUUID mDestinationFolderId;
- LLMeshUploadThread(instance_list& data, LLVector3& scale, bool upload_textures,
+ LLMeshUploadThread(instance_list& data, const lod_sources_map_t& sources_list,
+ LLVector3& scale, bool upload_textures,
bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position,
const std::string & upload_url,
const LLUUID destination_folder_id = LLUUID::null,
@@ -869,7 +872,8 @@ public:
bool meshUploadEnabled();
bool meshRezEnabled();
- void uploadModel(std::vector<LLModelInstance>& data, LLVector3& scale, bool upload_textures,
+ void uploadModel(std::vector<LLModelInstance>& data, const std::map<std::string, std::string> &lod_sources,
+ LLVector3& scale, bool upload_textures,
bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position,
std::string upload_url,
const LLUUID& destination_folder_id = LLUUID::null,
diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp
index cf0cf2a6a9..59bb78f8f4 100644
--- a/indra/newview/llviewerstats.cpp
+++ b/indra/newview/llviewerstats.cpp
@@ -234,6 +234,8 @@ LLTrace::SampleStatHandle<U32> FRAMETIME_JITTER_EVENTS("frametimeevents", "Numbe
FRAMETIME_JITTER_EVENTS_LAST_MINUTE("frametimeeventslastmin", "Number of frametime events in the last minute.");
LLTrace::SampleStatHandle<F64> NOTRMALIZED_FRAMETIME_JITTER_SESSION("normalizedframetimejitter", "Normalized frametime jitter over the session.");
+LLTrace::SampleStatHandle<F64> NFTV("nftv", "Normalized frametime variation.");
+LLTrace::SampleStatHandle<F64> NORMALIZED_FRAMTIME_JITTER_PERIOD("normalizedframetimejitterperiod", "Normalized frametime jitter over the last 5 seconds.");
LLTrace::EventStatHandle<LLUnit<F64, LLUnits::Meters> > AGENT_POSITION_SNAP("agentpositionsnap", "agent position corrections");
@@ -322,6 +324,8 @@ void LLViewerStats::updateFrameStats(const F64Seconds time_diff)
sample(LLStatViewer::FRAMETIME_JITTER_CUMULATIVE, mTotalFrametimeJitter);
sample(LLStatViewer::NOTRMALIZED_FRAMETIME_JITTER_SESSION, mTotalFrametimeJitter / mTotalTime);
+ mLastNoramlizedSessionJitter = mTotalFrametimeJitter / mTotalTime;
+
static LLCachedControl<F32> frameTimeEventThreshold(gSavedSettings, "StatsFrametimeEventThreshold", 0.1f);
if (time_diff - mLastTimeDiff > mLastTimeDiff * frameTimeEventThreshold())
@@ -360,6 +364,27 @@ void LLViewerStats::updateFrameStats(const F64Seconds time_diff)
sample(LLStatViewer::FRAMETIME_JITTER_99TH, ninety_ninth_percentile);
sample(LLStatViewer::FRAMETIME_JITTER_95TH, ninety_fifth_percentile);
+ F64 averageFrameTime = 0;
+ for (const auto& frame_time : mFrameTimes)
+ {
+ averageFrameTime += frame_time.value();
+ }
+ averageFrameTime /= mFrameTimes.size();
+
+ sample(LLStatViewer::NFTV, frame_time_stddev / averageFrameTime);
+ mLastNormalizedFrametimeVariance = frame_time_stddev / averageFrameTime;
+
+ // Add up all of the jitter values.
+ F64 totalJitter = 0;
+ for (const auto& frame_jitter : mFrameTimesJitter)
+ {
+ totalJitter += frame_jitter.value();
+ }
+
+ mLastNormalizedPeriodJitter = totalJitter / mLastFrameTimeSample;
+
+ sample(LLStatViewer::NORMALIZED_FRAMTIME_JITTER_PERIOD, mLastNormalizedPeriodJitter);
+
mFrameTimes.clear();
mFrameTimesJitter.clear();
mLastFrameTimeSample = F64Seconds(0);
@@ -648,6 +673,11 @@ void send_viewer_stats(bool include_preferences)
// send fps only for time app spends in foreground
agent["fps"] = (F32)gForegroundFrameCount / gForegroundTime.getElapsedTimeF32();
+
+ agent["normalized_session_jitter"] = LLViewerStats::instance().getLastNormalizedSessionJitter();
+ agent["normalized_frametime_variance"] = LLViewerStats::instance().getLastNormalizedFrametimeVariance();
+ agent["normalized_period_jitter"] = LLViewerStats::instance().getLastNormalizedPeriodJitter();
+
agent["version"] = LLVersionInfo::instance().getChannelAndVersion();
std::string language = LLUI::getLanguage();
agent["language"] = language;
diff --git a/indra/newview/llviewerstats.h b/indra/newview/llviewerstats.h
index 63fb7d4a17..011269d7ee 100644
--- a/indra/newview/llviewerstats.h
+++ b/indra/newview/llviewerstats.h
@@ -271,6 +271,10 @@ public:
LLTrace::Recording& getRecording() { return mRecording; }
const LLTrace::Recording& getRecording() const { return mRecording; }
+ F64 getLastNormalizedSessionJitter() const { return mLastNoramlizedSessionJitter; }
+ F64 getLastNormalizedFrametimeVariance() const { return mLastNormalizedFrametimeVariance; }
+ F64 getLastNormalizedPeriodJitter() const { return mLastNormalizedPeriodJitter; }
+
private:
LLTrace::Recording mRecording;
@@ -286,6 +290,11 @@ private:
F64Seconds mTimeSinceLastEventSample;
std::vector<F64Seconds> mFrameTimes; // used for frame time stats
std::vector<F64Seconds> mFrameTimesJitter; // used for frame time jitter stats
+
+ F64 mLastNoramlizedSessionJitter; // used for frame time jitter stats
+ F64 mLastNormalizedFrametimeVariance; // Used when submitting jitter stats
+ F64 mLastNormalizedPeriodJitter;
+
};
static const F32 SEND_STATS_PERIOD = 300.0f;
diff --git a/indra/newview/skins/default/xui/en/floater_stats.xml b/indra/newview/skins/default/xui/en/floater_stats.xml
index f2309eb817..1600c422c3 100644
--- a/indra/newview/skins/default/xui/en/floater_stats.xml
+++ b/indra/newview/skins/default/xui/en/floater_stats.xml
@@ -58,6 +58,14 @@
label="normalized sess. jitter"
decimal_digits="4"
stat="normalizedframetimejitter"/>
+ <stat_bar name="normalized_period_jitter"
+ label="normalized period jitter"
+ decimal_digits="4"
+ stat="normalizedframetimejitterperiod"/>
+ <stat_bar name="normalized_frametime_variation"
+ label="normalized frametime variation"
+ decimal_digits="4"
+ stat="nftv"/>
<stat_bar name="frame_events_per_minute"
label="frame events/minute"
decimal_digits="2"
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index 10c9efffd1..9963ccd84b 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -1322,6 +1322,14 @@ Error encoding snapshot.
<notification
icon="alertmodal.tga"
+ name="ErrorEncodingImage"
+ type="alertmodal">
+ Failed to encode image, reason: [REASON]
+ <tag>fail</tag>
+ </notification>
+
+ <notification
+ icon="alertmodal.tga"
name="ErrorTextureCannotAfford"
type="alertmodal">
You need L$[COST] to save a texture to your inventory. You may either buy L$ or save the photo to your computer instead.
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index ccae7d3df1..25827fa236 100644
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -3971,6 +3971,7 @@ Abuse Report</string>
<string name="AvatarBirthDateFormatShort">[mthnum,datetime,slt]/[day,datetime,slt]</string>
<string name="DefaultMimeType">none/none</string>
+ <string name="texture_load_area_error">Can't load images larger than [PIXELS] pixels</string>
<string name="texture_load_dimensions_error">Can't load images larger than [WIDTH]*[HEIGHT]</string>
<string name="texture_load_format_error">Incorrect image format.</string>
<string name="texture_load_empty_file">File is empty.</string>