From 41d9d0dfe8b7a341b18951d562643aba5f73323a Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 15 Sep 2026 19:07:17 +0300 Subject: #3988 Add a toggle for sculpty area validation for QA purposes --- indra/llmath/llvolume.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indra/llmath/llvolume.cpp') diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp index ece52db1b1..28ce4ef4b0 100644 --- a/indra/llmath/llvolume.cpp +++ b/indra/llmath/llvolume.cpp @@ -3289,7 +3289,7 @@ S32 sculpt_sides(F32 detail) } } -static bool validate_sculpt_geometry(const LLVolume* volume) +static bool validate_sculpt_geometry(const LLVolume* volume, bool validate_area) { if (!volume || volume->getNumVolumeFaces() == 0) { @@ -3365,7 +3365,7 @@ static bool validate_sculpt_geometry(const LLVolume* volume) F32 area_ratio = total_triangle_area / bounding_surface; constexpr F32 MAX_AREA_TO_AREA_RATIO = 16.0f; - if (area_ratio > MAX_AREA_TO_AREA_RATIO) + if (validate_area && area_ratio > MAX_AREA_TO_AREA_RATIO) { LL_DEBUGS("LLVOLUME") << "Sculpt rejected: excessive triangle area." << " Face index: " << face_idx @@ -3417,7 +3417,7 @@ static void sculpt_calc_mesh_resolution(U16 width, U16 height, U8 type, F32 deta } // sculpt replaces generate() for sculpted surfaces -void LLVolume::sculpt(U16 sculpt_width, U16 sculpt_height, S8 sculpt_components, const U8* sculpt_data, S32 sculpt_level, bool visible_placeholder) +void LLVolume::sculpt(U16 sculpt_width, U16 sculpt_height, S8 sculpt_components, const U8* sculpt_data, S32 sculpt_level, bool visible_placeholder, bool validate_area) { U8 sculpt_type = mParams.getSculptType(); @@ -3528,7 +3528,7 @@ void LLVolume::sculpt(U16 sculpt_width, U16 sculpt_height, S8 sculpt_components, // Todo: can this be backed into createVolumeFaces? // Or calculated without having to call createVolumeFaces first? // Todo 2: should the same be done for meshes? - valid_geometry = validate_sculpt_geometry(this); + valid_geometry = validate_sculpt_geometry(this, validate_area); mSculptValidationCache[sculpt_level] = valid_geometry ? LLSculptValidationState::Valid : LLSculptValidationState::Invalid; if (!valid_geometry) -- cgit v1.3 From 89973b5c52a967e63aecb756966d109eef209a85 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 18 Sep 2026 19:23:11 +0300 Subject: Revert "#3988 Don't render malformed sculpts" Reverted change affects existing content. That existing content has high impact on performance, but content is already inworld, need to find another solution. --- indra/llmath/llvolume.cpp | 161 +------------------------------- indra/llmath/llvolume.h | 11 +-- indra/newview/app_settings/settings.xml | 11 --- indra/newview/llfloaterimagepreview.cpp | 3 +- indra/newview/llvovolume.cpp | 3 +- 5 files changed, 9 insertions(+), 180 deletions(-) (limited to 'indra/llmath/llvolume.cpp') diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp index 28ce4ef4b0..d4f3d1ac25 100644 --- a/indra/llmath/llvolume.cpp +++ b/indra/llmath/llvolume.cpp @@ -1972,8 +1972,6 @@ LLVolume::LLVolume(const LLVolumeParams ¶ms, const F32 detail, const bool ge mNumHullIndices = 0; // set defaults - mSculptValidationCache.fill(LLSculptValidationState::Unvalidated); - if (mParams.getPathParams().getCurveType() == LL_PCODE_PATH_FLEXIBLE) { mPathp = new LLDynamicPath(); @@ -2001,13 +1999,6 @@ void LLVolume::resizePath(S32 length) setDirty(); } -void LLVolume::setDirty() -{ - mPathp->setDirty(); - mProfilep->setDirty(); - mSculptValidationCache.fill(LLSculptValidationState::Unvalidated); -} - void LLVolume::regen() { generate(); @@ -3289,100 +3280,10 @@ S32 sculpt_sides(F32 detail) } } -static bool validate_sculpt_geometry(const LLVolume* volume, bool validate_area) -{ - if (!volume || volume->getNumVolumeFaces() == 0) - { - return true; - } - - // Validate each face in the sculpt - for (S32 face_idx = 0; face_idx < volume->getNumVolumeFaces(); ++face_idx) - { - const LLVolumeFace& face = volume->getVolumeFace(face_idx); - - // Skip validation for degenerate faces - if (face.mNumVertices < 3 || face.mNumIndices < 3) - { - continue; - } - - // Calculate 'bounding' surface - LLVector4a extent_size; - extent_size.setSub(face.mExtents[1], face.mExtents[0]); - - F32 width = extent_size[0]; - F32 height = extent_size[1]; - F32 depth = extent_size[2]; - - F32 bounding_surface = width * height + width * depth + height * depth; - bounding_surface *= 2; - - if (!llfinite(bounding_surface) || bounding_surface <= FLT_EPSILON) - { - return false; - } - - // Calculate total surface area of all triangles - F32 total_triangle_area = 0.0f; - S32 num_triangles = face.mNumIndices / 3; - - for (S32 i = 0; i < num_triangles; ++i) - { - S32 idx_base = i * 3; - U16 idx0 = face.mIndices[idx_base]; - U16 idx1 = face.mIndices[idx_base + 1]; - U16 idx2 = face.mIndices[idx_base + 2]; - - // Validate indices - if (idx0 >= face.mNumVertices || idx1 >= face.mNumVertices || idx2 >= face.mNumVertices) - { - continue; - } - - const LLVector4a& v0 = face.mPositions[idx0]; - const LLVector4a& v1 = face.mPositions[idx1]; - const LLVector4a& v2 = face.mPositions[idx2]; - - // Calculate triangle area - LLVector4a edge1, edge2, cross; - edge1.setSub(v1, v0); - edge2.setSub(v2, v0); - cross.setCross3(edge1, edge2); - - F32 triangle_area = cross.getLength3().getF32() * 0.5f; - total_triangle_area += triangle_area; - } - - if (!llfinite(total_triangle_area)) - { - return false; - } - - // The idea here is that the total area of a sculpt is normally comparable - // to the area of a bounding box. But a random overlapping collection of - // triangles has a significantly larger area than a sculpt normally has. - F32 area_ratio = total_triangle_area / bounding_surface; - constexpr F32 MAX_AREA_TO_AREA_RATIO = 16.0f; - if (validate_area && area_ratio > MAX_AREA_TO_AREA_RATIO) - { - LL_DEBUGS("LLVOLUME") << "Sculpt rejected: excessive triangle area." - << " Face index: " << face_idx - << " Total triangle area: " << total_triangle_area << " sq m" - << " Bounding surface area: " << bounding_surface << " sq m" - << " Ratio: " << area_ratio - << " Triangle count: " << num_triangles - << " Bounds: [" << width << " x " << height << " x " << depth << "]" << LL_ENDL; - return false; - } - } - - return true; -} // determine the number of vertices in both s and t direction for this sculpt -static void sculpt_calc_mesh_resolution(U16 width, U16 height, U8 type, F32 detail, S32& s, S32& t) +void sculpt_calc_mesh_resolution(U16 width, U16 height, U8 type, F32 detail, S32& s, S32& t) { // this code has the following properties: // 1) the aspect ratio of the mesh is as close as possible to the ratio of the map @@ -3417,7 +3318,7 @@ static void sculpt_calc_mesh_resolution(U16 width, U16 height, U8 type, F32 deta } // sculpt replaces generate() for sculpted surfaces -void LLVolume::sculpt(U16 sculpt_width, U16 sculpt_height, S8 sculpt_components, const U8* sculpt_data, S32 sculpt_level, bool visible_placeholder, bool validate_area) +void LLVolume::sculpt(U16 sculpt_width, U16 sculpt_height, S8 sculpt_components, const U8* sculpt_data, S32 sculpt_level, bool visible_placeholder) { U8 sculpt_type = mParams.getSculptType(); @@ -3492,62 +3393,10 @@ void LLVolume::sculpt(U16 sculpt_width, U16 sculpt_height, S8 sculpt_components, mSculptLevel = sculpt_level; - LLSculptValidationState cached_state = LLSculptValidationState::Valid; - if (!data_is_empty && (sculpt_level >= 0) && sculpt_level < SCULPT_CACHE_SIZE) - { - // validation might be expensive, so we do it only once per lod. - cached_state = mSculptValidationCache[sculpt_level]; - } - - switch (cached_state) - { - case LLSculptValidationState::Valid: - { - // Delete any existing faces, then regenerate - mVolumeFaces.clear(); - createVolumeFaces(); - break; - } - case LLSculptValidationState::Invalid: - { - // Invalid geometry - // Regenerate mesh with an empty placeholder - mVolumeFaces.clear(); - sculptGenerateEmptyPlaceholder(); - createVolumeFaces(); - break; - } - case LLSculptValidationState::Unvalidated: - { - // First time at this LOD - mVolumeFaces.clear(); - createVolumeFaces(); - - bool valid_geometry = true; - - // Todo: can this be backed into createVolumeFaces? - // Or calculated without having to call createVolumeFaces first? - // Todo 2: should the same be done for meshes? - valid_geometry = validate_sculpt_geometry(this, validate_area); - mSculptValidationCache[sculpt_level] = valid_geometry ? LLSculptValidationState::Valid : LLSculptValidationState::Invalid; - - if (!valid_geometry) - { - LL_WARNS("LLVOLUME") << "Sculpt failed geometry validation - either invalid image or malicious. " - << "Replacing with placeholder. Image: " << mParams.getSculptID() << LL_ENDL; - - // Clear the malicious faces - mVolumeFaces.clear(); - - // Regenerate mesh with empty placeholder - sculptGenerateEmptyPlaceholder(); + // Delete any existing faces so that they get regenerated + mVolumeFaces.clear(); - // Recreate faces with placeholder geometry - createVolumeFaces(); - } - break; - } - } //switch (cached_state) + createVolumeFaces(); } diff --git a/indra/llmath/llvolume.h b/indra/llmath/llvolume.h index 40e6f8e220..3496928f7b 100644 --- a/indra/llmath/llvolume.h +++ b/indra/llmath/llvolume.h @@ -60,8 +60,6 @@ class LLVolumeOctree; #include "llalignedarray.h" #include "llrigginginfo.h" -#include - //============================================================================ constexpr S32 MIN_DETAIL_FACES = 6; @@ -1042,7 +1040,7 @@ public: const LLVector4a& getMeshPt(const U32 i) const { return mMesh[i]; } - void setDirty(); + void setDirty() { mPathp->setDirty(); mProfilep->setDirty(); } void regen(); void genTangents(S32 face); @@ -1095,7 +1093,7 @@ public: U32 mFaceMask; // bit array of which faces exist in this volume LLVector3 mLODScaleBias; // vector for biasing LOD based on scale - void sculpt(U16 sculpt_width, U16 sculpt_height, S8 sculpt_components, const U8* sculpt_data, S32 sculpt_level, bool visible_placeholder, bool validate_area); + void sculpt(U16 sculpt_width, U16 sculpt_height, S8 sculpt_components, const U8* sculpt_data, S32 sculpt_level, bool visible_placeholder); void copyVolumeFaces(const LLVolume* volume); void copyFacesTo(std::vector &faces) const; void copyFacesFrom(const std::vector &faces); @@ -1133,11 +1131,6 @@ public: bool mIsMeshAssetLoaded; bool mIsMeshAssetUnavaliable; - // Cache for sculpt geometry validation results - static constexpr S32 SCULPT_CACHE_SIZE = 6; // 0 to 5 inclusive - enum class LLSculptValidationState : S8 { Unvalidated, Valid, Invalid }; - std::array mSculptValidationCache; - const LLVolumeParams mParams; LLPath *mPathp; LLProfile *mProfilep; diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 9484a35241..ba44b68307 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -15964,17 +15964,6 @@ Value 1000 - ValidateSculptyAreaRatio - - Comment - Version information generated by running the viewer - Persist - 1 - Type - Boolean - Value - 1 - VersionChannelName Comment diff --git a/indra/newview/llfloaterimagepreview.cpp b/indra/newview/llfloaterimagepreview.cpp index 116286c872..bbff3e4c86 100644 --- a/indra/newview/llfloaterimagepreview.cpp +++ b/indra/newview/llfloaterimagepreview.cpp @@ -926,9 +926,8 @@ void LLImagePreviewSculpted::setPreviewTarget(LLImageRaw* imagep, F32 distance) if (imagep) { - static LLCachedControl validate_area(gSavedSettings, "ValidateSculptyAreaRatio", true); LLImageDataSharedLock lock(imagep); - mVolume->sculpt(imagep->getWidth(), imagep->getHeight(), imagep->getComponents(), imagep->getData(), 0, false, validate_area()); + mVolume->sculpt(imagep->getWidth(), imagep->getHeight(), imagep->getComponents(), imagep->getData(), 0, false); } const LLVolumeFace &vf = mVolume->getVolumeFace(0); diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 4a2d2da10c..076e5eb22e 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -1444,8 +1444,7 @@ void LLVOVolume::sculpt() } } - static LLCachedControl validate_area(gSavedSettings, "ValidateSculptyAreaRatio", true); - getVolume()->sculpt(sculpt_width, sculpt_height, sculpt_components, sculpt_data, discard_level, mSculptTexture->isMissingAsset(), validate_area()); + getVolume()->sculpt(sculpt_width, sculpt_height, sculpt_components, sculpt_data, discard_level, mSculptTexture->isMissingAsset()); } } -- cgit v1.3