/** * @file llvovolume.cpp * @brief LLVOVolume class implementation * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, 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$ */ // A "volume" is a box, cylinder, sphere, or other primitive shape. #include "llviewerprecompiledheaders.h" #include "llvovolume.h" #include #include "llviewercontrol.h" #include "lldir.h" #include "llflexibleobject.h" #include "llfloatertools.h" #include "llmaterialid.h" #include "llmaterialtable.h" #include "llprimitive.h" #include "llvolume.h" #include "llvolumeoctree.h" #include "llvolumemgr.h" #include "llvolumemessage.h" #include "material_codes.h" #include "message.h" #include "llpluginclassmedia.h" // for code in the mediaEvent handler #include "object_flags.h" #include "lldrawable.h" #include "lldrawpoolavatar.h" #include "lldrawpoolbump.h" #include "llface.h" #include "llspatialpartition.h" #include "llhudmanager.h" #include "llflexibleobject.h" #include "llskinningutil.h" #include "llsky.h" #include "lltexturefetch.h" #include "llvector4a.h" #include "llviewercamera.h" #include "llviewertexturelist.h" #include "llviewerobjectlist.h" #include "llviewerregion.h" #include "llviewertextureanim.h" #include "llworld.h" #include "llselectmgr.h" #include "pipeline.h" #include "llsdutil.h" #include "llmatrix4a.h" #include "llmediaentry.h" #include "llmediadataclient.h" #include "llmeshrepository.h" #include "llnotifications.h" #include "llnotificationsutil.h" #include "llagent.h" #include "llviewermediafocus.h" #include "lldatapacker.h" #include "llviewershadermgr.h" #include "llvoavatar.h" #include "llcontrolavatar.h" #include "llvoavatarself.h" #include "llvocache.h" #include "llmaterialmgr.h" #include "llanimationstates.h" #include "llinventorytype.h" #include "llviewerinventory.h" #include "llcallstack.h" #include "llsculptidsize.h" #include "llavatarappearancedefines.h" #include "llgltfmateriallist.h" const F32 FORCE_SIMPLE_RENDER_AREA = 512.f; const F32 FORCE_CULL_AREA = 8.f; U32 JOINT_COUNT_REQUIRED_FOR_FULLRIG = 1; BOOL gAnimateTextures = TRUE; F32 LLVOVolume::sLODFactor = 1.f; F32 LLVOVolume::sLODSlopDistanceFactor = 0.5f; //Changing this to zero, effectively disables the LOD transition slop F32 LLVOVolume::sDistanceFactor = 1.0f; S32 LLVOVolume::sNumLODChanges = 0; S32 LLVOVolume::mRenderComplexity_last = 0; S32 LLVOVolume::mRenderComplexity_current = 0; LLPointer LLVOVolume::sObjectMediaClient = NULL; LLPointer LLVOVolume::sObjectMediaNavigateClient = NULL; extern BOOL gCubeSnapshot; // Implementation class of LLMediaDataClientObject. See llmediadataclient.h class LLMediaDataClientObjectImpl : public LLMediaDataClientObject { public: LLMediaDataClientObjectImpl(LLVOVolume *obj, bool isNew) : mObject(obj), mNew(isNew) { mObject->addMDCImpl(); } ~LLMediaDataClientObjectImpl() { mObject->removeMDCImpl(); } virtual U8 getMediaDataCount() const { return mObject->getNumTEs(); } virtual LLSD getMediaDataLLSD(U8 index) const { LLSD result; LLTextureEntry *te = mObject->getTE(index); if (NULL != te) { llassert((te->getMediaData() != NULL) == te->hasMedia()); if (te->getMediaData() != NULL) { result = te->getMediaData()->asLLSD(); // XXX HACK: workaround bug in asLLSD() where whitelist is not set properly // See DEV-41949 if (!result.has(LLMediaEntry::WHITELIST_KEY)) { result[LLMediaEntry::WHITELIST_KEY] = LLSD::emptyArray(); } } } return result; } virtual bool isCurrentMediaUrl(U8 index, const std::string &url) const { LLTextureEntry *te = mObject->getTE(index); if (te) { if (te->getMediaData()) { return (te->getMediaData()->getCurrentURL() == url); } } return url.empty(); } virtual LLUUID getID() const { return mObject->getID(); } virtual void mediaNavigateBounceBack(U8 index) { mObject->mediaNavigateBounceBack(index); } virtual bool hasMedia() const { return mObject->hasMedia(); } virtual void updateObjectMediaData(LLSD const &data, const std::string &version_string) { mObject->updateObjectMediaData(data, version_string); } virtual F64 getMediaInterest() const { F64 interest = mObject->getTotalMediaInterest(); if (interest < (F64)0.0) { // media interest not valid yet, try pixel area interest = mObject->getPixelArea(); // HACK: force recalculation of pixel area if interest is the "magic default" of 1024. if (interest == 1024.f) { const_cast(static_cast(mObject))->setPixelAreaAndAngle(gAgent); interest = mObject->getPixelArea(); } } return interest; } virtual bool isInterestingEnough() const { return LLViewerMedia::getInstance()->isInterestingEnough(mObject, getMediaInterest()); } virtual std::string getCapabilityUrl(const std::string &name) const { return mObject->getRegion()->getCapability(name); } virtual bool isDead() const { return mObject->isDead(); } virtual U32 getMediaVersion() const { return LLTextureEntry::getVersionFromMediaVersionString(mObject->getMediaURL()); } virtual bool isNew() const { return mNew; } private: LLPointer mObject; bool mNew; }; LLVOVolume::LLVOVolume(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) : LLViewerObject(id, pcode, regionp), mVolumeImpl(NULL) { mTexAnimMode = 0; mRelativeXform.setIdentity(); mRelativeXformInvTrans.setIdentity(); mFaceMappingChanged = FALSE; mLOD = MIN_LOD; mLODDistance = 0.0f; mLODAdjustedDistance = 0.0f; mLODRadius = 0.0f; mTextureAnimp = NULL; mVolumeChanged = FALSE; mVObjRadius = LLVector3(1,1,0.5f).length(); mNumFaces = 0; mLODChanged = FALSE; mSculptChanged = FALSE; mColorChanged = FALSE; mSpotLightPriority = 0.f; mSkinInfoUnavaliable = false; mSkinInfo = NULL; mMediaImplList.resize(getNumTEs()); mLastFetchedMediaVersion = -1; mServerDrawableUpdateCount = 0; memset(&mIndexInTex, 0, sizeof(S32) * LLRender::NUM_VOLUME_TEXTURE_CHANNELS); mMDCImplCount = 0; mLastRiggingInfoLOD = -1; mResetDebugText = false; } LLVOVolume::~LLVOVolume() { LL_PROFILE_ZONE_SCOPED; delete mTextureAnimp; mTextureAnimp = NULL; delete mVolumeImpl; mVolumeImpl = NULL; gMeshRepo.unregisterMesh(this); if(!mMediaImplList.empty()) { for(U32 i = 0 ; i < mMediaImplList.size() ; i++) { if(mMediaImplList[i].notNull()) { mMediaImplList[i]->removeObject(this) ; } } } } void LLVOVolume::markDead() { if (!mDead) { LL_PROFILE_ZONE_SCOPED; if (getVolume()) { LLSculptIDSize::instance().rem(getVolume()->getParams().getSculptID()); } if(getMDCImplCount() > 0) { LLMediaDataClientObject::ptr_t obj = new LLMediaDataClientObjectImpl(const_cast(this), false); if (sObjectMediaClient) sObjectMediaClient->removeFromQueue(obj); if (sObjectMediaNavigateClient) sObjectMediaNavigateClient->removeFromQueue(obj); } // Detach all media impls from this object for(U32 i = 0 ; i < mMediaImplList.size() ; i++) { removeMediaImpl(i); } if (mSculptTexture.notNull()) { mSculptTexture->removeVolume(LLRender::SCULPT_TEX, this); } if (mLightTexture.notNull()) { mLightTexture->removeVolume(LLRender::LIGHT_TEX, this); } } LLViewerObject::markDead(); } // static void LLVOVolume::initClass() { // gSavedSettings better be around if (gSavedSettings.getBOOL("PrimMediaMasterEnabled")) { const F32 queue_timer_delay = gSavedSettings.getF32("PrimMediaRequestQueueDelay"); const F32 retry_timer_delay = gSavedSettings.getF32("PrimMediaRetryTimerDelay"); const U32 max_retries = gSavedSettings.getU32("PrimMediaMaxRetries"); const U32 max_sorted_queue_size = gSavedSettings.getU32("PrimMediaMaxSortedQueueSize"); const U32 max_round_robin_queue_size = gSavedSettings.getU32("PrimMediaMaxRoundRobinQueueSize"); sObjectMediaClient = new LLObjectMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries, max_sorted_queue_size, max_round_robin_queue_size); sObjectMediaNavigateClient = new LLObjectMediaNavigateClient(queue_timer_delay, retry_timer_delay, max_retries, max_sorted_queue_size, max_round_robin_queue_size); } } // static void LLVOVolume::cleanupClass() { sObjectMediaClient = NULL; sObjectMediaNavigateClient = NULL; } U32 LLVOVolume::processUpdateMessage(LLMessageSystem *mesgsys, void **user_data, U32 block_num, EObjectUpdateType update_type, LLDataPacker *dp) { LLColor4U color; const S32 teDirtyBits = (TEM_CHANGE_TEXTURE|TEM_CHANGE_COLOR|TEM_CHANGE_MEDIA); const bool previously_volume_changed = mVolumeChanged; const bool previously_face_mapping_changed = mFaceMappingChanged; const bool previously_color_changed = mColorChanged; // Do base class updates... U32 retval = LLViewerObject::processUpdateMessage(mesgsys, user_data, block_num, update_type, dp); LLUUID sculpt_id; U8 sculpt_type = 0; if (isSculpted()) { LLSculptParams *sculpt_params = (LLSculptParams *)getParameterEntry(LLNetworkData::PARAMS_SCULPT); sculpt_id = sculpt_params->getSculptTexture(); sculpt_type = sculpt_params->getSculptType(); LL_DEBUGS("ObjectUpdate") << "uuid " << mID << " set sculpt_id " << sculpt_id << LL_ENDL; dumpStack("ObjectUpdateStack"); } if (!dp) { if (update_type == OUT_FULL) { //////////////////////////////// // // Unpack texture animation data // // if (mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_TextureAnim)) { if (!mTextureAnimp) { mTextureAnimp = new LLViewerTextureAnim(this); } else { if (!(mTextureAnimp->mMode & LLTextureAnim::SMOOTH)) { mTextureAnimp->reset(); } } mTexAnimMode = 0; mTextureAnimp->unpackTAMessage(mesgsys, block_num); } else { if (mTextureAnimp) { delete mTextureAnimp; mTextureAnimp = NULL; for (S32 i = 0; i < getNumTEs(); i++) { LLFace* facep = mDrawable->getFace(i); if (facep && facep->mTextureMatrix) { // delete or reset delete facep->mTextureMatrix; facep->mTextureMatrix = NULL; } } gPipeline.markTextured(mDrawable); mFaceMappingChanged = TRUE; mTexAnimMode = 0; } } // Unpack volume data LLVolumeParams volume_params; LLVolumeMessage::unpackVolumeParams(&volume_params, mesgsys, _PREHASH_ObjectData, block_num); volume_params.setSculptID(sculpt_id, sculpt_type); if (setVolume(volume_params, 0)) { markForUpdate(); } } // Sigh, this needs to be done AFTER the volume is set as well, otherwise bad stuff happens... //////////////////////////// // // Unpack texture entry data // S32 result = unpackTEMessage(mesgsys, _PREHASH_ObjectData, (S32) block_num); if (result & TEM_CHANGE_MEDIA) { retval |= MEDIA_FLAGS_CHANGED; } } else { if (update_type != OUT_TERSE_IMPROVED) { LLVolumeParams volume_params; BOOL res = LLVolumeMessage::unpackVolumeParams(&volume_params, *dp); if (!res) { LL_WARNS() << "Bogus volume parameters in object " << getID() << LL_ENDL; LL_WARNS() << getRegion()->getOriginGlobal() << LL_ENDL; } volume_params.setSculptID(sculpt_id, sculpt_type); if (setVolume(volume_params, 0)) { markForUpdate(); } S32 res2 = unpackTEMessage(*dp); if (TEM_INVALID == res2) { // There's something bogus in the data that we're unpacking. dp->dumpBufferToLog(); LL_WARNS() << "Flushing cache files" << LL_ENDL; if(LLVOCache::instanceExists() && getRegion()) { LLVOCache::getInstance()->removeEntry(getRegion()->getHandle()) ; } LL_WARNS() << "Bogus TE data in " << getID() << LL_ENDL; } else { if (res2 & TEM_CHANGE_MEDIA) { retval |= MEDIA_FLAGS_CHANGED; } } U32 value = dp->getPassFlags(); if (value & 0x40) { if (!mTextureAnimp) { mTextureAnimp = new LLViewerTextureAnim(this); } else { if (!(mTextureAnimp->mMode & LLTextureAnim::SMOOTH)) { mTextureAnimp->reset(); } } mTexAnimMode = 0; mTextureAnimp->unpackTAMessage(*dp); } else if (mTextureAnimp) { delete mTextureAnimp; mTextureAnimp = NULL; for (S32 i = 0; i < getNumTEs(); i++) { LLFace* facep = mDrawable->getFace(i); if (facep && facep->mTextureMatrix) { // delete or reset delete facep->mTextureMatrix; facep->mTextureMatrix = NULL; } } gPipeline.markTextured(mDrawable); mFaceMappingChanged = TRUE; mTexAnimMode = 0; } if (value & 0x400) { //particle system (new) unpackParticleSource(*dp, mOwnerID, false); } } else { S32 texture_length = mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_TextureEntry); if (texture_length) { U8 tdpbuffer[1024]; LLDataPackerBinaryBuffer tdp(tdpbuffer, 1024); mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_TextureEntry, tdpbuffer, 0, block_num, 1024); S32 result = unpackTEMessage(tdp); if (result & teDirtyBits) { if (mDrawable) { //on the fly TE updates break batches, isolate in octree shrinkWrap(); } } if (result & TEM_CHANGE_MEDIA) { retval |= MEDIA_FLAGS_CHANGED; } } } } if (retval & (MEDIA_URL_REMOVED | MEDIA_URL_ADDED | MEDIA_URL_UPDATED | MEDIA_FLAGS_CHANGED)) { // If only the media URL changed, and it isn't a media version URL, // ignore it if ( ! ( retval & (MEDIA_URL_ADDED | MEDIA_URL_UPDATED) && mMedia && ! mMedia->mMediaURL.empty() && ! LLTextureEntry::isMediaVersionString(mMedia->mMediaURL) ) ) { // If the media changed at all, request new media data LL_DEBUGS("MediaOnAPrim") << "Media update: " << getID() << ": retval=" << retval << " Media URL: " << ((mMedia) ? mMedia->mMediaURL : std::string("")) << LL_ENDL; requestMediaDataUpdate(retval & MEDIA_FLAGS_CHANGED); } else { LL_INFOS("MediaOnAPrim") << "Ignoring media update for: " << getID() << " Media URL: " << ((mMedia) ? mMedia->mMediaURL : std::string("")) << LL_ENDL; } } // ...and clean up any media impls cleanUpMediaImpls(); if (( (mVolumeChanged && !previously_volume_changed) || (mFaceMappingChanged && !previously_face_mapping_changed) || (mColorChanged && !previously_color_changed) ) && !mLODChanged) { onDrawableUpdateFromServer(); } return retval; } // Called when a volume, material, etc is updated by the server, possibly by a // script. If this occurs too often for this object, mark it as active so that // it doesn't disrupt the octree/render batches, thereby potentially causing a // big performance penalty. void LLVOVolume::onDrawableUpdateFromServer() { constexpr U32 UPDATES_UNTIL_ACTIVE = 8; ++mServerDrawableUpdateCount; if (mDrawable && !mDrawable->isActive() && mServerDrawableUpdateCount > UPDATES_UNTIL_ACTIVE) { mDrawable->makeActive(); } } void LLVOVolume::animateTextures() { if (!mDead) { shrinkWrap(); F32 off_s = 0.f, off_t = 0.f, scale_s = 1.f, scale_t = 1.f, rot = 0.f; S32 result = mTextureAnimp->animateTextures(off_s, off_t, scale_s, scale_t, rot); if (result) { if (!mTexAnimMode) { mFaceMappingChanged = TRUE; gPipeline.markTextured(mDrawable); } mTexAnimMode = result | mTextureAnimp->mMode; S32 start=0, end=mDrawable->getNumFaces()-1; if (mTextureAnimp->mFace >= 0 && mTextureAnimp->mFace <= end) { start = end = mTextureAnimp->mFace; } for (S32 i = start; i <= end; i++) { LLFace* facep = mDrawable->getFace(i); if (!facep) continue; if(facep->getVirtualSize() <= MIN_TEX_ANIM_SIZE && facep->mTextureMatrix) continue; const LLTextureEntry* te = facep->getTextureEntry(); if (!te) { continue; } if (!(result & LLViewerTextureAnim::ROTATE)) { te->getRotation(&rot); } if (!(result & LLViewerTextureAnim::TRANSLATE)) { te->getOffset(&off_s,&off_t); } if (!(result & LLViewerTextureAnim::SCALE)) { te->getScale(&scale_s, &scale_t); } if (!facep->mTextureMatrix) { facep->mTextureMatrix = new LLMatrix4(); } LLMatrix4& tex_mat = *facep->mTextureMatrix; tex_mat.setIdentity(); LLVector3 trans ; trans.set(LLVector3(off_s+0.5f, off_t+0.5f, 0.f)); tex_mat.translate(LLVector3(-0.5f, -0.5f, 0.f)); LLVector3 scale(scale_s, scale_t, 1.f); LLQuaternion quat; quat.setQuat(rot, 0, 0, -1.f); tex_mat.rotate(quat); LLMatrix4 mat; mat.initAll(scale, LLQuaternion(), LLVector3()); tex_mat *= mat; tex_mat.translate(trans); } } else { if (mTexAnimMode && mTextureAnimp->mRate == 0) { U8 start, count; if (mTextureAnimp->mFace == -1) { start = 0; count = getNumTEs(); } else { start = (U8) mTextureAnimp->mFace; count = 1; } for (S32 i = start; i < start + count; i++) { if (mTexAnimMode & LLViewerTextureAnim::TRANSLATE) { setTEOffset(i, mTextureAnimp->mOffS, mTextureAnimp->mOffT); } if (mTexAnimMode & LLViewerTextureAnim::SCALE) { setTEScale(i, mTextureAnimp->mScaleS, mTextureAnimp->mScaleT); } if (mTexAnimMode & LLViewerTextureAnim::ROTATE) { setTERotation(i, mTextureAnimp->mRot); } } gPipeline.markTextured(mDrawable); mFaceMappingChanged = TRUE; mTexAnimMode = 0; } } } } void LLVOVolume::updateTextures() { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; updateTextureVirtualSize(); } BOOL LLVOVolume::isVisible() const { if(mDrawable.notNull() && mDrawable->isVisible()) { return TRUE ; } if(isAttachment()) { LLViewerObject* objp = (LLViewerObject*)getParent() ; while(objp && !objp->isAvatar()) { objp = (LLViewerObject*)objp->getParent() ; } return objp && objp->mDrawable.notNull() && objp->mDrawable->isVisible() ; } return FALSE ; } void LLVOVolume::updateTextureVirtualSize(bool forced) { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; // Update the pixel area of all faces if (mDrawable.isNull() || gCubeSnapshot) { return; } if(!forced) { if(!isVisible()) { //don't load textures for non-visible faces const S32 num_faces = mDrawable->getNumFaces(); for (S32 i = 0; i < num_faces; i++) { LLFace* face = mDrawable->getFace(i); if (face) { face->setPixelArea(0.f); face->setVirtualSize(0.f); } } return ; } if (!gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SIMPLE)) { return; } } static LLCachedControl dont_load_textures(gSavedSettings,"TextureDisable", false); if (dont_load_textures || LLAppViewer::getTextureFetch()->mDebugPause) // || !mDrawable->isVisible()) { return; } mTextureUpdateTimer.reset(); F32 old_area = mPixelArea; mPixelArea = 0.f; const S32 num_faces = mDrawable->getNumFaces(); F32 min_vsize=999999999.f, max_vsize=0.f; LLViewerCamera* camera = LLViewerCamera::getInstance(); std::stringstream debug_text; for (S32 i = 0; i < num_faces; i++) { LLFace* face = mDrawable->getFace(i); if (!face) continue; const LLTextureEntry *te = face->getTextureEntry(); LLViewerTexture *imagep = face->getTexture(); if (!imagep || !te || face->mExtents[0].equals3(face->mExtents[1])) { continue; } F32 vsize; F32 old_size = face->getVirtualSize(); if (isHUDAttachment()) { F32 area = (F32) camera->getScreenPixelArea(); vsize = area; imagep->setBoostLevel(LLGLTexture::BOOST_HUD); face->setPixelArea(area); // treat as full screen face->setVirtualSize(vsize); } else { vsize = face->getTextureVirtualSize(); } mPixelArea = llmax(mPixelArea, face->getPixelArea()); // if the face has gotten small enough to turn off texture animation and texture // animation is running, rebuild the render batch for this face to turn off // texture animation if (face->mTextureMatrix != NULL) { if ((vsize < MIN_TEX_ANIM_SIZE && old_size > MIN_TEX_ANIM_SIZE) || (vsize > MIN_TEX_ANIM_SIZE && old_size < MIN_TEX_ANIM_SIZE)) { gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); } } if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_PRIORITY)) { LLViewerFetchedTexture* img = LLViewerTextureManager::staticCastToFetchedTexture(imagep) ; if(img) { debug_text << img->getDiscardLevel() << ":" << img->getDesiredDiscardLevel() << ":" << img->getWidth() << ":" << (S32) sqrtf(vsize) << ":" << (S32) sqrtf(img->getMaxVirtualSize()) << "\n"; /*F32 pri = img->getDecodePriority(); pri = llmax(pri, 0.0f); if (pri < min_vsize) min_vsize = pri; if (pri > max_vsize) max_vsize = pri;*/ } } else if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_FACE_AREA)) { F32 pri = mPixelArea; if (pri < min_vsize) min_vsize = pri; if (pri > max_vsize) max_vsize = pri; } } if (isSculpted()) { updateSculptTexture(); if (mSculptTexture.notNull()) { mSculptTexture->setBoostLevel(llmax((S32)mSculptTexture->getBoostLevel(), (S32)LLGLTexture::BOOST_SCULPTED)); mSculptTexture->setForSculpt() ; if(!mSculptTexture->isCachedRawImageReady()) { S32 lod = llmin(mLOD, 3); F32 lodf = ((F32)(lod + 1.0f)/4.f); F32 tex_size = lodf * LLViewerTexture::sMaxSculptRez ; mSculptTexture->addTextureStats(2.f * tex_size * tex_size, FALSE); } S32 texture_discard = mSculptTexture->getCachedRawImageLevel(); //try to match the texture S32 current_discard = getVolume() ? getVolume()->getSculptLevel() : -2 ; if (texture_discard >= 0 && //texture has some data available (texture_discard < current_discard || //texture has more data than last rebuild current_discard < 0)) //no previous rebuild { gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); mSculptChanged = TRUE; } if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SCULPTED)) { setDebugText(llformat("T%d C%d V%d\n%dx%d", texture_discard, current_discard, getVolume()->getSculptLevel(), mSculptTexture->getHeight(), mSculptTexture->getWidth())); } } } if (getLightTextureID().notNull()) { LLLightImageParams* params = (LLLightImageParams*) getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); LLUUID id = params->getLightTexture(); mLightTexture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_NONE); if (mLightTexture.notNull()) { F32 rad = getLightRadius(); mLightTexture->addTextureStats(gPipeline.calcPixelArea(getPositionAgent(), LLVector3(rad,rad,rad), *camera)); } } if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_AREA)) { setDebugText(llformat("%.0f:%.0f", (F32) sqrt(min_vsize),(F32) sqrt(max_vsize))); } else if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_PRIORITY)) { //setDebugText(llformat("%.0f:%.0f", (F32) sqrt(min_vsize),(F32) sqrt(max_vsize))); setDebugText(debug_text.str()); } else if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_FACE_AREA)) { setDebugText(llformat("%.0f:%.0f", (F32) sqrt(min_vsize),(F32) sqrt(max_vsize))); } if (mPixelArea == 0) { //flexi phasing issues make this happen mPixelArea = old_area; } } BOOL LLVOVolume::isActive() const { return !mStatic; } BOOL LLVOVolume::setMaterial(const U8 material) { BOOL res = LLViewerObject::setMaterial(material); return res; } void LLVOVolume::setTexture(const S32 face) { llassert(face < getNumTEs()); gGL.getTexUnit(0)->bind(getTEImage(face)); } void LLVOVolume::setScale(const LLVector3 &scale, BOOL damped) { if (scale != getScale()) { // store local radius LLViewerObject::setScale(scale); if (mVolumeImpl) { mVolumeImpl->onSetScale(scale, damped); } updateRadius(); //since drawable transforms do not include scale, changing volume scale //requires an immediate rebuild of volume verts. gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_POSITION); if (mDrawable) { shrinkWrap(); } } } LLFace* LLVOVolume::addFace(S32 f) { const LLTextureEntry* te = getTE(f); LLViewerTexture* imagep = getTEImage(f); if (te->getMaterialParams().notNull()) { LLViewerTexture* normalp = getTENormalMap(f); LLViewerTexture* specularp = getTESpecularMap(f); return mDrawable->addFace(te, imagep, normalp, specularp); } return mDrawable->addFace(te, imagep); } LLDrawable *LLVOVolume::createDrawable(LLPipeline *pipeline) { pipeline->allocDrawable(this); mDrawable->setRenderType(LLPipeline::RENDER_TYPE_VOLUME); S32 max_tes_to_set = getNumTEs(); for (S32 i = 0; i < max_tes_to_set; i++) { addFace(i); } mNumFaces = max_tes_to_set; if (isAttachment()) { mDrawable->makeActive(); } if (getIsLight()) { // Add it to the pipeline mLightSet gPipeline.setLight(mDrawable, TRUE); } if (isReflectionProbe()) { updateReflectionProbePtr(); } updateRadius(); bool force_update = true; // avoid non-alpha mDistance update being optimized away mDrawable->updateDistance(*LLViewerCamera::getInstance(), force_update); return mDrawable; } BOOL LLVOVolume::setVolume(const LLVolumeParams ¶ms_in, const S32 detail, bool unique_volume) { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; LLVolumeParams volume_params = params_in; S32 last_lod = mVolumep.notNull() ? LLVolumeLODGroup::getVolumeDetailFromScale(mVolumep->getDetail()) : -1; S32 lod = mLOD; BOOL is404 = FALSE; if (isSculpted()) { // if it's a mesh if ((volume_params.getSculptType() & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH) { //meshes might not have all LODs, get the force detail to best existing LOD if (NO_LOD != lod) { lod = gMeshRepo.getActualMeshLOD(volume_params, lod); if (lod == -1) { is404 = TRUE; lod = 0; } } } } // Check if we need to change implementations bool is_flexible = (volume_params.getPathParams().getCurveType() == LL_PCODE_PATH_FLEXIBLE); if (is_flexible) { setParameterEntryInUse(LLNetworkData::PARAMS_FLEXIBLE, TRUE, false); if (!mVolumeImpl) { LLFlexibleObjectData* data = (LLFlexibleObjectData*)getParameterEntry(LLNetworkData::PARAMS_FLEXIBLE); mVolumeImpl = new LLVolumeImplFlexible(this, data); } } else { // Mark the parameter not in use setParameterEntryInUse(LLNetworkData::PARAMS_FLEXIBLE, FALSE, false); if (mVolumeImpl) { delete mVolumeImpl; mVolumeImpl = NULL; if (mDrawable.notNull()) { // Undo the damage we did to this matrix mDrawable->updateXform(FALSE); } } } if (is404) { setIcon(LLViewerTextureManager::getFetchedTextureFromFile("icons/Inv_Mesh.png", FTT_LOCAL_FILE, TRUE, LLGLTexture::BOOST_UI)); //render prim proxy when mesh loading attempts give up volume_params.setSculptID(LLUUID::null, LL_SCULPT_TYPE_NONE); } if ((LLPrimitive::setVolume(volume_params, lod, (mVolumeImpl && mVolumeImpl->isVolumeUnique()))) || mSculptChanged) { mFaceMappingChanged = TRUE; if (mVolumeImpl) { mVolumeImpl->onSetVolume(volume_params, mLOD); } updateSculptTexture(); if (isSculpted()) { updateSculptTexture(); // if it's a mesh if ((volume_params.getSculptType() & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH) { if (mSkinInfo && mSkinInfo->mMeshID != volume_params.getSculptID()) { mSkinInfo = NULL; mSkinInfoUnavaliable = false; } if (!getVolume()->isMeshAssetLoaded()) { //load request not yet issued, request pipeline load this mesh S32 available_lod = gMeshRepo.loadMesh(this, volume_params, lod, last_lod); if (available_lod != lod) { LLPrimitive::setVolume(volume_params, available_lod); } } if (!mSkinInfo && !mSkinInfoUnavaliable) { LLUUID mesh_id = volume_params.getSculptID(); if (gMeshRepo.hasHeader(mesh_id) && !gMeshRepo.hasSkinInfo(mesh_id)) { // If header is present but has no data about skin, // no point fetching mSkinInfoUnavaliable = true; } if (!mSkinInfoUnavaliable) { const LLMeshSkinInfo* skin_info = gMeshRepo.getSkinInfo(mesh_id, this); if (skin_info) { notifySkinInfoLoaded(skin_info); } } } } else // otherwise is sculptie { if (mSculptTexture.notNull()) { sculpt(); } } } return TRUE; } else if (NO_LOD == lod) { LLSculptIDSize::instance().resetSizeSum(volume_params.getSculptID()); } return FALSE; } void LLVOVolume::updateSculptTexture() { LLPointer old_sculpt = mSculptTexture; if (isSculpted() && !isMesh()) { LLSculptParams *sculpt_params = (LLSculptParams *)getParameterEntry(LLNetworkData::PARAMS_SCULPT); LLUUID id = sculpt_params->getSculptTexture(); if (id.notNull()) { mSculptTexture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); } mSkinInfoUnavaliable = false; mSkinInfo = NULL; } else { mSculptTexture = NULL; } if (mSculptTexture != old_sculpt) { if (old_sculpt.notNull()) { old_sculpt->removeVolume(LLRender::SCULPT_TEX, this); } if (mSculptTexture.notNull()) { mSculptTexture->addVolume(LLRender::SCULPT_TEX, this); } } } void LLVOVolume::updateVisualComplexity() { LLVOAvatar* avatar = getAvatarAncestor(); if (avatar) { avatar->updateVisualComplexity(); } LLVOAvatar* rigged_avatar = getAvatar(); if(rigged_avatar && (rigged_avatar != avatar)) { rigged_avatar->updateVisualComplexity(); } } void LLVOVolume::notifyMeshLoaded() { mSculptChanged = TRUE; gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_GEOMETRY); if (!mSkinInfo && !mSkinInfoUnavaliable) { // Header was loaded, update skin info state from header LLUUID mesh_id = getVolume()->getParams().getSculptID(); if (!gMeshRepo.hasSkinInfo(mesh_id)) { mSkinInfoUnavaliable = true; } } LLVOAvatar *av = getAvatar(); if (av && !isAnimatedObject()) { av->addAttachmentOverridesForObject(this); av->notifyAttachmentMeshLoaded(); } LLControlAvatar *cav = getControlAvatar(); if (cav && isAnimatedObject()) { cav->addAttachmentOverridesForObject(this); cav->notifyAttachmentMeshLoaded(); } updateVisualComplexity(); } void LLVOVolume::notifySkinInfoLoaded(const LLMeshSkinInfo* skin) { mSkinInfoUnavaliable = false; mSkinInfo = skin; notifyMeshLoaded(); } void LLVOVolume::notifySkinInfoUnavailable() { mSkinInfoUnavaliable = true; mSkinInfo = nullptr; } // sculpt replaces generate() for sculpted surfaces void LLVOVolume::sculpt() { if (mSculptTexture.notNull()) { U16 sculpt_height = 0; U16 sculpt_width = 0; S8 sculpt_components = 0; const U8* sculpt_data = NULL; S32 discard_level = mSculptTexture->getCachedRawImageLevel() ; LLImageRaw* raw_image = mSculptTexture->getCachedRawImage() ; S32 max_discard = mSculptTexture->getMaxDiscardLevel(); if (discard_level > max_discard) { discard_level = max_discard; // clamp to the best we can do } if(discard_level > MAX_DISCARD_LEVEL) { return; //we think data is not ready yet. } S32 current_discard = getVolume()->getSculptLevel() ; if(current_discard < -2) { static S32 low_sculpty_discard_warning_count = 1; S32 exponent = llmax(1, llfloor( log10((F64) low_sculpty_discard_warning_count) )); S32 interval = pow(10.0, exponent); if ( low_sculpty_discard_warning_count < 10 || (low_sculpty_discard_warning_count % interval) == 0) { // Log first 10 time, then decreasing intervals afterwards otherwise this can flood the logs LL_WARNS() << "WARNING!!: Current discard for sculpty " << mSculptTexture->getID() << " at " << current_discard << " is less than -2." << " Hit this " << low_sculpty_discard_warning_count << " times" << LL_ENDL; } low_sculpty_discard_warning_count++; // corrupted volume... don't update the sculpty return; } else if (current_discard > MAX_DISCARD_LEVEL) { static S32 high_sculpty_discard_warning_count = 1; S32 exponent = llmax(1, llfloor( log10((F64) high_sculpty_discard_warning_count) )); S32 interval = pow(10.0, exponent); if ( high_sculpty_discard_warning_count < 10 || (high_sculpty_discard_warning_count % interval) == 0) { // Log first 10 time, then decreasing intervals afterwards otherwise this can flood the logs LL_WARNS() << "WARNING!!: Current discard for sculpty " << mSculptTexture->getID() << " at " << current_discard << " is more than than allowed max of " << MAX_DISCARD_LEVEL << ". Hit this " << high_sculpty_discard_warning_count << " times" << LL_ENDL; } high_sculpty_discard_warning_count++; // corrupted volume... don't update the sculpty return; } if (current_discard == discard_level) // no work to do here return; if(!raw_image) { llassert(discard_level < 0) ; sculpt_width = 0; sculpt_height = 0; sculpt_data = NULL ; if(LLViewerTextureManager::sTesterp) { LLViewerTextureManager::sTesterp->updateGrayTextureBinding(); } } else { LLImageDataSharedLock lock(raw_image); sculpt_height = raw_image->getHeight(); sculpt_width = raw_image->getWidth(); sculpt_components = raw_image->getComponents(); sculpt_data = raw_image->getData(); if(LLViewerTextureManager::sTesterp) { mSculptTexture->updateBindStatsForTester() ; } } getVolume()->sculpt(sculpt_width, sculpt_height, sculpt_components, sculpt_data, discard_level, mSculptTexture->isMissingAsset()); //notify rebuild any other VOVolumes that reference this sculpty volume for (S32 i = 0; i < mSculptTexture->getNumVolumes(LLRender::SCULPT_TEX); ++i) { LLVOVolume* volume = (*(mSculptTexture->getVolumeList(LLRender::SCULPT_TEX)))[i]; if (volume != this && volume->getVolume() == getVolume()) { gPipeline.markRebuild(volume->mDrawable, LLDrawable::REBUILD_GEOMETRY); } } } } S32 LLVOVolume::computeLODDetail(F32 distance, F32 radius, F32 lod_factor) { S32 cur_detail; if (LLPipeline::sDynamicLOD) { // We've got LOD in the profile, and in the twist. Use radius. F32 tan_angle = (lod_factor*radius)/distance; cur_detail = LLVolumeLODGroup::getDetailFromTan(ll_round(tan_angle, 0.01f)); } else { cur_detail = llclamp((S32) (sqrtf(radius)*lod_factor*4.f), 0, 3); } return cur_detail; } std::string get_debug_object_lod_text(LLVOVolume *rootp) { std::string cam_dist_string = ""; cam_dist_string += LLStringOps::getReadableNumber(rootp->mLODDistance) + " "; std::string lod_string = llformat("%d",rootp->getLOD()); F32 lod_radius = rootp->mLODRadius; S32 cam_dist_count = 0; LLViewerObject::const_child_list_t& child_list = rootp->getChildren(); for (LLViewerObject::const_child_list_t::const_iterator iter = child_list.begin(); iter != child_list.end(); ++iter) { LLViewerObject *childp = *iter; LLVOVolume *volp = dynamic_cast(childp); if (volp) { lod_string += llformat("%d",volp->getLOD()); if (volp->isRiggedMesh()) { // Rigged/animatable mesh. This is computed from the // avatar dynamic box, so value from any vol will be // the same. lod_radius = volp->mLODRadius; } if (volp->mDrawable) { if (cam_dist_count < 4) { cam_dist_string += LLStringOps::getReadableNumber(volp->mLODDistance) + " "; cam_dist_count++; } } } } std::string result = llformat("lod_radius %s dists %s lods %s", LLStringOps::getReadableNumber(lod_radius).c_str(), cam_dist_string.c_str(), lod_string.c_str()); return result; } BOOL LLVOVolume::calcLOD() { if (mDrawable.isNull()) { return FALSE; } S32 cur_detail = 0; F32 radius; F32 distance; F32 lod_factor = LLVOVolume::sLODFactor; if (mDrawable->isState(LLDrawable::RIGGED)) { LLVOAvatar* avatar = getAvatar(); // Not sure how this can really happen, but alas it does. Better exit here than crashing. if( !avatar || !avatar->mDrawable ) { return FALSE; } distance = avatar->mDrawable->mDistanceWRTCamera; if (avatar->isControlAvatar()) { // MAINT-7926 Handle volumes in an animated object as a special case const LLVector3* box = avatar->getLastAnimExtents(); LLVector3 diag = box[1] - box[0]; radius = diag.magVec() * 0.5f; LL_DEBUGS("DynamicBox") << avatar->getFullname() << " diag " << diag << " radius " << radius << LL_ENDL; } else { // Volume in a rigged mesh attached to a regular avatar. // Note this isn't really a radius, so distance calcs are off by factor of 2 //radius = avatar->getBinRadius(); // SL-937: add dynamic box handling for rigged mesh on regular avatars. const LLVector3* box = avatar->getLastAnimExtents(); LLVector3 diag = box[1] - box[0]; radius = diag.magVec(); // preserve old BinRadius behavior - 2x off LL_DEBUGS("DynamicBox") << avatar->getFullname() << " diag " << diag << " radius " << radius << LL_ENDL; } if (distance <= 0.f || radius <= 0.f) { LL_DEBUGS("DynamicBox","CalcLOD") << "avatar distance/radius uninitialized, skipping" << LL_ENDL; return FALSE; } } else { distance = mDrawable->mDistanceWRTCamera; radius = getVolume() ? getVolume()->mLODScaleBias.scaledVec(getScale()).length() : getScale().length(); if (distance <= 0.f || radius <= 0.f) { LL_DEBUGS("DynamicBox","CalcLOD") << "non-avatar distance/radius uninitialized, skipping" << LL_ENDL; return FALSE; } } //hold onto unmodified distance for debugging //F32 debug_distance = distance; mLODDistance = distance; mLODRadius = radius; static LLCachedControl debug_lods(gSavedSettings, "DebugObjectLODs", false); if (debug_lods) { if (getAvatar() && isRootEdit()) { std::string debug_object_text = get_debug_object_lod_text(this); setDebugText(debug_object_text); mResetDebugText = true; } } else { if (mResetDebugText) { restoreHudText(); mResetDebugText = false; } } distance *= sDistanceFactor; F32 rampDist = LLVOVolume::sLODFactor * 2; if (distance < rampDist) { // Boost LOD when you're REALLY close distance *= 1.0f/rampDist; distance *= distance; distance *= rampDist; } distance *= F_PI/3.f; static LLCachedControl ignore_fov_zoom(gSavedSettings,"IgnoreFOVZoomForLODs"); if(!ignore_fov_zoom) { lod_factor *= DEFAULT_FIELD_OF_VIEW / LLViewerCamera::getInstance()->getDefaultFOV(); } mLODAdjustedDistance = distance; if (isHUDAttachment()) { // HUDs always show at highest detail cur_detail = 3; } else { cur_detail = computeLODDetail(ll_round(distance, 0.01f), ll_round(radius, 0.01f), lod_factor); } if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TRIANGLE_COUNT) && mDrawable->getFace(0)) { if (isRootEdit()) { S32 total_tris = recursiveGetTriangleCount(); S32 est_max_tris = recursiveGetEstTrianglesMax(); setDebugText(llformat("TRIS SHOWN %d EST %d", total_tris, est_max_tris)); } } if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_LOD_INFO) && mDrawable->getFace(0)) { // This is a debug display for LODs. Please don't put the texture index here. setDebugText(llformat("%d", cur_detail)); } if (cur_detail != mLOD) { LL_DEBUGS("DynamicBox","CalcLOD") << "new LOD " << cur_detail << " change from " << mLOD << " distance " << distance << " radius " << radius << " rampDist " << rampDist << " drawable rigged? " << (mDrawable ? (S32) mDrawable->isState(LLDrawable::RIGGED) : (S32) -1) << " mRiggedVolume " << (void*)getRiggedVolume() << " distanceWRTCamera " << (mDrawable ? mDrawable->mDistanceWRTCamera : -1.f) << LL_ENDL; mAppAngle = ll_round((F32) atan2( mDrawable->getRadius(), mDrawable->mDistanceWRTCamera) * RAD_TO_DEG, 0.01f); mLOD = cur_detail; return TRUE; } return FALSE; } bool LLVOVolume::updateLOD() { if (mDrawable.isNull()) { return false; } LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; bool lod_changed = false; if (!LLSculptIDSize::instance().isUnloaded(getVolume()->getParams().getSculptID())) { lod_changed = calcLOD(); } else { return false; } if (lod_changed) { gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); mLODChanged = true; } else { F32 new_radius = getBinRadius(); F32 old_radius = mDrawable->getBinRadius(); if (new_radius < old_radius * 0.9f || new_radius > old_radius*1.1f) { gPipeline.markPartitionMove(mDrawable); } } lod_changed = lod_changed || LLViewerObject::updateLOD(); return lod_changed; } BOOL LLVOVolume::setDrawableParent(LLDrawable* parentp) { if (!LLViewerObject::setDrawableParent(parentp)) { // no change in drawable parent return FALSE; } if (!mDrawable->isRoot()) { // rebuild vertices in parent relative space gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); if (mDrawable->isActive() && !parentp->isActive()) { parentp->makeActive(); } else if (mDrawable->isStatic() && parentp->isActive()) { mDrawable->makeActive(); } } return TRUE; } void LLVOVolume::updateFaceFlags() { // There's no guarantee that getVolume()->getNumFaces() == mDrawable->getNumFaces() for (S32 i = 0; i < getVolume()->getNumFaces() && i < mDrawable->getNumFaces(); i++) { LLFace *face = mDrawable->getFace(i); if (face) { BOOL fullbright = getTE(i)->getFullbright(); face->clearState(LLFace::FULLBRIGHT | LLFace::HUD_RENDER | LLFace::LIGHT); if (fullbright || (mMaterial == LL_MCODE_LIGHT)) { face->setState(LLFace::FULLBRIGHT); } if (mDrawable->isLight()) { face->setState(LLFace::LIGHT); } if (isHUDAttachment()) { face->setState(LLFace::HUD_RENDER); } } } } BOOL LLVOVolume::setParent(LLViewerObject* parent) { BOOL ret = FALSE ; LLViewerObject *old_parent = (LLViewerObject*) getParent(); if (parent != old_parent) { ret = LLViewerObject::setParent(parent); if (ret && mDrawable) { gPipeline.markMoved(mDrawable); gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); } onReparent(old_parent, parent); } return ret ; } // NOTE: regenFaces() MUST be followed by genTriangles()! void LLVOVolume::regenFaces() { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; // remove existing faces BOOL count_changed = mNumFaces != getNumTEs(); if (count_changed) { deleteFaces(); // add new faces mNumFaces = getNumTEs(); } for (S32 i = 0; i < mNumFaces; i++) { LLFace* facep = count_changed ? addFace(i) : mDrawable->getFace(i); if (!facep) continue; facep->setTEOffset(i); facep->setTexture(getTEImage(i)); if (facep->getTextureEntry()->getMaterialParams().notNull()) { facep->setNormalMap(getTENormalMap(i)); facep->setSpecularMap(getTESpecularMap(i)); } facep->setViewerObject(this); // If the face had media on it, this will have broken the link between the LLViewerMediaTexture and the face. // Re-establish the link. if((int)mMediaImplList.size() > i) { if(mMediaImplList[i]) { LLViewerMediaTexture* media_tex = LLViewerTextureManager::findMediaTexture(mMediaImplList[i]->getMediaTextureID()) ; if(media_tex) { media_tex->addMediaToFace(facep) ; } } } } if (!count_changed) { updateFaceFlags(); } } BOOL LLVOVolume::genBBoxes(BOOL force_global, BOOL should_update_octree_bounds) { LL_PROFILE_ZONE_SCOPED; BOOL res = TRUE; LLVector4a min, max; min.clear(); max.clear(); BOOL rebuild = mDrawable->isState(LLDrawable::REBUILD_VOLUME | LLDrawable::REBUILD_POSITION | LLDrawable::REBUILD_RIGGED); if (getRiggedVolume()) { // MAINT-8264 - better to use the existing call in calling // func LLVOVolume::updateGeometry() if we can detect when // updates needed, set REBUILD_RIGGED accordingly. // Without the flag, this will remove unused rigged volumes, which we are not currently very aggressive about. updateRiggedVolume(false); } LLVolume* volume = mRiggedVolume; if (!volume) { volume = getVolume(); } bool any_valid_boxes = false; if (getRiggedVolume()) { LL_DEBUGS("RiggedBox") << "rebuilding box, volume face count " << getVolume()->getNumVolumeFaces() << " drawable face count " << mDrawable->getNumFaces() << LL_ENDL; } // There's no guarantee that getVolume()->getNumFaces() == mDrawable->getNumFaces() for (S32 i = 0; i < getVolume()->getNumVolumeFaces() && i < mDrawable->getNumFaces() && i < getNumTEs(); i++) { LLFace* face = mDrawable->getFace(i); if (!face) { continue; } BOOL face_res = face->genVolumeBBoxes(*volume, i, mRelativeXform, (mVolumeImpl && mVolumeImpl->isVolumeGlobal()) || force_global); res &= face_res; // note that this result is never used // MAINT-8264 - ignore bboxes of ill-formed faces. if (!face_res) { continue; } if (rebuild) { if (getRiggedVolume()) { LL_DEBUGS("RiggedBox") << "rebuilding box, face " << i << " extents " << face->mExtents[0] << ", " << face->mExtents[1] << LL_ENDL; } if (!any_valid_boxes) { min = face->mExtents[0]; max = face->mExtents[1]; any_valid_boxes = true; } else { min.setMin(min, face->mExtents[0]); max.setMax(max, face->mExtents[1]); } } } if (any_valid_boxes) { if (rebuild && should_update_octree_bounds) { //get the Avatar associated with this object if it's rigged LLVOAvatar* avatar = nullptr; if (isRiggedMesh()) { if (!isAnimatedObject()) { if (isAttachment()) { avatar = getAvatar(); } } else { LLControlAvatar* controlAvatar = getControlAvatar(); if (controlAvatar && controlAvatar->mPlaying) { avatar = controlAvatar; } } } mDrawable->setSpatialExtents(min, max); if (avatar) { // put all rigged drawables in the same octree node for better batching mDrawable->setPositionGroup(LLVector4a(0, 0, 0)); } else { min.add(max); min.mul(0.5f); mDrawable->setPositionGroup(min); } } updateRadius(); mDrawable->movePartition(); } else { LL_DEBUGS("RiggedBox") << "genBBoxes failed to find any valid face boxes" << LL_ENDL; } return res; } void LLVOVolume::preRebuild() { if (mVolumeImpl != NULL) { mVolumeImpl->preRebuild(); } } void LLVOVolume::updateRelativeXform(bool force_identity) { if (mVolumeImpl) { mVolumeImpl->updateRelativeXform(force_identity); return; } LLDrawable* drawable = mDrawable; if (drawable->isState(LLDrawable::RIGGED) && mRiggedVolume.notNull()) { //rigged volume (which is in agent space) is used for generating bounding boxes etc //inverse of render matrix should go to partition space mRelativeXform = getRenderMatrix(); F32* dst = (F32*) mRelativeXformInvTrans.mMatrix; F32* src = (F32*) mRelativeXform.mMatrix; dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[4]; dst[4] = src[5]; dst[5] = src[6]; dst[6] = src[8]; dst[7] = src[9]; dst[8] = src[10]; mRelativeXform.invert(); mRelativeXformInvTrans.transpose(); } else if (drawable->isActive() || force_identity) { // setup relative transforms LLQuaternion delta_rot; LLVector3 delta_pos, delta_scale; //matrix from local space to parent relative/global space bool use_identity = force_identity || drawable->isSpatialRoot(); delta_rot = use_identity ? LLQuaternion() : mDrawable->getRotation(); delta_pos = use_identity ? LLVector3(0,0,0) : mDrawable->getPosition(); delta_scale = mDrawable->getScale(); // Vertex transform (4x4) LLVector3 x_axis = LLVector3(delta_scale.mV[VX], 0.f, 0.f) * delta_rot; LLVector3 y_axis = LLVector3(0.f, delta_scale.mV[VY], 0.f) * delta_rot; LLVector3 z_axis = LLVector3(0.f, 0.f, delta_scale.mV[VZ]) * delta_rot; mRelativeXform.initRows(LLVector4(x_axis, 0.f), LLVector4(y_axis, 0.f), LLVector4(z_axis, 0.f), LLVector4(delta_pos, 1.f)); // compute inverse transpose for normals // mRelativeXformInvTrans.setRows(x_axis, y_axis, z_axis); // mRelativeXformInvTrans.invert(); // mRelativeXformInvTrans.setRows(x_axis, y_axis, z_axis); // grumble - invert is NOT a matrix invert, so we do it by hand: LLMatrix3 rot_inverse = LLMatrix3(~delta_rot); LLMatrix3 scale_inverse; scale_inverse.setRows(LLVector3(1.0, 0.0, 0.0) / delta_scale.mV[VX], LLVector3(0.0, 1.0, 0.0) / delta_scale.mV[VY], LLVector3(0.0, 0.0, 1.0) / delta_scale.mV[VZ]); mRelativeXformInvTrans = rot_inverse * scale_inverse; mRelativeXformInvTrans.transpose(); } else { LLVector3 pos = getPosition(); LLVector3 scale = getScale(); LLQuaternion rot = getRotation(); if (mParent) { pos *= mParent->getRotation(); pos += mParent->getPosition(); rot *= mParent->getRotation(); } //LLViewerRegion* region = getRegion(); //pos += region->getOriginAgent(); LLVector3 x_axis = LLVector3(scale.mV[VX], 0.f, 0.f) * rot; LLVector3 y_axis = LLVector3(0.f, scale.mV[VY], 0.f) * rot; LLVector3 z_axis = LLVector3(0.f, 0.f, scale.mV[VZ]) * rot; mRelativeXform.initRows(LLVector4(x_axis, 0.f), LLVector4(y_axis, 0.f), LLVector4(z_axis, 0.f), LLVector4(pos, 1.f)); // compute inverse transpose for normals LLMatrix3 rot_inverse = LLMatrix3(~rot); LLMatrix3 scale_inverse; scale_inverse.setRows(LLVector3(1.0, 0.0, 0.0) / scale.mV[VX], LLVector3(0.0, 1.0, 0.0) / scale.mV[VY], LLVector3(0.0, 0.0, 1.0) / scale.mV[VZ]); mRelativeXformInvTrans = rot_inverse * scale_inverse; mRelativeXformInvTrans.transpose(); } } bool LLVOVolume::lodOrSculptChanged(LLDrawable *drawable, BOOL &compiled, BOOL &should_update_octree_bounds) { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; bool regen_faces = false; LLVolume *old_volumep, *new_volumep; F32 old_lod, new_lod; S32 old_num_faces, new_num_faces; old_volumep = getVolume(); old_lod = old_volumep->getDetail(); old_num_faces = old_volumep->getNumFaces(); old_volumep = NULL; { const LLVolumeParams &volume_params = getVolume()->getParams(); setVolume(volume_params, 0); } new_volumep = getVolume(); new_lod = new_volumep->getDetail(); new_num_faces = new_volumep->getNumFaces(); new_volumep = NULL; if ((new_lod != old_lod) || mSculptChanged) { if (mDrawable->isState(LLDrawable::RIGGED)) { updateVisualComplexity(); } compiled = TRUE; // new_lod > old_lod breaks a feedback loop between LOD updates and // bounding box updates. should_update_octree_bounds = should_update_octree_bounds || mSculptChanged || new_lod > old_lod; sNumLODChanges += new_num_faces; if ((S32)getNumTEs() != getVolume()->getNumFaces()) { setNumTEs(getVolume()->getNumFaces()); //mesh loading may change number of faces. } drawable->setState(LLDrawable::REBUILD_VOLUME); // for face->genVolumeTriangles() { regen_faces = new_num_faces != old_num_faces || mNumFaces != (S32)getNumTEs(); if (regen_faces) { regenFaces(); } if (mSculptChanged) { //changes in sculpt maps can thrash an object bounding box without //triggering a spatial group bounding box update -- force spatial group //to update bounding boxes LLSpatialGroup* group = mDrawable->getSpatialGroup(); if (group) { group->unbound(); } } } } return regen_faces; } BOOL LLVOVolume::updateGeometry(LLDrawable *drawable) { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; if (mDrawable->isState(LLDrawable::REBUILD_RIGGED)) { updateRiggedVolume(false); genBBoxes(FALSE); mDrawable->clearState(LLDrawable::REBUILD_RIGGED); } if (mVolumeImpl != NULL) { BOOL res; { res = mVolumeImpl->doUpdateGeometry(drawable); } updateFaceFlags(); return res; } LLSpatialGroup* group = drawable->getSpatialGroup(); if (group) { group->dirtyMesh(); } updateRelativeXform(); if (mDrawable.isNull()) // Not sure why this is happening, but it is... { return TRUE; // No update to complete } BOOL compiled = FALSE; // This should be true in most cases, unless we're sure no octree update is // needed. BOOL should_update_octree_bounds = bool(getRiggedVolume()) || mDrawable->isState(LLDrawable::REBUILD_POSITION) || !mDrawable->getSpatialExtents()->isFinite3(); if (mVolumeChanged || mFaceMappingChanged) { dirtySpatialGroup(); bool was_regen_faces = false; should_update_octree_bounds = true; if (mVolumeChanged) { was_regen_faces = lodOrSculptChanged(drawable, compiled, should_update_octree_bounds); drawable->setState(LLDrawable::REBUILD_VOLUME); } else if (mSculptChanged || mLODChanged || mColorChanged) { compiled = TRUE; was_regen_faces = lodOrSculptChanged(drawable, compiled, should_update_octree_bounds); } if (!was_regen_faces) { regenFaces(); } } else if (mLODChanged || mSculptChanged || mColorChanged) { dirtySpatialGroup(); compiled = TRUE; lodOrSculptChanged(drawable, compiled, should_update_octree_bounds); if(drawable->isState(LLDrawable::REBUILD_RIGGED | LLDrawable::RIGGED)) { updateRiggedVolume(false); } } // it has its own drawable (it's moved) or it has changed UVs or it has changed xforms from global<->local else { compiled = TRUE; // All it did was move or we changed the texture coordinate offset } // Generate bounding boxes if needed, and update the object's size in the // octree genBBoxes(FALSE, should_update_octree_bounds); // Update face flags updateFaceFlags(); if(compiled) { LLPipeline::sCompiles++; } mVolumeChanged = FALSE; mLODChanged = FALSE; mSculptChanged = FALSE; mFaceMappingChanged = FALSE; mColorChanged = FALSE; return LLViewerObject::updateGeometry(drawable); } void LLVOVolume::updateFaceSize(S32 idx) { if( mDrawable->getNumFaces() <= idx ) { return; } LLFace* facep = mDrawable->getFace(idx); if (facep) { if (idx >= getVolume()->getNumVolumeFaces()) { facep->setSize(0,0, true); } else { const LLVolumeFace& vol_face = getVolume()->getVolumeFace(idx); facep->setSize(vol_face.mNumVertices, vol_face.mNumIndices, true); // <--- volume faces should be padded for 16-byte alignment } } } BOOL LLVOVolume::isRootEdit() const { if (mParent && !((LLViewerObject*)mParent)->isAvatar()) { return FALSE; } return TRUE; } //virtual void LLVOVolume::setNumTEs(const U8 num_tes) { const U8 old_num_tes = getNumTEs() ; if(old_num_tes && old_num_tes < num_tes) //new faces added { LLViewerObject::setNumTEs(num_tes) ; if(mMediaImplList.size() >= old_num_tes && mMediaImplList[old_num_tes -1].notNull())//duplicate the last media textures if exists. { mMediaImplList.resize(num_tes) ; const LLTextureEntry* te = getTE(old_num_tes - 1) ; for(U8 i = old_num_tes; i < num_tes ; i++) { setTE(i, *te) ; mMediaImplList[i] = mMediaImplList[old_num_tes -1] ; } mMediaImplList[old_num_tes -1]->setUpdated(TRUE) ; } } else if(old_num_tes > num_tes && mMediaImplList.size() > num_tes) //old faces removed { U8 end = (U8)(mMediaImplList.size()) ; for(U8 i = num_tes; i < end ; i++) { removeMediaImpl(i) ; } mMediaImplList.resize(num_tes) ; LLViewerObject::setNumTEs(num_tes) ; } else { LLViewerObject::setNumTEs(num_tes) ; } return ; } //virtual void LLVOVolume::changeTEImage(S32 index, LLViewerTexture* imagep) { BOOL changed = (mTEImages[index] != imagep); LLViewerObject::changeTEImage(index, imagep); if (changed) { gPipeline.markTextured(mDrawable); mFaceMappingChanged = TRUE; } } void LLVOVolume::setTEImage(const U8 te, LLViewerTexture *imagep) { BOOL changed = (mTEImages[te] != imagep); LLViewerObject::setTEImage(te, imagep); if (changed) { gPipeline.markTextured(mDrawable); mFaceMappingChanged = TRUE; } } S32 LLVOVolume::setTETexture(const U8 te, const LLUUID &uuid) { S32 res = LLViewerObject::setTETexture(te, uuid); if (res) { if (mDrawable) { // dynamic texture changes break batches, isolate in octree shrinkWrap(); gPipeline.markTextured(mDrawable); } mFaceMappingChanged = TRUE; } return res; } S32 LLVOVolume::setTEColor(const U8 te, const LLColor3& color) { return setTEColor(te, LLColor4(color)); } S32 LLVOVolume::setTEColor(const U8 te, const LLColor4& color) { S32 retval = 0; const LLTextureEntry *tep = getTE(te); if (!tep) { LL_WARNS("MaterialTEs") << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; } else if (color != tep->getColor()) { F32 old_alpha = tep->getColor().mV[3]; if (color.mV[3] != old_alpha) { gPipeline.markTextured(mDrawable); //treat this alpha change as an LoD update since render batches may need to get rebuilt mLODChanged = TRUE; gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); } retval = LLPrimitive::setTEColor(te, color); if (mDrawable.notNull() && retval) { // These should only happen on updates which are not the initial update. mColorChanged = TRUE; mDrawable->setState(LLDrawable::REBUILD_COLOR); shrinkWrap(); dirtyMesh(); } } return retval; } S32 LLVOVolume::setTEBumpmap(const U8 te, const U8 bumpmap) { S32 res = LLViewerObject::setTEBumpmap(te, bumpmap); if (res) { gPipeline.markTextured(mDrawable); mFaceMappingChanged = TRUE; } return res; } S32 LLVOVolume::setTETexGen(const U8 te, const U8 texgen) { S32 res = LLViewerObject::setTETexGen(te, texgen); if (res) { gPipeline.markTextured(mDrawable); mFaceMappingChanged = TRUE; } return res; } S32 LLVOVolume::setTEMediaTexGen(const U8 te, const U8 media) { S32 res = LLViewerObject::setTEMediaTexGen(te, media); if (res) { gPipeline.markTextured(mDrawable); mFaceMappingChanged = TRUE; } return res; } S32 LLVOVolume::setTEShiny(const U8 te, const U8 shiny) { S32 res = LLViewerObject::setTEShiny(te, shiny); if (res) { gPipeline.markTextured(mDrawable); mFaceMappingChanged = TRUE; } return res; } S32 LLVOVolume::setTEFullbright(const U8 te, const U8 fullbright) { S32 res = LLViewerObject::setTEFullbright(te, fullbright); if (res) { gPipeline.markTextured(mDrawable); mFaceMappingChanged = TRUE; } return res; } S32 LLVOVolume::setTEBumpShinyFullbright(const U8 te, const U8 bump) { S32 res = LLViewerObject::setTEBumpShinyFullbright(te, bump); if (res) { gPipeline.markTextured(mDrawable); mFaceMappingChanged = TRUE; } return res; } S32 LLVOVolume::setTEMediaFlags(const U8 te, const U8 media_flags) { S32 res = LLViewerObject::setTEMediaFlags(te, media_flags); if (res) { gPipeline.markTextured(mDrawable); mFaceMappingChanged = TRUE; } return res; } S32 LLVOVolume::setTEGlow(const U8 te, const F32 glow) { S32 res = LLViewerObject::setTEGlow(te, glow); if (res) { if (mDrawable) { gPipeline.markTextured(mDrawable); shrinkWrap(); } mFaceMappingChanged = TRUE; } return res; } void LLVOVolume::setTEMaterialParamsCallbackTE(const LLUUID& objectID, const LLMaterialID &pMaterialID, const LLMaterialPtr pMaterialParams, U32 te) { LLVOVolume* pVol = (LLVOVolume*)gObjectList.findObject(objectID); if (pVol) { LL_DEBUGS("MaterialTEs") << "materialid " << pMaterialID.asString() << " to TE " << te << LL_ENDL; if (te >= pVol->getNumTEs()) return; LLTextureEntry* texture_entry = pVol->getTE(te); if (texture_entry && (texture_entry->getMaterialID() == pMaterialID)) { pVol->setTEMaterialParams(te, pMaterialParams); } } } S32 LLVOVolume::setTEMaterialID(const U8 te, const LLMaterialID& pMaterialID) { S32 res = LLViewerObject::setTEMaterialID(te, pMaterialID); LL_DEBUGS("MaterialTEs") << "te "<< (S32)te << " materialid " << pMaterialID.asString() << " res " << res << ( LLSelectMgr::getInstance()->getSelection()->contains(const_cast(this), te) ? " selected" : " not selected" ) << LL_ENDL; LL_DEBUGS("MaterialTEs") << " " << pMaterialID.asString() << LL_ENDL; if (res) { LLMaterialMgr::instance().getTE(getRegion()->getRegionID(), pMaterialID, te, boost::bind(&LLVOVolume::setTEMaterialParamsCallbackTE, getID(), _1, _2, _3)); setChanged(ALL_CHANGED); if (!mDrawable.isNull()) { gPipeline.markTextured(mDrawable); gPipeline.markRebuild(mDrawable,LLDrawable::REBUILD_ALL); } mFaceMappingChanged = TRUE; } return res; } S32 LLVOVolume::setTEMaterialParams(const U8 te, const LLMaterialPtr pMaterialParams) { S32 res = LLViewerObject::setTEMaterialParams(te, pMaterialParams); LL_DEBUGS("MaterialTEs") << "te " << (S32)te << " material " << ((pMaterialParams) ? pMaterialParams->asLLSD() : LLSD("null")) << " res " << res << ( LLSelectMgr::getInstance()->getSelection()->contains(const_cast(this), te) ? " selected" : " not selected" ) << LL_ENDL; setChanged(ALL_CHANGED); if (!mDrawable.isNull()) { gPipeline.markTextured(mDrawable); gPipeline.markRebuild(mDrawable,LLDrawable::REBUILD_ALL); } mFaceMappingChanged = TRUE; return TEM_CHANGE_TEXTURE; } S32 LLVOVolume::setTEGLTFMaterialOverride(U8 te, LLGLTFMaterial* mat) { S32 retval = LLViewerObject::setTEGLTFMaterialOverride(te, mat); if (retval == TEM_CHANGE_TEXTURE) { if (!mDrawable.isNull()) { gPipeline.markTextured(mDrawable); gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); } mFaceMappingChanged = TRUE; } return retval; } S32 LLVOVolume::setTEScale(const U8 te, const F32 s, const F32 t) { S32 res = LLViewerObject::setTEScale(te, s, t); if (res) { gPipeline.markTextured(mDrawable); mFaceMappingChanged = TRUE; } return res; } S32 LLVOVolume::setTEScaleS(const U8 te, const F32 s) { S32 res = LLViewerObject::setTEScaleS(te, s); if (res) { gPipeline.markTextured(mDrawable); mFaceMappingChanged = TRUE; } return res; } S32 LLVOVolume::setTEScaleT(const U8 te, const F32 t) { S32 res = LLViewerObject::setTEScaleT(te, t); if (res) { gPipeline.markTextured(mDrawable); mFaceMappingChanged = TRUE; } return res; } bool LLVOVolume::hasMedia() const { bool result = false; const U8 numTEs = getNumTEs(); for (U8 i = 0; i < numTEs; i++) { const LLTextureEntry* te = getTE(i); if(te->hasMedia()) { result = true; break; } } return result; } LLVector3 LLVOVolume::getApproximateFaceNormal(U8 face_id) { LLVolume* volume = getVolume(); LLVector4a result; result.clear(); LLVector3 ret; if (volume && face_id < volume->getNumVolumeFaces()) { const LLVolumeFace& face = volume->getVolumeFace(face_id); for (S32 i = 0; i < (S32)face.mNumVertices; ++i) { result.add(face.mNormals[i]); } LLVector3 ret(result.getF32ptr()); ret = volumeDirectionToAgent(ret); ret.normVec(); } return ret; } void LLVOVolume::requestMediaDataUpdate(bool isNew) { if (sObjectMediaClient) sObjectMediaClient->fetchMedia(new LLMediaDataClientObjectImpl(this, isNew)); } bool LLVOVolume::isMediaDataBeingFetched() const { // I know what I'm doing by const_casting this away: this is just // a wrapper class that is only going to do a lookup. return (sObjectMediaClient) ? sObjectMediaClient->isInQueue(new LLMediaDataClientObjectImpl(const_cast(this), false)) : false; } void LLVOVolume::cleanUpMediaImpls() { // Iterate through our TEs and remove any Impls that are no longer used const U8 numTEs = getNumTEs(); for (U8 i = 0; i < numTEs; i++) { const LLTextureEntry* te = getTE(i); if( ! te->hasMedia()) { // Delete the media IMPL! removeMediaImpl(i) ; } } } void LLVOVolume::updateObjectMediaData(const LLSD &media_data_array, const std::string &media_version) { // media_data_array is an array of media entry maps // media_version is the version string in the response. U32 fetched_version = LLTextureEntry::getVersionFromMediaVersionString(media_version); // Only update it if it is newer! if ( (S32)fetched_version > mLastFetchedMediaVersion) { mLastFetchedMediaVersion = fetched_version; //LL_INFOS() << "updating:" << this->getID() << " " << ll_pretty_print_sd(media_data_array) << LL_ENDL; LLSD::array_const_iterator iter = media_data_array.beginArray(); LLSD::array_const_iterator end = media_data_array.endArray(); U8 texture_index = 0; for (; iter != end; ++iter, ++texture_index) { syncMediaData(texture_index, *iter, false/*merge*/, false/*ignore_agent*/); } } } void LLVOVolume::syncMediaData(S32 texture_index, const LLSD &media_data, bool merge, bool ignore_agent) { if(mDead) { // If the object has been marked dead, don't process media updates. return; } LLTextureEntry *te = getTE(texture_index); if(!te) { return ; } LL_DEBUGS("MediaOnAPrim") << "BEFORE: texture_index = " << texture_index << " hasMedia = " << te->hasMedia() << " : " << ((NULL == te->getMediaData()) ? "NULL MEDIA DATA" : ll_pretty_print_sd(te->getMediaData()->asLLSD())) << LL_ENDL; std::string previous_url; LLMediaEntry* mep = te->getMediaData(); if(mep) { // Save the "current url" from before the update so we can tell if // it changes. previous_url = mep->getCurrentURL(); } if (merge) { te->mergeIntoMediaData(media_data); } else { // XXX Question: what if the media data is undefined LLSD, but the // update we got above said that we have media flags?? Here we clobber // that, assuming the data from the service is more up-to-date. te->updateMediaData(media_data); } mep = te->getMediaData(); if(mep) { bool update_from_self = false; if (!ignore_agent) { LLUUID updating_agent = LLTextureEntry::getAgentIDFromMediaVersionString(getMediaURL()); update_from_self = (updating_agent == gAgent.getID()); } viewer_media_t media_impl = LLViewerMedia::getInstance()->updateMediaImpl(mep, previous_url, update_from_self); addMediaImpl(media_impl, texture_index) ; } else { removeMediaImpl(texture_index); } LL_DEBUGS("MediaOnAPrim") << "AFTER: texture_index = " << texture_index << " hasMedia = " << te->hasMedia() << " : " << ((NULL == te->getMediaData()) ? "NULL MEDIA DATA" : ll_pretty_print_sd(te->getMediaData()->asLLSD())) << LL_ENDL; } void LLVOVolume::mediaNavigateBounceBack(U8 texture_index) { // Find the media entry for this navigate const LLMediaEntry* mep = NULL; viewer_media_t impl = getMediaImpl(texture_index); LLTextureEntry *te = getTE(texture_index); if(te) { mep = te->getMediaData(); } if (mep && impl) { std::string url = mep->getCurrentURL(); // Look for a ":", if not there, assume "http://" if (!url.empty() && std::string::npos == url.find(':')) { url = "http://" + url; } // If the url we're trying to "bounce back" to is either empty or not // allowed by the whitelist, try the home url. If *that* doesn't work, // set the media as failed and unload it if (url.empty() || !mep->checkCandidateUrl(url)) { url = mep->getHomeURL(); // Look for a ":", if not there, assume "http://" if (!url.empty() && std::string::npos == url.find(':')) { url = "http://" + url; } } if (url.empty() || !mep->checkCandidateUrl(url)) { // The url to navigate back to is not good, and we have nowhere else // to go. LL_WARNS("MediaOnAPrim") << "FAILED to bounce back URL \"" << url << "\" -- unloading impl" << LL_ENDL; impl->setMediaFailed(true); } // Make sure we are not bouncing to url we came from else if (impl->getCurrentMediaURL() != url) { // Okay, navigate now LL_INFOS("MediaOnAPrim") << "bouncing back to URL: " << url << LL_ENDL; impl->navigateTo(url, "", false, true); } } } bool LLVOVolume::hasMediaPermission(const LLMediaEntry* media_entry, MediaPermType perm_type) { // NOTE: This logic ALMOST duplicates the logic in the server (in particular, in llmediaservice.cpp). if (NULL == media_entry ) return false; // XXX should we assert here? // The agent has permissions if: // - world permissions are on, or // - group permissions are on, and agent_id is in the group, or // - agent permissions are on, and agent_id is the owner // *NOTE: We *used* to check for modify permissions here (i.e. permissions were // granted if permModify() was true). However, this doesn't make sense in the // viewer: we don't want to show controls or allow interaction if the author // has deemed it so. See DEV-42115. U8 media_perms = (perm_type == MEDIA_PERM_INTERACT) ? media_entry->getPermsInteract() : media_entry->getPermsControl(); // World permissions if (0 != (media_perms & LLMediaEntry::PERM_ANYONE)) { return true; } // Group permissions else if (0 != (media_perms & LLMediaEntry::PERM_GROUP)) { LLPermissions* obj_perm = LLSelectMgr::getInstance()->findObjectPermissions(this); if (obj_perm && gAgent.isInGroup(obj_perm->getGroup())) { return true; } } // Owner permissions else if (0 != (media_perms & LLMediaEntry::PERM_OWNER) && permYouOwner()) { return true; } return false; } void LLVOVolume::mediaNavigated(LLViewerMediaImpl *impl, LLPluginClassMedia* plugin, std::string new_location) { bool block_navigation = false; // FIXME: if/when we allow the same media impl to be used by multiple faces, the logic here will need to be fixed // to deal with multiple face indices. int face_index = getFaceIndexWithMediaImpl(impl, -1); // Find the media entry for this navigate LLMediaEntry* mep = NULL; LLTextureEntry *te = getTE(face_index); if(te) { mep = te->getMediaData(); } if(mep) { if(!mep->checkCandidateUrl(new_location)) { block_navigation = true; } if (!block_navigation && !hasMediaPermission(mep, MEDIA_PERM_INTERACT)) { block_navigation = true; } } else { LL_WARNS("MediaOnAPrim") << "Couldn't find media entry!" << LL_ENDL; } if(block_navigation) { LL_INFOS("MediaOnAPrim") << "blocking navigate to URI " << new_location << LL_ENDL; // "bounce back" to the current URL from the media entry mediaNavigateBounceBack(face_index); } else if (sObjectMediaNavigateClient) { LL_DEBUGS("MediaOnAPrim") << "broadcasting navigate with URI " << new_location << LL_ENDL; sObjectMediaNavigateClient->navigate(new LLMediaDataClientObjectImpl(this, false), face_index, new_location); } } void LLVOVolume::mediaEvent(LLViewerMediaImpl *impl, LLPluginClassMedia* plugin, LLViewerMediaObserver::EMediaEvent event) { switch(event) { case LLViewerMediaObserver::MEDIA_EVENT_LOCATION_CHANGED: { switch(impl->getNavState()) { case LLViewerMediaImpl::MEDIANAVSTATE_FIRST_LOCATION_CHANGED: { // This is the first location changed event after the start of a non-server-directed nav. It may need to be broadcast or bounced back. mediaNavigated(impl, plugin, plugin->getLocation()); } break; case LLViewerMediaImpl::MEDIANAVSTATE_FIRST_LOCATION_CHANGED_SPURIOUS: // This navigate didn't change the current URL. LL_DEBUGS("MediaOnAPrim") << " NOT broadcasting navigate (spurious)" << LL_ENDL; break; case LLViewerMediaImpl::MEDIANAVSTATE_SERVER_FIRST_LOCATION_CHANGED: // This is the first location changed event after the start of a server-directed nav. Don't broadcast it. LL_INFOS("MediaOnAPrim") << " NOT broadcasting navigate (server-directed)" << LL_ENDL; break; default: // This is a subsequent location-changed due to a redirect. Don't broadcast. LL_INFOS("MediaOnAPrim") << " NOT broadcasting navigate (redirect)" << LL_ENDL; break; } } break; case LLViewerMediaObserver::MEDIA_EVENT_NAVIGATE_COMPLETE: { switch(impl->getNavState()) { case LLViewerMediaImpl::MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED: { // This is the first location changed event after the start of a non-server-directed nav. It may need to be broadcast or bounced back. mediaNavigated(impl, plugin, plugin->getNavigateURI()); } break; case LLViewerMediaImpl::MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED_SPURIOUS: // This navigate didn't change the current URL. LL_DEBUGS("MediaOnAPrim") << " NOT broadcasting navigate (spurious)" << LL_ENDL; break; case LLViewerMediaImpl::MEDIANAVSTATE_SERVER_COMPLETE_BEFORE_LOCATION_CHANGED: // This is the the navigate complete event from a server-directed nav. Don't broadcast it. LL_INFOS("MediaOnAPrim") << " NOT broadcasting navigate (server-directed)" << LL_ENDL; break; default: // For all other states, the navigate should have been handled by LOCATION_CHANGED events already. break; } } break; case LLViewerMediaObserver::MEDIA_EVENT_FILE_DOWNLOAD: { // Media might be blocked, waiting for a file, // send an empty response to unblock it const std::vector empty_response; plugin->sendPickFileResponse(empty_response); LLNotificationsUtil::add("MediaFileDownloadUnsupported"); } break; default: break; } } void LLVOVolume::sendMediaDataUpdate() { if (sObjectMediaClient) sObjectMediaClient->updateMedia(new LLMediaDataClientObjectImpl(this, false)); } void LLVOVolume::removeMediaImpl(S32 texture_index) { if(mMediaImplList.size() <= (U32)texture_index || mMediaImplList[texture_index].isNull()) { return ; } //make the face referencing to mMediaImplList[texture_index] to point back to the old texture. if(mDrawable && texture_index < mDrawable->getNumFaces()) { LLFace* facep = mDrawable->getFace(texture_index) ; if(facep) { LLViewerMediaTexture* media_tex = LLViewerTextureManager::findMediaTexture(mMediaImplList[texture_index]->getMediaTextureID()) ; if(media_tex) { media_tex->removeMediaFromFace(facep) ; } } } //check if some other face(s) of this object reference(s)to this media impl. S32 i ; S32 end = (S32)mMediaImplList.size() ; for(i = 0; i < end ; i++) { if( i != texture_index && mMediaImplList[i] == mMediaImplList[texture_index]) { break ; } } if(i == end) //this object does not need this media impl. { mMediaImplList[texture_index]->removeObject(this) ; } mMediaImplList[texture_index] = NULL ; return ; } void LLVOVolume::addMediaImpl(LLViewerMediaImpl* media_impl, S32 texture_index) { if((S32)mMediaImplList.size() < texture_index + 1) { mMediaImplList.resize(texture_index + 1) ; } if(mMediaImplList[texture_index].notNull()) { if(mMediaImplList[texture_index] == media_impl) { return ; } removeMediaImpl(texture_index) ; } mMediaImplList[texture_index] = media_impl; media_impl->addObject(this) ; //add the face to show the media if it is in playing if(mDrawable) { LLFace* facep(NULL); if( texture_index < mDrawable->getNumFaces() ) { facep = mDrawable->getFace(texture_index) ; } if(facep) { LLViewerMediaTexture* media_tex = LLViewerTextureManager::findMediaTexture(mMediaImplList[texture_index]->getMediaTextureID()) ; if(media_tex) { media_tex->addMediaToFace(facep) ; } } else //the face is not available now, start media on this face later. { media_impl->setUpdated(TRUE) ; } } return ; } viewer_media_t LLVOVolume::getMediaImpl(U8 face_id) const { if(mMediaImplList.size() > face_id) { return mMediaImplList[face_id]; } return NULL; } F64 LLVOVolume::getTotalMediaInterest() const { // If this object is currently focused, this object has "high" interest if (LLViewerMediaFocus::getInstance()->getFocusedObjectID() == getID()) return F64_MAX; F64 interest = (F64)-1.0; // means not interested; // If this object is selected, this object has "high" interest, but since // there can be more than one, we still add in calculated impl interest // XXX Sadly, 'contains()' doesn't take a const :( if (LLSelectMgr::getInstance()->getSelection()->contains(const_cast(this))) interest = F64_MAX / 2.0; int i = 0; const int end = getNumTEs(); for ( ; i < end; ++i) { const viewer_media_t &impl = getMediaImpl(i); if (!impl.isNull()) { if (interest == (F64)-1.0) interest = (F64)0.0; interest += impl->getInterest(); } } return interest; } S32 LLVOVolume::getFaceIndexWithMediaImpl(const LLViewerMediaImpl* media_impl, S32 start_face_id) { S32 end = (S32)mMediaImplList.size() ; for(S32 face_id = start_face_id + 1; face_id < end; face_id++) { if(mMediaImplList[face_id] == media_impl) { return face_id ; } } return -1 ; } //---------------------------------------------------------------------------- void LLVOVolume::setLightTextureID(LLUUID id) { LLViewerTexture* old_texturep = getLightTexture(); // same as mLightTexture, but inits if nessesary if (id.notNull()) { if (!hasLightTexture()) { setParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE, TRUE, true); } else if (old_texturep) { old_texturep->removeVolume(LLRender::LIGHT_TEX, this); } LLLightImageParams* param_block = (LLLightImageParams*) getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); if (param_block && param_block->getLightTexture() != id) { param_block->setLightTexture(id); parameterChanged(LLNetworkData::PARAMS_LIGHT_IMAGE, true); } LLViewerTexture* tex = getLightTexture(); if (tex) { tex->addVolume(LLRender::LIGHT_TEX, this); // new texture } else { LL_WARNS() << "Can't get light texture for ID " << id.asString() << LL_ENDL; } } else if (hasLightTexture()) { if (old_texturep) { old_texturep->removeVolume(LLRender::LIGHT_TEX, this); } setParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE, FALSE, true); parameterChanged(LLNetworkData::PARAMS_LIGHT_IMAGE, true); mLightTexture = NULL; } } void LLVOVolume::setSpotLightParams(LLVector3 params) { LLLightImageParams* param_block = (LLLightImageParams*) getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); if (param_block && param_block->getParams() != params) { param_block->setParams(params); parameterChanged(LLNetworkData::PARAMS_LIGHT_IMAGE, true); } } void LLVOVolume::setIsLight(BOOL is_light) { BOOL was_light = getIsLight(); if (is_light != was_light) { if (is_light) { setParameterEntryInUse(LLNetworkData::PARAMS_LIGHT, TRUE, true); } else { setParameterEntryInUse(LLNetworkData::PARAMS_LIGHT, FALSE, true); } if (is_light) { // Add it to the pipeline mLightSet gPipeline.setLight(mDrawable, TRUE); } else { // Not a light. Remove it from the pipeline's light set. gPipeline.setLight(mDrawable, FALSE); } } } void LLVOVolume::setLightSRGBColor(const LLColor3& color) { setLightLinearColor(linearColor3(color)); } void LLVOVolume::setLightLinearColor(const LLColor3& color) { LLLightParams *param_block = (LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); if (param_block) { if (param_block->getLinearColor() != color) { param_block->setLinearColor(LLColor4(color, param_block->getLinearColor().mV[3])); parameterChanged(LLNetworkData::PARAMS_LIGHT, true); gPipeline.markTextured(mDrawable); mFaceMappingChanged = TRUE; } } } void LLVOVolume::setLightIntensity(F32 intensity) { LLLightParams *param_block = (LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); if (param_block) { if (param_block->getLinearColor().mV[3] != intensity) { param_block->setLinearColor(LLColor4(LLColor3(param_block->getLinearColor()), intensity)); parameterChanged(LLNetworkData::PARAMS_LIGHT, true); } } } void LLVOVolume::setLightRadius(F32 radius) { LLLightParams *param_block = (LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); if (param_block) { if (param_block->getRadius() != radius) { param_block->setRadius(radius); parameterChanged(LLNetworkData::PARAMS_LIGHT, true); } } } void LLVOVolume::setLightFalloff(F32 falloff) { LLLightParams *param_block = (LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); if (param_block) { if (param_block->getFalloff() != falloff) { param_block->setFalloff(falloff); parameterChanged(LLNetworkData::PARAMS_LIGHT, true); } } } void LLVOVolume::setLightCutoff(F32 cutoff) { LLLightParams *param_block = (LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); if (param_block) { if (param_block->getCutoff() != cutoff) { param_block->setCutoff(cutoff); parameterChanged(LLNetworkData::PARAMS_LIGHT, true); } } } //---------------------------------------------------------------------------- BOOL LLVOVolume::getIsLight() const { mIsLight = getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT); return mIsLight; } bool LLVOVolume::getIsLightFast() const { return mIsLight; } LLColor3 LLVOVolume::getLightSRGBBaseColor() const { return srgbColor3(getLightLinearBaseColor()); } LLColor3 LLVOVolume::getLightLinearBaseColor() const { const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); if (param_block) { return LLColor3(param_block->getLinearColor()); } else { return LLColor3(1,1,1); } } LLColor3 LLVOVolume::getLightLinearColor() const { const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); if (param_block) { return LLColor3(param_block->getLinearColor()) * param_block->getLinearColor().mV[3]; } else { return LLColor3(1, 1, 1); } } LLColor3 LLVOVolume::getLightSRGBColor() const { LLColor3 ret = getLightLinearColor(); ret = srgbColor3(ret); return ret; } LLUUID LLVOVolume::getLightTextureID() const { if (getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE)) { const LLLightImageParams *param_block = (const LLLightImageParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); if (param_block) { return param_block->getLightTexture(); } } return LLUUID::null; } LLVector3 LLVOVolume::getSpotLightParams() const { if (getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE)) { const LLLightImageParams *param_block = (const LLLightImageParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); if (param_block) { return param_block->getParams(); } } return LLVector3(); } F32 LLVOVolume::getSpotLightPriority() const { return mSpotLightPriority; } void LLVOVolume::updateSpotLightPriority() { if (gCubeSnapshot) { return; } F32 r = getLightRadius(); LLVector3 pos = mDrawable->getPositionAgent(); LLVector3 at(0,0,-1); at *= getRenderRotation(); pos += at * r; at = LLViewerCamera::getInstance()->getAtAxis(); pos -= at * r; mSpotLightPriority = gPipeline.calcPixelArea(pos, LLVector3(r,r,r), *LLViewerCamera::getInstance()); if (mLightTexture.notNull()) { mLightTexture->addTextureStats(mSpotLightPriority); } } bool LLVOVolume::isLightSpotlight() const { LLLightImageParams* params = (LLLightImageParams*) getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); if (params && getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE)) { return params->isLightSpotlight(); } return false; } LLViewerTexture* LLVOVolume::getLightTexture() { LLUUID id = getLightTextureID(); if (id.notNull()) { if (mLightTexture.isNull() || id != mLightTexture->getID()) { mLightTexture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, TRUE, LLGLTexture::BOOST_NONE); } } else { mLightTexture = NULL; } return mLightTexture; } F32 LLVOVolume::getLightIntensity() const { const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); if (param_block) { return param_block->getLinearColor().mV[3]; } else { return 1.f; } } F32 LLVOVolume::getLightRadius() const { const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); if (param_block) { return param_block->getRadius(); } else { return 0.f; } } F32 LLVOVolume::getLightFalloff(const F32 fudge_factor) const { const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); if (param_block) { return param_block->getFalloff() * fudge_factor; } else { return 0.f; } } F32 LLVOVolume::getLightCutoff() const { const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); if (param_block) { return param_block->getCutoff(); } else { return 0.f; } } BOOL LLVOVolume::isReflectionProbe() const { return getParameterEntryInUse(LLNetworkData::PARAMS_REFLECTION_PROBE); } bool LLVOVolume::setIsReflectionProbe(BOOL is_probe) { BOOL was_probe = isReflectionProbe(); if (is_probe != was_probe) { if (is_probe) { setParameterEntryInUse(LLNetworkData::PARAMS_REFLECTION_PROBE, TRUE, true); } else { setParameterEntryInUse(LLNetworkData::PARAMS_REFLECTION_PROBE, FALSE, true); } } updateReflectionProbePtr(); return was_probe != is_probe; } bool LLVOVolume::setReflectionProbeAmbiance(F32 ambiance) { LLReflectionProbeParams* param_block = (LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); if (param_block) { if (param_block->getAmbiance() != ambiance) { param_block->setAmbiance(ambiance); parameterChanged(LLNetworkData::PARAMS_REFLECTION_PROBE, true); return true; } } return false; } bool LLVOVolume::setReflectionProbeNearClip(F32 near_clip) { LLReflectionProbeParams* param_block = (LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); if (param_block) { if (param_block->getClipDistance() != near_clip) { param_block->setClipDistance(near_clip); parameterChanged(LLNetworkData::PARAMS_REFLECTION_PROBE, true); return true; } } return false; } bool LLVOVolume::setReflectionProbeIsBox(bool is_box) { LLReflectionProbeParams* param_block = (LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); if (param_block) { if (param_block->getIsBox() != is_box) { param_block->setIsBox(is_box); parameterChanged(LLNetworkData::PARAMS_REFLECTION_PROBE, true); return true; } } return false; } bool LLVOVolume::setReflectionProbeIsDynamic(bool is_dynamic) { LLReflectionProbeParams* param_block = (LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); if (param_block) { if (param_block->getIsDynamic() != is_dynamic) { param_block->setIsDynamic(is_dynamic); parameterChanged(LLNetworkData::PARAMS_REFLECTION_PROBE, true); return true; } } return false; } F32 LLVOVolume::getReflectionProbeAmbiance() const { const LLReflectionProbeParams* param_block = (const LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); if (param_block) { return param_block->getAmbiance(); } else { return 0.f; } } F32 LLVOVolume::getReflectionProbeNearClip() const { const LLReflectionProbeParams* param_block = (const LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); if (param_block) { return param_block->getClipDistance(); } else { return 0.f; } } bool LLVOVolume::getReflectionProbeIsBox() const { const LLReflectionProbeParams* param_block = (const LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); if (param_block) { return param_block->getIsBox(); } return false; } bool LLVOVolume::getReflectionProbeIsDynamic() const { const LLReflectionProbeParams* param_block = (const LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); if (param_block) { return param_block->getIsDynamic(); } return false; } U32 LLVOVolume::getVolumeInterfaceID() const { if (mVolumeImpl) { return mVolumeImpl->getID(); } return 0; } BOOL LLVOVolume::isFlexible() const { if (getParameterEntryInUse(LLNetworkData::PARAMS_FLEXIBLE)) { LLVolume* volume = getVolume(); if (volume && volume->getParams().getPathParams().getCurveType() != LL_PCODE_PATH_FLEXIBLE) { LLVolumeParams volume_params = getVolume()->getParams(); U8 profile_and_hole = volume_params.getProfileParams().getCurveType(); volume_params.setType(profile_and_hole, LL_PCODE_PATH_FLEXIBLE); } return TRUE; } else { return FALSE; } } BOOL LLVOVolume::isSculpted() const { if (getParameterEntryInUse(LLNetworkData::PARAMS_SCULPT)) { return TRUE; } return FALSE; } BOOL LLVOVolume::isMesh() const { if (isSculpted()) { LLSculptParams *sculpt_params = (LLSculptParams *)getParameterEntry(LLNetworkData::PARAMS_SCULPT); U8 sculpt_type = sculpt_params->getSculptType(); if ((sculpt_type & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH) // mesh is a mesh { return TRUE; } } return FALSE; } BOOL LLVOVolume::hasLightTexture() const { if (getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE)) { return TRUE; } return FALSE; } bool LLVOVolume::isFlexibleFast() const { return mVolumep && mVolumep->getParams().getPathParams().getCurveType() == LL_PCODE_PATH_FLEXIBLE; } bool LLVOVolume::isSculptedFast() const { return mVolumep && mVolumep->getParams().isSculpt(); } bool LLVOVolume::isMeshFast() const { return mVolumep && mVolumep->getParams().isMeshSculpt(); } bool LLVOVolume::isRiggedMeshFast() const { return mSkinInfo.notNull(); } bool LLVOVolume::isAnimatedObjectFast() const { return mIsAnimatedObject; } BOOL LLVOVolume::isVolumeGlobal() const { if (mVolumeImpl) { return mVolumeImpl->isVolumeGlobal() ? TRUE : FALSE; } else if (mRiggedVolume.notNull()) { return TRUE; } return FALSE; } BOOL LLVOVolume::canBeFlexible() const { U8 path = getVolume()->getParams().getPathParams().getCurveType(); return (path == LL_PCODE_PATH_FLEXIBLE || path == LL_PCODE_PATH_LINE); } BOOL LLVOVolume::setIsFlexible(BOOL is_flexible) { BOOL res = FALSE; BOOL was_flexible = isFlexible(); LLVolumeParams volume_params; if (is_flexible) { if (!was_flexible) { volume_params = getVolume()->getParams(); U8 profile_and_hole = volume_params.getProfileParams().getCurveType(); volume_params.setType(profile_and_hole, LL_PCODE_PATH_FLEXIBLE); res = TRUE; setFlags(FLAGS_USE_PHYSICS, FALSE); setFlags(FLAGS_PHANTOM, TRUE); setParameterEntryInUse(LLNetworkData::PARAMS_FLEXIBLE, TRUE, true); if (mDrawable) { mDrawable->makeActive(); } } } else { if (was_flexible) { volume_params = getVolume()->getParams(); U8 profile_and_hole = volume_params.getProfileParams().getCurveType(); volume_params.setType(profile_and_hole, LL_PCODE_PATH_LINE); res = TRUE; setFlags(FLAGS_PHANTOM, FALSE); setParameterEntryInUse(LLNetworkData::PARAMS_FLEXIBLE, FALSE, true); } } if (res) { res = setVolume(volume_params, 1); if (res) { markForUpdate(); } } return res; } const LLMeshSkinInfo* LLVOVolume::getSkinInfo() const { if (getVolume()) { return mSkinInfo; } else { return NULL; } } // virtual BOOL LLVOVolume::isRiggedMesh() const { return isMesh() && getSkinInfo(); } //---------------------------------------------------------------------------- U32 LLVOVolume::getExtendedMeshFlags() const { const LLExtendedMeshParams *param_block = (const LLExtendedMeshParams *)getParameterEntry(LLNetworkData::PARAMS_EXTENDED_MESH); if (param_block) { return param_block->getFlags(); } else { return 0; } } void LLVOVolume::onSetExtendedMeshFlags(U32 flags) { // The isAnySelected() check was needed at one point to prevent // graphics problems. These are now believed to be fixed so the // check has been disabled. if (/*!getRootEdit()->isAnySelected() &&*/ mDrawable.notNull()) { // Need to trigger rebuildGeom(), which is where control avatars get created/removed getRootEdit()->recursiveMarkForUpdate(); } if (isAttachment() && getAvatarAncestor()) { updateVisualComplexity(); if (flags & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG) { // Making a rigged mesh into an animated object getAvatarAncestor()->updateAttachmentOverrides(); } else { // Making an animated object into a rigged mesh getAvatarAncestor()->updateAttachmentOverrides(); } } } void LLVOVolume::setExtendedMeshFlags(U32 flags) { U32 curr_flags = getExtendedMeshFlags(); if (curr_flags != flags) { bool in_use = true; setParameterEntryInUse(LLNetworkData::PARAMS_EXTENDED_MESH, in_use, true); LLExtendedMeshParams *param_block = (LLExtendedMeshParams *)getParameterEntry(LLNetworkData::PARAMS_EXTENDED_MESH); if (param_block) { param_block->setFlags(flags); } parameterChanged(LLNetworkData::PARAMS_EXTENDED_MESH, true); LL_DEBUGS("AnimatedObjects") << this << " new flags " << flags << " curr_flags " << curr_flags << ", calling onSetExtendedMeshFlags()" << LL_ENDL; onSetExtendedMeshFlags(flags); } } bool LLVOVolume::canBeAnimatedObject() const { F32 est_tris = recursiveGetEstTrianglesMax(); if (est_tris < 0 || est_tris > getAnimatedObjectMaxTris()) { return false; } return true; } bool LLVOVolume::isAnimatedObject() const { LLVOVolume *root_vol = (LLVOVolume*)getRootEdit(); mIsAnimatedObject = root_vol->getExtendedMeshFlags() & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG; return mIsAnimatedObject; } // Called any time parenting changes for a volume. Update flags and // control av accordingly. This is called after parent has been // changed to new_parent, but before new_parent's mChildList has changed. // virtual void LLVOVolume::onReparent(LLViewerObject *old_parent, LLViewerObject *new_parent) { LLVOVolume *old_volp = dynamic_cast(old_parent); if (new_parent && !new_parent->isAvatar()) { if (mControlAvatar.notNull()) { // Here an animated object is being made the child of some // other prim. Should remove the control av from the child. LLControlAvatar *av = mControlAvatar; mControlAvatar = NULL; av->markForDeath(); } } if (old_volp && old_volp->isAnimatedObject()) { if (old_volp->getControlAvatar()) { // We have been removed from an animated object, need to do cleanup. old_volp->getControlAvatar()->updateAttachmentOverrides(); old_volp->getControlAvatar()->updateAnimations(); } } } // This needs to be called after onReparent(), because mChildList is // not updated until the end of LLViewerObject::addChild() // virtual void LLVOVolume::afterReparent() { { LL_DEBUGS("AnimatedObjects") << "new child added for parent " << ((LLViewerObject*)getParent())->getID() << LL_ENDL; } if (isAnimatedObject() && getControlAvatar()) { LL_DEBUGS("AnimatedObjects") << "adding attachment overrides, parent is animated object " << ((LLViewerObject*)getParent())->getID() << LL_ENDL; // MAINT-8239 - doing a full rebuild whenever parent is set // makes the joint overrides load more robustly. In theory, // addAttachmentOverrides should be sufficient, but in // practice doing a full rebuild helps compensate for // notifyMeshLoaded() not being called reliably enough. // was: getControlAvatar()->addAttachmentOverridesForObject(this); //getControlAvatar()->rebuildAttachmentOverrides(); getControlAvatar()->updateAnimations(); } else { LL_DEBUGS("AnimatedObjects") << "not adding overrides, parent: " << ((LLViewerObject*)getParent())->getID() << " isAnimated: " << isAnimatedObject() << " cav " << getControlAvatar() << LL_ENDL; } } //---------------------------------------------------------------------------- void LLVOVolume::updateRiggingInfo() { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; if (isRiggedMesh()) { const LLMeshSkinInfo* skin = getSkinInfo(); LLVOAvatar *avatar = getAvatar(); LLVolume *volume = getVolume(); if (skin && avatar && volume) { LL_DEBUGS("RigSpammish") << "starting, vovol " << this << " lod " << getLOD() << " last " << mLastRiggingInfoLOD << LL_ENDL; if (getLOD()>mLastRiggingInfoLOD || getLOD()==3) { // Rigging info may need update mJointRiggingInfoTab.clear(); for (S32 f = 0; f < volume->getNumVolumeFaces(); ++f) { LLVolumeFace& vol_face = volume->getVolumeFace(f); LLSkinningUtil::updateRiggingInfo(skin, avatar, vol_face); if (vol_face.mJointRiggingInfoTab.size()>0) { mJointRiggingInfoTab.merge(vol_face.mJointRiggingInfoTab); } } // Keep the highest LOD info available. mLastRiggingInfoLOD = getLOD(); LL_DEBUGS("RigSpammish") << "updated rigging info for LLVOVolume " << this << " lod " << mLastRiggingInfoLOD << LL_ENDL; } } } } //---------------------------------------------------------------------------- void LLVOVolume::generateSilhouette(LLSelectNode* nodep, const LLVector3& view_point) { LLVolume *volume = getVolume(); if (volume) { LLVector3 view_vector; view_vector = view_point; //transform view vector into volume space view_vector -= getRenderPosition(); //mDrawable->mDistanceWRTCamera = view_vector.length(); LLQuaternion worldRot = getRenderRotation(); view_vector = view_vector * ~worldRot; if (!isVolumeGlobal()) { LLVector3 objScale = getScale(); LLVector3 invObjScale(1.f / objScale.mV[VX], 1.f / objScale.mV[VY], 1.f / objScale.mV[VZ]); view_vector.scaleVec(invObjScale); } updateRelativeXform(); LLMatrix4 trans_mat = mRelativeXform; if (mDrawable->isStatic()) { trans_mat.translate(getRegion()->getOriginAgent()); } volume->generateSilhouetteVertices(nodep->mSilhouetteVertices, nodep->mSilhouetteNormals, view_vector, trans_mat, mRelativeXformInvTrans, nodep->getTESelectMask()); nodep->mSilhouetteExists = TRUE; } } void LLVOVolume::deleteFaces() { S32 face_count = mNumFaces; if (mDrawable.notNull()) { mDrawable->deleteFaces(0, face_count); } mNumFaces = 0; } void LLVOVolume::updateRadius() { if (mDrawable.isNull()) { return; } mVObjRadius = getScale().length(); mDrawable->setRadius(mVObjRadius); } BOOL LLVOVolume::isAttachment() const { return mAttachmentState != 0 ; } BOOL LLVOVolume::isHUDAttachment() const { // *NOTE: we assume hud attachment points are in defined range // since this range is constant for backwards compatibility // reasons this is probably a reasonable assumption to make S32 attachment_id = ATTACHMENT_ID_FROM_STATE(mAttachmentState); return ( attachment_id >= 31 && attachment_id <= 38 ); } const LLMatrix4 LLVOVolume::getRenderMatrix() const { if (mDrawable->isActive() && !mDrawable->isRoot()) { return mDrawable->getParent()->getWorldMatrix(); } return mDrawable->getWorldMatrix(); } //static S32 LLVOVolume::getTextureCost(const LLViewerTexture* img) { static const U32 ARC_TEXTURE_COST = 16; // multiplier for texture resolution - performance tested S32 texture_cost = 0; S8 type = img->getType(); if (type == LLViewerTexture::FETCHED_TEXTURE || type == LLViewerTexture::LOD_TEXTURE) { const LLViewerFetchedTexture* fetched_texturep = static_cast(img); if (fetched_texturep && fetched_texturep->getFTType() == FTT_LOCAL_FILE && (img->getID() == IMG_ALPHA_GRAD_2D || img->getID() == IMG_ALPHA_GRAD) ) { // These two textures appear to switch between each other, but are of different sizes (4x256 and 256x256). // Hardcode cost from larger one to not cause random complexity changes texture_cost = 320; } } if (texture_cost == 0) { texture_cost = 256 + (S32)(ARC_TEXTURE_COST * (img->getFullHeight() / 128.f + img->getFullWidth() / 128.f)); } return texture_cost; } // Returns a base cost and adds textures to passed in set. // total cost is returned value + 5 * size of the resulting set. // Cannot include cost of textures, as they may be re-used in linked // children, and cost should only be increased for unique textures -Nyx U32 LLVOVolume::getRenderCost(texture_cost_t &textures) const { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; /***************************************************************** * This calculation should not be modified by third party viewers, * since it is used to limit rendering and should be uniform for * everyone. If you have suggested improvements, submit them to * the official viewer for consideration. *****************************************************************/ // Get access to params we'll need at various points. // Skip if this is object doesn't have a volume (e.g. is an avatar). if (getVolume() == NULL) { return 0; } U32 num_triangles = 0; // per-prim costs static const U32 ARC_PARTICLE_COST = 1; // determined experimentally static const U32 ARC_PARTICLE_MAX = 2048; // default values static const U32 ARC_LIGHT_COST = 500; // static cost for light-producing prims static const U32 ARC_MEDIA_FACE_COST = 1500; // static cost per media-enabled face // per-prim multipliers static const F32 ARC_GLOW_MULT = 1.5f; // tested based on performance static const F32 ARC_BUMP_MULT = 1.25f; // tested based on performance static const F32 ARC_FLEXI_MULT = 5; // tested based on performance static const F32 ARC_SHINY_MULT = 1.6f; // tested based on performance static const F32 ARC_INVISI_COST = 1.2f; // tested based on performance static const F32 ARC_WEIGHTED_MESH = 1.2f; // tested based on performance static const F32 ARC_PLANAR_COST = 1.0f; // tested based on performance to have negligible impact static const F32 ARC_ANIM_TEX_COST = 4.f; // tested based on performance static const F32 ARC_ALPHA_COST = 4.f; // 4x max - based on performance F32 shame = 0; U32 invisi = 0; U32 shiny = 0; U32 glow = 0; U32 alpha = 0; U32 flexi = 0; U32 animtex = 0; U32 particles = 0; U32 bump = 0; U32 planar = 0; U32 weighted_mesh = 0; U32 produces_light = 0; U32 media_faces = 0; const LLDrawable* drawablep = mDrawable; U32 num_faces = drawablep->getNumFaces(); const LLVolumeParams& volume_params = getVolume()->getParams(); LLMeshCostData costs; if (getCostData(costs)) { if (isAnimatedObjectFast() && isRiggedMeshFast()) { // Scaling here is to make animated object vs // non-animated object ARC proportional to the // corresponding calculations for streaming cost. num_triangles = (ANIMATED_OBJECT_COST_PER_KTRI * 0.001 * costs.getEstTrisForStreamingCost())/0.06; } else { F32 radius = getScale().length()*0.5f; num_triangles = costs.getRadiusWeightedTris(radius); } } if (num_triangles <= 0) { num_triangles = 4; } if (isSculptedFast()) { if (isMeshFast()) { // base cost is dependent on mesh complexity // note that 3 is the highest LOD as of the time of this coding. S32 size = gMeshRepo.getMeshSize(volume_params.getSculptID(), getLOD()); if ( size > 0) { if (isRiggedMeshFast()) { // weighted attachment - 1 point for every 3 bytes weighted_mesh = 1; } } else { // something went wrong - user should know their content isn't render-free return 0; } } else { LLViewerFetchedTexture* texture = mSculptTexture; if (texture && textures.find(texture) == textures.end()) { textures.insert(texture); } } } if (isFlexibleFast()) { flexi = 1; } if (isParticleSource()) { particles = 1; } if (getIsLightFast()) { produces_light = 1; } { LL_PROFILE_ZONE_NAMED_CATEGORY_VOLUME("ARC - face list"); for (S32 i = 0; i < num_faces; ++i) { const LLFace* face = drawablep->getFace(i); if (!face) continue; const LLTextureEntry* te = face->getTextureEntry(); const LLViewerTexture* img = face->getTexture(); if (img) { textures.insert(img); } if (face->isInAlphaPool()) { alpha = 1; } else if (img && img->getPrimaryFormat() == GL_ALPHA) { invisi = 1; } if (face->hasMedia()) { media_faces++; } if (te) { if (te->getBumpmap()) { // bump is a multiplier, don't add per-face bump = 1; } if (te->getShiny()) { // shiny is a multiplier, don't add per-face shiny = 1; } if (te->getGlow() > 0.f) { // glow is a multiplier, don't add per-face glow = 1; } if (face->mTextureMatrix != NULL) { animtex = 1; } if (te->getTexGen()) { planar = 1; } } } } // shame currently has the "base" cost of 1 point per 15 triangles, min 2. shame = num_triangles * 5.f; shame = shame < 2.f ? 2.f : shame; // multiply by per-face modifiers if (planar) { shame *= planar * ARC_PLANAR_COST; } if (animtex) { shame *= animtex * ARC_ANIM_TEX_COST; } if (alpha) { shame *= alpha * ARC_ALPHA_COST; } if(invisi) { shame *= invisi * ARC_INVISI_COST; } if (glow) { shame *= glow * ARC_GLOW_MULT; } if (bump) { shame *= bump * ARC_BUMP_MULT; } if (shiny) { shame *= shiny * ARC_SHINY_MULT; } // multiply shame by multipliers if (weighted_mesh) { shame *= weighted_mesh * ARC_WEIGHTED_MESH; } if (flexi) { shame *= flexi * ARC_FLEXI_MULT; } // add additional costs if (particles) { const LLPartSysData *part_sys_data = &(mPartSourcep->mPartSysData); const LLPartData *part_data = &(part_sys_data->mPartData); U32 num_particles = (U32)(part_sys_data->mBurstPartCount * llceil( part_data->mMaxAge / part_sys_data->mBurstRate)); num_particles = num_particles > ARC_PARTICLE_MAX ? ARC_PARTICLE_MAX : num_particles; F32 part_size = (llmax(part_data->mStartScale[0], part_data->mEndScale[0]) + llmax(part_data->mStartScale[1], part_data->mEndScale[1])) / 2.f; shame += num_particles * part_size * ARC_PARTICLE_COST; } if (produces_light) { shame += ARC_LIGHT_COST; } if (media_faces) { shame += media_faces * ARC_MEDIA_FACE_COST; } // Streaming cost for animated objects includes a fixed cost // per linkset. Add a corresponding charge here translated into // triangles, but not weighted by any graphics properties. if (isAnimatedObjectFast() && isRootEdit()) { shame += (ANIMATED_OBJECT_BASE_COST/0.06) * 5.0f; } if (shame > mRenderComplexity_current) { mRenderComplexity_current = (S32)shame; } return (U32)shame; } F32 LLVOVolume::getEstTrianglesMax() const { if (isMeshFast() && getVolume()) { return gMeshRepo.getEstTrianglesMax(getVolume()->getParams().getSculptID()); } return 0.f; } F32 LLVOVolume::getEstTrianglesStreamingCost() const { if (isMeshFast() && getVolume()) { return gMeshRepo.getEstTrianglesStreamingCost(getVolume()->getParams().getSculptID()); } return 0.f; } F32 LLVOVolume::getStreamingCost() const { F32 radius = getScale().length()*0.5f; F32 linkset_base_cost = 0.f; LLMeshCostData costs; if (getCostData(costs)) { if (isRootEdit() && isAnimatedObject()) { // Root object of an animated object has this to account for skeleton overhead. linkset_base_cost = ANIMATED_OBJECT_BASE_COST; } if (isMesh()) { if (isAnimatedObject() && isRiggedMesh()) { return linkset_base_cost + costs.getTriangleBasedStreamingCost(); } else { return linkset_base_cost + costs.getRadiusBasedStreamingCost(radius); } } else { return linkset_base_cost + costs.getRadiusBasedStreamingCost(radius); } } else { return 0.f; } } // virtual bool LLVOVolume::getCostData(LLMeshCostData& costs) const { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; if (isMeshFast()) { return gMeshRepo.getCostData(getVolume()->getParams().getSculptID(), costs); } else { LLVolume* volume = getVolume(); S32 counts[4]; LLVolume::getLoDTriangleCounts(volume->getParams(), counts); LLMeshHeader header; header.mLodSize[0] = counts[0] * 10; header.mLodSize[1] = counts[1] * 10; header.mLodSize[2] = counts[2] * 10; header.mLodSize[3] = counts[3] * 10; return gMeshRepo.getCostData(header, costs); } } //static void LLVOVolume::updateRenderComplexity() { mRenderComplexity_last = mRenderComplexity_current; mRenderComplexity_current = 0; } U32 LLVOVolume::getTriangleCount(S32* vcount) const { U32 count = 0; LLVolume* volume = getVolume(); if (volume) { count = volume->getNumTriangles(vcount); } return count; } U32 LLVOVolume::getHighLODTriangleCount() { U32 ret = 0; LLVolume* volume = getVolume(); if (!isSculpted()) { LLVolume* ref = LLPrimitive::getVolumeManager()->refVolume(volume->getParams(), 3); ret = ref->getNumTriangles(); LLPrimitive::getVolumeManager()->unrefVolume(ref); } else if (isMesh()) { LLVolume* ref = LLPrimitive::getVolumeManager()->refVolume(volume->getParams(), 3); if (!ref->isMeshAssetLoaded() || ref->getNumVolumeFaces() == 0) { gMeshRepo.loadMesh(this, volume->getParams(), LLModel::LOD_HIGH); } ret = ref->getNumTriangles(); LLPrimitive::getVolumeManager()->unrefVolume(ref); } else { //default sculpts have a constant number of triangles ret = 31*2*31; //31 rows of 31 columns of quads for a 32x32 vertex patch } return ret; } //static void LLVOVolume::preUpdateGeom() { sNumLODChanges = 0; } void LLVOVolume::parameterChanged(U16 param_type, bool local_origin) { LLViewerObject::parameterChanged(param_type, local_origin); } void LLVOVolume::parameterChanged(U16 param_type, LLNetworkData* data, BOOL in_use, bool local_origin) { LLViewerObject::parameterChanged(param_type, data, in_use, local_origin); if (mVolumeImpl) { mVolumeImpl->onParameterChanged(param_type, data, in_use, local_origin); } if (!local_origin && param_type == LLNetworkData::PARAMS_EXTENDED_MESH) { U32 extended_mesh_flags = getExtendedMeshFlags(); bool enabled = (extended_mesh_flags & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG); bool was_enabled = (getControlAvatar() != NULL); if (enabled != was_enabled) { LL_DEBUGS("AnimatedObjects") << this << " calling onSetExtendedMeshFlags, enabled " << (U32) enabled << " was_enabled " << (U32) was_enabled << " local_origin " << (U32) local_origin << LL_ENDL; onSetExtendedMeshFlags(extended_mesh_flags); } } if (mDrawable.notNull()) { BOOL is_light = getIsLight(); if (is_light != mDrawable->isState(LLDrawable::LIGHT)) { gPipeline.setLight(mDrawable, is_light); } } updateReflectionProbePtr(); } void LLVOVolume::updateReflectionProbePtr() { if (isReflectionProbe()) { if (mReflectionProbe.isNull()) { mReflectionProbe = gPipeline.mReflectionMapManager.registerViewerObject(this); } } else if (mReflectionProbe.notNull()) { mReflectionProbe = nullptr; } } void LLVOVolume::setSelected(BOOL sel) { LLViewerObject::setSelected(sel); if (isAnimatedObject()) { getRootEdit()->recursiveMarkForUpdate(); } else { if (mDrawable.notNull()) { markForUpdate(); } } } void LLVOVolume::updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax) { } F32 LLVOVolume::getBinRadius() { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; F32 radius; static LLCachedControl octree_size_factor(gSavedSettings, "OctreeStaticObjectSizeFactor", 3); static LLCachedControl octree_attachment_size_factor(gSavedSettings, "OctreeAttachmentSizeFactor", 4); static LLCachedControl octree_distance_factor(gSavedSettings, "OctreeDistanceFactor", LLVector3(0.01f, 0.f, 0.f)); static LLCachedControl octree_alpha_distance_factor(gSavedSettings, "OctreeAlphaDistanceFactor", LLVector3(0.1f, 0.f, 0.f)); S32 size_factor = llmax((S32)octree_size_factor, 1); LLVector3 alpha_distance_factor = octree_alpha_distance_factor; //const LLVector4a* ext = mDrawable->getSpatialExtents(); bool shrink_wrap = mShouldShrinkWrap || mDrawable->isAnimating(); bool alpha_wrap = FALSE; if (!isHUDAttachment() && mDrawable->mDistanceWRTCamera < alpha_distance_factor[2]) { for (S32 i = 0; i < mDrawable->getNumFaces(); i++) { LLFace* face = mDrawable->getFace(i); if (!face) continue; if (face->isInAlphaPool() && !face->canRenderAsMask()) { alpha_wrap = TRUE; break; } } } else { shrink_wrap = FALSE; } if (alpha_wrap) { LLVector3 bounds = getScale(); radius = llmin(bounds.mV[1], bounds.mV[2]); radius = llmin(radius, bounds.mV[0]); radius *= 0.5f; //radius *= 1.f+mDrawable->mDistanceWRTCamera*alpha_distance_factor[1]; //radius += mDrawable->mDistanceWRTCamera*alpha_distance_factor[0]; } else if (shrink_wrap) { radius = mDrawable->getRadius() * 0.25f; } else { F32 szf = size_factor; radius = llmax(mDrawable->getRadius(), szf); //radius = llmax(radius, mDrawable->mDistanceWRTCamera * distance_factor[0]); } return llclamp(radius, 0.5f, 256.f); } const LLVector3 LLVOVolume::getPivotPositionAgent() const { if (mVolumeImpl) { return mVolumeImpl->getPivotPosition(); } return LLViewerObject::getPivotPositionAgent(); } void LLVOVolume::onShift(const LLVector4a &shift_vector) { if (mVolumeImpl) { mVolumeImpl->onShift(shift_vector); } updateRelativeXform(); } const LLMatrix4& LLVOVolume::getWorldMatrix(LLXformMatrix* xform) const { if (mVolumeImpl) { return mVolumeImpl->getWorldMatrix(xform); } return xform->getWorldMatrix(); } void LLVOVolume::markForUpdate() { if (mDrawable) { shrinkWrap(); } LLViewerObject::markForUpdate(); mVolumeChanged = TRUE; } LLVector3 LLVOVolume::agentPositionToVolume(const LLVector3& pos) const { LLVector3 ret = pos - getRenderPosition(); ret = ret * ~getRenderRotation(); if (!isVolumeGlobal()) { LLVector3 objScale = getScale(); LLVector3 invObjScale(1.f / objScale.mV[VX], 1.f / objScale.mV[VY], 1.f / objScale.mV[VZ]); ret.scaleVec(invObjScale); } return ret; } LLVector3 LLVOVolume::agentDirectionToVolume(const LLVector3& dir) const { LLVector3 ret = dir * ~getRenderRotation(); LLVector3 objScale = isVolumeGlobal() ? LLVector3(1,1,1) : getScale(); ret.scaleVec(objScale); return ret; } LLVector3 LLVOVolume::volumePositionToAgent(const LLVector3& dir) const { LLVector3 ret = dir; if (!isVolumeGlobal()) { LLVector3 objScale = getScale(); ret.scaleVec(objScale); } ret = ret * getRenderRotation(); ret += getRenderPosition(); return ret; } LLVector3 LLVOVolume::volumeDirectionToAgent(const LLVector3& dir) const { LLVector3 ret = dir; LLVector3 objScale = isVolumeGlobal() ? LLVector3(1,1,1) : getScale(); LLVector3 invObjScale(1.f / objScale.mV[VX], 1.f / objScale.mV[VY], 1.f / objScale.mV[VZ]); ret.scaleVec(invObjScale); ret = ret * getRenderRotation(); return ret; } BOOL LLVOVolume::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, S32 face, BOOL pick_transparent, BOOL pick_rigged, BOOL pick_unselectable, S32 *face_hitp, LLVector4a* intersection,LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent) { if (!mbCanSelect || mDrawable->isDead() || !gPipeline.hasRenderType(mDrawable->getRenderType())) { return FALSE; } if (!pick_unselectable) { if (!LLSelectMgr::instance().canSelectObject(this, TRUE)) { return FALSE; } } BOOL ret = FALSE; LLVolume* volume = getVolume(); bool transform = true; if (mDrawable->isState(LLDrawable::RIGGED)) { if ((pick_rigged) || (getAvatar() && (getAvatar()->isSelf()) && (LLFloater::isVisible(gFloaterTools)))) { updateRiggedVolume(true, LLRiggedVolume::DO_NOT_UPDATE_FACES); volume = mRiggedVolume; transform = false; } else { //cannot pick rigged attachments on other avatars or when not in build mode return FALSE; } } if (volume) { LLVector4a local_start = start; LLVector4a local_end = end; if (transform) { LLVector3 v_start(start.getF32ptr()); LLVector3 v_end(end.getF32ptr()); v_start = agentPositionToVolume(v_start); v_end = agentPositionToVolume(v_end); local_start.load3(v_start.mV); local_end.load3(v_end.mV); } LLVector4a p; LLVector4a n; LLVector2 tc; LLVector4a tn; if (intersection != NULL) { p = *intersection; } if (tex_coord != NULL) { tc = *tex_coord; } if (normal != NULL) { n = *normal; } if (tangent != NULL) { tn = *tangent; } S32 face_hit = -1; S32 start_face, end_face; if (face == -1) { start_face = 0; end_face = volume->getNumVolumeFaces(); } else { start_face = face; end_face = face+1; } pick_transparent |= isHiglightedOrBeacon(); // we *probably* shouldn't care about special cursor at all, but we *definitely* // don't care about special cursor for reflection probes -- makes alt-zoom // go through reflection probes on vehicles bool special_cursor = mReflectionProbe.isNull() && specialHoverCursor(); for (S32 i = start_face; i < end_face; ++i) { if (!special_cursor && !pick_transparent && getTE(i) && getTE(i)->getColor().mV[3] == 0.f) { //don't attempt to pick completely transparent faces unless //pick_transparent is true continue; } // This calculates the bounding box of the skinned mesh from scratch. It's actually quite expensive, but not nearly as expensive as building a full octree. // rebuild_face_octrees = false because an octree for this face will be built later only if needed for narrow phase picking. updateRiggedVolume(true, i, false); face_hit = volume->lineSegmentIntersect(local_start, local_end, i, &p, &tc, &n, &tn); if (face_hit >= 0 && mDrawable->getNumFaces() > face_hit) { LLFace* face = mDrawable->getFace(face_hit); bool ignore_alpha = false; const LLTextureEntry* te = face->getTextureEntry(); if (te) { LLMaterial* mat = te->getMaterialParams(); if (mat) { U8 mode = mat->getDiffuseAlphaMode(); if (mode == LLMaterial::DIFFUSE_ALPHA_MODE_EMISSIVE || mode == LLMaterial::DIFFUSE_ALPHA_MODE_NONE || (mode == LLMaterial::DIFFUSE_ALPHA_MODE_MASK && mat->getAlphaMaskCutoff() == 0)) { ignore_alpha = true; } } } BOOL no_texture = !face->getTexture() || !face->getTexture()->hasGLTexture(); BOOL mask = no_texture ? FALSE : face->getTexture()->getMask(face->surfaceToTexture(tc, p, n)); if (face && (ignore_alpha || pick_transparent || no_texture || mask)) { local_end = p; if (face_hitp != NULL) { *face_hitp = face_hit; } if (intersection != NULL) { if (transform) { LLVector3 v_p(p.getF32ptr()); intersection->load3(volumePositionToAgent(v_p).mV); // must map back to agent space } else { *intersection = p; } } if (normal != NULL) { if (transform) { LLVector3 v_n(n.getF32ptr()); normal->load3(volumeDirectionToAgent(v_n).mV); } else { *normal = n; } (*normal).normalize3fast(); } if (tangent != NULL) { if (transform) { LLVector3 v_tn(tn.getF32ptr()); LLVector4a trans_tangent; trans_tangent.load3(volumeDirectionToAgent(v_tn).mV); LLVector4Logical mask; mask.clear(); mask.setElement<3>(); tangent->setSelectWithMask(mask, tn, trans_tangent); } else { *tangent = tn; } (*tangent).normalize3fast(); } if (tex_coord != NULL) { *tex_coord = tc; } ret = TRUE; } } } } return ret; } bool LLVOVolume::treatAsRigged() { return isSelected() && (isAttachment() || isAnimatedObject()) && mDrawable.notNull() && mDrawable->isState(LLDrawable::RIGGED); } LLRiggedVolume* LLVOVolume::getRiggedVolume() { return mRiggedVolume; } void LLVOVolume::clearRiggedVolume() { if (mRiggedVolume.notNull()) { mRiggedVolume = NULL; updateRelativeXform(); } } void LLVOVolume::updateRiggedVolume(bool force_treat_as_rigged, LLRiggedVolume::FaceIndex face_index, bool rebuild_face_octrees) { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; //Update mRiggedVolume to match current animation frame of avatar. //Also update position/size in octree. if ((!force_treat_as_rigged) && (!treatAsRigged())) { clearRiggedVolume(); return; } LLVolume* volume = getVolume(); const LLMeshSkinInfo* skin = getSkinInfo(); if (!skin) { clearRiggedVolume(); return; } LLVOAvatar* avatar = getAvatar(); if (!avatar) { clearRiggedVolume(); return; } if (!mRiggedVolume) { LLVolumeParams p; mRiggedVolume = new LLRiggedVolume(p); updateRelativeXform(); } mRiggedVolume->update(skin, avatar, volume, face_index, rebuild_face_octrees); } void LLRiggedVolume::update( const LLMeshSkinInfo* skin, LLVOAvatar* avatar, const LLVolume* volume, FaceIndex face_index, bool rebuild_face_octrees) { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; bool copy = false; if (volume->getNumVolumeFaces() != getNumVolumeFaces()) { copy = true; } for (S32 i = 0; i < volume->getNumVolumeFaces() && !copy; ++i) { const LLVolumeFace& src_face = volume->getVolumeFace(i); const LLVolumeFace& dst_face = getVolumeFace(i); if (src_face.mNumIndices != dst_face.mNumIndices || src_face.mNumVertices != dst_face.mNumVertices) { copy = true; } } if (copy) { copyVolumeFaces(volume); } else { bool is_paused = avatar && avatar->areAnimationsPaused(); if (is_paused) { S32 frames_paused = LLFrameTimer::getFrameCount() - avatar->getMotionController().getPausedFrame(); if (frames_paused > 1) { return; } } } //build matrix palette static const size_t kMaxJoints = LL_MAX_JOINTS_PER_MESH_OBJECT; LLMatrix4a mat[kMaxJoints]; U32 maxJoints = LLSkinningUtil::getMeshJointCount(skin); LLSkinningUtil::initSkinningMatrixPalette(mat, maxJoints, skin, avatar); const LLMatrix4a bind_shape_matrix = skin->mBindShapeMatrix; S32 rigged_vert_count = 0; S32 rigged_face_count = 0; LLVector4a box_min, box_max; S32 face_begin; S32 face_end; if (face_index == DO_NOT_UPDATE_FACES) { face_begin = 0; face_end = 0; } else if (face_index == UPDATE_ALL_FACES) { face_begin = 0; face_end = volume->getNumVolumeFaces(); } else { face_begin = face_index; face_end = face_begin + 1; } for (S32 i = face_begin; i < face_end; ++i) { const LLVolumeFace& vol_face = volume->getVolumeFace(i); LLVolumeFace& dst_face = mVolumeFaces[i]; LLVector4a* weight = vol_face.mWeights; if ( weight ) { LLSkinningUtil::checkSkinWeights(weight, dst_face.mNumVertices, skin); LLVector4a* pos = dst_face.mPositions; if (pos && dst_face.mExtents) { U32 max_joints = LLSkinningUtil::getMaxJointCount(); rigged_vert_count += dst_face.mNumVertices; rigged_face_count++; #if USE_SEPARATE_JOINT_INDICES_AND_WEIGHTS if (vol_face.mJointIndices) // fast path with preconditioned joint indices { LLMatrix4a src[4]; U8* joint_indices_cursor = vol_face.mJointIndices; LLVector4a* just_weights = vol_face.mJustWeights; for (U32 j = 0; j < dst_face.mNumVertices; ++j) { LLMatrix4a final_mat; F32* w = just_weights[j].getF32ptr(); LLSkinningUtil::getPerVertexSkinMatrixWithIndices(w, joint_indices_cursor, mat, final_mat, src); joint_indices_cursor += 4; LLVector4a& v = vol_face.mPositions[j]; LLVector4a t; LLVector4a dst; bind_shape_matrix.affineTransform(v, t); final_mat.affineTransform(t, dst); pos[j] = dst; } } else #endif { for (U32 j = 0; j < dst_face.mNumVertices; ++j) { LLMatrix4a final_mat; LLSkinningUtil::getPerVertexSkinMatrix(weight[j].getF32ptr(), mat, false, final_mat, max_joints); LLVector4a& v = vol_face.mPositions[j]; LLVector4a t; LLVector4a dst; bind_shape_matrix.affineTransform(v, t); final_mat.affineTransform(t, dst); pos[j] = dst; } } //update bounding box // VFExtents change LLVector4a& min = dst_face.mExtents[0]; LLVector4a& max = dst_face.mExtents[1]; min = pos[0]; max = pos[1]; if (i==0) { box_min = min; box_max = max; } for (U32 j = 1; j < dst_face.mNumVertices; ++j) { min.setMin(min, pos[j]); max.setMax(max, pos[j]); } box_min.setMin(min,box_min); box_max.setMax(max,box_max); dst_face.mCenter->setAdd(dst_face.mExtents[0], dst_face.mExtents[1]); dst_face.mCenter->mul(0.5f); } if (rebuild_face_octrees) { dst_face.destroyOctree(); dst_face.createOctree(); } } } mExtraDebugText = llformat("rigged %d/%d - box (%f %f %f) (%f %f %f)", rigged_face_count, rigged_vert_count, box_min[0], box_min[1], box_min[2], box_max[0], box_max[1], box_max[2]); } U32 LLVOVolume::getPartitionType() const { if (isHUDAttachment()) { return LLViewerRegion::PARTITION_HUD; } if (isAnimatedObject() && getControlAvatar()) { return LLViewerRegion::PARTITION_CONTROL_AV; } if (isAttachment()) { return LLViewerRegion::PARTITION_AVATAR; } return LLViewerRegion::PARTITION_VOLUME; } LLVolumePartition::LLVolumePartition(LLViewerRegion* regionp) : LLSpatialPartition(LLVOVolume::VERTEX_DATA_MASK, TRUE, regionp), LLVolumeGeometryManager() { mLODPeriod = 32; mDepthMask = FALSE; mDrawableType = LLPipeline::RENDER_TYPE_VOLUME; mPartitionType = LLViewerRegion::PARTITION_VOLUME; mSlopRatio = 0.25f; } LLVolumeBridge::LLVolumeBridge(LLDrawable* drawablep, LLViewerRegion* regionp) : LLSpatialBridge(drawablep, TRUE, LLVOVolume::VERTEX_DATA_MASK, regionp), LLVolumeGeometryManager() { mDepthMask = FALSE; mLODPeriod = 32; mDrawableType = LLPipeline::RENDER_TYPE_VOLUME; mPartitionType = LLViewerRegion::PARTITION_BRIDGE; mSlopRatio = 0.25f; } LLAvatarBridge::LLAvatarBridge(LLDrawable* drawablep, LLViewerRegion* regionp) : LLVolumeBridge(drawablep, regionp) { mDrawableType = LLPipeline::RENDER_TYPE_AVATAR; mPartitionType = LLViewerRegion::PARTITION_AVATAR; } LLControlAVBridge::LLControlAVBridge(LLDrawable* drawablep, LLViewerRegion* regionp) : LLVolumeBridge(drawablep, regionp) { mDrawableType = LLPipeline::RENDER_TYPE_CONTROL_AV; mPartitionType = LLViewerRegion::PARTITION_CONTROL_AV; } bool can_batch_texture(LLFace* facep) { if (facep->getTextureEntry()->getBumpmap()) { //bump maps aren't worked into texture batching yet return false; } if (facep->getTextureEntry()->getMaterialParams().notNull()) { //materials don't work with texture batching yet return false; } if (facep->getTexture() && facep->getTexture()->getPrimaryFormat() == GL_ALPHA) { //can't batch invisiprims return false; } if (facep->isState(LLFace::TEXTURE_ANIM) && facep->getVirtualSize() > MIN_TEX_ANIM_SIZE) { //texture animation breaks batches return false; } if (facep->getTextureEntry()->getGLTFRenderMaterial() != nullptr) { // PBR materials break indexed texture batching return false; } return true; } const static U32 MAX_FACE_COUNT = 4096U; int32_t LLVolumeGeometryManager::sInstanceCount = 0; LLFace** LLVolumeGeometryManager::sFullbrightFaces[2] = { NULL }; LLFace** LLVolumeGeometryManager::sBumpFaces[2] = { NULL }; LLFace** LLVolumeGeometryManager::sSimpleFaces[2] = { NULL }; LLFace** LLVolumeGeometryManager::sNormFaces[2] = { NULL }; LLFace** LLVolumeGeometryManager::sSpecFaces[2] = { NULL }; LLFace** LLVolumeGeometryManager::sNormSpecFaces[2] = { NULL }; LLFace** LLVolumeGeometryManager::sPbrFaces[2] = { NULL }; LLFace** LLVolumeGeometryManager::sAlphaFaces[2] = { NULL }; LLVolumeGeometryManager::LLVolumeGeometryManager() : LLGeometryManager() { llassert(sInstanceCount >= 0); if (sInstanceCount == 0) { allocateFaces(MAX_FACE_COUNT); } ++sInstanceCount; } LLVolumeGeometryManager::~LLVolumeGeometryManager() { llassert(sInstanceCount > 0); --sInstanceCount; if (sInstanceCount <= 0) { freeFaces(); sInstanceCount = 0; } } void LLVolumeGeometryManager::allocateFaces(U32 pMaxFaceCount) { for (int i = 0; i < 2; ++i) { sFullbrightFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); sBumpFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); sSimpleFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); sNormFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); sSpecFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); sNormSpecFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); sPbrFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); sAlphaFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); } } void LLVolumeGeometryManager::freeFaces() { for (int i = 0; i < 2; ++i) { ll_aligned_free<64>(sFullbrightFaces[i]); ll_aligned_free<64>(sBumpFaces[i]); ll_aligned_free<64>(sSimpleFaces[i]); ll_aligned_free<64>(sNormFaces[i]); ll_aligned_free<64>(sSpecFaces[i]); ll_aligned_free<64>(sNormSpecFaces[i]); ll_aligned_free<64>(sPbrFaces[i]); ll_aligned_free<64>(sAlphaFaces[i]); sFullbrightFaces[i] = NULL; sBumpFaces[i] = NULL; sSimpleFaces[i] = NULL; sNormFaces[i] = NULL; sSpecFaces[i] = NULL; sNormSpecFaces[i] = NULL; sPbrFaces[i] = NULL; sAlphaFaces[i] = NULL; } } void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, U32 type) { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; if ( type == LLRenderPass::PASS_ALPHA && facep->getTextureEntry()->getMaterialParams().notNull() && !facep->getVertexBuffer()->hasDataType(LLVertexBuffer::TYPE_TANGENT) && LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_OBJECT) > 1) { LL_WARNS_ONCE("RenderMaterials") << "Oh no! No binormals for this alpha blended face!" << LL_ENDL; } bool selected = facep->getViewerObject()->isSelected(); if (selected && LLSelectMgr::getInstance()->mHideSelectedObjects) { return; } LL_LABEL_VERTEX_BUFFER(facep->getVertexBuffer(), LLRenderPass::lookupPassName(type)); U32 passType = type; bool rigged = facep->isState(LLFace::RIGGED); if (rigged) { // hacky, should probably clean up -- if this face is rigged, put it in "type + 1" // See LLRenderPass PASS_foo enum passType += 1; } //add face to drawmap LLSpatialGroup::drawmap_elem_t& draw_vec = group->mDrawMap[passType]; S32 idx = draw_vec.size()-1; bool fullbright = (type == LLRenderPass::PASS_FULLBRIGHT) || (type == LLRenderPass::PASS_INVISIBLE) || (type == LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK) || (type == LLRenderPass::PASS_ALPHA && facep->isState(LLFace::FULLBRIGHT)) || (facep->getTextureEntry()->getFullbright()); if (!fullbright && type != LLRenderPass::PASS_GLOW && !facep->getVertexBuffer()->hasDataType(LLVertexBuffer::TYPE_NORMAL)) { llassert(false); LL_WARNS() << "Non fullbright face has no normals!" << LL_ENDL; return; } const LLMatrix4* tex_mat = NULL; if (facep->isState(LLFace::TEXTURE_ANIM) && facep->getVirtualSize() > MIN_TEX_ANIM_SIZE) { tex_mat = facep->mTextureMatrix; } const LLMatrix4* model_mat = NULL; LLDrawable* drawable = facep->getDrawable(); if (rigged) { // rigged meshes ignore their model matrix model_mat = nullptr; } else if (drawable->isState(LLDrawable::ANIMATED_CHILD)) { model_mat = &drawable->getWorldMatrix(); } else if (drawable->isActive()) { model_mat = &drawable->getRenderMatrix(); } else { model_mat = &(drawable->getRegion()->mRenderMatrix); } //drawable->getVObj()->setDebugText(llformat("%d", drawable->isState(LLDrawable::ANIMATED_CHILD))); U8 bump = (type == LLRenderPass::PASS_BUMP || type == LLRenderPass::PASS_POST_BUMP) ? facep->getTextureEntry()->getBumpmap() : 0; U8 shiny = facep->getTextureEntry()->getShiny(); LLViewerTexture* tex = facep->getTexture(); U8 index = facep->getTextureIndex(); LLMaterial* mat = nullptr; LLUUID mat_id; auto* gltf_mat = (LLFetchedGLTFMaterial*) facep->getTextureEntry()->getGLTFRenderMaterial(); llassert(gltf_mat == nullptr || dynamic_cast(facep->getTextureEntry()->getGLTFRenderMaterial()) != nullptr); if (gltf_mat != nullptr) { mat_id = gltf_mat->getHash(); // TODO: cache this hash if (!facep->hasMedia()) { // no media texture, face texture will be unused tex = nullptr; } } else { mat = facep->getTextureEntry()->getMaterialParams().get(); if (mat) { mat_id = facep->getTextureEntry()->getMaterialParams()->getHash(); } } bool batchable = false; U32 shader_mask = 0xFFFFFFFF; //no shader if (mat) { BOOL is_alpha = (facep->getPoolType() == LLDrawPool::POOL_ALPHA) || (facep->getTextureEntry()->getColor().mV[3] < 0.999f) ? TRUE : FALSE; if (type == LLRenderPass::PASS_ALPHA) { shader_mask = mat->getShaderMask(LLMaterial::DIFFUSE_ALPHA_MODE_BLEND, is_alpha); } else { shader_mask = mat->getShaderMask(LLMaterial::DIFFUSE_ALPHA_MODE_DEFAULT, is_alpha); } } if (index < FACE_DO_NOT_BATCH_TEXTURES && idx >= 0) { if (mat || gltf_mat || draw_vec[idx]->mMaterial) { //can't batch textures when materials are present (yet) batchable = false; } else if (index < draw_vec[idx]->mTextureList.size()) { if (draw_vec[idx]->mTextureList[index].isNull()) { batchable = true; draw_vec[idx]->mTextureList[index] = tex; } else if (draw_vec[idx]->mTextureList[index] == tex) { //this face's texture index can be used with this batch batchable = true; } } else { //texture list can be expanded to fit this texture index batchable = true; } } LLDrawInfo* info = idx >= 0 ? draw_vec[idx] : nullptr; if (info && info->mVertexBuffer == facep->getVertexBuffer() && info->mEnd == facep->getGeomIndex()-1 && (LLPipeline::sTextureBindTest || draw_vec[idx]->mTexture == tex || batchable) && #if LL_DARWIN info->mEnd - draw_vec[idx]->mStart + facep->getGeomCount() <= (U32) gGLManager.mGLMaxVertexRange && info->mCount + facep->getIndicesCount() <= (U32) gGLManager.mGLMaxIndexRange && #endif info->mMaterialID == mat_id && info->mFullbright == fullbright && info->mBump == bump && (!mat || (info->mShiny == shiny)) && // need to break batches when a material is shared, but legacy settings are different info->mTextureMatrix == tex_mat && info->mModelMatrix == model_mat && info->mShaderMask == shader_mask && info->mAvatar == facep->mAvatar && info->getSkinHash() == facep->getSkinHash()) { info->mCount += facep->getIndicesCount(); info->mEnd += facep->getGeomCount(); if (index < FACE_DO_NOT_BATCH_TEXTURES && index >= info->mTextureList.size()) { info->mTextureList.resize(index+1); info->mTextureList[index] = tex; } info->validate(); } else { U32 start = facep->getGeomIndex(); U32 end = start + facep->getGeomCount()-1; U32 offset = facep->getIndicesStart(); U32 count = facep->getIndicesCount(); LLPointer draw_info = new LLDrawInfo(start,end,count,offset, tex, facep->getVertexBuffer(), fullbright, bump); info = draw_info; draw_vec.push_back(draw_info); draw_info->mTextureMatrix = tex_mat; draw_info->mModelMatrix = model_mat; draw_info->mBump = bump; draw_info->mShiny = shiny; static const float alpha[4] = { 0.00f, 0.25f, 0.5f, 0.75f }; float spec = alpha[shiny & TEM_SHINY_MASK]; LLVector4 specColor(spec, spec, spec, spec); draw_info->mSpecColor = specColor; draw_info->mEnvIntensity = spec; draw_info->mSpecularMap = NULL; draw_info->mMaterial = mat; draw_info->mGLTFMaterial = gltf_mat; draw_info->mShaderMask = shader_mask; draw_info->mAvatar = facep->mAvatar; draw_info->mSkinInfo = facep->mSkinInfo; if (gltf_mat) { // just remember the material ID, render pools will reference the GLTF material draw_info->mMaterialID = mat_id; } else if (mat) { draw_info->mMaterialID = mat_id; // We have a material. Update our draw info accordingly. if (!mat->getSpecularID().isNull()) { LLVector4 specColor; specColor.mV[0] = mat->getSpecularLightColor().mV[0] * (1.f / 255.f); specColor.mV[1] = mat->getSpecularLightColor().mV[1] * (1.f / 255.f); specColor.mV[2] = mat->getSpecularLightColor().mV[2] * (1.f / 255.f); specColor.mV[3] = mat->getSpecularLightExponent() * (1.f / 255.f); draw_info->mSpecColor = specColor; draw_info->mEnvIntensity = mat->getEnvironmentIntensity() * (1.f / 255.f); draw_info->mSpecularMap = facep->getViewerObject()->getTESpecularMap(facep->getTEOffset()); } draw_info->mAlphaMaskCutoff = mat->getAlphaMaskCutoff() * (1.f / 255.f); draw_info->mDiffuseAlphaMode = mat->getDiffuseAlphaMode(); draw_info->mNormalMap = facep->getViewerObject()->getTENormalMap(facep->getTEOffset()); } else { if (type == LLRenderPass::PASS_GRASS) { draw_info->mAlphaMaskCutoff = 0.5f; } else { draw_info->mAlphaMaskCutoff = 0.33f; } } // if (type == LLRenderPass::PASS_ALPHA) // always populate the draw_info ptr { //for alpha sorting facep->setDrawInfo(draw_info); } if (index < FACE_DO_NOT_BATCH_TEXTURES) { //initialize texture list for texture batching draw_info->mTextureList.resize(index+1); draw_info->mTextureList[index] = tex; } draw_info->validate(); } llassert(info->mGLTFMaterial == nullptr || (info->mVertexBuffer->getTypeMask() & LLVertexBuffer::MAP_TANGENT) != 0); llassert(type != LLPipeline::RENDER_TYPE_PASS_GLTF_PBR || info->mGLTFMaterial != nullptr); llassert(type != LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_RIGGED || info->mGLTFMaterial != nullptr); llassert(type != LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK || info->mGLTFMaterial != nullptr); llassert(type != LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK_RIGGED || info->mGLTFMaterial != nullptr); llassert(type != LLRenderPass::PASS_BUMP || (info->mVertexBuffer->getTypeMask() & LLVertexBuffer::MAP_TANGENT) != 0); llassert(type != LLRenderPass::PASS_NORMSPEC || info->mNormalMap.notNull()); llassert(type != LLRenderPass::PASS_SPECMAP || (info->mVertexBuffer->getTypeMask() & LLVertexBuffer::MAP_TEXCOORD2) != 0); } void LLVolumeGeometryManager::getGeometry(LLSpatialGroup* group) { } // add a face pointer to a list of face pointers without going over MAX_COUNT faces template static inline void add_face(T*** list, U32* count, T* face) { if (face->isState(LLFace::RIGGED)) { if (count[1] < MAX_FACE_COUNT) { face->setDrawOrderIndex(count[1]); list[1][count[1]++] = face; } } else { if (count[0] < MAX_FACE_COUNT) { face->setDrawOrderIndex(count[0]); list[0][count[0]++] = face; } } } void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; llassert(!gCubeSnapshot); if (group->changeLOD()) { group->mLastUpdateDistance = group->mDistance; } group->mLastUpdateViewAngle = group->mViewAngle; if (!group->hasState(LLSpatialGroup::GEOM_DIRTY | LLSpatialGroup::ALPHA_DIRTY)) { if (group->hasState(LLSpatialGroup::MESH_DIRTY)) { rebuildMesh(group); } return; } group->mBuilt = 1.f; LLSpatialBridge* bridge = group->getSpatialPartition()->asBridge(); LLViewerObject *vobj = NULL; LLVOVolume *vol_obj = NULL; if (bridge) { vobj = bridge->mDrawable->getVObj(); vol_obj = dynamic_cast(vobj); } if (vol_obj) { vol_obj->updateVisualComplexity(); } group->mGeometryBytes = 0; group->mSurfaceArea = 0; //cache object box size since it might be used for determining visibility const LLVector4a* bounds = group->getObjectBounds(); group->mObjectBoxSize = bounds[1].getLength3().getF32(); group->clearDrawMap(); U32 fullbright_count[2] = { 0 }; U32 bump_count[2] = { 0 }; U32 simple_count[2] = { 0 }; U32 alpha_count[2] = { 0 }; U32 norm_count[2] = { 0 }; U32 spec_count[2] = { 0 }; U32 normspec_count[2] = { 0 }; U32 pbr_count[2] = { 0 }; static LLCachedControl max_vbo_size(gSavedSettings, "RenderMaxVBOSize", 512); static LLCachedControl max_node_size(gSavedSettings, "RenderMaxNodeSize", 65536); U32 max_vertices = (max_vbo_size * 1024)/LLVertexBuffer::calcVertexSize(group->getSpatialPartition()->mVertexDataMask); U32 max_total = (max_node_size * 1024) / LLVertexBuffer::calcVertexSize(group->getSpatialPartition()->mVertexDataMask); max_vertices = llmin(max_vertices, (U32) 65535); U32 cur_total = 0; bool emissive = false; //Determine if we've received skininfo that contains an //alternate bind matrix - if it does then apply the translational component //to the joints of the avatar. #if 0 bool pelvisGotSet = false; #endif { LL_PROFILE_ZONE_NAMED("rebuildGeom - face list"); //get all the faces into a list for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter) { LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable(); if (!drawablep || drawablep->isDead() || drawablep->isState(LLDrawable::FORCE_INVISIBLE) ) { continue; } LLVOVolume* vobj = drawablep->getVOVolume(); if (!vobj) { continue; } // HACK -- brute force this check every time a drawable gets rebuilt for (S32 i = 0; i < drawablep->getNumFaces(); ++i) { vobj->updateTEMaterialTextures(i); } // apply any pending material overrides gGLTFMaterialList.applyQueuedOverrides(vobj); std::string vobj_name = llformat("Vol%p", vobj); bool is_mesh = vobj->isMesh(); if (is_mesh) { if ((vobj->getVolume() && !vobj->getVolume()->isMeshAssetLoaded()) || !gMeshRepo.meshRezEnabled()) { // Waiting for asset to fetch continue; } if (!vobj->getSkinInfo() && !vobj->isSkinInfoUnavaliable()) { // Waiting for skin info to fetch continue; } } LLVolume* volume = vobj->getVolume(); if (volume) { const LLVector3& scale = vobj->getScale(); group->mSurfaceArea += volume->getSurfaceArea() * llmax(llmax(scale.mV[0], scale.mV[1]), scale.mV[2]); } F32 est_tris = vobj->getEstTrianglesMax(); vobj->updateControlAvatar(); LL_DEBUGS("AnimatedObjectsLinkset") << vobj_name << " rebuilding, isAttachment: " << (U32) vobj->isAttachment() << " is_mesh " << is_mesh << " est_tris " << est_tris << " is_animated " << vobj->isAnimatedObject() << " can_animate " << vobj->canBeAnimatedObject() << " cav " << vobj->getControlAvatar() << " lod " << vobj->getLOD() << " drawable rigged " << (drawablep->isState(LLDrawable::RIGGED)) << " drawable state " << drawablep->getState() << " playing " << (U32) (vobj->getControlAvatar() ? vobj->getControlAvatar()->mPlaying : false) << " frame " << LLFrameTimer::getFrameCount() << LL_ENDL; llassert_always(vobj); vobj->updateTextureVirtualSize(true); vobj->preRebuild(); drawablep->clearState(LLDrawable::HAS_ALPHA); LLVOAvatar* avatar = nullptr; const LLMeshSkinInfo* skinInfo = nullptr; if (is_mesh) { skinInfo = vobj->getSkinInfo(); } if (skinInfo) { if (vobj->isAnimatedObject()) { avatar = vobj->getControlAvatar(); } else { avatar = vobj->getAvatar(); } } if (avatar != nullptr) { avatar->addAttachmentOverridesForObject(vobj, NULL, false); } // Standard rigged mesh attachments: bool rigged = !vobj->isAnimatedObject() && skinInfo && vobj->isAttachment(); // Animated objects. Have to check for isRiggedMesh() to // exclude static objects in animated object linksets. rigged = rigged || (vobj->isAnimatedObject() && vobj->isRiggedMesh() && vobj->getControlAvatar() && vobj->getControlAvatar()->mPlaying); bool any_rigged_face = false; //for each face for (S32 i = 0; i < drawablep->getNumFaces(); i++) { LLFace* facep = drawablep->getFace(i); if (!facep) { continue; } #if 0 #if LL_RELEASE_WITH_DEBUG_INFO const LLUUID pbr_id( "49c88210-7238-2a6b-70ac-92d4f35963cf" ); const LLUUID obj_id( vobj->getID() ); bool is_pbr = (obj_id == pbr_id); #else bool is_pbr = false; #endif #else LLGLTFMaterial *gltf_mat = facep->getTextureEntry()->getGLTFRenderMaterial(); bool is_pbr = gltf_mat != nullptr; #endif //ALWAYS null out vertex buffer on rebuild -- if the face lands in a render // batch, it will recover its vertex buffer reference from the spatial group facep->setVertexBuffer(NULL); //sum up face verts and indices drawablep->updateFaceSize(i); if (rigged) { if (!facep->isState(LLFace::RIGGED)) { //completely reset vertex buffer facep->clearVertexBuffer(); } facep->setState(LLFace::RIGGED); facep->mSkinInfo = (LLMeshSkinInfo*) skinInfo; // TODO -- fix ugly de-consting here facep->mAvatar = avatar; any_rigged_face = true; } else { if (facep->isState(LLFace::RIGGED)) { //face is not rigged but used to be, remove from rigged face pool LLDrawPoolAvatar* pool = (LLDrawPoolAvatar*)facep->getPool(); if (pool) { pool->removeFace(facep); } facep->clearState(LLFace::RIGGED); facep->mAvatar = NULL; facep->mSkinInfo = NULL; } } if (cur_total > max_total || facep->getIndicesCount() <= 0 || facep->getGeomCount() <= 0) { facep->clearVertexBuffer(); continue; } if (facep->hasGeometry() && (rigged || // <-- HACK FIXME -- getPixelArea might be incorrect for rigged objects facep->getPixelArea() > FORCE_CULL_AREA)) // <-- don't render tiny faces { cur_total += facep->getGeomCount(); const LLTextureEntry* te = facep->getTextureEntry(); LLViewerTexture* tex = facep->getTexture(); if (te->getGlow() > 0.f) { emissive = true; } if (facep->isState(LLFace::TEXTURE_ANIM)) { if (!vobj->mTexAnimMode) { facep->clearState(LLFace::TEXTURE_ANIM); } } BOOL force_simple = (facep->getPixelArea() < FORCE_SIMPLE_RENDER_AREA); U32 type = gPipeline.getPoolTypeFromTE(te, tex); if (is_pbr && gltf_mat && gltf_mat->mAlphaMode != LLGLTFMaterial::ALPHA_MODE_BLEND) { type = LLDrawPool::POOL_GLTF_PBR; } else if (type != LLDrawPool::POOL_ALPHA && force_simple) { type = LLDrawPool::POOL_SIMPLE; } facep->setPoolType(type); if (vobj->isHUDAttachment() && !is_pbr) { facep->setState(LLFace::FULLBRIGHT); } if (vobj->mTextureAnimp && vobj->mTexAnimMode) { if (vobj->mTextureAnimp->mFace <= -1) { S32 face; for (face = 0; face < vobj->getNumTEs(); face++) { LLFace * facep = drawablep->getFace(face); if (facep) { facep->setState(LLFace::TEXTURE_ANIM); } } } else if (vobj->mTextureAnimp->mFace < vobj->getNumTEs()) { LLFace * facep = drawablep->getFace(vobj->mTextureAnimp->mFace); if (facep) { facep->setState(LLFace::TEXTURE_ANIM); } } } if (type == LLDrawPool::POOL_ALPHA) { if (facep->canRenderAsMask()) { //can be treated as alpha mask add_face(sSimpleFaces, simple_count, facep); } else { F32 alpha; if (is_pbr) { alpha = gltf_mat ? gltf_mat->mBaseColor.mV[3] : 1.0; } else { alpha = te->getColor().mV[3]; } if (alpha > 0.f || te->getGlow() > 0.f) { //only treat as alpha in the pipeline if < 100% transparent drawablep->setState(LLDrawable::HAS_ALPHA); add_face(sAlphaFaces, alpha_count, facep); } else if (LLDrawPoolAlpha::sShowDebugAlpha || (gPipeline.sRenderHighlight && !drawablep->getParent() && //only root objects are highlighted with red color in this case drawablep->getVObj() && drawablep->getVObj()->flagScripted() && (LLPipeline::getRenderScriptedBeacons() || (LLPipeline::getRenderScriptedTouchBeacons() && drawablep->getVObj()->flagHandleTouch())))) { //draw the transparent face for debugging purposes using a custom texture add_face(sAlphaFaces, alpha_count, facep); } } } else { if (drawablep->isState(LLDrawable::REBUILD_VOLUME)) { facep->mLastUpdateTime = gFrameTimeSeconds; } { LLGLTFMaterial* gltf_mat = te->getGLTFRenderMaterial(); if (gltf_mat != nullptr || (te->getMaterialParams().notNull())) { if (gltf_mat != nullptr) { add_face(sPbrFaces, pbr_count, facep); } else { LLMaterial* mat = te->getMaterialParams().get(); if (mat->getNormalID().notNull() || // <-- has a normal map, needs tangents (te->getBumpmap() && (te->getBumpmap() < 18))) // <-- has an emboss bump map, needs tangents { if (mat->getSpecularID().notNull()) { //has normal and specular maps (needs texcoord1, texcoord2, and tangent) add_face(sNormSpecFaces, normspec_count, facep); } else { //has normal map (needs texcoord1 and tangent) add_face(sNormFaces, norm_count, facep); } } else if (mat->getSpecularID().notNull()) { //has specular map but no normal map, needs texcoord2 add_face(sSpecFaces, spec_count, facep); } else { //has neither specular map nor normal map, only needs texcoord0 add_face(sSimpleFaces, simple_count, facep); } } } else if (te->getBumpmap()) { //needs normal + tangent add_face(sBumpFaces, bump_count, facep); } else if (te->getShiny() || !te->getFullbright()) { //needs normal add_face(sSimpleFaces, simple_count, facep); } else { //doesn't need normal facep->setState(LLFace::FULLBRIGHT); add_face(sFullbrightFaces, fullbright_count, facep); } } } } else { //face has no renderable geometry facep->clearVertexBuffer(); } } if (any_rigged_face) { if (!drawablep->isState(LLDrawable::RIGGED)) { drawablep->setState(LLDrawable::RIGGED); LLDrawable* root = drawablep->getRoot(); if (root != drawablep) { root->setState(LLDrawable::RIGGED_CHILD); } //first time this is drawable is being marked as rigged, // do another LoD update to use avatar bounding box vobj->updateLOD(); } } else { drawablep->clearState(LLDrawable::RIGGED); vobj->updateRiggedVolume(false); } } } //PROCESS NON-ALPHA FACES U32 simple_mask = LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_COLOR; U32 alpha_mask = simple_mask | 0x80000000; //hack to give alpha verts their own VBO U32 bump_mask = LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_COLOR; U32 fullbright_mask = LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_COLOR; U32 norm_mask = simple_mask | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TANGENT; U32 normspec_mask = norm_mask | LLVertexBuffer::MAP_TEXCOORD2; U32 spec_mask = simple_mask | LLVertexBuffer::MAP_TEXCOORD2; U32 pbr_mask = LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_COLOR | LLVertexBuffer::MAP_TANGENT; if (emissive) { //emissive faces are present, include emissive byte to preserve batching simple_mask = simple_mask | LLVertexBuffer::MAP_EMISSIVE; alpha_mask = alpha_mask | LLVertexBuffer::MAP_EMISSIVE; bump_mask = bump_mask | LLVertexBuffer::MAP_EMISSIVE; fullbright_mask = fullbright_mask | LLVertexBuffer::MAP_EMISSIVE; norm_mask = norm_mask | LLVertexBuffer::MAP_EMISSIVE; normspec_mask = normspec_mask | LLVertexBuffer::MAP_EMISSIVE; spec_mask = spec_mask | LLVertexBuffer::MAP_EMISSIVE; pbr_mask = pbr_mask | LLVertexBuffer::MAP_EMISSIVE; } BOOL batch_textures = LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_OBJECT) > 1; // add extra vertex data for deferred rendering (not necessarily for batching textures) if (batch_textures) { bump_mask = bump_mask | LLVertexBuffer::MAP_TANGENT; simple_mask = simple_mask | LLVertexBuffer::MAP_TEXTURE_INDEX; alpha_mask = alpha_mask | LLVertexBuffer::MAP_TEXTURE_INDEX | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2; fullbright_mask = fullbright_mask | LLVertexBuffer::MAP_TEXTURE_INDEX; } group->mGeometryBytes = 0; U32 geometryBytes = 0; // generate render batches for static geometry U32 extra_mask = LLVertexBuffer::MAP_TEXTURE_INDEX; BOOL alpha_sort = TRUE; BOOL rigged = FALSE; for (int i = 0; i < 2; ++i) //two sets, static and rigged) { geometryBytes += genDrawInfo(group, simple_mask | extra_mask, sSimpleFaces[i], simple_count[i], FALSE, batch_textures, rigged); geometryBytes += genDrawInfo(group, fullbright_mask | extra_mask, sFullbrightFaces[i], fullbright_count[i], FALSE, batch_textures, rigged); geometryBytes += genDrawInfo(group, alpha_mask | extra_mask, sAlphaFaces[i], alpha_count[i], alpha_sort, batch_textures, rigged); geometryBytes += genDrawInfo(group, bump_mask | extra_mask, sBumpFaces[i], bump_count[i], FALSE, FALSE, rigged); geometryBytes += genDrawInfo(group, norm_mask | extra_mask, sNormFaces[i], norm_count[i], FALSE, FALSE, rigged); geometryBytes += genDrawInfo(group, spec_mask | extra_mask, sSpecFaces[i], spec_count[i], FALSE, FALSE, rigged); geometryBytes += genDrawInfo(group, normspec_mask | extra_mask, sNormSpecFaces[i], normspec_count[i], FALSE, FALSE, rigged); geometryBytes += genDrawInfo(group, pbr_mask | extra_mask, sPbrFaces[i], pbr_count[i], FALSE, FALSE, rigged); // for rigged set, add weights and disable alpha sorting (rigged items use depth buffer) extra_mask |= LLVertexBuffer::MAP_WEIGHT4; rigged = TRUE; } group->mGeometryBytes = geometryBytes; { //drawables have been rebuilt, clear rebuild status for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter) { LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable(); if(drawablep) { drawablep->clearState(LLDrawable::REBUILD_ALL); } } } group->mLastUpdateTime = gFrameTimeSeconds; group->mBuilt = 1.f; group->clearState(LLSpatialGroup::GEOM_DIRTY | LLSpatialGroup::ALPHA_DIRTY); } void LLVolumeGeometryManager::rebuildMesh(LLSpatialGroup* group) { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; llassert(group); if (group && group->hasState(LLSpatialGroup::MESH_DIRTY) && !group->hasState(LLSpatialGroup::GEOM_DIRTY)) { { LL_PROFILE_ZONE_NAMED("rebuildMesh - gen draw info"); group->mBuilt = 1.f; const U32 MAX_BUFFER_COUNT = 4096; LLVertexBuffer* locked_buffer[MAX_BUFFER_COUNT]; U32 buffer_count = 0; for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter) { LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable(); if (drawablep && !drawablep->isDead() && drawablep->isState(LLDrawable::REBUILD_ALL)) { LLVOVolume* vobj = drawablep->getVOVolume(); if (!vobj) continue; if (vobj->isNoLOD()) continue; vobj->preRebuild(); if (drawablep->isState(LLDrawable::ANIMATED_CHILD)) { vobj->updateRelativeXform(true); } LLVolume* volume = vobj->getVolume(); if (!volume) continue; for (S32 i = 0; i < drawablep->getNumFaces(); ++i) { LLFace* face = drawablep->getFace(i); if (face) { LLVertexBuffer* buff = face->getVertexBuffer(); if (buff) { if (!face->getGeometryVolume(*volume, // volume face->getTEOffset(), // face_index vobj->getRelativeXform(), // mat_vert_in vobj->getRelativeXformInvTrans(), // mat_norm_in face->getGeomIndex(), // index_offset false, // force_rebuild true)) // no_debug_assert { // Something's gone wrong with the vertex buffer accounting, // rebuild this group with no debug assert because MESH_DIRTY group->dirtyGeom(); gPipeline.markRebuild(group); } buff->unmapBuffer(); } } } if (drawablep->isState(LLDrawable::ANIMATED_CHILD)) { vobj->updateRelativeXform(); } drawablep->clearState(LLDrawable::REBUILD_ALL); } } { LL_PROFILE_ZONE_NAMED("rebuildMesh - flush"); for (LLVertexBuffer** iter = locked_buffer, ** end_iter = locked_buffer+buffer_count; iter != end_iter; ++iter) { (*iter)->unmapBuffer(); } // don't forget alpha if(group != NULL && !group->mVertexBuffer.isNull()) { group->mVertexBuffer->unmapBuffer(); } } group->clearState(LLSpatialGroup::MESH_DIRTY | LLSpatialGroup::NEW_DRAWINFO); } } } struct CompareBatchBreaker { bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) { const LLTextureEntry* lte = lhs->getTextureEntry(); const LLTextureEntry* rte = rhs->getTextureEntry(); if (lte->getBumpmap() != rte->getBumpmap()) { return lte->getBumpmap() < rte->getBumpmap(); } else if (lte->getFullbright() != rte->getFullbright()) { return lte->getFullbright() < rte->getFullbright(); } else if (lte->getMaterialID() != rte->getMaterialID()) { return lte->getMaterialID() < rte->getMaterialID(); } else if (lte->getShiny() != rte->getShiny()) { return lte->getShiny() < rte->getShiny(); } else if (lhs->getTexture() != rhs->getTexture()) { return lhs->getTexture() < rhs->getTexture(); } else { // all else being equal, maintain consistent draw order return lhs->getDrawOrderIndex() < rhs->getDrawOrderIndex(); } } }; struct CompareBatchBreakerRigged { bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) { if (lhs->mAvatar != rhs->mAvatar) { return lhs->mAvatar < rhs->mAvatar; } else if (lhs->mSkinInfo->mHash != rhs->mSkinInfo->mHash) { return lhs->mSkinInfo->mHash < rhs->mSkinInfo->mHash; } else { // "inherit" non-rigged behavior CompareBatchBreaker comp; return comp(lhs, rhs); } } }; U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace** faces, U32 face_count, BOOL distance_sort, BOOL batch_textures, BOOL rigged) { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; U32 geometryBytes = 0; //calculate maximum number of vertices to store in a single buffer static LLCachedControl max_vbo_size(gSavedSettings, "RenderMaxVBOSize", 512); U32 max_vertices = (max_vbo_size * 1024)/LLVertexBuffer::calcVertexSize(group->getSpatialPartition()->mVertexDataMask); max_vertices = llmin(max_vertices, (U32) 65535); { LL_PROFILE_ZONE_NAMED("genDrawInfo - sort"); if (rigged) { if (!distance_sort) // <--- alpha "sort" rigged faces by maintaining original draw order { //sort faces by things that break batches, including avatar and mesh id std::sort(faces, faces + face_count, CompareBatchBreakerRigged()); } } else if (!distance_sort) { //sort faces by things that break batches, not including avatar and mesh id std::sort(faces, faces + face_count, CompareBatchBreaker()); } else { //sort faces by distance std::sort(faces, faces+face_count, LLFace::CompareDistanceGreater()); } } bool hud_group = group->isHUDGroup() ; LLFace** face_iter = faces; LLFace** end_faces = faces+face_count; LLSpatialGroup::buffer_map_t buffer_map; LLViewerTexture* last_tex = NULL; S32 texture_index_channels = 1; if (gGLManager.mGLSLVersionMajor > 1 || gGLManager.mGLSLVersionMinor >= 30) { texture_index_channels = LLGLSLShader::sIndexedTextureChannels-1; //always reserve one for shiny for now just for simplicity; } if (distance_sort) { texture_index_channels = gDeferredAlphaProgram.mFeatures.mIndexedTextureChannels; } texture_index_channels = LLGLSLShader::sIndexedTextureChannels; bool flexi = false; while (face_iter != end_faces) { //pull off next face LLFace* facep = *face_iter; LLViewerTexture* tex = facep->getTexture(); const LLTextureEntry* te = facep->getTextureEntry(); LLMaterialPtr mat = te->getMaterialParams(); LLMaterialID matId = te->getMaterialID(); if (distance_sort) { tex = NULL; } if (last_tex != tex) { last_tex = tex; } bool bake_sunlight = LLPipeline::sBakeSunlight && facep->getDrawable()->isStatic(); U32 index_count = facep->getIndicesCount(); U32 geom_count = facep->getGeomCount(); flexi = flexi || facep->getViewerObject()->getVolume()->isUnique(); //sum up vertices needed for this render batch LLFace** i = face_iter; ++i; const U32 MAX_TEXTURE_COUNT = 32; LLViewerTexture* texture_list[MAX_TEXTURE_COUNT]; U32 texture_count = 0; { LL_PROFILE_ZONE_NAMED("genDrawInfo - face size"); if (batch_textures) { U8 cur_tex = 0; facep->setTextureIndex(cur_tex); if (texture_count < MAX_TEXTURE_COUNT) { texture_list[texture_count++] = tex; } if (can_batch_texture(facep)) { //populate texture_list with any textures that can be batched //move i to the next unbatchable face while (i != end_faces) { facep = *i; if (!can_batch_texture(facep)) { //face is bump mapped or has an animated texture matrix -- can't //batch more than 1 texture at a time facep->setTextureIndex(0); break; } if (facep->getTexture() != tex) { if (distance_sort) { //textures might be out of order, see if texture exists in current batch bool found = false; for (U32 tex_idx = 0; tex_idx < texture_count; ++tex_idx) { if (facep->getTexture() == texture_list[tex_idx]) { cur_tex = tex_idx; found = true; break; } } if (!found) { cur_tex = texture_count; } } else { cur_tex++; } if (cur_tex >= texture_index_channels) { //cut batches when index channels are depleted break; } tex = facep->getTexture(); if (texture_count < MAX_TEXTURE_COUNT) { texture_list[texture_count++] = tex; } } if (geom_count + facep->getGeomCount() > max_vertices) { //cut batches on geom count too big break; } ++i; flexi = flexi || facep->getViewerObject()->getVolume()->isUnique(); index_count += facep->getIndicesCount(); geom_count += facep->getGeomCount(); facep->setTextureIndex(cur_tex); } } else { facep->setTextureIndex(0); } tex = texture_list[0]; } else { while (i != end_faces && (LLPipeline::sTextureBindTest || (distance_sort || ((*i)->getTexture() == tex)))) { facep = *i; const LLTextureEntry* nextTe = facep->getTextureEntry(); if (nextTe->getMaterialID() != matId) { break; } //face has no texture index facep->mDrawInfo = NULL; facep->setTextureIndex(FACE_DO_NOT_BATCH_TEXTURES); if (geom_count + facep->getGeomCount() > max_vertices) { //cut batches on geom count too big break; } ++i; index_count += facep->getIndicesCount(); geom_count += facep->getGeomCount(); flexi = flexi || facep->getViewerObject()->getVolume()->isUnique(); } } } //create vertex buffer LLPointer buffer; { LL_PROFILE_ZONE_NAMED("genDrawInfo - allocate"); buffer = new LLVertexBuffer(mask); if(!buffer->allocateBuffer(geom_count, index_count)) { LL_WARNS() << "Failed to allocate group Vertex Buffer to " << geom_count << " vertices and " << index_count << " indices" << LL_ENDL; buffer = NULL; } } if (buffer) { geometryBytes += buffer->getSize() + buffer->getIndicesSize(); buffer_map[mask][*face_iter].push_back(buffer); } //add face geometry U32 indices_index = 0; U16 index_offset = 0; while (face_iter < i) { //update face indices for new buffer facep = *face_iter; if (buffer.isNull()) { // Bulk allocation failed facep->setVertexBuffer(buffer); facep->setSize(0, 0); // mark as no geometry ++face_iter; continue; } facep->setIndicesIndex(indices_index); facep->setGeomIndex(index_offset); facep->setVertexBuffer(buffer); if (batch_textures && facep->getTextureIndex() == FACE_DO_NOT_BATCH_TEXTURES) { LL_ERRS() << "Invalid texture index." << LL_ENDL; } { //for debugging, set last time face was updated vs moved facep->updateRebuildFlags(); { //copy face geometry into vertex buffer LLDrawable* drawablep = facep->getDrawable(); LLVOVolume* vobj = drawablep->getVOVolume(); LLVolume* volume = vobj->getVolume(); if (drawablep->isState(LLDrawable::ANIMATED_CHILD)) { vobj->updateRelativeXform(true); } U32 te_idx = facep->getTEOffset(); if (!facep->getGeometryVolume(*volume, te_idx, vobj->getRelativeXform(), vobj->getRelativeXformInvTrans(), index_offset,true)) { LL_WARNS() << "Failed to get geometry for face!" << LL_ENDL; } if (drawablep->isState(LLDrawable::ANIMATED_CHILD)) { vobj->updateRelativeXform(false); } } } index_offset += facep->getGeomCount(); indices_index += facep->getIndicesCount(); //append face to appropriate render batch BOOL force_simple = facep->getPixelArea() < FORCE_SIMPLE_RENDER_AREA; BOOL fullbright = facep->isState(LLFace::FULLBRIGHT); if ((mask & LLVertexBuffer::MAP_NORMAL) == 0) { //paranoia check to make sure GL doesn't try to read non-existant normals fullbright = TRUE; } const LLTextureEntry* te = facep->getTextureEntry(); LLGLTFMaterial* gltf_mat = te->getGLTFRenderMaterial(); if (hud_group && gltf_mat == nullptr) { //all hud attachments are fullbright fullbright = TRUE; } tex = facep->getTexture(); BOOL is_alpha = (facep->getPoolType() == LLDrawPool::POOL_ALPHA) ? TRUE : FALSE; LLMaterial* mat = nullptr; bool can_be_shiny = false; // ignore traditional material if GLTF material is present if (gltf_mat == nullptr) { mat = te->getMaterialParams().get(); can_be_shiny = true; if (mat) { U8 mode = mat->getDiffuseAlphaMode(); can_be_shiny = mode == LLMaterial::DIFFUSE_ALPHA_MODE_NONE || mode == LLMaterial::DIFFUSE_ALPHA_MODE_EMISSIVE; } } F32 blinn_phong_alpha = te->getColor().mV[3]; bool use_legacy_bump = te->getBumpmap() && (te->getBumpmap() < 18) && (!mat || mat->getNormalID().isNull()); bool blinn_phong_opaque = blinn_phong_alpha >= 0.999f; bool blinn_phong_transparent = blinn_phong_alpha < 0.999f; if (!gltf_mat) { is_alpha = (is_alpha || blinn_phong_transparent) ? TRUE : FALSE; } if (gltf_mat || (mat && !hud_group)) { bool material_pass = false; if (gltf_mat) { // all other parameters ignored if gltf material is present if (gltf_mat->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_BLEND) { registerFace(group, facep, LLRenderPass::PASS_ALPHA); } else if (gltf_mat->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_MASK) { registerFace(group, facep, LLRenderPass::PASS_GLTF_PBR_ALPHA_MASK); } else { registerFace(group, facep, LLRenderPass::PASS_GLTF_PBR); } } else // do NOT use 'fullbright' for this logic or you risk sending // things without normals down the materials pipeline and will // render poorly if not crash NORSPEC-240,314 // if (te->getFullbright()) { if (mat->getDiffuseAlphaMode() == LLMaterial::DIFFUSE_ALPHA_MODE_MASK) { if (blinn_phong_opaque) { registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK); } else { registerFace(group, facep, LLRenderPass::PASS_ALPHA); } } else if (is_alpha) { registerFace(group, facep, LLRenderPass::PASS_ALPHA); } else { if (mat->getEnvironmentIntensity() > 0 || te->getShiny() > 0) { material_pass = true; } else { if (blinn_phong_opaque) { registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT); } else { registerFace(group, facep, LLRenderPass::PASS_ALPHA); } } } } else if (blinn_phong_transparent) { registerFace(group, facep, LLRenderPass::PASS_ALPHA); } else if (use_legacy_bump) { llassert(mask & LLVertexBuffer::MAP_TANGENT); // we have a material AND legacy bump settings, but no normal map registerFace(group, facep, LLRenderPass::PASS_BUMP); } else { material_pass = true; } if (material_pass) { static const U32 pass[] = { LLRenderPass::PASS_MATERIAL, LLRenderPass::PASS_ALPHA, //LLRenderPass::PASS_MATERIAL_ALPHA, LLRenderPass::PASS_MATERIAL_ALPHA_MASK, LLRenderPass::PASS_MATERIAL_ALPHA_EMISSIVE, LLRenderPass::PASS_SPECMAP, LLRenderPass::PASS_ALPHA, //LLRenderPass::PASS_SPECMAP_BLEND, LLRenderPass::PASS_SPECMAP_MASK, LLRenderPass::PASS_SPECMAP_EMISSIVE, LLRenderPass::PASS_NORMMAP, LLRenderPass::PASS_ALPHA, //LLRenderPass::PASS_NORMMAP_BLEND, LLRenderPass::PASS_NORMMAP_MASK, LLRenderPass::PASS_NORMMAP_EMISSIVE, LLRenderPass::PASS_NORMSPEC, LLRenderPass::PASS_ALPHA, //LLRenderPass::PASS_NORMSPEC_BLEND, LLRenderPass::PASS_NORMSPEC_MASK, LLRenderPass::PASS_NORMSPEC_EMISSIVE, }; U32 alpha_mode = mat->getDiffuseAlphaMode(); if (!distance_sort && alpha_mode == LLMaterial::DIFFUSE_ALPHA_MODE_BLEND) { // HACK - this should never happen, but sometimes we get a material that thinks it has alpha blending when it ought not alpha_mode = LLMaterial::DIFFUSE_ALPHA_MODE_NONE; } U32 mask = mat->getShaderMask(alpha_mode, is_alpha); U32 vb_mask = facep->getVertexBuffer()->getTypeMask(); // HACK - this should also never happen, but sometimes we get here and the material thinks it has a specmap now // even though it didn't appear to have a specmap when the face was added to the list of faces if ((mask & 0x4) && !(vb_mask & LLVertexBuffer::MAP_TEXCOORD2)) { mask &= ~0x4; } llassert(mask < sizeof(pass)/sizeof(U32)); mask = llmin(mask, (U32)(sizeof(pass)/sizeof(U32)-1)); // if this is going into alpha pool, distance sort MUST be true llassert(pass[mask] == LLRenderPass::PASS_ALPHA ? distance_sort : true); registerFace(group, facep, pass[mask]); } } else if (mat) { U8 mode = mat->getDiffuseAlphaMode(); is_alpha = (is_alpha || (mode == LLMaterial::DIFFUSE_ALPHA_MODE_BLEND)); if (is_alpha) { mode = LLMaterial::DIFFUSE_ALPHA_MODE_BLEND; } if (mode == LLMaterial::DIFFUSE_ALPHA_MODE_MASK) { registerFace(group, facep, fullbright ? LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK : LLRenderPass::PASS_ALPHA_MASK); } else if (is_alpha ) { registerFace(group, facep, LLRenderPass::PASS_ALPHA); } else if (gPipeline.shadersLoaded() && te->getShiny() && can_be_shiny) { registerFace(group, facep, fullbright ? LLRenderPass::PASS_FULLBRIGHT_SHINY : LLRenderPass::PASS_SHINY); } else { registerFace(group, facep, fullbright ? LLRenderPass::PASS_FULLBRIGHT : LLRenderPass::PASS_SIMPLE); } } else if (is_alpha) { // can we safely treat this as an alpha mask? if (facep->getFaceColor().mV[3] <= 0.f) { //100% transparent, don't render unless we're highlighting transparent registerFace(group, facep, LLRenderPass::PASS_ALPHA_INVISIBLE); } else if (facep->canRenderAsMask() && !hud_group) { if (te->getFullbright() || LLPipeline::sNoAlpha) { registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK); } else { registerFace(group, facep, LLRenderPass::PASS_ALPHA_MASK); } } else { registerFace(group, facep, LLRenderPass::PASS_ALPHA); } } else if (gPipeline.shadersLoaded() && te->getShiny() && can_be_shiny) { //shiny if (tex->getPrimaryFormat() == GL_ALPHA) { //invisiprim+shiny registerFace(group, facep, LLRenderPass::PASS_INVISI_SHINY); registerFace(group, facep, LLRenderPass::PASS_INVISIBLE); } else if (!hud_group) { //deferred rendering if (te->getFullbright()) { //register in post deferred fullbright shiny pass registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT_SHINY); if (te->getBumpmap()) { //register in post deferred bump pass registerFace(group, facep, LLRenderPass::PASS_POST_BUMP); } } else if (use_legacy_bump) { //register in deferred bump pass llassert(mask& LLVertexBuffer::MAP_TANGENT); registerFace(group, facep, LLRenderPass::PASS_BUMP); } else { //register in deferred simple pass (deferred simple includes shiny) llassert(mask & LLVertexBuffer::MAP_NORMAL); registerFace(group, facep, LLRenderPass::PASS_SIMPLE); } } else if (fullbright) { //not deferred, register in standard fullbright shiny pass registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT_SHINY); } else { //not deferred or fullbright, register in standard shiny pass registerFace(group, facep, LLRenderPass::PASS_SHINY); } } else { //not alpha and not shiny if (!is_alpha && tex->getPrimaryFormat() == GL_ALPHA) { //invisiprim registerFace(group, facep, LLRenderPass::PASS_INVISIBLE); } else if (fullbright || bake_sunlight) { //fullbright if (mat && mat->getDiffuseAlphaMode() == LLMaterial::DIFFUSE_ALPHA_MODE_MASK) { registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK); } else { registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT); } if (!hud_group && use_legacy_bump) { //if this is the deferred render and a bump map is present, register in post deferred bump registerFace(group, facep, LLRenderPass::PASS_POST_BUMP); } } else { if (use_legacy_bump) { //non-shiny or fullbright deferred bump llassert(mask& LLVertexBuffer::MAP_TANGENT); registerFace(group, facep, LLRenderPass::PASS_BUMP); } else { //all around simple llassert(mask & LLVertexBuffer::MAP_NORMAL); if (mat && mat->getDiffuseAlphaMode() == LLMaterial::DIFFUSE_ALPHA_MODE_MASK) { //material alpha mask can be respected in non-deferred registerFace(group, facep, LLRenderPass::PASS_ALPHA_MASK); } else { registerFace(group, facep, LLRenderPass::PASS_SIMPLE); } } } if (!gPipeline.shadersLoaded() && !is_alpha && te->getShiny()) { //shiny as an extra pass when shaders are disabled registerFace(group, facep, LLRenderPass::PASS_SHINY); } } //not sure why this is here, and looks like it might cause bump mapped objects to get rendered redundantly -- davep 5/11/2010 if (!is_alpha && hud_group) { llassert((mask & LLVertexBuffer::MAP_NORMAL) || fullbright); facep->setPoolType((fullbright) ? LLDrawPool::POOL_FULLBRIGHT : LLDrawPool::POOL_SIMPLE); if (!force_simple && use_legacy_bump) { llassert(mask & LLVertexBuffer::MAP_TANGENT); registerFace(group, facep, LLRenderPass::PASS_BUMP); } } if (!is_alpha && LLPipeline::sRenderGlow && te->getGlow() > 0.f) { if (gltf_mat) { registerFace(group, facep, LLRenderPass::PASS_GLTF_GLOW); } else { registerFace(group, facep, LLRenderPass::PASS_GLOW); } } ++face_iter; } if (buffer) { buffer->unmapBuffer(); } } group->mBufferMap[mask].clear(); for (LLSpatialGroup::buffer_texture_map_t::iterator i = buffer_map[mask].begin(); i != buffer_map[mask].end(); ++i) { group->mBufferMap[mask][i->first] = i->second; } return geometryBytes; } void LLVolumeGeometryManager::addGeometryCount(LLSpatialGroup* group, U32& vertex_count, U32& index_count) { //for each drawable for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter) { LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable(); if (!drawablep || drawablep->isDead()) { continue; } } } void LLGeometryManager::addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32 &index_count) { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; //clear off any old faces mFaceList.clear(); //for each drawable for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter) { LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable(); if (!drawablep || drawablep->isDead()) { continue; } //for each face for (S32 i = 0; i < drawablep->getNumFaces(); i++) { //sum up face verts and indices drawablep->updateFaceSize(i); LLFace* facep = drawablep->getFace(i); if (facep) { if (facep->hasGeometry() && facep->getPixelArea() > FORCE_CULL_AREA && facep->getGeomCount() + vertex_count <= 65536) { vertex_count += facep->getGeomCount(); index_count += facep->getIndicesCount(); //remember face (for sorting) mFaceList.push_back(facep); } else { facep->clearVertexBuffer(); } } } } } LLHUDPartition::LLHUDPartition(LLViewerRegion* regionp) : LLBridgePartition(regionp) { mPartitionType = LLViewerRegion::PARTITION_HUD; mDrawableType = LLPipeline::RENDER_TYPE_HUD; mSlopRatio = 0.f; mLODPeriod = 1; } void LLHUDPartition::shift(const LLVector4a &offset) { //HUD objects don't shift with region crossing. That would be silly. }