diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index af296f918e1232e74688fac82fc432dbfcbe5f5f..810b2d9a1dfe06a4e98d367e6b401e94ba79660e 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -585,7 +585,7 @@
       <key>Value</key>
       <integer>2</integer>
     </map>
-    <key>AvatarBakedTextureTimeout</key>
+    <key>AvatarBakedTextureUploadTimeout</key>
     <map>
       <key>Comment</key>
       <string>Specifes the maximum time in seconds to wait before sending your baked textures for avatar appearance.  Set to 0 to disable and wait until all baked textures are at highest resolution.</string>
@@ -594,8 +594,20 @@
       <key>Type</key>
       <string>U32</string>
       <key>Value</key>
-      <integer>120</integer>
+      <integer>60</integer>
     </map>
+    <key>AvatarBakedLocalTextureUpdateTimeout</key>
+    <map>
+      <key>Comment</key>
+      <string>Specifes the maximum time in seconds to wait before updating your appearance during appearance mode.</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>U32</string>
+      <key>Value</key>
+      <integer>10</integer>
+    </map>
+
     <key>AvatarSex</key>
     <map>
       <key>Comment</key>
diff --git a/indra/newview/lltexlayer.cpp b/indra/newview/lltexlayer.cpp
index 46bd55de43b0ea18b86b106983f321aa08a8ca95..355f46e29091b08359dd9021d12679bd5a614b5b 100644
--- a/indra/newview/lltexlayer.cpp
+++ b/indra/newview/lltexlayer.cpp
@@ -119,14 +119,16 @@ LLTexLayerSetBuffer::LLTexLayerSetBuffer(LLTexLayerSet* const owner,
 										 S32 width, S32 height) :
 	// ORDER_LAST => must render these after the hints are created.
 	LLViewerDynamicTexture( width, height, 4, LLViewerDynamicTexture::ORDER_LAST, TRUE ), 
-	mNeedsUpdate(TRUE),
-	mNeedsUpload(FALSE),
 	mUploadPending(FALSE), // Not used for any logic here, just to sync sending of updates
+	mNeedsUpload(FALSE),
 	mNumLowresUploads(0),
+	mNeedsUpdate(TRUE),
+	mNumLowresUpdates(0),
 	mTexLayerSet(owner)
 {
 	LLTexLayerSetBuffer::sGLByteCount += getSize();
 	mNeedsUploadTimer.start();
+	mNeedsUpdateTimer.start();
 }
 
 LLTexLayerSetBuffer::~LLTexLayerSetBuffer()
@@ -165,8 +167,9 @@ void LLTexLayerSetBuffer::dumpTotalByteCount()
 
 void LLTexLayerSetBuffer::requestUpdate()
 {
-	conditionalRestartUploadTimer();
+	restartUpdateTimer();
 	mNeedsUpdate = TRUE;
+	mNumLowresUpdates = 0;
 	// If we're in the middle of uploading a baked texture, we don't care about it any more.
 	// When it's downloaded, ignore it.
 	mUploadID.setNull();
@@ -196,6 +199,12 @@ void LLTexLayerSetBuffer::conditionalRestartUploadTimer()
 	}
 }
 
+void LLTexLayerSetBuffer::restartUpdateTimer()
+{
+	mNeedsUpdateTimer.reset();
+	mNeedsUpdateTimer.start();
+}
+
 void LLTexLayerSetBuffer::cancelUpload()
 {
 	mNeedsUpload = FALSE;
@@ -229,25 +238,31 @@ BOOL LLTexLayerSetBuffer::needsRender()
 	llassert(mTexLayerSet->getAvatar() == gAgentAvatarp);
 	if (!isAgentAvatarValid()) return FALSE;
 
-	const BOOL upload_now = isReadyToUpload();
-	BOOL needs_update = (mNeedsUpdate || upload_now) && !gAgentAvatarp->mAppearanceAnimating;
-	if (needs_update)
+	const BOOL upload_now = mNeedsUpload && isReadyToUpload();
+	const BOOL update_now = mNeedsUpdate && isReadyToUpdate();
+
+	// Don't render if we don't want to (or aren't ready to) upload or update.
+	if (!(update_now || upload_now))
 	{
-		BOOL invalid_skirt = gAgentAvatarp->getBakedTE(mTexLayerSet) == LLVOAvatarDefines::TEX_SKIRT_BAKED && !gAgentAvatarp->isWearingWearableType(LLWearableType::WT_SKIRT);
-		if (invalid_skirt)
-		{
-			// we were trying to create a skirt texture
-			// but we're no longer wearing a skirt...
-			needs_update = FALSE;
-			cancelUpload();
-		}
-		else
-		{
-			needs_update &= mTexLayerSet->isLocalTextureDataAvailable();
-		}
+		return FALSE;
+	}
+
+	// Don't render if we're animating our appearance.
+	if (gAgentAvatarp->getIsAppearanceAnimating())
+	{
+		return FALSE;
 	}
 
-	return needs_update;
+	// Don't render if we are trying to create a shirt texture but aren't wearing a skirt.
+	if (gAgentAvatarp->getBakedTE(mTexLayerSet) == LLVOAvatarDefines::TEX_SKIRT_BAKED && 
+		!gAgentAvatarp->isWearingWearableType(LLWearableType::WT_SKIRT))
+	{
+		cancelUpload();
+		return FALSE;
+	}
+
+	// Render if we have at least minimal level of detail for each local texture.
+	return mTexLayerSet->isLocalTextureDataAvailable();
 }
 
 void LLTexLayerSetBuffer::preRender(BOOL clear_depth)
@@ -272,11 +287,12 @@ BOOL LLTexLayerSetBuffer::render()
 	gGL.setColorMask(true, true);
 
 	// do we need to upload, and do we have sufficient data to create an uploadable composite?
-	// When do we upload the texture if gAgent.mNumPendingQueries is non-zero?
-	const BOOL upload_now = isReadyToUpload();
+	// TODO: When do we upload the texture if gAgent.mNumPendingQueries is non-zero?
+	const BOOL upload_now = mNeedsUpload && isReadyToUpload();
+	const BOOL update_now = mNeedsUpdate && isReadyToUpdate();
+	
 	BOOL success = TRUE;
 
-
 	// Composite the color data
 	LLGLSUIDefault gls_ui;
 	success &= mTexLayerSet->render( mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight );
@@ -294,7 +310,7 @@ BOOL LLTexLayerSetBuffer::render()
 			if (mTexLayerSet->isVisible())
 			{
 				mTexLayerSet->getAvatar()->debugBakedTextureUpload(mTexLayerSet->getBakedTexIndex(), FALSE); // FALSE for start of upload, TRUE for finish.
-				readBackAndUpload();
+				doUpload();
 			}
 			else
 			{
@@ -305,6 +321,11 @@ BOOL LLTexLayerSetBuffer::render()
 			}
 		}
 	}
+	
+	if (update_now)
+	{
+		doUpdate();
+	}
 
 	// reset GL state
 	gGL.setColorMask(true, true);
@@ -312,7 +333,6 @@ BOOL LLTexLayerSetBuffer::render()
 
 	// we have valid texture data now
 	mGLTexturep->setGLTextureCreated(true);
-	mNeedsUpdate = FALSE;
 
 	return success;
 }
@@ -339,16 +359,16 @@ BOOL LLTexLayerSetBuffer::uploadInProgress() const
 
 BOOL LLTexLayerSetBuffer::isReadyToUpload() const
 {
-	if (!mNeedsUpload) return FALSE; // Don't need to upload if we haven't requested one.
 	if (!gAgentQueryManager.hasNoPendingQueries()) return FALSE; // Can't upload if there are pending queries.
 	if (isAgentAvatarValid() && !gAgentAvatarp->isUsingBakedTextures()) return FALSE; // Don't upload if avatar is using composites.
 
 	// If we requested an upload and have the final LOD ready, then upload.
-	const BOOL can_highest_lod = mTexLayerSet->isLocalTextureDataFinal();
-	if (can_highest_lod) return TRUE;
+	if (mTexLayerSet->isLocalTextureDataFinal()) return TRUE;
 
-	const U32 texture_timeout = gSavedSettings.getU32("AvatarBakedTextureTimeout");
-	if (texture_timeout)
+	// Upload if we've hit a timeout.  Upload is a pretty expensive process so we need to make sure
+	// we aren't doing uploads too frequently.
+	const U32 texture_timeout = gSavedSettings.getU32("AvatarBakedTextureUploadTimeout");
+	if (texture_timeout != 0)
 	{
 		// The timeout period increases exponentially between every lowres upload in order to prevent
 		// spamming the server with frequent uploads.
@@ -359,10 +379,33 @@ BOOL LLTexLayerSetBuffer::isReadyToUpload() const
 		const BOOL has_lower_lod = mTexLayerSet->isLocalTextureDataAvailable();
 		if (has_lower_lod && is_upload_textures_timeout) return TRUE; 
 	}
+
 	return FALSE;
 }
 
-BOOL LLTexLayerSetBuffer::updateImmediate()
+BOOL LLTexLayerSetBuffer::isReadyToUpdate() const
+{
+	// If we requested an update and have the final LOD ready, then update.
+	if (mTexLayerSet->isLocalTextureDataFinal()) return TRUE;
+
+	// If we haven't done an update yet, then just do one now regardless of state of textures.
+	if (mNumLowresUpdates == 0) return TRUE;
+
+	// Update if we've hit a timeout.  Unlike for uploads, we can make this timeout fairly small
+	// since render unnecessarily doesn't cost much.
+	const U32 texture_timeout = gSavedSettings.getU32("AvatarBakedLocalTextureUpdateTimeout");
+	if (texture_timeout != 0)
+	{
+		// If we hit our timeout and have textures available at even lower resolution, then update.
+		const BOOL is_update_textures_timeout = mNeedsUpdateTimer.getElapsedTimeF32() >= texture_timeout;
+		const BOOL has_lower_lod = mTexLayerSet->isLocalTextureDataAvailable();
+		if (has_lower_lod && is_update_textures_timeout) return TRUE; 
+	}
+
+	return FALSE;
+}
+
+BOOL LLTexLayerSetBuffer::requestUpdateImmediate()
 {
 	mNeedsUpdate = TRUE;
 	BOOL result = FALSE;
@@ -377,7 +420,9 @@ BOOL LLTexLayerSetBuffer::updateImmediate()
 	return result;
 }
 
-void LLTexLayerSetBuffer::readBackAndUpload()
+// Create the baked texture, send it out to the server, then wait for it to come
+// back so we can switch to using it.
+void LLTexLayerSetBuffer::doUpload()
 {
 	llinfos << "Uploading baked " << mTexLayerSet->getBodyRegionName() << llendl;
 	LLViewerStats::getInstance()->incStat(LLViewerStats::ST_TEX_BAKES);
@@ -447,6 +492,7 @@ void LLTexLayerSetBuffer::readBackAndUpload()
 				LLBakedUploadData* baked_upload_data = new LLBakedUploadData(gAgentAvatarp, 
 																			 this->mTexLayerSet, 
 																			 asset_id);
+				// upload ID is used to avoid overlaps, e.g. when the user rapidly makes two changes outside of Face Edit.
 				mUploadID = asset_id;
 
 				// Upload the image
@@ -493,9 +539,10 @@ void LLTexLayerSetBuffer::readBackAndUpload()
 					std::string lod_str = highest_lod ? "HighRes" : "LowRes";
 					LLSD args;
 					args["EXISTENCE"] = llformat("%d",(U32)mTexLayerSet->getAvatar()->debugGetExistenceTimeElapsedF32());
-					args["TIME"] = llformat("%d",(U32)mNeedsUploadTimer.getElapsedTimeF32());
+					args["TIME"] = llformat("%d",(U32)mNeedsUpdateTimer.getElapsedTimeF32());
 					args["BODYREGION"] = mTexLayerSet->getBodyRegionName();
 					args["RESOLUTION"] = lod_str;
+					args["ACTION"] = "uploaded";
 					LLNotificationsUtil::add("AvatarRezSelfBakeNotification",args);
 					llinfos << "Uploading [ name: " << mTexLayerSet->getBodyRegionName() << " res:" << lod_str << " time:" << (U32)mNeedsUploadTimer.getElapsedTimeF32() << " ]" << llendl;
 				}
@@ -520,6 +567,37 @@ void LLTexLayerSetBuffer::readBackAndUpload()
 	delete [] baked_color_data;
 }
 
+// Mostly bookkeeping; don't need to actually "do" anything since
+// render() will actually do the update.
+void LLTexLayerSetBuffer::doUpdate()
+{
+	const BOOL highest_lod = mTexLayerSet->isLocalTextureDataFinal();
+	if (highest_lod)
+	{
+		mNeedsUpdate = FALSE;
+	}
+	else
+	{
+		mNumLowresUpdates++;
+	}
+
+	restartUpdateTimer();
+	
+	// Print out notification that we uploaded this texture.
+	if (gSavedSettings.getBOOL("DebugAvatarRezTime"))
+	{
+		const BOOL highest_lod = mTexLayerSet->isLocalTextureDataFinal();
+		std::string lod_str = highest_lod ? "HighRes" : "LowRes";
+		LLSD args;
+		args["EXISTENCE"] = llformat("%d",(U32)mTexLayerSet->getAvatar()->debugGetExistenceTimeElapsedF32());
+		args["TIME"] = llformat("%d",(U32)mNeedsUploadTimer.getElapsedTimeF32());
+		args["BODYREGION"] = mTexLayerSet->getBodyRegionName();
+		args["RESOLUTION"] = lod_str;
+		args["ACTION"] = "locally updated";
+		LLNotificationsUtil::add("AvatarRezSelfBakeNotification",args);
+		llinfos << "Locally updating [ name: " << mTexLayerSet->getBodyRegionName() << " res:" << lod_str << " time:" << (U32)mNeedsUpdateTimer.getElapsedTimeF32() << " ]" << llendl;
+	}
+}
 
 // static
 void LLTexLayerSetBuffer::onTextureUploadComplete(const LLUUID& uuid,
@@ -931,7 +1009,7 @@ void LLTexLayerSet::setUpdatesEnabled( BOOL b )
 void LLTexLayerSet::updateComposite()
 {
 	createComposite();
-	mComposite->updateImmediate();
+	mComposite->requestUpdateImmediate();
 }
 
 LLTexLayerSetBuffer* LLTexLayerSet::getComposite()
diff --git a/indra/newview/lltexlayer.h b/indra/newview/lltexlayer.h
index cb2e1faaa68ff6311d260083e05373d67ec6c7d1..745cd88c47d587b9a5fea952bc6e45c7f8acfa0a 100644
--- a/indra/newview/lltexlayer.h
+++ b/indra/newview/lltexlayer.h
@@ -270,47 +270,69 @@ class LLTexLayerSetBuffer : public LLViewerDynamicTexture
 public:
 	LLTexLayerSetBuffer(LLTexLayerSet* const owner, S32 width, S32 height);
 	virtual ~LLTexLayerSetBuffer();
-	/*virtual*/ S8          getType() const;
-	virtual void			preRender(BOOL clear_depth);
-	virtual void			postRender(BOOL success);
-	virtual BOOL			render();
-	BOOL					updateImmediate();
 
+public:
+	/*virtual*/ S8          getType() const;
 	BOOL					isInitialized(void) const;
-	BOOL					uploadPending() const; // We are expecting a new texture to be uploaded at some point
-	BOOL					uploadNeeded() const; // We need to upload a new texture
-	BOOL					uploadInProgress() const; // We have started uploading a new texture and are awaiting the result
+	static void				dumpTotalByteCount();
+	const std::string		dumpTextureInfo() const;
+	virtual void 			restoreGLTexture();
+	virtual void 			destroyGLTexture();
+protected:
+	void					pushProjection() const;
+	void					popProjection() const;
+private:
+	LLTexLayerSet* const    mTexLayerSet;
+	static S32				sGLByteCount;
 
+	//--------------------------------------------------------------------
+	// Render
+	//--------------------------------------------------------------------
+public:
 	/*virtual*/ BOOL		needsRender();
-	void					requestUpdate();
+protected:
+	BOOL					render(S32 x, S32 y, S32 width, S32 height);
+	virtual void			preRender(BOOL clear_depth);
+	virtual void			postRender(BOOL success);
+	virtual BOOL			render();	
+	
+	//--------------------------------------------------------------------
+	// Uploads
+	//--------------------------------------------------------------------
+public:
 	void					requestUpload();
 	void					cancelUpload();
-	BOOL					render(S32 x, S32 y, S32 width, S32 height);
-	void					readBackAndUpload();
+	BOOL					uploadNeeded() const; 			// We need to upload a new texture
+	BOOL					uploadInProgress() const; 		// We have started uploading a new texture and are awaiting the result
+	BOOL					uploadPending() const; 			// We are expecting a new texture to be uploaded at some point
 	static void				onTextureUploadComplete(const LLUUID& uuid,
 													void* userdata,
 													S32 result, LLExtStat ext_status);
-	static void				dumpTotalByteCount();
-	const std::string		dumpTextureInfo() const;
-	virtual void 			restoreGLTexture();
-	virtual void 			destroyGLTexture();
-
-
 protected:
-	void					pushProjection() const;
-	void					popProjection() const;
 	BOOL					isReadyToUpload() const;
+	void					doUpload(); 					// Does a read back and upload.
 	void					conditionalRestartUploadTimer();
-	
 private:
-	LLTexLayerSet* const    mTexLayerSet;
-	BOOL					mNeedsUpdate; // whether we need to update our baked textures
-	BOOL					mNeedsUpload; // whether we need to send our baked textures to the server
-	U32						mNumLowresUploads; // number of times we've sent a lowres version of our baked textures to the server
-	BOOL					mUploadPending; // whether we have received back the new baked textures
-	LLUUID					mUploadID; // the current upload process (null if none).  Used to avoid overlaps, e.g. when the user rapidly makes two changes outside of Face Edit.
-	static S32				sGLByteCount;
-	LLFrameTimer    		mNeedsUploadTimer; // Tracks time since upload was requested
+	BOOL					mNeedsUpload; 					// Whether we need to send our baked textures to the server
+	U32						mNumLowresUploads; 				// Number of times we've sent a lowres version of our baked textures to the server
+	BOOL					mUploadPending; 				// Whether we have received back the new baked textures
+	LLUUID					mUploadID; 						// The current upload process (null if none).
+	LLFrameTimer    		mNeedsUploadTimer; 				// Tracks time since upload was requested and performed.
+
+	//--------------------------------------------------------------------
+	// Updates
+	//--------------------------------------------------------------------
+public:
+	void					requestUpdate();
+	BOOL					requestUpdateImmediate();
+protected:
+	BOOL					isReadyToUpdate() const;
+	void					doUpdate();
+	void					restartUpdateTimer();
+private:
+	BOOL					mNeedsUpdate; 					// Whether we need to locally update our baked textures
+	U32						mNumLowresUpdates; 				// Number of times we've locally updated with lowres version of our baked textures
+	LLFrameTimer    		mNeedsUpdateTimer; 				// Tracks time since update was requested and performed.
 };
 
 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp
index 8ea4dbeb04d66d3c1bc76d130223128061ad3109..b588ff91d1503a65bab2d03bd2e043acd817e71a 100644
--- a/indra/newview/lltextureview.cpp
+++ b/indra/newview/lltextureview.cpp
@@ -450,14 +450,14 @@ void LLAvatarTexBar::draw()
 												 text_color, LLFontGL::LEFT, LLFontGL::TOP); //, LLFontGL::BOLD, LLFontGL::DROP_SHADOW_SOFT);
 		line_num++;
 	}
-	const U32 texture_timeout = gSavedSettings.getU32("AvatarBakedTextureTimeout");
+	const U32 texture_timeout = gSavedSettings.getU32("AvatarBakedTextureUploadTimeout");
 	const U32 override_tex_discard_level = gSavedSettings.getU32("TextureDiscardLevel");
 	
 	LLColor4 header_color(1.f, 1.f, 1.f, 0.9f);
 
 	const std::string texture_timeout_str = texture_timeout ? llformat("%d",texture_timeout) : "Disabled";
 	const std::string override_tex_discard_level_str = override_tex_discard_level ? llformat("%d",override_tex_discard_level) : "Disabled";
-	std::string header_text = llformat("[ Timeout('AvatarBakedTextureTimeout'):%s ] [ LOD_Override('TextureDiscardLevel'):%s ]", texture_timeout_str.c_str(), override_tex_discard_level_str.c_str());
+	std::string header_text = llformat("[ Timeout('AvatarBakedTextureUploadTimeout'):%s ] [ LOD_Override('TextureDiscardLevel'):%s ]", texture_timeout_str.c_str(), override_tex_discard_level_str.c_str());
 	LLFontGL::getFontMonospace()->renderUTF8(header_text, 0, l_offset, v_offset + line_height*line_num,
 											 header_color, LLFontGL::LEFT, LLFontGL::TOP); //, LLFontGL::BOLD, LLFontGL::DROP_SHADOW_SOFT);
 	line_num++;
diff --git a/indra/newview/lltoolmorph.cpp b/indra/newview/lltoolmorph.cpp
index fa21b1a86684d72bca44407121793788526a6ecc..81559429b0fd545043e6ada915b06ac10265330b 100644
--- a/indra/newview/lltoolmorph.cpp
+++ b/indra/newview/lltoolmorph.cpp
@@ -147,7 +147,7 @@ void LLVisualParamHint::requestHintUpdates( LLVisualParamHint* exception1, LLVis
 
 BOOL LLVisualParamHint::needsRender()
 {
-	return mNeedsUpdate && mDelayFrames-- <= 0 && !gAgentAvatarp->mAppearanceAnimating && mAllowsUpdates;
+	return mNeedsUpdate && mDelayFrames-- <= 0 && !gAgentAvatarp->getIsAppearanceAnimating() && mAllowsUpdates;
 }
 
 void LLVisualParamHint::preRender(BOOL clear_depth)
diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h
index 49b9fe1536ebf632a55d15c450deb8ef76e5735b..22fc595ea299f4766a7632ca8341af8867e8855f 100644
--- a/indra/newview/llvoavatar.h
+++ b/indra/newview/llvoavatar.h
@@ -611,8 +611,9 @@ class LLVOAvatar :
 	// Appearance morphing
 	//--------------------------------------------------------------------
 public:
-	BOOL			mAppearanceAnimating;
+	BOOL			getIsAppearanceAnimating() const { return mAppearanceAnimating; }
 private:
+	BOOL			mAppearanceAnimating;
 	LLFrameTimer	mAppearanceMorphTimer;
 	F32				mLastAppearanceBlendTime;
 
@@ -622,7 +623,7 @@ class LLVOAvatar :
 public:
 	void			setClothesColor(LLVOAvatarDefines::ETextureIndex te, const LLColor4& new_color, BOOL upload_bake);
 	LLColor4		getClothesColor(LLVOAvatarDefines::ETextureIndex te);
-	static BOOL			teToColorParams(LLVOAvatarDefines::ETextureIndex te, U32 *param_name);
+	static BOOL		teToColorParams(LLVOAvatarDefines::ETextureIndex te, U32 *param_name);
 
 	//--------------------------------------------------------------------
 	// Global colors
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index 04bdb4302c2da0e022d66e75bcc722b72fe5f5f3..4c241562e6b011f703ccdd7590d2b85c024ddfce 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -6343,7 +6343,7 @@ Avatar '[NAME]' left as fully loaded.
    name="AvatarRezSelfBakeNotification"
    type="notifytip">
 ( [EXISTENCE] seconds alive )
-You uploaded a [RESOLUTION] baked texture for '[BODYREGION]' after [TIME] seconds.
+You [ACTION] a [RESOLUTION] baked texture for '[BODYREGION]' after [TIME] seconds.
   </notification>
 
   <notification