diff options
author | Andrey Kleshchev <andreykproductengine@lindenlab.com> | 2021-11-29 20:33:13 +0200 |
---|---|---|
committer | Andrey Kleshchev <andreykproductengine@lindenlab.com> | 2021-11-29 20:33:13 +0200 |
commit | 27e4e245d948e4d11f78a272d969b533cfe65fbf (patch) | |
tree | eeef5d10decb9caa671eb8d9e42b02a4c11a6b72 /indra | |
parent | 7a0ca277f82acd2511a5dbf33956c1665d14b957 (diff) | |
parent | cba1daaf6be4dfd031cd10d9aea07c7124d32726 (diff) |
Merge branch 'DRTVWR-542-meshopt' into DRTVWR-546
Diffstat (limited to 'indra')
24 files changed, 983 insertions, 404 deletions
diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index bc2ee2e6cd..345e96b943 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -38,6 +38,7 @@ add_subdirectory(${LIBS_OPEN_PREFIX}llkdu) add_subdirectory(${LIBS_OPEN_PREFIX}llimagej2coj) add_subdirectory(${LIBS_OPEN_PREFIX}llinventory) add_subdirectory(${LIBS_OPEN_PREFIX}llmath) +add_subdirectory(${LIBS_OPEN_PREFIX}llmeshoptimizer) add_subdirectory(${LIBS_OPEN_PREFIX}llmessage) add_subdirectory(${LIBS_OPEN_PREFIX}llprimitive) add_subdirectory(${LIBS_OPEN_PREFIX}llrender) diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index cca305c741..78d8652394 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -40,7 +40,6 @@ set(cmake_SOURCE_FILES FreeType.cmake GLEXT.cmake GLH.cmake - GLOD.cmake ## GStreamer010Plugin.cmake GoogleMock.cmake Havok.cmake @@ -59,6 +58,7 @@ set(cmake_SOURCE_FILES LLKDU.cmake LLLogin.cmake LLMath.cmake + LLMeshOptimizer.cmake LLMessage.cmake LLPhysicsExtensions.cmake LLPlugin.cmake @@ -72,6 +72,7 @@ set(cmake_SOURCE_FILES LLXML.cmake Linking.cmake MediaPluginBase.cmake + MESHOPTIMIZER.cmake NDOF.cmake OPENAL.cmake OpenGL.cmake diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index 1c56f49486..b408702f04 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -57,7 +57,6 @@ if(WINDOWS) libaprutil-1.dll libapriconv-1.dll nghttp2.dll - glod.dll libhunspell.dll uriparser.dll ) @@ -168,7 +167,6 @@ elseif(DARWIN) libaprutil-1.0.dylib libaprutil-1.dylib ${EXPAT_COPY} - libGLOD.dylib libhunspell-1.3.0.dylib libndofdev.dylib libnghttp2.dylib @@ -217,7 +215,6 @@ elseif(LINUX) ${EXPAT_COPY} libfreetype.so.6.6.2 libfreetype.so.6 - libGLOD.so libgmodule-2.0.so libgobject-2.0.so libhunspell-1.3.so.0.0.0 diff --git a/indra/cmake/GLOD.cmake b/indra/cmake/GLOD.cmake deleted file mode 100644 index a347eb6fee..0000000000 --- a/indra/cmake/GLOD.cmake +++ /dev/null @@ -1,9 +0,0 @@ -# -*- cmake -*- -include(Prebuilt) - -if (NOT USESYSTEMLIBS) - use_prebuilt_binary(glod) -endif (NOT USESYSTEMLIBS) - -set(GLOD_INCLUDE_DIR ${LIBS_PREBUILT_DIR}/include) -set(GLOD_LIBRARIES GLOD) diff --git a/indra/cmake/LLMeshOptimizer.cmake b/indra/cmake/LLMeshOptimizer.cmake new file mode 100644 index 0000000000..b79944f618 --- /dev/null +++ b/indra/cmake/LLMeshOptimizer.cmake @@ -0,0 +1,7 @@ +# -*- cmake -*- + +set(LLMESHOPTIMIZER_INCLUDE_DIRS + ${LIBS_OPEN_DIR}/llmeshoptimizer + ) + +set(LLMESHOPTIMIZER_LIBRARIES llmeshoptimizer) diff --git a/indra/cmake/MESHOPTIMIZER.cmake b/indra/cmake/MESHOPTIMIZER.cmake new file mode 100644 index 0000000000..1c5b47b9bd --- /dev/null +++ b/indra/cmake/MESHOPTIMIZER.cmake @@ -0,0 +1,16 @@ +# -*- cmake -*- + +include(Linking) +include(Prebuilt) + +use_prebuilt_binary(meshoptimizer) + +if (WINDOWS) + set(MESHOPTIMIZER_LIBRARIES meshoptimizer.lib) +elseif (LINUX) + set(MESHOPTIMIZER_LIBRARIES meshoptimizer.o) +elseif (DARWIN) + set(MESHOPTIMIZER_LIBRARIES libmeshoptimizer.a) +endif (WINDOWS) + +set(MESHOPTIMIZER_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include/meshoptimizer) diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp index 130f30bedc..c226315e5c 100644 --- a/indra/llmath/llvolume.cpp +++ b/indra/llmath/llvolume.cpp @@ -6348,9 +6348,9 @@ void LLVolumeFace::resizeVertices(S32 num_verts) if (num_verts) { //pad texture coordinate block end to allow for QWORD reads - S32 size = ((num_verts*sizeof(LLVector2)) + 0xF) & ~0xF; + S32 tc_size = ((num_verts*sizeof(LLVector2)) + 0xF) & ~0xF; - mPositions = (LLVector4a*) ll_aligned_malloc<64>(sizeof(LLVector4a)*2*num_verts+size); + mPositions = (LLVector4a*) ll_aligned_malloc<64>(sizeof(LLVector4a)*2*num_verts+tc_size); mNormals = mPositions+num_verts; mTexCoords = (LLVector2*) (mNormals+num_verts); diff --git a/indra/llmath/llvolume.h b/indra/llmath/llvolume.h index a77e8c08c6..c0b224b1ff 100644 --- a/indra/llmath/llvolume.h +++ b/indra/llmath/llvolume.h @@ -936,17 +936,23 @@ public: LLVector4a* mCenter; LLVector2 mTexCoordExtents[2]; //minimum and maximum of texture coordinates of the face. - S32 mNumVertices; + S32 mNumVertices; // num vertices == num normals == num texcoords S32 mNumAllocatedVertices; S32 mNumIndices; - LLVector4a* mPositions; - LLVector4a* mNormals; + LLVector4a* mPositions; // Contains vertices, nortmals and texcoords + LLVector4a* mNormals; // pointer into mPositions LLVector4a* mTangents; - LLVector2* mTexCoords; + LLVector2* mTexCoords; // pointer into mPositions + + // mIndices contains mNumIndices amount of elements. + // It contains triangles, each 3 indices describe one triangle. + // If mIndices contains {0, 2, 3, 1, 2, 4}, it means there + // are two triangles {0, 2, 3} and {1, 2, 4} with values being + // indexes for mPositions/mNormals/mTexCoords U16* mIndices; - //vertex buffer filled in by LLFace to cache this volume face geometry in vram + // vertex buffer filled in by LLFace to cache this volume face geometry in vram // (declared as a LLPointer to LLRefCount to avoid dependency on LLVertexBuffer) mutable LLPointer<LLRefCount> mVertexBuffer; diff --git a/indra/llmeshoptimizer/CMakeLists.txt b/indra/llmeshoptimizer/CMakeLists.txt new file mode 100644 index 0000000000..016794cfad --- /dev/null +++ b/indra/llmeshoptimizer/CMakeLists.txt @@ -0,0 +1,44 @@ +# -*- cmake -*- + +project(llmeshoptimizer) + +include(MESHOPTIMIZER) + +include(00-Common) +include(LLCommon) +include(LLMath) + +include_directories( + ${LLCOMMON_INCLUDE_DIRS} + ${LLMATH_INCLUDE_DIRS} + ${LLMESHOPTIMIZER_INCLUDE_DIR} + ${MESHOPTIMIZER_INCLUDE_DIRS} + ${LIBS_PREBUILT_DIR}/include #access to boost headers, needed for LLError + ) + +set(llmeshoptimizer_SOURCE_FILES + llmeshoptimizer.cpp + ) + +set(llmeshoptimizer_HEADER_FILES + CMakeLists.txt + + llmeshoptimizer.h + ) + +set_source_files_properties(${llmeshoptimizer_HEADER_FILES} + PROPERTIES HEADER_FILE_ONLY TRUE) + +list(APPEND llmeshoptimizer_SOURCE_FILES ${llmeshoptimizer_HEADER_FILES}) + +#if (USE_MESHOPT) + add_library (llmeshoptimizer ${llmeshoptimizer_SOURCE_FILES}) + + target_link_libraries(llmeshoptimizer + ${LLCOMMON_LIBRARIES} + ${LLMATH_LIBRARIES} + ${MESHOPTIMIZER_LIBRARIES}) + + # Add tests + +#endif (USE_MESHOPT) diff --git a/indra/llmeshoptimizer/llmeshoptimizer.cpp b/indra/llmeshoptimizer/llmeshoptimizer.cpp new file mode 100644 index 0000000000..a879389c5a --- /dev/null +++ b/indra/llmeshoptimizer/llmeshoptimizer.cpp @@ -0,0 +1,142 @@ + /** +* @file llmeshoptimizer.cpp +* @brief Wrapper around meshoptimizer +* +* $LicenseInfo:firstyear=2021&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2021, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llmeshoptimizer.h" + +#include "meshoptimizer.h" + +LLMeshOptimizer::LLMeshOptimizer() +{ + // Todo: Looks like for memory management, we can add allocator and deallocator callbacks + // Should be one time + // meshopt_setAllocator(allocate, deallocate); +} + +LLMeshOptimizer::~LLMeshOptimizer() +{ +} + +//static +void LLMeshOptimizer::generateShadowIndexBuffer(U16 *destination, + const U16 *indices, + U64 index_count, + const LLVector4a *vertex_positions, + U64 vertex_count, + U64 vertex_positions_stride +) +{ + meshopt_generateShadowIndexBuffer<unsigned short>(destination, + indices, + index_count, + (const float*)vertex_positions, // verify that it is correct to convert to float + vertex_count, + sizeof(LLVector4a), + vertex_positions_stride + ); +} + +//static +U64 LLMeshOptimizer::simplifyU32(U32 *destination, + const U32 *indices, + U64 index_count, + const LLVector4a *vertex_positions, + U64 vertex_count, + U64 vertex_positions_stride, + U64 target_index_count, + F32 target_error, + bool sloppy, + F32* result_error +) +{ + if (sloppy) + { + return meshopt_simplifySloppy<unsigned int>(destination, + indices, + index_count, + (const float*)vertex_positions, + vertex_count, + vertex_positions_stride, + target_index_count, + target_error, + result_error + ); + } + else + { + return meshopt_simplify<unsigned int>(destination, + indices, + index_count, + (const float*)vertex_positions, + vertex_count, + vertex_positions_stride, + target_index_count, + target_error, + result_error + ); + } +} + +//static +U64 LLMeshOptimizer::simplify(U16 *destination, + const U16 *indices, + U64 index_count, + const LLVector4a *vertex_positions, + U64 vertex_count, + U64 vertex_positions_stride, + U64 target_index_count, + F32 target_error, + bool sloppy, + F32* result_error + ) +{ + if (sloppy) + { + return meshopt_simplifySloppy<unsigned short>(destination, + indices, + index_count, + (const float*)vertex_positions, + vertex_count, + vertex_positions_stride, + target_index_count, + target_error, + result_error + ); + } + else + { + return meshopt_simplify<unsigned short>(destination, + indices, + index_count, + (const float*)vertex_positions, + vertex_count, + vertex_positions_stride, + target_index_count, + target_error, + result_error + ); + } +} + diff --git a/indra/llmeshoptimizer/llmeshoptimizer.h b/indra/llmeshoptimizer/llmeshoptimizer.h new file mode 100644 index 0000000000..e8dd16dae9 --- /dev/null +++ b/indra/llmeshoptimizer/llmeshoptimizer.h @@ -0,0 +1,83 @@ +/** +* @file llmeshoptimizer.h +* @brief Wrapper around meshoptimizer +* +* $LicenseInfo:firstyear=2021&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2021, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LLMESHOPTIMIZER_H +#define LLMESHOPTIMIZER_H + +#include "linden_common.h" + +#include "llmath.h" + +class LLMeshOptimizer +{ +public: + LLMeshOptimizer(); + ~LLMeshOptimizer(); + + static void generateShadowIndexBuffer( + U16 *destination, + const U16 *indices, + U64 index_count, + const LLVector4a *vertex_positions, + U64 vertex_count, + U64 vertex_positions_stride); + + // returns amount of indices in destiantion + // sloppy engages a variant of a mechanizm that does not respect topology as much + // but is much more efective for simpler models + // result_error returns how far from original the model is in % if not NULL + // Works with U32 indices (LLFace uses U16 indices) + static U64 simplifyU32( + U32 *destination, + const U32 *indices, + U64 index_count, + const LLVector4a *vertex_positions, + U64 vertex_count, + U64 vertex_positions_stride, + U64 target_index_count, + F32 target_error, + bool sloppy, + F32* result_error); + + // Returns amount of indices in destiantion + // sloppy engages a variant of a mechanizm that does not respect topology as much + // but is much better for simpler models + // result_error returns how far from original the model is in % if not NULL + // Meant for U16 indices (LLFace uses U16 indices) + static U64 simplify( + U16 *destination, + const U16 *indices, + U64 index_count, + const LLVector4a *vertex_positions, + U64 vertex_count, + U64 vertex_positions_stride, + U64 target_index_count, + F32 target_error, + bool sloppy, + F32* result_error); +private: +}; + +#endif //LLMESHOPTIMIZER_H diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index 8343de0cbc..4b13da5586 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -149,7 +149,11 @@ bool get_dom_sources(const domInputLocalOffset_Array& inputs, S32& pos_offset, S return true; } -LLModel::EModelStatus load_face_from_dom_triangles(std::vector<LLVolumeFace>& face_list, std::vector<std::string>& materials, domTrianglesRef& tri) +LLModel::EModelStatus load_face_from_dom_triangles( + std::vector<LLVolumeFace>& face_list, + std::vector<std::string>& materials, + domTrianglesRef& tri, + LLSD& log_msg) { LLVolumeFace face; std::vector<LLVolumeFace::VertexData> verts; @@ -169,12 +173,18 @@ LLModel::EModelStatus load_face_from_dom_triangles(std::vector<LLVolumeFace>& fa if ( !get_dom_sources(inputs, pos_offset, tc_offset, norm_offset, idx_stride, pos_source, tc_source, norm_source)) { + LLSD args; + args["Message"] = "ParsingErrorBadElement"; + log_msg.append(args); return LLModel::BAD_ELEMENT; } if (!pos_source || !pos_source->getFloat_array()) { LL_WARNS() << "Unable to process mesh without position data; invalid model; invalid model." << LL_ENDL; + LLSD args; + args["Message"] = "ParsingErrorPositionInvalidModel"; + log_msg.append(args); return LLModel::BAD_ELEMENT; } @@ -343,7 +353,11 @@ LLModel::EModelStatus load_face_from_dom_triangles(std::vector<LLVolumeFace>& fa return LLModel::NO_ERRORS ; } -LLModel::EModelStatus load_face_from_dom_polylist(std::vector<LLVolumeFace>& face_list, std::vector<std::string>& materials, domPolylistRef& poly, LLSD& log_msg) +LLModel::EModelStatus load_face_from_dom_polylist( + std::vector<LLVolumeFace>& face_list, + std::vector<std::string>& materials, + domPolylistRef& poly, + LLSD& log_msg) { domPRef p = poly->getP(); domListOfUInts& idx = p->getValue(); @@ -370,6 +384,10 @@ LLModel::EModelStatus load_face_from_dom_polylist(std::vector<LLVolumeFace>& fac if (!get_dom_sources(inputs, pos_offset, tc_offset, norm_offset, idx_stride, pos_source, tc_source, norm_source)) { + LL_WARNS() << "Bad element." << LL_ENDL; + LLSD args; + args["Message"] = "ParsingErrorBadElement"; + log_msg.append(args); return LLModel::BAD_ELEMENT; } @@ -421,6 +439,9 @@ LLModel::EModelStatus load_face_from_dom_polylist(std::vector<LLVolumeFace>& fac if (!cv.getPosition().isFinite3()) { LL_WARNS() << "Found NaN while loading position data from DAE-Model, invalid model." << LL_ENDL; + LLSD args; + args["Message"] = "PositionNaN"; + log_msg.append(args); return LLModel::BAD_ELEMENT; } } @@ -453,6 +474,10 @@ LLModel::EModelStatus load_face_from_dom_polylist(std::vector<LLVolumeFace>& fac if (!cv.getNormal().isFinite3()) { LL_WARNS() << "Found NaN while loading normals from DAE-Model, invalid model." << LL_ENDL; + LLSD args; + args["Message"] = "NormalsNaN"; + log_msg.append(args); + return LLModel::BAD_ELEMENT; } } @@ -770,6 +795,9 @@ LLModel::EModelStatus load_face_from_dom_polygons(std::vector<LLVolumeFace>& fac } } + // Viewer can only fit U16 vertices, shouldn't we do some checks here and return overflow if result has more? + llassert(vert_idx.size() < U16_MAX); + //build vertex array from map std::vector<LLVolumeFace::VertexData> new_verts; new_verts.resize(vert_idx.size()); @@ -787,7 +815,12 @@ LLModel::EModelStatus load_face_from_dom_polygons(std::vector<LLVolumeFace>& fac for (U32 i = 0; i < verts.size(); ++i) { indices[i] = vert_idx[verts[i]]; - llassert(!i || (indices[i-1] != indices[i])); + if (i % 3 != 0) // assumes GL_TRIANGLES, compare 0-1, 1-2, 3-4, 4-5 but not 2-3 or 5-6 + { + // A faulty degenerate triangle detection (triangle with 0 area), + // probably should be a warning and not an assert + llassert(!i || (indices[i-1] != indices[i])); + } } // DEBUG just build an expanded triangle list @@ -907,6 +940,9 @@ bool LLDAELoader::OpenFile(const std::string& filename) if (!dom) { LL_INFOS() <<" Error with dae - traditionally indicates a corrupt file."<<LL_ENDL; + LLSD args; + args["Message"] = "ParsingErrorCorrupt"; + mWarningsArray.append(args); setLoadState( ERROR_PARSING ); return false; } @@ -934,6 +970,9 @@ bool LLDAELoader::OpenFile(const std::string& filename) if (!doc) { LL_WARNS() << "can't find internal doc" << LL_ENDL; + LLSD args; + args["Message"] = "ParsingErrorNoDoc"; + mWarningsArray.append(args); return false; } @@ -941,6 +980,9 @@ bool LLDAELoader::OpenFile(const std::string& filename) if (!root) { LL_WARNS() << "document has no root" << LL_ENDL; + LLSD args; + args["Message"] = "ParsingErrorNoRoot"; + mWarningsArray.append(args); return false; } @@ -956,6 +998,9 @@ bool LLDAELoader::OpenFile(const std::string& filename) if (!result) { LL_INFOS() << "Could not verify controller" << LL_ENDL; + LLSD args; + args["Message"] = "ParsingErrorBadElement"; + mWarningsArray.append(args); setLoadState( ERROR_PARSING ); return true; } @@ -1089,6 +1134,9 @@ bool LLDAELoader::OpenFile(const std::string& filename) if (!scene) { LL_WARNS() << "document has no visual_scene" << LL_ENDL; + LLSD args; + args["Message"] = "ParsingErrorNoScene"; + mWarningsArray.append(args); setLoadState( ERROR_PARSING ); return true; } @@ -1097,11 +1145,14 @@ bool LLDAELoader::OpenFile(const std::string& filename) bool badElement = false; - processElement( scene, badElement, &dae ); + processElement( scene, badElement, &dae); if ( badElement ) { LL_INFOS()<<"Scene could not be parsed"<<LL_ENDL; + LLSD args; + args["Message"] = "ParsingErrorCantParseScene"; + mWarningsArray.append(args); setLoadState( ERROR_PARSING ); } @@ -1942,7 +1993,7 @@ daeElement* LLDAELoader::getChildFromElement( daeElement* pElement, std::string return NULL; } -void LLDAELoader::processElement( daeElement* element, bool& badElement, DAE* dae ) +void LLDAELoader::processElement( daeElement* element, bool& badElement, DAE* dae) { LLMatrix4 saved_transform; bool pushed_mat = false; @@ -2036,6 +2087,11 @@ void LLDAELoader::processElement( daeElement* element, bool& badElement, DAE* da if (mTransform.determinant() < 0) { //negative scales are not supported LL_INFOS() << "Negative scale detected, unsupported transform. domInstance_geometry: " << getElementLabel(instance_geo) << LL_ENDL; + LLSD args; + args["Message"] = "NegativeScaleTrans"; + args["LABEL"] = getElementLabel(instance_geo); + mWarningsArray.append(args); + badElement = true; } @@ -2059,6 +2115,10 @@ void LLDAELoader::processElement( daeElement* element, bool& badElement, DAE* da if (transformation.determinant() < 0) { //negative scales are not supported LL_INFOS() << "Negative scale detected, unsupported post-normalization transform. domInstance_geometry: " << getElementLabel(instance_geo) << LL_ENDL; + LLSD args; + args["Message"] = "NegativeScaleNormTrans"; + args["LABEL"] = getElementLabel(instance_geo); + mWarningsArray.append(args); badElement = true; } @@ -2100,6 +2160,9 @@ void LLDAELoader::processElement( daeElement* element, bool& badElement, DAE* da else { LL_INFOS()<<"Unable to resolve geometry URL."<<LL_ENDL; + LLSD args; + args["Message"] = "CantResolveGeometryUrl"; + mWarningsArray.append(args); badElement = true; } @@ -2390,7 +2453,7 @@ bool LLDAELoader::addVolumeFacesFromDomMesh(LLModel* pModel,domMesh* mesh, LLSD& { domTrianglesRef& tri = tris.get(i); - status = load_face_from_dom_triangles(pModel->getVolumeFaces(), pModel->getMaterialList(), tri); + status = load_face_from_dom_triangles(pModel->getVolumeFaces(), pModel->getMaterialList(), tri, log_msg); pModel->mStatus = status; if(status != LLModel::NO_ERRORS) { @@ -2417,6 +2480,7 @@ bool LLDAELoader::addVolumeFacesFromDomMesh(LLModel* pModel,domMesh* mesh, LLSD& for (U32 i = 0; i < polygons.getCount(); ++i) { domPolygonsRef& poly = polygons.get(i); + status = load_face_from_dom_polygons(pModel->getVolumeFaces(), pModel->getMaterialList(), poly); if(status != LLModel::NO_ERRORS) diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index 2d27592bc8..3881b1338c 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -291,6 +291,8 @@ public: EModelStatus mStatus ; + // A model/object can only have 8 faces, spillover faces will + // be moved to new model/object and assigned a submodel id. int mSubmodelID; } LL_ALIGN_POSTFIX(16); diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 5f085bb9ad..4a7f17f15b 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -16,7 +16,6 @@ include(DBusGlib) include(DragDrop) include(EXPAT) include(FMODSTUDIO) -include(GLOD) include(Hunspell) include(JsonCpp) include(LLAppearance) @@ -31,6 +30,7 @@ include(LLInventory) include(LLKDU) include(LLLogin) include(LLMath) +include(LLMeshOptimizer) include(LLMessage) include(LLPhysicsExtensions) include(LLPlugin) @@ -68,7 +68,6 @@ endif(FMODSTUDIO) include_directories( ${DBUSGLIB_INCLUDE_DIRS} ${JSONCPP_INCLUDE_DIR} - ${GLOD_INCLUDE_DIR} ${LLAUDIO_INCLUDE_DIRS} ${LLCHARACTER_INCLUDE_DIRS} ${LLCOMMON_INCLUDE_DIRS} @@ -78,6 +77,7 @@ include_directories( ${LLKDU_INCLUDE_DIRS} ${LLINVENTORY_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} + ${LLMESHOPTIMIZER_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} ${LLPLUGIN_INCLUDE_DIRS} ${LLPRIMITIVE_INCLUDE_DIRS} @@ -1813,9 +1813,6 @@ if (WINDOWS) ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}/libapr-1.dll ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}/libaprutil-1.dll ${SHARED_LIB_STAGING_DIR}/${CMAKE_CFG_INTDIR}/libapriconv-1.dll - ${SHARED_LIB_STAGING_DIR}/Release/glod.dll - ${SHARED_LIB_STAGING_DIR}/RelWithDebInfo/glod.dll - ${SHARED_LIB_STAGING_DIR}/Debug/glod.dll ${SHARED_LIB_STAGING_DIR}/Release/libcollada14dom22.dll ${SHARED_LIB_STAGING_DIR}/RelWithDebInfo/libcollada14dom22.dll ${SHARED_LIB_STAGING_DIR}/Debug/libcollada14dom22-d.dll @@ -2027,6 +2024,7 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${LLCHARACTER_LIBRARIES} ${LLIMAGE_LIBRARIES} ${LLINVENTORY_LIBRARIES} + ${LLMESHOPTIMIZER_LIBRARIES} ${LLMESSAGE_LIBRARIES} ${LLPLUGIN_LIBRARIES} ${LLPRIMITIVE_LIBRARIES} @@ -2050,7 +2048,6 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${DBUSGLIB_LIBRARIES} ${OPENGL_LIBRARIES} ${FMODWRAPPER_LIBRARY} # must come after LLAudio - ${GLOD_LIBRARIES} ${OPENGL_LIBRARIES} ${JSONCPP_LIBRARIES} ${SDL_LIBRARY} diff --git a/indra/newview/licenses-mac.txt b/indra/newview/licenses-mac.txt index d0747ccd03..fba6a55da3 100644 --- a/indra/newview/licenses-mac.txt +++ b/indra/newview/licenses-mac.txt @@ -693,3 +693,29 @@ From Vivox: Attn: customer support 40 Speen Street Suite 402 Framingham, MA 01701 + + +============= +meshoptimizer +============= +MIT License + +Copyright (c) 2016-2021 Arseny Kapoulkine + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/indra/newview/licenses-win32.txt b/indra/newview/licenses-win32.txt index 98edd35bea..837d92139d 100644 --- a/indra/newview/licenses-win32.txt +++ b/indra/newview/licenses-win32.txt @@ -770,71 +770,29 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ============= -GLOD license +meshoptimizer ============= -The GLOD Open-Source License Version 1.0 June 16, 2004 +MIT License -Copyright (C) 2003-04 Jonathan Cohen, Nat Duca, Chris Niski, Johns -Hopkins University and David Luebke, Brenden Schubert, University of -Virginia. All rights reserved. +Copyright (c) 2016-2021 Arseny Kapoulkine -Redistribution and use in source and binary forms, with or without -modification, is permitted provided that the following conditions are -met: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer and - request. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer and - request in the documentation and/or other materials provided with - the distribution. - -3. The name "GLOD" must not be used to endorse or promote products - derived from this software without prior written permission. - -4. Redistributions of any modified version of this source, whether in - source or binary form , must include a form of the following - acknowledgment: "This product is derived from the GLOD library, - which is available from http://www.cs.jhu.edu/~graphics/GLOD." - -5. Redistributions of any modified version of this source in binary - form must provide, free of charge, access to the modified version - of the code. - -6. This license shall be governed by and construed and enforced in - accordance with the laws of the State of Maryland, without - reference to its conflicts of law provisions. The exclusive - jurisdiction and venue for all legal actions relating to this - license shall be in courts of competent subject matter jurisdiction - located in the State of Maryland. - -TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, GLOD IS PROVIDED -UNDER THIS LICENSE ON AN AS IS BASIS, WITHOUT WARRANTY OF ANY KIND, -EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES -THAT GLOD IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR -PURPOSE OR NON-INFRINGING. ALL WARRANTIES ARE DISCLAIMED AND THE -ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE CODE IS WITH -YOU. SHOULD ANY CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE -COPYRIGHT HOLDER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY -NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY -CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY CODE IS -AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. - -TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL -THE COPYRIGHT HOLDER OR ANY OTHER CONTRIBUTOR BE LIABLE FOR ANY -SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES FOR LOSS OF -PROFITS, REVENUE, OR FOR LOSS OF INFORMATION OR ANY OTHER LOSS. - -YOU EXPRESSLY AGREE TO FOREVER INDEMNIFY, DEFEND AND HOLD HARMLESS THE -COPYRIGHT HOLDERS AND CONTRIBUTORS OF GLOD AGAINST ALL CLAIMS, -DEMANDS, SUITS OR OTHER ACTIONS ARISING DIRECTLY OR INDIRECTLY FROM -YOUR ACCEPTANCE AND USE OF GLOD. - -Although NOT REQUIRED, we would appreciate it if active users of GLOD -put a link on their web site to the GLOD web site when possible. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 0e54b66ea9..aec4e4b8d9 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -136,10 +136,10 @@ mAvatarTabIndex(0) mStatusLock = new LLMutex(); mModelPreview = NULL; - mLODMode[LLModel::LOD_HIGH] = 0; + mLODMode[LLModel::LOD_HIGH] = LLModelPreview::LOD_FROM_FILE; for (U32 i = 0; i < LLModel::LOD_HIGH; i++) { - mLODMode[i] = 1; + mLODMode[i] = LLModelPreview::MESH_OPTIMIZER_AUTO; } } @@ -722,7 +722,20 @@ void LLFloaterModelPreview::onAutoFillCommit(LLUICtrl* ctrl, void* userdata) void LLFloaterModelPreview::onLODParamCommit(S32 lod, bool enforce_tri_limit) { - mModelPreview->onLODParamCommit(lod, enforce_tri_limit); + LLComboBox* lod_source_combo = getChild<LLComboBox>("lod_source_" + lod_name[lod]); + S32 mode = lod_source_combo->getCurrentIndex(); + switch (mode) + { + case LLModelPreview::MESH_OPTIMIZER_AUTO: + case LLModelPreview::MESH_OPTIMIZER: + case LLModelPreview::MESH_OPTIMIZER_SLOPPY: + case LLModelPreview::MESH_OPTIMIZER_COMBINE: + mModelPreview->onLODMeshOptimizerParamCommit(lod, enforce_tri_limit, mode); + break; + default: + LL_ERRS() << "Only supposed to be called to generate models" << LL_ENDL; + break; + } //refresh LoDs that reference this one for (S32 i = lod - 1; i >= 0; --i) @@ -1721,7 +1734,11 @@ void LLFloaterModelPreview::onLoDSourceCommit(S32 lod) refresh(); LLComboBox* lod_source_combo = getChild<LLComboBox>("lod_source_" + lod_name[lod]); - if (lod_source_combo->getCurrentIndex() == LLModelPreview::GENERATE) + S32 index = lod_source_combo->getCurrentIndex(); + if (index == LLModelPreview::MESH_OPTIMIZER_AUTO + || index == LLModelPreview::MESH_OPTIMIZER + || index == LLModelPreview::MESH_OPTIMIZER_SLOPPY + || index == LLModelPreview::MESH_OPTIMIZER_COMBINE) { //rebuild LoD to update triangle counts onLODParamCommit(lod, true); } @@ -1752,7 +1769,7 @@ void LLFloaterModelPreview::resetUploadOptions() getChild<LLComboBox>("lod_source_" + lod_name[NUM_LOD - 1])->setCurrentByIndex(LLModelPreview::LOD_FROM_FILE); for (S32 lod = 0; lod < NUM_LOD - 1; ++lod) { - getChild<LLComboBox>("lod_source_" + lod_name[lod])->setCurrentByIndex(LLModelPreview::GENERATE); + getChild<LLComboBox>("lod_source_" + lod_name[lod])->setCurrentByIndex(LLModelPreview::MESH_OPTIMIZER_AUTO); childSetValue("lod_file_" + lod_name[lod], ""); } diff --git a/indra/newview/llfloatermodelpreview.h b/indra/newview/llfloatermodelpreview.h index 8a01b0c307..51f9b3a0e2 100644 --- a/indra/newview/llfloatermodelpreview.h +++ b/indra/newview/llfloatermodelpreview.h @@ -196,9 +196,7 @@ protected: std::map<std::string, bool> mViewOptionDisabled; //store which lod mode each LOD is using - // 0 - load from file - // 1 - auto generate - // 2 - use LoD above + // See eLoDMode S32 mLODMode[4]; LLMutex* mStatusLock; diff --git a/indra/newview/llkeyconflict.cpp b/indra/newview/llkeyconflict.cpp index b6107eeedf..d7a17b237e 100644 --- a/indra/newview/llkeyconflict.cpp +++ b/indra/newview/llkeyconflict.cpp @@ -730,13 +730,19 @@ void LLKeyConflictHandler::resetToDefault(const std::string &control_name, U32 i { return; } + LLKeyConflict &type_data = mControlsMap[control_name]; + if (!type_data.mAssignable) + { + return; + } LLKeyData data = getDefaultControl(control_name, index); - if (data != mControlsMap[control_name].getKeyData(index)) + if (data != type_data.getKeyData(index)) { // reset controls that might have been switched to our current control removeConflicts(data, mControlsMap[control_name].mConflictMask); mControlsMap[control_name].setKeyData(data, index); + mHasUnsavedChanges = true; } } diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 4fce6735e0..d7e9e234ca 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -41,6 +41,7 @@ #include "lliconctrl.h" #include "llmatrix4a.h" #include "llmeshrepository.h" +#include "llmeshoptimizer.h" #include "llrender.h" #include "llsdutil_math.h" #include "llskinningutil.h" @@ -66,7 +67,6 @@ #include "lltabcontainer.h" #include "lltextbox.h" -#include "glod/glod.h" #include <boost/algorithm/string.hpp> bool LLModelPreview::sIgnoreLoadedCallback = false; @@ -89,19 +89,6 @@ static const F32 PREVIEW_ZOOM_LIMIT(10.f); const F32 SKIN_WEIGHT_CAMERA_DISTANCE = 16.f; -BOOL stop_gloderror() -{ - GLuint error = glodGetError(); - - if (error != GLOD_NO_ERROR) - { - LL_WARNS() << "GLOD error detected, cannot generate LOD: " << std::hex << error << LL_ENDL; - return TRUE; - } - - return FALSE; -} - LLViewerFetchedTexture* bindMaterialDiffuseTexture(const LLImportMaterial& material) { LLViewerFetchedTexture *texture = LLViewerTextureManager::getFetchedTexture(material.getDiffuseMap(), FTT_DEFAULT, TRUE, LLGLTexture::BOOST_PREVIEW); @@ -193,6 +180,7 @@ LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp) mPreviewLOD = 0; mModelLoader = NULL; mMaxTriangleLimit = 0; + mMinTriangleLimit = 0; mDirty = false; mGenLOD = false; mLoading = false; @@ -200,10 +188,6 @@ LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp) mLoadState = LLModelLoader::STARTING; mGroup = 0; mLODFrozen = false; - mBuildShareTolerance = 0.f; - mBuildQueueMode = GLOD_QUEUE_GREEDY; - mBuildBorderMode = GLOD_BORDER_UNLOCK; - mBuildOperator = GLOD_OPERATOR_EDGE_COLLAPSE; for (U32 i = 0; i < LLModel::NUM_LODS; ++i) { @@ -211,10 +195,6 @@ LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp) mRequestedCreaseAngle[i] = -1.f; mRequestedLoDMode[i] = 0; mRequestedErrorThreshold[i] = 0.f; - mRequestedBuildOperator[i] = 0; - mRequestedQueueMode[i] = 0; - mRequestedBorderMode[i] = 0; - mRequestedShareTolerance[i] = 0.f; } mViewOption["show_textures"] = false; @@ -224,23 +204,11 @@ LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp) mHasPivot = false; mModelPivot = LLVector3(0.0f, 0.0f, 0.0f); - glodInit(); - createPreviewAvatar(); } LLModelPreview::~LLModelPreview() { - // glod apparently has internal mem alignment issues that are angering - // the heap-check code in windows, these should be hunted down in that - // TP code, if possible - // - // kernel32.dll!HeapFree() + 0x14 bytes - // msvcr100.dll!free(void * pBlock) Line 51 C - // glod.dll!glodGetGroupParameteriv() + 0x119 bytes - // glod.dll!glodShutdown() + 0x77 bytes - // - //glodShutdown(); if (mModelLoader) { mModelLoader->shutdown(); @@ -826,11 +794,6 @@ void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable mLODFile[lod] = filename; - if (lod == LLModel::LOD_HIGH) - { - clearGLODGroup(); - } - std::map<std::string, std::string> joint_alias_map; getJointAliases(joint_alias_map); @@ -931,7 +894,6 @@ void LLModelPreview::clearIncompatible(S32 lod) if (i == LLModel::LOD_HIGH) { mBaseModel = mModel[lod]; - clearGLODGroup(); mBaseScene = mScene[lod]; mVertexBuffer[5].clear(); } @@ -940,23 +902,6 @@ void LLModelPreview::clearIncompatible(S32 lod) } } -void LLModelPreview::clearGLODGroup() -{ - if (mGroup) - { - for (std::map<LLPointer<LLModel>, U32>::iterator iter = mObject.begin(); iter != mObject.end(); ++iter) - { - glodDeleteObject(iter->second); - stop_gloderror(); - } - mObject.clear(); - - glodDeleteGroup(mGroup); - stop_gloderror(); - mGroup = 0; - } -} - void LLModelPreview::loadModelCallback(S32 loaded_lod) { assert_main_thread(); @@ -1108,7 +1053,6 @@ void LLModelPreview::loadModelCallback(S32 loaded_lod) } mBaseModel = mModel[loaded_lod]; - clearGLODGroup(); mBaseScene = mScene[loaded_lod]; mVertexBuffer[5].clear(); @@ -1339,240 +1283,445 @@ void LLModelPreview::restoreNormals() updateStatusMessages(); } -void LLModelPreview::genLODs(S32 which_lod, U32 decimation, bool enforce_tri_limit) +// 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) { - // Allow LoD from -1 to LLModel::LOD_PHYSICS - if (which_lod < -1 || which_lod > LLModel::NUM_LODS - 1) + // Figure out buffer size + S32 size_indices = 0; + S32 size_vertices = 0; + + for (U32 face_idx = 0; face_idx < base_model->getNumVolumeFaces(); ++face_idx) { - std::ostringstream out; - out << "Invalid level of detail: " << which_lod; - LL_WARNS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, false); - assert(which_lod >= -1 && which_lod < LLModel::NUM_LODS); - return; + const LLVolumeFace &face = base_model->getVolumeFace(face_idx); + size_indices += face.mNumIndices; + size_vertices += face.mNumVertices; } - if (mBaseModel.empty()) + // Allocate buffers, note that we are using U32 buffer instead of U16 + U32* combined_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32)); + U32* output_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32)); + + // extra space for normals and text coords + S32 tc_bytes_size = ((size_vertices * sizeof(LLVector2)) + 0xF) & ~0xF; + LLVector4a* combined_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 2 * size_vertices + tc_bytes_size); + LLVector4a* combined_normals = combined_positions + size_vertices; + LLVector2* combined_tex_coords = (LLVector2*)(combined_normals + size_vertices); + + // copy indices and vertices into new buffers + S32 combined_positions_shift = 0; + S32 indices_idx_shift = 0; + S32 combined_indices_shift = 0; + for (U32 face_idx = 0; face_idx < base_model->getNumVolumeFaces(); ++face_idx) { - return; + const LLVolumeFace &face = base_model->getVolumeFace(face_idx); + + // vertices + S32 copy_bytes = face.mNumVertices * sizeof(LLVector4a); + LLVector4a::memcpyNonAliased16((F32*)(combined_positions + combined_positions_shift), (F32*)face.mPositions, copy_bytes); + + // normals + LLVector4a::memcpyNonAliased16((F32*)(combined_normals + combined_positions_shift), (F32*)face.mNormals, copy_bytes); + + // tex coords + copy_bytes = (face.mNumVertices * sizeof(LLVector2) + 0xF) & ~0xF; + LLVector4a::memcpyNonAliased16((F32*)(combined_tex_coords + combined_positions_shift), (F32*)face.mTexCoords, copy_bytes); + + combined_positions_shift += face.mNumVertices; + + // 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]; + + combined_indices[combined_indices_shift] = idx + indices_idx_shift; + combined_indices_shift++; + } + indices_idx_shift += face.mNumVertices; } - LLVertexBuffer::unbind(); + // Now that we have buffers, optimize + S32 target_indices = 0; + F32 result_code = 0; // how far from original the model is, 1 == 100% + S32 new_indices = 0; - bool no_ff = LLGLSLShader::sNoFixedFunction; - LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; - LLGLSLShader::sNoFixedFunction = false; + target_indices = llmax(3, llfloor(size_indices / indices_decimator)); // leave at least one triangle + new_indices = LLMeshOptimizer::simplifyU32( + output_indices, + combined_indices, + size_indices, + combined_positions, + size_vertices, + LLVertexBuffer::sTypeSize[LLVertexBuffer::TYPE_VERTEX], + target_indices, + error_threshold, + sloppy, + &result_code); - if (shader) + + if (result_code < 0) { - shader->unbind(); + LL_WARNS() << "Negative result code from meshoptimizer for model " << target_model->mLabel + << " target Indices: " << target_indices + << " new Indices: " << new_indices + << " original count: " << size_indices << LL_ENDL; } - stop_gloderror(); - static U32 cur_name = 1; - - S32 limit = -1; + // repack back into individual faces - U32 triangle_count = 0; + 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_vertices]; - U32 instanced_triangle_count = 0; + S32 buf_positions_copied = 0; + S32 buf_indices_copied = 0; + indices_idx_shift = 0; - //get the triangle count for the whole scene - for (LLModelLoader::scene::iterator iter = mBaseScene.begin(), endIter = mBaseScene.end(); iter != endIter; ++iter) + // Crude method to copy indices back into face + for (U32 face_idx = 0; face_idx < base_model->getNumVolumeFaces(); ++face_idx) { - for (LLModelLoader::model_instance_list::iterator instance = iter->second.begin(), end_instance = iter->second.end(); instance != end_instance; ++instance) + const LLVolumeFace &face = base_model->getVolumeFace(face_idx); + + // reset data for new run + buf_positions_copied = 0; + buf_indices_copied = 0; + bool copy_triangle = false; + S32 range = indices_idx_shift + face.mNumVertices; + + 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) { - LLModel* mdl = instance->mModel; - if (mdl) + U32 idx = output_indices[i]; + + if ((i % 3) == 0) { - instanced_triangle_count += mdl->getNumTriangles(); + copy_triangle = idx >= indices_idx_shift && idx < range; } - } - } - //get the triangle count for the non-instanced set of models - for (U32 i = 0; i < mBaseModel.size(); ++i) - { - triangle_count += mBaseModel[i]->getNumTriangles(); - } + if (copy_triangle) + { + if (old_to_new_positions_map[idx] == -1) + { + // New position, need to copy it + // Validate size + if (buf_positions_copied >= U16_MAX) + { + // Normally this shouldn't happen since the whole point is to reduce amount of vertices + // but it might happen if user tries to run optimization with too large triangle or error value + // so fallback to 'per face' mode or verify requested limits and copy base model as is. + 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 + << " original count: " << size_indices + << " error treshold: " << error_threshold + << LL_ENDL; + return -1; + } - //get ratio of uninstanced triangles to instanced triangles - F32 triangle_ratio = (F32)triangle_count / (F32)instanced_triangle_count; + // 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]; - U32 base_triangle_count = triangle_count; + old_to_new_positions_map[idx] = buf_positions_copied; - U32 type_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0; + buffer_indices[buf_indices_copied] = (U16)buf_positions_copied; + buf_positions_copied++; + } + else + { + // existing position + buffer_indices[buf_indices_copied] = (U16)old_to_new_positions_map[idx]; + } + buf_indices_copied++; + } + } - U32 lod_mode = 0; + if (buf_positions_copied >= U16_MAX) + { + break; + } - F32 lod_error_threshold = 0; + LLVolumeFace &new_face = target_model->getVolumeFace(face_idx); + //new_face = face; //temp - // The LoD should be in range from Lowest to High - if (which_lod > -1 && which_lod < NUM_LOD) - { - LLCtrlSelectionInterface* iface = mFMP->childGetSelectionInterface("lod_mode_" + lod_name[which_lod]); - if (iface) + if (buf_indices_copied < 3) { - lod_mode = iface->getFirstSelectedIndex(); + // face was optimized away + new_face.resizeIndices(3); + new_face.resizeVertices(1); + memset(new_face.mIndices, 0, sizeof(U16) * 3); + new_face.mPositions[0].clear(); // set first vertice to 0 + new_face.mNormals[0].clear(); + new_face.mTexCoords[0].setZero(); } + else + { + new_face.resizeIndices(buf_indices_copied); + new_face.resizeVertices(buf_positions_copied); - lod_error_threshold = mFMP->childGetValue("lod_error_threshold_" + lod_name[which_lod]).asReal(); - } + S32 idx_size = (buf_indices_copied * sizeof(U16) + 0xF) & ~0xF; + LLVector4a::memcpyNonAliased16((F32*)new_face.mIndices, (F32*)buffer_indices, idx_size); - if (which_lod != -1) - { - mRequestedLoDMode[which_lod] = lod_mode; + LLVector4a::memcpyNonAliased16((F32*)new_face.mPositions, (F32*)buffer_positions, buf_positions_copied * sizeof(LLVector4a)); + LLVector4a::memcpyNonAliased16((F32*)new_face.mNormals, (F32*)buffer_normals, buf_positions_copied * sizeof(LLVector4a)); + + U32 tex_size = (buf_positions_copied * sizeof(LLVector2) + 0xF)&~0xF; + LLVector4a::memcpyNonAliased16((F32*)new_face.mTexCoords, (F32*)buffer_tex_coords, tex_size); + } + + indices_idx_shift += face.mNumVertices; } - if (lod_mode == 0) + delete[]old_to_new_positions_map; + ll_aligned_free<64>(combined_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) { - lod_mode = GLOD_TRIANGLE_BUDGET; + // Model should have at least one visible triangle - // The LoD should be in range from Lowest to High - if (which_lod > -1 && which_lod < NUM_LOD) + if (!sloppy) { - limit = mFMP->childGetValue("lod_triangle_limit_" + lod_name[which_lod]).asInteger(); - //convert from "scene wide" to "non-instanced" triangle limit - limit = (S32)((F32)limit*triangle_ratio); + // Should only happen with sloppy + // non sloppy shouldn't be capable of optimizing mesh away + LL_WARNS() << "Failed to generate triangles" + << " model " << target_model->mLabel + << " target Indices: " << target_indices + << " new Indices: " << new_indices + << " original count: " << size_indices + << " error treshold: " << error_threshold + << LL_ENDL; } + + return -1; + } + + return (F32)size_indices / (F32)new_indices; +} + +F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target_model, U32 face_idx, F32 indices_decimator, F32 error_threshold, bool sloppy) +{ + const LLVolumeFace &face = base_model->getVolumeFace(face_idx); + S32 size_indices = face.mNumIndices; + // 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); + + S32 target_indices = 0; + F32 result_code = 0; // how far from original the model is, 1 == 100% + S32 new_indices = 0; + + target_indices = llmax(3, llfloor(size_indices / indices_decimator)); // leave at least one triangle + new_indices = LLMeshOptimizer::simplify( + output, + face.mIndices, + size_indices, + face.mPositions, + face.mNumVertices, + LLVertexBuffer::sTypeSize[LLVertexBuffer::TYPE_VERTEX], + target_indices, + error_threshold, + sloppy, + &result_code); + + + if (result_code < 0) + { + LL_WARNS() << "Negative result code from meshoptimizer for face " << face_idx + << " of model " << target_model->mLabel + << " target Indices: " << target_indices + << " new Indices: " << new_indices + << " original count: " << size_indices + << " error treshold: " << error_threshold + << LL_ENDL; + } + + LLVolumeFace &new_face = target_model->getVolumeFace(face_idx); + + // Copy old values + new_face = face; + + + if (new_indices < 3) + { + if (!sloppy) + { + // meshopt_optimizeSloppy() can optimize triangles away even if target_indices is > 2, + // but optimize() isn't supposed to + LL_INFOS() << "No indices generated by meshoptimizer for face " << face_idx + << " of model " << target_model->mLabel + << " target Indices: " << target_indices + << " original count: " << size_indices + << " error treshold: " << error_threshold + << LL_ENDL; + } + + // Face got optimized away + // Generate empty triangle + new_face.resizeIndices(3); + new_face.resizeVertices(1); + memset(new_face.mIndices, 0, sizeof(U16) * 3); + new_face.mPositions[0].clear(); // set first vertice to 0 + new_face.mNormals[0].clear(); + new_face.mTexCoords[0].setZero(); } else { - lod_mode = GLOD_ERROR_THRESHOLD; - } + // 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); - bool object_dirty = false; + // clear unused values + new_face.optimize(); + } - if (mGroup == 0) + ll_aligned_free_16(output); + + if (new_indices < 3) { - object_dirty = true; - mGroup = cur_name++; - glodNewGroup(mGroup); + // At least one triangle is needed + return -1; } - if (object_dirty) + return (F32)size_indices / (F32)new_indices; +} + +void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 decimation, bool enforce_tri_limit) +{ + LL_INFOS() << "Generating lod " << which_lod << " using meshoptimizer" << LL_ENDL; + // Allow LoD from -1 to LLModel::LOD_PHYSICS + if (which_lod < -1 || which_lod > LLModel::NUM_LODS - 1) { - for (LLModelLoader::model_list::iterator iter = mBaseModel.begin(); iter != mBaseModel.end(); ++iter) - { //build GLOD objects for each model in base model list - LLModel* mdl = *iter; + std::ostringstream out; + out << "Invalid level of detail: " << which_lod; + LL_WARNS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, false); + assert(lod >= -1 && lod < LLModel::NUM_LODS); + return; + } - if (mObject[mdl] != 0) - { - glodDeleteObject(mObject[mdl]); - } + if (mBaseModel.empty()) + { + return; + } - mObject[mdl] = cur_name++; + //get the triangle count for all base models + S32 base_triangle_count = 0; + for (S32 i = 0; i < mBaseModel.size(); ++i) + { + base_triangle_count += mBaseModel[i]->getNumTriangles(); + } - glodNewObject(mObject[mdl], mGroup, GLOD_DISCRETE); - stop_gloderror(); + // Urgh... + // TODO: add interface to mFMP to get error treshold or let mFMP write one into LLModelPreview + // We should not be accesing views from other class! + U32 lod_mode = LIMIT_TRIANGLES; + F32 indices_decimator = 0; + F32 triangle_limit = 0; + F32 lod_error_threshold = 1; //100% - if (iter == mBaseModel.begin() && !mdl->mSkinWeights.empty()) - { //regenerate vertex buffer for skinned models to prevent animation feedback during LOD generation - mVertexBuffer[5].clear(); - } + // If requesting a single lod + if (which_lod > -1 && which_lod < NUM_LOD) + { + LLCtrlSelectionInterface* iface = mFMP->childGetSelectionInterface("lod_mode_" + lod_name[which_lod]); + if (iface) + { + lod_mode = iface->getFirstSelectedIndex(); + } - if (mVertexBuffer[5].empty()) + if (lod_mode == LIMIT_TRIANGLES) + { + if (!enforce_tri_limit) { - genBuffers(5, false); - } + triangle_limit = base_triangle_count; + // reset to default value for this lod + F32 pw = pow((F32)decimation, (F32)(LLModel::LOD_HIGH - which_lod)); - U32 tri_count = 0; - for (U32 i = 0; i < mVertexBuffer[5][mdl].size(); ++i) + triangle_limit /= pw; //indices_ratio can be 1/pw + } + else { - LLVertexBuffer* buff = mVertexBuffer[5][mdl][i]; - buff->setBuffer(type_mask & buff->getTypeMask()); - U32 num_indices = mVertexBuffer[5][mdl][i]->getNumIndices(); - if (num_indices > 2) - { - glodInsertElements(mObject[mdl], i, GL_TRIANGLES, num_indices, GL_UNSIGNED_SHORT, (U8*)mVertexBuffer[5][mdl][i]->getIndicesPointer(), 0, 0.f); - } - tri_count += num_indices / 3; - stop_gloderror(); - } + // UI spacifies limit for all models of single lod + triangle_limit = mFMP->childGetValue("lod_triangle_limit_" + lod_name[which_lod]).asInteger(); - glodBuildObject(mObject[mdl]); - stop_gloderror(); + } + // meshoptimizer doesn't use triangle limit, it uses indices limit, so convert it to aproximate ratio + indices_decimator = (F32)base_triangle_count / triangle_limit; + } + else + { + lod_error_threshold = mFMP->childGetValue("lod_error_threshold_" + lod_name[which_lod]).asReal(); } } + else + { + // we are genrating all lods and each lod will get own indices_decimator + indices_decimator = 1; + triangle_limit = base_triangle_count; + } + mMaxTriangleLimit = base_triangle_count; + mMinTriangleLimit = mBaseModel.size(); + + // Build models S32 start = LLModel::LOD_HIGH; S32 end = 0; if (which_lod != -1) { - start = end = which_lod; + start = which_lod; + end = which_lod; } - mMaxTriangleLimit = base_triangle_count; + LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; + if (shader) + { + shader->unbind(); + } for (S32 lod = start; lod >= end; --lod) { if (which_lod == -1) { + // we are genrating all lods and each lod gets own indices_ratio if (lod < start) { - triangle_count /= decimation; - } - } - else - { - if (enforce_tri_limit) - { - triangle_count = limit; - } - else - { - for (S32 j = LLModel::LOD_HIGH; j>which_lod; --j) - { - triangle_count /= decimation; - } + indices_decimator *= decimation; + triangle_limit /= decimation; } } + mRequestedTriangleCount[lod] = llmax(mMinTriangleLimit, (S32)triangle_limit); + mRequestedErrorThreshold[lod] = lod_error_threshold; + mRequestedLoDMode[lod] = lod_mode; + mModel[lod].clear(); mModel[lod].resize(mBaseModel.size()); mVertexBuffer[lod].clear(); - U32 actual_tris = 0; - U32 actual_verts = 0; - U32 submeshes = 0; - - mRequestedTriangleCount[lod] = (S32)((F32)triangle_count / triangle_ratio); - mRequestedErrorThreshold[lod] = lod_error_threshold; - - glodGroupParameteri(mGroup, GLOD_ADAPT_MODE, lod_mode); - stop_gloderror(); - - glodGroupParameteri(mGroup, GLOD_ERROR_MODE, GLOD_OBJECT_SPACE_ERROR); - stop_gloderror(); - - glodGroupParameterf(mGroup, GLOD_OBJECT_SPACE_ERROR_THRESHOLD, lod_error_threshold); - stop_gloderror(); - - if (lod_mode != GLOD_TRIANGLE_BUDGET) - { - glodGroupParameteri(mGroup, GLOD_MAX_TRIANGLES, 0); - } - else - { - //SH-632: always add 1 to desired amount to avoid decimating below desired amount - glodGroupParameteri(mGroup, GLOD_MAX_TRIANGLES, triangle_count + 1); - } - - stop_gloderror(); - glodAdaptGroup(mGroup); - stop_gloderror(); for (U32 mdl_idx = 0; mdl_idx < mBaseModel.size(); ++mdl_idx) { LLModel* base = mBaseModel[mdl_idx]; - GLint patch_count = 0; - glodGetObjectParameteriv(mObject[base], GLOD_NUM_PATCHES, &patch_count); - stop_gloderror(); - LLVolumeParams volume_params; volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); mModel[lod][mdl_idx] = new LLModel(volume_params, 0.f); @@ -1581,74 +1730,87 @@ void LLModelPreview::genLODs(S32 which_lod, U32 decimation, bool enforce_tri_lim mModel[lod][mdl_idx]->mLabel = name; mModel[lod][mdl_idx]->mSubmodelID = base->mSubmodelID; - - GLint* sizes = new GLint[patch_count * 2]; - glodGetObjectParameteriv(mObject[base], GLOD_PATCH_SIZES, sizes); - stop_gloderror(); - - GLint* names = new GLint[patch_count]; - glodGetObjectParameteriv(mObject[base], GLOD_PATCH_NAMES, names); - stop_gloderror(); - - mModel[lod][mdl_idx]->setNumVolumeFaces(patch_count); + mModel[lod][mdl_idx]->setNumVolumeFaces(base->getNumVolumeFaces()); LLModel* target_model = mModel[lod][mdl_idx]; - for (GLint i = 0; i < patch_count; ++i) - { - type_mask = mVertexBuffer[5][base][i]->getTypeMask(); + S32 model_meshopt_mode = meshopt_mode; - LLPointer<LLVertexBuffer> buff = new LLVertexBuffer(type_mask, 0); + // Ideally this should run not per model, + // but combine all submodels with origin model as well + if (model_meshopt_mode == MESH_OPTIMIZER_COMBINE) + { + // Run meshoptimizer for each model/object, up to 8 faces in one model - if (sizes[i * 2 + 1] > 0 && sizes[i * 2] > 0) + // 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) { - if (!buff->allocateBuffer(sizes[i * 2 + 1], sizes[i * 2], true)) + // U16 vertices overflow, shouldn't happen, but just in case + for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx) { - // Todo: find a way to stop preview in this case instead of crashing - LL_ERRS() << "Failed buffer allocation during preview LOD generation." - << " Vertices: " << sizes[i * 2 + 1] - << " Indices: " << sizes[i * 2] << LL_ENDL; + genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, false); } - buff->setBuffer(type_mask); - glodFillElements(mObject[base], names[i], GL_UNSIGNED_SHORT, (U8*)buff->getIndicesPointer()); - stop_gloderror(); } - else + } + + if (model_meshopt_mode == MESH_OPTIMIZER) + { + // Run meshoptimizer for each face + for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx) { - // This face was eliminated or we failed to allocate buffer, - // attempt to create a dummy triangle (one vertex, 3 indices, all 0) - buff->allocateBuffer(1, 3, true); - memset((U8*)buff->getMappedData(), 0, buff->getSize()); - memset((U8*)buff->getIndicesPointer(), 0, buff->getIndicesSize()); + genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, false); } + } - buff->validateRange(0, buff->getNumVerts() - 1, buff->getNumIndices(), 0); + if (model_meshopt_mode == MESH_OPTIMIZER_SLOPPY) + { + // Run meshoptimizer for each face + for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx) + { + genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, true); + } + } - LLStrider<LLVector3> pos; - LLStrider<LLVector3> norm; - LLStrider<LLVector2> tc; - LLStrider<U16> index; + if (model_meshopt_mode == MESH_OPTIMIZER_AUTO) + { + F32 allowed_ratio_drift = 2.f; + F32 res_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, false); - buff->getVertexStrider(pos); - if (type_mask & LLVertexBuffer::MAP_NORMAL) + if (res_ratio < 0) { - buff->getNormalStrider(norm); + // U16 vertices overflow, shouldn't happen, but just in case + for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx) + { + genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, false); + } + LL_INFOS() << "Model " << target_model->getName() + << " lod " << which_lod + << " per model method overflow, defaulting to per face." << LL_ENDL; } - if (type_mask & LLVertexBuffer::MAP_TEXCOORD0) + else if (res_ratio * allowed_ratio_drift < indices_decimator) { - buff->getTexCoord0Strider(tc); - } - - buff->getIndexStrider(index); + // Try sloppy variant if normal one failed to simplify model enough. + res_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, true); + LL_INFOS() << "Model " << target_model->getName() + << " lod " << which_lod + << " sloppily simplified using per model method." << LL_ENDL; - target_model->setVolumeFaceData(names[i], pos, norm, tc, index, buff->getNumVerts(), buff->getNumIndices()); - actual_tris += buff->getNumIndices() / 3; - actual_verts += buff->getNumVerts(); - ++submeshes; - if (!validate_face(target_model->getVolumeFace(names[i]))) + if (res_ratio < 0) + { + // Sloppy variant failed to generate triangles. + // Can happen with models that are too simple as is. + // Fallback to normal method. + genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, false); + } + } + else { - LL_ERRS() << "Invalid face generated during LOD generation." << LL_ENDL; + LL_INFOS() << "Model " << target_model->getName() + << " lod " << which_lod + << " simplified using per model method." << LL_ENDL; } } @@ -1658,6 +1820,7 @@ void LLModelPreview::genLODs(S32 which_lod, U32 decimation, bool enforce_tri_lim target_model->mPosition = base->mPosition; target_model->mSkinWeights = base->mSkinWeights; target_model->mSkinInfo = base->mSkinInfo; + //copy material list target_model->mMaterialList = base->mMaterialList; @@ -1665,9 +1828,6 @@ void LLModelPreview::genLODs(S32 which_lod, U32 decimation, bool enforce_tri_lim { LL_ERRS() << "Invalid model generated when creating LODs" << LL_ENDL; } - - delete[] sizes; - delete[] names; } //rebuild scene based on mBaseScene @@ -1697,7 +1857,6 @@ void LLModelPreview::genLODs(S32 which_lod, U32 decimation, bool enforce_tri_lim mResourceCost = calcResourceCost(); LLVertexBuffer::unbind(); - LLGLSLShader::sNoFixedFunction = no_ff; if (shader) { shader->bind(); @@ -1821,6 +1980,7 @@ void LLModelPreview::updateStatusMessages() if (mMaxTriangleLimit == 0) { mMaxTriangleLimit = total_tris[LLModel::LOD_HIGH]; + mMinTriangleLimit = mUploadData.size(); } mHasDegenerate = false; @@ -2264,7 +2424,7 @@ void LLModelPreview::updateLodControls(S32 lod) S32 lod_mode = lod_combo->getCurrentIndex(); if (lod_mode == LOD_FROM_FILE) // LoD from file { - fmp->mLODMode[lod] = 0; + fmp->mLODMode[lod] = LOD_FROM_FILE; for (U32 i = 0; i < num_file_controls; ++i) { mFMP->childSetVisible(file_controls[i] + lod_name[lod], true); @@ -2277,7 +2437,7 @@ void LLModelPreview::updateLodControls(S32 lod) } else if (lod_mode == USE_LOD_ABOVE) // use LoD above { - fmp->mLODMode[lod] = 2; + fmp->mLODMode[lod] = USE_LOD_ABOVE; for (U32 i = 0; i < num_file_controls; ++i) { mFMP->childSetVisible(file_controls[i] + lod_name[lod], false); @@ -2303,7 +2463,7 @@ void LLModelPreview::updateLodControls(S32 lod) } else // auto generate, the default case for all LoDs except High { - fmp->mLODMode[lod] = 1; + fmp->mLODMode[lod] = MESH_OPTIMIZER_AUTO; //don't actually regenerate lod when refreshing UI mLODFrozen = true; @@ -2323,6 +2483,7 @@ void LLModelPreview::updateLodControls(S32 lod) LLSpinCtrl* limit = mFMP->getChild<LLSpinCtrl>("lod_triangle_limit_" + lod_name[lod]); limit->setMaxValue(mMaxTriangleLimit); + limit->setMinValue(mMinTriangleLimit); limit->forceSetValue(mRequestedTriangleCount[lod]); threshold->forceSetValue(mRequestedErrorThreshold[lod]); @@ -2335,7 +2496,8 @@ void LLModelPreview::updateLodControls(S32 lod) threshold->setVisible(false); limit->setMaxValue(mMaxTriangleLimit); - limit->setIncrement(mMaxTriangleLimit / 32); + limit->setMinValue(mMinTriangleLimit); + limit->setIncrement(llmax((U32)1, mMaxTriangleLimit / 32)); } else { @@ -2957,7 +3119,6 @@ BOOL LLModelPreview::render() { genBuffers(-1, skin_weight); //genBuffers(3); - //genLODs(); } if (!mModel[mPreviewLOD].empty()) @@ -3543,7 +3704,7 @@ bool LLModelPreview::lodQueryCallback() { S32 lod = preview->mLodsQuery.back(); preview->mLodsQuery.pop_back(); - preview->genLODs(lod); + preview->genMeshOptimizerLODs(lod, MESH_OPTIMIZER_AUTO); if (preview->mLookUpLodFiles && (lod == LLModel::LOD_HIGH)) { @@ -3558,11 +3719,11 @@ bool LLModelPreview::lodQueryCallback() return true; } -void LLModelPreview::onLODParamCommit(S32 lod, bool enforce_tri_limit) +void LLModelPreview::onLODMeshOptimizerParamCommit(S32 requested_lod, bool enforce_tri_limit, S32 mode) { if (!mLODFrozen) { - genLODs(lod, 3, enforce_tri_limit); + genMeshOptimizerLODs(requested_lod, mode, 3, enforce_tri_limit); refresh(); } } diff --git a/indra/newview/llmodelpreview.h b/indra/newview/llmodelpreview.h index 3664a27a72..7d4507ccf2 100644 --- a/indra/newview/llmodelpreview.h +++ b/indra/newview/llmodelpreview.h @@ -124,10 +124,19 @@ public: typedef enum { LOD_FROM_FILE = 0, - GENERATE, + MESH_OPTIMIZER_AUTO, // automatically selects method based on model or face + MESH_OPTIMIZER_COMBINE, + MESH_OPTIMIZER, + MESH_OPTIMIZER_SLOPPY, USE_LOD_ABOVE, } eLoDMode; + typedef enum + { + LIMIT_TRIANGLES = 0, + LIMIT_ERROR_TRESHOLD, + } eLoDLimit; + public: // Todo: model preview shouldn't need floater dependency, it // should just expose data to floater, not control flaoter like it does @@ -155,7 +164,7 @@ public: void loadModelCallback(S32 lod); bool lodsReady() { return !mGenLOD && mLodsQuery.empty(); } void queryLODs() { mGenLOD = true; }; - void genLODs(S32 which_lod = -1, U32 decimation = 3, bool enforce_tri_limit = false); + void genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 decimation = 3, bool enforce_tri_limit = false); void generateNormals(); void restoreNormals(); U32 calcResourceCost(); @@ -165,8 +174,7 @@ public: void clearIncompatible(S32 lod); void updateStatusMessages(); void updateLodControls(S32 lod); - void clearGLODGroup(); - void onLODParamCommit(S32 lod, bool enforce_tri_limit); + void onLODMeshOptimizerParamCommit(S32 lod, bool enforce_tri_limit, S32 mode); void addEmptyFace(LLModel* pTarget); const bool getModelPivot(void) const { return mHasPivot; } @@ -218,6 +226,10 @@ private: // Count amount of original models, excluding sub-models static U32 countRootModels(LLModelLoader::model_list models); + // functions for meshoptimizer, return reached simplification ratio + F32 genMeshOptimizerPerModel(LLModel *base_model, LLModel *target_model, 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, bool sloppy); + protected: friend class LLModelLoader; friend class LLFloaterModelPreview; @@ -249,19 +261,11 @@ protected: std::map<std::string, bool> mViewOption; - //GLOD object parameters (must rebuild object if these change) + // Model generation parameters (must rebuild object if these change) bool mLODFrozen; - F32 mBuildShareTolerance; - U32 mBuildQueueMode; - U32 mBuildOperator; - U32 mBuildBorderMode; U32 mRequestedLoDMode[LLModel::NUM_LODS]; S32 mRequestedTriangleCount[LLModel::NUM_LODS]; F32 mRequestedErrorThreshold[LLModel::NUM_LODS]; - U32 mRequestedBuildOperator[LLModel::NUM_LODS]; - U32 mRequestedQueueMode[LLModel::NUM_LODS]; - U32 mRequestedBorderMode[LLModel::NUM_LODS]; - F32 mRequestedShareTolerance[LLModel::NUM_LODS]; F32 mRequestedCreaseAngle[LLModel::NUM_LODS]; LLModelLoader* mModelLoader; @@ -280,8 +284,14 @@ protected: U32 mGroup; std::map<LLPointer<LLModel>, U32> mObject; + + // Amount of triangles in original(base) model U32 mMaxTriangleLimit; + // Minimum amount of allowed triangles in lod for spin cntrl. + // Leave at least one triangle per model. + S32 mMinTriangleLimit; + LLMeshUploadThread::instance_list mUploadData; std::set<LLViewerFetchedTexture * > mTextureSet; diff --git a/indra/newview/skins/default/xui/en/floater_about.xml b/indra/newview/skins/default/xui/en/floater_about.xml index 71f4d81195..eb07425dfe 100644 --- a/indra/newview/skins/default/xui/en/floater_about.xml +++ b/indra/newview/skins/default/xui/en/floater_about.xml @@ -101,11 +101,11 @@ Dummy Name replaced at run time expat Copyright (C) 1998, 1999, 2000 Thai Open Source Software Center Ltd. FreeType Copyright (C) 1996-2002, 2006 David Turner, Robert Wilhelm, and Werner Lemberg. GL Copyright (C) 1999-2004 Brian Paul. - GLOD Copyright (C) 2003-04 Jonathan Cohen, Nat Duca, Chris Niski, Johns Hopkins University and David Luebke, Brenden Schubert, University of Virginia. google-perftools Copyright (c) 2005, Google Inc. Havok.com(TM) Copyright (C) 1999-2001, Telekinesys Research Limited. jpeg2000 Copyright (C) 2001, David Taubman, The University of New South Wales (UNSW) jpeglib Copyright (C) 1991-1998, Thomas G. Lane. + meshoptimizer Copyright (c) 2016-2021 Arseny Kapoulkine ogg/vorbis Copyright (C) 2002, Xiphophorus OpenSSL Copyright (C) 1998-2008 The OpenSSL Project. PCRE Copyright (c) 1997-2012 University of Cambridge diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 7f863756eb..d08bc92e5d 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -45,7 +45,21 @@ <string name="UnrecognizedJoint">Rigged to unrecognized joint name [NAME]</string> <string name="UnknownJoints">Skinning disabled due to [COUNT] unknown joints</string> <string name="ModelLoaded">Model [MODEL_NAME] loaded</string> + <string name="IncompleteTC">Texture coordinates data is not complete.</string> + <string name="PositionNaN">Found NaN while loading position data from DAE-Model, invalid model.</string> + <string name="NormalsNaN">Found NaN while loading normals from DAE-Model, invalid model.</string> + <string name="NegativeScaleTrans">Negative scale detected, unsupported transform. domInstance_geometry: [LABEL]</string> + <string name="NegativeScaleNormTrans">Negative scale detected, unsupported post-normalization transform. domInstance_geometry: [LABEL]</string> + <string name="CantResolveGeometryUrl">Unable to resolve geometry URL.</string> + <string name="ParsingErrorBadElement">Bad element</string> + <string name="ParsingErrorCantParseScene">Scene could not be parsed</string> + <string name="ParsingErrorCorrupt">Error with dae - traditionally indicates a corrupt file.</string> + <string name="ParsingErrorNoController">Could not verify controller</string> + <string name="ParsingErrorNoDoc">Can't find internal doc</string> + <string name="ParsingErrorNoRoot">Document has no root</string> + <string name="ParsingErrorNoScene">Document has no visual_scene</string> + <string name="ParsingErrorPositionInvalidModel">Unable to process mesh without position data. Invalid model.</string> <panel follows="top|left" @@ -173,9 +187,21 @@ label="Load from file" value="Load from file" /> <item - name="Generate" - label="Generate" - value="Generate" /> + name="MeshOpt Auto" + label="Generate Auto" + value="MeshOpt Auto" /> + <item + name="MeshOptCombine" + label="Generate Precise" + value="MeshOptCombine" /> + <item + name="MeshOpt" + label="Generate per face" + value="MeshOpt" /> + <item + name="MeshOptSloppy" + label="Generate Sloppy" + value="MeshOptSloppy" /> </combo_box> <line_editor follows="left|top" @@ -302,9 +328,21 @@ label="Load from file" value="Load from file" /> <item - name="Generate" - label="Generate" - value="Generate" /> + name="MeshOpt Auto" + label="Generate Auto" + value="MeshOpt Auto" /> + <item + name="MeshOptCombine" + label="Generate Precise" + value="MeshOptCombine" /> + <item + name="MeshOpt" + label="Generate per face" + value="MeshOpt" /> + <item + name="MeshOptSloppy" + label="Generate Sloppy" + value="MeshOptSloppy" /> <item name="Use LoD above" label="Use LoD above" @@ -435,9 +473,21 @@ label="Load from file" value="Load from file" /> <item - name="Generate" - label="Generate" - value="Generate" /> + name="MeshOpt Auto" + label="Generate Auto" + value="MeshOpt Auto" /> + <item + name="MeshOptCombine" + label="Generate Precise" + value="MeshOptCombine" /> + <item + name="MeshOpt" + label="Generate per face" + value="MeshOpt" /> + <item + name="MeshOptSloppy" + label="Generate Sloppy" + value="MeshOptSloppy" /> <item name="Use LoD above" label="Use LoD above" @@ -568,9 +618,21 @@ label="Load from file" value="Load from file" /> <item - name="Generate" - label="Generate" - value="Generate" /> + name="MeshOpt Auto" + label="Generate Auto" + value="MeshOpt Auto" /> + <item + name="MeshOptCombine" + label="Generate Precise" + value="MeshOptCombine" /> + <item + name="MeshOpt" + label="Generate per face" + value="MeshOpt" /> + <item + name="MeshOptSloppy" + label="Generate Sloppy" + value="MeshOptSloppy" /> <item name="Use LoD above" label="Use LoD above" diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 6fcd1e84e8..efa7321d3e 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -510,14 +510,6 @@ class WindowsManifest(ViewerManifest): # Get shared libs from the shared libs staging directory with self.prefix(src=os.path.join(self.args['build'], os.pardir, 'sharedlibs', self.args['configuration'])): - - # Mesh 3rd party libs needed for auto LOD and collada reading - try: - self.path("glod.dll") - except RuntimeError as err: - print err.message - print "Skipping GLOD library (assumming linked statically)" - # Get fmodstudio dll if needed if self.args['fmodstudio'] == 'ON': if(self.args['configuration'].lower() == 'debug'): @@ -1029,7 +1021,6 @@ class DarwinManifest(ViewerManifest): "libapr-1.0.dylib", "libaprutil-1.0.dylib", "libexpat.1.dylib", - "libGLOD.dylib", # libnghttp2.dylib is a symlink to # libnghttp2.major.dylib, which is a symlink to # libnghttp2.version.dylib. Get all of them. @@ -1475,7 +1466,6 @@ class Linux_i686_Manifest(LinuxManifest): self.path("libaprutil-1.so.0.4.1") self.path("libdb*.so") self.path("libexpat.so.*") - self.path("libGLOD.so") self.path("libuuid.so*") self.path("libSDL-1.2.so.*") self.path("libdirectfb-1.*.so.*") |