From b08340f1831005ae227577899a64408cc939a12d Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Fri, 10 Jun 2022 19:43:14 +0300 Subject: [PATCH 01/11] SL-17475 Remap models before simplification --- indra/llmath/CMakeLists.txt | 3 + indra/llmath/llvolume.cpp | 42 ++++++ indra/llmath/llvolume.h | 4 + indra/llmeshoptimizer/llmeshoptimizer.cpp | 170 ++++++++++++++++++++-- indra/llmeshoptimizer/llmeshoptimizer.h | 70 ++++++++- indra/llprimitive/lldaeloader.cpp | 2 +- indra/llprimitive/llmodel.cpp | 8 + indra/llprimitive/llmodel.h | 1 + indra/newview/llfloatermodelpreview.cpp | 4 +- indra/newview/llmodelpreview.cpp | 81 +++++++---- indra/newview/llmodelpreview.h | 2 +- 11 files changed, 343 insertions(+), 44 deletions(-) diff --git a/indra/llmath/CMakeLists.txt b/indra/llmath/CMakeLists.txt index 552e820127..4617309606 100644 --- a/indra/llmath/CMakeLists.txt +++ b/indra/llmath/CMakeLists.txt @@ -4,12 +4,14 @@ project(llmath) include(00-Common) include(LLCommon) +include(LLMeshOptimizer) include(bugsplat) include(Boost) include_directories( ${LLCOMMON_INCLUDE_DIRS} ${LLCOMMON_SYSTEM_INCLUDE_DIRS} + ${LLMESHOPTIMIZER_INCLUDE_DIRS} ) set(llmath_SOURCE_FILES @@ -109,6 +111,7 @@ add_library (llmath ${llmath_SOURCE_FILES}) target_link_libraries(llmath ${LLCOMMON_LIBRARIES} + ${LLMESHOPTIMIZER_LIBRARIES} ) # Add tests diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp index 5099920f32..23f372f6e3 100644 --- a/indra/llmath/llvolume.cpp +++ b/indra/llmath/llvolume.cpp @@ -49,6 +49,7 @@ #include "llsdserialize.h" #include "llvector4a.h" #include "llmatrix4a.h" +#include "llmeshoptimizer.h" #include "lltimer.h" #define DEBUG_SILHOUETTE_BINORMALS 0 @@ -4952,6 +4953,47 @@ bool LLVolumeFace::VertexMapData::ComparePosition::operator()(const LLVector3& a return a.mV[2] < b.mV[2]; } +void LLVolumeFace::remap() +{ + // generate a remap buffer + std::vector remap(mNumIndices); + S32 remap_vertices_count = LLMeshOptimizer::generateRemapMulti(&remap[0], + NULL, + mNumIndices, + mPositions, + mNormals, + mTexCoords, + mNumVertices); + + // Allocate new buffers + U16* remap_indices = (U16*)ll_aligned_malloc_16(mNumIndices * sizeof(U16)); + + S32 tc_bytes_size = ((remap_vertices_count * sizeof(LLVector2)) + 0xF) & ~0xF; + LLVector4a* remap_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 2 * remap_vertices_count + tc_bytes_size); + LLVector4a* remap_normals = remap_positions + remap_vertices_count; + LLVector2* remap_tex_coords = (LLVector2*)(remap_normals + remap_vertices_count); + + // fill the buffers + LLMeshOptimizer::remapIndexBufferU16(remap_indices, mIndices, mNumIndices, &remap[0]); + LLMeshOptimizer::remapPositionsBuffer(remap_positions, mPositions, mNumVertices, &remap[0]); + LLMeshOptimizer::remapNormalsBuffer(remap_normals, mNormals, mNumVertices, &remap[0]); + LLMeshOptimizer::remapUVBuffer(remap_tex_coords, mTexCoords, mNumVertices, &remap[0]); + + // free unused buffers + ll_aligned_free_16(mIndices); + ll_aligned_free<64>(mPositions); + ll_aligned_free_16(mTangents); + + mTangents = NULL; + + mIndices = remap_indices; + mPositions = remap_positions; + mNormals = remap_normals; + mTexCoords = remap_tex_coords; + mNumVertices = remap_vertices_count; + mNumAllocatedVertices = remap_vertices_count; +} + void LLVolumeFace::optimize(F32 angle_cutoff) { LLVolumeFace new_face; diff --git a/indra/llmath/llvolume.h b/indra/llmath/llvolume.h index c0b224b1ff..9697952f5b 100644 --- a/indra/llmath/llvolume.h +++ b/indra/llmath/llvolume.h @@ -902,6 +902,10 @@ public: typedef std::map, VertexMapData::ComparePosition > PointMap; }; + // Eliminates non unique triangles, takes positions, + // normals and texture coordinates into account. + void remap(); + void optimize(F32 angle_cutoff = 2.f); bool cacheOptimize(); diff --git a/indra/llmeshoptimizer/llmeshoptimizer.cpp b/indra/llmeshoptimizer/llmeshoptimizer.cpp index a879389c5a..8570887ddd 100644 --- a/indra/llmeshoptimizer/llmeshoptimizer.cpp +++ b/indra/llmeshoptimizer/llmeshoptimizer.cpp @@ -28,6 +28,9 @@ #include "meshoptimizer.h" +#include "llmath.h" +#include "v2math.h" + LLMeshOptimizer::LLMeshOptimizer() { // Todo: Looks like for memory management, we can add allocator and deallocator callbacks @@ -40,24 +43,173 @@ LLMeshOptimizer::~LLMeshOptimizer() } //static -void LLMeshOptimizer::generateShadowIndexBuffer(U16 *destination, - const U16 *indices, +void LLMeshOptimizer::generateShadowIndexBufferU32(U32 *destination, + const U32 *indices, U64 index_count, - const LLVector4a *vertex_positions, - U64 vertex_count, - U64 vertex_positions_stride + const LLVector4a * vertex_positions, + const LLVector4a * normals, + const LLVector2 * text_coords, + U64 vertex_count ) { - meshopt_generateShadowIndexBuffer(destination, + meshopt_Stream streams[3]; + + S32 index = 0; + if (vertex_positions) + { + streams[index].data = (const float*)vertex_positions; + // Despite being LLVector4a, only x, y and z are in use + streams[index].size = sizeof(F32) * 3; + streams[index].stride = sizeof(F32) * 4; + index++; + } + if (normals) + { + streams[index].data = (const float*)normals; + streams[index].size = sizeof(F32) * 3; + streams[index].stride = sizeof(F32) * 4; + index++; + } + if (text_coords) + { + streams[index].data = (const float*)text_coords; + streams[index].size = sizeof(F32) * 2; + streams[index].stride = sizeof(F32) * 2; + index++; + } + + if (index == 0) + { + // invalid + return; + } + + meshopt_generateShadowIndexBufferMulti(destination, indices, index_count, - (const float*)vertex_positions, // verify that it is correct to convert to float vertex_count, - sizeof(LLVector4a), - vertex_positions_stride + streams, + index ); } +//static +void LLMeshOptimizer::generateShadowIndexBufferU16(U16 *destination, + const U16 *indices, + U64 index_count, + const LLVector4a * vertex_positions, + const LLVector4a * normals, + const LLVector2 * text_coords, + U64 vertex_count +) +{ + meshopt_Stream streams[3]; + + S32 index = 0; + if (vertex_positions) + { + streams[index].data = (const float*)vertex_positions; + streams[index].size = sizeof(F32) * 3; + streams[index].stride = sizeof(F32) * 4; + index++; + } + if (normals) + { + streams[index].data = (const float*)normals; + streams[index].size = sizeof(F32) * 3; + streams[index].stride = sizeof(F32) * 4; + index++; + } + if (text_coords) + { + streams[index].data = (const float*)text_coords; + streams[index].size = sizeof(F32) * 2; + streams[index].stride = sizeof(F32) * 2; + index++; + } + + if (index == 0) + { + // invalid + return; + } + + meshopt_generateShadowIndexBufferMulti(destination, + indices, + index_count, + vertex_count, + streams, + index); +} + +void LLMeshOptimizer::optimizeVertexCacheU32(U32 * destination, const U32 * indices, U64 index_count, U64 vertex_count) +{ + meshopt_optimizeVertexCache(destination, indices, index_count, vertex_count); +} + +void LLMeshOptimizer::optimizeVertexCacheU16(U16 * destination, const U16 * indices, U64 index_count, U64 vertex_count) +{ + meshopt_optimizeVertexCache(destination, indices, index_count, vertex_count); +} + +size_t LLMeshOptimizer::generateRemapMulti( + unsigned int* remap, + const U32 * indices, + U64 index_count, + const LLVector4a * vertex_positions, + const LLVector4a * normals, + const LLVector2 * text_coords, + U64 vertex_count) +{ + meshopt_Stream streams[] = { + {(const float*)vertex_positions, sizeof(F32) * 3, sizeof(F32) * 4}, + {(const float*)normals, sizeof(F32) * 3, sizeof(F32) * 4}, + {(const float*)text_coords, sizeof(F32) * 2, sizeof(F32) * 2}, + }; + + return meshopt_generateVertexRemapMulti(&remap[0], indices, index_count, vertex_count, streams, sizeof(streams) / sizeof(streams[0])); +} + +void LLMeshOptimizer::remapIndexBufferU32(U32 * destination_indices, + const U32 * indices, + U64 index_count, + const unsigned int* remap) +{ + meshopt_remapIndexBuffer(destination_indices, indices, index_count, remap); +} + +void LLMeshOptimizer::remapIndexBufferU16(U16 * destination_indices, + const U16 * indices, + U64 index_count, + const unsigned int* remap) +{ + meshopt_remapIndexBuffer(destination_indices, indices, index_count, remap); +} + +void LLMeshOptimizer::remapPositionsBuffer(LLVector4a * destination_vertices, + const LLVector4a * vertex_positions, + U64 vertex_count, + const unsigned int* remap) +{ + meshopt_remapVertexBuffer((float*)destination_vertices, (const float*)vertex_positions, vertex_count, sizeof(LLVector4a), remap); +} + +void LLMeshOptimizer::remapNormalsBuffer(LLVector4a * destination_normalss, + const LLVector4a * normals, + U64 mormals_count, + const unsigned int* remap) +{ + meshopt_remapVertexBuffer((float*)destination_normalss, (const float*)normals, mormals_count, sizeof(LLVector4a), remap); +} + +void LLMeshOptimizer::remapUVBuffer(LLVector2 * destination_uvs, + const LLVector2 * uv_positions, + U64 uv_count, + const unsigned int* remap) +{ + meshopt_remapVertexBuffer((float*)destination_uvs, (const float*)uv_positions, uv_count, sizeof(LLVector2), remap); +} + //static U64 LLMeshOptimizer::simplifyU32(U32 *destination, const U32 *indices, diff --git a/indra/llmeshoptimizer/llmeshoptimizer.h b/indra/llmeshoptimizer/llmeshoptimizer.h index e8dd16dae9..c76f8a5a89 100644 --- a/indra/llmeshoptimizer/llmeshoptimizer.h +++ b/indra/llmeshoptimizer/llmeshoptimizer.h @@ -28,7 +28,8 @@ #include "linden_common.h" -#include "llmath.h" +class LLVector4a; +class LLVector2; class LLMeshOptimizer { @@ -36,13 +37,74 @@ public: LLMeshOptimizer(); ~LLMeshOptimizer(); - static void generateShadowIndexBuffer( + static void generateShadowIndexBufferU32( + U32 *destination, + const U32 *indices, + U64 index_count, + const LLVector4a * vertex_positions, + const LLVector4a * normals, + const LLVector2 * text_coords, + U64 vertex_count); + + static void generateShadowIndexBufferU16( U16 *destination, const U16 *indices, U64 index_count, - const LLVector4a *vertex_positions, + const LLVector4a * vertex_positions, + const LLVector4a * normals, + const LLVector2 * text_coords, + U64 vertex_count); + + static void optimizeVertexCacheU32( + U32 *destination, + const U32 *indices, + U64 index_count, + U64 vertex_count); + + static void optimizeVertexCacheU16( + U16 *destination, + const U16 *indices, + U64 index_count, + U64 vertex_count); + + // Remap functions + + static size_t generateRemapMulti( + unsigned int* remap, + const U32 * indices, + U64 index_count, + const LLVector4a * vertex_positions, + const LLVector4a * normals, + const LLVector2 * text_coords, + U64 vertex_count); + + static void remapIndexBufferU32(U32 * destination_indices, + const U32 * indices, + U64 index_count, + const unsigned int* remap); + + static void remapIndexBufferU16(U16 * destination_indices, + const U16 * indices, + U64 index_count, + const unsigned int* remap); + + + static void remapPositionsBuffer(LLVector4a * destination_vertices, + const LLVector4a * vertex_positions, U64 vertex_count, - U64 vertex_positions_stride); + const unsigned int* remap); + + static void remapNormalsBuffer(LLVector4a * destination_normalss, + const LLVector4a * normals, + U64 mormals_count, + const unsigned int* remap); + + static void remapUVBuffer(LLVector2 * destination_uvs, + const LLVector2 * uv_positions, + U64 uv_count, + const unsigned int* remap); + + // Simplification // returns amount of indices in destiantion // sloppy engages a variant of a mechanizm that does not respect topology as much diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index e89690438e..68654486a4 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -2577,7 +2577,7 @@ bool LLDAELoader::loadModelsFromDomMesh(domMesh* mesh, std::vector& mo if (!mNoOptimize) { - ret->optimizeVolumeFaces(); + ret->remapVolumeFaces(); } volume_faces = remainder.size(); diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 204ff63712..a2716ecfab 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -107,6 +107,14 @@ void LLModel::offsetMesh( const LLVector3& pivotPoint ) } } +void LLModel::remapVolumeFaces() +{ + for (U32 i = 0; i < getNumVolumeFaces(); ++i) + { + mVolumeFaces[i].remap(); + } +} + void LLModel::optimizeVolumeFaces() { for (U32 i = 0; i < getNumVolumeFaces(); ++i) diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index 3881b1338c..354ceb26b7 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -184,6 +184,7 @@ public: void sortVolumeFacesByMaterialName(); void normalizeVolumeFaces(); void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL); + void remapVolumeFaces(); void optimizeVolumeFaces(); void offsetMesh( const LLVector3& pivotPoint ); void getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out); diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index fe5120376c..58fbdba315 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -741,7 +741,7 @@ void LLFloaterModelPreview::onLODParamCommit(S32 lod, bool enforce_tri_limit) { case LLModelPreview::MESH_OPTIMIZER_AUTO: case LLModelPreview::MESH_OPTIMIZER_SLOPPY: - case LLModelPreview::MESH_OPTIMIZER_COMBINE: + case LLModelPreview::MESH_OPTIMIZER_PRECISE: mModelPreview->onLODMeshOptimizerParamCommit(lod, enforce_tri_limit, mode); break; default: @@ -1745,7 +1745,7 @@ void LLFloaterModelPreview::onLoDSourceCommit(S32 lod) S32 index = lod_source_combo->getCurrentIndex(); if (index == LLModelPreview::MESH_OPTIMIZER_AUTO || index == LLModelPreview::MESH_OPTIMIZER_SLOPPY - || index == LLModelPreview::MESH_OPTIMIZER_COMBINE) + || index == LLModelPreview::MESH_OPTIMIZER_PRECISE) { //rebuild LoD to update triangle counts onLODParamCommit(lod, true); } diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 859d987fc3..707a8b970f 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -1223,6 +1223,7 @@ void LLModelPreview::restoreNormals() // returns -1 in case of failure F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *target_model, F32 indices_decimator, F32 error_threshold, bool sloppy) { + // I. Weld faces together // Figure out buffer size S32 size_indices = 0; S32 size_vertices = 0; @@ -1281,7 +1282,35 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe indices_idx_shift += face.mNumVertices; } - // Now that we have buffers, optimize + // II. Remap. + std::vector remap(size_indices); + S32 size_remap_vertices = LLMeshOptimizer::generateRemapMulti(&remap[0], + combined_indices, + size_indices, + combined_positions, + combined_normals, + combined_tex_coords, + size_vertices); + + // Allocate new buffers + U32* remap_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32)); + + S32 remap_tc_bytes_size = ((size_remap_vertices * sizeof(LLVector2)) + 0xF) & ~0xF; + LLVector4a* remap_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 2 * size_remap_vertices + remap_tc_bytes_size); + LLVector4a* remap_normals = remap_positions + size_remap_vertices; + LLVector2* remap_tex_coords = (LLVector2*)(remap_normals + size_remap_vertices); + + // fill the buffers + LLMeshOptimizer::remapIndexBufferU32(remap_indices, combined_indices, size_indices, &remap[0]); + LLMeshOptimizer::remapPositionsBuffer(remap_positions, combined_positions, size_vertices, &remap[0]); + LLMeshOptimizer::remapNormalsBuffer(remap_normals, combined_normals, size_vertices, &remap[0]); + LLMeshOptimizer::remapUVBuffer(remap_tex_coords, combined_tex_coords, size_vertices, &remap[0]); + + // free unused buffers + ll_aligned_free<64>(combined_positions); + ll_aligned_free_32(combined_indices); + + // III. Simplify S32 target_indices = 0; F32 result_error = 0; // how far from original the model is, 1 == 100% S32 new_indices = 0; @@ -1294,19 +1323,19 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe { target_indices = 3; } + new_indices = LLMeshOptimizer::simplifyU32( output_indices, - combined_indices, + remap_indices, size_indices, - combined_positions, - size_vertices, + remap_positions, + size_remap_vertices, LLVertexBuffer::sTypeSize[LLVertexBuffer::TYPE_VERTEX], target_indices, error_threshold, sloppy, &result_error); - if (result_error < 0) { LL_WARNS() << "Negative result error from meshoptimizer for model " << target_model->mLabel @@ -1315,24 +1344,25 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe << " original count: " << size_indices << LL_ENDL; } + ll_aligned_free_32(remap_indices); + if (new_indices < 3) { // Model should have at least one visible triangle - ll_aligned_free<64>(combined_positions); + ll_aligned_free<64>(remap_positions); ll_aligned_free_32(output_indices); - ll_aligned_free_32(combined_indices); return -1; } - // repack back into individual faces + // IV. Repack back into individual faces - LLVector4a* buffer_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 2 * size_vertices + tc_bytes_size); - LLVector4a* buffer_normals = buffer_positions + size_vertices; - LLVector2* buffer_tex_coords = (LLVector2*)(buffer_normals + size_vertices); + LLVector4a* buffer_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 2 * size_remap_vertices + tc_bytes_size); + LLVector4a* buffer_normals = buffer_positions + size_remap_vertices; + LLVector2* buffer_tex_coords = (LLVector2*)(buffer_normals + size_remap_vertices); S32 buffer_idx_size = (size_indices * sizeof(U16) + 0xF) & ~0xF; U16* buffer_indices = (U16*)ll_aligned_malloc_16(buffer_idx_size); - S32* old_to_new_positions_map = new S32[size_vertices]; + S32* old_to_new_positions_map = new S32[size_remap_vertices]; S32 buf_positions_copied = 0; S32 buf_indices_copied = 0; @@ -1350,7 +1380,7 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe bool copy_triangle = false; S32 range = indices_idx_shift + face.mNumVertices; - for (S32 i = 0; i < size_vertices; i++) + for (S32 i = 0; i < size_remap_vertices; i++) { old_to_new_positions_map[i] = -1; } @@ -1408,9 +1438,9 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe } // Copy vertice, normals, tcs - buffer_positions[buf_positions_copied] = combined_positions[idx]; - buffer_normals[buf_positions_copied] = combined_normals[idx]; - buffer_tex_coords[buf_positions_copied] = combined_tex_coords[idx]; + buffer_positions[buf_positions_copied] = remap_positions[idx]; + buffer_normals[buf_positions_copied] = remap_normals[idx]; + buffer_tex_coords[buf_positions_copied] = remap_tex_coords[idx]; old_to_new_positions_map[idx] = buf_positions_copied; @@ -1465,11 +1495,10 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe } delete[]old_to_new_positions_map; - ll_aligned_free<64>(combined_positions); + ll_aligned_free<64>(remap_positions); ll_aligned_free<64>(buffer_positions); ll_aligned_free_32(output_indices); ll_aligned_free_16(buffer_indices); - ll_aligned_free_32(combined_indices); if (new_indices < 3 || valid_faces == 0) { @@ -1488,10 +1517,9 @@ F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target { return -1; } - // todo: do not allocate per each face, add one large buffer somewhere - // faces have limited amount of indices + S32 size = (size_indices * sizeof(U16) + 0xF) & ~0xF; - U16* output = (U16*)ll_aligned_malloc_16(size); + U16* output_indices = (U16*)ll_aligned_malloc_16(size); S32 target_indices = 0; F32 result_error = 0; // how far from original the model is, 1 == 100% @@ -1505,8 +1533,9 @@ F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target { target_indices = 3; } + new_indices = LLMeshOptimizer::simplify( - output, + output_indices, face.mIndices, size_indices, face.mPositions, @@ -1517,7 +1546,6 @@ F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target sloppy, &result_error); - if (result_error < 0) { LL_WARNS() << "Negative result error from meshoptimizer for face " << face_idx @@ -1534,7 +1562,6 @@ F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target // Copy old values new_face = face; - if (new_indices < 3) { if (!sloppy) @@ -1563,13 +1590,13 @@ F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target // Assign new values new_face.resizeIndices(new_indices); // will wipe out mIndices, so new_face can't substitute output S32 idx_size = (new_indices * sizeof(U16) + 0xF) & ~0xF; - LLVector4a::memcpyNonAliased16((F32*)new_face.mIndices, (F32*)output, idx_size); + LLVector4a::memcpyNonAliased16((F32*)new_face.mIndices, (F32*)output_indices, idx_size); // clear unused values new_face.optimize(); } - ll_aligned_free_16(output); + ll_aligned_free_16(output_indices); if (new_indices < 3) { @@ -1711,7 +1738,7 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d // Ideally this should run not per model, // but combine all submodels with origin model as well - if (model_meshopt_mode == MESH_OPTIMIZER_COMBINE) + if (model_meshopt_mode == MESH_OPTIMIZER_PRECISE) { // Run meshoptimizer for each model/object, up to 8 faces in one model. diff --git a/indra/newview/llmodelpreview.h b/indra/newview/llmodelpreview.h index 9e32215e6a..727fe79373 100644 --- a/indra/newview/llmodelpreview.h +++ b/indra/newview/llmodelpreview.h @@ -125,7 +125,7 @@ public: { LOD_FROM_FILE = 0, MESH_OPTIMIZER_AUTO, // automatically selects method based on model or face - MESH_OPTIMIZER_COMBINE, // combines faces into a single model, simplifies, then splits back into faces + MESH_OPTIMIZER_PRECISE, // combines faces into a single model, simplifies, then splits back into faces MESH_OPTIMIZER_SLOPPY, // uses sloppy method, works per face USE_LOD_ABOVE, } eLoDMode; From 45bcefd981e268b158d11d59f2ba9063293986a6 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 14 Jun 2022 16:39:46 +0300 Subject: [PATCH 02/11] SL-17475 fix remap causing an assert --- indra/llmath/llvolume.cpp | 4 +- indra/llmeshoptimizer/llmeshoptimizer.cpp | 59 ++++++++++++++++++++++- indra/llmeshoptimizer/llmeshoptimizer.h | 13 ++++- indra/newview/llmodelpreview.cpp | 2 +- 4 files changed, 72 insertions(+), 6 deletions(-) diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp index 23f372f6e3..9efdcd4e8c 100644 --- a/indra/llmath/llvolume.cpp +++ b/indra/llmath/llvolume.cpp @@ -4957,8 +4957,8 @@ void LLVolumeFace::remap() { // generate a remap buffer std::vector remap(mNumIndices); - S32 remap_vertices_count = LLMeshOptimizer::generateRemapMulti(&remap[0], - NULL, + S32 remap_vertices_count = LLMeshOptimizer::generateRemapMultiU16(&remap[0], + mIndices, mNumIndices, mPositions, mNormals, diff --git a/indra/llmeshoptimizer/llmeshoptimizer.cpp b/indra/llmeshoptimizer/llmeshoptimizer.cpp index 8570887ddd..cb9716a907 100644 --- a/indra/llmeshoptimizer/llmeshoptimizer.cpp +++ b/indra/llmeshoptimizer/llmeshoptimizer.cpp @@ -152,7 +152,7 @@ void LLMeshOptimizer::optimizeVertexCacheU16(U16 * destination, const U16 * indi meshopt_optimizeVertexCache(destination, indices, index_count, vertex_count); } -size_t LLMeshOptimizer::generateRemapMulti( +size_t LLMeshOptimizer::generateRemapMultiU32( unsigned int* remap, const U32 * indices, U64 index_count, @@ -167,7 +167,62 @@ size_t LLMeshOptimizer::generateRemapMulti( {(const float*)text_coords, sizeof(F32) * 2, sizeof(F32) * 2}, }; - return meshopt_generateVertexRemapMulti(&remap[0], indices, index_count, vertex_count, streams, sizeof(streams) / sizeof(streams[0])); + // Remap can function without indices, + // but providing indices helps with removing unused vertices + U64 indeces_cmp = indices ? index_count : vertex_count; + + // meshopt_generateVertexRemapMulti will throw an assert if (indices[i] >= vertex_count) + return meshopt_generateVertexRemapMulti(&remap[0], indices, indeces_cmp, vertex_count, streams, sizeof(streams) / sizeof(streams[0])); +} + +size_t LLMeshOptimizer::generateRemapMultiU16( + unsigned int* remap, + const U16 * indices, + U64 index_count, + const LLVector4a * vertex_positions, + const LLVector4a * normals, + const LLVector2 * text_coords, + U64 vertex_count) +{ + meshopt_Stream streams[] = { + {(const float*)vertex_positions, sizeof(F32) * 3, sizeof(F32) * 4}, + {(const float*)normals, sizeof(F32) * 3, sizeof(F32) * 4}, + {(const float*)text_coords, sizeof(F32) * 2, sizeof(F32) * 2}, + }; + + S32 out_of_range_count = 0; + U32* indices_u32 = NULL; + if (indices) + { + indices_u32 = (U32*)ll_aligned_malloc_32(index_count * sizeof(U32)); + for (U64 i = 0; i < index_count; i++) + { + if (indices[i] < vertex_count) + { + indices_u32[i] = indices[i]; + } + else + { + out_of_range_count++; + indices_u32[i] = 0; + } + } + } + + if (out_of_range_count) + { + LL_WARNS() << out_of_range_count << " indexes are out of range." << LL_ENDL; + } + + // Remap can function without indices, + // but providing indices helps with removing unused vertices + U64 indeces_cmp = indices_u32 ? index_count : vertex_count; + + size_t unique = meshopt_generateVertexRemapMulti(&remap[0], indices_u32, indeces_cmp, vertex_count, streams, sizeof(streams) / sizeof(streams[0])); + + ll_aligned_free_32(indices_u32); + + return unique; } void LLMeshOptimizer::remapIndexBufferU32(U32 * destination_indices, diff --git a/indra/llmeshoptimizer/llmeshoptimizer.h b/indra/llmeshoptimizer/llmeshoptimizer.h index c76f8a5a89..ea965d6b47 100644 --- a/indra/llmeshoptimizer/llmeshoptimizer.h +++ b/indra/llmeshoptimizer/llmeshoptimizer.h @@ -68,8 +68,10 @@ public: U64 vertex_count); // Remap functions + // Welds indentical vertexes together. + // Removes unused vertices if indices were provided. - static size_t generateRemapMulti( + static size_t generateRemapMultiU32( unsigned int* remap, const U32 * indices, U64 index_count, @@ -78,6 +80,15 @@ public: const LLVector2 * text_coords, U64 vertex_count); + static size_t generateRemapMultiU16( + unsigned int* remap, + const U16 * indices, + U64 index_count, + const LLVector4a * vertex_positions, + const LLVector4a * normals, + const LLVector2 * text_coords, + U64 vertex_count); + static void remapIndexBufferU32(U32 * destination_indices, const U32 * indices, U64 index_count, diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 707a8b970f..ef791fd80d 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -1284,7 +1284,7 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe // II. Remap. std::vector remap(size_indices); - S32 size_remap_vertices = LLMeshOptimizer::generateRemapMulti(&remap[0], + S32 size_remap_vertices = LLMeshOptimizer::generateRemapMultiU32(&remap[0], combined_indices, size_indices, combined_positions, From b1cbf369cf63f270cb810f640a14420be53035ed Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 14 Jun 2022 18:33:50 +0300 Subject: [PATCH 03/11] SL-17475 Use a shadow indices buffer before simplification --- indra/llmath/llvolume.cpp | 13 +- indra/llmeshoptimizer/llmeshoptimizer.cpp | 16 +- indra/newview/llmodelpreview.cpp | 225 +++++++++++++--------- indra/newview/llmodelpreview.h | 12 +- 4 files changed, 159 insertions(+), 107 deletions(-) diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp index 9efdcd4e8c..04d3e9fc08 100644 --- a/indra/llmath/llvolume.cpp +++ b/indra/llmath/llvolume.cpp @@ -4955,7 +4955,7 @@ bool LLVolumeFace::VertexMapData::ComparePosition::operator()(const LLVector3& a void LLVolumeFace::remap() { - // generate a remap buffer + // Generate a remap buffer std::vector remap(mNumIndices); S32 remap_vertices_count = LLMeshOptimizer::generateRemapMultiU16(&remap[0], mIndices, @@ -4966,26 +4966,29 @@ void LLVolumeFace::remap() mNumVertices); // Allocate new buffers - U16* remap_indices = (U16*)ll_aligned_malloc_16(mNumIndices * sizeof(U16)); + S32 size = ((mNumIndices * sizeof(U16)) + 0xF) & ~0xF; + U16* remap_indices = (U16*)ll_aligned_malloc_16(size); S32 tc_bytes_size = ((remap_vertices_count * sizeof(LLVector2)) + 0xF) & ~0xF; LLVector4a* remap_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 2 * remap_vertices_count + tc_bytes_size); LLVector4a* remap_normals = remap_positions + remap_vertices_count; LLVector2* remap_tex_coords = (LLVector2*)(remap_normals + remap_vertices_count); - // fill the buffers + // Fill the buffers LLMeshOptimizer::remapIndexBufferU16(remap_indices, mIndices, mNumIndices, &remap[0]); LLMeshOptimizer::remapPositionsBuffer(remap_positions, mPositions, mNumVertices, &remap[0]); LLMeshOptimizer::remapNormalsBuffer(remap_normals, mNormals, mNumVertices, &remap[0]); LLMeshOptimizer::remapUVBuffer(remap_tex_coords, mTexCoords, mNumVertices, &remap[0]); - // free unused buffers + // Free unused buffers ll_aligned_free_16(mIndices); ll_aligned_free<64>(mPositions); - ll_aligned_free_16(mTangents); + // Tangets are now invalid + ll_aligned_free_16(mTangents); mTangents = NULL; + // Assign new values mIndices = remap_indices; mPositions = remap_positions; mNormals = remap_normals; diff --git a/indra/llmeshoptimizer/llmeshoptimizer.cpp b/indra/llmeshoptimizer/llmeshoptimizer.cpp index cb9716a907..c178348968 100644 --- a/indra/llmeshoptimizer/llmeshoptimizer.cpp +++ b/indra/llmeshoptimizer/llmeshoptimizer.cpp @@ -184,12 +184,6 @@ size_t LLMeshOptimizer::generateRemapMultiU16( const LLVector2 * text_coords, U64 vertex_count) { - meshopt_Stream streams[] = { - {(const float*)vertex_positions, sizeof(F32) * 3, sizeof(F32) * 4}, - {(const float*)normals, sizeof(F32) * 3, sizeof(F32) * 4}, - {(const float*)text_coords, sizeof(F32) * 2, sizeof(F32) * 2}, - }; - S32 out_of_range_count = 0; U32* indices_u32 = NULL; if (indices) @@ -199,7 +193,7 @@ size_t LLMeshOptimizer::generateRemapMultiU16( { if (indices[i] < vertex_count) { - indices_u32[i] = indices[i]; + indices_u32[i] = (U32)indices[i]; } else { @@ -211,14 +205,10 @@ size_t LLMeshOptimizer::generateRemapMultiU16( if (out_of_range_count) { - LL_WARNS() << out_of_range_count << " indexes are out of range." << LL_ENDL; + LL_WARNS() << out_of_range_count << " indices are out of range." << LL_ENDL; } - // Remap can function without indices, - // but providing indices helps with removing unused vertices - U64 indeces_cmp = indices_u32 ? index_count : vertex_count; - - size_t unique = meshopt_generateVertexRemapMulti(&remap[0], indices_u32, indeces_cmp, vertex_count, streams, sizeof(streams) / sizeof(streams[0])); + size_t unique = generateRemapMultiU32(remap, indices_u32, index_count, vertex_positions, normals, text_coords, vertex_count); ll_aligned_free_32(indices_u32); diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index ef791fd80d..4ec2d82350 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -1221,7 +1221,7 @@ void LLModelPreview::restoreNormals() // Runs per object, but likely it is a better way to run per model+submodels // returns a ratio of base model indices to resulting indices // returns -1 in case of failure -F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *target_model, F32 indices_decimator, F32 error_threshold, bool sloppy) +F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *target_model, F32 indices_decimator, F32 error_threshold, eSimplificationMode simplification_mode) { // I. Weld faces together // Figure out buffer size @@ -1258,20 +1258,21 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe { const LLVolumeFace &face = base_model->getVolumeFace(face_idx); - // vertices + // Vertices S32 copy_bytes = face.mNumVertices * sizeof(LLVector4a); LLVector4a::memcpyNonAliased16((F32*)(combined_positions + combined_positions_shift), (F32*)face.mPositions, copy_bytes); - // normals + // Normals LLVector4a::memcpyNonAliased16((F32*)(combined_normals + combined_positions_shift), (F32*)face.mNormals, copy_bytes); - // tex coords + // Tex coords copy_bytes = face.mNumVertices * sizeof(LLVector2); memcpy((void*)(combined_tex_coords + combined_positions_shift), (void*)face.mTexCoords, copy_bytes); combined_positions_shift += face.mNumVertices; - // indices, sadly can't do dumb memcpy for indices, need to adjust each value + // Indices + // Sadly can't do dumb memcpy for indices, need to adjust each value for (S32 i = 0; i < face.mNumIndices; ++i) { U16 idx = face.mIndices[i]; @@ -1282,38 +1283,42 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe indices_idx_shift += face.mNumVertices; } - // II. Remap. - std::vector remap(size_indices); - S32 size_remap_vertices = LLMeshOptimizer::generateRemapMultiU32(&remap[0], - combined_indices, - size_indices, - combined_positions, - combined_normals, - combined_tex_coords, - size_vertices); + // II. Generate a shadow buffer if nessesary. + // Welds together vertices if possible - // Allocate new buffers - U32* remap_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32)); + U32* shadow_indices = NULL; + // if MESH_OPTIMIZER_FULL, just leave as is, since generateShadowIndexBufferU32 + // won't do anything new, model was remaped on a per face basis. + // Similar for MESH_OPTIMIZER_NO_TOPOLOGY, it's pointless + // since 'simplifySloppy' ignores all topology, including normals and uvs. + // Note: simplifySloppy can affect UVs significantly. + if (simplification_mode == MESH_OPTIMIZER_NO_NORMALS) + { + // strip normals, reflections should restore relatively correctly + shadow_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32)); + LLMeshOptimizer::generateShadowIndexBufferU32(shadow_indices, combined_indices, size_indices, combined_positions, NULL, combined_tex_coords, size_vertices); + } + if (simplification_mode == MESH_OPTIMIZER_NO_UVS) + { + // strip uvs, can heavily affect textures + shadow_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32)); + LLMeshOptimizer::generateShadowIndexBufferU32(shadow_indices, combined_indices, size_indices, combined_positions, NULL, NULL, size_vertices); + } - S32 remap_tc_bytes_size = ((size_remap_vertices * sizeof(LLVector2)) + 0xF) & ~0xF; - LLVector4a* remap_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 2 * size_remap_vertices + remap_tc_bytes_size); - LLVector4a* remap_normals = remap_positions + size_remap_vertices; - LLVector2* remap_tex_coords = (LLVector2*)(remap_normals + size_remap_vertices); - - // fill the buffers - LLMeshOptimizer::remapIndexBufferU32(remap_indices, combined_indices, size_indices, &remap[0]); - LLMeshOptimizer::remapPositionsBuffer(remap_positions, combined_positions, size_vertices, &remap[0]); - LLMeshOptimizer::remapNormalsBuffer(remap_normals, combined_normals, size_vertices, &remap[0]); - LLMeshOptimizer::remapUVBuffer(remap_tex_coords, combined_tex_coords, size_vertices, &remap[0]); - - // free unused buffers - ll_aligned_free<64>(combined_positions); - ll_aligned_free_32(combined_indices); + U32* source_indices = NULL; + if (shadow_indices) + { + source_indices = shadow_indices; + } + else + { + source_indices = combined_indices; + } // III. Simplify S32 target_indices = 0; F32 result_error = 0; // how far from original the model is, 1 == 100% - S32 new_indices = 0; + S32 size_new_indices = 0; if (indices_decimator > 0) { @@ -1324,32 +1329,36 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe target_indices = 3; } - new_indices = LLMeshOptimizer::simplifyU32( + size_new_indices = LLMeshOptimizer::simplifyU32( output_indices, - remap_indices, + source_indices, size_indices, - remap_positions, - size_remap_vertices, + combined_positions, + size_vertices, LLVertexBuffer::sTypeSize[LLVertexBuffer::TYPE_VERTEX], target_indices, error_threshold, - sloppy, + simplification_mode == MESH_OPTIMIZER_NO_TOPOLOGY, &result_error); if (result_error < 0) { LL_WARNS() << "Negative result error from meshoptimizer for model " << target_model->mLabel << " target Indices: " << target_indices - << " new Indices: " << new_indices + << " new Indices: " << size_new_indices << " original count: " << size_indices << LL_ENDL; } - ll_aligned_free_32(remap_indices); + // free unused buffers + ll_aligned_free_32(combined_indices); + ll_aligned_free_32(shadow_indices); + combined_indices = NULL; + shadow_indices = NULL; - if (new_indices < 3) + if (size_new_indices < 3) { // Model should have at least one visible triangle - ll_aligned_free<64>(remap_positions); + ll_aligned_free<64>(combined_positions); ll_aligned_free_32(output_indices); return -1; @@ -1357,12 +1366,12 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe // IV. Repack back into individual faces - LLVector4a* buffer_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 2 * size_remap_vertices + tc_bytes_size); - LLVector4a* buffer_normals = buffer_positions + size_remap_vertices; - LLVector2* buffer_tex_coords = (LLVector2*)(buffer_normals + size_remap_vertices); + LLVector4a* buffer_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 2 * size_vertices + tc_bytes_size); + LLVector4a* buffer_normals = buffer_positions + size_vertices; + LLVector2* buffer_tex_coords = (LLVector2*)(buffer_normals + size_vertices); S32 buffer_idx_size = (size_indices * sizeof(U16) + 0xF) & ~0xF; U16* buffer_indices = (U16*)ll_aligned_malloc_16(buffer_idx_size); - S32* old_to_new_positions_map = new S32[size_remap_vertices]; + S32* old_to_new_positions_map = new S32[size_vertices]; S32 buf_positions_copied = 0; S32 buf_indices_copied = 0; @@ -1380,13 +1389,13 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe bool copy_triangle = false; S32 range = indices_idx_shift + face.mNumVertices; - for (S32 i = 0; i < size_remap_vertices; i++) + for (S32 i = 0; i < size_vertices; i++) { old_to_new_positions_map[i] = -1; } // Copy relevant indices and vertices - for (S32 i = 0; i < new_indices; ++i) + for (S32 i = 0; i < size_new_indices; ++i) { U32 idx = output_indices[i]; @@ -1409,19 +1418,19 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe LL_WARNS() << "Over triangle limit. Failed to optimize in 'per object' mode, falling back to per face variant for" << " model " << target_model->mLabel << " target Indices: " << target_indices - << " new Indices: " << new_indices + << " new Indices: " << size_new_indices << " original count: " << size_indices << " error treshold: " << error_threshold << LL_ENDL; // U16 vertices overflow shouldn't happen, but just in case - new_indices = 0; + size_new_indices = 0; valid_faces = 0; for (U32 face_idx = 0; face_idx < base_model->getNumVolumeFaces(); ++face_idx) { - genMeshOptimizerPerFace(base_model, target_model, face_idx, indices_decimator, error_threshold, false); + genMeshOptimizerPerFace(base_model, target_model, face_idx, indices_decimator, error_threshold, simplification_mode); const LLVolumeFace &face = target_model->getVolumeFace(face_idx); - new_indices += face.mNumIndices; + size_new_indices += face.mNumIndices; if (face.mNumIndices >= 3) { valid_faces++; @@ -1429,7 +1438,7 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe } if (valid_faces) { - return (F32)size_indices / (F32)new_indices; + return (F32)size_indices / (F32)size_new_indices; } else { @@ -1438,9 +1447,9 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe } // Copy vertice, normals, tcs - buffer_positions[buf_positions_copied] = remap_positions[idx]; - buffer_normals[buf_positions_copied] = remap_normals[idx]; - buffer_tex_coords[buf_positions_copied] = remap_tex_coords[idx]; + buffer_positions[buf_positions_copied] = combined_positions[idx]; + buffer_normals[buf_positions_copied] = combined_normals[idx]; + buffer_tex_coords[buf_positions_copied] = combined_tex_coords[idx]; old_to_new_positions_map[idx] = buf_positions_copied; @@ -1495,21 +1504,21 @@ F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *targe } delete[]old_to_new_positions_map; - ll_aligned_free<64>(remap_positions); + ll_aligned_free<64>(combined_positions); ll_aligned_free<64>(buffer_positions); ll_aligned_free_32(output_indices); ll_aligned_free_16(buffer_indices); - if (new_indices < 3 || valid_faces == 0) + if (size_new_indices < 3 || valid_faces == 0) { // Model should have at least one visible triangle return -1; } - return (F32)size_indices / (F32)new_indices; + return (F32)size_indices / (F32)size_new_indices; } -F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target_model, U32 face_idx, F32 indices_decimator, F32 error_threshold, bool sloppy) +F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target_model, U32 face_idx, F32 indices_decimator, F32 error_threshold, eSimplificationMode simplification_mode) { const LLVolumeFace &face = base_model->getVolumeFace(face_idx); S32 size_indices = face.mNumIndices; @@ -1521,9 +1530,36 @@ F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target S32 size = (size_indices * sizeof(U16) + 0xF) & ~0xF; U16* output_indices = (U16*)ll_aligned_malloc_16(size); + U16* shadow_indices = NULL; + // if MESH_OPTIMIZER_FULL, just leave as is, since generateShadowIndexBufferU32 + // won't do anything new, model was remaped on a per face basis. + // Similar for MESH_OPTIMIZER_NO_TOPOLOGY, it's pointless + // since 'simplifySloppy' ignores all topology, including normals and uvs. + if (simplification_mode == MESH_OPTIMIZER_NO_NORMALS) + { + U16* shadow_indices = (U16*)ll_aligned_malloc_16(size); + LLMeshOptimizer::generateShadowIndexBufferU16(shadow_indices, face.mIndices, size_indices, face.mPositions, NULL, face.mTexCoords, face.mNumVertices); + } + if (simplification_mode == MESH_OPTIMIZER_NO_UVS) + { + U16* shadow_indices = (U16*)ll_aligned_malloc_16(size); + LLMeshOptimizer::generateShadowIndexBufferU16(shadow_indices, face.mIndices, size_indices, face.mPositions, NULL, NULL, face.mNumVertices); + } + // Don't run ShadowIndexBuffer for MESH_OPTIMIZER_NO_TOPOLOGY, it's pointless + + U16* source_indices = NULL; + if (shadow_indices) + { + source_indices = shadow_indices; + } + else + { + source_indices = face.mIndices; + } + S32 target_indices = 0; F32 result_error = 0; // how far from original the model is, 1 == 100% - S32 new_indices = 0; + S32 size_new_indices = 0; if (indices_decimator > 0) { @@ -1534,16 +1570,16 @@ F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target target_indices = 3; } - new_indices = LLMeshOptimizer::simplify( + size_new_indices = LLMeshOptimizer::simplify( output_indices, - face.mIndices, + source_indices, size_indices, face.mPositions, face.mNumVertices, LLVertexBuffer::sTypeSize[LLVertexBuffer::TYPE_VERTEX], target_indices, error_threshold, - sloppy, + simplification_mode == MESH_OPTIMIZER_NO_TOPOLOGY, &result_error); if (result_error < 0) @@ -1551,7 +1587,7 @@ F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target LL_WARNS() << "Negative result error from meshoptimizer for face " << face_idx << " of model " << target_model->mLabel << " target Indices: " << target_indices - << " new Indices: " << new_indices + << " new Indices: " << size_new_indices << " original count: " << size_indices << " error treshold: " << error_threshold << LL_ENDL; @@ -1562,9 +1598,9 @@ F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target // Copy old values new_face = face; - if (new_indices < 3) + if (size_new_indices < 3) { - if (!sloppy) + if (simplification_mode != MESH_OPTIMIZER_NO_TOPOLOGY) { // meshopt_optimizeSloppy() can optimize triangles away even if target_indices is > 2, // but optimize() isn't supposed to @@ -1588,23 +1624,24 @@ F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target else { // Assign new values - new_face.resizeIndices(new_indices); // will wipe out mIndices, so new_face can't substitute output - S32 idx_size = (new_indices * sizeof(U16) + 0xF) & ~0xF; + new_face.resizeIndices(size_new_indices); // will wipe out mIndices, so new_face can't substitute output + S32 idx_size = (size_new_indices * sizeof(U16) + 0xF) & ~0xF; LLVector4a::memcpyNonAliased16((F32*)new_face.mIndices, (F32*)output_indices, idx_size); - // clear unused values + // Clear unused values new_face.optimize(); } ll_aligned_free_16(output_indices); + ll_aligned_free_16(shadow_indices); - if (new_indices < 3) + if (size_new_indices < 3) { // At least one triangle is needed return -1; } - return (F32)size_indices / (F32)new_indices; + return (F32)size_indices / (F32)size_new_indices; } void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 decimation, bool enforce_tri_limit) @@ -1740,14 +1777,17 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d // but combine all submodels with origin model as well if (model_meshopt_mode == MESH_OPTIMIZER_PRECISE) { - // Run meshoptimizer for each model/object, up to 8 faces in one model. - - // Ideally this should run not per model, - // but combine all submodels with origin model as well - F32 res = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, false); - if (res < 0) + // Run meshoptimizer for each face + for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx) { - target_model->copyVolumeFaces(base); + F32 res = genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL); + if (res < 0) + { + // Mesh optimizer failed and returned an invalid model + const LLVolumeFace &face = base->getVolumeFace(face_idx); + LLVolumeFace &new_face = target_model->getVolumeFace(face_idx); + new_face = face; + } } } @@ -1756,19 +1796,29 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d // Run meshoptimizer for each face for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx) { - if (genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, true) < 0) + if (genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_TOPOLOGY) < 0) { // Sloppy failed and returned an invalid model - genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, false); + genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL); } } } if (model_meshopt_mode == MESH_OPTIMIZER_AUTO) { - // Switches between 'combine' method and 'sloppy' based on combine's result. - F32 allowed_ratio_drift = 2.f; - F32 precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, false); + // Remove progressively more data if we can't reach the target. + F32 allowed_ratio_drift = 1.8f; + F32 precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL); + + if (precise_ratio < 0 || (precise_ratio * allowed_ratio_drift < indices_decimator)) + { + precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_NORMALS); + } + + if (precise_ratio < 0 || (precise_ratio * allowed_ratio_drift < indices_decimator)) + { + precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_UVS); + } if (precise_ratio < 0 || (precise_ratio * allowed_ratio_drift < indices_decimator)) { @@ -1776,10 +1826,11 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d // Sloppy variant can fail entirely and has issues with precision, // so code needs to do multiple attempts with different decimators. // Todo: this is a bit of a mess, needs to be refined and improved + F32 last_working_decimator = 0.f; F32 last_working_ratio = F32_MAX; - F32 sloppy_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, true); + F32 sloppy_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_TOPOLOGY); if (sloppy_ratio > 0) { @@ -1802,13 +1853,13 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d // side due to overal lack of precision, and we don't need an ideal result, which // likely does not exist, just a better one, so a partial correction is enough. F32 sloppy_decimator = indices_decimator * (indices_decimator / sloppy_ratio + 1) / 2; - sloppy_ratio = genMeshOptimizerPerModel(base, target_model, sloppy_decimator, lod_error_threshold, true); + sloppy_ratio = genMeshOptimizerPerModel(base, target_model, sloppy_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_TOPOLOGY); } if (last_working_decimator > 0 && sloppy_ratio < last_working_ratio) { // Compensation didn't work, return back to previous decimator - sloppy_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, true); + sloppy_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_TOPOLOGY); } if (sloppy_ratio < 0) @@ -1841,7 +1892,7 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d && sloppy_decimator > precise_ratio && sloppy_decimator > 1)// precise_ratio isn't supposed to be below 1, but check just in case { - sloppy_ratio = genMeshOptimizerPerModel(base, target_model, sloppy_decimator, lod_error_threshold, true); + sloppy_ratio = genMeshOptimizerPerModel(base, target_model, sloppy_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_TOPOLOGY); sloppy_decimator = sloppy_decimator / sloppy_decimation_step; } } @@ -1861,7 +1912,7 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d else { // Fallback to normal method - precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, false); + precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL); } LL_INFOS() << "Model " << target_model->getName() diff --git a/indra/newview/llmodelpreview.h b/indra/newview/llmodelpreview.h index 727fe79373..215f44357f 100644 --- a/indra/newview/llmodelpreview.h +++ b/indra/newview/llmodelpreview.h @@ -225,13 +225,21 @@ private: // Count amount of original models, excluding sub-models static U32 countRootModels(LLModelLoader::model_list models); + typedef enum + { + MESH_OPTIMIZER_FULL, + MESH_OPTIMIZER_NO_NORMALS, + MESH_OPTIMIZER_NO_UVS, + MESH_OPTIMIZER_NO_TOPOLOGY, + } eSimplificationMode; + // Merges faces into single mesh, simplifies using mesh optimizer, // then splits back into faces. // Returns reached simplification ratio. -1 in case of a failure. - F32 genMeshOptimizerPerModel(LLModel *base_model, LLModel *target_model, F32 indices_ratio, F32 error_threshold, bool sloppy); + F32 genMeshOptimizerPerModel(LLModel *base_model, LLModel *target_model, F32 indices_ratio, F32 error_threshold, eSimplificationMode simplification_mode); // Simplifies specified face using mesh optimizer. // Returns reached simplification ratio. -1 in case of a failure. - F32 genMeshOptimizerPerFace(LLModel *base_model, LLModel *target_model, U32 face_idx, F32 indices_ratio, F32 error_threshold, bool sloppy); + F32 genMeshOptimizerPerFace(LLModel *base_model, LLModel *target_model, U32 face_idx, F32 indices_ratio, F32 error_threshold, eSimplificationMode simplification_mode); protected: friend class LLModelLoader; From dcac3f0514444684c57a97c6e01d32499da345d6 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Wed, 15 Jun 2022 17:53:22 +0300 Subject: [PATCH 04/11] SL-17577 Allow setting Object for sale for L$0 from Object Profile --- indra/newview/skins/default/xui/en/sidepanel_task_info.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/skins/default/xui/en/sidepanel_task_info.xml b/indra/newview/skins/default/xui/en/sidepanel_task_info.xml index 8a3e18707f..1c9d750aa6 100644 --- a/indra/newview/skins/default/xui/en/sidepanel_task_info.xml +++ b/indra/newview/skins/default/xui/en/sidepanel_task_info.xml @@ -459,7 +459,7 @@ label="Price: L$" label_width="73" width="150" - min_val="1" + min_val="0" height="20" max_val="999999999" tool_tip="Object cost." /> From f451672b0c549092796a018997152cbeb57418b2 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Thu, 16 Jun 2022 16:46:04 +0300 Subject: [PATCH 05/11] SL-17475 Fix wrong remap buffer size --- indra/llmath/llvolume.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp index 04d3e9fc08..4a069b0f63 100644 --- a/indra/llmath/llvolume.cpp +++ b/indra/llmath/llvolume.cpp @@ -4956,7 +4956,7 @@ bool LLVolumeFace::VertexMapData::ComparePosition::operator()(const LLVector3& a void LLVolumeFace::remap() { // Generate a remap buffer - std::vector remap(mNumIndices); + std::vector remap(mNumVertices); S32 remap_vertices_count = LLMeshOptimizer::generateRemapMultiU16(&remap[0], mIndices, mNumIndices, From 9f5a38b98ef0e05828de729004447e572d139fd4 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Thu, 16 Jun 2022 19:40:33 +0300 Subject: [PATCH 06/11] SL-17562 FIXED Grid Options changes position upon reopening of Build Window --- indra/newview/llfloatertools.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llfloatertools.cpp b/indra/newview/llfloatertools.cpp index c550b81094..77a04bc5d7 100644 --- a/indra/newview/llfloatertools.cpp +++ b/indra/newview/llfloatertools.cpp @@ -1127,7 +1127,7 @@ void LLFloaterTools::onClickGridOptions() { LLFloater* floaterp = LLFloaterReg::showInstance("build_options"); // position floater next to build tools, not over - floaterp->setRect(gFloaterView->findNeighboringPosition(this, floaterp)); + floaterp->setShape(gFloaterView->findNeighboringPosition(this, floaterp), true); } // static From e8dd01beecaeea8b67c88ffa2212411e672495b6 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 17 Jun 2022 19:00:38 +0300 Subject: [PATCH 07/11] SL-17589 FIXED Camera does not zoom correctly when editing wearables --- indra/newview/llpaneleditwearable.cpp | 3 ++- indra/newview/llviewerwindow.cpp | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/newview/llpaneleditwearable.cpp b/indra/newview/llpaneleditwearable.cpp index be11a4a9f3..6e897e2c7e 100644 --- a/indra/newview/llpaneleditwearable.cpp +++ b/indra/newview/llpaneleditwearable.cpp @@ -1308,7 +1308,8 @@ void LLPanelEditWearable::changeCamera(U8 subpart) gMorphView->setCameraOffset( subpart_entry->mCameraOffset ); if (gSavedSettings.getBOOL("AppearanceCameraMovement")) { - gMorphView->updateCamera(); + gAgentCamera.setFocusOnAvatar(FALSE, FALSE); + gMorphView->updateCamera(); } } diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 8e565bbdca..f4a0b80548 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -103,7 +103,6 @@ #include "llfilepicker.h" #include "llfirstuse.h" #include "llfloater.h" -#include "llfloaterbuildoptions.h" #include "llfloaterbuyland.h" #include "llfloatercamera.h" #include "llfloaterland.h" From 0c169dd37a82266d42b728ca6d5a7c20ee1ab208 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 21 Jun 2022 23:24:14 +0300 Subject: [PATCH 08/11] SL-17590 Telehub beacon sometimes renders black --- indra/newview/pipeline.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index da16c8209f..bd6df4ec37 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -3939,7 +3939,7 @@ void LLPipeline::postSort(LLCamera& camera) } LL_PUSH_CALLSTACKS(); // If managing your telehub, draw beacons at telehub and currently selected spawnpoint. - if (LLFloaterTelehub::renderBeacons()) + if (LLFloaterTelehub::renderBeacons() && !sShadowRender) { LLFloaterTelehub::addBeacons(); } From 48d2836ce6277f41c172ae5b68f2cb8845df7ad1 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Fri, 20 May 2022 23:19:26 +0300 Subject: [PATCH 09/11] SL-17436 Tools floater drops negative Z position for attachments --- indra/newview/llpanelobject.cpp | 10 +++++----- indra/newview/skins/default/xui/en/floater_tools.xml | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/indra/newview/llpanelobject.cpp b/indra/newview/llpanelobject.cpp index 3e6697b3c1..1fd1784d4b 100644 --- a/indra/newview/llpanelobject.cpp +++ b/indra/newview/llpanelobject.cpp @@ -1661,13 +1661,13 @@ void LLPanelObject::sendPosition(BOOL btn_down) if (!regionp) return; - // Clamp the Z height - const F32 height = newpos.mV[VZ]; - const F32 min_height = LLWorld::getInstance()->getMinAllowedZ(mObject, mObject->getPositionGlobal()); - const F32 max_height = LLWorld::getInstance()->getRegionMaxHeight(); - if (!mObject->isAttachment()) { + // Clamp the Z height + const F32 height = newpos.mV[VZ]; + const F32 min_height = LLWorld::getInstance()->getMinAllowedZ(mObject, mObject->getPositionGlobal()); + const F32 max_height = LLWorld::getInstance()->getRegionMaxHeight(); + if ( height < min_height) { newpos.mV[VZ] = min_height; diff --git a/indra/newview/skins/default/xui/en/floater_tools.xml b/indra/newview/skins/default/xui/en/floater_tools.xml index f5214420b4..ade79b8884 100644 --- a/indra/newview/skins/default/xui/en/floater_tools.xml +++ b/indra/newview/skins/default/xui/en/floater_tools.xml @@ -1505,6 +1505,7 @@ even though the user gets a free copy. layout="topleft" left_delta="0" max_val="4096" + min_val="-32" name="Pos Z" text_enabled_color="0 0.8 1 .65" top_pad="3" From 10ab905c96bf979827951146d22e98214bf5a310 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Sat, 25 Jun 2022 00:30:51 +0300 Subject: [PATCH 10/11] SL-17628 Added attachments can be moved past limit Support 'button up' when losing focus When releasing button far out of view or by refocusing something, button was not commiting. LLPanelObject last call was sendPosition(btn_down) with btn_down true which resulted in missed update. Now it will get one more call with btn_down==false. --- indra/llui/llbutton.cpp | 20 ++++++++++++++++++-- indra/llui/llbutton.h | 2 ++ indra/llui/llspinctrl.cpp | 2 ++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp index 0e59fdf519..8028f397f3 100644 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -102,6 +102,7 @@ LLButton::Params::Params() scale_image("scale_image", true), hover_glow_amount("hover_glow_amount"), commit_on_return("commit_on_return", true), + commit_on_capture_lost("commit_on_capture_lost", false), display_pressed_state("display_pressed_state", true), use_draw_context_alpha("use_draw_context_alpha", true), badge("badge"), @@ -165,6 +166,7 @@ LLButton::LLButton(const LLButton::Params& p) mBottomVPad(p.pad_bottom), mHoverGlowStrength(p.hover_glow_amount), mCommitOnReturn(p.commit_on_return), + mCommitOnCaptureLost(p.commit_on_capture_lost), mFadeWhenDisabled(FALSE), mForcePressedState(false), mDisplayPressedState(p.display_pressed_state), @@ -475,6 +477,10 @@ BOOL LLButton::handleMouseUp(S32 x, S32 y, MASK mask) // We only handle the click if the click both started and ended within us if( hasMouseCapture() ) { + // reset timers before focus change, to not cause + // additional commits if mCommitOnCaptureLost. + resetMouseDownTimer(); + // Always release the mouse gFocusMgr.setMouseCapture( NULL ); @@ -489,8 +495,6 @@ BOOL LLButton::handleMouseUp(S32 x, S32 y, MASK mask) // Regardless of where mouseup occurs, handle callback if(mMouseUpSignal) (*mMouseUpSignal)(this, LLSD()); - resetMouseDownTimer(); - // DO THIS AT THE VERY END to allow the button to be destroyed as a result of being clicked. // If mouseup in the widget, it's been clicked if (pointInView(x, y)) @@ -1195,6 +1199,18 @@ void LLButton::setImageOverlay(const LLUUID& image_id, LLFontGL::HAlign alignmen void LLButton::onMouseCaptureLost() { + if (mCommitOnCaptureLost + && mMouseDownTimer.getStarted()) + { + if (mMouseUpSignal) (*mMouseUpSignal)(this, LLSD()); + + if (mIsToggle) + { + toggleState(); + } + + LLUICtrl::onCommit(); + } resetMouseDownTimer(); } diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h index 572d36996c..ccd31e90c0 100644 --- a/indra/llui/llbutton.h +++ b/indra/llui/llbutton.h @@ -124,6 +124,7 @@ public: Optional is_toggle, scale_image, commit_on_return, + commit_on_capture_lost, display_pressed_state; Optional hover_glow_amount; @@ -374,6 +375,7 @@ protected: F32 mCurGlowStrength; bool mCommitOnReturn; + bool mCommitOnCaptureLost; bool mFadeWhenDisabled; bool mForcePressedState; bool mDisplayPressedState; diff --git a/indra/llui/llspinctrl.cpp b/indra/llui/llspinctrl.cpp index ee78b82429..ef7c8ec012 100644 --- a/indra/llui/llspinctrl.cpp +++ b/indra/llui/llspinctrl.cpp @@ -103,6 +103,7 @@ LLSpinCtrl::LLSpinCtrl(const LLSpinCtrl::Params& p) up_button_params.rect = LLRect(btn_left, getRect().getHeight(), btn_right, getRect().getHeight() - spinctrl_btn_height); up_button_params.click_callback.function(boost::bind(&LLSpinCtrl::onUpBtn, this, _2)); up_button_params.mouse_held_callback.function(boost::bind(&LLSpinCtrl::onUpBtn, this, _2)); + up_button_params.commit_on_capture_lost = true; mUpBtn = LLUICtrlFactory::create(up_button_params); addChild(mUpBtn); @@ -111,6 +112,7 @@ LLSpinCtrl::LLSpinCtrl(const LLSpinCtrl::Params& p) down_button_params.rect = LLRect(btn_left, getRect().getHeight() - spinctrl_btn_height, btn_right, getRect().getHeight() - 2 * spinctrl_btn_height); down_button_params.click_callback.function(boost::bind(&LLSpinCtrl::onDownBtn, this, _2)); down_button_params.mouse_held_callback.function(boost::bind(&LLSpinCtrl::onDownBtn, this, _2)); + down_button_params.commit_on_capture_lost = true; mDownBtn = LLUICtrlFactory::create(down_button_params); addChild(mDownBtn); From 8488d8600905a8f5f8e80513e39a67dce7a8cc84 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Thu, 30 Jun 2022 13:22:19 +0300 Subject: [PATCH 11/11] SL-17668 Add operation "show in inventory" on in-world avatar attachment --- indra/newview/llviewermenu.cpp | 27 +++++++++++++++++++ .../default/xui/en/menu_attachment_self.xml | 7 +++++ 2 files changed, 34 insertions(+) diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 8522c2ed55..459bbaa00a 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -2725,6 +2725,32 @@ void handle_object_touch() send_ObjectDeGrab_message(object, pick); } +void handle_object_show_original() +{ + LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if (!object) + { + return; + } + + LLViewerObject *parent = (LLViewerObject*)object->getParent(); + while (parent) + { + if(parent->isAvatar()) + { + break; + } + object = parent; + parent = (LLViewerObject*)parent->getParent(); + } + + if (!object || object->isAvatar()) + { + return; + } + + show_item_original(object->getAttachmentItemID()); +} static void init_default_item_label(const std::string& item_name) @@ -9510,6 +9536,7 @@ void initialize_menus() // Object pie menu view_listener_t::addMenu(new LLObjectBuild(), "Object.Build"); commit.add("Object.Touch", boost::bind(&handle_object_touch)); + commit.add("Object.ShowOriginal", boost::bind(&handle_object_show_original)); commit.add("Object.SitOrStand", boost::bind(&handle_object_sit_or_stand)); commit.add("Object.Delete", boost::bind(&handle_object_delete)); view_listener_t::addMenu(new LLObjectAttachToAvatar(true), "Object.AttachToAvatar"); diff --git a/indra/newview/skins/default/xui/en/menu_attachment_self.xml b/indra/newview/skins/default/xui/en/menu_attachment_self.xml index 26b1c86c53..3b91b9df7a 100644 --- a/indra/newview/skins/default/xui/en/menu_attachment_self.xml +++ b/indra/newview/skins/default/xui/en/menu_attachment_self.xml @@ -33,6 +33,13 @@ function="Object.EnableTouch" name="EnableTouch"/> + + +