summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrey Kleshchev <andreykproductengine@lindenlab.com>2021-08-04 21:14:00 +0300
committerAndrey Kleshchev <andreykproductengine@lindenlab.com>2021-08-04 21:14:00 +0300
commit1a1793244002effe46cedf63180de60f4bc69a9a (patch)
tree0550c807886ab4bad74620c82edfb8b5bc11a4cd
parent7235d333ea24388fc13a6d01dbafc707b658a0d4 (diff)
DRTVWR-542 Automated method selection
Normally simplification methods apply for whole upload, but this one selects methods per model or per face.
-rw-r--r--indra/llmeshoptimizer/llmeshoptimizer.cpp97
-rw-r--r--indra/llmeshoptimizer/llmeshoptimizer.h21
-rw-r--r--indra/llprimitive/lldaeloader.cpp32
-rw-r--r--indra/llprimitive/llmodel.cpp3
-rw-r--r--indra/llprimitive/llmodel.h5
-rw-r--r--indra/newview/llfloatermodelpreview.cpp6
-rw-r--r--indra/newview/llmodelpreview.cpp631
-rw-r--r--indra/newview/llmodelpreview.h7
-rw-r--r--indra/newview/skins/default/xui/en/floater_model_preview.xml48
9 files changed, 481 insertions, 369 deletions
diff --git a/indra/llmeshoptimizer/llmeshoptimizer.cpp b/indra/llmeshoptimizer/llmeshoptimizer.cpp
index 8097a05511..a879389c5a 100644
--- a/indra/llmeshoptimizer/llmeshoptimizer.cpp
+++ b/indra/llmeshoptimizer/llmeshoptimizer.cpp
@@ -67,19 +67,36 @@ U64 LLMeshOptimizer::simplifyU32(U32 *destination,
U64 vertex_positions_stride,
U64 target_index_count,
F32 target_error,
+ bool sloppy,
F32* result_error
)
{
- 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
- );
+ 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
@@ -91,41 +108,35 @@ U64 LLMeshOptimizer::simplify(U16 *destination,
U64 vertex_positions_stride,
U64 target_index_count,
F32 target_error,
+ bool sloppy,
F32* result_error
)
{
- 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
- );
+ 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
+ );
+ }
}
-//static
-U64 LLMeshOptimizer::simplifySloppy(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,
- F32* result_error
- )
-{
- 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
- );
-}
diff --git a/indra/llmeshoptimizer/llmeshoptimizer.h b/indra/llmeshoptimizer/llmeshoptimizer.h
index e881ec26c5..e8dd16dae9 100644
--- a/indra/llmeshoptimizer/llmeshoptimizer.h
+++ b/indra/llmeshoptimizer/llmeshoptimizer.h
@@ -45,6 +45,8 @@ public:
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(
@@ -56,10 +58,13 @@ public:
U64 vertex_positions_stride,
U64 target_index_count,
F32 target_error,
+ bool sloppy,
F32* result_error);
// Returns amount of indices in destiantion
- // Result_error returns how far from original the model is in % if not NULL
+ // 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,
@@ -70,19 +75,7 @@ public:
U64 vertex_positions_stride,
U64 target_index_count,
F32 target_error,
- F32* result_error);
-
- // returns amount of indices in destiantion
- // result_error returns how far from original the model is in % if not NULL
- static U64 simplifySloppy(
- 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:
};
diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp
index 17e9e05edd..dcf3b5fa0e 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,
+ bool &generated_additional_faces)
{
LLVolumeFace face;
std::vector<LLVolumeFace::VertexData> verts;
@@ -282,6 +286,8 @@ LLModel::EModelStatus load_face_from_dom_triangles(std::vector<LLVolumeFace>& fa
if (indices.size()%3 == 0 && verts.size() >= 65532)
{
+ generated_additional_faces = true;
+
std::string material;
if (tri->getMaterial())
@@ -289,7 +295,6 @@ LLModel::EModelStatus load_face_from_dom_triangles(std::vector<LLVolumeFace>& fa
material = std::string(tri->getMaterial());
}
- // Todo: mark model in some way as having generated(split) faces
materials.push_back(material);
face_list.push_back(face);
face_list.rbegin()->fillFromLegacyData(verts, indices);
@@ -344,7 +349,12 @@ 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,
+ bool& generated_additional_faces,
+ LLSD& log_msg)
{
domPRef p = poly->getP();
domListOfUInts& idx = p->getValue();
@@ -546,6 +556,8 @@ LLModel::EModelStatus load_face_from_dom_polylist(std::vector<LLVolumeFace>& fac
if (indices.size()%3 == 0 && indices.size() >= 65532)
{
+ generated_additional_faces = true;
+
std::string material;
if (poly->getMaterial())
@@ -771,6 +783,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());
@@ -2390,11 +2405,13 @@ bool LLDAELoader::addVolumeFacesFromDomMesh(LLModel* pModel,domMesh* mesh, LLSD&
LLModel::EModelStatus status = LLModel::NO_ERRORS;
domTriangles_Array& tris = mesh->getTriangles_array();
+ pModel->mHasGeneratedFaces = false;
+
for (U32 i = 0; i < tris.getCount(); ++i)
{
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, pModel->mHasGeneratedFaces);
pModel->mStatus = status;
if(status != LLModel::NO_ERRORS)
{
@@ -2407,7 +2424,7 @@ bool LLDAELoader::addVolumeFacesFromDomMesh(LLModel* pModel,domMesh* mesh, LLSD&
for (U32 i = 0; i < polys.getCount(); ++i)
{
domPolylistRef& poly = polys.get(i);
- status = load_face_from_dom_polylist(pModel->getVolumeFaces(), pModel->getMaterialList(), poly, log_msg);
+ status = load_face_from_dom_polylist(pModel->getVolumeFaces(), pModel->getMaterialList(), poly, pModel->mHasGeneratedFaces, log_msg);
if(status != LLModel::NO_ERRORS)
{
@@ -2421,6 +2438,10 @@ bool LLDAELoader::addVolumeFacesFromDomMesh(LLModel* pModel,domMesh* mesh, LLSD&
for (U32 i = 0; i < polygons.getCount(); ++i)
{
domPolygonsRef& poly = polygons.get(i);
+
+ // Due to how poligons work, assume that face was 'generated' by default
+ pModel->mHasGeneratedFaces = true;
+
status = load_face_from_dom_polygons(pModel->getVolumeFaces(), pModel->getMaterialList(), poly);
if(status != LLModel::NO_ERRORS)
@@ -2520,6 +2541,7 @@ bool LLDAELoader::loadModelsFromDomMesh(domMesh* mesh, std::vector<LLModel*>& mo
{
LLModel* next = new LLModel(volume_params, 0.f);
next->mSubmodelID = ++submodelID;
+ next->mHasGeneratedFaces = ret->mHasGeneratedFaces;
next->mLabel = model_name + (char)((int)'a' + next->mSubmodelID) + lod_suffix[mLod];
next->getVolumeFaces() = remainder;
next->mNormalizedScale = ret->mNormalizedScale;
diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp
index 702a1b5238..8b8fde0ea0 100644
--- a/indra/llprimitive/llmodel.cpp
+++ b/indra/llprimitive/llmodel.cpp
@@ -55,7 +55,8 @@ LLModel::LLModel(LLVolumeParams& params, F32 detail)
mNormalizedTranslation(0,0,0),
mPelvisOffset( 0.0f ),
mStatus(NO_ERRORS),
- mSubmodelID(0)
+ mSubmodelID(0),
+ mHasGeneratedFaces(false)
{
mDecompID = -1;
mLocalID = -1;
diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h
index 96368d64e5..87a47dcb36 100644
--- a/indra/llprimitive/llmodel.h
+++ b/indra/llprimitive/llmodel.h
@@ -284,6 +284,11 @@ public:
// A model/object can only have 8 faces, spillover faces will
// be moved to new model/object and assigned a submodel id.
int mSubmodelID;
+ // A .dae face can have more than 65K vertices, but viewer
+ // is limited to U16 for indices, in such case spilower will
+ // be moved into new face and this will be set to true.
+ // Also true in case faces were generated from polygons
+ bool mHasGeneratedFaces;
};
typedef std::vector<LLPointer<LLModel> > model_list;
diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp
index e5d451e909..6795ea8f53 100644
--- a/indra/newview/llfloatermodelpreview.cpp
+++ b/indra/newview/llfloatermodelpreview.cpp
@@ -139,7 +139,7 @@ mAvatarTabIndex(0)
mLODMode[LLModel::LOD_HIGH] = LLModelPreview::LOD_FROM_FILE;
for (U32 i = 0; i < LLModel::LOD_HIGH; i++)
{
- mLODMode[i] = LLModelPreview::MESH_OPTIMIZER;
+ mLODMode[i] = LLModelPreview::MESH_OPTIMIZER_AUTO;
}
}
@@ -729,6 +729,7 @@ void LLFloaterModelPreview::onLODParamCommit(S32 lod, bool enforce_tri_limit)
case LLModelPreview::GENERATE:
mModelPreview->onLODGenerateParamCommit(lod, enforce_tri_limit);
break;
+ case LLModelPreview::MESH_OPTIMIZER_AUTO:
case LLModelPreview::MESH_OPTIMIZER:
case LLModelPreview::MESH_OPTIMIZER_SLOPPY:
case LLModelPreview::MESH_OPTIMIZER_COMBINE:
@@ -1738,6 +1739,7 @@ void LLFloaterModelPreview::onLoDSourceCommit(S32 lod)
LLComboBox* lod_source_combo = getChild<LLComboBox>("lod_source_" + lod_name[lod]);
S32 index = lod_source_combo->getCurrentIndex();
if (index == LLModelPreview::GENERATE
+ || index == LLModelPreview::MESH_OPTIMIZER_AUTO
|| index == LLModelPreview::MESH_OPTIMIZER
|| index == LLModelPreview::MESH_OPTIMIZER_SLOPPY
|| index == LLModelPreview::MESH_OPTIMIZER_COMBINE)
@@ -1771,7 +1773,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::MESH_OPTIMIZER);
+ getChild<LLComboBox>("lod_source_" + lod_name[lod])->setCurrentByIndex(LLModelPreview::MESH_OPTIMIZER_AUTO);
childSetValue("lod_file_" + lod_name[lod], "");
}
diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp
index 11a536473c..3a8676d7b9 100644
--- a/indra/newview/llmodelpreview.cpp
+++ b/indra/newview/llmodelpreview.cpp
@@ -1706,6 +1706,307 @@ void LLModelPreview::genGlodLODs(S32 which_lod, U32 decimation, bool enforce_tri
}
}
+// 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
+F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *target_model, F32 indices_decimator, F32 error_threshold, bool sloppy)
+{
+ // Figure out buffer size
+ S32 size_indices = 0;
+ S32 size_vertices = 0;
+
+ for (U32 face_idx = 0; face_idx < base_model->getNumVolumeFaces(); ++face_idx)
+ {
+ const LLVolumeFace &face = base_model->getVolumeFace(face_idx);
+ size_indices += face.mNumIndices;
+ size_vertices += face.mNumVertices;
+ }
+
+ // 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)
+ {
+ 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;
+ }
+
+ // 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;
+
+ 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 (result_code < 0)
+ {
+ 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;
+ }
+
+ // repack back into individual faces
+
+ LLVector4a* buffer_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 2 * size_vertices + tc_bytes_size);
+ LLVector4a* buffer_normals = buffer_positions + size_vertices;
+ LLVector2* buffer_tex_coords = (LLVector2*)(buffer_normals + size_vertices);
+ U16* buffer_indices = (U16*)ll_aligned_malloc_16(U16_MAX * sizeof(U16));
+ S32* old_to_new_positions_map = new S32[size_vertices];
+
+ S32 buf_positions_copied = 0;
+ S32 buf_indices_copied = 0;
+ indices_idx_shift = 0;
+
+ // Crude method to copy indices back into face
+ for (U32 face_idx = 0; face_idx < base_model->getNumVolumeFaces(); ++face_idx)
+ {
+ 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)
+ {
+ U32 idx = output_indices[i];
+
+ if ((i % 3) == 0)
+ {
+ copy_triangle = idx >= indices_idx_shift && idx < range;
+ }
+
+ 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;
+ }
+
+ // 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];
+
+ old_to_new_positions_map[idx] = buf_positions_copied;
+
+ 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++;
+ }
+ }
+
+ if (buf_positions_copied >= U16_MAX)
+ {
+ break;
+ }
+
+ LLVolumeFace &new_face = target_model->getVolumeFace(face_idx);
+ //new_face = face; //temp
+
+ if (buf_indices_copied < 3)
+ {
+ // 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);
+
+ S32 idx_size = (buf_indices_copied * sizeof(U16) + 0xF) & ~0xF;
+ LLVector4a::memcpyNonAliased16((F32*)new_face.mIndices, (F32*)buffer_indices, idx_size);
+
+ 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;
+ }
+
+ 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_32(buffer_indices);
+ ll_aligned_free_32(combined_indices);
+
+ if (new_indices <= 0)
+ {
+ 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 == 0)
+ {
+ 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
+ {
+ // 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);
+
+ // clear unused values
+ new_face.optimize();
+ }
+
+ ll_aligned_free_16(output);
+
+ if (new_indices <= 0)
+ {
+ return -1;
+ }
+
+ 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;
@@ -1736,7 +2037,7 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d
// 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_ratio = 0;
+ F32 indices_decimator = 0;
F32 triangle_limit = 0;
F32 lod_error_threshold = 1; //100%
@@ -1767,7 +2068,7 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d
}
// meshoptimizer doesn't use triangle limit, it uses indices limit, so convert it to aproximate ratio
- indices_ratio = triangle_limit / (F32)base_triangle_count;
+ indices_decimator = (F32)base_triangle_count / triangle_limit;
}
else
{
@@ -1776,8 +2077,8 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d
}
else
{
- // we are genrating all lods and each lod will get own indices_ratio
- indices_ratio = 1;
+ // we are genrating all lods and each lod will get own indices_decimator
+ indices_decimator = 1;
triangle_limit = base_triangle_count;
}
@@ -1810,7 +2111,7 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d
// we are genrating all lods and each lod gets own indices_ratio
if (lod < start)
{
- indices_ratio /= decimation;
+ indices_decimator *= decimation;
triangle_limit /= decimation;
}
}
@@ -1846,308 +2147,64 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d
// but combine all submodels with origin model as well
if (model_meshopt_mode == MESH_OPTIMIZER_COMBINE)
{
- // Figure out buffer size
- S32 size_indices = 0;
- S32 size_vertices = 0;
+ // Run meshoptimizer for each model/object, up to 8 faces in one model
+ // Ideally this should run not per model,
+ // but combine all submodels with origin model as well
+ genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, false);
+ }
+
+ if (model_meshopt_mode == MESH_OPTIMIZER)
+ {
+ // Run meshoptimizer for each face
for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx)
{
- const LLVolumeFace &face = base->getVolumeFace(face_idx);
- size_indices += face.mNumIndices;
- size_vertices += face.mNumVertices;
+ genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, false);
}
+ }
- // 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;
+ if (model_meshopt_mode == MESH_OPTIMIZER_SLOPPY)
+ {
+ // Run meshoptimizer for each face
for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx)
{
- const LLVolumeFace &face = base->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;
- }
-
- // 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;
-
- target_indices = llmax(3, llfloor(size_indices * indices_ratio)); // 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,
- lod_error_threshold,
- &result_code);
-
-
- if (result_code < 0)
- {
- 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;
+ genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, true);
}
+ }
- // repack back into individual faces
-
- LLVector4a* buffer_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 2 * size_vertices + tc_bytes_size);
- LLVector4a* buffer_normals = buffer_positions + size_vertices;
- LLVector2* buffer_tex_coords = (LLVector2*)(buffer_normals + size_vertices);
- U16* buffer_indices = (U16*)ll_aligned_malloc_16(U16_MAX * sizeof(U16));
- S32* old_to_new_positions_map = new S32[size_vertices];
-
- S32 buf_positions_copied = 0;
- S32 buf_indices_copied = 0;
- indices_idx_shift = 0;
-
- // Crude method to copy indices back into face
- for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx)
+ if (model_meshopt_mode == MESH_OPTIMIZER_AUTO)
+ {
+ F32 allowed_ratio_drift = 2.f;
+ S32 res = 0;
+ if (base->mHasGeneratedFaces)
{
- const LLVolumeFace &face = base->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)
- {
- U32 idx = output_indices[i];
-
- if ((i % 3) == 0)
- {
- copy_triangle = idx >= indices_idx_shift && idx < range;
- }
-
- 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: " << lod_error_threshold
- << LL_ENDL;
- model_meshopt_mode = MESH_OPTIMIZER;
- break;
- }
-
- // 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];
-
- old_to_new_positions_map[idx] = buf_positions_copied;
-
- 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++;
- }
- }
-
- if (buf_positions_copied >= U16_MAX)
- {
- break;
- }
-
- LLVolumeFace &new_face = target_model->getVolumeFace(face_idx);
- //new_face = face; //temp
+ res = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, false);
- if (buf_indices_copied < 3)
+ if (res * allowed_ratio_drift < indices_decimator)
{
- // 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();
+ res = 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;
}
else
{
- new_face.resizeIndices(buf_indices_copied);
- new_face.resizeVertices(buf_positions_copied);
-
- S32 idx_size = (buf_indices_copied * sizeof(U16) + 0xF) & ~0xF;
- LLVector4a::memcpyNonAliased16((F32*)new_face.mIndices, (F32*)buffer_indices, idx_size);
-
- 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);
+ LL_INFOS() << "Model " << target_model->getName()
+ << " lod " << which_lod
+ << " simplified using per model method." << LL_ENDL;
}
-
- indices_idx_shift += face.mNumVertices;
}
-
- 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_32(buffer_indices);
- ll_aligned_free_32(combined_indices);
- }
-
- if (model_meshopt_mode == MESH_OPTIMIZER
- || model_meshopt_mode == MESH_OPTIMIZER_SLOPPY)
- {
- // Run meshoptimizer for each face
- for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx)
+ else
{
- const LLVolumeFace &face = base->getVolumeFace(face_idx);
- S32 num_indices = face.mNumIndices;
- // todo: do not allocate per each face, add one large buffer somewhere
- // faces have limited amount of indices
- S32 size = (num_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;
-
- if (model_meshopt_mode == MESH_OPTIMIZER_SLOPPY)
- {
- target_indices = llfloor(num_indices * indices_ratio);
- new_indices = LLMeshOptimizer::simplifySloppy(
- output,
- face.mIndices,
- num_indices,
- face.mPositions,
- face.mNumVertices,
- LLVertexBuffer::sTypeSize[LLVertexBuffer::TYPE_VERTEX],
- target_indices,
- lod_error_threshold,
- &result_code);
- }
-
- if (model_meshopt_mode == MESH_OPTIMIZER)
- {
- target_indices = llmax(3, llfloor(num_indices * indices_ratio)); // leave at least one triangle
- new_indices = LLMeshOptimizer::simplify(
- output,
- face.mIndices,
- num_indices,
- face.mPositions,
- face.mNumVertices,
- LLVertexBuffer::sTypeSize[LLVertexBuffer::TYPE_VERTEX],
- target_indices,
- lod_error_threshold,
- &result_code);
- }
-
-
- if (result_code < 0)
+ for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx)
{
- 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: " << num_indices
- << " error treshold: " << lod_error_threshold
- << LL_ENDL;
- }
-
- LLVolumeFace &new_face = target_model->getVolumeFace(face_idx);
-
- // Copy old values
- new_face = face;
-
-
- if (new_indices == 0)
- {
- if (meshopt_mode != MESH_OPTIMIZER_SLOPPY)
+ res = genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, false);
+
+ if (res * allowed_ratio_drift < indices_decimator)
{
- // 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: " << num_indices
- << " error treshold: " << lod_error_threshold
- << LL_ENDL;
+ res = genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, true);
}
-
- // 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
- {
- // 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);
-
- // clear unused values
- new_face.optimize();
- }
-
- ll_aligned_free_16(output);
}
}
@@ -2799,7 +2856,7 @@ void LLModelPreview::updateLodControls(S32 lod)
}
else // auto generate, the default case for all LoDs except High
{
- fmp->mLODMode[lod] = MESH_OPTIMIZER;
+ fmp->mLODMode[lod] = MESH_OPTIMIZER_AUTO;
//don't actually regenerate lod when refreshing UI
mLODFrozen = true;
@@ -4040,7 +4097,7 @@ bool LLModelPreview::lodQueryCallback()
{
S32 lod = preview->mLodsQuery.back();
preview->mLodsQuery.pop_back();
- preview->genMeshOptimizerLODs(lod, MESH_OPTIMIZER);
+ preview->genMeshOptimizerLODs(lod, MESH_OPTIMIZER_AUTO);
if (preview->mLookUpLodFiles && (lod == LLModel::LOD_HIGH))
{
diff --git a/indra/newview/llmodelpreview.h b/indra/newview/llmodelpreview.h
index b784345b5a..b3296fecf6 100644
--- a/indra/newview/llmodelpreview.h
+++ b/indra/newview/llmodelpreview.h
@@ -125,9 +125,10 @@ public:
{
LOD_FROM_FILE = 0,
GENERATE,
+ MESH_OPTIMIZER_AUTO, // automatically selects method based on model or face
+ MESH_OPTIMIZER_COMBINE,
MESH_OPTIMIZER,
MESH_OPTIMIZER_SLOPPY,
- MESH_OPTIMIZER_COMBINE,
USE_LOD_ABOVE,
} eLoDMode;
@@ -229,6 +230,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;
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 cd67adf42a..db9d296fa5 100644
--- a/indra/newview/skins/default/xui/en/floater_model_preview.xml
+++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml
@@ -178,16 +178,20 @@
value="Generate" />
<item
name="MeshOpt"
+ label="Simplify (auto)"
+ value="MeshOpt" />
+ <item
+ name="MeshOptCombine"
+ label="Simplify per object"
+ value="MeshOptCombine" />
+ <item
+ name="MeshOpt"
label="Simplify per face"
value="MeshOpt" />
<item
name="MeshOptSloppy"
label="Simplify sloppy"
value="MeshOptSloppy" />
- <item
- name="MeshOptCombine"
- label="Simplify per object"
- value="MeshOptCombine" />
</combo_box>
<line_editor
follows="left|top"
@@ -319,6 +323,14 @@
value="Generate" />
<item
name="MeshOpt"
+ label="Simplify (auto)"
+ value="MeshOpt" />
+ <item
+ name="MeshOptCombine"
+ label="Simplify per object"
+ value="MeshOptCombine" />
+ <item
+ name="MeshOpt"
label="Simplify per face"
value="MeshOpt" />
<item
@@ -326,10 +338,6 @@
label="Simplify sloppy"
value="MeshOptSloppy" />
<item
- name="MeshOptCombine"
- label="Simplify per object"
- value="MeshOptCombine" />
- <item
name="Use LoD above"
label="Use LoD above"
value="Use LoD above" />
@@ -464,6 +472,14 @@
value="Generate" />
<item
name="MeshOpt"
+ label="Simplify (auto)"
+ value="MeshOpt" />
+ <item
+ name="MeshOptCombine"
+ label="Simplify per object"
+ value="MeshOptCombine" />
+ <item
+ name="MeshOpt"
label="Simplify per face"
value="MeshOpt" />
<item
@@ -471,10 +487,6 @@
label="Simplify sloppy"
value="MeshOptSloppy" />
<item
- name="MeshOptCombine"
- label="Simplify per object"
- value="MeshOptCombine" />
- <item
name="Use LoD above"
label="Use LoD above"
value="Use LoD above" />
@@ -609,6 +621,14 @@
value="Generate" />
<item
name="MeshOpt"
+ label="Simplify (auto)"
+ value="MeshOpt" />
+ <item
+ name="MeshOptCombine"
+ label="Simplify per object"
+ value="MeshOptCombine" />
+ <item
+ name="MeshOpt"
label="Simplify per face"
value="MeshOpt" />
<item
@@ -616,10 +636,6 @@
label="Simplify sloppy"
value="MeshOptSloppy" />
<item
- name="MeshOptCombine"
- label="Simplify per object"
- value="MeshOptCombine" />
- <item
name="Use LoD above"
label="Use LoD above"
value="Use LoD above" />