From 2dc61c53c62b36bc3ff66deeb693e803445190c9 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 25 Oct 2026 09:37:49 -0400 Subject: [PATCH 1/5] Delete the Forsyth vertex cache remnant LLVCacheVertexData, LLVCacheTriangleData, find_vertex_score, LLVCacheFIFO and LLVCacheLRU are what is left of the vertex cache optimizer meshoptimizer replaced; nothing calls them. Co-Authored-By: Claude Opus 5.5 --- indra/llmath/llvolume.cpp | 263 -------------------------------------- 1 file changed, 263 deletions(-) diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp index cee1206c8c..950cf5177a 100644 --- a/indra/llmath/llvolume.cpp +++ b/indra/llmath/llvolume.cpp @@ -5160,269 +5160,6 @@ void LLVolumeFace::optimize(F32 angle_cutoff) } -class LLVCacheTriangleData; - -class LLVCacheVertexData -{ -public: - S32 mIdx; - S32 mCacheTag; - F64 mScore; - U32 mActiveTriangles; - std::vector mTriangles; - - LLVCacheVertexData() - { - mCacheTag = -1; - mScore = 0.0; - mActiveTriangles = 0; - mIdx = -1; - } -}; - -class LLVCacheTriangleData -{ -public: - bool mActive; - F64 mScore; - LLVCacheVertexData* mVertex[3]; - - LLVCacheTriangleData() - { - mActive = true; - mScore = 0.0; - mVertex[0] = mVertex[1] = mVertex[2] = NULL; - } - - void complete() - { - mActive = false; - for (S32 i = 0; i < 3; ++i) - { - if (mVertex[i]) - { - llassert(mVertex[i]->mActiveTriangles > 0); - mVertex[i]->mActiveTriangles--; - } - } - } - - bool operator<(const LLVCacheTriangleData& rhs) const - { //highest score first - return rhs.mScore < mScore; - } -}; - -constexpr F64 FindVertexScore_CacheDecayPower = 1.5; -constexpr F64 FindVertexScore_LastTriScore = 0.75; -constexpr F64 FindVertexScore_ValenceBoostScale = 2.0; -constexpr F64 FindVertexScore_ValenceBoostPower = 0.5; -constexpr U32 MaxSizeVertexCache = 32; -constexpr F64 FindVertexScore_Scaler = 1.0/(MaxSizeVertexCache-3); - -F64 find_vertex_score(LLVCacheVertexData& data) -{ - F64 score = -1.0; - - score = 0.0; - - S32 cache_idx = data.mCacheTag; - - if (cache_idx < 0) - { - //not in cache - } - else - { - if (cache_idx < 3) - { //vertex was in the last triangle - score = FindVertexScore_LastTriScore; - } - else - { //more points for being higher in the cache - score = 1.0-((cache_idx-3)*FindVertexScore_Scaler); - score = pow(score, FindVertexScore_CacheDecayPower); - } - } - - //bonus points for having low valence - F64 valence_boost = pow((F64)data.mActiveTriangles, -FindVertexScore_ValenceBoostPower); - score += FindVertexScore_ValenceBoostScale * valence_boost; - - return score; -} - -class LLVCacheFIFO -{ -public: - LLVCacheVertexData* mCache[MaxSizeVertexCache]; - U32 mMisses; - - LLVCacheFIFO() - { - mMisses = 0; - for (U32 i = 0; i < MaxSizeVertexCache; ++i) - { - mCache[i] = NULL; - } - } - - void addVertex(LLVCacheVertexData* data) - { - if (data->mCacheTag == -1) - { - mMisses++; - - S32 end = MaxSizeVertexCache-1; - - if (mCache[end]) - { - mCache[end]->mCacheTag = -1; - } - - for (S32 i = end; i > 0; --i) - { - mCache[i] = mCache[i-1]; - if (mCache[i]) - { - mCache[i]->mCacheTag = i; - } - } - - mCache[0] = data; - data->mCacheTag = 0; - } - } -}; - -class LLVCacheLRU -{ -public: - LLVCacheVertexData* mCache[MaxSizeVertexCache+3]; - - LLVCacheTriangleData* mBestTriangle; - - U32 mMisses; - - LLVCacheLRU() - { - for (U32 i = 0; i < MaxSizeVertexCache+3; ++i) - { - mCache[i] = NULL; - } - - mBestTriangle = NULL; - mMisses = 0; - } - - void addVertex(LLVCacheVertexData* data) - { - S32 end = MaxSizeVertexCache+2; - if (data->mCacheTag != -1) - { //just moving a vertex to the front of the cache - end = data->mCacheTag; - } - else - { - mMisses++; - if (mCache[end]) - { //adding a new vertex, vertex at end of cache falls off - mCache[end]->mCacheTag = -1; - } - } - - for (S32 i = end; i > 0; --i) - { //adjust cache pointers and tags - mCache[i] = mCache[i-1]; - - if (mCache[i]) - { - mCache[i]->mCacheTag = i; - } - } - - mCache[0] = data; - mCache[0]->mCacheTag = 0; - } - - void addTriangle(LLVCacheTriangleData* data) - { - addVertex(data->mVertex[0]); - addVertex(data->mVertex[1]); - addVertex(data->mVertex[2]); - } - - void updateScores() - { - LLVCacheVertexData** data_iter = mCache+MaxSizeVertexCache; - LLVCacheVertexData** end_data = mCache+MaxSizeVertexCache+3; - - while(data_iter != end_data) - { - LLVCacheVertexData* data = *data_iter++; - //trailing 3 vertices aren't actually in the cache for scoring purposes - if (data) - { - data->mCacheTag = -1; - } - } - - data_iter = mCache; - end_data = mCache+MaxSizeVertexCache; - - while (data_iter != end_data) - { //update scores of vertices in cache - LLVCacheVertexData* data = *data_iter++; - if (data) - { - data->mScore = find_vertex_score(*data); - } - } - - mBestTriangle = NULL; - //update triangle scores - data_iter = mCache; - end_data = mCache+MaxSizeVertexCache+3; - - while (data_iter != end_data) - { - LLVCacheVertexData* data = *data_iter++; - if (data) - { - for (std::vector::iterator iter = data->mTriangles.begin(), end_iter = data->mTriangles.end(); iter != end_iter; ++iter) - { - LLVCacheTriangleData* tri = *iter; - if (tri->mActive) - { - tri->mScore = tri->mVertex[0] ? tri->mVertex[0]->mScore : 0; - tri->mScore += tri->mVertex[1] ? tri->mVertex[1]->mScore : 0; - tri->mScore += tri->mVertex[2] ? tri->mVertex[2]->mScore : 0; - - if (!mBestTriangle || mBestTriangle->mScore < tri->mScore) - { - mBestTriangle = tri; - } - } - } - } - } - - //knock trailing 3 vertices off the cache - data_iter = mCache+MaxSizeVertexCache; - end_data = mCache+MaxSizeVertexCache+3; - while (data_iter != end_data) - { - LLVCacheVertexData* data = *data_iter; - if (data) - { - llassert(data->mCacheTag == -1); - *data_iter = NULL; - } - ++data_iter; - } - } -}; - namespace { LLVector4a plane_axis(const LLVector4a& normal); From 04f90d33f25280e03afc3f5e840dfbc89b8236d9 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 25 Oct 2026 09:38:21 -0400 Subject: [PATCH 2/5] Delete LLModel::optimizeVolumeFaces Nothing calls it. It was the only caller of LLVolumeFace::optimize. Co-Authored-By: Claude Opus 5.5 --- indra/llprimitive/llmodel.cpp | 8 -------- indra/llprimitive/llmodel.h | 1 - 2 files changed, 9 deletions(-) diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 17456882c0..9521c82269 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -113,14 +113,6 @@ void LLModel::remapVolumeFaces() } } -void LLModel::optimizeVolumeFaces() -{ - for (S32 i = 0; i < getNumVolumeFaces(); ++i) - { - mVolumeFaces[i].optimize(); - } -} - struct MaterialBinding { int index; diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index 8b7e41e312..55adbe7412 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -218,7 +218,6 @@ class alignas(16) LLModel : public LLVolume void normalizeVolumeFacesAndWeights(); 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) const; LLVector3 getTransformedCenter(const LLMatrix4& mat); From 3fcda7eeae9be5ab04979ff33a7632ae01f7468a Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 25 Oct 2026 09:40:29 -0400 Subject: [PATCH 3/5] Delete LLVolumeFace::optimize Its one caller, LLModel::optimizeVolumeFaces, is gone, and the uploader compacts its levels of detail through meshoptimizer since the simplifier change. The members only it used go with it: getVertexData, VertexData::compareNormal, both pushVertex overloads, pushIndex and swapData. VertexMapData stays for the DAE loader's weld. Co-Authored-By: Claude Opus 5.5 --- indra/llmath/llvolume.cpp | 234 -------------------------------------- indra/llmath/llvolume.h | 15 +-- 2 files changed, 2 insertions(+), 247 deletions(-) diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp index 950cf5177a..7481880baf 100644 --- a/indra/llmath/llvolume.cpp +++ b/indra/llmath/llvolume.cpp @@ -2401,30 +2401,6 @@ bool LLVolumeFace::VertexData::operator==(const LLVolumeFace::VertexData& rhs)co mTexCoord == rhs.mTexCoord; } -bool LLVolumeFace::VertexData::compareNormal(const LLVolumeFace::VertexData& rhs, F32 angle_cutoff) const -{ - bool retval = false; - - const F32 epsilon = 0.00001f; - - if (rhs.mPosition.equals3(mPosition, epsilon) && - fabs(rhs.mTexCoord[0]-mTexCoord[0]) < epsilon && - fabs(rhs.mTexCoord[1]-mTexCoord[1]) < epsilon) - { - if (angle_cutoff > 1.f) - { - retval = (mNormal.equals3(rhs.mNormal, epsilon)); - } - else - { - F32 cur_angle = rhs.mNormal.dot3(mNormal).getF32(); - retval = cur_angle > angle_cutoff; - } - } - - return retval; -} - bool LLVolume::unpackVolumeFaces(std::istream& is, S32 size) { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; @@ -4965,28 +4941,6 @@ bool LLVolumeFace::create(LLVolume* volume, bool partial_build) return ret ; } -void LLVolumeFace::getVertexData(U16 index, LLVolumeFace::VertexData& cv) -{ - cv.setPosition(mPositions[index]); - if (mNormals) - { - cv.setNormal(mNormals[index]); - } - else - { - cv.getNormal().clear(); - } - - if (mTexCoords) - { - cv.mTexCoord = mTexCoords[index]; - } - else - { - cv.mTexCoord.clear(); - } -} - bool LLVolumeFace::VertexMapData::operator==(const LLVolumeFace::VertexData& rhs) const { return getPosition().equals3(rhs.getPosition()) && @@ -5056,110 +5010,6 @@ void LLVolumeFace::remap() mNumAllocatedVertices = remap_vertices_count; } -void LLVolumeFace::optimize(F32 angle_cutoff) -{ - LLVolumeFace new_face; - - //map of points to vector of vertices at that point - std::map > point_map; - - LLVector4a range; - range.setSub(mExtents[1],mExtents[0]); - - //remove redundant vertices - for (S32 i = 0; i < mNumIndices; ++i) - { - U16 index = mIndices[i]; - - if (index >= mNumVertices) - { - // invalid index - // replace with a valid index to avoid crashes - index = mNumVertices - 1; - mIndices[i] = index; - - // Needs better logging - LL_DEBUGS_ONCE("LLVOLUME") << "Invalid index, substituting" << LL_ENDL; - } - - LLVolumeFace::VertexData cv; - getVertexData(index, cv); - - bool found = false; - - LLVector4a pos; - pos.setSub(mPositions[index], mExtents[0]); - pos.div(range); - - U64 pos64 = 0; - - pos64 = (U16) (pos[0]*65535); - pos64 = pos64 | (((U64) (pos[1]*65535)) << 16); - pos64 = pos64 | (((U64) (pos[2]*65535)) << 32); - - std::map >::iterator point_iter = point_map.find(pos64); - - if (point_iter != point_map.end()) - { //duplicate point might exist - for (U32 j = 0; j < point_iter->second.size(); ++j) - { - LLVolumeFace::VertexData& tv = (point_iter->second)[j]; - if (tv.compareNormal(cv, angle_cutoff)) - { - found = true; - new_face.pushIndex((point_iter->second)[j].mIndex); - break; - } - } - } - - if (!found) - { - new_face.pushVertex(cv); - U16 index = (U16) new_face.mNumVertices-1; - new_face.pushIndex(index); - - VertexMapData d; - d.setPosition(cv.getPosition()); - d.mTexCoord = cv.mTexCoord; - d.setNormal(cv.getNormal()); - d.mIndex = index; - if (point_iter != point_map.end()) - { - point_iter->second.push_back(d); - } - else - { - point_map[pos64].push_back(d); - } - } - } - - - if (angle_cutoff > 1.f && !mNormals) - { - // Now alloc'd with positions - //ll_aligned_free_16(new_face.mNormals); - new_face.mNormals = NULL; - } - - if (!mTexCoords) - { - // Now alloc'd with positions - //ll_aligned_free_16(new_face.mTexCoords); - new_face.mTexCoords = NULL; - } - - // Only swap data if we've actually optimized the mesh - // - if (new_face.mNumVertices <= mNumVertices) - { - llassert(new_face.mNumIndices == mNumIndices); - swapData(new_face); - } - -} - namespace { LLVector4a plane_axis(const LLVector4a& normal); @@ -5912,20 +5762,6 @@ LLVector4a LLVolumeFace::getPlanarTangent(const LLVector4a& normal, const LLVect return out; } - -void LLVolumeFace::swapData(LLVolumeFace& rhs) -{ - llswap(rhs.mPositions, mPositions); - llswap(rhs.mNormals, mNormals); - llswap(rhs.mTangents, mTangents); - llswap(rhs.mTangentsStale, mTangentsStale); - llswap(rhs.mTexCoords, mTexCoords); - llswap(rhs.mIndices,mIndices); - llswap(rhs.mNumVertices, mNumVertices); - llswap(rhs.mNumIndices, mNumIndices); - std::swap(rhs.mPlanarNormal, mPlanarNormal); -} - namespace { // Points closer than this are one place: far above the rounding that a @@ -7475,61 +7311,6 @@ void LLVolumeFace::resizeVertices(S32 num_verts) mJointRiggingInfoTab.clear(); } -void LLVolumeFace::pushVertex(const LLVolumeFace::VertexData& cv) -{ - pushVertex(cv.getPosition(), cv.getNormal(), cv.mTexCoord); -} - -void LLVolumeFace::pushVertex(const LLVector4a& pos, const LLVector4a& norm, const LLVector2& tc) -{ - S32 new_verts = mNumVertices+1; - - if (new_verts > mNumAllocatedVertices) - { - // double buffer size on expansion - new_verts *= 2; - - S32 new_tc_size = ((new_verts*8)+0xF) & ~0xF; - S32 old_tc_size = ((mNumVertices*8)+0xF) & ~0xF; - - S32 old_vsize = mNumVertices*16; - - S32 new_size = new_verts*16*2+new_tc_size; - - LLVector4a* old_buf = mPositions; - - mPositions = (LLVector4a*) ll_aligned_malloc<64>(new_size); - mNormals = mPositions+new_verts; - mTexCoords = (LLVector2*) (mNormals+new_verts); - - if (old_buf != NULL) - { - // copy old positions into new buffer - LLVector4a::memcpyNonAliased16((F32*)mPositions, (F32*)old_buf, old_vsize); - - // normals - LLVector4a::memcpyNonAliased16((F32*)mNormals, (F32*)(old_buf + mNumVertices), old_vsize); - - // tex coords - LLVector4a::memcpyNonAliased16((F32*)mTexCoords, (F32*)(old_buf + mNumVertices * 2), old_tc_size); - } - - // just clear tangents - ll_aligned_free_16(mTangents); - mTangents = NULL; - ll_aligned_free<64>(old_buf); - - mNumAllocatedVertices = new_verts; - - } - - mPositions[mNumVertices] = pos; - mNormals[mNumVertices] = norm; - mTexCoords[mNumVertices] = tc; - - mNumVertices++; -} - void LLVolumeFace::allocateTangents(S32 num_verts) { ll_aligned_free_16(mTangents); @@ -7585,21 +7366,6 @@ void LLVolumeFace::resizeIndices(S32 num_indices) } } -void LLVolumeFace::pushIndex(const U16& idx) -{ - S32 new_count = mNumIndices + 1; - S32 new_size = ((new_count*2)+0xF) & ~0xF; - - S32 old_size = ((mNumIndices*2)+0xF) & ~0xF; - if (new_size != old_size) - { - mIndices = (U16*) ll_aligned_realloc_16(mIndices, new_size, old_size); - ll_assert_aligned(mIndices,16); - } - - mIndices[mNumIndices++] = idx; -} - void LLVolumeFace::fillFromLegacyData(std::vector& v, std::vector& idx) { resizeVertices(static_cast(v.size())); diff --git a/indra/llmath/llvolume.h b/indra/llmath/llvolume.h index 87aa12de9b..4e41753984 100644 --- a/indra/llmath/llvolume.h +++ b/indra/llmath/llvolume.h @@ -858,9 +858,8 @@ class LLDynamicPath : public LLPath class LLVolumeFace { public: - // A vertex as the importers and the optimizer pass it around: position, - // normal and texture coordinate in one block, so a vector of them is one - // allocation. + // A vertex as the importers pass it around: position, normal and texture + // coordinate in one block, so a vector of them is one allocation. class VertexData { public: @@ -875,7 +874,6 @@ class LLVolumeFace bool operator<(const VertexData& rhs) const; bool operator==(const VertexData& rhs) const; - bool compareNormal(const VertexData& rhs, F32 angle_cutoff) const; private: LLVector4a mPosition; @@ -909,14 +907,6 @@ class LLVolumeFace void resizeIndices(S32 num_indices); void fillFromLegacyData(std::vector& v, std::vector& idx); - void pushVertex(const VertexData& cv); - void pushVertex(const LLVector4a& pos, const LLVector4a& norm, const LLVector2& tc); - void pushIndex(const U16& idx); - - void swapData(LLVolumeFace& rhs); - - void getVertexData(U16 indx, LLVolumeFace::VertexData& cv); - class VertexMapData : public LLVolumeFace::VertexData { public: @@ -936,7 +926,6 @@ class LLVolumeFace // normals and texture coordinates into account. void remap(); - void optimize(F32 angle_cutoff = 2.f); bool cacheOptimize(bool gen_tangents = false); // Normals made from the face's shape: smooth across an edge whose two From fd479a908faf85b83aa16bf642fe52a43740b501 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 25 Oct 2026 09:44:06 -0400 Subject: [PATCH 4/5] LLVolumeFace::remap calls meshoptimizer itself remap welds an uploaded face through meshoptimizer's 32-bit entry points, with its scratch from the thread's arena, in place of LLMeshOptimizer's U16 wrappers. The weld is the same for every valid face: the same streams, the same unique count, and each corner given back its vertex. An upload's indices are untrusted, so the check stays: an index past the face's vertices is taken as vertex 0, with the same warning. The index buffer is now remapped through the checked indices too. Before, only the weld saw them, and the remap went through the unchecked ones, which is why its table was sized by the larger of the index and vertex counts. An index past both read beyond the table: the new test, with an index of 65535, crashed there against the old code. An index below the index count took the first vertex the weld numbered instead of vertex 0. The table is now one entry a vertex, as meshoptimizer documents it. Co-Authored-By: Claude Opus 5.5 --- indra/llmath/llvolume.cpp | 68 ++++++++++++++++++------ indra/llmath/tests/llvolume_test.cpp | 77 ++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 17 deletions(-) diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp index 7481880baf..a8cecf7f7a 100644 --- a/indra/llmath/llvolume.cpp +++ b/indra/llmath/llvolume.cpp @@ -51,7 +51,6 @@ #include "llmatrix4a.h" #include "alscratcharena.h" #include "alsimdkernels.h" -#include "llmeshoptimizer.h" #include "lltimer.h" #include "llvolumeoctree.h" #include "workqueue.h" @@ -4963,20 +4962,52 @@ bool LLVolumeFace::VertexMapData::ComparePosition::operator()(const LLVector3& a return a.mV[2] < b.mV[2]; } +// Uploads reach this, so the indices are untrusted: an index past the face's +// vertices is taken as vertex 0, with a warning, before anything reads +// through it. void LLVolumeFace::remap() { - // Generate a remap buffer - // Documentation for meshopt_generateVertexRemapMulti claims that remap should use vertice count - // but all examples use indice count. There are out of bounds crashes when using vertice count. - // To be on the safe side use bigger of the two. - std::vector remap(llmax(mNumIndices, mNumVertices), 0); - S32 remap_vertices_count = static_cast(LLMeshOptimizer::generateRemapMultiU16(&remap[0], - mIndices, - mNumIndices, - mPositions, - mNormals, - mTexCoords, - mNumVertices)); + ALScratchArena::Scope scratch; + + // The indices at 32 bits, as meshoptimizer takes them, checked as they + // go. A face without indices is welded as unindexed geometry. + U32* indices = scratch.allocate(mNumIndices); + const U32* checked = nullptr; + if (mIndices) + { + S32 out_of_range_count = 0; + for (S32 i = 0; i < mNumIndices; ++i) + { + if (mIndices[i] < mNumVertices) + { + indices[i] = mIndices[i]; + } + else + { + out_of_range_count++; + indices[i] = 0; + } + } + + if (out_of_range_count) + { + LL_WARNS() << out_of_range_count << " indices are out of range." << LL_ENDL; + } + checked = indices; + } + + // Each vertex's place among the vertices that differ in position, normal + // or texture coordinate, or ~0u where no index uses it. + const meshopt_Stream streams[] = { + { mPositions, sizeof(F32) * 3, sizeof(LLVector4a) }, + { mNormals, sizeof(F32) * 3, sizeof(LLVector4a) }, + { mTexCoords, sizeof(LLVector2), sizeof(LLVector2) }, + }; + U32* remap = scratch.allocate(mNumVertices); + S32 remap_vertices_count = static_cast(meshopt_generateVertexRemapMulti(remap, checked, checked ? mNumIndices : mNumVertices, + mNumVertices, streams, + sizeof(streams) / sizeof(streams[0]))); + meshopt_remapIndexBuffer(indices, checked, mNumIndices, remap); // Allocate new buffers S32 size = ((mNumIndices * sizeof(U16)) + 0xF) & ~0xF; @@ -4988,10 +5019,13 @@ void LLVolumeFace::remap() 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]); + for (S32 i = 0; i < mNumIndices; ++i) + { + remap_indices[i] = static_cast(indices[i]); + } + meshopt_remapVertexBuffer(remap_positions, mPositions, mNumVertices, sizeof(LLVector4a), remap); + meshopt_remapVertexBuffer(remap_normals, mNormals, mNumVertices, sizeof(LLVector4a), remap); + meshopt_remapVertexBuffer(remap_tex_coords, mTexCoords, mNumVertices, sizeof(LLVector2), remap); // Free unused buffers ll_aligned_free_16(mIndices); diff --git a/indra/llmath/tests/llvolume_test.cpp b/indra/llmath/tests/llvolume_test.cpp index 63ecaba9f2..9cbd534146 100644 --- a/indra/llmath/tests/llvolume_test.cpp +++ b/indra/llmath/tests/llvolume_test.cpp @@ -3526,6 +3526,83 @@ namespace } } + // A vertex whose position, normal and texture coordinate each differ from + // those of a vertex made from another n. + LLVolumeFace::VertexData distinct_vertex(F32 n) + { + LLVolumeFace::VertexData v; + v.setPosition(LLVector4a(n, n + 0.25f, n + 0.5f)); + v.setNormal(LLVector4a(-n, 1.f, n * 2.f)); + v.mTexCoord = LLVector2(n * 0.125f, 1.f - n * 0.125f); + return v; + } + + // A face of these vertices and indices, as an importer hands one to remap. + LLVolumeFace face_of(std::vector vertices, std::vector indices) + { + LLVolumeFace face; + face.fillFromLegacyData(vertices, indices); + return face; + } + + LLVolumeFace::VertexData vertex_at(const LLVolumeFace& face, U16 index) + { + LLVolumeFace::VertexData v; + v.setPosition(face.mPositions[index]); + v.setNormal(face.mNormals[index]); + v.mTexCoord = face.mTexCoords[index]; + return v; + } + + // remap welds an uploaded face as weldVertices does: copies made one, + // unused vertices gone, and every corner given back the vertex it had. + TEST_CASE("llvolume: remap welds copies and drops unused vertices with every corner keeping its vertex", "[llvolume]") + { + // 5 is a copy of 0, and no index uses 6. + const std::vector vertices = { distinct_vertex(1.f), distinct_vertex(2.f), distinct_vertex(3.f), + distinct_vertex(4.f), distinct_vertex(5.f), distinct_vertex(1.f), + distinct_vertex(6.f) }; + const std::vector indices = { 3, 5, 1, 0, 2, 4 }; + + LLVolumeFace face = face_of(vertices, indices); + face.remap(); + + REQUIRE(face.mNumIndices == (S32)indices.size()); + CHECK(face.mNumVertices == 5); + CHECK(face.mIndices[1] == face.mIndices[3]); + for (size_t i = 0; i < indices.size(); ++i) + { + INFO(fmt::format("corner {}, vertex {}, welded {}", i, indices[i], face.mIndices[i])); + REQUIRE(face.mIndices[i] < face.mNumVertices); + CHECK(vertex_at(face, face.mIndices[i]) == vertices[indices[i]]); + } + } + + // An uploaded face's indices are untrusted. remap takes one past the + // face's vertices, however far past, as vertex 0, and reads its weld's + // table, one entry a vertex, only through indices it has checked. + TEST_CASE("llvolume: remap takes an index past the face's vertices as vertex 0", "[llvolume]") + { + const std::vector vertices = { distinct_vertex(1.f), distinct_vertex(2.f), distinct_vertex(3.f), + distinct_vertex(4.f) }; + // Vertex 0 is not the first the indices use, so the weld does not + // number it 0. + const std::vector indices = { 1, 2, 3, 1, 3, 5, 2, 3, 65535 }; + + LLVolumeFace face = face_of(vertices, indices); + face.remap(); + + REQUIRE(face.mNumIndices == (S32)indices.size()); + CHECK(face.mNumVertices == 4); + for (size_t i = 0; i < indices.size(); ++i) + { + const size_t vertex = indices[i] < vertices.size() ? size_t(indices[i]) : size_t(0); + INFO(fmt::format("corner {}, index {}, welded {}", i, indices[i], face.mIndices[i])); + REQUIRE(face.mIndices[i] < face.mNumVertices); + CHECK(vertex_at(face, face.mIndices[i]) == vertices[vertex]); + } + } + // A flat parallelogram as authored, P = a e1 + b e2 for a and b in // [-0.5, 0.5], whose box is 4 by 1 by 1. Its UVs are turned 45 degrees // against its edges, u = (a + b) / 2 + 0.5 and v = (b - a) / 2 + 0.5, so u From 0f979fecba0e56bb835ec6cc8ec67e45da753d84 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 25 Oct 2026 09:52:41 -0400 Subject: [PATCH 5/5] Retire LLMeshOptimizer Its last callers were LLVolumeFace::remap, now on meshoptimizer's own entry points, and the arena setup. Everything else in llmath and the viewer already calls meshoptimizer directly, at 32 bits. LLVector4a::initClass, the llmath startup the viewer already makes, installs ALScratchArena as meshoptimizer's allocator, at the same point in LLAppViewer::init where LLMeshOptimizer::initClass did. The tests and the benchmark that put meshoptimizer on the arena for a scope install it the same way. llmeshoptimizer_test's case moves to llvector4a_test as a test of initClass, run on a thread of its own so the floating-point mode initClass sets stays off the test thread. Co-Authored-By: Claude Opus 5.5 --- indra/llmath/CMakeLists.txt | 3 - indra/llmath/llmeshoptimizer.cpp | 359 ------------------- indra/llmath/llmeshoptimizer.h | 160 --------- indra/llmath/llvector4a.cpp | 4 + indra/llmath/llvector4a.h | 6 +- indra/llmath/tests/almeshsimplifier_test.cpp | 4 +- indra/llmath/tests/llmeshoptimizer_test.cpp | 87 ----- indra/llmath/tests/llvector4a_test.cpp | 60 ++++ indra/llmath/tests/llvolume_bench.cpp | 3 +- indra/llmath/tests/llvolume_test.cpp | 3 +- indra/newview/llappviewer.cpp | 7 +- 11 files changed, 74 insertions(+), 622 deletions(-) delete mode 100644 indra/llmath/llmeshoptimizer.cpp delete mode 100644 indra/llmath/llmeshoptimizer.h delete mode 100644 indra/llmath/tests/llmeshoptimizer_test.cpp diff --git a/indra/llmath/CMakeLists.txt b/indra/llmath/CMakeLists.txt index 7c1a792c57..064d4fd3d1 100644 --- a/indra/llmath/CMakeLists.txt +++ b/indra/llmath/CMakeLists.txt @@ -19,7 +19,6 @@ target_sources( llline.cpp llmatrix3a.cpp llmatrix4a.cpp - llmeshoptimizer.cpp llmodularmath.cpp lloctree.cpp llquaternion.cpp @@ -66,7 +65,6 @@ target_sources( llmatrix3a.h llmatrix3a.inl llmatrix4a.h - llmeshoptimizer.h llmodularmath.h lloctree.h llplane.h @@ -139,7 +137,6 @@ if(AL_BUILD_TESTS) tests/llbboxlocal_test.cpp tests/llmatrix3a_test.cpp tests/llmatrix4a_test.cpp - tests/llmeshoptimizer_test.cpp tests/llmodularmath_test.cpp tests/lloctree_test.cpp tests/llquaternion2_test.cpp diff --git a/indra/llmath/llmeshoptimizer.cpp b/indra/llmath/llmeshoptimizer.cpp deleted file mode 100644 index ce0899cfa4..0000000000 --- a/indra/llmath/llmeshoptimizer.cpp +++ /dev/null @@ -1,359 +0,0 @@ - /** -* @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 "linden_common.h" - -#include "llmeshoptimizer.h" - -#include - -#include "alscratcharena.h" -#include "llmath.h" -#include "v2math.h" - -namespace -{ - void* MESHOPTIMIZER_ALLOC_CALLCONV scratch_allocate(size_t bytes) - { - return ALScratchArena::allocate(bytes); - } - - void MESHOPTIMIZER_ALLOC_CALLCONV scratch_deallocate(void* block) - { - ALScratchArena::deallocate(block); - } -} // namespace - -LLMeshOptimizer::LLMeshOptimizer() -{ -} - -LLMeshOptimizer::~LLMeshOptimizer() -{ -} - -//static -void LLMeshOptimizer::initClass() -{ - meshopt_setAllocator(scratch_allocate, scratch_deallocate); -} - -//static -void LLMeshOptimizer::generateShadowIndexBufferU32(U32 *destination, - const U32 *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; - // 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, - vertex_count, - 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::generateRemapMultiU32( - 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}, - }; - - // Remap can function without indices, - // but providing indices helps with removing unused vertices - U64 indeces_cmp = indices ? index_count : 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) -{ - 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] = (U32)indices[i]; - } - else - { - out_of_range_count++; - indices_u32[i] = 0; - } - } - } - - if (out_of_range_count) - { - LL_WARNS() << out_of_range_count << " indices are out of range." << LL_ENDL; - } - - size_t unique = generateRemapMultiU32(remap, indices_u32, index_count, vertex_positions, normals, text_coords, vertex_count); - - ll_aligned_free_32(indices_u32); - - return unique; -} - -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, - 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(destination, - indices, - index_count, - (const float*)vertex_positions, - vertex_count, - vertex_positions_stride, - target_index_count, - target_error, - result_error - ); - } - else - { - return meshopt_simplify(destination, - indices, - index_count, - (const float*)vertex_positions, - vertex_count, - vertex_positions_stride, - target_index_count, - target_error, - 0, - 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(destination, - indices, - index_count, - (const float*)vertex_positions, - vertex_count, - vertex_positions_stride, - target_index_count, - target_error, - result_error - ); - } - else - { - return meshopt_simplify(destination, - indices, - index_count, - (const float*)vertex_positions, - vertex_count, - vertex_positions_stride, - target_index_count, - target_error, - 0, - result_error - ); - } -} - diff --git a/indra/llmath/llmeshoptimizer.h b/indra/llmath/llmeshoptimizer.h deleted file mode 100644 index f5d4169941..0000000000 --- a/indra/llmath/llmeshoptimizer.h +++ /dev/null @@ -1,160 +0,0 @@ -/** -* @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" - -class LLVector4a; -class LLVector2; - -class LLMeshOptimizer -{ -public: - LLMeshOptimizer(); - ~LLMeshOptimizer(); - - // Makes meshoptimizer take its scratch from the calling thread's - // ALScratchArena. Called once, before any thread calls meshoptimizer. - static void initClass(); - - 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 * 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 - // Welds indentical vertexes together. - // Removes unused vertices if indices were provided. - - static size_t generateRemapMultiU32( - unsigned int* remap, - const U32 * indices, - U64 index_count, - const LLVector4a * vertex_positions, - const LLVector4a * normals, - 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, - 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, - 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 - // 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/llmath/llvector4a.cpp b/indra/llmath/llvector4a.cpp index 288143415a..8acd1248bb 100644 --- a/indra/llmath/llvector4a.cpp +++ b/indra/llmath/llvector4a.cpp @@ -26,17 +26,21 @@ #include "linden_common.h" +#include "alscratcharena.h" #include "llmemory.h" #include "llmath.h" #include "llquantize.h" #include "llthread.h" +#include + extern const LLVector4a LL_V4A_ZERO(0.f, 0.f, 0.f, 0.f); extern const LLVector4a LL_V4A_EPSILON(F_APPROXIMATELY_ZERO, F_APPROXIMATELY_ZERO, F_APPROXIMATELY_ZERO, F_APPROXIMATELY_ZERO); /*static */void LLVector4a::initClass() { set_thread_fp_mode(); + meshopt_setAllocator(ALScratchArena::allocate, ALScratchArena::deallocate); } /*static */void LLVector4a::memcpyNonAliased16(F32* __restrict dst, const F32* __restrict src, size_t bytes) diff --git a/indra/llmath/llvector4a.h b/indra/llmath/llvector4a.h index 44d45c8168..def450c47d 100644 --- a/indra/llmath/llvector4a.h +++ b/indra/llmath/llvector4a.h @@ -46,8 +46,10 @@ class alignas(16) LLVector4a // STATIC METHODS /////////////////////////////////// - // Sets the main thread's floating-point mode at startup; worker threads - // set their own at entry (set_thread_fp_mode in llthread.h). + // llmath's startup, once, before any other thread starts: sets the main + // thread's floating-point mode, worker threads setting their own at + // entry (set_thread_fp_mode in llthread.h), and makes meshoptimizer take + // its scratch from the calling thread's ALScratchArena. static void initClass(); // Return a vector of all zeros diff --git a/indra/llmath/tests/almeshsimplifier_test.cpp b/indra/llmath/tests/almeshsimplifier_test.cpp index 1594fed00e..786bcabc53 100644 --- a/indra/llmath/tests/almeshsimplifier_test.cpp +++ b/indra/llmath/tests/almeshsimplifier_test.cpp @@ -26,8 +26,8 @@ #include "linden_common.h" #include "almeshsimplifier.h" +#include "alscratcharena.h" #include "llmath.h" -#include "llmeshoptimizer.h" #include "llvector4a.h" #include "llvolume.h" #include "v2math.h" @@ -66,7 +66,7 @@ namespace static void* MESHOPTIMIZER_ALLOC_CALLCONV newBlock(size_t bytes) { return ::operator new(bytes); } static void MESHOPTIMIZER_ALLOC_CALLCONV deleteBlock(void* block) { ::operator delete(block); } - ArenaInstalled() { LLMeshOptimizer::initClass(); } + ArenaInstalled() { meshopt_setAllocator(ALScratchArena::allocate, ALScratchArena::deallocate); } ~ArenaInstalled() { meshopt_setAllocator(newBlock, deleteBlock); } }; diff --git a/indra/llmath/tests/llmeshoptimizer_test.cpp b/indra/llmath/tests/llmeshoptimizer_test.cpp deleted file mode 100644 index b044a694aa..0000000000 --- a/indra/llmath/tests/llmeshoptimizer_test.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/** - * @file llmeshoptimizer_test.cpp - * @brief meshoptimizer's scratch from the calling thread's arena. - * - * $LicenseInfo:firstyear=2026&license=viewerlgpl$ - * Alchemy Viewer Source Code - * Copyright (C) 2026, Rye - * - * 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 - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "llmeshoptimizer.h" -#include "alscratcharena.h" - -#include -#include - -#include -#include - -namespace -{ - // meshoptimizer on the arena while it lives, and on its own default, - // operator new and delete, after. - struct ArenaInstalled - { - static void* MESHOPTIMIZER_ALLOC_CALLCONV newBlock(size_t bytes) { return ::operator new(bytes); } - static void MESHOPTIMIZER_ALLOC_CALLCONV deleteBlock(void* block) { ::operator delete(block); } - - ArenaInstalled() { LLMeshOptimizer::initClass(); } - ~ArenaInstalled() { meshopt_setAllocator(newBlock, deleteBlock); } - }; - - // Vertex i is (3i, 3i + 1, 3i + 2) modulo 97 a coordinate: 97 vertices, - // each count times over. - void repeating(size_t count, std::vector& indices, std::vector& positions) - { - indices.resize(count); - std::iota(indices.begin(), indices.end(), 0); - positions.resize(count * 3); - for (size_t i = 0; i < positions.size(); ++i) - { - positions[i] = (F32)(i % 97); - } - } - - TEST_CASE("llmeshoptimizer: meshoptimizer takes its blocks from the arena", "[llmeshoptimizer]") - { - ArenaInstalled installed; - std::vector indices; - std::vector positions; - repeating(3000, indices, positions); - std::vector remap(indices.size()); - - ALScratchArena::resetCounters(); - CHECK(meshopt_generateVertexRemap(remap.data(), indices.data(), indices.size(), positions.data(), indices.size(), sizeof(F32) * 3) == 97); - ALScratchArena::Counters counters = ALScratchArena::counters(); - CHECK(counters.arena_blocks > 0); - CHECK(counters.heap_blocks == 0); - - // A hash table for more vertices than the arena holds comes from the - // heap, and the weld still comes out. - const size_t big = ALScratchArena::CAPACITY / sizeof(U32); - repeating(big, indices, positions); - remap.resize(big); - ALScratchArena::resetCounters(); - CHECK(meshopt_generateVertexRemap(remap.data(), indices.data(), indices.size(), positions.data(), big, sizeof(F32) * 3) == 97); - counters = ALScratchArena::counters(); - CHECK(counters.heap_blocks > 0); - CHECK(remap[97] == remap[0]); - } -} diff --git a/indra/llmath/tests/llvector4a_test.cpp b/indra/llmath/tests/llvector4a_test.cpp index 3479dc3f92..27712c557f 100644 --- a/indra/llmath/tests/llvector4a_test.cpp +++ b/indra/llmath/tests/llvector4a_test.cpp @@ -42,12 +42,18 @@ #include "../llquaternion.h" #include "../m3math.h" #include "../v3math.h" +#include "alscratcharena.h" + +#include #include #include #include #include +#include #include +#include +#include namespace { @@ -760,4 +766,58 @@ namespace // a dot product comes back in the lane that is read CHECK(n_a().dot3(LLVector4a(1.f, 0.f, 0.f)).getF32() == na[0]); } + + // meshoptimizer back on its own default, operator new and delete, as + // the scope ends. + struct DefaultAllocatorAfter + { + static void* MESHOPTIMIZER_ALLOC_CALLCONV newBlock(size_t bytes) { return ::operator new(bytes); } + static void MESHOPTIMIZER_ALLOC_CALLCONV deleteBlock(void* block) { ::operator delete(block); } + + ~DefaultAllocatorAfter() { meshopt_setAllocator(newBlock, deleteBlock); } + }; + + // Vertex i is (3i, 3i + 1, 3i + 2) modulo 97 a coordinate: 97 vertices, + // each count times over. + void repeating(size_t count, std::vector& indices, std::vector& positions) + { + indices.resize(count); + std::iota(indices.begin(), indices.end(), 0); + positions.resize(count * 3); + for (size_t i = 0; i < positions.size(); ++i) + { + positions[i] = (F32)(i % 97); + } + } + + TEST_CASE("LLVector4a: initClass has meshoptimizer take its blocks from the arena", "[LLVector4a]") + { + // initClass also sets the floating-point mode of the thread it runs + // on, so it runs on a thread of its own; the allocator it sets is + // every thread's. + DefaultAllocatorAfter restore; + std::thread([] { LLVector4a::initClass(); }).join(); + + std::vector indices; + std::vector positions; + repeating(3000, indices, positions); + std::vector remap(indices.size()); + + ALScratchArena::resetCounters(); + CHECK(meshopt_generateVertexRemap(remap.data(), indices.data(), indices.size(), positions.data(), indices.size(), sizeof(F32) * 3) == 97); + ALScratchArena::Counters counters = ALScratchArena::counters(); + CHECK(counters.arena_blocks > 0); + CHECK(counters.heap_blocks == 0); + + // A hash table for more vertices than the arena holds comes from the + // heap, and the weld still comes out. + const size_t big = ALScratchArena::CAPACITY / sizeof(U32); + repeating(big, indices, positions); + remap.resize(big); + ALScratchArena::resetCounters(); + CHECK(meshopt_generateVertexRemap(remap.data(), indices.data(), indices.size(), positions.data(), big, sizeof(F32) * 3) == 97); + counters = ALScratchArena::counters(); + CHECK(counters.heap_blocks > 0); + CHECK(remap[97] == remap[0]); + } } diff --git a/indra/llmath/tests/llvolume_bench.cpp b/indra/llmath/tests/llvolume_bench.cpp index ffb1d89dc7..ee7e0436c6 100644 --- a/indra/llmath/tests/llvolume_bench.cpp +++ b/indra/llmath/tests/llvolume_bench.cpp @@ -41,7 +41,6 @@ #include "alscratcharena.h" #include "alsimdkernels.h" #include "llmath.h" -#include "llmeshoptimizer.h" #include "llquaternion.h" #include "m3math.h" #include "v2math.h" @@ -567,7 +566,7 @@ namespace static void* MESHOPTIMIZER_ALLOC_CALLCONV newBlock(size_t bytes) { return ::operator new(bytes); } static void MESHOPTIMIZER_ALLOC_CALLCONV deleteBlock(void* block) { ::operator delete(block); } - ArenaInstalled() { LLMeshOptimizer::initClass(); } + ArenaInstalled() { meshopt_setAllocator(ALScratchArena::allocate, ALScratchArena::deallocate); } ~ArenaInstalled() { meshopt_setAllocator(newBlock, deleteBlock); } }; diff --git a/indra/llmath/tests/llvolume_test.cpp b/indra/llmath/tests/llvolume_test.cpp index 9cbd534146..70fd3dc5aa 100644 --- a/indra/llmath/tests/llvolume_test.cpp +++ b/indra/llmath/tests/llvolume_test.cpp @@ -52,7 +52,6 @@ #include "llvector4a.h" #include "alscratcharena.h" #include "alsimdkernels.h" -#include "llmeshoptimizer.h" #include "almeshcorpus.h" #include "altestfatal.h" @@ -2523,7 +2522,7 @@ namespace static void* MESHOPTIMIZER_ALLOC_CALLCONV newBlock(size_t bytes) { return ::operator new(bytes); } static void MESHOPTIMIZER_ALLOC_CALLCONV deleteBlock(void* block) { ::operator delete(block); } - ArenaInstalled() { LLMeshOptimizer::initClass(); } + ArenaInstalled() { meshopt_setAllocator(ALScratchArena::allocate, ALScratchArena::deallocate); } ~ArenaInstalled() { meshopt_setAllocator(newBlock, deleteBlock); } }; diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 938bd8b681..bff29fed13 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -97,7 +97,6 @@ #include "lltoast.h" #include "llsdutil_math.h" #include "lllocationhistory.h" -#include "llmeshoptimizer.h" #include "llvector4a.h" #include "llviewermenufile.h" #include "llvoicechannel.h" @@ -808,12 +807,10 @@ bool LLAppViewer::init() // the translation bridge LLWearableType reads its labels through LLTranslationBridge::ptr_t trans = std::make_shared(); - // initialize SSE options + // llmath: the floating-point mode, and meshoptimizer's scratch from each + // thread's own arena LLVector4a::initClass(); - // meshoptimizer's scratch from each thread's own arena - LLMeshOptimizer::initClass(); - //initialize particle index pool LLVOPartGroup::initClass();