diff --git a/indra/llprimitive/llprimitive.cpp b/indra/llprimitive/llprimitive.cpp index c4b4447a23..33f47f6096 100644 --- a/indra/llprimitive/llprimitive.cpp +++ b/indra/llprimitive/llprimitive.cpp @@ -262,7 +262,14 @@ LLTextureEntry* LLPrimitive::getTE(const U8 index) const //=============================================================== void LLPrimitive::setNumTEs(const U8 num_tes) { + const U8 old_num_tes = getNumTEs(); + // A face gained starts as a copy of the last, and takes the values of the + // entry last unpacked where there is one. mTextureList.setSize(num_tes); + if (num_tes > old_num_tes) + { + applyTEEntry(old_num_tes); + } } //=============================================================== @@ -1307,6 +1314,28 @@ S32 LLPrimitive::parseTEMessage(LLMessageSystem* mesgsys, char const* block_name namespace { + // Reads a message's TextureEntry field whole into packed; block_num < 0 + // reads block 0. + void read_te_field(LLMessageSystem* mesgsys, char const* block_name, const S32 block_num, boost::container::small_vector_base& packed) + { + S32 size; + if (block_num < 0) + { + size = mesgsys->getSizeFast(block_name, _PREHASH_TextureEntry); + } + else + { + size = mesgsys->getSizeFast(block_name, block_num, _PREHASH_TextureEntry); + } + + packed.clear(); + if (size > 0) + { + packed.resize(size); + mesgsys->getBinaryDataFast(block_name, _PREHASH_TextureEntry, packed.data(), 0, std::max(block_num, 0), size); + } + } + // Parses a packed TextureEntry field of any length into tec, for // face_count faces. S32 parse_te_entry(boost::container::small_vector_base& packed, LLTEContents& tec, U8 face_count) @@ -1367,23 +1396,8 @@ namespace // static S32 LLPrimitive::parseTEMessage(LLMessageSystem* mesgsys, char const* block_name, const S32 block_num, LLTEContents& tec, U8 face_count) { - S32 size; - if (block_num < 0) - { - size = mesgsys->getSizeFast(block_name, _PREHASH_TextureEntry); - } - else - { - size = mesgsys->getSizeFast(block_name, block_num, _PREHASH_TextureEntry); - } - te_bytes_t packed; - if (size > 0) - { - packed.resize(size); - // if block_num < 0 ask for block 0 - mesgsys->getBinaryDataFast(block_name, _PREHASH_TextureEntry, packed.data(), 0, std::max(block_num, 0), size); - } + read_te_field(mesgsys, block_name, block_num, packed); return parse_te_entry(packed, tec, face_count); } @@ -1408,11 +1422,16 @@ void LLPrimitive::unpackCompressedData(LLDataPacker& dp, U32 flags, boost::conta } S32 LLPrimitive::applyParsedTEMessage(LLTEContents& tec) +{ + return applyParsedTEFaces(tec, 0); +} + +S32 LLPrimitive::applyParsedTEFaces(LLTEContents& tec, U32 first_face) { S32 retval = 0; LLColor4 color; - for (U32 i = 0; i < tec.face_count; i++) + for (U32 i = first_face; i < tec.face_count; i++) { LLUUID& req_id = ((LLUUID*)tec.image_data)[i]; retval |= setTETexture(i, req_id); @@ -1440,11 +1459,9 @@ S32 LLPrimitive::applyParsedTEMessage(LLTEContents& tec) S32 LLPrimitive::unpackTEMessage(LLMessageSystem* mesgsys, char const* block_name, const S32 block_num) { - LLTEContents tec; - S32 retval = parseTEMessage(mesgsys, block_name, block_num, tec); - if (!retval) - return retval; - return applyParsedTEMessage(tec); + te_bytes_t packed; + read_te_field(mesgsys, block_name, block_num, packed); + return unpackTEEntry(packed); } S32 LLPrimitive::unpackTEMessage(LLDataPacker &dp) @@ -1463,11 +1480,29 @@ S32 LLPrimitive::unpackTEEntry(boost::container::small_vector_base& packed) LLTEContents tec; if (!parse_te_entry(packed, tec, getNumTEs())) { + // An entry refused, or none, leaves the one kept as it was. return 0; } + // Kept as it arrived, without the zero the parse ends it with. + mTEEntry.assign(packed.begin(), packed.end() - 1); return applyParsedTEMessage(tec); } +void LLPrimitive::applyTEEntry(U8 first_face) +{ + if (mTEEntry.empty()) + { + return; + } + + te_bytes_t packed(mTEEntry.begin(), mTEEntry.end()); + LLTEContents tec; + if (parse_te_entry(packed, tec, getNumTEs())) + { + applyParsedTEFaces(tec, first_face); + } +} + U8 LLPrimitive::getExpectedNumTEs() const { U8 expected_face_count = 0; diff --git a/indra/llprimitive/llprimitive.h b/indra/llprimitive/llprimitive.h index a1f79e0e55..a18558d25d 100644 --- a/indra/llprimitive/llprimitive.h +++ b/indra/llprimitive/llprimitive.h @@ -556,6 +556,10 @@ class LLPrimitive : public LLXform S32 applyParsedTEMessage(LLTEContents& tec); + // Whether the primitive keeps a texture entry it unpacked, whose values + // the faces it gains take (setNumTEs). + bool hasTEEntry() const { return !mTEEntry.empty(); } + #ifdef CHECK_FOR_FINITE inline void setPosition(const LLVector3& pos); inline void setPosition(const F32 x, const F32 y, const F32 z); @@ -634,6 +638,11 @@ class LLPrimitive : public LLXform private: void updateNumBumpmap(const U8 index, const U8 bump); + // Applies the faces of tec from first_face on. + S32 applyParsedTEFaces(LLTEContents& tec, U32 first_face); + // The faces from first_face on take the values mTEEntry gives them. + void applyTEEntry(U8 first_face); + protected: LLPCode mPrimitiveCode; // Primitive code LLVector3 mVelocity; // how fast are we moving? @@ -646,6 +655,11 @@ class LLPrimitive : public LLXform U8 mNumBumpmapTEs; // number of bumpmap TEs. U32 mMiscFlags; // home for misc bools + // The texture entry last unpacked, as it arrived. It gives a value for + // every face, so a face the primitive gains later, as a mesh's asset + // loads, takes its own value from it rather than the last face's. + std::vector mTEEntry; + public: static LLVolumeMgr* sVolumeManager; diff --git a/indra/llprimitive/tests/llprimitivetemessage_test.cpp b/indra/llprimitive/tests/llprimitivetemessage_test.cpp index 1cb2e67a81..4972a7fd93 100644 --- a/indra/llprimitive/tests/llprimitivetemessage_test.cpp +++ b/indra/llprimitive/tests/llprimitivetemessage_test.cpp @@ -163,4 +163,130 @@ namespace CHECK(copy.unpackTEMessage(reader) == TEM_INVALID); CHECK(packed_entry(copy) == before); } + + // Unpacks entry into prim through a data packer, as an update does. + S32 unpack_entry(LLPrimitive& prim, const std::vector& entry) + { + std::vector buffer = sized(entry); + LLDataPackerBinaryBuffer reader(buffer.data(), (S32)buffer.size()); + return prim.unpackTEMessage(reader); + } + + // A mesh has the faces of its placeholder shape until its asset loads, + // fewer than the entry it is sent names. + TEST_CASE("LLPrimitiveTEMessage: the faces a primitive gains take the values its entry gives them", "[LLPrimitiveTEMessage]") + { + LLPrimitive sent; + give_faces_their_own_entries(sent, 6); + const std::vector entry = packed_entry(sent); + + LLPrimitive prim; + prim.setNumTEs(4); + CHECK(unpack_entry(prim, entry) != TEM_INVALID); + REQUIRE(prim.hasTEEntry()); + + prim.setNumTEs(6); + REQUIRE(prim.getNumTEs() == 6); + CHECK(prim.getTE(4)->getID() == face_uuid(4, 1)); + CHECK(prim.getTE(5)->getID() == face_uuid(5, 1)); + CHECK(prim.getTE(5)->getMaterialID() == LLMaterialID(face_uuid(5, 100))); + CHECK(packed_entry(prim) == entry); + } + + TEST_CASE("LLPrimitiveTEMessage: faces removed and gained again take the values the entry gives them", "[LLPrimitiveTEMessage]") + { + LLPrimitive sent; + give_faces_their_own_entries(sent, 6); + const std::vector entry = packed_entry(sent); + + LLPrimitive prim; + prim.setNumTEs(6); + CHECK(unpack_entry(prim, entry) != TEM_INVALID); + + prim.setNumTEs(2); + REQUIRE(prim.getNumTEs() == 2); + CHECK(prim.getTE(1)->getID() == face_uuid(1, 1)); + + prim.setNumTEs(6); + CHECK(packed_entry(prim) == entry); + } + + TEST_CASE("LLPrimitiveTEMessage: the faces a primitive gains take the values of the newest entry", "[LLPrimitiveTEMessage]") + { + LLPrimitive sent; + give_faces_their_own_entries(sent, 6); + const std::vector first = packed_entry(sent); + sent.setTETexture(4, face_uuid(4, 50)); + sent.setTEColor(5, LLColor4(0.5f, 0.25f, 0.75f, 1.f)); + const std::vector second = packed_entry(sent); + REQUIRE(second != first); + + LLPrimitive prim; + prim.setNumTEs(4); + CHECK(unpack_entry(prim, first) != TEM_INVALID); + CHECK(unpack_entry(prim, second) != TEM_INVALID); + + prim.setNumTEs(6); + CHECK(prim.getTE(4)->getID() == face_uuid(4, 50)); + CHECK(packed_entry(prim) == second); + } + + // A refused entry changes no face, so the faces gained later take the + // values of the entry before it. + TEST_CASE("LLPrimitiveTEMessage: the faces a primitive gains after a refused entry take the entry before it", "[LLPrimitiveTEMessage]") + { + LLPrimitive sent; + give_faces_their_own_entries(sent, 6); + const std::vector entry = packed_entry(sent); + + LLPrimitive prim; + prim.setNumTEs(4); + CHECK(unpack_entry(prim, entry) != TEM_INVALID); + + // Cut inside the first field's default value, and empty: each + // changes nothing. + const std::vector malformed(entry.begin(), entry.begin() + 8); + CHECK(unpack_entry(prim, malformed) == TEM_CHANGE_NONE); + CHECK(unpack_entry(prim, {}) == TEM_CHANGE_NONE); + + prim.setNumTEs(6); + CHECK(packed_entry(prim) == entry); + } + + // An entry gives every face past the last it packs that face's value. + TEST_CASE("LLPrimitiveTEMessage: faces gained past those an entry packs take the value it gives every face", "[LLPrimitiveTEMessage]") + { + LLPrimitive sent; + give_faces_their_own_entries(sent, 4); + const std::vector entry = packed_entry(sent); + + LLPrimitive prim; + prim.setNumTEs(4); + CHECK(unpack_entry(prim, entry) != TEM_INVALID); + + prim.setNumTEs(6); + for (U8 face = 4; face < 6; ++face) + { + CAPTURE((int)face); + CHECK(*prim.getTE(face) == *prim.getTE(3)); + } + CHECK(prim.getTE(3)->getID() == face_uuid(3, 1)); + } + + // With no entry to take values from, a face gained is a copy of the + // last. + TEST_CASE("LLPrimitiveTEMessage: a primitive that unpacked no entry copies its last face into the faces it gains", "[LLPrimitiveTEMessage]") + { + LLPrimitive prim; + give_faces_their_own_entries(prim, 2); + REQUIRE_FALSE(prim.hasTEEntry()); + + prim.setNumTEs(4); + for (U8 face = 2; face < 4; ++face) + { + CAPTURE((int)face); + CHECK(*prim.getTE(face) == *prim.getTE(1)); + } + CHECK(prim.getTE(1)->getID() == face_uuid(1, 1)); + } } diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 57d3e5bcbc..f1025c7d1c 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -5229,8 +5229,29 @@ void LLViewerObject::setNumTEs(const U8 num_tes) LLPrimitive::setNumTEs(num_tes); setChanged(TEXTURE); + if (hasTEEntry()) + { + // The faces gained took their values from the texture entry last + // unpacked. Each takes the render material the parameter block + // names for it, and the override the region holds for it, rather + // than the last face's. + if (original_tes < getNumTEs()) + { + for (int i = original_tes; i < getNumTEs(); ++i) + { + LLTextureEntry* tep = getTE(i); + const LLUUID id = getRenderMaterialID(i); + tep->setGLTFMaterialOverride(nullptr); + if (id.notNull() || tep->getGLTFMaterial()) + { + setRenderMaterialID(i, id, false, false); + } + } + gGLTFMaterialList.applyQueuedOverrides(this); + } + } // touch up GLTF materials - if (original_tes > 0) + else if (original_tes > 0) { for (int i = original_tes; i < getNumTEs(); ++i) { @@ -7908,7 +7929,12 @@ void LLViewerObject::setRenderMaterialID(S32 te_in, const LLUUID& id, bool updat { // update existing parameter block for (S32 te = start_idx; te < end_idx; ++te) { - param_block->setMaterial(te, id); + // Only where the block names another material: a face given no + // material where it names none takes no entry for it. + if (param_block->getMaterial(te) != id) + { + param_block->setMaterial(te, id); + } } }