diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index a110c018ec0a77c59f67cd7ec152e4ce72b646ac..0835f9751e278ba16bd51d4eb2dc5cd338e412cb 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -11,13 +11,15 @@ jobs:
   build:
     strategy:
       matrix:
-        runner: [windows-large]
+        runner: [windows-large, macos-12-xl]
         configuration: [ReleaseOS]
         addrsize: [64]
         include:
           - runner: windows-large
             configuration: ReleaseOS
             addrsize: 32
+          - runner: macos-12-xl
+            developer_dir: "/Applications/Xcode_14.0.1.app/Contents/Developer"
     runs-on: ${{ matrix.runner }}
     env:
       AUTOBUILD_CONFIGURATION: ${{ matrix.configuration }}
@@ -25,8 +27,10 @@ jobs:
       AUTOBUILD_INSTALLABLE_CACHE: ${{ github.workspace }}/.autobuild-installables
       AUTOBUILD_VARIABLES_FILE: ${{ github.workspace }}/.build-variables/variables
       AUTOBUILD_VSVER: "170" # vs2k22
+      DEVELOPER_DIR: ${{ matrix.developer_dir }}
       LOGFAIL: debug # Show details when tests fail
       GIT_REF: ${{ github.head_ref || github.ref }}
+      LL_SKIP_REQUIRE_SYSROOT: 1
     steps:
       - name: Checkout code
         uses: actions/checkout@v3
diff --git a/autobuild.xml b/autobuild.xml
index 53d5ee795f94a49c5c2222f0b1f5e872b1a18df0..2940c4492deb4f8b0f5adf73eedca233a0d3c59e 100644
--- a/autobuild.xml
+++ b/autobuild.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" ?>
-<llsd><map>
+<llsd>
+<map>
     <key>installables</key>
     <map>
       <key>SDL</key>
@@ -183,9 +184,9 @@
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>02b569ac2bd71f201e3dd86ade7b3eeb</string>
+              <string>27a77bfba1fa56fd59f4f26605baac35</string>
               <key>url</key>
-              <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/113876/983684/bugsplat-1.0.7.579696-darwin64-579696.tar.bz2</string>
+              <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/113802/983479/bugsplat-1.0.7.579669-darwin64-579669.tar.bz2</string>
             </map>
             <key>name</key>
             <string>darwin64</string>
@@ -195,9 +196,9 @@
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>5b32c47ae8e8cf0d4106f08e8db18044</string>
+              <string>c5abb9545039bd9113c8bf11d58f4501</string>
               <key>url</key>
-              <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/113878/983697/bugsplat-4.0.3.0.579696-windows-579696.tar.bz2</string>
+              <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/113803/983490/bugsplat-4.0.3.0.579669-windows-579669.tar.bz2</string>
             </map>
             <key>name</key>
             <string>windows</string>
@@ -207,16 +208,16 @@
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>79c005fd8a660f8551b3c9ede64fa4ef</string>
+              <string>45e9b9215ce653171b572f44ee7bbf0c</string>
               <key>url</key>
-              <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/113879/983696/bugsplat-4.0.3.0.579696-windows64-579696.tar.bz2</string>
+              <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/113804/983491/bugsplat-4.0.3.0.579669-windows64-579669.tar.bz2</string>
             </map>
             <key>name</key>
             <string>windows64</string>
           </map>
         </map>
         <key>version</key>
-        <string>4.0.3.0.579696</string>
+        <string>4.0.3.0.579669</string>
       </map>
       <key>colladadom</key>
       <map>
@@ -773,9 +774,11 @@
             <key>archive</key>
             <map>
               <key>hash</key>
-              <string>4f8dc85863fec36e8d872c31f4abcd05</string>
+              <string>7cc58b3acb230a7e65ea5f0ff800be393eb4aa1b</string>
+              <key>hash_algorithm</key>
+              <string>sha1</string>
               <key>url</key>
-              <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/101480/892402/glext-68-common-572829.tar.bz2</string>
+              <string>https://github.com/secondlife/3p-glext/releases/download/v69/glext-68-common-685b36e.tar.zst</string>
             </map>
             <key>name</key>
             <string>common</string>
@@ -3180,6 +3183,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
                   <string>RelWithDebInfo</string>
                   <string>-project</string>
                   <string>SecondLife.xcodeproj</string>
+                  <string>-parallelizeTargets</string>
                 </array>
               </map>
               <key>configure</key>
@@ -3211,6 +3215,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
                   <string>RelWithDebInfo</string>
                   <string>-project</string>
                   <string>SecondLife.xcodeproj</string>
+                  <string>-parallelizeTargets</string>
                 </array>
               </map>
               <key>configure</key>
@@ -3236,6 +3241,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
                   <string>Release</string>
                   <string>-project</string>
                   <string>SecondLife.xcodeproj</string>
+                  <string>-parallelizeTargets</string>
                 </array>
               </map>
               <key>configure</key>
@@ -3265,6 +3271,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
                   <string>Release</string>
                   <string>-project</string>
                   <string>SecondLife.xcodeproj</string>
+                  <string>-parallelizeTargets</string>
                 </array>
               </map>
               <key>configure</key>
@@ -3511,4 +3518,5 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
     <string>autobuild</string>
     <key>version</key>
     <string>1.3</string>
-  </map></llsd>
+  </map>
+</llsd>
diff --git a/doc/contributions.txt b/doc/contributions.txt
index 6e08cba599a4c2af8cd0f228622634e072619fe3..c56c4c13038de0270aaead566f3aad6c4a07bd70 100755
--- a/doc/contributions.txt
+++ b/doc/contributions.txt
@@ -283,8 +283,11 @@ Beq Janus
 	SL-11300
 	SL-15709
 	SL-16021
+	SL-18202
+	SL-18586
 	SL-18592
 	SL-18637
+	SL-19317
 Beth Walcher
 Bezilon Kasei
 Biancaluce Robbiani
@@ -1401,6 +1404,7 @@ Sovereign Engineer
     SL-18497
     SL-18525
     SL-18534
+    SL-19690
 SpacedOut Frye
 	VWR-34
 	VWR-45
diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake
index 1ee2a621f2e8d81f3c63fa1f3041de6643182689..d43cc3070690f5798dca64a714cf8474489998c1 100644
--- a/indra/cmake/Copy3rdPartyLibs.cmake
+++ b/indra/cmake/Copy3rdPartyLibs.cmake
@@ -220,6 +220,7 @@ elseif(LINUX)
                  libfreetype.so.6.6.2
                  libfreetype.so.6
                  libhunspell-1.3.so.0.0.0
+                 libopenjp2.so
                  libuuid.so.16
                  libuuid.so.16.0.22
                  libfontconfig.so.1.8.0
@@ -295,6 +296,6 @@ if(DARWIN)
     # that end up in any of the above SHARED_LIB_STAGING_DIR_MUMBLE
     # directories.
     add_custom_command( TARGET stage_third_party_libs POST_BUILD
-            COMMAND cmake -E create_symlink ${SHARED_LIB_STAGING_DIR} ${CMAKE_BINARY_DIR}/sharedlibs/Resources
+            COMMAND ${CMAKE_COMMAND} -E create_symlink ${SHARED_LIB_STAGING_DIR} ${CMAKE_BINARY_DIR}/sharedlibs/Resources
             )
 endif()
diff --git a/indra/cmake/FMODSTUDIO.cmake b/indra/cmake/FMODSTUDIO.cmake
index c5b21ac4e5acd23d951bb31379b2a18675fce063..9a1cdff6cb461d2bfd55701b8c9b08e8983c0ccf 100644
--- a/indra/cmake/FMODSTUDIO.cmake
+++ b/indra/cmake/FMODSTUDIO.cmake
@@ -2,7 +2,7 @@
 
 include_guard()
 
-# FMODSTUDIO can be set when launching the make using the argument -DFMODSTUDIO:BOOL=ON
+# FMODSTUDIO can be set when launching the make using the argument -DUSE_FMODSTUDIO:BOOL=ON
 # When building using proprietary binaries though (i.e. having access to LL private servers),
 # we always build with FMODSTUDIO.
 if (INSTALL_PROPRIETARY)
diff --git a/indra/cmake/LLAddBuildTest.cmake b/indra/cmake/LLAddBuildTest.cmake
index bf569e5d992dcbef8bd8a5385f24beaa9a4ec126..2172b56da2f8f4a88f6b521fec5ac8cdd957ec21 100644
--- a/indra/cmake/LLAddBuildTest.cmake
+++ b/indra/cmake/LLAddBuildTest.cmake
@@ -126,6 +126,13 @@ MACRO(LL_ADD_PROJECT_UNIT_TESTS project sources)
       message("LL_ADD_PROJECT_UNIT_TESTS ${name}_test_additional_CFLAGS ${${name}_test_additional_CFLAGS}")
     endif()
 
+    if (DARWIN)
+      # test binaries always need to be signed for local development
+      set_target_properties(PROJECT_${project}_TEST_${name}
+          PROPERTIES
+              XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-")
+    endif ()
+
     #
     # Setup test targets
     #
@@ -221,6 +228,13 @@ FUNCTION(LL_ADD_INTEGRATION_TEST
             )
   endif ()
 
+  if (DARWIN)
+    # test binaries always need to be signed for local development
+    set_target_properties(INTEGRATION_TEST_${testname}
+            PROPERTIES
+            XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-")
+  endif ()
+
   # Add link deps to the executable
   if(TEST_DEBUG)
     message(STATUS "TARGET_LINK_LIBRARIES(INTEGRATION_TEST_${testname} ${libraries})")
diff --git a/indra/cmake/Variables.cmake b/indra/cmake/Variables.cmake
index 653db2069ab1e4ad0d519b6104ded9314792ad52..469fb3d3300285ab082c82c918601de78cdf0444 100644
--- a/indra/cmake/Variables.cmake
+++ b/indra/cmake/Variables.cmake
@@ -33,7 +33,7 @@ set(LIBS_OPEN_PREFIX)
 set(SCRIPTS_PREFIX ../scripts)
 set(VIEWER_PREFIX)
 set(INTEGRATION_TESTS_PREFIX)
-set(LL_TESTS ON CACHE BOOL "Build and run unit and integration tests (disable for build timing runs to reduce variation")
+set(LL_TESTS OFF CACHE BOOL "Build and run unit and integration tests (disable for build timing runs to reduce variation")
 set(INCREMENTAL_LINK OFF CACHE BOOL "Use incremental linking on win32 builds (enable for faster links on some machines)")
 set(ENABLE_MEDIA_PLUGINS ON CACHE BOOL "Turn off building media plugins if they are imported by third-party library mechanism")
 set(VIEWER_SYMBOL_FILE "" CACHE STRING "Name of tarball into which to place symbol files")
@@ -173,13 +173,17 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
   set(CMAKE_XCODE_ATTRIBUTE_GCC_OPTIMIZATION_LEVEL "${CMAKE_MATCH_1}")
   message(STATUS "CMAKE_XCODE_ATTRIBUTE_GCC_OPTIMIZATION_LEVEL = '${CMAKE_XCODE_ATTRIBUTE_GCC_OPTIMIZATION_LEVEL}'")
 
-  string(REGEX MATCHALL "[^ ]+" LL_BUILD_LIST "$ENV{LL_BUILD}")
-  list(FIND LL_BUILD_LIST "-iwithsysroot" sysroot_idx)
-  if ("${sysroot_idx}" LESS 0)
-    message(FATAL_ERROR "Environment variable LL_BUILD must contain '-iwithsysroot'")
-  endif ()
-  math(EXPR sysroot_idx "${sysroot_idx} + 1")
-  list(GET LL_BUILD_LIST "${sysroot_idx}" CMAKE_OSX_SYSROOT)
+  # allow disabling this check by setting LL_SKIP_REQUIRE_SYSROOT either ON as cmake cache var or non-empty as environment var
+  set(LL_SKIP_REQUIRE_SYSROOT OFF CACHE BOOL "Skip requirement to set toolchain sysroot ahead of time. Not skipped by default for consistency, but skipping can be useful for selecting alternative xcode versions side by side")
+  if("$ENV{LL_SKIP_REQUIRE_SYSROOT}" STREQUAL "" AND NOT ${LL_SKIP_REQUIRE_SYSROOT})
+    string(REGEX MATCHALL "[^ ]+" LL_BUILD_LIST "$ENV{LL_BUILD}")
+    list(FIND LL_BUILD_LIST "-iwithsysroot" sysroot_idx)
+    if ("${sysroot_idx}" LESS 0)
+      message(FATAL_ERROR "Environment variable LL_BUILD must contain '-iwithsysroot'")
+    endif ()
+    math(EXPR sysroot_idx "${sysroot_idx} + 1")
+    list(GET LL_BUILD_LIST "${sysroot_idx}" CMAKE_OSX_SYSROOT)
+  endif()
   message(STATUS "CMAKE_OSX_SYSROOT = '${CMAKE_OSX_SYSROOT}'")
 
   set(CMAKE_XCODE_ATTRIBUTE_GCC_VERSION "com.apple.compilers.llvm.clang.1_0")
diff --git a/indra/edit-me-to-trigger-new-build.txt b/indra/edit-me-to-trigger-new-build.txt
index eab7c17b71d5a7bf2c6444aa88a346d5e0c74d0d..8b137891791fe96927ad78e64b0aad7bded08bdc 100644
--- a/indra/edit-me-to-trigger-new-build.txt
+++ b/indra/edit-me-to-trigger-new-build.txt
@@ -1,4 +1 @@
-euclid 5/29/2020
-euclid 7/23/2020
-euclid 4/29/2021
-euclid 10/5/2021 DRTVWR-546
+
diff --git a/indra/integration_tests/llui_libtest/CMakeLists.txt b/indra/integration_tests/llui_libtest/CMakeLists.txt
index d603e57aab87fab8bd8203bda2dbcd8df5750cd6..a6ce657f4f3458e844edf12e186113bfbf225878 100644
--- a/indra/integration_tests/llui_libtest/CMakeLists.txt
+++ b/indra/integration_tests/llui_libtest/CMakeLists.txt
@@ -58,19 +58,12 @@ if (WINDOWS)
     # Copy over OpenJPEG.dll
     # *NOTE: On Windows with VS2005, only the first comment prints
     set(OPENJPEG_RELEASE
-        "${ARCH_PREBUILT_DIRS_RELEASE}/openjp2.dll")
+            "${ARCH_PREBUILT_DIRS_RELEASE}/openjpeg.dll")
     add_custom_command( TARGET llui_libtest POST_BUILD
             COMMAND ${CMAKE_COMMAND} -E copy_if_different
             ${OPENJPEG_RELEASE} ${CMAKE_CURRENT_BINARY_DIR}
-        COMMENT "Copying OpenJPEG DLLs to binary directory"
-        )
-    set(OPENJPEG_DEBUG
-        "${ARCH_PREBUILT_DIRS_DEBUG}/openjp2.dll")
-    add_custom_command( TARGET llui_libtest POST_BUILD
-        COMMAND ${CMAKE_COMMAND} -E copy_if_different 
-            ${OPENJPEG_DEBUG} ${CMAKE_CURRENT_BINARY_DIR}
-        )
-  
+            COMMENT "Copying OpenJPEG DLLs to binary directory"
+            )
 endif (WINDOWS)
 
 # Ensure people working on the viewer don't break this library
diff --git a/indra/llaudio/llaudiodecodemgr.cpp b/indra/llaudio/llaudiodecodemgr.cpp
index 38a6b41afeba8e314db1a95fe4916550fea99942..190c5290cb4782404d6af12537c702d9dbd3ad19 100755
--- a/indra/llaudio/llaudiodecodemgr.cpp
+++ b/indra/llaudio/llaudiodecodemgr.cpp
@@ -607,40 +607,37 @@ void LLAudioDecodeMgr::Impl::startMoreDecodes()
 
         // Kick off a decode
         mDecodes[decode_id] = LLPointer<LLVorbisDecodeState>(NULL);
-        try
-        {
-            main_queue->postTo(
-                general_queue,
-                [decode_id]() // Work done on general queue
-                {
-                    LLPointer<LLVorbisDecodeState> decode_state = beginDecodingAndWritingAudio(decode_id);
-
-                    if (!decode_state)
-                    {
-                        // Audio decode has errored
-                        return decode_state;
-                    }
+        bool posted = main_queue->postTo(
+            general_queue,
+            [decode_id]() // Work done on general queue
+            {
+                LLPointer<LLVorbisDecodeState> decode_state = beginDecodingAndWritingAudio(decode_id);
 
-                    // Disk write of decoded audio is now in progress off-thread
+                if (!decode_state)
+                {
+                    // Audio decode has errored
                     return decode_state;
-                },
-                [decode_id, this](LLPointer<LLVorbisDecodeState> decode_state) // Callback to main thread
-                mutable {
-                    if (!gAudiop)
-                    {
-                        // There is no LLAudioEngine anymore. This might happen if
-                        // an audio decode is enqueued just before shutdown.
-                        return;
-                    }
-
-                    // At this point, we can be certain that the pointer to "this"
-                    // is valid because the lifetime of "this" is dependent upon
-                    // the lifetime of gAudiop.
-
-                    enqueueFinishAudio(decode_id, decode_state);
-                });
-        }
-        catch (const LLThreadSafeQueueInterrupt&)
+                }
+
+                // Disk write of decoded audio is now in progress off-thread
+                return decode_state;
+            },
+            [decode_id, this](LLPointer<LLVorbisDecodeState> decode_state) // Callback to main thread
+            mutable {
+                if (!gAudiop)
+                {
+                    // There is no LLAudioEngine anymore. This might happen if
+                    // an audio decode is enqueued just before shutdown.
+                    return;
+                }
+
+                // At this point, we can be certain that the pointer to "this"
+                // is valid because the lifetime of "this" is dependent upon
+                // the lifetime of gAudiop.
+
+                enqueueFinishAudio(decode_id, decode_state);
+            });
+        if (! posted)
         {
             // Shutdown
             // Consider making processQueue() do a cleanup instead
diff --git a/indra/llcommon/llcallstack.h b/indra/llcommon/llcallstack.h
index 5acf04a49fa16c4355f112fc1e0047f5d5222009..d5a2b7b1572be966dbd02d4056f597007984ed47 100644
--- a/indra/llcommon/llcallstack.h
+++ b/indra/llcommon/llcallstack.h
@@ -79,9 +79,9 @@ struct LLContextStatus
 
 LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLContextStatus& context_status);
 
-#define dumpStack(tag) \
-    if (debugLoggingEnabled(tag)) \
-    { \
-        LLCallStack cs; \
-        LL_DEBUGS(tag) << "STACK:\n" << "====================\n" << cs << "====================" << LL_ENDL; \
-    }
+#define dumpStack(tag)                          \
+    LL_DEBUGS(tag) << "STACK:\n"                \
+                   << "====================\n"  \
+                   << LLCallStack()             \
+                   << "===================="    \
+                   << LL_ENDL;
diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp
index 56fb7c21ca46c51a2e7bc1e562186aff1de049ed..5aa8558878ee93bcd0d785566ee806622f8978f3 100644
--- a/indra/llcommon/llerror.cpp
+++ b/indra/llcommon/llerror.cpp
@@ -1609,19 +1609,5 @@ namespace LLError
     }
 }
 
-bool debugLoggingEnabled(const std::string& tag)
-{
-    LLMutexTrylock lock(getMutex<LOG_MUTEX>(), 5);
-    if (!lock.isLocked())
-    {
-        return false;
-    }
-
-    SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
-    LLError::ELevel level = LLError::LEVEL_DEBUG;
-    bool res = checkLevelMap(s->mTagLevelMap, tag, level);
-    return res;
-}
-
 
 
diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h
index b7dec3cb7fb08469b03f327c7e619fdd18a66b5d..08eb323c4a7042704e09ed983469c31155d0521c 100644
--- a/indra/llcommon/llerror.h
+++ b/indra/llcommon/llerror.h
@@ -467,7 +467,29 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG;
 		LLError::CallSite& _site(_sites[which]);                        \
 		lllog_test_()
 
-// Check at run-time whether logging is enabled, without generating output
+/*
+// Check at run-time whether logging is enabled, without generating output.
+Resist the temptation to add a function like this because it incurs the
+expense of locking and map-searching every time control reaches it.
 bool debugLoggingEnabled(const std::string& tag);
 
+Instead of:
+
+if debugLoggingEnabled("SomeTag")
+{
+    // ... presumably expensive operation ...
+    LL_DEBUGS("SomeTag") << ... << LL_ENDL;
+}
+
+Use this:
+
+LL_DEBUGS("SomeTag");
+// ... presumably expensive operation ...
+LL_CONT << ...;
+LL_ENDL;
+
+LL_DEBUGS("SomeTag") performs the locking and map-searching ONCE, then caches
+the result in a static variable.
+*/ 
+
 #endif // LL_LLERROR_H
diff --git a/indra/llcommon/llmutex.h b/indra/llcommon/llmutex.h
index 838d7d34c069271a4a81a13f4967e439e6eaaf93..0d70da6178c1a80ef8ad07976e14845c6c886cec 100644
--- a/indra/llcommon/llmutex.h
+++ b/indra/llcommon/llmutex.h
@@ -36,7 +36,8 @@
 
 //============================================================================
 
-#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO)
+//#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO)
+#define MUTEX_DEBUG 0 //disable mutex debugging as it's interfering with profiles
 
 #if MUTEX_DEBUG
 #include <map>
@@ -61,7 +62,7 @@ class LL_COMMON_API LLMutex
 	mutable LLThread::id_t	mLockingThread;
 	
 #if MUTEX_DEBUG
-	std::map<LLThread::id_t, BOOL> mIsLocked;
+	std::unordered_map<LLThread::id_t, BOOL> mIsLocked;
 #endif
 };
 
diff --git a/indra/llcommon/llqueuedthread.cpp b/indra/llcommon/llqueuedthread.cpp
index 9b1de2e9a598976e608231473c635fe1568c889f..7da7c1e026e09af97ce765a7112fe55f34f86f11 100644
--- a/indra/llcommon/llqueuedthread.cpp
+++ b/indra/llcommon/llqueuedthread.cpp
@@ -146,7 +146,7 @@ S32 LLQueuedThread::updateQueue(F32 max_time_ms)
         // schedule a call to threadedUpdate for every call to updateQueue
         if (!isQuitting())
         {
-            mRequestQueue.postIfOpen([=]()
+            mRequestQueue.post([=]()
                 {
                     LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qt - update");
                     mIdleThread = FALSE;
diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h
index 1321615805bfec449bedcd91b464962a67ce1fef..372278c51aed4c0c88f1ccc64881e98dc29a7857 100644
--- a/indra/llcommon/llsdutil.h
+++ b/indra/llcommon/llsdutil.h
@@ -191,75 +191,6 @@ LLSD& drill_ref(  LLSD& blob, const LLSD& path);
 
 }
 
-/*****************************************************************************
-*   LLSDArray
-*****************************************************************************/
-/**
- * Construct an LLSD::Array inline, with implicit conversion to LLSD. Usage:
- *
- * @code
- * void somefunc(const LLSD&);
- * ...
- * somefunc(LLSDArray("text")(17)(3.14));
- * @endcode
- *
- * For completeness, LLSDArray() with no args constructs an empty array, so
- * <tt>LLSDArray()("text")(17)(3.14)</tt> produces an array equivalent to the
- * above. But for most purposes, LLSD() is already equivalent to an empty
- * array, and if you explicitly want an empty isArray(), there's
- * LLSD::emptyArray(). However, supporting a no-args LLSDArray() constructor
- * follows the principle of least astonishment.
- */
-class LLSDArray
-{
-public:
-    LLSDArray():
-        _data(LLSD::emptyArray())
-    {}
-
-    /**
-     * Need an explicit copy constructor. Consider the following:
-     *
-     * @code
-     * LLSD array_of_arrays(LLSDArray(LLSDArray(17)(34))
-     *                               (LLSDArray("x")("y")));
-     * @endcode
-     *
-     * The coder intends to construct [[17, 34], ["x", "y"]].
-     *
-     * With the compiler's implicit copy constructor, s/he gets instead
-     * [17, 34, ["x", "y"]].
-     *
-     * The expression LLSDArray(17)(34) constructs an LLSDArray with those two
-     * values. The reader assumes it should be converted to LLSD, as we always
-     * want with LLSDArray, before passing it to the @em outer LLSDArray
-     * constructor! This copy constructor makes that happen.
-     */
-    LLSDArray(const LLSDArray& inner):
-        _data(LLSD::emptyArray())
-    {
-        _data.append(inner);
-    }
-
-    LLSDArray(const LLSD& value):
-        _data(LLSD::emptyArray())
-    {
-        _data.append(value);
-    }
-
-    LLSDArray& operator()(const LLSD& value)
-    {
-        _data.append(value);
-        return *this;
-    }
-
-    operator LLSD() const { return _data; }
-    LLSD get() const { return _data; }
-
-private:
-    LLSD _data;
-};
-
 namespace llsd
 {
 
diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp
index 9da1ecfd67a71fc87cf889f118c986e6b7a79ffe..991ec4819f045fe4320fabb5a7a51fcf3cc16db1 100644
--- a/indra/llcommon/tests/lleventdispatcher_test.cpp
+++ b/indra/llcommon/tests/lleventdispatcher_test.cpp
@@ -345,7 +345,7 @@ namespace tut
         lleventdispatcher_data():
             work("test dispatcher", "op"),
             // map {d=double, array=[3 elements]}
-            required(LLSDMap("d", LLSD::Real(0))("array", LLSDArray(LLSD())(LLSD())(LLSD()))),
+            required(LLSDMap("d", LLSD::Real(0))("array", llsd::array(LLSD(), LLSD(), LLSD()))),
             // first several params are required, last couple optional
             partial_offset(3)
         {
@@ -434,8 +434,8 @@ namespace tut
 
             // freena(), methodna(), cmethodna(), smethodna() all take same param list.
             // Same for freenb() et al.
-            params = LLSDMap("a", LLSDArray("b")("i")("f")("d")("cp"))
-                            ("b", LLSDArray("s")("uuid")("date")("uri")("bin"));
+            params = LLSDMap("a", llsd::array("b", "i", "f", "d", "cp"))
+                            ("b", llsd::array("s", "uuid", "date", "uri", "bin"));
             debug("params:\n",
                   params, "\n"
                   "params[\"a\"]:\n",
@@ -452,12 +452,12 @@ namespace tut
             // LLDate values are, as long as they're different from the
             // LLUUID() and LLDate() default values so inspect() will report
             // them.
-            dft_array_full = LLSDMap("a", LLSDArray(true)(17)(3.14)(123456.78)("classic"))
-                                    ("b", LLSDArray("string")
-                                                   (LLUUID::generateNewID())
-                                                   (LLDate::now())
-                                                   (LLURI("http://www.ietf.org/rfc/rfc3986.txt"))
-                                                   (binary));
+            dft_array_full = LLSDMap("a", llsd::array(true, 17, 3.14, 123456.78, "classic"))
+                                    ("b", llsd::array("string",
+                                                      LLUUID::generateNewID(),
+                                                      LLDate::now(),
+                                                      LLURI("http://www.ietf.org/rfc/rfc3986.txt"),
+                                                      binary));
             debug("dft_array_full:\n",
                   dft_array_full);
             // Partial defaults arrays.
@@ -723,7 +723,7 @@ namespace tut
     {
         set_test_name("map-style registration with non-array params");
         // Pass "param names" as scalar or as map
-        LLSD attempts(LLSDArray(17)(LLSDMap("pi", 3.14)("two", 2)));
+        LLSD attempts(llsd::array(17, LLSDMap("pi", 3.14)("two", 2)));
         foreach(LLSD ae, inArray(attempts))
         {
             std::string threw = catch_what<std::exception>([this, &ae](){
@@ -738,7 +738,7 @@ namespace tut
     {
         set_test_name("map-style registration with badly-formed defaults");
         std::string threw = catch_what<std::exception>([this](){
-                work.add("freena_err", "freena", freena, LLSDArray("a")("b"), 17);
+                work.add("freena_err", "freena", freena, llsd::array("a", "b"), 17);
             });
         ensure_has(threw, "must be a map or an array");
     }
@@ -749,8 +749,8 @@ namespace tut
         set_test_name("map-style registration with too many array defaults");
         std::string threw = catch_what<std::exception>([this](){
                 work.add("freena_err", "freena", freena,
-                         LLSDArray("a")("b"),
-                         LLSDArray(17)(0.9)("gack"));
+                         llsd::array("a", "b"),
+                         llsd::array(17, 0.9, "gack"));
             });
         ensure_has(threw, "shorter than");
     }
@@ -761,7 +761,7 @@ namespace tut
         set_test_name("map-style registration with too many map defaults");
         std::string threw = catch_what<std::exception>([this](){
                 work.add("freena_err", "freena", freena,
-                         LLSDArray("a")("b"),
+                         llsd::array("a", "b"),
                          LLSDMap("b", 17)("foo", 3.14)("bar", "sinister"));
             });
         ensure_has(threw, "nonexistent params");
@@ -798,7 +798,7 @@ namespace tut
     void object::test<8>()
     {
         set_test_name("query Callables with/out required params");
-        LLSD names(LLSDArray("free1")("Dmethod1")("Dcmethod1")("method1"));
+        LLSD names(llsd::array("free1", "Dmethod1", "Dcmethod1", "method1"));
         foreach(LLSD nm, inArray(names))
         {
             LLSD metadata(getMetadata(nm));
@@ -821,13 +821,13 @@ namespace tut
     {
         set_test_name("query array-style functions/methods");
         // Associate each registered name with expected arity.
-        LLSD expected(LLSDArray
-                      (LLSDArray
-                       (0)(LLSDArray("free0_array")("smethod0_array")("method0_array")))
-                      (LLSDArray
-                       (5)(LLSDArray("freena_array")("smethodna_array")("methodna_array")))
-                      (LLSDArray
-                       (5)(LLSDArray("freenb_array")("smethodnb_array")("methodnb_array"))));
+        LLSD expected(llsd::array
+                      (llsd::array
+                       (0, llsd::array("free0_array", "smethod0_array", "method0_array")),
+                       llsd::array
+                       (5, llsd::array("freena_array", "smethodna_array", "methodna_array")),
+                       llsd::array
+                       (5, llsd::array("freenb_array", "smethodnb_array", "methodnb_array"))));
         foreach(LLSD ae, inArray(expected))
         {
             LLSD::Integer arity(ae[0].asInteger());
@@ -853,7 +853,7 @@ namespace tut
         set_test_name("query map-style no-params functions/methods");
         // - (Free function | non-static method), map style, no params (ergo
         //   no defaults)
-        LLSD names(LLSDArray("free0_map")("smethod0_map")("method0_map"));
+        LLSD names(llsd::array("free0_map", "smethod0_map", "method0_map"));
         foreach(LLSD nm, inArray(names))
         {
             LLSD metadata(getMetadata(nm));
@@ -877,13 +877,13 @@ namespace tut
         // there should (!) be no difference beween array defaults and map
         // defaults. Verify, so we can ignore the distinction for all other
         // tests.
-        LLSD equivalences(LLSDArray
-                          (LLSDArray("freena_map_adft")("freena_map_mdft"))
-                          (LLSDArray("freenb_map_adft")("freenb_map_mdft"))
-                          (LLSDArray("smethodna_map_adft")("smethodna_map_mdft"))
-                          (LLSDArray("smethodnb_map_adft")("smethodnb_map_mdft"))
-                          (LLSDArray("methodna_map_adft")("methodna_map_mdft"))
-                          (LLSDArray("methodnb_map_adft")("methodnb_map_mdft")));
+        LLSD equivalences(llsd::array
+                          (llsd::array("freena_map_adft", "freena_map_mdft"),
+                           llsd::array("freenb_map_adft", "freenb_map_mdft"),
+                           llsd::array("smethodna_map_adft", "smethodna_map_mdft"),
+                           llsd::array("smethodnb_map_adft", "smethodnb_map_mdft"),
+                           llsd::array("methodna_map_adft", "methodna_map_mdft"),
+                           llsd::array("methodnb_map_adft", "methodnb_map_mdft")));
         foreach(LLSD eq, inArray(equivalences))
         {
             LLSD adft(eq[0]);
@@ -953,42 +953,42 @@ namespace tut
         debug("skipreq:\n",
               skipreq);
 
-        LLSD groups(LLSDArray       // array of groups
+        LLSD groups(llsd::array       // array of groups
 
-                    (LLSDArray      // group
-                     (LLSDArray("freena_map_allreq")("smethodna_map_allreq")("methodna_map_allreq"))
-                     (LLSDArray(allreq["a"])(LLSD()))) // required, optional
+                    (llsd::array      // group
+                     (llsd::array("freena_map_allreq", "smethodna_map_allreq", "methodna_map_allreq"),
+                      llsd::array(allreq["a"], LLSD())),  // required, optional
 
-                    (LLSDArray        // group
-                     (LLSDArray("freenb_map_allreq")("smethodnb_map_allreq")("methodnb_map_allreq"))
-                     (LLSDArray(allreq["b"])(LLSD()))) // required, optional
+                     llsd::array        // group
+                     (llsd::array("freenb_map_allreq", "smethodnb_map_allreq", "methodnb_map_allreq"),
+                      llsd::array(allreq["b"], LLSD())),  // required, optional
 
-                    (LLSDArray        // group
-                     (LLSDArray("freena_map_leftreq")("smethodna_map_leftreq")("methodna_map_leftreq"))
-                     (LLSDArray(leftreq["a"])(rightdft["a"]))) // required, optional
+                     llsd::array        // group
+                     (llsd::array("freena_map_leftreq", "smethodna_map_leftreq", "methodna_map_leftreq"),
+                      llsd::array(leftreq["a"], rightdft["a"])),  // required, optional
 
-                    (LLSDArray        // group
-                     (LLSDArray("freenb_map_leftreq")("smethodnb_map_leftreq")("methodnb_map_leftreq"))
-                     (LLSDArray(leftreq["b"])(rightdft["b"]))) // required, optional
+                     llsd::array        // group
+                     (llsd::array("freenb_map_leftreq", "smethodnb_map_leftreq", "methodnb_map_leftreq"),
+                      llsd::array(leftreq["b"], rightdft["b"])),  // required, optional
 
-                    (LLSDArray        // group
-                     (LLSDArray("freena_map_skipreq")("smethodna_map_skipreq")("methodna_map_skipreq"))
-                     (LLSDArray(skipreq["a"])(dft_map_partial["a"]))) // required, optional
+                     llsd::array        // group
+                     (llsd::array("freena_map_skipreq", "smethodna_map_skipreq", "methodna_map_skipreq"),
+                      llsd::array(skipreq["a"], dft_map_partial["a"])),  // required, optional
 
-                    (LLSDArray        // group
-                     (LLSDArray("freenb_map_skipreq")("smethodnb_map_skipreq")("methodnb_map_skipreq"))
-                     (LLSDArray(skipreq["b"])(dft_map_partial["b"]))) // required, optional
+                     llsd::array        // group
+                     (llsd::array("freenb_map_skipreq", "smethodnb_map_skipreq", "methodnb_map_skipreq"),
+                      llsd::array(skipreq["b"], dft_map_partial["b"])),  // required, optional
 
-                    // We only need mention the full-map-defaults ("_mdft" suffix)
-                    // registrations, having established their equivalence with the
-                    // full-array-defaults ("_adft" suffix) registrations in another test.
-                    (LLSDArray        // group
-                     (LLSDArray("freena_map_mdft")("smethodna_map_mdft")("methodna_map_mdft"))
-                     (LLSDArray(LLSD::emptyMap())(dft_map_full["a"]))) // required, optional
+                     // We only need mention the full-map-defaults ("_mdft" suffix)
+                     // registrations, having established their equivalence with the
+                     // full-array-defaults ("_adft" suffix) registrations in another test.
+                     llsd::array        // group
+                     (llsd::array("freena_map_mdft", "smethodna_map_mdft", "methodna_map_mdft"),
+                      llsd::array(LLSD::emptyMap(), dft_map_full["a"])),  // required, optional
 
-                    (LLSDArray        // group
-                     (LLSDArray("freenb_map_mdft")("smethodnb_map_mdft")("methodnb_map_mdft"))
-                     (LLSDArray(LLSD::emptyMap())(dft_map_full["b"])))); // required, optional
+                     llsd::array        // group
+                     (llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"),
+                      llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional
 
         foreach(LLSD grp, inArray(groups))
         {
@@ -1077,7 +1077,7 @@ namespace tut
         // with 'required'.
         LLSD answer(42);
         // LLSD value matching 'required' according to llsd_matches() rules.
-        LLSD matching(LLSDMap("d", 3.14)("array", LLSDArray("answer")(true)(answer)));
+        LLSD matching(LLSDMap("d", 3.14)("array", llsd::array("answer", true, answer)));
         // Okay, walk through 'tests'.
         foreach(const CallablesTriple& tr, tests)
         {
@@ -1114,17 +1114,17 @@ namespace tut
         call_exc("free0_map", 17, map_exc);
         // Passing an array to a map-style function works now! No longer an
         // error case!
-//      call_exc("free0_map", LLSDArray("a")("b"), map_exc);
+//      call_exc("free0_map", llsd::array("a", "b"), map_exc);
     }
 
     template<> template<>
     void object::test<18>()
     {
         set_test_name("call no-args functions");
-        LLSD names(LLSDArray
-                   ("free0_array")("free0_map")
-                   ("smethod0_array")("smethod0_map")
-                   ("method0_array")("method0_map"));
+        LLSD names(llsd::array
+                   ("free0_array", "free0_map",
+                    "smethod0_array", "smethod0_map",
+                    "method0_array", "method0_map"));
         foreach(LLSD name, inArray(names))
         {
             // Look up the Vars instance for this function.
@@ -1142,10 +1142,10 @@ namespace tut
     }
 
     // Break out this data because we use it in a couple different tests.
-    LLSD array_funcs(LLSDArray
-                     (LLSDMap("a", "freena_array")   ("b", "freenb_array"))
-                     (LLSDMap("a", "smethodna_array")("b", "smethodnb_array"))
-                     (LLSDMap("a", "methodna_array") ("b", "methodnb_array")));
+    LLSD array_funcs(llsd::array
+                     (LLSDMap("a", "freena_array")   ("b", "freenb_array"),
+                      LLSDMap("a", "smethodna_array")("b", "smethodnb_array"),
+                      LLSDMap("a", "methodna_array") ("b", "methodnb_array")));
 
     template<> template<>
     void object::test<19>()
@@ -1153,7 +1153,7 @@ namespace tut
         set_test_name("call array-style functions with too-short arrays");
         // Could have two different too-short arrays, one for *na and one for
         // *nb, but since they both take 5 params...
-        LLSD tooshort(LLSDArray("this")("array")("too")("short"));
+        LLSD tooshort(llsd::array("this", "array", "too", "short"));
         foreach(const LLSD& funcsab, inArray(array_funcs))
         {
             foreach(const llsd::MapEntry& e, inMap(funcsab))
@@ -1172,12 +1172,12 @@ namespace tut
         {
             binary.push_back((U8)h);
         }
-        LLSD args(LLSDMap("a", LLSDArray(true)(17)(3.14)(123.456)("char*"))
-                         ("b", LLSDArray("string")
-                                        (LLUUID("01234567-89ab-cdef-0123-456789abcdef"))
-                                        (LLDate("2011-02-03T15:07:00Z"))
-                                        (LLURI("http://secondlife.com"))
-                                        (binary)));
+        LLSD args(LLSDMap("a", llsd::array(true, 17, 3.14, 123.456, "char*"))
+                         ("b", llsd::array("string",
+                                           LLUUID("01234567-89ab-cdef-0123-456789abcdef"),
+                                           LLDate("2011-02-03T15:07:00Z"),
+                                           LLURI("http://secondlife.com"),
+                                           binary)));
         LLSD argsplus(args);
         argsplus["a"].append("bogus");
         argsplus["b"].append("bogus");
@@ -1191,7 +1191,7 @@ namespace tut
         debug("expect: ", expect);
 
         // Use substantially the same logic for args and argsplus
-        LLSD argsarrays(LLSDArray(args)(argsplus));
+        LLSD argsarrays(llsd::array(args, argsplus));
         // So i==0 selects 'args', i==1 selects argsplus
         for (LLSD::Integer i(0), iend(argsarrays.size()); i < iend; ++i)
         {
@@ -1236,8 +1236,8 @@ namespace tut
         set_test_name("call map-style functions with (full | oversized) (arrays | maps)");
         const char binary[] = "\x99\x88\x77\x66\x55";
         LLSD array_full(LLSDMap
-                        ("a", LLSDArray(false)(255)(98.6)(1024.5)("pointer"))
-                        ("b", LLSDArray("object")(LLUUID::generateNewID())(LLDate::now())(LLURI("http://wiki.lindenlab.com/wiki"))(LLSD::Binary(boost::begin(binary), boost::end(binary)))));
+                        ("a", llsd::array(false, 255, 98.6, 1024.5, "pointer"))
+                        ("b", llsd::array("object", LLUUID::generateNewID(), LLDate::now(), LLURI("http://wiki.lindenlab.com/wiki"), LLSD::Binary(boost::begin(binary), boost::end(binary)))));
         LLSD array_overfull(array_full);
         foreach(LLSD::String a, ab)
         {
@@ -1280,20 +1280,20 @@ namespace tut
         // parameter defaults should make NO DIFFERENCE WHATSOEVER. Every call
         // should pass all params.
         LLSD names(LLSDMap
-                   ("a", LLSDArray
-                         ("freena_map_allreq") ("smethodna_map_allreq") ("methodna_map_allreq")
-                         ("freena_map_leftreq")("smethodna_map_leftreq")("methodna_map_leftreq")
-                         ("freena_map_skipreq")("smethodna_map_skipreq")("methodna_map_skipreq")
-                         ("freena_map_adft")   ("smethodna_map_adft")   ("methodna_map_adft")
-                         ("freena_map_mdft")   ("smethodna_map_mdft")   ("methodna_map_mdft"))
-                   ("b", LLSDArray
-                         ("freenb_map_allreq") ("smethodnb_map_allreq") ("methodnb_map_allreq")
-                         ("freenb_map_leftreq")("smethodnb_map_leftreq")("methodnb_map_leftreq")
-                         ("freenb_map_skipreq")("smethodnb_map_skipreq")("methodnb_map_skipreq")
-                         ("freenb_map_adft")   ("smethodnb_map_adft")   ("methodnb_map_adft")
-                         ("freenb_map_mdft")   ("smethodnb_map_mdft")   ("methodnb_map_mdft")));
+                   ("a", llsd::array
+                    ("freena_map_allreq",  "smethodna_map_allreq",  "methodna_map_allreq",
+                     "freena_map_leftreq", "smethodna_map_leftreq", "methodna_map_leftreq",
+                     "freena_map_skipreq", "smethodna_map_skipreq", "methodna_map_skipreq",
+                     "freena_map_adft",    "smethodna_map_adft",    "methodna_map_adft",
+                     "freena_map_mdft",    "smethodna_map_mdft",    "methodna_map_mdft"))
+                   ("b", llsd::array
+                    ("freenb_map_allreq",  "smethodnb_map_allreq",  "methodnb_map_allreq",
+                     "freenb_map_leftreq", "smethodnb_map_leftreq", "methodnb_map_leftreq",
+                     "freenb_map_skipreq", "smethodnb_map_skipreq", "methodnb_map_skipreq",
+                     "freenb_map_adft",    "smethodnb_map_adft",    "methodnb_map_adft",
+                     "freenb_map_mdft",    "smethodnb_map_mdft",    "methodnb_map_mdft")));
         // Treat (full | overfull) (array | map) the same.
-        LLSD argssets(LLSDArray(array_full)(array_overfull)(map_full)(map_overfull));
+        LLSD argssets(llsd::array(array_full, array_overfull, map_full, map_overfull));
         foreach(const LLSD& args, inArray(argssets))
         {
             foreach(LLSD::String a, ab)
diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp
index 999d4320798e630fd41f56fae6591e928a71d7d7..81449b4a421ba549c78c04f77b1d6c3b87b2f721 100644
--- a/indra/llcommon/tests/llprocess_test.cpp
+++ b/indra/llcommon/tests/llprocess_test.cpp
@@ -356,7 +356,7 @@ namespace tut
 
         // Create a script file in a temporary place.
         NamedTempFile script("py",
-			"from __future__ import print_function" EOL
+            "from __future__ import print_function" EOL
             "import sys" EOL
             "import time" EOL
             EOL
@@ -366,7 +366,7 @@ namespace tut
             "time.sleep(2)" EOL
             "print('stderr after wait',file=sys.stderr)" EOL
             "sys.stderr.flush()" EOL
-            );
+        );
 
         // Arrange to track the history of our interaction with child: what we
         // fetched, which pipe it came from, how many tries it took before we
@@ -862,8 +862,8 @@ namespace tut
         set_test_name("'bogus' test");
         CaptureLog recorder;
         PythonProcessLauncher py(get_test_name(),
-                                 "from __future__ import print_function\n"
-                                 "print('Hello world')\n");
+            "from __future__ import print_function\n"
+            "print('Hello world')\n");
         py.mParams.files.add(LLProcess::FileParam("bogus"));
         py.mPy = LLProcess::create(py.mParams);
         ensure("should have rejected 'bogus'", ! py.mPy);
@@ -878,8 +878,8 @@ namespace tut
         // Replace this test with one or more real 'file' tests when we
         // implement 'file' support
         PythonProcessLauncher py(get_test_name(),
-                                 "from __future__ import print_function\n"
-                                 "print('Hello world')\n");
+            "from __future__ import print_function\n"
+            "print('Hello world')\n");
         py.mParams.files.add(LLProcess::FileParam());
         py.mParams.files.add(LLProcess::FileParam("file"));
         py.mPy = LLProcess::create(py.mParams);
@@ -894,8 +894,8 @@ namespace tut
         // implement 'tpipe' support
         CaptureLog recorder;
         PythonProcessLauncher py(get_test_name(),
-                                 "from __future__ import print_function\n"
-                                 "print('Hello world')\n");
+            "from __future__ import print_function\n"
+            "print('Hello world')\n");
         py.mParams.files.add(LLProcess::FileParam());
         py.mParams.files.add(LLProcess::FileParam("tpipe"));
         py.mPy = LLProcess::create(py.mParams);
@@ -912,8 +912,8 @@ namespace tut
         // implement 'npipe' support
         CaptureLog recorder;
         PythonProcessLauncher py(get_test_name(),
-                                 "from __future__ import print_function\n"
-                                 "print('Hello world')\n");
+            "from __future__ import print_function\n"
+            "print('Hello world')\n");
         py.mParams.files.add(LLProcess::FileParam());
         py.mParams.files.add(LLProcess::FileParam());
         py.mParams.files.add(LLProcess::FileParam("npipe"));
@@ -989,20 +989,20 @@ namespace tut
     {
         set_test_name("get*Pipe() validation");
         PythonProcessLauncher py(get_test_name(),
-                                 "from __future__ import print_function\n"
-                                 "print('this output is expected')\n");
+            "from __future__ import print_function\n"
+            "print('this output is expected')\n");
         py.mParams.files.add(LLProcess::FileParam("pipe")); // pipe for  stdin
         py.mParams.files.add(LLProcess::FileParam());       // inherit stdout
         py.mParams.files.add(LLProcess::FileParam("pipe")); // pipe for stderr
         py.run();
         TEST_getPipe(*py.mPy, getWritePipe, getOptWritePipe,
-                     LLProcess::STDIN,   // VALID
-                     LLProcess::STDOUT,  // NOPIPE
-                     LLProcess::STDERR); // BADPIPE
+            LLProcess::STDIN,   // VALID
+            LLProcess::STDOUT,  // NOPIPE
+            LLProcess::STDERR); // BADPIPE
         TEST_getPipe(*py.mPy, getReadPipe,  getOptReadPipe,
-                     LLProcess::STDERR,  // VALID
-                     LLProcess::STDOUT,  // NOPIPE
-                     LLProcess::STDIN);  // BADPIPE
+            LLProcess::STDERR,  // VALID
+            LLProcess::STDOUT,  // NOPIPE
+            LLProcess::STDIN);  // BADPIPE
     }
 
     template<> template<>
@@ -1129,8 +1129,8 @@ namespace tut
     {
         set_test_name("ReadPipe \"eof\" event");
         PythonProcessLauncher py(get_test_name(),
-                                 "from __future__ import print_function\n"
-                                 "print('Hello from Python!')\n");
+            "from __future__ import print_function\n"
+            "print('Hello from Python!')\n");
         py.mParams.files.add(LLProcess::FileParam()); // stdin
         py.mParams.files.add(LLProcess::FileParam("pipe")); // stdout
         py.launch();
diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp
index c246f5ee56da7879998115a5cbbece6a2713f442..5dbcf4c9b864aa5290ffb6508808026611236cb1 100644
--- a/indra/llcommon/tests/llsdserialize_test.cpp
+++ b/indra/llcommon/tests/llsdserialize_test.cpp
@@ -1817,10 +1817,10 @@ namespace tut
     {
         set_test_name("verify sequence to Python");
 
-        LLSD cdata(LLSDArray(17)(3.14)
-                  ("This string\n"
-                   "has several\n"
-                   "lines."));
+        LLSD cdata(llsd::array(17, 3.14,
+                               "This string\n"
+                               "has several\n"
+                               "lines."));
 
         const char pydata[] =
             "def verify(iterable):\n"
diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp
index 83e0216ae72b3bf57a9da5298fccc9cd088e290d..cf80ce0656d5287002f82d20b44cba3e2327d86d 100644
--- a/indra/llcommon/workqueue.cpp
+++ b/indra/llcommon/workqueue.cpp
@@ -161,12 +161,7 @@ bool LL::WorkQueue::done()
     return mQueue.done();
 }
 
-void LL::WorkQueue::post(const Work& callable)
-{
-    mQueue.push(callable);
-}
-
-bool LL::WorkQueue::postIfOpen(const Work& callable)
+bool LL::WorkQueue::post(const Work& callable)
 {
     return mQueue.pushIfOpen(callable);
 }
@@ -215,26 +210,16 @@ bool LL::WorkSchedule::done()
     return mQueue.done();
 }
 
-void LL::WorkSchedule::post(const Work& callable)
+bool LL::WorkSchedule::post(const Work& callable)
 {
     // Use TimePoint::clock::now() instead of TimePoint's representation of
     // the epoch because this WorkSchedule may contain a mix of past-due
     // TimedWork items and TimedWork items scheduled for the future. Sift this
     // new item into the correct place.
-    post(callable, TimePoint::clock::now());
-}
-
-void LL::WorkSchedule::post(const Work& callable, const TimePoint& time)
-{
-    mQueue.push(TimedWork(time, callable));
-}
-
-bool LL::WorkSchedule::postIfOpen(const Work& callable)
-{
-    return postIfOpen(callable, TimePoint::clock::now());
+    return post(callable, TimePoint::clock::now());
 }
 
-bool LL::WorkSchedule::postIfOpen(const Work& callable, const TimePoint& time)
+bool LL::WorkSchedule::post(const Work& callable, const TimePoint& time)
 {
     return mQueue.pushIfOpen(TimedWork(time, callable));
 }
diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h
index 5461ce6c2334a4081c413ef3dcb576fbb4e9c035..ec0700a718756aa8a09fe0e29cd6b1a64e6c4119 100644
--- a/indra/llcommon/workqueue.h
+++ b/indra/llcommon/workqueue.h
@@ -83,13 +83,10 @@ namespace LL
 
         /*---------------------- fire and forget API -----------------------*/
 
-        /// fire-and-forget
-        virtual void post(const Work&) = 0;
-
         /**
          * post work, unless the queue is closed before we can post
          */
-        virtual bool postIfOpen(const Work&) = 0;
+        virtual bool post(const Work&) = 0;
 
         /**
          * post work, unless the queue is full
@@ -247,13 +244,10 @@ namespace LL
 
         /*---------------------- fire and forget API -----------------------*/
 
-        /// fire-and-forget
-        void post(const Work&) override;
-
         /**
          * post work, unless the queue is closed before we can post
          */
-        bool postIfOpen(const Work&) override;
+        bool post(const Work&) override;
 
         /**
          * post work, unless the queue is full
@@ -320,22 +314,16 @@ namespace LL
 
         /*---------------------- fire and forget API -----------------------*/
 
-        /// fire-and-forget
-        void post(const Work& callable) override;
-
-        /// fire-and-forget, but at a particular (future?) time
-        void post(const Work& callable, const TimePoint& time);
-
         /**
          * post work, unless the queue is closed before we can post
          */
-        bool postIfOpen(const Work& callable) override;
+        bool post(const Work& callable) override;
 
         /**
          * post work for a particular time, unless the queue is closed before
          * we can post
          */
-        bool postIfOpen(const Work& callable, const TimePoint& time);
+        bool post(const Work& callable, const TimePoint& time);
 
         /**
          * post work, unless the queue is full
@@ -356,7 +344,7 @@ namespace LL
          * an LLCond variant, e.g. LLOneShotCond or LLBoolCond.
          */
         template <typename Rep, typename Period, typename CALLABLE>
-        void postEvery(const std::chrono::duration<Rep, Period>& interval,
+        bool postEvery(const std::chrono::duration<Rep, Period>& interval,
                        CALLABLE&& callable);
 
     private:
@@ -417,15 +405,10 @@ namespace LL
                 // move-only callable; but naturally this statement must be
                 // the last time we reference this instance, which may become
                 // moved-from.
-                try
-                {
-                    auto target{ std::dynamic_pointer_cast<WorkSchedule>(mTarget.lock()) };
-                    target->post(std::move(*this), mStart);
-                }
-                catch (const Closed&)
-                {
-                    // Once this queue is closed, oh well, just stop
-                }
+                auto target{ std::dynamic_pointer_cast<WorkSchedule>(mTarget.lock()) };
+                // Discard bool return: once this queue is closed, oh well,
+                // just stop
+                target->post(std::move(*this), mStart);
             }
         }
 
@@ -437,7 +420,7 @@ namespace LL
     };
 
     template <typename Rep, typename Period, typename CALLABLE>
-    void WorkSchedule::postEvery(const std::chrono::duration<Rep, Period>& interval,
+    bool WorkSchedule::postEvery(const std::chrono::duration<Rep, Period>& interval,
                                  CALLABLE&& callable)
     {
         if (interval.count() <= 0)
@@ -454,7 +437,7 @@ namespace LL
         // Instantiate and post a suitable BackJack, binding a weak_ptr to
         // self, the current time, the desired interval and the desired
         // callable.
-        post(
+        return post(
             BackJack<Rep, Period, CALLABLE>(
                  getWeak(), TimePoint::clock::now(), interval, std::move(callable)));
     }
@@ -516,7 +499,7 @@ namespace LL
         // Here we believe target WorkQueue still exists. Post to it a
         // lambda that packages our callable, our callback and a weak_ptr
         // to this originating WorkQueue.
-        tptr->post(
+        return tptr->post(
             [reply = super::getWeak(),
              callable = std::move(callable),
              callback = std::move(callback)]
@@ -547,9 +530,6 @@ namespace LL
             },
             // if caller passed a TimePoint, pass it along to post()
             std::forward<ARGS>(args)...);
-
-        // looks like we were able to post()
-        return true;
     }
 
     template <typename... ARGS>
@@ -560,18 +540,9 @@ namespace LL
         auto tptr = target.lock();
         if (tptr)
         {
-            try
-            {
-                tptr->post(std::forward<ARGS>(args)...);
-                // we were able to post()
-                return true;
-            }
-            catch (const Closed&)
-            {
-                // target WorkQueue still exists, but is Closed
-            }
+            return tptr->post(std::forward<ARGS>(args)...);
         }
-        // either target no longer exists, or its WorkQueue is Closed
+        // target no longer exists
         return false;
     }
 
@@ -583,7 +554,7 @@ namespace LL
         auto operator()(WorkQueueBase* self, CALLABLE&& callable, ARGS&&... args)
         {
             LLCoros::Promise<RETURNTYPE> promise;
-            self->post(
+            bool posted = self->post(
                 // We dare to bind a reference to Promise because it's
                 // specifically designed for cross-thread communication.
                 [&promise, callable = std::move(callable)]()
@@ -600,6 +571,10 @@ namespace LL
                 },
                 // if caller passed a TimePoint, pass it to post()
                 std::forward<ARGS>(args)...);
+            if (! posted)
+            {
+                LLTHROW(WorkQueueBase::Closed());
+            }
             auto future{ LLCoros::getFuture(promise) };
             // now, on the calling thread, wait for that result
             LLCoros::TempStatus st("waiting for WorkQueue::waitForResult()");
@@ -615,7 +590,7 @@ namespace LL
         void operator()(WorkQueueBase* self, CALLABLE&& callable, ARGS&&... args)
         {
             LLCoros::Promise<void> promise;
-            self->post(
+            bool posted = self->post(
                 // &promise is designed for cross-thread access
                 [&promise, callable = std::move(callable)]()
                 mutable {
@@ -631,6 +606,10 @@ namespace LL
                 },
                 // if caller passed a TimePoint, pass it to post()
                 std::forward<ARGS>(args)...);
+            if (! posted)
+            {
+                LLTHROW(WorkQueueBase::Closed());
+            }
             auto future{ LLCoros::getFuture(promise) };
             // block until set_value()
             LLCoros::TempStatus st("waiting for void WorkQueue::waitForResult()");
diff --git a/indra/llimage/llimageworker.cpp b/indra/llimage/llimageworker.cpp
index 0093958e6ddf6d0365a0fbeb9ff48d367b417b78..520c81a8ecbe0807e8753c1a0ffaee12d1122985 100644
--- a/indra/llimage/llimageworker.cpp
+++ b/indra/llimage/llimageworker.cpp
@@ -93,13 +93,18 @@ LLImageDecodeThread::handle_t LLImageDecodeThread::decodeImage(
     LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
 
     // Instantiate the ImageRequest right in the lambda, why not?
-    mThreadPool->getQueue().post(
+    bool posted = mThreadPool->getQueue().post(
         [req = ImageRequest(image, discard, needs_aux, responder)]
         () mutable
         {
             auto done = req.processRequest();
             req.finishRequest(done);
         });
+    if (! posted)
+    {
+        LL_DEBUGS() << "Tried to start decoding on shutdown" << LL_ENDL;
+        // should this return 0?
+    }
 
     // It's important to our consumer (LLTextureFetchWorker) that we return a
     // nonzero handle. It is NOT important that the nonzero handle be unique:
diff --git a/indra/llinventory/llsettingsdaycycle.cpp b/indra/llinventory/llsettingsdaycycle.cpp
index 241826604f660e5c6c1f9c0e1f6b397ab34e5907..42dd5e3d10c0d0a42d27503d43200db44a46b48d 100644
--- a/indra/llinventory/llsettingsdaycycle.cpp
+++ b/indra/llinventory/llsettingsdaycycle.cpp
@@ -440,8 +440,8 @@ LLSD LLSettingsDay::defaults()
         }
 
         LLSD tracks;
-        tracks.append(LLSDArray(waterTrack));
-        tracks.append(LLSDArray(skyTrack));
+        tracks.append(llsd::array(waterTrack));
+        tracks.append(llsd::array(skyTrack));
 
         dfltsetting[SETTING_TRACKS] = tracks;
         dfltsetting[SETTING_FRAMES] = frames;
diff --git a/indra/llinventory/llsettingssky.cpp b/indra/llinventory/llsettingssky.cpp
index 8e801db2dc18ed28e57bc4d0f070caae7af6e348..eb8385281c971e1b3e1ded636d1c8ca98f3d0be4 100644
--- a/indra/llinventory/llsettingssky.cpp
+++ b/indra/llinventory/llsettingssky.cpp
@@ -154,24 +154,24 @@ LLSettingsSky::validation_list_t legacyHazeValidationList()
     {
         legacyHazeValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_AMBIENT,             false,  LLSD::TypeArray, 
             boost::bind(&LLSettingsBase::Validator::verifyVectorMinMax, _1, _2,
-                LLSD(LLSDArray(0.0f)(0.0f)(0.0f)("*")),
-                LLSD(LLSDArray(3.0f)(3.0f)(3.0f)("*")))));
+                llsd::array(0.0f, 0.0f, 0.0f, "*"),
+                llsd::array(3.0f, 3.0f, 3.0f, "*"))));
         legacyHazeValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_BLUE_DENSITY,        false,  LLSD::TypeArray, 
             boost::bind(&LLSettingsBase::Validator::verifyVectorMinMax, _1, _2,
-                LLSD(LLSDArray(0.0f)(0.0f)(0.0f)("*")),
-                LLSD(LLSDArray(3.0f)(3.0f)(3.0f)("*")))));
+                llsd::array(0.0f, 0.0f, 0.0f, "*"),
+                llsd::array(3.0f, 3.0f, 3.0f, "*"))));
         legacyHazeValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_BLUE_HORIZON,        false,  LLSD::TypeArray, 
             boost::bind(&LLSettingsBase::Validator::verifyVectorMinMax, _1, _2,
-                LLSD(LLSDArray(0.0f)(0.0f)(0.0f)("*")),
-                LLSD(LLSDArray(3.0f)(3.0f)(3.0f)("*")))));
+                llsd::array(0.0f, 0.0f, 0.0f, "*"),
+                llsd::array(3.0f, 3.0f, 3.0f, "*"))));
         legacyHazeValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_HAZE_DENSITY,        false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(5.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 5.0f))));
         legacyHazeValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_HAZE_HORIZON,        false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(5.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 5.0f))));
         legacyHazeValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_DENSITY_MULTIPLIER,  false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0001f)(2.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(0.0001f, 2.0f))));
         legacyHazeValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_DISTANCE_MULTIPLIER, false,  LLSD::TypeReal,
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0001f)(1000.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(0.0001f, 1000.0f))));
     }
     return legacyHazeValidation;
 }
@@ -182,19 +182,19 @@ LLSettingsSky::validation_list_t rayleighValidationList()
     if (rayleighValidation.empty())
     {
         rayleighValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_DENSITY_PROFILE_WIDTH,      false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(32768.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 32768.0f))));
 
         rayleighValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_TERM,   false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(2.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 2.0f))));
         
         rayleighValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_SCALE_FACTOR, false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(-1.0f)(1.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(-1.0f, 1.0f))));
 
         rayleighValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_DENSITY_PROFILE_LINEAR_TERM, false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(2.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 2.0f))));
 
         rayleighValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM, false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(1.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 1.0f))));
     }
     return rayleighValidation;
 }
@@ -205,19 +205,19 @@ LLSettingsSky::validation_list_t absorptionValidationList()
     if (absorptionValidation.empty())
     {
         absorptionValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_DENSITY_PROFILE_WIDTH,      false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(32768.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 32768.0f))));
 
         absorptionValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_TERM,   false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(2.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 2.0f))));
         
         absorptionValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_SCALE_FACTOR, false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(-1.0f)(1.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(-1.0f, 1.0f))));
 
         absorptionValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_DENSITY_PROFILE_LINEAR_TERM, false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(2.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 2.0f))));
 
         absorptionValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM, false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(1.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 1.0f))));
     }
     return absorptionValidation;
 }
@@ -228,22 +228,22 @@ LLSettingsSky::validation_list_t mieValidationList()
     if (mieValidation.empty())
     {
         mieValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_DENSITY_PROFILE_WIDTH,      false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(32768.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 32768.0f))));
 
         mieValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_TERM,   false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(2.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 2.0f))));
         
         mieValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_SCALE_FACTOR, false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(-1.0f)(1.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(-1.0f, 1.0f))));
 
         mieValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_DENSITY_PROFILE_LINEAR_TERM, false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(2.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 2.0f))));
 
         mieValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM, false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(1.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 1.0f))));
 
         mieValidation.push_back(LLSettingsBase::Validator(LLSettingsSky::SETTING_MIE_ANISOTROPY_FACTOR, false,  LLSD::TypeReal,  
-            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(1.0f)))));
+            boost::bind(&LLSettingsBase::Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 1.0f))));
     }
     return mieValidation;
 }
@@ -548,92 +548,89 @@ LLSettingsSky::validation_list_t LLSettingsSky::validationList()
     static validation_list_t validation;
 
     if (validation.empty())
-    {   // Note the use of LLSD(LLSDArray()()()...) This is due to an issue with the 
-        // copy constructor for LLSDArray.  Directly binding the LLSDArray as 
-        // a parameter without first wrapping it in a pure LLSD object will result 
-        // in deeply nested arrays like this [[[[[[[[[[v1,v2,v3]]]]]]]]]]
+    {
         validation.push_back(Validator(SETTING_BLOOM_TEXTUREID,     true,  LLSD::TypeUUID));
         validation.push_back(Validator(SETTING_RAINBOW_TEXTUREID,   false,  LLSD::TypeUUID));
         validation.push_back(Validator(SETTING_HALO_TEXTUREID,      false,  LLSD::TypeUUID));
 
         validation.push_back(Validator(SETTING_CLOUD_COLOR,         true,  LLSD::TypeArray, 
             boost::bind(&Validator::verifyVectorMinMax, _1, _2,
-                LLSD(LLSDArray(0.0f)(0.0f)(0.0f)("*")),
-                LLSD(LLSDArray(1.0f)(1.0f)(1.0f)("*")))));
+                llsd::array(0.0f, 0.0f, 0.0f, "*"),
+                llsd::array(1.0f, 1.0f, 1.0f, "*"))));
         validation.push_back(Validator(SETTING_CLOUD_POS_DENSITY1,  true,  LLSD::TypeArray, 
             boost::bind(&Validator::verifyVectorMinMax, _1, _2,
-                LLSD(LLSDArray(0.0f)(0.0f)(0.0f)("*")),
-                LLSD(LLSDArray(1.0f)(1.0f)(3.0f)("*")))));
+                llsd::array(0.0f, 0.0f, 0.0f, "*"),
+                llsd::array(1.0f, 1.0f, 3.0f, "*"))));
         validation.push_back(Validator(SETTING_CLOUD_POS_DENSITY2,  true,  LLSD::TypeArray, 
             boost::bind(&Validator::verifyVectorMinMax, _1, _2,
-                LLSD(LLSDArray(0.0f)(0.0f)(0.0f)("*")),
-                LLSD(LLSDArray(1.0f)(1.0f)(1.0f)("*")))));
+                llsd::array(0.0f, 0.0f, 0.0f, "*"),
+                llsd::array(1.0f, 1.0f, 1.0f, "*"))));
         validation.push_back(Validator(SETTING_CLOUD_SCALE,         true,  LLSD::TypeReal,  
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.001f)(3.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.001f, 3.0f))));
         validation.push_back(Validator(SETTING_CLOUD_SCROLL_RATE,   true,  LLSD::TypeArray, 
             boost::bind(&Validator::verifyVectorMinMax, _1, _2,
-                LLSD(LLSDArray(-50.0f)(-50.0f)),
-                LLSD(LLSDArray(50.0f)(50.0f)))));
+                llsd::array(-50.0f, -50.0f),
+                llsd::array(50.0f, 50.0f))));
         validation.push_back(Validator(SETTING_CLOUD_SHADOW,        true,  LLSD::TypeReal,  
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(1.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 1.0f))));
         validation.push_back(Validator(SETTING_CLOUD_TEXTUREID,     false, LLSD::TypeUUID));
         validation.push_back(Validator(SETTING_CLOUD_VARIANCE,      false,  LLSD::TypeReal,  
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(1.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 1.0f))));
 
         validation.push_back(Validator(SETTING_DOME_OFFSET,         false, LLSD::TypeReal,  
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(1.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 1.0f))));
         validation.push_back(Validator(SETTING_DOME_RADIUS,         false, LLSD::TypeReal,  
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(1000.0f)(2000.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(1000.0f, 2000.0f))));
         validation.push_back(Validator(SETTING_GAMMA,               true,  LLSD::TypeReal,  
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(20.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 20.0f))));
         validation.push_back(Validator(SETTING_GLOW,                true,  LLSD::TypeArray, 
             boost::bind(&Validator::verifyVectorMinMax, _1, _2,
-                LLSD(LLSDArray(0.2f)("*")(-10.0f)("*")),
-                LLSD(LLSDArray(40.0f)("*")(10.0f)("*")))));
+                llsd::array(0.2f, "*", -10.0f, "*"),
+                llsd::array(40.0f, "*", 10.0f, "*"))));
         
         validation.push_back(Validator(SETTING_MAX_Y,               true,  LLSD::TypeReal,  
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(10000.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 10000.0f))));
         validation.push_back(Validator(SETTING_MOON_ROTATION,       true,  LLSD::TypeArray, &Validator::verifyQuaternionNormal));
         validation.push_back(Validator(SETTING_MOON_SCALE,          false, LLSD::TypeReal,
-                boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.25f)(20.0f))), LLSD::Real(1.0)));
+                boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.25f, 20.0f)), LLSD::Real(1.0)));
         validation.push_back(Validator(SETTING_MOON_TEXTUREID,      false, LLSD::TypeUUID));
         validation.push_back(Validator(SETTING_MOON_BRIGHTNESS,     false,  LLSD::TypeReal, 
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(1.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 1.0f))));
 
         validation.push_back(Validator(SETTING_STAR_BRIGHTNESS,     true,  LLSD::TypeReal, 
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(500.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 500.0f))));
         validation.push_back(Validator(SETTING_SUNLIGHT_COLOR,      true,  LLSD::TypeArray, 
             boost::bind(&Validator::verifyVectorMinMax, _1, _2,
-                LLSD(LLSDArray(0.0f)(0.0f)(0.0f)("*")),
-                LLSD(LLSDArray(3.0f)(3.0f)(3.0f)("*")))));
+                llsd::array(0.0f, 0.0f, 0.0f, "*"),
+                llsd::array(3.0f, 3.0f, 3.0f, "*"))));
         validation.push_back(Validator(SETTING_SUN_ROTATION,        true,  LLSD::TypeArray, &Validator::verifyQuaternionNormal));
         validation.push_back(Validator(SETTING_SUN_SCALE,           false, LLSD::TypeReal,
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.25f)(20.0f))), LLSD::Real(1.0)));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.25f, 20.0f)), LLSD::Real(1.0)));
         validation.push_back(Validator(SETTING_SUN_TEXTUREID, false, LLSD::TypeUUID));
 
         validation.push_back(Validator(SETTING_PLANET_RADIUS,       true,  LLSD::TypeReal,  
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(1000.0f)(32768.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(1000.0f, 32768.0f))));
 
         validation.push_back(Validator(SETTING_SKY_BOTTOM_RADIUS,   true,  LLSD::TypeReal,  
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(1000.0f)(32768.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(1000.0f, 32768.0f))));
 
         validation.push_back(Validator(SETTING_SKY_TOP_RADIUS,       true,  LLSD::TypeReal,  
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(1000.0f)(32768.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(1000.0f, 32768.0f))));
 
         validation.push_back(Validator(SETTING_SUN_ARC_RADIANS,      true,  LLSD::TypeReal,  
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(0.1f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 0.1f))));
 
         validation.push_back(Validator(SETTING_SKY_MOISTURE_LEVEL,      false,  LLSD::TypeReal,  
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(1.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 1.0f))));
 
         validation.push_back(Validator(SETTING_SKY_DROPLET_RADIUS,      false,  LLSD::TypeReal,  
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(5.0f)(1000.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(5.0f, 1000.0f))));
 
         validation.push_back(Validator(SETTING_SKY_ICE_LEVEL,      false,  LLSD::TypeReal,  
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(1.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 1.0f))));
 
         validation.push_back(Validator(SETTING_REFLECTION_PROBE_AMBIANCE, false, LLSD::TypeReal,
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(10.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(llsd::array(0.0f, 10.0f)))));
 
         validation.push_back(Validator(SETTING_RAYLEIGH_CONFIG, true, LLSD::TypeArray, &validateRayleighLayers));
         validation.push_back(Validator(SETTING_ABSORPTION_CONFIG, true, LLSD::TypeArray, &validateAbsorptionLayers));
@@ -724,7 +721,7 @@ LLSD LLSettingsSky::defaults(const LLSettingsBase::TrackPosition& position)
         dfltsetting[SETTING_CLOUD_POS_DENSITY1] = LLColor4(1.0000, 0.5260, 1.0000, 0.0).getValue();
         dfltsetting[SETTING_CLOUD_POS_DENSITY2] = LLColor4(1.0000, 0.5260, 1.0000, 0.0).getValue();
         dfltsetting[SETTING_CLOUD_SCALE]        = LLSD::Real(0.4199);
-        dfltsetting[SETTING_CLOUD_SCROLL_RATE]  = LLSDArray(0.0f)(0.0f);
+        dfltsetting[SETTING_CLOUD_SCROLL_RATE]  = llsd::array(0.0f, 0.0f);
         dfltsetting[SETTING_CLOUD_SHADOW]       = LLSD::Real(0.2699);
         dfltsetting[SETTING_CLOUD_VARIANCE]     = LLSD::Real(0.0);
 
diff --git a/indra/llinventory/llsettingswater.cpp b/indra/llinventory/llsettingswater.cpp
index 90f99e81986dba3d0694d851c46bb5ead53d39d2..f19beb5be5f068dfca107cf9c478463b7bd8f918 100644
--- a/indra/llinventory/llsettingswater.cpp
+++ b/indra/llinventory/llsettingswater.cpp
@@ -222,42 +222,38 @@ LLSettingsWater::validation_list_t LLSettingsWater::validationList()
     static validation_list_t validation;
 
     if (validation.empty())
-    {   // Note the use of LLSD(LLSDArray()()()...) This is due to an issue with the 
-        // copy constructor for LLSDArray.  Directly binding the LLSDArray as 
-        // a parameter without first wrapping it in a pure LLSD object will result 
-        // in deeply nested arrays like this [[[[[[[[[[v1,v2,v3]]]]]]]]]]
-
+    {
         validation.push_back(Validator(SETTING_BLUR_MULTIPLIER, true, LLSD::TypeReal,
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(-0.5f)(0.5f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(-0.5f, 0.5f))));
         validation.push_back(Validator(SETTING_FOG_COLOR, true, LLSD::TypeArray,
             boost::bind(&Validator::verifyVectorMinMax, _1, _2,
-                LLSD(LLSDArray(0.0f)(0.0f)(0.0f)(1.0f)),
-                LLSD(LLSDArray(1.0f)(1.0f)(1.0f)(1.0f)))));
+                llsd::array(0.0f, 0.0f, 0.0f, 1.0f),
+                llsd::array(1.0f, 1.0f, 1.0f, 1.0f))));
         validation.push_back(Validator(SETTING_FOG_DENSITY, true, LLSD::TypeReal,
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(-10.0f)(10.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(-10.0f, 10.0f))));
         validation.push_back(Validator(SETTING_FOG_MOD, true, LLSD::TypeReal,
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(20.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 20.0f))));
         validation.push_back(Validator(SETTING_FRESNEL_OFFSET, true, LLSD::TypeReal,
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(1.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 1.0f))));
         validation.push_back(Validator(SETTING_FRESNEL_SCALE, true, LLSD::TypeReal,
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(1.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 1.0f))));
         validation.push_back(Validator(SETTING_NORMAL_MAP, true, LLSD::TypeUUID));
         validation.push_back(Validator(SETTING_NORMAL_SCALE, true, LLSD::TypeArray,
             boost::bind(&Validator::verifyVectorMinMax, _1, _2,
-                LLSD(LLSDArray(0.0f)(0.0f)(0.0f)),
-                LLSD(LLSDArray(10.0f)(10.0f)(10.0f)))));
+                llsd::array(0.0f, 0.0f, 0.0f),
+                llsd::array(10.0f, 10.0f, 10.0f))));
         validation.push_back(Validator(SETTING_SCALE_ABOVE, true, LLSD::TypeReal,
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(3.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 3.0f))));
         validation.push_back(Validator(SETTING_SCALE_BELOW, true, LLSD::TypeReal,
-            boost::bind(&Validator::verifyFloatRange, _1, _2, LLSD(LLSDArray(0.0f)(3.0f)))));
+            boost::bind(&Validator::verifyFloatRange, _1, _2, llsd::array(0.0f, 3.0f))));
         validation.push_back(Validator(SETTING_WAVE1_DIR, true, LLSD::TypeArray,
             boost::bind(&Validator::verifyVectorMinMax, _1, _2,
-                LLSD(LLSDArray(-20.0f)(-20.0f)),
-                LLSD(LLSDArray(20.0f)(20.0f)))));
+                llsd::array(-20.0f, -20.0f),
+                llsd::array(20.0f, 20.0f))));
         validation.push_back(Validator(SETTING_WAVE2_DIR, true, LLSD::TypeArray,
             boost::bind(&Validator::verifyVectorMinMax, _1, _2,
-                LLSD(LLSDArray(-20.0f)(-20.0f)),
-                LLSD(LLSDArray(20.0f)(20.0f)))));
+                llsd::array(-20.0f, -20.0f),
+                llsd::array(20.0f, 20.0f))));
     }
 
     return validation;
diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp
index 2a906c8d4118c004fbeeaf1d309743de429f69a2..b6cdcb2736ce8a31a34d1a09416a4257d5296d57 100644
--- a/indra/llmath/llvolume.cpp
+++ b/indra/llmath/llvolume.cpp
@@ -3351,12 +3351,12 @@ BOOL LLVolume::isFlat(S32 face)
 
 bool LLVolumeParams::isSculpt() const
 {
-	return mSculptID.notNull();
+    return (mSculptType & LL_SCULPT_TYPE_MASK) != LL_SCULPT_TYPE_NONE;
 }
 
 bool LLVolumeParams::isMeshSculpt() const
 {
-	return isSculpt() && ((mSculptType & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH);
+	return (mSculptType & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH;
 }
 
 bool LLVolumeParams::operator==(const LLVolumeParams &params) const
@@ -3771,6 +3771,7 @@ bool LLVolumeParams::validate(U8 prof_curve, F32 prof_begin, F32 prof_end, F32 h
 void LLVolume::getLoDTriangleCounts(const LLVolumeParams& params, S32* counts)
 { //attempt to approximate the number of triangles that will result from generating a volume LoD set for the 
 	//supplied LLVolumeParams -- inaccurate, but a close enough approximation for determining streaming cost
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME;
 	F32 detail[] = {1.f, 1.5f, 2.5f, 4.f};	
 	for (S32 i = 0; i < 4; i++)
 	{
diff --git a/indra/llprimitive/llgltfmaterial.h b/indra/llprimitive/llgltfmaterial.h
index 0bd65fadef40646fafe5452339ae740d261e989d..ad7784f6d1f41ca6874faf99b393f6f3f30ad89b 100644
--- a/indra/llprimitive/llgltfmaterial.h
+++ b/indra/llprimitive/llgltfmaterial.h
@@ -167,6 +167,7 @@ class LLGLTFMaterial : public LLRefCount
     
     // set the contents of this LLGLTFMaterial from the given json
     // returns true if successful
+    // if unsuccessful, the contents of this LLGLTFMaterial should be left unchanged and false is returned
     // json - the json text to load from
     // warn_msg - warning message from TinyGLTF if any
     // error_msg - error_msg from TinyGLTF if any
diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp
index 04ac2476a75a06a371d230714ec31e9a917dbe99..b7f08aa9af148d5546a9098e0561a1982b589bcc 100644
--- a/indra/llrender/llglslshader.cpp
+++ b/indra/llrender/llglslshader.cpp
@@ -243,9 +243,9 @@ void LLGLSLShader::stopProfile()
     }
 }
 
-void LLGLSLShader::placeProfileQuery()
+void LLGLSLShader::placeProfileQuery(bool for_runtime)
 {
-    if (sProfileEnabled)
+    if (sProfileEnabled || for_runtime)
     {
         if (mTimerQuery == 0)
         {
@@ -254,42 +254,70 @@ void LLGLSLShader::placeProfileQuery()
             glGenQueries(1, &mPrimitivesQuery);
         }
 
-        glBeginQuery(GL_SAMPLES_PASSED, mSamplesQuery);
         glBeginQuery(GL_TIME_ELAPSED, mTimerQuery);
-        glBeginQuery(GL_PRIMITIVES_GENERATED, mPrimitivesQuery);
+
+        if (!for_runtime)
+        {
+            glBeginQuery(GL_SAMPLES_PASSED, mSamplesQuery);
+            glBeginQuery(GL_PRIMITIVES_GENERATED, mPrimitivesQuery);
+        }
     }
 }
 
-void LLGLSLShader::readProfileQuery()
+bool LLGLSLShader::readProfileQuery(bool for_runtime, bool force_read)
 {
-    if (sProfileEnabled)
+    if (sProfileEnabled || for_runtime)
     {
-        glEndQuery(GL_TIME_ELAPSED);
-        glEndQuery(GL_SAMPLES_PASSED);
-        glEndQuery(GL_PRIMITIVES_GENERATED);
+        if (!mProfilePending)
+        {
+            glEndQuery(GL_TIME_ELAPSED);
+            if (!for_runtime)
+            {
+                glEndQuery(GL_SAMPLES_PASSED);
+                glEndQuery(GL_PRIMITIVES_GENERATED);
+            }
+            mProfilePending = for_runtime;
+        }
+
+        if (mProfilePending && for_runtime && !force_read)
+        {
+            GLuint64 result = 0;
+            glGetQueryObjectui64v(mTimerQuery, GL_QUERY_RESULT_AVAILABLE, &result);
+
+            if (result != GL_TRUE)
+            {
+                return false;
+            }
+        }
 
         GLuint64 time_elapsed = 0;
         glGetQueryObjectui64v(mTimerQuery, GL_QUERY_RESULT, &time_elapsed);
+        mTimeElapsed += time_elapsed;
+        mProfilePending = false;
 
-        GLuint64 samples_passed = 0;
-        glGetQueryObjectui64v(mSamplesQuery, GL_QUERY_RESULT, &samples_passed);
+        if (!for_runtime)
+        {
+            GLuint64 samples_passed = 0;
+            glGetQueryObjectui64v(mSamplesQuery, GL_QUERY_RESULT, &samples_passed);
 
-        U64 primitives_generated = 0;
-        glGetQueryObjectui64v(mPrimitivesQuery, GL_QUERY_RESULT, &primitives_generated);
-        sTotalTimeElapsed += time_elapsed;
-        mTimeElapsed += time_elapsed;
+            U64 primitives_generated = 0;
+            glGetQueryObjectui64v(mPrimitivesQuery, GL_QUERY_RESULT, &primitives_generated);
+            sTotalTimeElapsed += time_elapsed;
 
-        sTotalSamplesDrawn += samples_passed;
-        mSamplesDrawn += samples_passed;
+            sTotalSamplesDrawn += samples_passed;
+            mSamplesDrawn += samples_passed;
 
-        U32 tri_count = (U32)primitives_generated / 3;
+            U32 tri_count = (U32)primitives_generated / 3;
 
-        mTrianglesDrawn += tri_count;
-        sTotalTrianglesDrawn += tri_count;
+            mTrianglesDrawn += tri_count;
+            sTotalTrianglesDrawn += tri_count;
 
-        sTotalBinds++;
-        mBinds++;
+            sTotalBinds++;
+            mBinds++;
+        }
     }
+
+    return true;
 }
 
 
diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h
index 9d187c972c673f430b90ba45d6312374bb6bca09..3e7dae666981ffcf72569b6298dd852775c377d3 100644
--- a/indra/llrender/llglslshader.h
+++ b/indra/llrender/llglslshader.h
@@ -168,8 +168,16 @@ class LLGLSLShader
     void unload();
     void clearStats();
     void dumpStats();
-    void placeProfileQuery();
-    void readProfileQuery();
+
+    // place query objects for profiling if profiling is enabled
+    // if for_runtime is true, will place timer query only whether or not profiling is enabled
+    void placeProfileQuery(bool for_runtime = false);
+
+    // Readback query objects if profiling is enabled
+    // If for_runtime is true, will readback timer query iff query is available
+    // Will return false if a query is pending (try again later)
+    // If force_read is true, will force an immediate readback (severe performance penalty)
+    bool readProfileQuery(bool for_runtime = false, bool force_read = false);
 
     BOOL createShader(std::vector<LLStaticHashedString>* attributes,
         std::vector<LLStaticHashedString>* uniforms,
@@ -292,6 +300,7 @@ class LLGLSLShader
     defines_map_t mDefines;
 
     //statistics for profiling shader performance
+    bool mProfilePending = false;
     U32 mTimerQuery;
     U32 mSamplesQuery;
     U32 mPrimitivesQuery;
@@ -308,6 +317,9 @@ class LLGLSLShader
     // this pointer should be set to whichever shader represents this shader's rigged variant
     LLGLSLShader* mRiggedVariant = nullptr;
 
+    // hacky flag used for optimization in LLDrawPoolAlpha
+    bool mCanBindFast = false;
+
 #ifdef LL_PROFILER_ENABLE_RENDER_DOC
     void setLabel(const char* label);
 #endif
diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h
index 87fb9e363e6f739d9c504d48cae409d632aa205c..243aeaea257867546144cdcbfb74db0c0c71e713 100644
--- a/indra/llrender/llimagegl.h
+++ b/indra/llrender/llimagegl.h
@@ -340,7 +340,7 @@ class LLImageGLThread : public LLSimpleton<LLImageGLThread>, LL::ThreadPool
     template <typename CALLABLE>
     bool post(CALLABLE&& func)
     {
-        return getQueue().postIfOpen(std::forward<CALLABLE>(func));
+        return getQueue().post(std::forward<CALLABLE>(func));
     }
 
     void run() override;
diff --git a/indra/llrender/llrender.h b/indra/llrender/llrender.h
index 909a1de2b3fda0790cfd8f875b3ff95430fb9c3a..98141d71f5917d61ba68912300312778aca2399b 100644
--- a/indra/llrender/llrender.h
+++ b/indra/llrender/llrender.h
@@ -545,12 +545,7 @@ glh::matrix4f gl_ortho(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top,
 glh::matrix4f gl_perspective(GLfloat fovy, GLfloat aspect, GLfloat zNear, GLfloat zFar);
 glh::matrix4f gl_lookat(LLVector3 eye, LLVector3 center, LLVector3 up);
 
-#if LL_RELEASE_FOR_DOWNLOAD
-    #define LL_SHADER_LOADING_WARNS(...) LL_WARNS_ONCE("ShaderLoading")
-    #define LL_SHADER_UNIFORM_ERRS(...)  LL_WARNS_ONCE("Shader")
-#else
-    #define LL_SHADER_LOADING_WARNS(...) LL_WARNS()
-    #define LL_SHADER_UNIFORM_ERRS(...)  LL_ERRS("Shader")    
-#endif
+#define LL_SHADER_LOADING_WARNS(...) LL_WARNS()
+#define LL_SHADER_UNIFORM_ERRS(...)  LL_ERRS("Shader")
 
 #endif
diff --git a/indra/llrender/llrendertarget.cpp b/indra/llrender/llrendertarget.cpp
index 629664b76d51c858413e5644b8b330b2507898b1..88c48e51660b80ee53bc67a04889e2600cb69306 100644
--- a/indra/llrender/llrendertarget.cpp
+++ b/indra/llrender/llrendertarget.cpp
@@ -388,14 +388,14 @@ void LLRenderTarget::release()
         glBindFramebuffer(GL_FRAMEBUFFER, sCurFBO);
     }
 
-    if (mFBO == sCurFBO)
-    {
-        sCurFBO = 0;
-        glBindFramebuffer(GL_FRAMEBUFFER, 0);
-    }
-
     if (mFBO)
     {
+        if (mFBO == sCurFBO)
+        {
+            sCurFBO = 0;
+            glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        }
+
         glDeleteFramebuffers(1, (GLuint *) &mFBO);
         mFBO = 0;
     }
diff --git a/indra/llui/llscrolllistcell.cpp b/indra/llui/llscrolllistcell.cpp
index 8dd552d2adeae1e6c10c98bf29c5cc5337f8976b..f73c9aa5390b1368523abbc036c036c74f58ee5b 100644
--- a/indra/llui/llscrolllistcell.cpp
+++ b/indra/llui/llscrolllistcell.cpp
@@ -54,6 +54,10 @@ LLScrollListCell* LLScrollListCell::create(const LLScrollListCell::Params& cell_
 	{
 		cell = new LLScrollListIconText(cell_p);
 	}
+    else if (cell_p.type() == "bar")
+    {
+        cell = new LLScrollListBar(cell_p);
+    }
 	else	// default is "text"
 	{
 		cell = new LLScrollListText(cell_p);
@@ -173,6 +177,74 @@ void LLScrollListIcon::draw(const LLColor4& color, const LLColor4& highlight_col
 	}
 }
 
+//
+// LLScrollListBar
+//
+LLScrollListBar::LLScrollListBar(const LLScrollListCell::Params& p)
+    :	LLScrollListCell(p),
+    mRatio(0),
+    mColor(p.color),
+    mBottom(1),
+    mLeftPad(1),
+    mRightPad(1)
+{}
+
+LLScrollListBar::~LLScrollListBar()
+{
+}
+
+/*virtual*/
+S32 LLScrollListBar::getHeight() const
+{ 
+    return LLScrollListCell::getHeight();
+}
+
+/*virtual*/
+const LLSD LLScrollListBar::getValue() const
+{ 
+    return LLStringUtil::null; 
+}
+
+void LLScrollListBar::setValue(const LLSD& value)
+{
+    if (value.has("ratio"))
+    {
+        mRatio = value["ratio"].asReal();
+    }
+    if (value.has("bottom"))
+    {
+        mBottom = value["bottom"].asInteger();
+    }
+    if (value.has("left_pad"))
+    {
+        mLeftPad = value["left_pad"].asInteger();
+    }
+    if (value.has("right_pad"))
+    {
+        mRightPad = value["right_pad"].asInteger();
+    }
+}
+
+void LLScrollListBar::setColor(const LLColor4& color)
+{
+    mColor = color;
+}
+
+S32	LLScrollListBar::getWidth() const 
+{
+    return LLScrollListCell::getWidth();
+}
+
+
+void LLScrollListBar::draw(const LLColor4& color, const LLColor4& highlight_color)	 const
+{
+    S32 bar_width = getWidth() - mLeftPad - mRightPad;
+    S32 left = bar_width - bar_width * mRatio;
+    left = llclamp(left, mLeftPad, getWidth() - mRightPad - 1);
+
+    gl_rect_2d(left, mBottom, getWidth() - mRightPad, mBottom - 1, mColor);
+}
+
 //
 // LLScrollListText
 //
diff --git a/indra/llui/llscrolllistcell.h b/indra/llui/llscrolllistcell.h
index ede8d847d9e9f73ba16a5d7ba50ab47101cbe95e..2588da2331e899c67617a877797c5f06e90626d4 100644
--- a/indra/llui/llscrolllistcell.h
+++ b/indra/llui/llscrolllistcell.h
@@ -33,6 +33,7 @@
 #include "lluistring.h"
 #include "v4color.h"
 #include "llui.h"
+#include "llgltexture.h"
 
 class LLCheckBoxCtrl;
 class LLSD;
@@ -159,6 +160,7 @@ class LLScrollListText : public LLScrollListCell
 
 	void			setText(const LLStringExplicit& text);
 	void			setFontStyle(const U8 font_style);
+    void			setAlignment(LLFontGL::HAlign align) { mFontAlignment = align; }
 
 protected:
 	LLUIString		mText;
@@ -199,6 +201,26 @@ class LLScrollListIcon : public LLScrollListCell
 	LLFontGL::HAlign		mAlignment;
 };
 
+
+class LLScrollListBar : public LLScrollListCell
+{
+public:
+    LLScrollListBar(const LLScrollListCell::Params& p);
+    /*virtual*/ ~LLScrollListBar();
+    /*virtual*/ void	draw(const LLColor4& color, const LLColor4& highlight_color) const;
+    /*virtual*/ S32		getWidth() const;
+    /*virtual*/ S32		getHeight() const;
+    /*virtual*/ const LLSD		getValue() const;
+    /*virtual*/ void	setColor(const LLColor4&);
+    /*virtual*/ void	setValue(const LLSD& value);
+
+private:
+    LLColor4                    mColor;
+    F32                         mRatio;
+    S32                         mBottom;
+    S32                         mRightPad;
+    S32                         mLeftPad;
+};
 /*
  * An interactive cell containing a check box.
  */
diff --git a/indra/llwindow/llwindow.cpp b/indra/llwindow/llwindow.cpp
index 66be3efffceb8744b072da0205e3fe7179ae95f1..9e281dfc99c93be3671eee793169c957f58e9aac 100644
--- a/indra/llwindow/llwindow.cpp
+++ b/indra/llwindow/llwindow.cpp
@@ -117,7 +117,8 @@ LLWindow::LLWindow(LLWindowCallbacks* callbacks, BOOL fullscreen, U32 flags)
 	  mSwapMethod(SWAP_METHOD_UNDEFINED),
 	  mHideCursorPermanent(FALSE),
 	  mFlags(flags),
-	  mHighSurrogate(0)
+	  mHighSurrogate(0),
+	  mRefreshRate(0)
 {
 }
 
diff --git a/indra/llwindow/llwindow.h b/indra/llwindow/llwindow.h
index 862897a48c995ae8294c905aae94a49c0ded198a..b1408d894f192eade480825cddb063df68d55e57 100644
--- a/indra/llwindow/llwindow.h
+++ b/indra/llwindow/llwindow.h
@@ -199,6 +199,8 @@ class LLWindow : public LLInstanceTracker<LLWindow>
     // windows only DirectInput8 for joysticks
     virtual void* getDirectInput8() { return NULL; };
     virtual bool getInputDevices(U32 device_type_filter, void * devices_callback, void* userdata) { return false; };
+
+    virtual S32 getRefreshRate() { return mRefreshRate; }
 protected:
 	LLWindow(LLWindowCallbacks* callbacks, BOOL fullscreen, U32 flags);
 	virtual ~LLWindow();
@@ -232,6 +234,7 @@ class LLWindow : public LLInstanceTracker<LLWindow>
 	U16			mHighSurrogate;
 	S32			mMinWindowWidth;
 	S32			mMinWindowHeight;
+    S32         mRefreshRate;
 
  	// Handle a UTF-16 encoding unit received from keyboard.
  	// Converting the series of UTF-16 encoding units to UTF-32 data,
diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp
index aae6539685cfd0aedf8ec94d11c1bcc7ba9be3fd..8bfaeca614ede5fce6b8d56574f8bf66d694fa21 100644
--- a/indra/llwindow/llwindowmacosx.cpp
+++ b/indra/llwindow/llwindowmacosx.cpp
@@ -49,6 +49,8 @@ BOOL gHiDPISupport = TRUE;
 const S32	BITS_PER_PIXEL = 32;
 const S32	MAX_NUM_RESOLUTIONS = 32;
 
+const S32   DEFAULT_REFRESH_RATE = 60;
+
 namespace
 {
     NSKeyEventRef mRawKeyEvent = NULL;
@@ -654,6 +656,13 @@ BOOL LLWindowMacOSX::createContext(int x, int y, int width, int height, int bits
 		}
 	}
 
+    mRefreshRate = CGDisplayModeGetRefreshRate(CGDisplayCopyDisplayMode(mDisplay));
+    if(mRefreshRate == 0)
+    {
+        //consider adding more appropriate fallback later
+        mRefreshRate = DEFAULT_REFRESH_RATE;
+    }
+
 	// Disable vertical sync for swap
     toggleVSync(enable_vsync);
 
diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp
index 2bdfaf57dea2a6621ddf2f66ed3b2b842fef0169..1f91cbbaa8bf100502b2ecc75cb4fdc7f902e0bf 100644
--- a/indra/llwindow/llwindowwin32.cpp
+++ b/indra/llwindow/llwindowwin32.cpp
@@ -370,15 +370,10 @@ struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool
     template <typename CALLABLE>
     void post(CALLABLE&& func)
     {
-        try
-        {
-            getQueue().post(std::forward<CALLABLE>(func));
-        }
-        catch (const LLThreadSafeQueueInterrupt&)
-        {
-            // Shutdown timing is tricky. The main thread can end up trying
-            // to post a cursor position after having closed the WorkQueue.
-        }
+        // Ignore bool return. Shutdown timing is tricky: the main thread can
+        // end up trying to post a cursor position after having closed the
+        // WorkQueue.
+        getQueue().post(std::forward<CALLABLE>(func));
     }
 
     /**
@@ -691,7 +686,7 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
 	{
 		current_refresh = 60;
 	}
-
+    mRefreshRate = current_refresh;
 	//-----------------------------------------------------------------------
 	// Drop resolution and go fullscreen
 	// use a display mode with our desired size and depth, with a refresh
@@ -1172,6 +1167,7 @@ BOOL LLWindowWin32::switchContext(BOOL fullscreen, const LLCoordScreen& size, BO
     {
         current_refresh = 60;
     }
+    mRefreshRate = current_refresh;
 
     gGLManager.shutdownGL();
     //destroy gl context
@@ -2280,13 +2276,8 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
     ASSERT_WINDOW_THREAD();
     LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32;
 
-    LL_DEBUGS("Window") << "mainWindowProc(" << std::hex << h_wnd
-                        << ", " << u_msg
-                        << ", " << w_param << ")" << std::dec << LL_ENDL;
-
     if (u_msg == WM_POST_FUNCTION_)
     {
-        LL_DEBUGS("Window") << "WM_POST_FUNCTION_" << LL_ENDL;
         // from LLWindowWin32Thread::Post()
         // Cast l_param back to the pointer to the heap FuncType
         // allocated by Post(). Capture in unique_ptr so we'll delete
@@ -2307,8 +2298,6 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
 
     LLWindowWin32* window_imp = (LLWindowWin32*)GetWindowLongPtr(h_wnd, GWLP_USERDATA);
 
-    bool debug_window_proc = false; // gDebugWindowProc || debugLoggingEnabled("Window");
-
     if (NULL != window_imp)
     {
         // Juggle to make sure we can get negative positions for when
@@ -2335,11 +2324,6 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
         case WM_DEVICECHANGE:
         {
             LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_DEVICECHANGE");
-            if (debug_window_proc)
-            {
-                LL_INFOS("Window") << "  WM_DEVICECHANGE: wParam=" << w_param
-                    << "; lParam=" << l_param << LL_ENDL;
-            }
             if (w_param == DBT_DEVNODES_CHANGED || w_param == DBT_DEVICEARRIVAL)
             {
                 WINDOW_IMP_POST(window_imp->mCallbacks->handleDeviceChange(window_imp));
@@ -2401,16 +2385,6 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
                 {
                     // This message should be sent whenever the app gains or loses focus.
                     BOOL activating = (BOOL)w_param;
-                    BOOL minimized = window_imp->getMinimized();
-
-                    if (debug_window_proc)
-                    {
-                        LL_INFOS("Window") << "WINDOWPROC ActivateApp "
-                            << " activating " << S32(activating)
-                            << " minimized " << S32(minimized)
-                            << " fullscreen " << S32(window_imp->mFullscreen)
-                            << LL_ENDL;
-                    }
 
                     if (window_imp->mFullscreen)
                     {
@@ -2445,23 +2419,10 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
                     // Can be one of WA_ACTIVE, WA_CLICKACTIVE, or WA_INACTIVE
                     BOOL activating = (LOWORD(w_param) != WA_INACTIVE);
 
-                    BOOL minimized = BOOL(HIWORD(w_param));
-
                     if (!activating && LLWinImm::isAvailable() && window_imp->mPreeditor)
                     {
                         window_imp->interruptLanguageTextInput();
                     }
-
-                    // JC - I'm not sure why, but if we don't report that we handled the 
-                    // WM_ACTIVATE message, the WM_ACTIVATEAPP messages don't work 
-                    // properly when we run fullscreen.
-                    if (debug_window_proc)
-                    {
-                        LL_INFOS("Window") << "WINDOWPROC Activate "
-                            << " activating " << S32(activating)
-                            << " minimized " << S32(minimized)
-                            << LL_ENDL;
-                    }
                 });
             
             break;
@@ -2539,16 +2500,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
                     window_imp->mRawWParam = w_param;
                     window_imp->mRawLParam = l_param;
 
-                    {
-                        if (debug_window_proc)
-                        {
-                            LL_INFOS("Window") << "Debug WindowProc WM_KEYDOWN "
-                                << " key " << S32(w_param)
-                                << LL_ENDL;
-                        }
-                        
-                        gKeyboard->handleKeyDown(w_param, mask);
-                    }
+                    gKeyboard->handleKeyDown(w_param, mask);
                 });
             if (eat_keystroke) return 0;    // skip DefWindowProc() handling if we're consuming the keypress 
             break;
@@ -2568,14 +2520,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
                 window_imp->mRawLParam = l_param;
 
                 {
-                    LL_RECORD_BLOCK_TIME(FTM_KEYHANDLER);
-
-                    if (debug_window_proc)
-                    {
-                        LL_INFOS("Window") << "Debug WindowProc WM_KEYUP "
-                            << " key " << S32(w_param)
-                            << LL_ENDL;
-                    }
+                    LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_KEYUP");
                     gKeyboard->handleKeyUp(w_param, mask);
                 }
             });
@@ -2585,10 +2530,6 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
         case WM_IME_SETCONTEXT:
         {
             LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_IME_SETCONTEXT");
-            if (debug_window_proc)
-            {
-                LL_INFOS("Window") << "WM_IME_SETCONTEXT" << LL_ENDL;
-            }
             if (LLWinImm::isAvailable() && window_imp->mPreeditor)
             {
                 l_param &= ~ISC_SHOWUICOMPOSITIONWINDOW;
@@ -2599,10 +2540,6 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
         case WM_IME_STARTCOMPOSITION:
         {
             LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_IME_STARTCOMPOSITION");
-            if (debug_window_proc)
-            {
-                LL_INFOS() << "WM_IME_STARTCOMPOSITION" << LL_ENDL;
-            }
             if (LLWinImm::isAvailable() && window_imp->mPreeditor)
             {
                 WINDOW_IMP_POST(window_imp->handleStartCompositionMessage());
@@ -2613,10 +2550,6 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
         case WM_IME_ENDCOMPOSITION:
         {
             LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_IME_ENDCOMPOSITION");
-            if (debug_window_proc)
-            {
-                LL_INFOS() << "WM_IME_ENDCOMPOSITION" << LL_ENDL;
-            }
             if (LLWinImm::isAvailable() && window_imp->mPreeditor)
             {
                 return 0;
@@ -2626,10 +2559,6 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
         case WM_IME_COMPOSITION:
         {
             LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_IME_COMPOSITION");
-            if (debug_window_proc)
-            {
-                LL_INFOS() << "WM_IME_COMPOSITION" << LL_ENDL;
-            }
             if (LLWinImm::isAvailable() && window_imp->mPreeditor)
             {
                 WINDOW_IMP_POST(window_imp->handleCompositionMessage(l_param));
@@ -2640,10 +2569,6 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
         case WM_IME_REQUEST:
         {
             LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_IME_REQUEST");
-            if (debug_window_proc)
-            {
-                LL_INFOS() << "WM_IME_REQUEST" << LL_ENDL;
-            }
             if (LLWinImm::isAvailable() && window_imp->mPreeditor)
             {
                 LRESULT result;
@@ -2672,12 +2597,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
                     // it is worth trying.  The good old WM_CHAR works just fine even for supplementary
                     // characters.  We just need to take care of surrogate pairs sent as two WM_CHAR's
                     // by ourselves.  It is not that tough.  -- Alissa Sabre @ SL
-                    if (debug_window_proc)
-                    {
-                        LL_INFOS("Window") << "Debug WindowProc WM_CHAR "
-                            << " key " << S32(w_param)
-                            << LL_ENDL;
-                    }
+                    
                     // Even if LLWindowCallbacks::handleUnicodeChar(llwchar, BOOL) returned FALSE,
                     // we *did* processed the event, so I believe we should not pass it to DefWindowProc...
                     window_imp->handleUnicodeUTF16((U16)w_param, gKeyboard->currentMask(FALSE));
@@ -3001,23 +2921,6 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
         {
             LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_SIZE");
             window_imp->updateWindowRect();
-            S32 width = S32(LOWORD(l_param));
-            S32 height = S32(HIWORD(l_param));
-
-            
-            if (debug_window_proc)
-            {
-                BOOL maximized = (w_param == SIZE_MAXIMIZED);
-                BOOL restored = (w_param == SIZE_RESTORED);
-                BOOL minimized = (w_param == SIZE_MINIMIZED);
-
-                LL_INFOS("Window") << "WINDOWPROC Size "
-                    << width << "x" << height
-                    << " max " << S32(maximized)
-                    << " min " << S32(minimized)
-                    << " rest " << S32(restored)
-                    << LL_ENDL;
-            }
 
             // There's an odd behavior with WM_SIZE that I would call a bug. If 
             // the window is maximized, and you call MoveWindow() with a size smaller
@@ -3083,10 +2986,6 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
         case WM_SETFOCUS:
         {
             LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_SETFOCUS");
-            if (debug_window_proc)
-            {
-                LL_INFOS("Window") << "WINDOWPROC SetFocus" << LL_ENDL;
-            }
             WINDOW_IMP_POST(window_imp->mCallbacks->handleFocus(window_imp));
             return 0;
         }
@@ -3094,10 +2993,6 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
         case WM_KILLFOCUS:
         {
             LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_KILLFOCUS");
-            if (debug_window_proc)
-            {
-                LL_INFOS("Window") << "WINDOWPROC KillFocus" << LL_ENDL;
-            }
             WINDOW_IMP_POST(window_imp->mCallbacks->handleFocusLost(window_imp));
             return 0;
         }
@@ -3218,10 +3113,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
         default:
         {
             LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - default");
-            if (debug_window_proc)
-            {
-                LL_INFOS("Window") << "Unhandled windows message code: 0x" << std::hex << U32(u_msg) << LL_ENDL;
-            }
+            LL_DEBUGS("Window") << "Unhandled windows message code: 0x" << std::hex << U32(u_msg) << LL_ENDL;
         }
         break;
         }
@@ -4897,8 +4789,6 @@ void LLWindowWin32::LLWindowWin32Thread::updateVRAMUsage()
         { // current usage is sometimes unreliable on Intel GPUs, fall back to estimated usage
             cu_mb = llmax((U32)1, eu_mb);
         }
-        F32 eu_error = (F32)((S32)eu_mb - (S32)cu_mb) / (F32)cu_mb;
-
         U32 target_mb = budget_mb;
 
         if (target_mb > 4096)  // if 4GB are installed, try to leave 2GB free 
@@ -4912,6 +4802,9 @@ void LLWindowWin32::LLWindowWin32Thread::updateVRAMUsage()
 
         mAvailableVRAM = cu_mb < target_mb ? target_mb - cu_mb : 0;
 
+#if 0
+        
+        F32 eu_error = (F32)((S32)eu_mb - (S32)cu_mb) / (F32)cu_mb;
         LL_INFOS("Window") << "\nLocal\nAFR: " << info.AvailableForReservation / 1024 / 1024
             << "\nBudget: " << info.Budget / 1024 / 1024
             << "\nCR: " << info.CurrentReservation / 1024 / 1024
@@ -4919,12 +4812,7 @@ void LLWindowWin32::LLWindowWin32Thread::updateVRAMUsage()
             << "\nEU: " << eu_mb << llformat(" (%.2f)", eu_error)
             << "\nTU: " << target_mb
             << "\nAM: " << mAvailableVRAM << LL_ENDL;
-
-        /*mDXGIAdapter->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL, &info);
-        LL_INFOS("Window") << "\nNon-Local\nAFR: " << info.AvailableForReservation / 1024 / 1024
-            << "\nBudget: " << info.Budget / 1024 / 1024
-            << "\nCR: " << info.CurrentReservation / 1024 / 1024
-            << "\nCU: " << info.CurrentUsage / 1024 / 1024 << LL_ENDL;*/
+#endif
     }
     else if (mD3DDevice != NULL)
     { // fallback to D3D9
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 3bc636bac450e7f24e7cedc921f9c56643680a0c..c6d82ea260ccdd15a5a27666441942984c056bbc 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -257,10 +257,12 @@ set(viewer_SOURCE_FILES
     llfloaterpathfindinglinksets.cpp
     llfloaterpathfindingobjects.cpp
     llfloaterpay.cpp
+    llfloaterperformance.cpp
     llfloaterperms.cpp
     llfloaterpostprocess.cpp
     llfloaterprofile.cpp
     llfloaterpreference.cpp
+    llfloaterpreferencesgraphicsadvanced.cpp
     llfloaterpreferenceviewadvanced.cpp
     llfloaterpreviewtrash.cpp
     llfloaterprofiletexture.cpp
@@ -904,10 +906,12 @@ set(viewer_HEADER_FILES
     llfloaterpathfindinglinksets.h
     llfloaterpathfindingobjects.h
     llfloaterpay.h
+    llfloaterperformance.h
     llfloaterperms.h
     llfloaterpostprocess.h
     llfloaterprofile.h
     llfloaterpreference.h
+    llfloaterpreferencesgraphicsadvanced.h
     llfloaterpreferenceviewadvanced.h
     llfloaterpreviewtrash.h
     llfloaterprofiletexture.h
@@ -1350,6 +1354,9 @@ set(viewer_HEADER_FILES
     VorbisFramework.h
     )
 
+  list(APPEND viewer_SOURCE_FILES llperfstats.cpp)
+  list(APPEND viewer_HEADER_FILES llperfstats.h)
+
 source_group("CMake Rules" FILES ViewerInstall.cmake)
 
 #build_data.json creation moved to viewer_manifest.py MAINT-6413
diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml
index 2644f5f449c74813797aa06d771ae67254aeeb12..4a3dfffde19c3f97ca346d83de27d9fa42cfdc4c 100644
--- a/indra/newview/app_settings/commands.xml
+++ b/indra/newview/app_settings/commands.xml
@@ -285,4 +285,14 @@
          is_running_function="Floater.IsOpen"
          is_running_parameters="360capture"
            />
+  <command name="performance"
+         available_in_toybox="true"
+         icon="Command_Performance_Icon"
+         label_ref="Command_Performance_Label"
+         tooltip_ref="Command_Performance_Tooltip"
+         execute_function="Floater.ToggleOrBringToFront"
+         execute_parameters="performance"
+         is_running_function="Floater.IsOpen"
+         is_running_parameters="performance"
+           />
 </commands>
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 998d139bf1592827c2c41c0c5f05b57658369466..df36c51f55d183dbaa94720e11c5c2a8f097baef 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -1426,7 +1426,7 @@
       <key>Type</key>
       <string>U32</string>
       <key>Value</key>
-      <integer>1024</integer>
+      <integer>4096</integer>
     </map>
     <key>CacheValidateCounter</key>
     <map>
@@ -8828,6 +8828,17 @@
       <key>Value</key>
       <integer>0</integer>
     </map>
+  <key>RenderClass1MemoryBandwidth</key>
+  <map>
+    <key>Comment</key>
+    <string>Memory bandwidth at which to default to Class 1 in gigabytes per second.  Used as basis for higher classes.</string>
+    <key>Persist</key>
+    <integer>1</integer>
+    <key>Type</key>
+    <string>F32</string>
+    <key>Value</key>
+    <real>32.0</real>
+  </map>
   <key>RenderCloudShadowAmbianceFactor</key>
   <map>
     <key>Comment</key>
@@ -8839,6 +8850,17 @@
     <key>Value</key>
     <real>0.1</real>
   </map>
+  <key>RenderCPUBasis</key>
+  <map>
+    <key>Comment</key>
+    <string>Reference CPU clockspeed to use to bias GPU class (in MHz).</string>
+    <key>Persist</key>
+    <integer>1</integer>
+    <key>Type</key>
+    <string>F32</string>
+    <key>Value</key>
+    <real>3000.0</real>
+  </map>
   <key>RenderComplexityColorMin</key>
     <map>
       <key>Comment</key>
@@ -9061,6 +9083,17 @@
     <key>Value</key>
     <real>0.5</real>
   </map>
+  <key>RenderShadowSplits</key>
+  <map>
+    <key>Comment</key>
+    <string>Amount of shadow map splits to render (0 - 3).</string>
+    <key>Persist</key>
+    <integer>1</integer>
+    <key>Type</key>
+    <string>S32</string>
+    <key>Value</key>
+    <integer>3</integer>
+  </map>
   <key>RenderSSAOScale</key>
   <map>
     <key>Comment</key>
@@ -9252,6 +9285,17 @@
       <key>Value</key>
       <integer>0</integer>
     </map>
+  <key>RenderDisablePostProcessing</key>
+  <map>
+    <key>Comment</key>
+    <string>Disable tone mapping and exposure correction when build floater is open (for artists developing materials)</string>
+    <key>Persist</key>
+    <integer>0</integer>
+    <key>Type</key>
+    <string>Boolean</string>
+    <key>Value</key>
+    <integer>0</integer>
+  </map>
   <key>RenderMaxOpenGLVersion</key>
   <map>
     <key>Comment</key>
@@ -16887,6 +16931,149 @@
       <key>Value</key>
       <integer>0</integer>
     </map>
+  <key>TargetFPS</key>
+  <map>
+    <key>Comment</key>
+    <string>Desired minimum FPS</string>
+    <key>Persist</key>
+    <integer>1</integer>
+    <key>Type</key>
+    <string>U32</string>
+    <key>Value</key>
+    <integer>15</integer>
+  </map>
+  <key>AutoTuneFPS</key>
+  <map>
+    <key>Comment</key>
+    <string>Allow the viewer to adjust your settings to achieve target FPS</string>
+    <key>Persist</key>
+    <integer>1</integer>
+    <key>Type</key>
+    <string>Boolean</string>
+    <key>Value</key>
+    <integer>0</integer>
+  </map>
+  <key>AutoTuneLock</key>
+  <map>
+    <key>Comment</key>
+    <string>When enabled the viewer will dynamically change settings until auto tune is explicitly turned off.</string>
+    <key>Persist</key>
+    <integer>1</integer>
+    <key>Type</key>
+    <string>Boolean</string>
+    <key>Value</key>
+    <integer>0</integer>
+  </map>
+  <key>KeepAutoTuneLock</key>
+  <map>
+    <key>Comment</key>
+    <string>When enabled the AutoTuneLock will be maintainted all following sessions.</string>
+    <key>Persist</key>
+    <integer>1</integer>
+    <key>Type</key>
+    <string>U32</string>
+    <key>Value</key>
+    <integer>1</integer>
+  </map>
+  <key>AllowSelfImpostor</key>
+  <map>
+    <key>Comment</key>
+    <string>Allow own render time to impostor your avatar.</string>
+    <key>Persist</key>
+    <integer>1</integer>
+    <key>Type</key>
+    <string>Boolean</string>
+    <key>Value</key>
+    <integer>0</integer>
+  </map>
+  <key>ShowTunedART</key>
+  <map>
+    <key>Comment</key>
+    <string>Show the current render time not the pre-tuning render time in the avatar display.</string>
+    <key>Persist</key>
+    <integer>1</integer>
+    <key>Type</key>
+    <string>Boolean</string>
+    <key>Value</key>
+    <integer>1</integer>
+  </map>
+  <key>RenderAvatarMaxART</key>
+  <map>
+    <key>Comment</key>
+    <string>Render Time Limit in microseconds (0.0 = no limit)</string>
+    <key>Persist</key>
+    <integer>0</integer>
+    <key>Type</key>
+    <string>F32</string>
+    <key>Value</key>
+    <real>4.699</real>
+  </map>
+  <key>AutoTuneRenderFarClipMin</key>
+  <map>
+    <key>Comment</key>
+    <string>The lowest draw distance that auto tune is allowed to use</string>
+    <key>Persist</key>
+    <integer>0</integer>
+    <key>Type</key>
+    <string>F32</string>
+    <key>Value</key>
+    <real>32.0</real>
+  </map>
+  <key>AutoTuneRenderFarClipTarget</key>
+  <map>
+    <key>Comment</key>
+    <string>The draw distance that auto tune will try to achieve</string>
+    <key>Persist</key>
+    <integer>0</integer>
+    <key>Type</key>
+    <string>F32</string>
+    <key>Value</key>
+    <real>256.0</real>
+  </map>
+  <key>PerfStatsCaptureEnabled</key>
+  <map>
+    <key>Comment</key>
+    <string>Enable/disable render time data to support autotune.</string>
+    <key>Persist</key>
+    <integer>1</integer>
+    <key>Type</key>
+    <string>Boolean</string>
+    <key>Value</key>
+    <integer>1</integer>
+  </map>
+  <key>AutoTuneImpostorByDistEnabled</key>
+  <map>
+    <key>Comment</key>
+    <string>Enable/disable using MaxNonImpostor to limit avatar rendering by distance.</string>
+    <key>Persist</key>
+    <integer>1</integer>
+    <key>Type</key>
+    <string>Boolean</string>
+    <key>Value</key>
+    <integer>0</integer>
+  </map>
+  <key>AutoTuneImpostorFarAwayDistance</key>
+  <map>
+    <key>Comment</key>
+    <string>Avatars beyond this range will automatically be optimized</string>
+    <key>Persist</key>
+    <integer>0</integer>
+    <key>Type</key>
+    <string>F32</string>
+    <key>Value</key>
+    <real>64.0</real>
+  </map>
+  <key>TuningFPSStrategy</key>
+  <map>
+    <key>Comment</key>
+    <string>Strategy to use when tuning FPS. 0=Tune avatar rendering only, 1=Tune both avatar and global scene settings, 2=Tune only global scene.</string>
+    <key>Persist</key>
+    <integer>1</integer>
+    <key>Type</key>
+    <string>U32</string>
+    <key>Value</key>
+    <integer>1</integer>
+  </map>
   <key>CameraOpacity</key>
   <map>
     <key>Comment</key>
diff --git a/indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskF.glsl b/indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskF.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..5ef9bb68059b77cb21d523a8aa3034ee8f4191b0
--- /dev/null
+++ b/indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskF.glsl
@@ -0,0 +1,50 @@
+/** 
+ * @file pbrShadowAlphaMaskF.glsl
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, 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$
+ */
+
+#ifdef DEFINE_GL_FRAGCOLOR
+out vec4 frag_color;
+#else
+#define frag_color gl_FragColor
+#endif
+
+uniform sampler2D diffuseMap;
+
+in vec4 post_pos;
+in float target_pos_x;
+in vec4 vertex_color;
+in vec2 vary_texcoord0;
+uniform float minimum_alpha;
+
+void main() 
+{
+    float alpha = diffuseLookup(vary_texcoord0.xy).a;
+
+    if (alpha < minimum_alpha)
+    {
+        discard;
+    }
+
+    frag_color = vec4(1,1,1,1);
+}
diff --git a/indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskV.glsl b/indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskV.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..4fb5fbcf06a25743d4872823a73ff88d626379f4
--- /dev/null
+++ b/indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskV.glsl
@@ -0,0 +1,75 @@
+/** 
+ * @file pbrShadowAlphaMaskV.glsl
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, 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$
+ */
+
+uniform mat4 texture_matrix0;
+#if defined(HAS_SKIN)
+uniform mat4 modelview_matrix;
+uniform mat4 projection_matrix;
+mat4 getObjectSkinnedTransform();
+#else
+uniform mat4 modelview_projection_matrix;
+#endif
+
+uniform vec4[2] texture_base_color_transform;
+vec2 texture_transform(vec2 vertex_texcoord, vec4[2] khr_gltf_transform, mat4 sl_animation_transform);
+
+uniform float shadow_target_width;
+
+in vec3 position;
+in vec4 diffuse_color;
+in vec2 texcoord0;
+
+out vec4 post_pos;
+out float target_pos_x;
+out vec4 vertex_color;
+out vec2 vary_texcoord0;
+
+void passTextureIndex();
+
+void main()
+{
+    //transform vertex
+#if defined(HAS_SKIN)
+    vec4 pre_pos = vec4(position.xyz, 1.0);
+    mat4 mat = getObjectSkinnedTransform();
+    mat = modelview_matrix * mat;
+    vec4 pos = mat * pre_pos;
+    pos = projection_matrix * pos;
+#else
+    vec4 pre_pos = vec4(position.xyz, 1.0);
+    vec4 pos = modelview_projection_matrix * pre_pos;
+#endif
+
+    target_pos_x = 0.5 * (shadow_target_width - 1.0) * pos.x;
+
+    post_pos = pos;
+
+    gl_Position = pos;
+    
+    passTextureIndex();
+
+    vary_texcoord0 = texture_transform(texcoord0, texture_base_color_transform, texture_matrix0);
+    vertex_color = diffuse_color;
+}
diff --git a/indra/newview/app_settings/shaders/class1/deferred/postDeferredGammaCorrect.glsl b/indra/newview/app_settings/shaders/class1/deferred/postDeferredGammaCorrect.glsl
index 82d7bb983533f5c251f53c738fd150400f8cf78c..80816912daaad0b540c5edfc3d15ff5992596560 100644
--- a/indra/newview/app_settings/shaders/class1/deferred/postDeferredGammaCorrect.glsl
+++ b/indra/newview/app_settings/shaders/class1/deferred/postDeferredGammaCorrect.glsl
@@ -104,20 +104,20 @@ vec3 toneMapACES_Hill(vec3 color)
 uniform float exposure;
 uniform float gamma;
 
-vec3 legacy_adjust_post(vec3 c);
-
-vec3 toneMap(vec3 color, float gs)
+vec3 toneMap(vec3 color)
 {
+#ifndef NO_POST
     float exp_scale = texture(exposureMap, vec2(0.5,0.5)).r;
 
-    color *= exposure * exp_scale * gs;
+    color *= exposure * exp_scale;
 
     color = toneMapACES_Hill(color);
+#else
+    color *= 0.6;
+#endif
 
     color = linear_to_srgb(color);
 
-    color = legacy_adjust_post(color);
-
     return color;
 }
 
@@ -170,22 +170,19 @@ vec3 legacyGamma(vec3 color)
     return color;
 }
 
-float legacyGammaApprox()
-{
-    //TODO -- figure out how to plumb this in as a uniform
-    float c = 0.5;
-    float gc = 1.0-pow(c, gamma);
-    
-    return gc/c * gamma;
-}
-
 void main() 
 {
     //this is the one of the rare spots where diffuseRect contains linear color values (not sRGB)
     vec4 diff = texture2D(diffuseRect, vary_fragcoord);
 
-    diff.rgb = toneMap(diff.rgb, legacyGammaApprox());
-    
+    diff.rgb = toneMap(diff.rgb);
+
+#ifdef LEGACY_GAMMA
+#ifndef NO_POST
+    diff.rgb = legacyGamma(diff.rgb);
+#endif
+#endif
+
     vec2 tc = vary_fragcoord.xy*screen_res*4.0;
     vec3 seed = (diff.rgb+vec3(1.0))*vec3(tc.xy, tc.x+tc.y);
     vec3 nz = vec3(noise(seed.rg), noise(seed.gb), noise(seed.rb));
diff --git a/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskF.glsl b/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskF.glsl
index eb2ba68415dc2b8908bb198ffcc9e03678716321..8c9b6f81909082789ce8cbb7b152056b16491864 100644
--- a/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskF.glsl
+++ b/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskF.glsl
@@ -48,8 +48,6 @@ void main()
         discard;
     }
 
-#if !defined(GLTF)
-
 #if !defined(IS_FULLBRIGHT)
     alpha *= vertex_color.a;
 #endif
@@ -66,7 +64,6 @@ void main()
             discard;
         }
     }
-#endif
 
 	frag_color = vec4(1,1,1,1);
 }
diff --git a/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskV.glsl b/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskV.glsl
index 72a07fa3d01040be816f69a30595f96faac628af..26db1a5d4dbfbd33d16fd9b16211f5715be50d82 100644
--- a/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskV.glsl
+++ b/indra/newview/app_settings/shaders/class1/deferred/shadowAlphaMaskV.glsl
@@ -32,9 +32,6 @@ mat4 getObjectSkinnedTransform();
 uniform mat4 modelview_projection_matrix;
 #endif
 
-uniform vec4[2] texture_base_color_transform;
-vec2 texture_transform(vec2 vertex_texcoord, vec4[2] khr_gltf_transform, mat4 sl_animation_transform);
-
 uniform float shadow_target_width;
 
 in vec3 position;
@@ -70,6 +67,6 @@ void main()
 	
 	passTextureIndex();
 
-	vary_texcoord0 = texture_transform(texcoord0, texture_base_color_transform, texture_matrix0);
+	vary_texcoord0 = (texture_matrix0 * vec4(texcoord0,0,1)).xy;
 	vertex_color = diffuse_color;
 }
diff --git a/indra/newview/app_settings/shaders/class1/environment/srgbF.glsl b/indra/newview/app_settings/shaders/class1/environment/srgbF.glsl
index 35f2395ef1602cf4c06039463fb1acb9179177c0..3817633df03b58fe3f9e719471d081fe2b9b40a5 100644
--- a/indra/newview/app_settings/shaders/class1/environment/srgbF.glsl
+++ b/indra/newview/app_settings/shaders/class1/environment/srgbF.glsl
@@ -128,8 +128,3 @@ vec3 legacy_adjust_fullbright(vec3 c)
     return c / exp_scale * 1.34;
 }
 
-
-vec3 legacy_adjust_post(vec3 c)
-{
-    return c;
-}
diff --git a/indra/newview/app_settings/shaders/class3/deferred/reflectionProbeF.glsl b/indra/newview/app_settings/shaders/class3/deferred/reflectionProbeF.glsl
index 35563713f5cd9f35189cb45d326dbd8774dd8bfb..53e15bd1a195102f26333e4c3eb89c9e7d47aadc 100644
--- a/indra/newview/app_settings/shaders/class3/deferred/reflectionProbeF.glsl
+++ b/indra/newview/app_settings/shaders/class3/deferred/reflectionProbeF.glsl
@@ -47,7 +47,11 @@ layout (std140) uniform ReflectionProbes
     mat4 refBox[MAX_REFMAP_COUNT];
     // list of bounding spheres for reflection probes sorted by distance to camera (closest first)
     vec4 refSphere[MAX_REFMAP_COUNT];
-    // extra parameters (currently only .x used for probe ambiance)
+    // extra parameters 
+    //  x - irradiance scale
+    //  y - radiance scale
+    //  z - fade in
+    //  w - znear
     vec4 refParams[MAX_REFMAP_COUNT];
     // index  of cube map in reflectionProbes for a corresponding reflection probe
     // e.g. cube map channel of refSphere[2] is stored in refIndex[2]
@@ -60,6 +64,8 @@ layout (std140) uniform ReflectionProbes
     // neighbor list data (refSphere indices, not cubemap array layer)
     ivec4 refNeighbor[1024];
 
+    ivec4 refBucket[256];
+
     // number of reflection probes present in refSphere
     int refmapCount;
 };
@@ -118,13 +124,26 @@ bool shouldSampleProbe(int i, vec3 pos)
     return true;
 }
 
+int getStartIndex(vec3 pos)
+{
+#if 1
+    int idx = clamp(int(floor(-pos.z)), 0, 255);
+    return clamp(refBucket[idx].x, 1, refmapCount+1);
+#else
+    return 1;
+#endif
+}
+
 // call before sampleRef
 // populate "probeIndex" with N probe indices that influence pos where N is REF_SAMPLE_COUNT
 void preProbeSample(vec3 pos)
 {
 #if REFMAP_LEVEL > 0
+
+    int start = getStartIndex(pos);
+
     // TODO: make some sort of structure that reduces the number of distance checks
-    for (int i = 1; i < refmapCount; ++i)
+    for (int i = start; i < refmapCount; ++i)
     {
         // found an influencing probe
         if (shouldSampleProbe(i, pos))
@@ -142,6 +161,7 @@ void preProbeSample(vec3 pos)
                 {
                     // check up to REF_SAMPLE_COUNT-1 neighbors (neighborIdx is ivec4 index)
 
+                    // sample refNeighbor[neighborIdx].x
                     int idx = refNeighbor[neighborIdx].x;
                     if (shouldSampleProbe(idx, pos))
                     {
@@ -157,6 +177,7 @@ void preProbeSample(vec3 pos)
                         break;
                     }
 
+                    // sample refNeighbor[neighborIdx].y
                     idx = refNeighbor[neighborIdx].y;
                     if (shouldSampleProbe(idx, pos))
                     {
@@ -172,6 +193,7 @@ void preProbeSample(vec3 pos)
                         break;
                     }
 
+                    // sample refNeighbor[neighborIdx].z
                     idx = refNeighbor[neighborIdx].z;
                     if (shouldSampleProbe(idx, pos))
                     {
@@ -187,6 +209,7 @@ void preProbeSample(vec3 pos)
                         break;
                     }
 
+                    // sample refNeighbor[neighborIdx].w
                     idx = refNeighbor[neighborIdx].w;
                     if (shouldSampleProbe(idx, pos))
                     {
@@ -197,11 +220,7 @@ void preProbeSample(vec3 pos)
                         }
                     }
                     count++;
-                    if (count == neighborCount)
-                    {
-                        break;
-                    }
-
+                    
                     ++neighborIdx;
                 }
 
@@ -735,6 +754,14 @@ vec4 sampleReflectionProbesDebug(vec3 pos)
         debugTapRefMap(pos, dir, d, i, col);
     }
 
+#if 0 //debug getStartIndex
+    col.g = float(getStartIndex(pos));
+
+    col.g /= 255.0;
+    col.rb = vec2(0);
+    col.a = 1.0;
+#endif
+
     return col;
 }
 
diff --git a/indra/newview/app_settings/toolbars.xml b/indra/newview/app_settings/toolbars.xml
index f3a23edc5837aa788e51934d4fdf53e9cf80e5b1..a1c9d6d9ee52e33f8ab644ed1ba3e9154c4aefae 100644
--- a/indra/newview/app_settings/toolbars.xml
+++ b/indra/newview/app_settings/toolbars.xml
@@ -22,6 +22,7 @@
     <command name="voice"/>
     <command name="minimap"/>
     <command name="snapshot"/>
+    <command name="performance"/>
   </left_toolbar>
   <right_toolbar
     button_display_mode="icons_only">
diff --git a/indra/newview/featuretable.txt b/indra/newview/featuretable.txt
index 78c2578cec0389fb50d96c323fd7969589d1033f..79d92adcc22cfb6d486fd0e18152fc275bf39d9c 100644
--- a/indra/newview/featuretable.txt
+++ b/indra/newview/featuretable.txt
@@ -113,9 +113,9 @@ RenderReflectionProbeLevel  1   0
 list LowMid
 RenderAnisotropic			1	0
 RenderAvatarLODFactor		1	0.5
-RenderAvatarMaxNonImpostors 1   5
 RenderAvatarMaxComplexity   1	100000
 RenderAvatarPhysicsLODFactor 1	0.75
+RenderAvatarMaxNonImpostors 1   5
 RenderFarClip				1	96
 RenderFlexTimeFactor		1	1.0
 RenderGlowResolutionPow		1	8
@@ -143,9 +143,9 @@ RenderReflectionProbeLevel  1   1
 list Mid
 RenderAnisotropic			1	1
 RenderAvatarLODFactor		1	1.0
-RenderAvatarMaxNonImpostors 1   7
 RenderAvatarMaxComplexity   1	200000
 RenderAvatarPhysicsLODFactor 1	1.0
+RenderAvatarMaxNonImpostors 1   7
 RenderFarClip				1	128
 RenderFlexTimeFactor		1	1.0
 RenderGlowResolutionPow		1	9
@@ -173,9 +173,9 @@ RenderReflectionProbeLevel  1   2
 list MidHigh
 RenderAnisotropic			1	1
 RenderAvatarLODFactor		1	1.0
-RenderAvatarMaxNonImpostors 1   9
 RenderAvatarMaxComplexity   1	250000
 RenderAvatarPhysicsLODFactor 1	1.0
+RenderAvatarMaxNonImpostors 1   9
 RenderFarClip				1	128
 RenderFlexTimeFactor		1	1.0
 RenderGlowResolutionPow		1	9
@@ -203,9 +203,9 @@ RenderReflectionProbeLevel  1   2
 list High
 RenderAnisotropic			1	1
 RenderAvatarLODFactor		1	1.0
-RenderAvatarMaxNonImpostors 1   11
 RenderAvatarMaxComplexity   1	300000
 RenderAvatarPhysicsLODFactor 1	1.0
+RenderAvatarMaxNonImpostors 1   11
 RenderFarClip				1	128
 RenderFlexTimeFactor		1	1.0
 RenderGlowResolutionPow		1	9
@@ -233,9 +233,9 @@ RenderReflectionProbeLevel  1   3
 list HighUltra
 RenderAnisotropic			1	1
 RenderAvatarLODFactor		1	1.0
-RenderAvatarMaxNonImpostors 1   16
 RenderAvatarMaxComplexity   1	350000
 RenderAvatarPhysicsLODFactor 1	1.0
+RenderAvatarMaxNonImpostors 1   16
 RenderFarClip				1	128
 RenderFlexTimeFactor		1	1.0
 RenderGlowResolutionPow		1	9
diff --git a/indra/newview/featuretable_linux.txt b/indra/newview/featuretable_linux.txt
index 66197e6484dc0bf034fc144821868a5f2790fb6b..4bcefc154667b832c2d0ca5f939e55623b39d477 100644
--- a/indra/newview/featuretable_linux.txt
+++ b/indra/newview/featuretable_linux.txt
@@ -59,7 +59,6 @@ WindLightUseAtmosShaders	1	1
 WLSkyDetail					1	128
 Disregard128DefaultDrawDistance	1	1
 Disregard96DefaultDrawDistance	1	1
-RenderTextureMemoryMultiple		1	1.0
 RenderCompressTextures		1	1
 RenderShaderLightingMaxLevel	1	3
 RenderDeferred				1	1
diff --git a/indra/newview/featuretable_mac.txt b/indra/newview/featuretable_mac.txt
index 1d407b52d817cbd3fdd60fe0c2c63d0f1eb98299..72deabd9f1afae5bb68ee63d07551d5d5e4824c9 100644
--- a/indra/newview/featuretable_mac.txt
+++ b/indra/newview/featuretable_mac.txt
@@ -93,7 +93,7 @@ RenderTerrainDetail			1	0
 RenderTerrainLODFactor		1	1
 RenderTransparentWater		1	0
 RenderTreeLODFactor			1	0
-RenderVolumeLODFactor		1	0.5
+RenderVolumeLODFactor		1	1.125
 RenderDeferredSSAO			1	0
 RenderUseAdvancedAtmospherics 1 0
 RenderShadowDetail			1	0
@@ -111,9 +111,9 @@ RenderReflectionProbeLevel  1   0
 list LowMid
 RenderAnisotropic			1	0
 RenderAvatarLODFactor		1	0.5
-RenderAvatarMaxNonImpostors 1   5
 RenderAvatarMaxComplexity   1	100000
 RenderAvatarPhysicsLODFactor 1	0.75
+RenderAvatarMaxNonImpostors 1   5
 RenderFarClip				1	96
 RenderFlexTimeFactor		1	1.0
 RenderGlowResolutionPow		1	8
@@ -141,9 +141,9 @@ RenderReflectionProbeLevel  1   1
 list Mid
 RenderAnisotropic			1	1
 RenderAvatarLODFactor		1	1.0
-RenderAvatarMaxNonImpostors 1   7
 RenderAvatarMaxComplexity   1	200000
 RenderAvatarPhysicsLODFactor 1	1.0
+RenderAvatarMaxNonImpostors 1   7
 RenderFarClip				1	128
 RenderFlexTimeFactor		1	1.0
 RenderGlowResolutionPow		1	9
@@ -171,9 +171,9 @@ RenderReflectionProbeLevel  1   2
 list MidHigh
 RenderAnisotropic			1	1
 RenderAvatarLODFactor		1	1.0
-RenderAvatarMaxNonImpostors 1   9
 RenderAvatarMaxComplexity   1	250000
 RenderAvatarPhysicsLODFactor 1	1.0
+RenderAvatarMaxNonImpostors 1   9
 RenderFarClip				1	128
 RenderFlexTimeFactor		1	1.0
 RenderGlowResolutionPow		1	9
@@ -201,9 +201,9 @@ RenderReflectionProbeLevel  1   2
 list High
 RenderAnisotropic			1	1
 RenderAvatarLODFactor		1	1.0
-RenderAvatarMaxNonImpostors 1   11
 RenderAvatarMaxComplexity   1	300000
 RenderAvatarPhysicsLODFactor 1	1.0
+RenderAvatarMaxNonImpostors 1   11
 RenderFarClip				1	128
 RenderFlexTimeFactor		1	1.0
 RenderGlowResolutionPow		1	9
@@ -261,8 +261,8 @@ RenderReflectionProbeLevel  1   3
 list Ultra
 RenderAnisotropic			1	1
 RenderAvatarLODFactor		1	1.0
-RenderAvatarMaxNonImpostors 1   16
 RenderAvatarPhysicsLODFactor 1	1.0
+RenderAvatarMaxNonImpostors 1   16
 RenderFarClip				1	256
 RenderFlexTimeFactor		1	1.0
 RenderGlowResolutionPow		1	9
diff --git a/indra/newview/groupchatlistener.cpp b/indra/newview/groupchatlistener.cpp
index ef015a950d5e86fd6ed3885530fc61a1aac9f8e6..a05caa961b7db78bbb091a0439f0d2b3774df13c 100644
--- a/indra/newview/groupchatlistener.cpp
+++ b/indra/newview/groupchatlistener.cpp
@@ -64,11 +64,11 @@ GroupChatListener::GroupChatListener():
         "Leave a group chat in group with UUID [\"id\"]\n"
         "Assumes a prior successful startIM request.",
         &LLGroupActions::endIM,
-        LLSDArray("id"));
-	add("sendIM",
-		"send a groupchat IM",
-		&send_message_wrapper,
-        LLSDArray("text")("session_id")("group_id"));
+        llsd::array("id"));
+    add("sendIM",
+        "send a groupchat IM",
+        &send_message_wrapper,
+        llsd::array("text", "session_id", "group_id"));
 }
 /*
 	static void sendMessage(const std::string& utf8_text, const LLUUID& im_session_id,
diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp
index a68dba98bb8d12c59edcbd73805a69f2ea312028..4c3891f302766bc1a9c38e2bb6feaf27c1f1fcbe 100644
--- a/indra/newview/llagent.cpp
+++ b/indra/newview/llagent.cpp
@@ -63,6 +63,7 @@
 #include "llnotificationsutil.h"
 #include "llpaneltopinfobar.h"
 #include "llparcel.h"
+#include "llperfstats.h"
 #include "llrendersphere.h"
 #include "llscriptruntimeperms.h"
 #include "llsdutil.h"
@@ -4094,6 +4095,7 @@ void LLAgent::handleTeleportFinished()
             mRegionp->setCapabilitiesReceivedCallback(boost::bind(&LLAgent::onCapabilitiesReceivedAfterTeleport));
         }
     }
+    LLPerfStats::tunables.autoTuneTimeout = true;
 }
 
 void LLAgent::handleTeleportFailed()
@@ -4125,6 +4127,8 @@ void LLAgent::handleTeleportFailed()
 	}
 
     mTPNeedsNeabyChatSeparator = false;
+
+    LLPerfStats::tunables.autoTuneTimeout = true;
 }
 
 /*static*/
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index b17c78abac2060a9aab3ad79ed4922ffceb2657a..c9fc0e2cc8da48f6691c58a22a105736fd1e156a 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -107,6 +107,7 @@
 #include "llscenemonitor.h"
 #include "llavatarrenderinfoaccountant.h"
 #include "lllocalbitmaps.h"
+#include "llperfstats.h" 
 #include "llgltfmateriallist.h"
 
 // Linden library includes
@@ -1358,82 +1359,93 @@ bool LLAppViewer::frame()
 
 bool LLAppViewer::doFrame()
 {
-	LL_RECORD_BLOCK_TIME(FTM_FRAME);
+    LL_RECORD_BLOCK_TIME(FTM_FRAME);
+    {
+    // and now adjust the visuals from previous frame.
+    if(LLPerfStats::tunables.userAutoTuneEnabled && LLPerfStats::tunables.tuningFlag != LLPerfStats::Tunables::Nothing)
+    {
+        LLPerfStats::tunables.applyUpdates();
+    }
 
+    LLPerfStats::RecordSceneTime T (LLPerfStats::StatType_t::RENDER_FRAME);
     if (!LLWorld::instanceExists())
     {
         LLWorld::createInstance();
     }
 
-	LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop"));
-	LLSD newFrame;
-
-	{
-        LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df LLTrace");
-        if (LLFloaterReg::instanceVisible("block_timers"))
+    LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop"));
+    LLSD newFrame;
+    {
+        LLPerfStats::RecordSceneTime T (LLPerfStats::StatType_t::RENDER_IDLE); // perf stats
         {
-	LLTrace::BlockTimer::processTimes();
-        }
-        
-	LLTrace::get_frame_recording().nextPeriod();
-	LLTrace::BlockTimer::logStats();
-	}
+            LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df LLTrace");
+            if (LLFloaterReg::instanceVisible("block_timers"))
+            {
+                LLTrace::BlockTimer::processTimes();
+            }
 
-	LLTrace::get_thread_recorder()->pullFromChildren();
+            LLTrace::get_frame_recording().nextPeriod();
+            LLTrace::BlockTimer::logStats();
+        }
 
-	//clear call stack records
-	LL_CLEAR_CALLSTACKS();
+        LLTrace::get_thread_recorder()->pullFromChildren();
 
-	{
-		LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df processMiscNativeEvents" )
-		pingMainloopTimeout("Main:MiscNativeWindowEvents");
+        //clear call stack records
+        LL_CLEAR_CALLSTACKS();
+    }
+    {
+        {
+            LLPerfStats::RecordSceneTime T(LLPerfStats::StatType_t::RENDER_IDLE); // ensure we have the entire top scope of frame covered (input event and coro)
+            LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df processMiscNativeEvents")
+            pingMainloopTimeout("Main:MiscNativeWindowEvents");
 
-		if (gViewerWindow)
-		{
-            LL_PROFILE_ZONE_NAMED_CATEGORY_APP("System Messages");
-			gViewerWindow->getWindow()->processMiscNativeEvents();
-		}
+            if (gViewerWindow)
+            {
+                LL_PROFILE_ZONE_NAMED_CATEGORY_APP("System Messages");
+                gViewerWindow->getWindow()->processMiscNativeEvents();
+            }
 
-		{
-			LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df gatherInput" )
-		pingMainloopTimeout("Main:GatherInput");
-		}
+            {
+                LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df gatherInput")
+                pingMainloopTimeout("Main:GatherInput");
+            }
 
-		if (gViewerWindow)
-		{
-            LL_PROFILE_ZONE_NAMED_CATEGORY_APP("System Messages");
-			if (!restoreErrorTrap())
-			{
-				LL_WARNS() << " Someone took over my signal/exception handler (post messagehandling)!" << LL_ENDL;
-			}
+            if (gViewerWindow)
+            {
+                LL_PROFILE_ZONE_NAMED_CATEGORY_APP("System Messages");
+                if (!restoreErrorTrap())
+                {
+                    LL_WARNS() << " Someone took over my signal/exception handler (post messagehandling)!" << LL_ENDL;
+                }
 
-			gViewerWindow->getWindow()->gatherInput();
-		}
+                gViewerWindow->getWindow()->gatherInput();
+            }
 
-		//memory leaking simulation
-		if (gSimulateMemLeak)
-		{
-			LLFloaterMemLeak* mem_leak_instance =
-				LLFloaterReg::findTypedInstance<LLFloaterMemLeak>("mem_leaking");
-			if (mem_leak_instance)
-			{
-				mem_leak_instance->idle();
-			}
-		}
+            //memory leaking simulation
+            if (gSimulateMemLeak)
+            {
+                LLFloaterMemLeak* mem_leak_instance =
+                    LLFloaterReg::findTypedInstance<LLFloaterMemLeak>("mem_leaking");
+                if (mem_leak_instance)
+                {
+                    mem_leak_instance->idle();
+                }
+            }
 
-		{
-			LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df mainloop" )
-		// canonical per-frame event
-		mainloop.post(newFrame);
-		}
+            {
+                LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df mainloop")
+                // canonical per-frame event
+                mainloop.post(newFrame);
+            }
 
-		{
-			LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df suspend" )
-		// give listeners a chance to run
-		llcoro::suspend();
-		// if one of our coroutines threw an uncaught exception, rethrow it now
-		LLCoros::instance().rethrow();
-		}
+            {
+                LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df suspend")
+                // give listeners a chance to run
+                llcoro::suspend();
+                // if one of our coroutines threw an uncaught exception, rethrow it now
+                LLCoros::instance().rethrow();
+            }
+        }
 
 		if (!LLApp::isExiting())
 		{
@@ -1451,6 +1463,7 @@ bool LLAppViewer::doFrame()
 				&& (gHeadlessClient || !gViewerWindow->getShowProgress())
 				&& !gFocusMgr.focusLocked())
 			{
+                LLPerfStats::RecordSceneTime T (LLPerfStats::StatType_t::RENDER_IDLE);
 				joystick->scanJoystick();
 				gKeyboard->scanKeyboard();
                 gViewerInput.scanMouse();
@@ -1464,7 +1477,8 @@ bool LLAppViewer::doFrame()
 				}
 
 				{
-					LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df idle");
+                    LLPerfStats::RecordSceneTime T (LLPerfStats::StatType_t::RENDER_IDLE);
+                    LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df idle");
 					idle();
 				}
 
@@ -1499,6 +1513,7 @@ bool LLAppViewer::doFrame()
                 display();
 
                 {
+                    LLPerfStats::RecordSceneTime T(LLPerfStats::StatType_t::RENDER_IDLE);
                     LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df Snapshot");
                     pingMainloopTimeout("Main:Snapshot");
                     gPipeline.mReflectionMapManager.update();
@@ -1550,7 +1565,8 @@ bool LLAppViewer::doFrame()
 				// of equal priority on Windows
 				if (milliseconds_to_sleep > 0)
 				{
-					ms_sleep(milliseconds_to_sleep);
+                    LLPerfStats::RecordSceneTime T ( LLPerfStats::StatType_t::RENDER_SLEEP );
+                    ms_sleep(milliseconds_to_sleep);
 					// also pause worker threads during this wait period
 					LLAppViewer::getTextureCache()->pause();
 				}
@@ -1637,7 +1653,7 @@ bool LLAppViewer::doFrame()
 
 		LL_INFOS() << "Exiting main_loop" << LL_ENDL;
 	}
-
+    }LLPerfStats::StatsRecorder::endFrame();
     LL_PROFILER_FRAME_END
 
 	return ! LLApp::isRunning();
@@ -2996,15 +3012,9 @@ void LLAppViewer::initStrings()
 	}
 }
 
-//
-// This function decides whether the client machine meets the minimum requirements to
-// run in a maximized window, per the consensus of davep, boa and nyx on 3/30/2011.
-//
 bool LLAppViewer::meetsRequirementsForMaximizedStart()
 {
-	bool maximizedOk = (LLFeatureManager::getInstance()->getGPUClass() >= GPU_CLASS_2);
-
-	maximizedOk &= (gSysMemory.getPhysicalMemoryKB() >= U32Gigabytes(1));
+    bool maximizedOk = (gSysMemory.getPhysicalMemoryKB() >= U32Gigabytes(1));
 
 	return maximizedOk;
 }
@@ -3181,15 +3191,16 @@ LLSD LLAppViewer::getViewerInfo() const
 	// LLFloaterAbout.
 	LLSD info;
 	auto& versionInfo(LLVersionInfo::instance());
-	info["VIEWER_VERSION"] = LLSDArray(versionInfo.getMajor())(versionInfo.getMinor())(versionInfo.getPatch())(versionInfo.getBuild());
+	info["VIEWER_VERSION"] = llsd::array(versionInfo.getMajor(), versionInfo.getMinor(),
+										 versionInfo.getPatch(), versionInfo.getBuild());
 	info["VIEWER_VERSION_STR"] = versionInfo.getVersion();
 	info["CHANNEL"] = versionInfo.getChannel();
-    info["ADDRESS_SIZE"] = ADDRESS_SIZE;
-    std::string build_config = versionInfo.getBuildConfig();
-    if (build_config != "Release")
-    {
-        info["BUILD_CONFIG"] = build_config;
-    }
+	info["ADDRESS_SIZE"] = ADDRESS_SIZE;
+	std::string build_config = versionInfo.getBuildConfig();
+	if (build_config != "Release")
+	{
+		info["BUILD_CONFIG"] = build_config;
+	}
 
 	// return a URL to the release notes for this viewer, such as:
 	// https://releasenotes.secondlife.com/viewer/2.1.0.123456.html
@@ -4687,6 +4698,8 @@ void LLAppViewer::idle()
 	LLFrameTimer::updateFrameTime();
 	LLFrameTimer::updateFrameCount();
 	LLEventTimer::updateClass();
+    LLPerfStats::updateClass();
+
 	// LLApp::stepFrame() performs the above three calls plus mRunner.run().
 	// Not sure why we don't call stepFrame() here, except that LLRunner seems
 	// completely redundant with LLEventTimer.
diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp
index 6457c13ef3b6775e57ba76c9840b54d01e7bea28..31c5d2a16f6e9156c90e984ca8443fa0fc270575 100644
--- a/indra/newview/llappviewerwin32.cpp
+++ b/indra/newview/llappviewerwin32.cpp
@@ -656,16 +656,18 @@ bool LLAppViewerWin32::init()
 													   LL_VIEWER_VERSION_PATCH << '.' <<
 													   LL_VIEWER_VERSION_BUILD));
 
-                DWORD dwFlags = MDSF_NONINTERACTIVE | // automatically submit report without prompting
-                                MDSF_PREVENTHIJACKING; // disallow swiping Exception filter
-
-                bool needs_log_file = !isSecondInstance() && debugLoggingEnabled("BUGSPLAT");
-                if (needs_log_file)
-                {
-                    // Startup only!
-                    LL_INFOS("BUGSPLAT") << "Engaged BugSplat logging to bugsplat.log" << LL_ENDL;
-                    dwFlags |= MDSF_LOGFILE | MDSF_LOG_VERBOSE;
-                }
+				DWORD dwFlags = MDSF_NONINTERACTIVE | // automatically submit report without prompting
+								MDSF_PREVENTHIJACKING; // disallow swiping Exception filter
+
+				bool needs_log_file = !isSecondInstance();
+				LL_DEBUGS("BUGSPLAT");
+				if (needs_log_file)
+				{
+					// Startup only!
+					LL_INFOS("BUGSPLAT") << "Engaged BugSplat logging to bugsplat.log" << LL_ENDL;
+					dwFlags |= MDSF_LOGFILE | MDSF_LOG_VERBOSE;
+				}
+				LL_ENDL;
 
 				// have to convert normal wide strings to strings of __wchar_t
 				sBugSplatSender = new MiniDmpSender(
@@ -676,12 +678,14 @@ bool LLAppViewerWin32::init()
 					dwFlags);
 				sBugSplatSender->setCallback(bugsplatSendLog);
 
-                if (needs_log_file)
-                {
-                    // Log file will be created in %TEMP%, but it will be moved into logs folder in case of crash
-                    std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "bugsplat.log");
-                    sBugSplatSender->setLogFilePath(WCSTR(log_file));
-                }
+				LL_DEBUGS("BUGSPLAT");
+				if (needs_log_file)
+				{
+					// Log file will be created in %TEMP%, but it will be moved into logs folder in case of crash
+					std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "bugsplat.log");
+					sBugSplatSender->setLogFilePath(WCSTR(log_file));
+				}
+				LL_ENDL;
 
 				// engage stringize() overload that converts from wstring
 				LL_INFOS("BUGSPLAT") << "Engaged BugSplat(" << LL_TO_STRING(LL_VIEWER_CHANNEL)
diff --git a/indra/newview/llavatarrendernotifier.cpp b/indra/newview/llavatarrendernotifier.cpp
index 94584a623b95b56b202b16fd3cc9c720d3bc565a..8b09f7903dcf1006c3c1e62cc575ed57057bf52d 100644
--- a/indra/newview/llavatarrendernotifier.cpp
+++ b/indra/newview/llavatarrendernotifier.cpp
@@ -235,6 +235,12 @@ void LLAvatarRenderNotifier::updateNotificationAgent(U32 agentComplexity)
     // save the value for use in following messages
     mLatestAgentComplexity = agentComplexity;
 
+    static LLCachedControl<U32> show_my_complexity_changes(gSavedSettings, "ShowMyComplexityChanges", 20);
+    if (!show_my_complexity_changes)
+    {
+        return;
+    }
+
     if (!isAgentAvatarValid() || !gAgentWearables.areWearablesLoaded())
     {
         // data not ready, nothing to show.
@@ -282,7 +288,8 @@ static const char* e_hud_messages[] =
 };
 
 LLHUDRenderNotifier::LLHUDRenderNotifier() :
-mReportedHUDWarning(WARN_NONE)
+mReportedHUDWarning(WARN_NONE),
+mHUDsCount(0)
 {
 }
 
@@ -298,6 +305,15 @@ void LLHUDRenderNotifier::updateNotificationHUD(hud_complexity_list_t complexity
         return;
     }
 
+    mHUDComplexityList = complexity;
+    mHUDsCount = mHUDComplexityList.size();
+
+    static LLCachedControl<U32> show_my_complexity_changes(gSavedSettings, "ShowMyComplexityChanges", 20);
+    if (!show_my_complexity_changes)
+    {
+        return;
+    }
+        
     // TODO:
     // Find a way to show message with list of issues, but without making it too large
     // and intrusive.
diff --git a/indra/newview/llavatarrendernotifier.h b/indra/newview/llavatarrendernotifier.h
index ec17b3d9e62117bdeaadb9db4016dd8ba662b051..37130bfcf6e88d42351cf66d17da2836b0a28f16 100644
--- a/indra/newview/llavatarrendernotifier.h
+++ b/indra/newview/llavatarrendernotifier.h
@@ -63,6 +63,25 @@ struct LLHUDComplexity
 
 typedef std::list<LLHUDComplexity> hud_complexity_list_t;
 
+struct LLObjectComplexity
+{
+    LLObjectComplexity()
+    {
+        reset();
+    }
+    void reset()
+    {
+        objectId = LLUUID::null;
+        objectName = "";
+        objectCost = 0;
+    }
+    LLUUID objectId;
+    std::string objectName;
+    U32 objectCost;
+};
+
+typedef std::list<LLObjectComplexity> object_complexity_list_t;
+
 // Class to notify user about drastic changes in agent's render weights or if other agents
 // reported that user's agent is too 'heavy' for their settings
 class LLAvatarRenderNotifier : public LLSingleton<LLAvatarRenderNotifier>
@@ -77,6 +96,9 @@ class LLAvatarRenderNotifier : public LLSingleton<LLAvatarRenderNotifier>
     void updateNotificationState();
 	void updateNotificationAgent(U32 agentComplexity);
 
+    void setObjectComplexityList(object_complexity_list_t object_list) { mObjectComplexityList = object_list; }
+    object_complexity_list_t getObjectComplexityList() { return mObjectComplexityList; }
+
 private:
 
 	LLNotificationPtr mNotificationPtr;
@@ -109,6 +131,8 @@ class LLAvatarRenderNotifier : public LLSingleton<LLAvatarRenderNotifier>
     // Used to detect changes in voavatar's rezzed status.
     // If value decreases - there were changes in outfit.
     S32 mLastOutfitRezStatus;
+
+    object_complexity_list_t mObjectComplexityList;
 };
 
 // Class to notify user about heavy set of HUD
@@ -121,6 +145,9 @@ class LLHUDRenderNotifier : public LLSingleton<LLHUDRenderNotifier>
     void updateNotificationHUD(hud_complexity_list_t complexity);
     bool isNotificationVisible();
 
+    hud_complexity_list_t getHUDComplexityList() { return mHUDComplexityList; }
+    S32 getHUDsCount() { return mHUDsCount; }
+
 private:
     enum EWarnLevel
     {
@@ -141,6 +168,8 @@ class LLHUDRenderNotifier : public LLSingleton<LLHUDRenderNotifier>
     EWarnLevel mReportedHUDWarning;
     LLHUDComplexity mLatestHUDComplexity;
     LLFrameTimer mHUDPopUpDelayTimer;
+    hud_complexity_list_t mHUDComplexityList;
+    S32 mHUDsCount;
 };
 
 #endif /* ! defined(LL_llavatarrendernotifier_H) */
diff --git a/indra/newview/lldrawable.cpp b/indra/newview/lldrawable.cpp
index 04b6ebd14ca3266436336e36b7731606cb5b2278..ea59a413fafd203cae84303f1fe343f0fc38c62c 100644
--- a/indra/newview/lldrawable.cpp
+++ b/indra/newview/lldrawable.cpp
@@ -908,12 +908,6 @@ void LLDrawable::updateDistance(LLCamera& camera, bool force_update)
                 LLVector3 cam_pos_from_agent = LLViewerCamera::getInstance()->getOrigin();
                 LLVector3 cam_to_box_offset = point_to_box_offset(cam_pos_from_agent, av_box);
                 mDistanceWRTCamera = llmax(0.01f, ll_round(cam_to_box_offset.magVec(), 0.01f));
-                LL_DEBUGS("DynamicBox") << volume->getAvatar()->getFullname() 
-                                        << " pos (ignored) " << pos
-                                        << " cam pos " << cam_pos_from_agent
-                                        << " box " << av_box[0] << "," << av_box[1] 
-                                        << " -> dist " << mDistanceWRTCamera
-                                        << LL_ENDL;
                 mVObjp->updateLOD();
                 return;
             }
diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp
index 3de0e8a7c4e4c79c93e1cd89653a14e4d850fb05..187e29006635a4ac440eca37da13554267158c8b 100644
--- a/indra/newview/lldrawpool.cpp
+++ b/indra/newview/lldrawpool.cpp
@@ -53,7 +53,6 @@
 #include "llvoavatar.h"
 #include "llviewershadermgr.h"
 
-
 S32 LLDrawPool::sNumDrawPools = 0;
 
 //=============================
@@ -385,13 +384,13 @@ void LLRenderPass::renderGroup(LLSpatialGroup* group, U32 type, bool texture)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
 	LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[type];
-	
+
 	for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k)	
 	{
 		LLDrawInfo *pparams = *k;
 		if (pparams) 
         {
-			pushBatch(*pparams, texture);
+            pushBatch(*pparams, texture);
 		}
 	}
 }
@@ -420,91 +419,7 @@ void LLRenderPass::renderRiggedGroup(LLSpatialGroup* group, U32 type, bool textu
     }
 }
 
-void setup_texture_matrix(LLDrawInfo& params)
-{
-    if (params.mTextureMatrix)
-    { //special case implementation of texture animation here because of special handling of textures for PBR batches
-        gGL.getTexUnit(0)->activate();
-        gGL.matrixMode(LLRender::MM_TEXTURE);
-        gGL.loadMatrix((GLfloat*)params.mTextureMatrix->mMatrix);
-        gPipeline.mTextureMatrixOps++;
-    }
-}
-
-void teardown_texture_matrix(LLDrawInfo& params)
-{
-    if (params.mTextureMatrix)
-    {
-        gGL.matrixMode(LLRender::MM_TEXTURE0);
-        gGL.loadIdentity();
-        gGL.matrixMode(LLRender::MM_MODELVIEW);
-    }
-}
-
-void LLRenderPass::pushGLTFBatches(U32 type)
-{
-    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
-    auto* begin = gPipeline.beginRenderMap(type);
-    auto* end = gPipeline.endRenderMap(type);
-    for (LLCullResult::drawinfo_iterator i = begin; i != end; )
-    {
-        LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("pushGLTFBatch");
-        LLDrawInfo& params = **i;
-        LLCullResult::increment_iterator(i, end);
-
-        pushGLTFBatch(params);
-    }
-}
-
-void LLRenderPass::pushGLTFBatch(LLDrawInfo& params)
-{
-    auto& mat = params.mGLTFMaterial;
-
-    mat->bind(params.mTexture);
-
-    LLGLDisable cull_face(mat->mDoubleSided ? GL_CULL_FACE : 0);
-
-    setup_texture_matrix(params);
-    
-    applyModelMatrix(params);
-
-    params.mVertexBuffer->setBuffer();
-    params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset);
-
-    teardown_texture_matrix(params);
-}
-
-void LLRenderPass::pushRiggedGLTFBatches(U32 type)
-{
-    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
-    LLVOAvatar* lastAvatar = nullptr;
-    U64 lastMeshId = 0;
-
-    auto* begin = gPipeline.beginRenderMap(type);
-    auto* end = gPipeline.endRenderMap(type);
-    for (LLCullResult::drawinfo_iterator i = begin; i != end; )
-    {
-        LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("pushRiggedGLTFBatch");
-        LLDrawInfo& params = **i;
-        LLCullResult::increment_iterator(i, end);
-
-        pushRiggedGLTFBatch(params, lastAvatar, lastMeshId);
-    }
-}
-
-void LLRenderPass::pushRiggedGLTFBatch(LLDrawInfo& params, LLVOAvatar*& lastAvatar, U64& lastMeshId)
-{
-    if (params.mAvatar.notNull() && (lastAvatar != params.mAvatar || lastMeshId != params.mSkinInfo->mHash))
-    {
-        uploadMatrixPalette(params);
-        lastAvatar = params.mAvatar;
-        lastMeshId = params.mSkinInfo->mHash;
-    }
-
-    pushGLTFBatch(params);
-}
-
-void LLRenderPass::pushBatches(U32 type, bool texture, bool batch_textures, bool reset_gltf)
+void LLRenderPass::pushBatches(U32 type, bool texture, bool batch_textures)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
     auto* begin = gPipeline.beginRenderMap(type);
@@ -514,12 +429,11 @@ void LLRenderPass::pushBatches(U32 type, bool texture, bool batch_textures, bool
         LLDrawInfo* pparams = *i;
         LLCullResult::increment_iterator(i, end);
 
-		pushBatch(*pparams, texture, batch_textures, reset_gltf);
-        reset_gltf = false;
+		pushBatch(*pparams, texture, batch_textures);
 	}
 }
 
-void LLRenderPass::pushRiggedBatches(U32 type, bool texture, bool batch_textures, bool reset_gltf)
+void LLRenderPass::pushRiggedBatches(U32 type, bool texture, bool batch_textures)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
     LLVOAvatar* lastAvatar = nullptr;
@@ -538,12 +452,11 @@ void LLRenderPass::pushRiggedBatches(U32 type, bool texture, bool batch_textures
             lastMeshId = pparams->mSkinInfo->mHash;
         }
 
-        pushBatch(*pparams, texture, batch_textures, reset_gltf);
-        reset_gltf = false;
+        pushBatch(*pparams, texture, batch_textures);
     }
 }
 
-void LLRenderPass::pushMaskBatches(U32 type, bool texture, bool batch_textures, bool reset_gltf)
+void LLRenderPass::pushMaskBatches(U32 type, bool texture, bool batch_textures)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
     auto* begin = gPipeline.beginRenderMap(type);
@@ -553,12 +466,11 @@ void LLRenderPass::pushMaskBatches(U32 type, bool texture, bool batch_textures,
         LLDrawInfo* pparams = *i;
         LLCullResult::increment_iterator(i, end);
 		LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff);
-		pushBatch(*pparams, texture, batch_textures, reset_gltf);
-        reset_gltf = false;
+		pushBatch(*pparams, texture, batch_textures);
 	}
 }
 
-void LLRenderPass::pushRiggedMaskBatches(U32 type, bool texture, bool batch_textures, bool reset_gltf)
+void LLRenderPass::pushRiggedMaskBatches(U32 type, bool texture, bool batch_textures)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
     LLVOAvatar* lastAvatar = nullptr;
@@ -571,6 +483,8 @@ void LLRenderPass::pushRiggedMaskBatches(U32 type, bool texture, bool batch_text
 
         LLCullResult::increment_iterator(i, end);
 
+        llassert(pparams);
+
         if (LLGLSLShader::sCurBoundShaderPtr)
         {
             LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff);
@@ -587,8 +501,7 @@ void LLRenderPass::pushRiggedMaskBatches(U32 type, bool texture, bool batch_text
             lastMeshId = pparams->mSkinInfo->mHash;
         }
 
-        pushBatch(*pparams, texture, batch_textures, reset_gltf);
-        reset_gltf = false;
+        pushBatch(*pparams, texture, batch_textures);
     }
 }
 
@@ -607,14 +520,7 @@ void LLRenderPass::applyModelMatrix(const LLDrawInfo& params)
 	}
 }
 
-void LLRenderPass::resetGLTFTextureTransform()
-{
-    F32 ignore_gltf_transform[8];
-    LLGLTFMaterial::sDefault.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR].getPacked(ignore_gltf_transform);
-    LLGLSLShader::sCurBoundShaderPtr->uniform4fv(LLShaderMgr::TEXTURE_BASE_COLOR_TRANSFORM, 2, (F32*)ignore_gltf_transform);
-}
-
-void LLRenderPass::pushBatch(LLDrawInfo& params, bool texture, bool batch_textures, bool reset_gltf)
+void LLRenderPass::pushBatch(LLDrawInfo& params, bool texture, bool batch_textures)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
     if (!params.mCount)
@@ -623,7 +529,6 @@ void LLRenderPass::pushBatch(LLDrawInfo& params, bool texture, bool batch_textur
     }
 
 	applyModelMatrix(params);
-    if (reset_gltf) { resetGLTFTextureTransform(); }
 
 	bool tex_setup = false;
 
@@ -702,3 +607,87 @@ bool LLRenderPass::uploadMatrixPalette(LLVOAvatar* avatar, LLMeshSkinInfo* skinI
     return true;
 }
 
+void setup_texture_matrix(LLDrawInfo& params)
+{
+    if (params.mTextureMatrix)
+    { //special case implementation of texture animation here because of special handling of textures for PBR batches
+        gGL.getTexUnit(0)->activate();
+        gGL.matrixMode(LLRender::MM_TEXTURE);
+        gGL.loadMatrix((GLfloat*)params.mTextureMatrix->mMatrix);
+        gPipeline.mTextureMatrixOps++;
+    }
+}
+
+void teardown_texture_matrix(LLDrawInfo& params)
+{
+    if (params.mTextureMatrix)
+    {
+        gGL.matrixMode(LLRender::MM_TEXTURE0);
+        gGL.loadIdentity();
+        gGL.matrixMode(LLRender::MM_MODELVIEW);
+    }
+}
+
+void LLRenderPass::pushGLTFBatches(U32 type)
+{
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
+    auto* begin = gPipeline.beginRenderMap(type);
+    auto* end = gPipeline.endRenderMap(type);
+    for (LLCullResult::drawinfo_iterator i = begin; i != end; )
+    {
+        LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("pushGLTFBatch");
+        LLDrawInfo& params = **i;
+        LLCullResult::increment_iterator(i, end);
+
+        pushGLTFBatch(params);
+    }
+}
+
+void LLRenderPass::pushGLTFBatch(LLDrawInfo& params)
+{
+    auto& mat = params.mGLTFMaterial;
+
+    mat->bind(params.mTexture);
+
+    LLGLDisable cull_face(mat->mDoubleSided ? GL_CULL_FACE : 0);
+
+    setup_texture_matrix(params);
+
+    applyModelMatrix(params);
+
+    params.mVertexBuffer->setBuffer();
+    params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset);
+
+    teardown_texture_matrix(params);
+}
+
+void LLRenderPass::pushRiggedGLTFBatches(U32 type)
+{
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
+    LLVOAvatar* lastAvatar = nullptr;
+    U64 lastMeshId = 0;
+
+    auto* begin = gPipeline.beginRenderMap(type);
+    auto* end = gPipeline.endRenderMap(type);
+    for (LLCullResult::drawinfo_iterator i = begin; i != end; )
+    {
+        LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("pushRiggedGLTFBatch");
+        LLDrawInfo& params = **i;
+        LLCullResult::increment_iterator(i, end);
+
+        pushRiggedGLTFBatch(params, lastAvatar, lastMeshId);
+    }
+}
+
+void LLRenderPass::pushRiggedGLTFBatch(LLDrawInfo& params, LLVOAvatar*& lastAvatar, U64& lastMeshId)
+{
+    if (params.mAvatar.notNull() && (lastAvatar != params.mAvatar || lastMeshId != params.mSkinInfo->mHash))
+    {
+        uploadMatrixPalette(params);
+        lastAvatar = params.mAvatar;
+        lastMeshId = params.mSkinInfo->mHash;
+    }
+
+    pushGLTFBatch(params);
+}
+
diff --git a/indra/newview/lldrawpool.h b/indra/newview/lldrawpool.h
index 8704f2e34051deb4db89813b245d1b01435ce589..eef19199b94445bf751b2d284d5b496efa29260b 100644
--- a/indra/newview/lldrawpool.h
+++ b/indra/newview/lldrawpool.h
@@ -349,17 +349,18 @@ class LLRenderPass : public LLDrawPool
 	void resetDrawOrders() { }
 
 	static void applyModelMatrix(const LLDrawInfo& params);
+    // Use before a non-GLTF batch if it is interleaved with GLTF batches that share the same shader
     static void resetGLTFTextureTransform();
-	virtual void pushBatches(U32 type, bool texture = true, bool batch_textures = false, bool reset_gltf = false);
-    virtual void pushRiggedBatches(U32 type, bool texture = true, bool batch_textures = false, bool reset_gltf = false);
+	void pushBatches(U32 type, bool texture = true, bool batch_textures = false);
+    void pushRiggedBatches(U32 type, bool texture = true, bool batch_textures = false);
     void pushGLTFBatches(U32 type);
     void pushGLTFBatch(LLDrawInfo& params);
     void pushRiggedGLTFBatches(U32 type);
     void pushRiggedGLTFBatch(LLDrawInfo& params, LLVOAvatar*& lastAvatar, U64& lastMeshId);
-	virtual void pushMaskBatches(U32 type, bool texture = true, bool batch_textures = false, bool reset_gltf = false);
-    virtual void pushRiggedMaskBatches(U32 type, bool texture = true, bool batch_textures = false, bool reset_gltf = false);
-    // reset_gltf: batch is interleaved with GLTF batches that share the same shader
-	virtual void pushBatch(LLDrawInfo& params, bool texture, bool batch_textures = false, bool reset_gltf = false);
+	void pushMaskBatches(U32 type, bool texture = true, bool batch_textures = false);
+    void pushRiggedMaskBatches(U32 type, bool texture = true, bool batch_textures = false);
+	void pushBatch(LLDrawInfo& params, bool texture, bool batch_textures = false);
+	void pushBumpBatch(LLDrawInfo& params, bool texture, bool batch_textures = false);
     static bool uploadMatrixPalette(LLDrawInfo& params);
     static bool uploadMatrixPalette(LLVOAvatar* avatar, LLMeshSkinInfo* skinInfo);
 	virtual void renderGroup(LLSpatialGroup* group, U32 type, bool texture = true);
diff --git a/indra/newview/lldrawpoolalpha.cpp b/indra/newview/lldrawpoolalpha.cpp
index 280be162bcd7188c5132eb2dcddfc9d5e7e04139..f8d2a9e942e232dd1636cca74cabd3db387bc1e5 100644
--- a/indra/newview/lldrawpoolalpha.cpp
+++ b/indra/newview/lldrawpoolalpha.cpp
@@ -107,12 +107,10 @@ static void prepare_alpha_shader(LLGLSLShader* shader, bool textureGamma, bool d
     // i.e. shaders\class1\deferred\alphaF.glsl
     if (deferredEnvironment)
     {
-        gPipeline.bindDeferredShader( *shader );
-    }
-    else
-    {
-        shader->bind();
+        shader->mCanBindFast = false;
     }
+    
+    shader->bind();
     shader->uniform1f(LLShaderMgr::DISPLAY_GAMMA, (gamma > 0.1f) ? 1.0f / gamma : (1.0f / 2.2f));
 
     if (LLPipeline::sRenderingHUDs)
@@ -159,7 +157,7 @@ void LLDrawPoolAlpha::renderPostDeferred(S32 pass)
 { 
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
 
-    if ((!LLPipeline::sRenderTransparentWater || gCubeSnapshot) && getType() == LLDrawPool::POOL_ALPHA_PRE_WATER)
+    if (LLPipeline::isWaterClip() && getType() == LLDrawPool::POOL_ALPHA_PRE_WATER)
     { // don't render alpha objects on the other side of the water plane if water is opaque
         return;
     }
@@ -537,6 +535,8 @@ void LLDrawPoolAlpha::renderRiggedEmissives(std::vector<LLDrawInfo*>& emissives)
 
     for (LLDrawInfo* draw : emissives)
     {
+        LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("Emissives");
+
         bool tex_setup = TexSetup(draw, false);
         if (lastAvatar != draw->mAvatar || lastMeshId != draw->mSkinInfo->mHash)
         {
@@ -664,7 +664,7 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged)
 
 			LLSpatialGroup::drawmap_elem_t& draw_info = rigged ? group->mDrawMap[LLRenderPass::PASS_ALPHA_RIGGED] : group->mDrawMap[LLRenderPass::PASS_ALPHA];
 
-			for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k)	
+            for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k)	
 			{
 				LLDrawInfo& params = **k;
                 if ((bool)params.mAvatar != rigged)
diff --git a/indra/newview/lldrawpoolavatar.cpp b/indra/newview/lldrawpoolavatar.cpp
index 9820202f9b6a0a9e6c32a858c74e661ad0630cdf..19b23609a6a8991f936d3312427868fd06949049 100644
--- a/indra/newview/lldrawpoolavatar.cpp
+++ b/indra/newview/lldrawpoolavatar.cpp
@@ -369,9 +369,11 @@ void LLDrawPoolAvatar::renderShadow(S32 pass)
 	{
 		return;
 	}
-	LLVOAvatar::AvatarOverallAppearance oa = avatarp->getOverallAppearance();
+
+    LLVOAvatar::AvatarOverallAppearance oa = avatarp->getOverallAppearance();
 	BOOL impostor = !LLPipeline::sImpostorRender && avatarp->isImpostor();
-    if (impostor || (oa == LLVOAvatar::AOA_INVISIBLE))
+    // no shadows if the shadows are causing this avatar to breach the limit.
+    if (avatarp->isTooSlow() || impostor || (oa == LLVOAvatar::AOA_INVISIBLE))
 	{
         // No shadows for impostored (including jellydolled) or invisible avs.
 		return;
diff --git a/indra/newview/lldrawpoolbump.cpp b/indra/newview/lldrawpoolbump.cpp
index 35265b6df07a4d259ba0240ab2d1a95672df1308..38768a19c83749402db2b810d7335c0eb7c5ceb0 100644
--- a/indra/newview/lldrawpoolbump.cpp
+++ b/indra/newview/lldrawpoolbump.cpp
@@ -77,6 +77,7 @@ static LLGLSLShader* shader = NULL;
 static S32 cube_channel = -1;
 static S32 diffuse_channel = -1;
 static S32 bump_channel = -1;
+static BOOL shiny = FALSE;
 
 // Enabled after changing LLViewerTexture::mNeedsCreateTexture to an
 // LLAtomicBool; this should work just fine, now. HB
@@ -197,7 +198,7 @@ void LLStandardBumpmap::destroyGL()
 LLDrawPoolBump::LLDrawPoolBump() 
 :  LLRenderPass(LLDrawPool::POOL_BUMP)
 {
-	mShiny = FALSE;
+	shiny = FALSE;
 }
 
 
@@ -346,7 +347,7 @@ void LLDrawPoolBump::beginFullbrightShiny()
 		diffuse_channel = 0;
 	}
 
-	mShiny = TRUE;
+	shiny = TRUE;
 }
 
 void LLDrawPoolBump::renderFullbrightShiny()
@@ -398,14 +399,14 @@ void LLDrawPoolBump::endFullbrightShiny()
 	
 	diffuse_channel = -1;
 	cube_channel = 0;
-	mShiny = FALSE;
+	shiny = FALSE;
 }
 
 void LLDrawPoolBump::renderGroup(LLSpatialGroup* group, U32 type, bool texture = true)
 {					
 	LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[type];	
 	
-	for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) 
+    for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) 
 	{
 		LLDrawInfo& params = **k;
 		
@@ -541,7 +542,7 @@ void LLDrawPoolBump::renderDeferred(S32 pass)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; //LL_RECORD_BLOCK_TIME(FTM_RENDER_BUMP);
 
-    mShiny = TRUE;
+    shiny = TRUE;
     for (int i = 0; i < 2; ++i)
     {
         bool rigged = i == 1;
@@ -575,11 +576,11 @@ void LLDrawPoolBump::renderDeferred(S32 pass)
                     avatar = params.mAvatar;
                     skin = params.mSkinInfo->mHash;
                 }
-                pushBatch(params, true, false);
+                pushBumpBatch(params, true, false);
             }
             else
             {
-                pushBatch(params, true, false);
+                pushBumpBatch(params, true, false);
             }
         }
 
@@ -589,7 +590,7 @@ void LLDrawPoolBump::renderDeferred(S32 pass)
         gGL.getTexUnit(0)->activate();
     }
 
-    mShiny = FALSE;
+    shiny = FALSE;
 }
 
 
@@ -1213,20 +1214,18 @@ void LLDrawPoolBump::pushBumpBatches(U32 type)
                     }
                 }
             }
-			pushBatch(params, false);
+			pushBumpBatch(params, false);
 		}
 	}
 }
 
-void LLDrawPoolBump::pushBatch(LLDrawInfo& params, bool texture, bool batch_textures, bool reset_gltf)
+void LLRenderPass::pushBumpBatch(LLDrawInfo& params, bool texture, bool batch_textures)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
 	applyModelMatrix(params);
 
 	bool tex_setup = false;
 
-	if (reset_gltf) { LLRenderPass::resetGLTFTextureTransform(); }
-
 	if (batch_textures && params.mTextureList.size() > 1)
 	{
 		for (U32 i = 0; i < params.mTextureList.size(); ++i)
@@ -1241,7 +1240,7 @@ void LLDrawPoolBump::pushBatch(LLDrawInfo& params, bool texture, bool batch_text
 	{ //not batching textures or batch has only 1 texture -- might need a texture matrix
 		if (params.mTextureMatrix)
 		{
-			if (mShiny)
+			if (shiny)
 			{
 				gGL.getTexUnit(0)->activate();
 				gGL.matrixMode(LLRender::MM_TEXTURE);
@@ -1260,7 +1259,7 @@ void LLDrawPoolBump::pushBatch(LLDrawInfo& params, bool texture, bool batch_text
 			tex_setup = true;
 		}
 
-		if (mShiny && mShaderLevel > 1 && texture)
+		if (shiny && mShaderLevel > 1 && texture)
 		{
 			if (params.mTexture.notNull())
 			{
@@ -1278,7 +1277,7 @@ void LLDrawPoolBump::pushBatch(LLDrawInfo& params, bool texture, bool batch_text
 
     if (tex_setup)
 	{
-		if (mShiny)
+		if (shiny)
 		{
 			gGL.getTexUnit(0)->activate();
 		}
diff --git a/indra/newview/lldrawpoolbump.h b/indra/newview/lldrawpoolbump.h
index 41c4879dedd6cc6724c46096128b7bca263bbf7f..b1fe454c721e08a812e823589fbbdfd898b955c3 100644
--- a/indra/newview/lldrawpoolbump.h
+++ b/indra/newview/lldrawpoolbump.h
@@ -53,7 +53,6 @@ protected :
 	LLDrawPoolBump();
 
 	/*virtual*/ void prerender() override;
-	void pushBatch(LLDrawInfo& params, bool texture, bool batch_textures = false, bool reset_gltf = false) override;
 
 	void pushBumpBatches(U32 type);
 	void renderGroup(LLSpatialGroup* group, U32 type, bool texture) override;
diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp
index 0974ae07423f0c753982c801c8d0477f60352a75..a2aae91ae149d91af477b76f235f00b47c0b73a0 100644
--- a/indra/newview/llfeaturemanager.cpp
+++ b/indra/newview/llfeaturemanager.cpp
@@ -408,6 +408,7 @@ bool LLFeatureManager::loadGPUClass()
 {
 	if (!gSavedSettings.getBOOL("SkipBenchmark"))
 	{
+		F32 class1_gbps = gSavedSettings.getF32("RenderClass1MemoryBandwidth");
 		//get memory bandwidth from benchmark
 		F32 gbps;
 		try
@@ -424,6 +425,14 @@ bool LLFeatureManager::loadGPUClass()
 			LL_WARNS("RenderInit") << "GPU benchmark failed: " << e.what() << LL_ENDL;
 		}
 	
+        mGPUMemoryBandwidth = gbps;
+
+        // bias by CPU speed
+        F32 cpu_basis_mhz = gSavedSettings.getF32("RenderCPUBasis");
+        F32 cpu_mhz = (F32) gSysCPU.getMHz();
+        F32 cpu_bias = llclamp(cpu_mhz / cpu_basis_mhz, 0.5f, 1.f);
+        gbps *= cpu_bias;
+
 		if (gbps < 0.f)
 		{ //couldn't bench, default to Low
 	#if LL_DARWIN
@@ -434,19 +443,19 @@ bool LLFeatureManager::loadGPUClass()
 			mGPUClass = GPU_CLASS_0;
 	#endif
 		}
-		else if (gbps <= 32.0f)
+		else if (gbps <= class1_gbps)
 		{
 			mGPUClass = GPU_CLASS_1;
 		}
-		else if (gbps <= 64.0f)
+		else if (gbps <= class1_gbps *2.f)
 		{
 			mGPUClass = GPU_CLASS_2;
 		}
-		else if (gbps <= 128.0f)
+		else if (gbps <= class1_gbps*4.f)
 		{
 			mGPUClass = GPU_CLASS_3;
 		}
-		else if (gbps <= 256.0f)
+		else if (gbps <= class1_gbps*8.f)
 		{
 			mGPUClass = GPU_CLASS_4;
 		}
diff --git a/indra/newview/llfeaturemanager.h b/indra/newview/llfeaturemanager.h
index 42a226cd18f843bdc40ed58e07f30543e58eedc4..651404d8905a270e2ab767e804ab1ad2d5065465 100644
--- a/indra/newview/llfeaturemanager.h
+++ b/indra/newview/llfeaturemanager.h
@@ -111,6 +111,10 @@ class LLFeatureManager : public LLFeatureList, public LLSingleton<LLFeatureManag
 
 	EGPUClass getGPUClass() 			{ return mGPUClass; }
 	std::string& getGPUString() 		{ return mGPUString; }
+    
+    // get the measured GPU memory bandwidth in GB/sec
+    // may return 0 of benchmark has not been run or failed to run
+    F32 getGPUMemoryBandwidth() { return mGPUMemoryBandwidth; }
 	BOOL isGPUSupported()				{ return mGPUSupported; }
 	F32 getExpectedGLVersion()			{ return mExpectedGLVersion; }
 	
@@ -162,6 +166,7 @@ class LLFeatureManager : public LLFeatureList, public LLSingleton<LLFeatureManag
 	S32			mTableVersion;
 	BOOL		mSafe;					// Reinitialize everything to the "safe" mask
 	EGPUClass	mGPUClass;
+    F32         mGPUMemoryBandwidth = 0.f;  // measured memory bandwidth of GPU in GB/second
 	F32			mExpectedGLVersion;		//expected GL version according to gpu table
 	std::string	mGPUString;
 	BOOL		mGPUSupported;
diff --git a/indra/newview/llfloater360capture.cpp b/indra/newview/llfloater360capture.cpp
index 542a1ea39ba93926cb7b1241e6fc2e32ce3bd7d3..23f86e2361eeaa3abfb464f5796a8099b4e2608f 100644
--- a/indra/newview/llfloater360capture.cpp
+++ b/indra/newview/llfloater360capture.cpp
@@ -319,13 +319,7 @@ const std::string LLFloater360Capture::getHTMLBaseFolder()
 // triggered when the 'capture' button in the UI is pressed
 void LLFloater360Capture::onCapture360ImagesBtn()
 {
-    // launch the main capture code in a coroutine so we can
-    // yield/suspend at some points to give the main UI
-    // thread a look-in occasionally.
-    LLCoros::instance().launch("capture360cap", [this]()
-    {
-        capture360Images();
-    });
+    capture360Images();
 }
 
 // Gets the full path name for a given JavaScript file in the HTML folder. We
@@ -686,9 +680,6 @@ void LLFloater360Capture::capture360Images()
     mCaptureBtn->setEnabled(true);
     mSaveLocalBtn->setEnabled(true);
 
-    // allow the UI to update by suspending and waiting for the
-    // main render loop to update the UI
-    suspendForAFrame();
 }
 
 // once the request is made to navigate to the web page containing the code
diff --git a/indra/newview/llfloateravatarrendersettings.cpp b/indra/newview/llfloateravatarrendersettings.cpp
index 8b28f6941ef66653c67fcc67cc57bdc8235e5d5b..7d098e6c8819331c2169beeaefcc5a6b2222a520 100644
--- a/indra/newview/llfloateravatarrendersettings.cpp
+++ b/indra/newview/llfloateravatarrendersettings.cpp
@@ -89,8 +89,6 @@ BOOL LLFloaterAvatarRenderSettings::postBuild()
     LLFloater::postBuild();
     mAvatarSettingsList = getChild<LLNameListCtrl>("render_settings_list");
     mAvatarSettingsList->setRightMouseDownCallback(boost::bind(&LLFloaterAvatarRenderSettings::onAvatarListRightClick, this, _1, _2, _3));
-    mAvatarSettingsList->setAlternateSort();
-    getChild<LLFilterEditor>("people_filter_input")->setCommitCallback(boost::bind(&LLFloaterAvatarRenderSettings::onFilterEdit, this, _2));
 
 	return TRUE;
 }
@@ -134,37 +132,13 @@ void LLFloaterAvatarRenderSettings::updateList()
     {
         item_params.value = iter->first;
         LLAvatarNameCache::get(iter->first, &av_name);
-        if(!isHiddenRow(av_name.getCompleteName()))
-        {
-            item_params.columns.add().value(av_name.getCompleteName()).column("name");
-            std::string setting = getString(iter->second == 1 ? "av_never_render" : "av_always_render");
-            item_params.columns.add().value(setting).column("setting");
-            S32 mute_date = LLRenderMuteList::getInstance()->getVisualMuteDate(iter->first);
-            item_params.columns.add().value(createTimestamp(mute_date)).column("timestamp").alt_value(std::to_string(mute_date));
-            mAvatarSettingsList->addNameItemRow(item_params);
-        }
+        item_params.columns.add().value(av_name.getCompleteName()).column("name");
+        std::string setting = getString(iter->second == 1 ? "av_never_render" : "av_always_render");
+        item_params.columns.add().value(setting).column("setting");
+        mAvatarSettingsList->addNameItemRow(item_params);
     }
 }
 
-void LLFloaterAvatarRenderSettings::onFilterEdit(const std::string& search_string)
-{
-    std::string filter_upper = search_string;
-    LLStringUtil::toUpper(filter_upper);
-    if (mNameFilter != filter_upper)
-    {
-        mNameFilter = filter_upper;
-        mNeedsUpdate = true;
-    }
-}
-
-bool LLFloaterAvatarRenderSettings::isHiddenRow(const std::string& av_name)
-{
-    if (mNameFilter.empty()) return false;
-    std::string upper_name = av_name;
-    LLStringUtil::toUpper(upper_name);
-    return std::string::npos == upper_name.find(mNameFilter);
-}
-
 static LLVOAvatar* find_avatar(const LLUUID& id)
 {
     LLViewerObject *obj = gObjectList.findObject(id);
@@ -215,6 +189,10 @@ bool LLFloaterAvatarRenderSettings::isActionChecked(const LLSD& userdata, const
     {
         return (visual_setting == S32(LLVOAvatar::AV_RENDER_NORMALLY));
     }
+    else if ("non_default" == command_name)
+    {
+        return (visual_setting != S32(LLVOAvatar::AV_RENDER_NORMALLY));
+    }
     else if ("never" == command_name)
     {
         return (visual_setting == S32(LLVOAvatar::AV_DO_NOT_RENDER));
diff --git a/indra/newview/llfloateravatarrendersettings.h b/indra/newview/llfloateravatarrendersettings.h
index 00ee074f17510690e6924eac0a4021ce1b854a23..2e0a844afd451bf03b4365917c0abaa5ce85c99b 100644
--- a/indra/newview/llfloateravatarrendersettings.h
+++ b/indra/newview/llfloateravatarrendersettings.h
@@ -48,7 +48,6 @@ class LLFloaterAvatarRenderSettings : public LLFloater
     void onAvatarListRightClick(LLUICtrl* ctrl, S32 x, S32 y);
 
     void updateList();
-    void onFilterEdit(const std::string& search_string);
     void onCustomAction (const LLSD& userdata, const LLUUID& av_id);
     bool isActionChecked(const LLSD& userdata, const LLUUID& av_id);
     void onClickAdd(const LLSD& userdata);
@@ -59,15 +58,12 @@ class LLFloaterAvatarRenderSettings : public LLFloater
     static void setNeedsUpdate();
 
 private:
-    bool isHiddenRow(const std::string& av_name);
     void callbackAvatarPicked(const uuid_vec_t& ids, S32 visual_setting);
     void removePicker();
 
     bool mNeedsUpdate;
     LLListContextMenu* mContextMenu;
     LLNameListCtrl* mAvatarSettingsList;
-
-    std::string mNameFilter;
 };
 
 
diff --git a/indra/newview/llfloatereditsky.cpp b/indra/newview/llfloatereditsky.cpp
index 6bdc5ee8233496ecdd25192bdf67d44a104132d9..2d5e86869d8393e0bc4e5fa27f78892e32eae7fd 100644
--- a/indra/newview/llfloatereditsky.cpp
+++ b/indra/newview/llfloatereditsky.cpp
@@ -523,7 +523,7 @@ void LLFloaterEditSky::refreshSkyPresetsList()
 
     for (LLEnvironment::list_name_id_t::iterator it = list.begin(); it != list.end(); ++it)
     {
-        mSkyPresetCombo->add((*it).first, LLSDArray((*it).first)((*it).second));
+        mSkyPresetCombo->add((*it).first, llsd::array((*it).first, (*it).second));
     }
 
 	mSkyPresetCombo->setLabel(getString("combo_label"));
diff --git a/indra/newview/llfloatereditwater.cpp b/indra/newview/llfloatereditwater.cpp
index 6e7b777e70a31b4654737290fafb3f88981645e3..c44ae7faca97eef87ea67159c6aa57fc0731ed13 100644
--- a/indra/newview/llfloatereditwater.cpp
+++ b/indra/newview/llfloatereditwater.cpp
@@ -335,7 +335,7 @@ void LLFloaterEditWater::refreshWaterPresetsList()
 
     for (LLEnvironment::list_name_id_t::iterator it = list.begin(); it != list.end(); ++it)
     {
-        mWaterPresetCombo->add((*it).first, LLSDArray((*it).first)((*it).second));
+        mWaterPresetCombo->add((*it).first, llsd::array((*it).first, (*it).second));
     }
 
 	mWaterPresetCombo->setLabel(getString("combo_label"));
diff --git a/indra/newview/llfloaterperformance.cpp b/indra/newview/llfloaterperformance.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..19fc3e673e41f0a2523291cf7fcb14fa24528ad5
--- /dev/null
+++ b/indra/newview/llfloaterperformance.cpp
@@ -0,0 +1,748 @@
+/** 
+ * @file llfloaterperformance.cpp
+ *
+ * $LicenseInfo:firstyear=2021&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2021, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+#include "llfloaterperformance.h"
+
+#include "llagent.h"
+#include "llagentcamera.h"
+#include "llappearancemgr.h"
+#include "llavataractions.h"
+#include "llavatarrendernotifier.h"
+#include "llcheckboxctrl.h"
+#include "llcombobox.h"
+#include "llfeaturemanager.h"
+#include "llfloaterpreference.h" // LLAvatarComplexityControls
+#include "llfloaterreg.h"
+#include "llnamelistctrl.h"
+#include "llnotificationsutil.h"
+#include "llperfstats.h"
+#include "llpresetsmanager.h"
+#include "llradiogroup.h"
+#include "llsliderctrl.h"
+#include "lltextbox.h"
+#include "lltrans.h"
+#include "llviewerobjectlist.h"
+#include "llviewerwindow.h"
+#include "llvoavatar.h"
+#include "llvoavatarself.h"
+#include "llworld.h"
+#include "pipeline.h"
+
+const F32 REFRESH_INTERVAL = 1.0f;
+const S32 BAR_LEFT_PAD = 2;
+const S32 BAR_RIGHT_PAD = 5;
+const S32 BAR_BOTTOM_PAD = 9;
+
+class LLExceptionsContextMenu : public LLListContextMenu
+{
+public:
+    LLExceptionsContextMenu(LLFloaterPerformance* floater_settings)
+        :   mFloaterPerformance(floater_settings)
+    {}
+protected:
+    LLContextMenu* createMenu()
+    {
+        LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
+        LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar;
+        registrar.add("Settings.SetRendering", boost::bind(&LLFloaterPerformance::onCustomAction, mFloaterPerformance, _2, mUUIDs.front()));
+        enable_registrar.add("Settings.IsSelected", boost::bind(&LLFloaterPerformance::isActionChecked, mFloaterPerformance, _2, mUUIDs.front()));
+        LLContextMenu* menu = createFromFile("menu_avatar_rendering_settings.xml");
+
+        return menu;
+    }
+
+    LLFloaterPerformance* mFloaterPerformance;
+};
+
+LLFloaterPerformance::LLFloaterPerformance(const LLSD& key)
+:   LLFloater(key),
+    mUpdateTimer(new LLTimer())
+{
+    mContextMenu = new LLExceptionsContextMenu(this);
+}
+
+LLFloaterPerformance::~LLFloaterPerformance()
+{
+    mMaxARTChangedSignal.disconnect();
+    delete mContextMenu;
+    delete mUpdateTimer;
+}
+
+BOOL LLFloaterPerformance::postBuild()
+{
+    mMainPanel = getChild<LLPanel>("panel_performance_main");
+    mNearbyPanel = getChild<LLPanel>("panel_performance_nearby");
+    mComplexityPanel = getChild<LLPanel>("panel_performance_complexity");
+    mSettingsPanel = getChild<LLPanel>("panel_performance_preferences");
+    mHUDsPanel = getChild<LLPanel>("panel_performance_huds");
+    mAutoadjustmentsPanel = getChild<LLPanel>("panel_performance_autoadjustments");
+
+    getChild<LLPanel>("nearby_subpanel")->setMouseDownCallback(boost::bind(&LLFloaterPerformance::showSelectedPanel, this, mNearbyPanel));
+    getChild<LLPanel>("complexity_subpanel")->setMouseDownCallback(boost::bind(&LLFloaterPerformance::showSelectedPanel, this, mComplexityPanel));
+    getChild<LLPanel>("settings_subpanel")->setMouseDownCallback(boost::bind(&LLFloaterPerformance::showSelectedPanel, this, mSettingsPanel));
+    getChild<LLPanel>("huds_subpanel")->setMouseDownCallback(boost::bind(&LLFloaterPerformance::showSelectedPanel, this, mHUDsPanel));
+    getChild<LLPanel>("autoadjustments_subpanel")->setMouseDownCallback(boost::bind(&LLFloaterPerformance::showSelectedPanel, this, mAutoadjustmentsPanel));
+
+    initBackBtn(mNearbyPanel);
+    initBackBtn(mComplexityPanel);
+    initBackBtn(mSettingsPanel);
+    initBackBtn(mHUDsPanel);
+    initBackBtn(mAutoadjustmentsPanel);
+
+    mHUDList = mHUDsPanel->getChild<LLNameListCtrl>("hud_list");
+    mHUDList->setNameListType(LLNameListCtrl::SPECIAL);
+    mHUDList->setHoverIconName("StopReload_Off");
+    mHUDList->setIconClickedCallback(boost::bind(&LLFloaterPerformance::detachItem, this, _1));
+
+    mObjectList = mComplexityPanel->getChild<LLNameListCtrl>("obj_list");
+    mObjectList->setNameListType(LLNameListCtrl::SPECIAL);
+    mObjectList->setHoverIconName("StopReload_Off");
+    mObjectList->setIconClickedCallback(boost::bind(&LLFloaterPerformance::detachItem, this, _1));
+
+    mSettingsPanel->getChild<LLButton>("advanced_btn")->setCommitCallback(boost::bind(&LLFloaterPerformance::onClickAdvanced, this));
+    mSettingsPanel->getChild<LLButton>("defaults_btn")->setCommitCallback(boost::bind(&LLFloaterPerformance::onClickDefaults, this));
+    mSettingsPanel->getChild<LLRadioGroup>("graphics_quality")->setCommitCallback(boost::bind(&LLFloaterPerformance::onChangeQuality, this, _2));
+    mSettingsPanel->getChild<LLCheckBoxCtrl>("advanced_lighting_model")->setMouseDownCallback(boost::bind(&LLFloaterPerformance::onClickAdvancedLighting, this));
+    mSettingsPanel->getChild<LLComboBox>("ShadowDetail")->setMouseDownCallback(boost::bind(&LLFloaterPerformance::onClickShadows, this));
+
+    mNearbyPanel->getChild<LLButton>("exceptions_btn")->setCommitCallback(boost::bind(&LLFloaterPerformance::onClickExceptions, this));
+    mNearbyPanel->getChild<LLCheckBoxCtrl>("hide_avatars")->setCommitCallback(boost::bind(&LLFloaterPerformance::onClickHideAvatars, this));
+    mNearbyPanel->getChild<LLCheckBoxCtrl>("hide_avatars")->set(!LLPipeline::hasRenderTypeControl(LLPipeline::RENDER_TYPE_AVATAR));
+    mNearbyList = mNearbyPanel->getChild<LLNameListCtrl>("nearby_list");
+    mNearbyList->setRightMouseDownCallback(boost::bind(&LLFloaterPerformance::onAvatarListRightClick, this, _1, _2, _3));
+
+    mMaxARTChangedSignal = gSavedSettings.getControl("RenderAvatarMaxART")->getCommitSignal()->connect(boost::bind(&LLFloaterPerformance::updateMaxRenderTime, this));
+    mNearbyPanel->getChild<LLSliderCtrl>("RenderAvatarMaxART")->setCommitCallback(boost::bind(&LLFloaterPerformance::updateMaxRenderTime, this));
+
+    if(!LLPerfStats::tunables.userAutoTuneEnabled)
+    {
+        gSavedSettings.setF32("AutoTuneRenderFarClipTarget", LLPipeline::RenderFarClip);
+    }
+
+    LLStringExplicit fps_limit(llformat("%d", gViewerWindow->getWindow()->getRefreshRate()));
+    mAutoadjustmentsPanel->getChild<LLTextBox>("vsync_desc_limit")->setTextArg("[FPS_LIMIT]", fps_limit);
+    mAutoadjustmentsPanel->getChild<LLTextBox>("display_desc")->setTextArg("[FPS_LIMIT]", fps_limit);
+    mAutoadjustmentsPanel->getChild<LLButton>("defaults_btn")->setCommitCallback(boost::bind(&LLFloaterPerformance::onClickDefaults, this));
+
+    mStartAutotuneBtn = mAutoadjustmentsPanel->getChild<LLButton>("start_autotune");
+    mStopAutotuneBtn = mAutoadjustmentsPanel->getChild<LLButton>("stop_autotune");
+    mStartAutotuneBtn->setCommitCallback(boost::bind(&LLFloaterPerformance::startAutotune, this));
+    mStopAutotuneBtn->setCommitCallback(boost::bind(&LLFloaterPerformance::stopAutotune, this));
+
+    gSavedPerAccountSettings.declareBOOL("HadEnabledAutoFPS", FALSE, "User had enabled AutoFPS at least once", LLControlVariable::PERSIST_ALWAYS);
+
+    return TRUE;
+}
+
+void LLFloaterPerformance::showSelectedPanel(LLPanel* selected_panel)
+{
+    hidePanels();
+    mMainPanel->setVisible(FALSE);
+    selected_panel->setVisible(TRUE);
+
+    if (mHUDsPanel == selected_panel)
+    {
+        populateHUDList();
+    }
+    else if (mNearbyPanel == selected_panel)
+    {
+        populateNearbyList();
+    }
+    else if (mComplexityPanel == selected_panel)
+    {
+        populateObjectList();
+    }
+}
+
+void LLFloaterPerformance::showAutoadjustmentsPanel()
+{
+    showSelectedPanel(mAutoadjustmentsPanel);
+}
+
+void LLFloaterPerformance::draw()
+{
+    enableAutotuneWarning();
+
+    if (mUpdateTimer->hasExpired() && 
+        !LLFloaterReg::instanceVisible("save_pref_preset", PRESETS_GRAPHIC)) // give user a chance to save the graphics settings before updating them
+    {
+        setFPSText();
+        if (mHUDsPanel->getVisible())
+        {
+            populateHUDList();
+        }
+        else if (mNearbyPanel->getVisible())
+        {
+            populateNearbyList();
+            mNearbyPanel->getChild<LLCheckBoxCtrl>("hide_avatars")->set(!LLPipeline::hasRenderTypeControl(LLPipeline::RENDER_TYPE_AVATAR));
+        }
+        else if (mComplexityPanel->getVisible())
+        {
+            populateObjectList();
+        }
+
+        mUpdateTimer->setTimerExpirySec(REFRESH_INTERVAL);
+    }
+    updateAutotuneCtrls(LLPerfStats::tunables.userAutoTuneEnabled);
+
+    LLFloater::draw();
+}
+
+void LLFloaterPerformance::showMainPanel()
+{
+    hidePanels();
+    mMainPanel->setVisible(TRUE);
+}
+
+void LLFloaterPerformance::hidePanels()
+{
+    mNearbyPanel->setVisible(FALSE);
+    mComplexityPanel->setVisible(FALSE);
+    mHUDsPanel->setVisible(FALSE);
+    mSettingsPanel->setVisible(FALSE);
+    mAutoadjustmentsPanel->setVisible(FALSE);
+}
+
+void LLFloaterPerformance::initBackBtn(LLPanel* panel)
+{
+    panel->getChild<LLButton>("back_btn")->setCommitCallback(boost::bind(&LLFloaterPerformance::showMainPanel, this));
+
+    panel->getChild<LLTextBox>("back_lbl")->setShowCursorHand(false);
+    panel->getChild<LLTextBox>("back_lbl")->setSoundFlags(LLView::MOUSE_UP);
+    panel->getChild<LLTextBox>("back_lbl")->setClickedCallback(boost::bind(&LLFloaterPerformance::showMainPanel, this));
+}
+
+void LLFloaterPerformance::populateHUDList()
+{
+    S32 prev_pos = mHUDList->getScrollPos();
+    LLUUID prev_selected_id = mHUDList->getSelectedSpecialId();
+    mHUDList->clearRows();
+    mHUDList->updateColumns(true);
+
+    LLVOAvatar* avatar = gAgentAvatarp;
+
+    gPipeline.profileAvatar(avatar, true);
+
+    LLVOAvatar::attachment_map_t::iterator iter;
+    LLVOAvatar::attachment_map_t::iterator begin = avatar->mAttachmentPoints.begin();
+    LLVOAvatar::attachment_map_t::iterator end = avatar->mAttachmentPoints.end();
+
+    // get max gpu render time of all attachments
+    F32 max_gpu_time = -1.f;
+
+    for (iter = begin; iter != end; ++iter)
+    {
+        LLViewerJointAttachment* attachment = iter->second;
+        for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+            attachment_iter != attachment->mAttachedObjects.end();
+            ++attachment_iter)
+        {
+            LLViewerObject* attached_object = attachment_iter->get();
+            if (attached_object && attached_object->isHUDAttachment())
+            {
+                max_gpu_time = llmax(max_gpu_time, attached_object->mGPURenderTime);
+            }
+        }
+    }
+
+
+    for (iter = begin; iter != end; ++iter)
+    {
+        if (!iter->second)
+        {
+            continue;
+        }
+
+        LLViewerJointAttachment* attachment = iter->second;
+        for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+            attachment_iter != attachment->mAttachedObjects.end();
+            ++attachment_iter)
+        {
+            LLViewerObject* attached_object = attachment_iter->get();
+            if (attached_object && attached_object->isHUDAttachment())
+            {
+                F32 gpu_time = attached_object->mGPURenderTime;
+
+                LLSD item;
+                item["special_id"] = attached_object->getID();
+                item["target"] = LLNameListCtrl::SPECIAL;
+                LLSD& row = item["columns"];
+                row[0]["column"] = "complex_visual";
+                row[0]["type"] = "bar";
+                LLSD& value = row[0]["value"];
+                value["ratio"] = gpu_time / max_gpu_time;
+                value["bottom"] = BAR_BOTTOM_PAD;
+                value["left_pad"] = BAR_LEFT_PAD;
+                value["right_pad"] = BAR_RIGHT_PAD;
+
+                row[1]["column"] = "complex_value";
+                row[1]["type"] = "text";
+                // show gpu time in us
+                row[1]["value"] = llformat("%.f", gpu_time * 1000.f);
+                row[1]["font"]["name"] = "SANSSERIF";
+
+                row[2]["column"] = "name";
+                row[2]["type"] = "text";
+                row[2]["value"] = attached_object->getAttachmentItemName();
+                row[2]["font"]["name"] = "SANSSERIF";
+
+                LLScrollListItem* obj = mHUDList->addElement(item);
+                if (obj)
+                {
+                    LLScrollListText* value_text = dynamic_cast<LLScrollListText*>(obj->getColumn(1));
+                    if (value_text)
+                    {
+                        value_text->setAlignment(LLFontGL::HCENTER);
+                    }
+                }
+            }
+        }
+    }
+    mHUDList->sortByColumnIndex(1, FALSE);
+    mHUDList->setScrollPos(prev_pos);
+    mHUDList->selectItemBySpecialId(prev_selected_id);
+}
+
+void LLFloaterPerformance::populateObjectList()
+{
+    S32 prev_pos = mObjectList->getScrollPos();
+    LLUUID prev_selected_id = mObjectList->getSelectedSpecialId();
+    mObjectList->clearRows();
+    mObjectList->updateColumns(true);
+
+    LLVOAvatar* avatar = gAgentAvatarp;
+
+    gPipeline.profileAvatar(avatar, true);
+
+    LLVOAvatar::attachment_map_t::iterator iter;
+    LLVOAvatar::attachment_map_t::iterator begin = avatar->mAttachmentPoints.begin();
+    LLVOAvatar::attachment_map_t::iterator end = avatar->mAttachmentPoints.end();
+
+    // get max gpu render time of all attachments
+    F32 max_gpu_time = -1.f;
+
+    for (iter = begin; iter != end; ++iter)
+    {
+        LLViewerJointAttachment* attachment = iter->second;
+        for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+            attachment_iter != attachment->mAttachedObjects.end();
+            ++attachment_iter)
+        {
+            LLViewerObject* attached_object = attachment_iter->get();
+            if (attached_object && !attached_object->isHUDAttachment())
+            {
+                max_gpu_time = llmax(max_gpu_time, attached_object->mGPURenderTime);
+            }
+        }
+    }
+
+    {
+        for (iter = begin; iter != end; ++iter)
+        {
+            if (!iter->second)
+            {
+                continue;
+            }
+
+            LLViewerJointAttachment* attachment = iter->second;
+            for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+                attachment_iter != attachment->mAttachedObjects.end();
+                ++attachment_iter)
+            {
+                LLViewerObject* attached_object = attachment_iter->get();
+                if (attached_object && !attached_object->isHUDAttachment())
+                {
+                    F32 gpu_time = attached_object->mGPURenderTime;
+
+                    LLSD item;
+                    item["special_id"] = attached_object->getID();
+                    item["target"] = LLNameListCtrl::SPECIAL;
+                    LLSD& row = item["columns"];
+                    row[0]["column"] = "complex_visual";
+                    row[0]["type"] = "bar";
+                    LLSD& value = row[0]["value"];
+                    value["ratio"] = gpu_time / max_gpu_time;
+                    value["bottom"] = BAR_BOTTOM_PAD;
+                    value["left_pad"] = BAR_LEFT_PAD;
+                    value["right_pad"] = BAR_RIGHT_PAD;
+
+                    row[1]["column"] = "complex_value";
+                    row[1]["type"] = "text";
+                    // show gpu time in us
+                    row[1]["value"] = llformat("%.f", gpu_time * 1000.f);
+                    row[1]["font"]["name"] = "SANSSERIF";
+
+                    row[2]["column"] = "name";
+                    row[2]["type"] = "text";
+                    row[2]["value"] = attached_object->getAttachmentItemName();
+                    row[2]["font"]["name"] = "SANSSERIF";
+
+                    LLScrollListItem* obj = mObjectList->addElement(item);
+                    if (obj)
+                    {
+                        LLScrollListText* value_text = dynamic_cast<LLScrollListText*>(obj->getColumn(1));
+                        if (value_text)
+                        {
+                            value_text->setAlignment(LLFontGL::HCENTER);
+                        }
+                    }
+                }
+            }
+        }
+    }
+    mObjectList->sortByColumnIndex(1, FALSE);
+    mObjectList->setScrollPos(prev_pos);
+    mObjectList->selectItemBySpecialId(prev_selected_id);
+}
+
+void LLFloaterPerformance::populateNearbyList()
+{
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_APP;
+    static LLCachedControl<bool> showTunedART(gSavedSettings, "ShowTunedART");
+    S32 prev_pos = mNearbyList->getScrollPos();
+    LLUUID prev_selected_id = mNearbyList->getStringUUIDSelectedItem();
+    mNearbyList->clearRows();
+    mNearbyList->updateColumns(true);
+
+    static LLCachedControl<U32> max_render_cost(gSavedSettings, "RenderAvatarMaxComplexity", 0);
+    std::vector<LLCharacter*> valid_nearby_avs;
+    mNearbyMaxGPUTime = LLWorld::getInstance()->getNearbyAvatarsAndMaxGPUTime(valid_nearby_avs);
+
+    std::vector<LLCharacter*>::iterator char_iter = valid_nearby_avs.begin();
+
+    while (char_iter != valid_nearby_avs.end())
+    {
+        LLVOAvatar* avatar = dynamic_cast<LLVOAvatar*>(*char_iter);
+        if (avatar && (LLVOAvatar::AOA_INVISIBLE != avatar->getOverallAppearance()))
+        {
+            F32 render_av_gpu_ms = avatar->getGPURenderTime();
+
+            auto is_slow = avatar->isTooSlow();
+            LLSD item;
+            item["id"] = avatar->getID();
+            LLSD& row = item["columns"];
+            row[0]["column"] = "complex_visual";
+            row[0]["type"] = "bar";
+            LLSD& value = row[0]["value"];
+            // The ratio used in the bar is the current cost, as soon as we take action this changes so we keep the 
+            // pre-tune value for the numerical column and sorting.
+            value["ratio"] = render_av_gpu_ms / mNearbyMaxGPUTime;
+            value["bottom"] = BAR_BOTTOM_PAD;
+            value["left_pad"] = BAR_LEFT_PAD;
+            value["right_pad"] = BAR_RIGHT_PAD;
+
+            row[1]["column"] = "complex_value";
+            row[1]["type"] = "text";
+            // use GPU time in us
+            row[1]["value"] = llformat( "%.f", render_av_gpu_ms * 1000.f);
+            row[1]["font"]["name"] = "SANSSERIF";
+
+            row[3]["column"] = "name";
+            row[3]["type"] = "text";
+            row[3]["value"] = avatar->getFullname();
+            row[3]["font"]["name"] = "SANSSERIF";
+
+            LLScrollListItem* av_item = mNearbyList->addElement(item);
+            if(av_item)
+            {
+                LLScrollListText* value_text = dynamic_cast<LLScrollListText*>(av_item->getColumn(1));
+                if (value_text)
+                {
+                    value_text->setAlignment(LLFontGL::HCENTER);
+                }
+                LLScrollListText* name_text = dynamic_cast<LLScrollListText*>(av_item->getColumn(2));
+                if (name_text)
+                {
+                    if (avatar->isSelf())
+                    {
+                        name_text->setColor(LLUIColorTable::instance().getColor("DrYellow"));
+                    }
+                    else
+                    {
+                        std::string color = "white";
+                        if (is_slow || LLVOAvatar::AOA_JELLYDOLL == avatar->getOverallAppearance())
+                        {
+                            color = "LabelDisabledColor";
+                            LLScrollListBar* bar = dynamic_cast<LLScrollListBar*>(av_item->getColumn(0));
+                            if (bar)
+                            {
+                                bar->setColor(LLUIColorTable::instance().getColor(color));
+                            }
+                        }
+                        else if (LLVOAvatar::AOA_NORMAL == avatar->getOverallAppearance())
+                        {
+                            color = LLAvatarActions::isFriend(avatar->getID()) ? "ConversationFriendColor" : "white";
+                        }
+                        name_text->setColor(LLUIColorTable::instance().getColor(color));
+                    }
+                }
+            }
+        }
+        char_iter++;
+    }
+    mNearbyList->sortByColumnIndex(1, FALSE);
+    mNearbyList->setScrollPos(prev_pos);
+    mNearbyList->selectByID(prev_selected_id);
+}
+
+void LLFloaterPerformance::setFPSText()
+{
+    const S32 NUM_PERIODS = 50;
+    S32 current_fps = (S32)llround(LLTrace::get_frame_recording().getPeriodMedianPerSec(LLStatViewer::FPS, NUM_PERIODS));
+    getChild<LLTextBox>("fps_value")->setValue(current_fps);
+
+    std::string fps_text = getString("fps_text");
+    static LLCachedControl<bool> vsync_enabled(gSavedSettings, "RenderVSyncEnable", true);
+    S32 refresh_rate = gViewerWindow->getWindow()->getRefreshRate();
+    if (vsync_enabled && (refresh_rate > 0) && (current_fps >= refresh_rate))
+    {
+        fps_text += getString("max_text");
+    }
+    getChild<LLTextBox>("fps_lbl")->setValue(fps_text);
+}
+
+void LLFloaterPerformance::detachItem(const LLUUID& item_id)
+{
+    LLAppearanceMgr::instance().removeItemFromAvatar(item_id);
+}
+
+void LLFloaterPerformance::onClickAdvanced()
+{
+    LLFloaterPreference* instance = LLFloaterReg::getTypedInstance<LLFloaterPreference>("preferences");
+    if (instance)
+    {
+        instance->saveSettings();
+    }
+    LLFloaterReg::showInstance("prefs_graphics_advanced");
+}
+
+void LLFloaterPerformance::onClickDefaults()
+{
+    LLFloaterPreference* instance = LLFloaterReg::getTypedInstance<LLFloaterPreference>("preferences");
+    if (instance)
+    {
+        instance->setRecommendedSettings();
+    }
+}
+
+void LLFloaterPerformance::onChangeQuality(const LLSD& data)
+{
+    LLFloaterPreference* instance = LLFloaterReg::getTypedInstance<LLFloaterPreference>("preferences");
+    if (instance)
+    {
+        instance->onChangeQuality(data);
+    }
+}
+
+void LLFloaterPerformance::onClickHideAvatars()
+{
+    LLPipeline::toggleRenderTypeControl(LLPipeline::RENDER_TYPE_AVATAR);
+}
+
+void LLFloaterPerformance::onClickExceptions()
+{
+    LLFloaterReg::showInstance("avatar_render_settings");
+}
+
+void LLFloaterPerformance::updateMaxRenderTime()
+{
+    LLAvatarComplexityControls::updateMaxRenderTime(
+        mNearbyPanel->getChild<LLSliderCtrl>("RenderAvatarMaxART"),
+        mNearbyPanel->getChild<LLTextBox>("RenderAvatarMaxARTText"), 
+        true);
+}
+
+static LLVOAvatar* find_avatar(const LLUUID& id)
+{
+    LLViewerObject *obj = gObjectList.findObject(id);
+    while (obj && obj->isAttachment())
+    {
+        obj = (LLViewerObject *)obj->getParent();
+    }
+
+    if (obj && obj->isAvatar())
+    {
+        return (LLVOAvatar*)obj;
+    }
+    else
+    {
+        return NULL;
+    }
+}
+
+void LLFloaterPerformance::onCustomAction(const LLSD& userdata, const LLUUID& av_id)
+{
+    const std::string command_name = userdata.asString();
+
+    S32 new_setting = 0;
+    if ("default" == command_name)
+    {
+        new_setting = S32(LLVOAvatar::AV_RENDER_NORMALLY);
+    }
+    else if ("never" == command_name)
+    {
+        new_setting = S32(LLVOAvatar::AV_DO_NOT_RENDER);
+    }
+    else if ("always" == command_name)
+    {
+        new_setting = S32(LLVOAvatar::AV_ALWAYS_RENDER);
+    }
+
+    LLVOAvatar *avatarp = find_avatar(av_id);
+    if (avatarp)
+    {
+        avatarp->setVisualMuteSettings(LLVOAvatar::VisualMuteSettings(new_setting));
+    }
+    else
+    {
+        LLRenderMuteList::getInstance()->saveVisualMuteSetting(av_id, new_setting);
+    }
+}
+
+
+bool LLFloaterPerformance::isActionChecked(const LLSD& userdata, const LLUUID& av_id)
+{
+    const std::string command_name = userdata.asString();
+
+    S32 visual_setting = LLRenderMuteList::getInstance()->getSavedVisualMuteSetting(av_id);
+    if ("default" == command_name)
+    {
+        return (visual_setting == S32(LLVOAvatar::AV_RENDER_NORMALLY));
+    }
+    else if ("non_default" == command_name)
+    {
+        return (visual_setting != S32(LLVOAvatar::AV_RENDER_NORMALLY));
+    }
+    else if ("never" == command_name)
+    {
+        return (visual_setting == S32(LLVOAvatar::AV_DO_NOT_RENDER));
+    }
+    else if ("always" == command_name)
+    {
+        return (visual_setting == S32(LLVOAvatar::AV_ALWAYS_RENDER));
+    }
+    return false;
+}
+
+void LLFloaterPerformance::onAvatarListRightClick(LLUICtrl* ctrl, S32 x, S32 y)
+{
+    LLNameListCtrl* list = dynamic_cast<LLNameListCtrl*>(ctrl);
+    if (!list) return;
+    list->selectItemAt(x, y, MASK_NONE);
+    uuid_vec_t selected_uuids;
+
+    if((list->getCurrentID().notNull()) && (list->getCurrentID() != gAgentID))
+    {
+        selected_uuids.push_back(list->getCurrentID());
+        mContextMenu->show(ctrl, selected_uuids, x, y);
+    }
+}
+
+const U32 RENDER_QUALITY_LEVEL = 3;
+void LLFloaterPerformance::changeQualityLevel(const std::string& notif)
+{
+    LLNotificationsUtil::add(notif, LLSD(), LLSD(),
+        [](const LLSD&notif, const LLSD&resp)
+    {
+        S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp);
+        if (opt == 0)
+        {
+            LLFloaterPreference* instance = LLFloaterReg::getTypedInstance<LLFloaterPreference>("preferences");
+            if (instance)
+            {
+                gSavedSettings.setU32("RenderQualityPerformance", RENDER_QUALITY_LEVEL);
+                instance->onChangeQuality(LLSD((S32)RENDER_QUALITY_LEVEL));
+            }
+        }
+    });
+}
+
+bool is_ALM_available()
+{
+    bool bumpshiny = LLCubeMap::sUseCubeMaps && LLFeatureManager::getInstance()->isFeatureAvailable("RenderObjectBump") && gSavedSettings.getBOOL("RenderObjectBump");
+    bool shaders = gSavedSettings.getBOOL("WindLightUseAtmosShaders");
+    
+    return LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred") &&
+        bumpshiny &&
+        shaders;
+}
+
+void LLFloaterPerformance::onClickAdvancedLighting()
+{
+    if (!is_ALM_available())
+    {
+        changeQualityLevel("AdvancedLightingConfirm");
+    }
+}
+
+void LLFloaterPerformance::onClickShadows()
+{
+    if (!is_ALM_available() || !gSavedSettings.getBOOL("RenderDeferred"))
+    {
+        changeQualityLevel("ShadowsConfirm");
+    }
+
+}
+
+void LLFloaterPerformance::startAutotune()
+{
+    LLPerfStats::tunables.userAutoTuneEnabled = true;
+}
+
+void LLFloaterPerformance::stopAutotune()
+{
+    LLPerfStats::tunables.userAutoTuneEnabled = false;
+}
+
+void LLFloaterPerformance::updateAutotuneCtrls(bool autotune_enabled)
+{
+    static LLCachedControl<bool> auto_tune_locked(gSavedSettings, "AutoTuneLock");
+    mStartAutotuneBtn->setEnabled(!autotune_enabled && !auto_tune_locked);
+    mStopAutotuneBtn->setEnabled(autotune_enabled && !auto_tune_locked);
+    getChild<LLCheckBoxCtrl>("AutoTuneContinuous")->setEnabled(!autotune_enabled || (autotune_enabled && auto_tune_locked));
+
+    getChild<LLTextBox>("wip_desc")->setVisible(autotune_enabled && !auto_tune_locked);
+    getChild<LLTextBox>("display_desc")->setVisible(LLPerfStats::tunables.vsyncEnabled);
+}
+
+void LLFloaterPerformance::enableAutotuneWarning()
+{
+    if (!gSavedPerAccountSettings.getBOOL("HadEnabledAutoFPS") && LLPerfStats::tunables.userAutoTuneEnabled)
+    {
+        gSavedPerAccountSettings.setBOOL("HadEnabledAutoFPS", TRUE);
+
+        LLNotificationsUtil::add("EnableAutoFPSWarning", LLSD(), LLSD(),
+            [](const LLSD& notif, const LLSD& resp)
+            {
+                S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp);
+                if (opt == 0)
+                { // offer user to save current graphics settings as a preset
+                    LLFloaterReg::showInstance("save_pref_preset", PRESETS_GRAPHIC);
+                }
+            });
+    }
+}
+// EOF
diff --git a/indra/newview/llfloaterperformance.h b/indra/newview/llfloaterperformance.h
new file mode 100644
index 0000000000000000000000000000000000000000..620dbac5bb7c117a6b346793f02c53a3789398f6
--- /dev/null
+++ b/indra/newview/llfloaterperformance.h
@@ -0,0 +1,104 @@
+/** 
+ * @file llfloaterperformance.h
+ *
+ * $LicenseInfo:firstyear=2021&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2021, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLFLOATERPERFORMANCE_H
+#define LL_LLFLOATERPERFORMANCE_H
+
+#include "llfloater.h"
+#include "lllistcontextmenu.h"
+
+class LLCharacter;
+class LLNameListCtrl;
+
+class LLFloaterPerformance : public LLFloater
+{
+public:
+    LLFloaterPerformance(const LLSD& key);
+    virtual ~LLFloaterPerformance();
+
+    /*virtual*/ BOOL postBuild();
+    /*virtual*/ void draw();
+
+    void showSelectedPanel(LLPanel* selected_panel);
+    void showMainPanel();
+    void hidePanels();
+    void showAutoadjustmentsPanel();
+
+    void detachItem(const LLUUID& item_id);
+
+    void onAvatarListRightClick(LLUICtrl* ctrl, S32 x, S32 y);
+
+    void onCustomAction (const LLSD& userdata, const LLUUID& av_id);
+    bool isActionChecked(const LLSD& userdata, const LLUUID& av_id);
+
+private:
+    void initBackBtn(LLPanel* panel);
+    void populateHUDList();
+    void populateObjectList();
+    void populateNearbyList();
+    void setFPSText();
+
+    void onClickAdvanced();
+    void onClickDefaults();
+    void onChangeQuality(const LLSD& data);
+    void onClickHideAvatars();
+    void onClickExceptions();
+    void onClickShadows();
+    void onClickAdvancedLighting();
+
+    void startAutotune();
+    void stopAutotune();
+    void updateAutotuneCtrls(bool autotune_enabled);
+    void enableAutotuneWarning();
+
+    void updateMaxRenderTime();
+
+    static void changeQualityLevel(const std::string& notif);
+
+    LLPanel* mMainPanel;
+    LLPanel* mNearbyPanel;
+    LLPanel* mComplexityPanel;
+    LLPanel* mHUDsPanel;
+    LLPanel* mSettingsPanel;
+    LLPanel* mAutoadjustmentsPanel;
+    LLNameListCtrl* mHUDList;
+    LLNameListCtrl* mObjectList;
+    LLNameListCtrl* mNearbyList;
+
+    LLButton* mStartAutotuneBtn;
+    LLButton* mStopAutotuneBtn;
+
+    LLListContextMenu* mContextMenu;
+
+    LLTimer* mUpdateTimer;
+
+    // maximum GPU time of nearby avatars in ms according to LLWorld::getNearbyAvatarsAndMaxGPUTime
+    // -1.f if no profile has happened yet
+    F32 mNearbyMaxGPUTime = -1.f;
+
+    boost::signals2::connection	mMaxARTChangedSignal;
+};
+
+#endif // LL_LLFLOATERPERFORMANCE_H
diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp
index 7487c1c9f91f5642cc1dae29985e369985ad7f43..d4e40ff1037fc833bc815ef2dd3acc90495a61de 100644
--- a/indra/newview/llfloaterpreference.cpp
+++ b/indra/newview/llfloaterpreference.cpp
@@ -50,6 +50,8 @@
 #include "llfloaterreg.h"
 #include "llfloaterabout.h"
 #include "llfavoritesbar.h"
+#include "llfloaterpreferencesgraphicsadvanced.h"
+#include "llfloaterperformance.h"
 #include "llfloatersidepanelcontainer.h"
 #include "llfloaterimsession.h"
 #include "llkeyboard.h"
@@ -74,7 +76,6 @@
 #include "llviewereventrecorder.h"
 #include "llviewermessage.h"
 #include "llviewerwindow.h"
-#include "llviewershadermgr.h"
 #include "llviewerthrottle.h"
 #include "llvoavatarself.h"
 #include "llvotree.h"
@@ -97,11 +98,9 @@
 #include "lltextbox.h"
 #include "llui.h"
 #include "llviewerobjectlist.h"
-#include "llvoavatar.h"
 #include "llvovolume.h"
 #include "llwindow.h"
 #include "llworld.h"
-#include "pipeline.h"
 #include "lluictrlfactory.h"
 #include "llviewermedia.h"
 #include "llpluginclassmedia.h"
@@ -114,10 +113,9 @@
 #include "llpresetsmanager.h"
 #include "llviewercontrol.h"
 #include "llpresetsmanager.h"
-#include "llfeaturemanager.h"
-#include "llviewertexturelist.h"
 
 #include "llsearchableui.h"
+#include "llperfstats.h"
 
 const F32 BANDWIDTH_UPDATER_TIMEOUT = 0.5f;
 char const* const VISIBILITY_DEFAULT = "default";
@@ -289,6 +287,7 @@ LLFloaterPreference::LLFloaterPreference(const LLSD& key)
 	mCommitCallbackRegistrar.add("Pref.ClickDisablePopup",		boost::bind(&LLFloaterPreference::onClickDisablePopup, this));	
 	mCommitCallbackRegistrar.add("Pref.LogPath",				boost::bind(&LLFloaterPreference::onClickLogPath, this));
 	mCommitCallbackRegistrar.add("Pref.RenderExceptions",       boost::bind(&LLFloaterPreference::onClickRenderExceptions, this));
+	mCommitCallbackRegistrar.add("Pref.AutoAdjustments",         boost::bind(&LLFloaterPreference::onClickAutoAdjustments, this));
 	mCommitCallbackRegistrar.add("Pref.HardwareDefaults",		boost::bind(&LLFloaterPreference::setHardwareDefaults, this));
 	mCommitCallbackRegistrar.add("Pref.AvatarImpostorsEnable",	boost::bind(&LLFloaterPreference::onAvatarImpostorsEnable, this));
 	mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxComplexity",	boost::bind(&LLFloaterPreference::updateMaxComplexity, this));
@@ -317,9 +316,12 @@ LLFloaterPreference::LLFloaterPreference(const LLSD& key)
 	gSavedSettings.getControl("UseDisplayNames")->getCommitSignal()->connect(boost::bind(&handleDisplayNamesOptionChanged,  _2));
 
 	gSavedSettings.getControl("AppearanceCameraMovement")->getCommitSignal()->connect(boost::bind(&handleAppearanceCameraMovementChanged,  _2));
+    gSavedSettings.getControl("WindLightUseAtmosShaders")->getCommitSignal()->connect(boost::bind(&LLFloaterPreference::onAtmosShaderChange, this));
 
 	LLAvatarPropertiesProcessor::getInstance()->addObserver( gAgent.getID(), this );
 
+    mComplexityChangedSignal = gSavedSettings.getControl("RenderAvatarMaxComplexity")->getCommitSignal()->connect(boost::bind(&LLFloaterPreference::updateComplexityText, this));
+
 	mCommitCallbackRegistrar.add("Pref.ClearLog",				boost::bind(&LLConversationLog::onClearLog, &LLConversationLog::instance()));
 	mCommitCallbackRegistrar.add("Pref.DeleteTranscripts",      boost::bind(&LLFloaterPreference::onDeleteTranscripts, this));
 	mCommitCallbackRegistrar.add("UpdateFilter", boost::bind(&LLFloaterPreference::onUpdateFilterTerm, this, false)); // <FS:ND/> Hook up for filtering
@@ -487,6 +489,7 @@ void LLFloaterPreference::onDoNotDisturbResponseChanged()
 LLFloaterPreference::~LLFloaterPreference()
 {
 	LLConversationLog::instance().removeObserver(this);
+    mComplexityChangedSignal.disconnect();
 }
 
 void LLFloaterPreference::draw()
@@ -730,13 +733,15 @@ void LLFloaterPreference::onOpen(const LLSD& key)
 	LLButton* save_btn = findChild<LLButton>("PrefSaveButton");
 	LLButton* delete_btn = findChild<LLButton>("PrefDeleteButton");
 	LLButton* exceptions_btn = findChild<LLButton>("RenderExceptionsButton");
+    LLButton* auto_adjustments_btn = findChild<LLButton>("AutoAdjustmentsButton");
 
-	if (load_btn && save_btn && delete_btn && exceptions_btn)
+	if (load_btn && save_btn && delete_btn && exceptions_btn && auto_adjustments_btn)
 	{
 		load_btn->setEnabled(started);
 		save_btn->setEnabled(started);
 		delete_btn->setEnabled(started);
 		exceptions_btn->setEnabled(started);
+        auto_adjustments_btn->setEnabled(started);
 	}
 
     collectSearchableItems();
@@ -752,33 +757,6 @@ void LLFloaterPreference::onRenderOptionEnable()
 	refreshEnabledGraphics();
 }
 
-void LLFloaterPreferenceGraphicsAdvanced::onRenderOptionEnable()
-{
-	LLFloaterPreference* instance = LLFloaterReg::findTypedInstance<LLFloaterPreference>("preferences");
-	if (instance)
-	{
-		instance->refresh();
-	}
-
-	refreshEnabledGraphics();
-}
-
-void LLFloaterPreferenceGraphicsAdvanced::onAdvancedAtmosphericsEnable()
-{
-	LLFloaterPreference* instance = LLFloaterReg::findTypedInstance<LLFloaterPreference>("preferences");
-	if (instance)
-	{
-		instance->refresh();
-	}
-
-	refreshEnabledGraphics();
-}
-
-void LLFloaterPreferenceGraphicsAdvanced::refreshEnabledGraphics()
-{
-	refreshEnabledState();
-}
-
 void LLFloaterPreference::onAvatarImpostorsEnable()
 {
 	refreshEnabledGraphics();
@@ -812,6 +790,13 @@ void LLFloaterPreference::setHardwareDefaults()
 		saveGraphicsPreset(preset_graphic_active);
 		saveSettings(); // save here to be able to return to the previous preset by Cancel
 	}
+    setRecommendedSettings();
+}
+
+void LLFloaterPreference::setRecommendedSettings()
+{
+    resetAutotuneSettings();
+    gSavedSettings.getControl("RenderVSyncEnable")->resetToDefault(true);
 
 	LLFeatureManager::getInstance()->applyRecommendedSettings();
 
@@ -836,6 +821,28 @@ void LLFloaterPreference::setHardwareDefaults()
 	}
 }
 
+void LLFloaterPreference::resetAutotuneSettings()
+{
+    gSavedSettings.setBOOL("AutoTuneFPS", FALSE);
+
+    const std::string autotune_settings[] = {
+        "AutoTuneLock",
+        "KeepAutoTuneLock",
+        "TargetFPS",
+        "TuningFPSStrategy",
+        "AutoTuneImpostorByDistEnabled",
+        "AutoTuneImpostorFarAwayDistance" ,
+        "AutoTuneRenderFarClipMin",
+        "AutoTuneRenderFarClipTarget",
+        "RenderAvatarMaxART"
+    };
+
+    for (auto it : autotune_settings)
+    {
+        gSavedSettings.getControl(it)->resetToDefault(true);
+    }
+}
+
 void LLFloaterPreference::getControlNames(std::vector<std::string>& names)
 {
 	LLView* view = findChild<LLView>("display");
@@ -1194,62 +1201,6 @@ void LLFloaterPreference::refreshEnabledState()
 	getChildView("block_list")->setEnabled(LLLoginInstance::getInstance()->authSuccess());
 }
 
-void LLFloaterPreferenceGraphicsAdvanced::refreshEnabledState()
-{
-    // WindLight
-    LLSliderCtrl* sky = getChild<LLSliderCtrl>("SkyMeshDetail");
-    LLTextBox* sky_text = getChild<LLTextBox>("SkyMeshDetailText");
-    sky->setEnabled(TRUE);
-    sky_text->setEnabled(TRUE);
-
-	LLCheckBoxCtrl* ctrl_ssao = getChild<LLCheckBoxCtrl>("UseSSAO");
-	LLCheckBoxCtrl* ctrl_dof = getChild<LLCheckBoxCtrl>("UseDoF");
-	LLComboBox* ctrl_shadow = getChild<LLComboBox>("ShadowDetail");
-	LLTextBox* shadow_text = getChild<LLTextBox>("RenderShadowDetailText");
-
-	BOOL enabled = LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferredSSAO");
-	
-	ctrl_ssao->setEnabled(enabled);
-	ctrl_dof->setEnabled(enabled);
-
-	enabled = enabled && LLFeatureManager::getInstance()->isFeatureAvailable("RenderShadowDetail");
-
-	ctrl_shadow->setEnabled(enabled);
-	shadow_text->setEnabled(enabled);
-
-    if (!LLFeatureManager::instance().isFeatureAvailable("RenderFSAASamples"))
-    {
-        getChildView("fsaa")->setEnabled(FALSE);
-    }
-
-    enabled = false;
-    if (!LLFeatureManager::instance().isFeatureAvailable("RenderReflectionsEnabled"))
-    {
-        getChildView("ReflectionsEnabled")->setEnabled(FALSE);
-    }
-    else 
-    {
-        enabled = gSavedSettings.getBOOL("RenderReflectionsEnabled");
-    }
-
-    getChildView("ReflectionDetail")->setEnabled(enabled);
-    getChildView("ReflectionDetailText")->setEnabled(enabled);
-    getChildView("ScreenSpaceReflections")->setEnabled(enabled);
-
-	// Hardware settings
-	
-	if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderCompressTextures"))
-	{
-		getChildView("texture compression")->setEnabled(FALSE);
-	}
-
-	getChildView("antialiasing restart")->setVisible(!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred"));
-
-	// now turn off any features that are unavailable
-	disableUnavailableSettings();
-}
-
-// static
 void LLAvatarComplexityControls::setIndirectControls()
 {
 	/*
@@ -1291,63 +1242,6 @@ void LLAvatarComplexityControls::setIndirectMaxArc()
 	gSavedSettings.setU32("IndirectMaxComplexity", indirect_max_arc);
 }
 
-void LLFloaterPreferenceGraphicsAdvanced::disableUnavailableSettings()
-{	
-	LLComboBox* ctrl_shadows = getChild<LLComboBox>("ShadowDetail");
-	LLTextBox* shadows_text = getChild<LLTextBox>("RenderShadowDetailText");
-	LLCheckBoxCtrl* ctrl_ssao = getChild<LLCheckBoxCtrl>("UseSSAO");
-	LLCheckBoxCtrl* ctrl_dof = getChild<LLCheckBoxCtrl>("UseDoF");
-	LLSliderCtrl* sky = getChild<LLSliderCtrl>("SkyMeshDetail");
-	LLTextBox* sky_text = getChild<LLTextBox>("SkyMeshDetailText");
-
-	// disabled windlight
-	if (!LLFeatureManager::getInstance()->isFeatureAvailable("WindLightUseAtmosShaders"))
-	{
-		sky->setEnabled(FALSE);
-		sky_text->setEnabled(FALSE);
-
-		//deferred needs windlight, disable deferred
-		ctrl_shadows->setEnabled(FALSE);
-		ctrl_shadows->setValue(0);
-		shadows_text->setEnabled(FALSE);
-		
-		ctrl_ssao->setEnabled(FALSE);
-		ctrl_ssao->setValue(FALSE);
-
-		ctrl_dof->setEnabled(FALSE);
-		ctrl_dof->setValue(FALSE);
-	}
-
-	// disabled deferred
-	if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred"))
-	{
-		ctrl_shadows->setEnabled(FALSE);
-		ctrl_shadows->setValue(0);
-		shadows_text->setEnabled(FALSE);
-		
-		ctrl_ssao->setEnabled(FALSE);
-		ctrl_ssao->setValue(FALSE);
-
-		ctrl_dof->setEnabled(FALSE);
-		ctrl_dof->setValue(FALSE);
-	}
-	
-    // disabled deferred SSAO
-	if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferredSSAO"))
-	{
-		ctrl_ssao->setEnabled(FALSE);   
-		ctrl_ssao->setValue(FALSE);
-	}
-	
-	// disabled deferred shadows
-    if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderShadowDetail"))
-    {
-        ctrl_shadows->setEnabled(FALSE);
-        ctrl_shadows->setValue(0);
-        shadows_text->setEnabled(FALSE);
-    }
-}
-
 void LLFloaterPreference::refresh()
 {
 	LLPanel::refresh();
@@ -1363,32 +1257,6 @@ void LLFloaterPreference::refresh()
     updateClickActionViews();
 }
 
-void LLFloaterPreferenceGraphicsAdvanced::refresh()
-{
-	getChild<LLUICtrl>("fsaa")->setValue((LLSD::Integer)  gSavedSettings.getU32("RenderFSAASamples"));
-
-	// sliders and their text boxes
-	//	mPostProcess = gSavedSettings.getS32("RenderGlowResolutionPow");
-	// slider text boxes
-	updateSliderText(getChild<LLSliderCtrl>("ObjectMeshDetail",		true), getChild<LLTextBox>("ObjectMeshDetailText",		true));
-	updateSliderText(getChild<LLSliderCtrl>("FlexibleMeshDetail",	true), getChild<LLTextBox>("FlexibleMeshDetailText",	true));
-	updateSliderText(getChild<LLSliderCtrl>("TreeMeshDetail",		true), getChild<LLTextBox>("TreeMeshDetailText",		true));
-	updateSliderText(getChild<LLSliderCtrl>("AvatarMeshDetail",		true), getChild<LLTextBox>("AvatarMeshDetailText",		true));
-	updateSliderText(getChild<LLSliderCtrl>("AvatarPhysicsDetail",	true), getChild<LLTextBox>("AvatarPhysicsDetailText",		true));
-	updateSliderText(getChild<LLSliderCtrl>("TerrainMeshDetail",	true), getChild<LLTextBox>("TerrainMeshDetailText",		true));
-	updateSliderText(getChild<LLSliderCtrl>("RenderPostProcess",	true), getChild<LLTextBox>("PostProcessText",			true));
-	updateSliderText(getChild<LLSliderCtrl>("SkyMeshDetail",		true), getChild<LLTextBox>("SkyMeshDetailText",			true));
-	updateSliderText(getChild<LLSliderCtrl>("TerrainDetail",		true), getChild<LLTextBox>("TerrainDetailText",			true));	
-    LLAvatarComplexityControls::setIndirectControls();
-	setMaxNonImpostorsText(
-        gSavedSettings.getU32("RenderAvatarMaxNonImpostors"),
-        getChild<LLTextBox>("IndirectMaxNonImpostorsText", true));
-    LLAvatarComplexityControls::setText(
-        gSavedSettings.getU32("RenderAvatarMaxComplexity"),
-        getChild<LLTextBox>("IndirectMaxComplexityText", true));
-	refreshEnabledState();
-}
-
 void LLFloaterPreference::onCommitWindowedMode()
 {
 	refresh();
@@ -1600,64 +1468,7 @@ void LLFloaterPreference::refreshUI()
 	refresh();
 }
 
-void LLFloaterPreferenceGraphicsAdvanced::updateSliderText(LLSliderCtrl* ctrl, LLTextBox* text_box)
-{
-	if (text_box == NULL || ctrl== NULL)
-		return;
-
-	// get range and points when text should change
-	F32 value = (F32)ctrl->getValue().asReal();
-	F32 min = ctrl->getMinValue();
-	F32 max = ctrl->getMaxValue();
-	F32 range = max - min;
-	llassert(range > 0);
-	F32 midPoint = min + range / 3.0f;
-	F32 highPoint = min + (2.0f * range / 3.0f);
-
-	// choose the right text
-	if (value < midPoint)
-	{
-		text_box->setText(LLTrans::getString("GraphicsQualityLow"));
-	} 
-	else if (value < highPoint)
-	{
-		text_box->setText(LLTrans::getString("GraphicsQualityMid"));
-	}
-	else
-	{
-		text_box->setText(LLTrans::getString("GraphicsQualityHigh"));
-	}
-}
-
-void LLFloaterPreferenceGraphicsAdvanced::updateMaxNonImpostors()
-{
-	// Called when the IndirectMaxNonImpostors control changes
-	// Responsible for fixing the slider label (IndirectMaxNonImpostorsText) and setting RenderAvatarMaxNonImpostors
-	LLSliderCtrl* ctrl = getChild<LLSliderCtrl>("IndirectMaxNonImpostors",true);
-	U32 value = ctrl->getValue().asInteger();
-
-	if (0 == value || LLVOAvatar::NON_IMPOSTORS_MAX_SLIDER <= value)
-	{
-		value=0;
-	}
-	gSavedSettings.setU32("RenderAvatarMaxNonImpostors", value);
-	LLVOAvatar::updateImpostorRendering(value); // make it effective immediately
-	setMaxNonImpostorsText(value, getChild<LLTextBox>("IndirectMaxNonImpostorsText"));
-}
-
-void LLFloaterPreferenceGraphicsAdvanced::setMaxNonImpostorsText(U32 value, LLTextBox* text_box)
-{
-	if (0 == value)
-	{
-		text_box->setText(LLTrans::getString("no_limit"));
-	}
-	else
-	{
-		text_box->setText(llformat("%d", value));
-	}
-}
-
-void LLAvatarComplexityControls::updateMax(LLSliderCtrl* slider, LLTextBox* value_label)
+void LLAvatarComplexityControls::updateMax(LLSliderCtrl* slider, LLTextBox* value_label, bool short_val)
 {
 	// Called when the IndirectMaxComplexity control changes
 	// Responsible for fixing the slider label (IndirectMaxComplexityText) and setting RenderAvatarMaxComplexity
@@ -1679,10 +1490,10 @@ void LLAvatarComplexityControls::updateMax(LLSliderCtrl* slider, LLTextBox* valu
 	}
 
 	gSavedSettings.setU32("RenderAvatarMaxComplexity", (U32)max_arc);
-	setText(max_arc, value_label);
+	setText(max_arc, value_label, short_val);
 }
 
-void LLAvatarComplexityControls::setText(U32 value, LLTextBox* text_box)
+void LLAvatarComplexityControls::setText(U32 value, LLTextBox* text_box, bool short_val)
 {
 	if (0 == value)
 	{
@@ -1690,24 +1501,40 @@ void LLAvatarComplexityControls::setText(U32 value, LLTextBox* text_box)
 	}
 	else
 	{
-		text_box->setText(llformat("%d", value));
+        std::string text_value = short_val ? llformat("%d", value / 1000) : llformat("%d", value);
+        text_box->setText(text_value);
 	}
 }
 
+void LLAvatarComplexityControls::updateMaxRenderTime(LLSliderCtrl* slider, LLTextBox* value_label, bool short_val)
+{
+    setRenderTimeText((F32)(LLPerfStats::renderAvatarMaxART_ns/1000), value_label, short_val);
+}
+
+void LLAvatarComplexityControls::setRenderTimeText(F32 value, LLTextBox* text_box, bool short_val)
+{
+    if (0 == value)
+    {
+        text_box->setText(LLTrans::getString("no_limit"));
+    }
+    else
+    {
+        text_box->setText(llformat("%.0f", value));
+    }
+}
+
 void LLFloaterPreference::updateMaxComplexity()
 {
 	// Called when the IndirectMaxComplexity control changes
     LLAvatarComplexityControls::updateMax(
         getChild<LLSliderCtrl>("IndirectMaxComplexity"),
         getChild<LLTextBox>("IndirectMaxComplexityText"));
+}
 
-    LLFloaterPreferenceGraphicsAdvanced* floater_graphics_advanced = LLFloaterReg::findTypedInstance<LLFloaterPreferenceGraphicsAdvanced>("prefs_graphics_advanced");
-    if (floater_graphics_advanced)
-    {
-        LLAvatarComplexityControls::updateMax(
-            floater_graphics_advanced->getChild<LLSliderCtrl>("IndirectMaxComplexity"),
-            floater_graphics_advanced->getChild<LLTextBox>("IndirectMaxComplexityText"));
-    }
+void LLFloaterPreference::updateComplexityText()
+{
+    LLAvatarComplexityControls::setText(gSavedSettings.getU32("RenderAvatarMaxComplexity"),
+        getChild<LLTextBox>("IndirectMaxComplexityText", true));
 }
 
 bool LLFloaterPreference::loadFromFilename(const std::string& filename, std::map<std::string, std::string> &label_map)
@@ -1749,22 +1576,6 @@ bool LLFloaterPreference::loadFromFilename(const std::string& filename, std::map
     return true;
 }
 
-void LLFloaterPreferenceGraphicsAdvanced::updateMaxComplexity()
-{
-	// Called when the IndirectMaxComplexity control changes
-    LLAvatarComplexityControls::updateMax(
-        getChild<LLSliderCtrl>("IndirectMaxComplexity"),
-        getChild<LLTextBox>("IndirectMaxComplexityText"));
-
-    LLFloaterPreference* floater_preferences = LLFloaterReg::findTypedInstance<LLFloaterPreference>("preferences");
-    if (floater_preferences)
-    {
-        LLAvatarComplexityControls::updateMax(
-            floater_preferences->getChild<LLSliderCtrl>("IndirectMaxComplexity"),
-            floater_preferences->getChild<LLTextBox>("IndirectMaxComplexityText"));
-    }
-}
-
 void LLFloaterPreference::onChangeMaturity()
 {
 	U8 sim_access = gSavedSettings.getU32("PreferredMaturity");
@@ -1877,6 +1688,15 @@ void LLFloaterPreference::onClickRenderExceptions()
     LLFloaterReg::showInstance("avatar_render_settings");
 }
 
+void LLFloaterPreference::onClickAutoAdjustments()
+{
+    LLFloaterPerformance* performance_floater = LLFloaterReg::showTypedInstance<LLFloaterPerformance>("performance");
+    if (performance_floater)
+    {
+        performance_floater->showAutoadjustmentsPanel();
+    }
+}
+
 void LLFloaterPreference::onClickAdvanced()
 {
 	LLFloaterReg::showInstance("prefs_graphics_advanced");
@@ -1899,6 +1719,22 @@ void LLFloaterPreference::onClickActionChange()
     updateClickActionControls();
 }
 
+void LLFloaterPreference::onAtmosShaderChange()
+{
+    LLCheckBoxCtrl* ctrl_alm = getChild<LLCheckBoxCtrl>("UseLightShaders");
+    if(ctrl_alm)
+    {
+        //Deferred/SSAO/Shadows
+        BOOL bumpshiny = LLCubeMap::sUseCubeMaps && LLFeatureManager::getInstance()->isFeatureAvailable("RenderObjectBump") && gSavedSettings.getBOOL("RenderObjectBump");
+        BOOL shaders = gSavedSettings.getBOOL("WindLightUseAtmosShaders");
+        BOOL enabled = LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred") &&
+                        bumpshiny &&
+                        shaders;
+
+        ctrl_alm->setEnabled(enabled);
+    }
+}
+
 void LLFloaterPreference::onClickPermsDefault()
 {
 	LLFloaterReg::showInstance("perms_default");
@@ -3219,18 +3055,6 @@ void LLPanelPreferenceControls::onCancelKeyBind()
     pControlsTable->deselectAllItems();
 }
 
-LLFloaterPreferenceGraphicsAdvanced::LLFloaterPreferenceGraphicsAdvanced(const LLSD& key)
-	: LLFloater(key)
-{
-    mCommitCallbackRegistrar.add("Pref.RenderOptionUpdate",            boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onRenderOptionEnable, this));
-	mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxNonImpostors", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateMaxNonImpostors,this));
-	mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxComplexity",   boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateMaxComplexity,this));
-}
-
-LLFloaterPreferenceGraphicsAdvanced::~LLFloaterPreferenceGraphicsAdvanced()
-{
-}
-
 LLFloaterPreferenceProxy::LLFloaterPreferenceProxy(const LLSD& key)
 	: LLFloater(key),
 	  mSocksSettingsDirty(false)
@@ -3240,41 +3064,6 @@ LLFloaterPreferenceProxy::LLFloaterPreferenceProxy(const LLSD& key)
 	mCommitCallbackRegistrar.add("Proxy.Change",            boost::bind(&LLFloaterPreferenceProxy::onChangeSocksSettings, this));
 }
 
-BOOL LLFloaterPreferenceGraphicsAdvanced::postBuild()
-{
-    // Don't do this on Mac as their braindead GL versioning
-    // sets this when 8x and 16x are indeed available
-    //
-#if !LL_DARWIN
-    if (gGLManager.mIsIntel || gGLManager.mGLVersion < 3.f)
-    { //remove FSAA settings above "4x"
-        LLComboBox* combo = getChild<LLComboBox>("fsaa");
-        combo->remove("8x");
-        combo->remove("16x");
-    }
-	
-	LLCheckBoxCtrl *use_HiDPI = getChild<LLCheckBoxCtrl>("use HiDPI");
-	use_HiDPI->setVisible(FALSE);
-#endif
-
-    return TRUE;
-}
-
-void LLFloaterPreferenceGraphicsAdvanced::onOpen(const LLSD& key)
-{
-    refresh();
-}
-
-void LLFloaterPreferenceGraphicsAdvanced::onClickCloseBtn(bool app_quitting)
-{
-	LLFloaterPreference* instance = LLFloaterReg::findTypedInstance<LLFloaterPreference>("preferences");
-	if (instance)
-	{
-		instance->cancel();
-	}
-	updateMaxComplexity();
-}
-
 LLFloaterPreferenceProxy::~LLFloaterPreferenceProxy()
 {
 }
diff --git a/indra/newview/llfloaterpreference.h b/indra/newview/llfloaterpreference.h
index bab1ffdd56d4fd2da88e6919f1b37377dfb1ac4c..04ac87364dbe1b1b7c281a124f50b7bda96a7d3b 100644
--- a/indra/newview/llfloaterpreference.h
+++ b/indra/newview/llfloaterpreference.h
@@ -109,10 +109,11 @@ class LLFloaterPreference : public LLFloater, public LLAvatarPropertiesObserver,
 	void updateClickActionViews();
     void updateSearchableItems();
 
-protected:	
 	void		onBtnOK(const LLSD& userdata);
 	void		onBtnCancel(const LLSD& userdata);
 
+protected:	
+
 	void		onClickClearCache();			// Clear viewer texture cache, file cache on next startup
 	void		onClickBrowserClearCache();		// Clear web history and caches as well as viewer caches above
 	void		onLanguageChange();
@@ -138,6 +139,8 @@ class LLFloaterPreference : public LLFloater, public LLAvatarPropertiesObserver,
 	// updates click/double-click action keybindngs depending on view values
 	void updateClickActionControls();
 
+    void onAtmosShaderChange();
+
 public:
 	// This function squirrels away the current values of the controls so that
 	// cancel() can restore them.	
@@ -188,6 +191,7 @@ class LLFloaterPreference : public LLFloater, public LLAvatarPropertiesObserver,
 	void onClickAutoReplace();
 	void onClickSpellChecker();
 	void onClickRenderExceptions();
+	void onClickAutoAdjustments();
 	void onClickAdvanced();
 	void applyUIColor(LLUICtrl* ctrl, const LLSD& param);
 	void getUIColor(LLUICtrl* ctrl, const LLSD& param);
@@ -198,12 +202,16 @@ class LLFloaterPreference : public LLFloater, public LLAvatarPropertiesObserver,
 	void saveCameraPreset(std::string& preset);
 	void saveGraphicsPreset(std::string& preset);
 
+    void setRecommendedSettings();
+    void resetAutotuneSettings();
+
 private:
 
 	void onDeleteTranscripts();
 	void onDeleteTranscriptsResponse(const LLSD& notification, const LLSD& response);
 	void updateDeleteTranscriptsButton();
 	void updateMaxComplexity();
+    void updateComplexityText();
 	static bool loadFromFilename(const std::string& filename, std::map<std::string, std::string> &label_map);
 
 	static std::string sSkin;
@@ -225,6 +233,8 @@ class LLFloaterPreference : public LLFloater, public LLAvatarPropertiesObserver,
 	std::unique_ptr< ll::prefs::SearchData > mSearchData;
 	bool mSearchDataDirty;
 
+    boost::signals2::connection	mComplexityChangedSignal;
+
 	void onUpdateFilterTerm( bool force = false );
 	void collectSearchableItems();
     void filterIgnorableNotifications();
@@ -354,37 +364,13 @@ class LLPanelPreferenceControls : public LLPanelPreference, public LLKeyBindResp
 	S32 mEditingMode;
 };
 
-class LLFloaterPreferenceGraphicsAdvanced : public LLFloater
-{
-  public: 
-	LLFloaterPreferenceGraphicsAdvanced(const LLSD& key);
-	~LLFloaterPreferenceGraphicsAdvanced();
-	/*virtual*/ BOOL postBuild();
-	void onOpen(const LLSD& key);
-	void onClickCloseBtn(bool app_quitting);
-	void disableUnavailableSettings();
-	void refreshEnabledGraphics();
-	void refreshEnabledState();
-	void updateSliderText(LLSliderCtrl* ctrl, LLTextBox* text_box);
-	void updateMaxNonImpostors();
-	void setMaxNonImpostorsText(U32 value, LLTextBox* text_box);
-	void updateMaxComplexity();
-	void setMaxComplexityText(U32 value, LLTextBox* text_box);
-	static void setIndirectControls();
-	static void setIndirectMaxNonImpostors();
-	static void setIndirectMaxArc();
-	void refresh();
-	// callback for when client modifies a render option
-	void onRenderOptionEnable();
-    void onAdvancedAtmosphericsEnable();
-	LOG_CLASS(LLFloaterPreferenceGraphicsAdvanced);
-};
-
 class LLAvatarComplexityControls
 {
   public: 
-	static void updateMax(LLSliderCtrl* slider, LLTextBox* value_label);
-	static void setText(U32 value, LLTextBox* text_box);
+	static void updateMax(LLSliderCtrl* slider, LLTextBox* value_label, bool short_val = false);
+	static void setText(U32 value, LLTextBox* text_box, bool short_val = false);
+	static void updateMaxRenderTime(LLSliderCtrl* slider, LLTextBox* value_label, bool short_val = false);
+	static void setRenderTimeText(F32 value, LLTextBox* text_box, bool short_val = false);
 	static void setIndirectControls();
 	static void setIndirectMaxNonImpostors();
 	static void setIndirectMaxArc();
diff --git a/indra/newview/llfloaterpreferencesgraphicsadvanced.cpp b/indra/newview/llfloaterpreferencesgraphicsadvanced.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a91f0ec0603462e1cf472a20f0ef73154540281f
--- /dev/null
+++ b/indra/newview/llfloaterpreferencesgraphicsadvanced.cpp
@@ -0,0 +1,480 @@
+/** 
+ * @file llfloaterpreferencesgraphicsadvanced.cpp
+ * @brief floater for adjusting camera position
+ *
+ * $LicenseInfo:firstyear=2021&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2021, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+#include "llfloaterpreferencesgraphicsadvanced.h"
+
+#include "llcheckboxctrl.h"
+#include "llcombobox.h"
+#include "llfeaturemanager.h"
+#include "llfloaterpreference.h"
+#include "llfloaterreg.h"
+#include "llnotificationsutil.h"
+#include "llsliderctrl.h"
+#include "lltextbox.h"
+#include "lltrans.h"
+#include "llviewershadermgr.h"
+#include "llviewertexturelist.h"
+#include "llvoavatar.h"
+#include "pipeline.h"
+
+
+LLFloaterPreferenceGraphicsAdvanced::LLFloaterPreferenceGraphicsAdvanced(const LLSD& key)
+    : LLFloater(key)
+{
+    mCommitCallbackRegistrar.add("Pref.RenderOptionUpdate",            boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onRenderOptionEnable, this));
+    mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxNonImpostors", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateMaxNonImpostors,this));
+    mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxComplexity",   boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateMaxComplexity,this));
+
+    mCommitCallbackRegistrar.add("Pref.Cancel", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onBtnCancel, this, _2));
+    mCommitCallbackRegistrar.add("Pref.OK",     boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onBtnOK, this, _2));
+
+    gSavedSettings.getControl("RenderAvatarMaxNonImpostors")->getSignal()->connect(boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateIndirectMaxNonImpostors, this, _2));
+}
+
+LLFloaterPreferenceGraphicsAdvanced::~LLFloaterPreferenceGraphicsAdvanced()
+{
+    mComplexityChangedSignal.disconnect();
+    mLODFactorChangedSignal.disconnect();
+}
+
+BOOL LLFloaterPreferenceGraphicsAdvanced::postBuild()
+{
+    // Don't do this on Mac as their braindead GL versioning
+    // sets this when 8x and 16x are indeed available
+    //
+#if !LL_DARWIN
+    if (gGLManager.mIsIntel || gGLManager.mGLVersion < 3.f)
+    { //remove FSAA settings above "4x"
+        LLComboBox* combo = getChild<LLComboBox>("fsaa");
+        combo->remove("8x");
+        combo->remove("16x");
+    }
+
+    LLCheckBoxCtrl *use_HiDPI = getChild<LLCheckBoxCtrl>("use HiDPI");
+    use_HiDPI->setVisible(FALSE);
+#endif
+
+    mComplexityChangedSignal = gSavedSettings.getControl("RenderAvatarMaxComplexity")->getCommitSignal()->connect(boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateComplexityText, this)); 
+    mLODFactorChangedSignal = gSavedSettings.getControl("RenderVolumeLODFactor")->getCommitSignal()->connect(boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateObjectMeshDetailText, this));
+    return TRUE;
+}
+
+void LLFloaterPreferenceGraphicsAdvanced::onOpen(const LLSD& key)
+{
+    refresh();
+}
+
+void LLFloaterPreferenceGraphicsAdvanced::onClickCloseBtn(bool app_quitting)
+{
+    LLFloaterPreference* instance = LLFloaterReg::findTypedInstance<LLFloaterPreference>("preferences");
+    if (instance)
+    {
+        instance->cancel();
+    }
+    updateMaxComplexity();
+}
+
+void LLFloaterPreferenceGraphicsAdvanced::onRenderOptionEnable()
+{
+    LLFloaterPreference* instance = LLFloaterReg::findTypedInstance<LLFloaterPreference>("preferences");
+    if (instance)
+    {
+        instance->refresh();
+    }
+
+    refreshEnabledGraphics();
+}
+
+void LLFloaterPreferenceGraphicsAdvanced::onAdvancedAtmosphericsEnable()
+{
+    LLFloaterPreference* instance = LLFloaterReg::findTypedInstance<LLFloaterPreference>("preferences");
+    if (instance)
+    {
+        instance->refresh();
+    }
+
+    refreshEnabledGraphics();
+}
+
+void LLFloaterPreferenceGraphicsAdvanced::refresh()
+{
+    getChild<LLUICtrl>("fsaa")->setValue((LLSD::Integer)  gSavedSettings.getU32("RenderFSAASamples"));
+
+    // sliders and their text boxes
+    //	mPostProcess = gSavedSettings.getS32("RenderGlowResolutionPow");
+    // slider text boxes
+    updateSliderText(getChild<LLSliderCtrl>("ObjectMeshDetail",		true), getChild<LLTextBox>("ObjectMeshDetailText",		true));
+    updateSliderText(getChild<LLSliderCtrl>("FlexibleMeshDetail",	true), getChild<LLTextBox>("FlexibleMeshDetailText",	true));
+    updateSliderText(getChild<LLSliderCtrl>("TreeMeshDetail",		true), getChild<LLTextBox>("TreeMeshDetailText",		true));
+    updateSliderText(getChild<LLSliderCtrl>("AvatarMeshDetail",		true), getChild<LLTextBox>("AvatarMeshDetailText",		true));
+    updateSliderText(getChild<LLSliderCtrl>("AvatarPhysicsDetail",	true), getChild<LLTextBox>("AvatarPhysicsDetailText",		true));
+    updateSliderText(getChild<LLSliderCtrl>("TerrainMeshDetail",	true), getChild<LLTextBox>("TerrainMeshDetailText",		true));
+    updateSliderText(getChild<LLSliderCtrl>("RenderPostProcess",	true), getChild<LLTextBox>("PostProcessText",			true));
+    updateSliderText(getChild<LLSliderCtrl>("SkyMeshDetail",		true), getChild<LLTextBox>("SkyMeshDetailText",			true));
+    updateSliderText(getChild<LLSliderCtrl>("TerrainDetail",		true), getChild<LLTextBox>("TerrainDetailText",			true));	
+    LLAvatarComplexityControls::setIndirectControls();
+    setMaxNonImpostorsText(
+        gSavedSettings.getU32("RenderAvatarMaxNonImpostors"),
+        getChild<LLTextBox>("IndirectMaxNonImpostorsText", true));
+    LLAvatarComplexityControls::setText(
+        gSavedSettings.getU32("RenderAvatarMaxComplexity"),
+        getChild<LLTextBox>("IndirectMaxComplexityText", true));
+    refreshEnabledState();
+}
+
+void LLFloaterPreferenceGraphicsAdvanced::refreshEnabledGraphics()
+{
+    refreshEnabledState();
+}
+
+void LLFloaterPreferenceGraphicsAdvanced::updateMaxComplexity()
+{
+    // Called when the IndirectMaxComplexity control changes
+    LLAvatarComplexityControls::updateMax(
+        getChild<LLSliderCtrl>("IndirectMaxComplexity"),
+        getChild<LLTextBox>("IndirectMaxComplexityText"));
+}
+
+void LLFloaterPreferenceGraphicsAdvanced::updateComplexityText()
+{
+    LLAvatarComplexityControls::setText(gSavedSettings.getU32("RenderAvatarMaxComplexity"),
+        getChild<LLTextBox>("IndirectMaxComplexityText", true));
+}
+
+void LLFloaterPreferenceGraphicsAdvanced::updateObjectMeshDetailText()
+{
+    updateSliderText(getChild<LLSliderCtrl>("ObjectMeshDetail", true), getChild<LLTextBox>("ObjectMeshDetailText", true));
+}
+
+void LLFloaterPreferenceGraphicsAdvanced::updateSliderText(LLSliderCtrl* ctrl, LLTextBox* text_box)
+{
+    if (text_box == NULL || ctrl== NULL)
+        return;
+
+    // get range and points when text should change
+    F32 value = (F32)ctrl->getValue().asReal();
+    F32 min = ctrl->getMinValue();
+    F32 max = ctrl->getMaxValue();
+    F32 range = max - min;
+    llassert(range > 0);
+    F32 midPoint = min + range / 3.0f;
+    F32 highPoint = min + (2.0f * range / 3.0f);
+
+    // choose the right text
+    if (value < midPoint)
+    {
+        text_box->setText(LLTrans::getString("GraphicsQualityLow"));
+    } 
+    else if (value < highPoint)
+    {
+        text_box->setText(LLTrans::getString("GraphicsQualityMid"));
+    }
+    else
+    {
+        text_box->setText(LLTrans::getString("GraphicsQualityHigh"));
+    }
+}
+
+void LLFloaterPreferenceGraphicsAdvanced::updateMaxNonImpostors()
+{
+    // Called when the IndirectMaxNonImpostors control changes
+    // Responsible for fixing the slider label (IndirectMaxNonImpostorsText) and setting RenderAvatarMaxNonImpostors
+    LLSliderCtrl* ctrl = getChild<LLSliderCtrl>("IndirectMaxNonImpostors",true);
+    U32 value = ctrl->getValue().asInteger();
+
+    if (0 == value || LLVOAvatar::NON_IMPOSTORS_MAX_SLIDER <= value)
+    {
+        value=0;
+    }
+    gSavedSettings.setU32("RenderAvatarMaxNonImpostors", value);
+    LLVOAvatar::updateImpostorRendering(value); // make it effective immediately
+    setMaxNonImpostorsText(value, getChild<LLTextBox>("IndirectMaxNonImpostorsText"));
+}
+
+void LLFloaterPreferenceGraphicsAdvanced::updateIndirectMaxNonImpostors(const LLSD& newvalue)
+{
+    U32 value = newvalue.asInteger();
+    if ((value != 0) && (value != gSavedSettings.getU32("IndirectMaxNonImpostors")))
+    {
+        gSavedSettings.setU32("IndirectMaxNonImpostors", value);
+        setMaxNonImpostorsText(value, getChild<LLTextBox>("IndirectMaxNonImpostorsText"));
+    }
+}
+
+void LLFloaterPreferenceGraphicsAdvanced::setMaxNonImpostorsText(U32 value, LLTextBox* text_box)
+{
+    if (0 == value)
+    {
+        text_box->setText(LLTrans::getString("no_limit"));
+    }
+    else
+    {
+        text_box->setText(llformat("%d", value));
+    }
+}
+
+void LLFloaterPreferenceGraphicsAdvanced::disableUnavailableSettings()
+{	
+    LLComboBox* ctrl_reflections   = getChild<LLComboBox>("Reflections");
+    LLTextBox* reflections_text = getChild<LLTextBox>("ReflectionsText");
+    LLCheckBoxCtrl* ctrl_avatar_vp     = getChild<LLCheckBoxCtrl>("AvatarVertexProgram");
+    LLCheckBoxCtrl* ctrl_avatar_cloth  = getChild<LLCheckBoxCtrl>("AvatarCloth");
+    LLCheckBoxCtrl* ctrl_wind_light    = getChild<LLCheckBoxCtrl>("WindLightUseAtmosShaders");
+    LLCheckBoxCtrl* ctrl_deferred = getChild<LLCheckBoxCtrl>("UseLightShaders");
+    LLComboBox* ctrl_shadows = getChild<LLComboBox>("ShadowDetail");
+    LLTextBox* shadows_text = getChild<LLTextBox>("RenderShadowDetailText");
+    LLCheckBoxCtrl* ctrl_ssao = getChild<LLCheckBoxCtrl>("UseSSAO");
+    LLCheckBoxCtrl* ctrl_dof = getChild<LLCheckBoxCtrl>("UseDoF");
+    LLSliderCtrl* sky = getChild<LLSliderCtrl>("SkyMeshDetail");
+    LLTextBox* sky_text = getChild<LLTextBox>("SkyMeshDetailText");
+
+    // disabled windlight
+    if (!LLFeatureManager::getInstance()->isFeatureAvailable("WindLightUseAtmosShaders"))
+    {
+        ctrl_wind_light->setEnabled(FALSE);
+        ctrl_wind_light->setValue(FALSE);
+
+        sky->setEnabled(FALSE);
+        sky_text->setEnabled(FALSE);
+
+        //deferred needs windlight, disable deferred
+        ctrl_shadows->setEnabled(FALSE);
+        ctrl_shadows->setValue(0);
+        shadows_text->setEnabled(FALSE);
+
+        ctrl_ssao->setEnabled(FALSE);
+        ctrl_ssao->setValue(FALSE);
+
+        ctrl_dof->setEnabled(FALSE);
+        ctrl_dof->setValue(FALSE);
+
+        ctrl_deferred->setEnabled(FALSE);
+        ctrl_deferred->setValue(FALSE);
+    }
+
+    // disabled deferred
+    if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred"))
+    {
+        ctrl_shadows->setEnabled(FALSE);
+        ctrl_shadows->setValue(0);
+        shadows_text->setEnabled(FALSE);
+
+        ctrl_ssao->setEnabled(FALSE);
+        ctrl_ssao->setValue(FALSE);
+
+        ctrl_dof->setEnabled(FALSE);
+        ctrl_dof->setValue(FALSE);
+
+        ctrl_deferred->setEnabled(FALSE);
+        ctrl_deferred->setValue(FALSE);
+    }
+
+    // disabled deferred SSAO
+    if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferredSSAO"))
+    {
+        ctrl_ssao->setEnabled(FALSE);
+        ctrl_ssao->setValue(FALSE);
+    }
+
+    // disabled deferred shadows
+    if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderShadowDetail"))
+    {
+        ctrl_shadows->setEnabled(FALSE);
+        ctrl_shadows->setValue(0);
+        shadows_text->setEnabled(FALSE);
+    }
+
+    // disabled reflections
+    if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderReflectionDetail"))
+    {
+        ctrl_reflections->setEnabled(FALSE);
+        ctrl_reflections->setValue(FALSE);
+        reflections_text->setEnabled(FALSE);
+    }
+
+    // disabled av
+    if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderAvatarVP"))
+    {
+        ctrl_avatar_vp->setEnabled(FALSE);
+        ctrl_avatar_vp->setValue(FALSE);
+
+        ctrl_avatar_cloth->setEnabled(FALSE);
+        ctrl_avatar_cloth->setValue(FALSE);
+
+        //deferred needs AvatarVP, disable deferred
+        ctrl_shadows->setEnabled(FALSE);
+        ctrl_shadows->setValue(0);
+        shadows_text->setEnabled(FALSE);
+
+        ctrl_ssao->setEnabled(FALSE);
+        ctrl_ssao->setValue(FALSE);
+
+        ctrl_dof->setEnabled(FALSE);
+        ctrl_dof->setValue(FALSE);
+
+        ctrl_deferred->setEnabled(FALSE);
+        ctrl_deferred->setValue(FALSE);
+    }
+
+    // disabled cloth
+    if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderAvatarCloth"))
+    {
+        ctrl_avatar_cloth->setEnabled(FALSE);
+        ctrl_avatar_cloth->setValue(FALSE);
+    }
+}
+
+void LLFloaterPreferenceGraphicsAdvanced::refreshEnabledState()
+{
+    LLComboBox* ctrl_reflections = getChild<LLComboBox>("Reflections");
+    LLTextBox* reflections_text = getChild<LLTextBox>("ReflectionsText");
+
+    // Reflections
+    BOOL reflections = LLCubeMap::sUseCubeMaps;
+    ctrl_reflections->setEnabled(reflections);
+    reflections_text->setEnabled(reflections);
+
+    // Bump & Shiny	
+    LLCheckBoxCtrl* bumpshiny_ctrl = getChild<LLCheckBoxCtrl>("BumpShiny");
+    bool bumpshiny = LLCubeMap::sUseCubeMaps && LLFeatureManager::getInstance()->isFeatureAvailable("RenderObjectBump");
+    bumpshiny_ctrl->setEnabled(bumpshiny ? TRUE : FALSE);
+
+    // Avatar Mode
+    // Enable Avatar Shaders
+    LLCheckBoxCtrl* ctrl_avatar_vp = getChild<LLCheckBoxCtrl>("AvatarVertexProgram");
+    // Avatar Render Mode
+    LLCheckBoxCtrl* ctrl_avatar_cloth = getChild<LLCheckBoxCtrl>("AvatarCloth");
+
+    bool avatar_vp_enabled = LLFeatureManager::getInstance()->isFeatureAvailable("RenderAvatarVP");
+    if (LLViewerShaderMgr::sInitialized)
+    {
+        S32 max_avatar_shader = LLViewerShaderMgr::instance()->mMaxAvatarShaderLevel;
+        avatar_vp_enabled = (max_avatar_shader > 0) ? TRUE : FALSE;
+    }
+
+    ctrl_avatar_vp->setEnabled(avatar_vp_enabled);
+
+    if (gSavedSettings.getBOOL("RenderAvatarVP") == FALSE)
+    {
+        ctrl_avatar_cloth->setEnabled(FALSE);
+    } 
+    else
+    {
+        ctrl_avatar_cloth->setEnabled(TRUE);
+    }
+
+    // Vertex Shaders, Global Shader Enable
+    // SL-12594 Basic shaders are always enabled. DJH TODO clean up now-orphaned state handling code
+    LLSliderCtrl* terrain_detail = getChild<LLSliderCtrl>("TerrainDetail");   // can be linked with control var
+    LLTextBox* terrain_text = getChild<LLTextBox>("TerrainDetailText");
+
+    terrain_detail->setEnabled(FALSE);
+    terrain_text->setEnabled(FALSE);
+
+    // WindLight
+    //LLCheckBoxCtrl* ctrl_wind_light = getChild<LLCheckBoxCtrl>("WindLightUseAtmosShaders");
+    //ctrl_wind_light->setEnabled(TRUE);
+    LLSliderCtrl* sky = getChild<LLSliderCtrl>("SkyMeshDetail");
+    LLTextBox* sky_text = getChild<LLTextBox>("SkyMeshDetailText");
+    sky->setEnabled(TRUE);
+    sky_text->setEnabled(TRUE);
+
+    BOOL enabled = TRUE;
+#if 0 // deferred always on now
+    //Deferred/SSAO/Shadows
+    LLCheckBoxCtrl* ctrl_deferred = getChild<LLCheckBoxCtrl>("UseLightShaders");
+
+    enabled = LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred") &&
+        ((bumpshiny_ctrl && bumpshiny_ctrl->get()) ? TRUE : FALSE) &&
+        (ctrl_wind_light->get()) ? TRUE : FALSE;
+
+    ctrl_deferred->setEnabled(enabled);
+#endif
+    
+    LLCheckBoxCtrl* ctrl_pbr = getChild<LLCheckBoxCtrl>("UsePBRShaders");
+
+    //PBR
+    ctrl_pbr->setEnabled(TRUE);
+
+    LLCheckBoxCtrl* ctrl_ssao = getChild<LLCheckBoxCtrl>("UseSSAO");
+    LLCheckBoxCtrl* ctrl_dof = getChild<LLCheckBoxCtrl>("UseDoF");
+    LLComboBox* ctrl_shadow = getChild<LLComboBox>("ShadowDetail");
+    LLTextBox* shadow_text = getChild<LLTextBox>("RenderShadowDetailText");
+
+    // note, okay here to get from ctrl_deferred as it's twin, ctrl_deferred2 will alway match it
+    enabled = enabled && LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferredSSAO");// && (ctrl_deferred->get() ? TRUE : FALSE);
+
+    //ctrl_deferred->set(gSavedSettings.getBOOL("RenderDeferred"));
+
+    ctrl_ssao->setEnabled(enabled);
+    ctrl_dof->setEnabled(enabled);
+
+    enabled = enabled && LLFeatureManager::getInstance()->isFeatureAvailable("RenderShadowDetail");
+
+    ctrl_shadow->setEnabled(enabled);
+    shadow_text->setEnabled(enabled);
+
+    // Hardware settings
+    
+    if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderVBOEnable"))
+    {
+        getChildView("vbo")->setEnabled(FALSE);
+    }
+
+    if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderCompressTextures"))
+    {
+        getChildView("texture compression")->setEnabled(FALSE);
+    }
+
+    // if no windlight shaders, turn off nighttime brightness, gamma, and fog distance
+    LLUICtrl* gamma_ctrl = getChild<LLUICtrl>("gamma");
+    gamma_ctrl->setEnabled(!gPipeline.canUseWindLightShaders());
+    getChildView("(brightness, lower is brighter)")->setEnabled(!gPipeline.canUseWindLightShaders());
+    getChildView("fog")->setEnabled(!gPipeline.canUseWindLightShaders());
+    getChildView("antialiasing restart")->setVisible(!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred"));
+
+    // now turn off any features that are unavailable
+    disableUnavailableSettings();
+}
+
+void LLFloaterPreferenceGraphicsAdvanced::onBtnOK(const LLSD& userdata)
+{
+    LLFloaterPreference* instance = LLFloaterReg::getTypedInstance<LLFloaterPreference>("preferences");
+    if (instance)
+    {
+        instance->onBtnOK(userdata);
+    }
+}
+
+void LLFloaterPreferenceGraphicsAdvanced::onBtnCancel(const LLSD& userdata)
+{
+    LLFloaterPreference* instance = LLFloaterReg::getTypedInstance<LLFloaterPreference>("preferences");
+    if (instance)
+    {
+        instance->onBtnCancel(userdata);
+    }
+}
diff --git a/indra/newview/llfloaterpreferencesgraphicsadvanced.h b/indra/newview/llfloaterpreferencesgraphicsadvanced.h
new file mode 100644
index 0000000000000000000000000000000000000000..2c92f3dbf1062cf0040c4cac1e48e8a6d0fc9a9f
--- /dev/null
+++ b/indra/newview/llfloaterpreferencesgraphicsadvanced.h
@@ -0,0 +1,68 @@
+/** 
+ * @file llfloaterpreferencesgraphicsadvanced.h
+ *
+ * $LicenseInfo:firstyear=2021&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2021, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LLFLOATERPREFERENCEGRAPHICSADVANCED_H
+#define LLFLOATERPREFERENCEGRAPHICSADVANCED_H
+
+#include "llcontrol.h"
+#include "llfloater.h"
+
+class LLSliderCtrl;
+class LLTextBox;
+
+class LLFloaterPreferenceGraphicsAdvanced : public LLFloater
+{
+public: 
+    LLFloaterPreferenceGraphicsAdvanced(const LLSD& key);
+    ~LLFloaterPreferenceGraphicsAdvanced();
+    /*virtual*/ BOOL postBuild();
+    void onOpen(const LLSD& key);
+    void onClickCloseBtn(bool app_quitting);
+    void disableUnavailableSettings();
+    void refreshEnabledGraphics();
+    void refreshEnabledState();
+    void updateSliderText(LLSliderCtrl* ctrl, LLTextBox* text_box);
+    void updateMaxNonImpostors();
+    void updateIndirectMaxNonImpostors(const LLSD& newvalue);
+    void setMaxNonImpostorsText(U32 value, LLTextBox* text_box);
+    void updateMaxComplexity();
+    void updateComplexityText();
+    void updateObjectMeshDetailText();
+    void refresh();
+    // callback for when client modifies a render option
+    void onRenderOptionEnable();
+    void onAdvancedAtmosphericsEnable();
+    LOG_CLASS(LLFloaterPreferenceGraphicsAdvanced);
+
+protected:	
+    void		onBtnOK(const LLSD& userdata);
+    void		onBtnCancel(const LLSD& userdata);
+
+    boost::signals2::connection	mComplexityChangedSignal;
+    boost::signals2::connection	mLODFactorChangedSignal;
+};
+
+#endif //LLFLOATERPREFERENCEGRAPHICSADVANCED_H
+
diff --git a/indra/newview/llglsandbox.cpp b/indra/newview/llglsandbox.cpp
index 2e4d0f85b92c798f660feaa3006bfd71c3c86c0a..098fe89265b25c29275ebe9a764d4839e54ebea3 100644
--- a/indra/newview/llglsandbox.cpp
+++ b/indra/newview/llglsandbox.cpp
@@ -984,6 +984,7 @@ class ShaderBinder
 
 //-----------------------------------------------------------------------------
 // gpu_benchmark()
+//  returns measured memory bandwidth of GPU in gigabytes per second
 //-----------------------------------------------------------------------------
 F32 gpu_benchmark()
 {
@@ -1028,8 +1029,6 @@ F32 gpu_benchmark()
 	//time limit, allocation operations shouldn't take longer then 30 seconds, same for actual benchmark.
 	const F32 time_limit = 30;
 
-	ShaderProfileHelper initProfile;
-	
 	std::vector<LLRenderTarget> dest(count);
 	TextureHolder texHolder(0, count);
 	std::vector<F32> results;
@@ -1111,46 +1110,49 @@ F32 gpu_benchmark()
 
 	buff->unmapBuffer();
 
-	// ensure matched pair of bind() and unbind() calls
-	ShaderBinder binder(gBenchmarkProgram);
-
-	buff->setBuffer();
-	glFinish();
-
-	F32 time_passed = 0; // seconds
-	for (S32 c = -1; c < samples && time_passed < time_limit; ++c)
-	{
-		LLTimer timer;
-		timer.start();
-
-		for (U32 i = 0; i < count; ++i)
-		{
-			dest[i].bindTarget();
-			texHolder.bind(i);
-			buff->drawArrays(LLRender::TRIANGLES, 0, 3);
-			dest[i].flush();
-		}
-
-		//wait for current batch of copies to finish
-		glFinish();
-
-		F32 time = timer.getElapsedTimeF32();
-		time_passed += time;
-
-		if (c >= 0) // <-- ignore the first sample as it tends to be artificially slow
-		{ 
-			//store result in gigabytes per second
-			F32 gb = (F32) ((F64) (res*res*8*count))/(1000000000);
-			F32 gbps = gb/time;
-			results.push_back(gbps);
-		}
-	}
+    LLGLSLShader::unbind();
+
+    F32 time_passed = 0; // seconds
+
+    { //run CPU timer benchmark
+        glFinish();
+        gBenchmarkProgram.bind();
+        for (S32 c = -1; c < samples && time_passed < time_limit; ++c)
+        {
+            LLTimer timer;
+            timer.start();
+
+            for (U32 i = 0; i < count; ++i)
+            {
+                dest[i].bindTarget();
+                texHolder.bind(i);
+                buff->setBuffer();
+                buff->drawArrays(LLRender::TRIANGLES, 0, 3);
+                dest[i].flush();
+            }
+
+            //wait for current batch of copies to finish
+            glFinish();
+
+            F32 time = timer.getElapsedTimeF32();
+            time_passed += time;
+
+            if (c >= 0) // <-- ignore the first sample as it tends to be artificially slow
+            {
+                //store result in gigabytes per second
+                F32 gb = (F32)((F64)(res * res * 8 * count)) / (1000000000);
+                F32 gbps = gb / time;
+                results.push_back(gbps);
+            }
+        }
+        gBenchmarkProgram.unbind();
+    }
 
 	std::sort(results.begin(), results.end());
 
 	F32 gbps = results[results.size()/2];
 
-	LL_INFOS("Benchmark") << "Memory bandwidth is " << llformat("%.3f", gbps) << "GB/sec according to CPU timers, " << (F32)results.size() << " tests took " << time_passed << " seconds" << LL_ENDL;
+	LL_INFOS("Benchmark") << "Memory bandwidth is " << llformat("%.3f", gbps) << " GB/sec according to CPU timers, " << (F32)results.size() << " tests took " << time_passed << " seconds" << LL_ENDL;
   
 #if LL_DARWIN
     if (gbps > 512.f)
@@ -1161,14 +1163,32 @@ F32 gpu_benchmark()
     }
 #endif
 
+    // run GPU timer benchmark
+    { 
+        ShaderProfileHelper initProfile;
+        dest[0].bindTarget();
+        gBenchmarkProgram.bind();
+        for (S32 c = 0; c < samples; ++c)
+        {
+            for (U32 i = 0; i < count; ++i)
+            {
+                texHolder.bind(i);
+                buff->setBuffer();
+                buff->drawArrays(LLRender::TRIANGLES, 0, 3);
+            }
+        }
+        gBenchmarkProgram.unbind();
+        dest[0].flush();
+    }
+
 	F32 ms = gBenchmarkProgram.mTimeElapsed/1000000.f;
 	F32 seconds = ms/1000.f;
 
-	F64 samples_drawn = res*res*count*results.size();
+    F64 samples_drawn = gBenchmarkProgram.mSamplesDrawn;
 	F32 samples_sec = (samples_drawn/1000000000.0)/seconds;
-	gbps = samples_sec*8;
+	gbps = samples_sec*4;  // 4 bytes per sample
 
-	LL_INFOS("Benchmark") << "Memory bandwidth is " << llformat("%.3f", gbps) << "GB/sec according to ARB_timer_query, total time " << seconds << " seconds" << LL_ENDL;
+	LL_INFOS("Benchmark") << "Memory bandwidth is " << llformat("%.3f", gbps) << " GB/sec according to ARB_timer_query, total time " << seconds << " seconds" << LL_ENDL;
 
 	return gbps;
 }
diff --git a/indra/newview/llgltfmateriallist.cpp b/indra/newview/llgltfmateriallist.cpp
index 151d7fa96943c22130284895b0bd54db484e122f..99a052f719092fd8240f71c2ebf8c9c7eda0ccc4 100644
--- a/indra/newview/llgltfmateriallist.cpp
+++ b/indra/newview/llgltfmateriallist.cpp
@@ -219,40 +219,36 @@ class LLGLTFMaterialOverrideDispatchHandler : public LLDispatchHandler
         struct ReturnData
         {
         public:
-            LLPointer<LLGLTFMaterial> mMaterial;
+            LLGLTFMaterial mMaterial;
             S32 mSide;
             bool mSuccess;
         };
 
-        // fromJson() is performance heavy offload to a thread.
-        main_queue->postTo(
-            general_queue,
-            [object_override]() // Work done on general queue
+        if (!object_override.mSides.empty())
         {
-            std::vector<ReturnData> results;
-
-            if (!object_override.mSides.empty())
+            // fromJson() is performance heavy offload to a thread.
+            main_queue->postTo(
+                general_queue,
+                [sides=object_override.mSides]() // Work done on general queue
             {
-                results.reserve(object_override.mSides.size());
+                std::vector<ReturnData> results;
+
+                results.reserve(sides.size());
                 // parse json
-                std::unordered_map<S32, std::string>::const_iterator iter = object_override.mSides.begin();
-                std::unordered_map<S32, std::string>::const_iterator end = object_override.mSides.end();
+                std::unordered_map<S32, std::string>::const_iterator iter = sides.begin();
+                std::unordered_map<S32, std::string>::const_iterator end = sides.end();
                 while (iter != end)
                 {
-                    LLPointer<LLGLTFMaterial> override_data = new LLGLTFMaterial();
                     std::string warn_msg, error_msg;
 
-                    bool success = override_data->fromJSON(iter->second, warn_msg, error_msg);
-
                     ReturnData result;
+
+                    bool success = result.mMaterial.fromJSON(iter->second, warn_msg, error_msg);
+
                     result.mSuccess = success;
                     result.mSide = iter->first;
 
-                    if (success)
-                    {
-                        result.mMaterial = override_data;
-                    }
-                    else
+                    if (!success)
                     {
                         LL_WARNS("GLTF") << "failed to parse GLTF override data.  errors: " << error_msg << " | warnings: " << warn_msg << LL_ENDL;
                     }
@@ -260,64 +256,68 @@ class LLGLTFMaterialOverrideDispatchHandler : public LLDispatchHandler
                     results.push_back(result);
                     iter++;
                 }
-            }
-            return results;
-        },
-            [object_override, this](std::vector<ReturnData> results) // Callback to main thread
+                return results;
+            },
+            [object_id=object_override.mObjectId, this](std::vector<ReturnData> results) // Callback to main thread
             {
-            LLViewerObject * obj = gObjectList.findObject(object_override.mObjectId);
+                LLViewerObject * obj = gObjectList.findObject(object_id);
 
-            if (results.size() > 0 )
-            {
-                std::unordered_set<S32> side_set;
-
-                for (int i = 0; i < results.size(); ++i)
+                if (results.size() > 0 )
                 {
-                    if (results[i].mSuccess)
+                    std::unordered_set<S32> side_set;
+
+                    for (auto const & result : results)
                     {
-                        // flag this side to not be nulled out later
-                        side_set.insert(results[i].mSide);
+                        S32 side = result.mSide;
+                        if (result.mSuccess)
+                        {
+                            // copy to heap here because LLTextureEntry is going to take ownership with an LLPointer
+                            LLGLTFMaterial * material = new LLGLTFMaterial(result.mMaterial);
+
+                            // flag this side to not be nulled out later
+                            side_set.insert(side);
 
-                        if (obj)
+                            if (obj)
+                            {
+                                obj->setTEGLTFMaterialOverride(side, material);
+                            }
+                        }
+
+                        // unblock material editor
+                        if (obj && obj->getTE(side) && obj->getTE(side)->isSelected())
                         {
-                            obj->setTEGLTFMaterialOverride(results[i].mSide, results[i].mMaterial);
+                            doSelectionCallbacks(object_id, side);
                         }
                     }
-                    
-                    // unblock material editor
-                    if (obj && obj->getTE(results[i].mSide) && obj->getTE(results[i].mSide)->isSelected())
-                    {
-                        doSelectionCallbacks(object_override.mObjectId, results[i].mSide);
-                    }
-                }
 
-                if (obj && side_set.size() != obj->getNumTEs())
-                { // object exists and at least one texture entry needs to have its override data nulled out
-                    for (int i = 0; i < obj->getNumTEs(); ++i)
-                    {
-                        if (side_set.find(i) == side_set.end())
+                    if (obj && side_set.size() != obj->getNumTEs())
+                    { // object exists and at least one texture entry needs to have its override data nulled out
+                        for (int i = 0; i < obj->getNumTEs(); ++i)
                         {
-                            obj->setTEGLTFMaterialOverride(i, nullptr);
-                            if (obj->getTE(i) && obj->getTE(i)->isSelected())
+                            if (side_set.find(i) == side_set.end())
                             {
-                                doSelectionCallbacks(object_override.mObjectId, i);
+                                obj->setTEGLTFMaterialOverride(i, nullptr);
+                                if (obj->getTE(i) && obj->getTE(i)->isSelected())
+                                {
+                                    doSelectionCallbacks(object_id, i);
+                                }
                             }
                         }
                     }
                 }
-            }
-            else if (obj)
-            { // override list was empty or an error occurred, null out all overrides for this object
-                for (int i = 0; i < obj->getNumTEs(); ++i)
-                {
-                    obj->setTEGLTFMaterialOverride(i, nullptr);
-                    if (obj->getTE(i) && obj->getTE(i)->isSelected())
+                else if (obj)
+                { // override list was empty or an error occurred, null out all overrides for this object
+                    for (int i = 0; i < obj->getNumTEs(); ++i)
                     {
-                        doSelectionCallbacks(obj->getID(), i);
+                        obj->setTEGLTFMaterialOverride(i, nullptr);
+                        if (obj->getTE(i) && obj->getTE(i)->isSelected())
+                        {
+                            doSelectionCallbacks(obj->getID(), i);
+                        }
                     }
                 }
-            }
-        });
+            });
+        }
     }
 
 private:
@@ -433,6 +433,19 @@ void LLGLTFMaterialList::queueApply(const LLViewerObject* obj, S32 side, const L
     }
 }
 
+void LLGLTFMaterialList::queueApply(const LLViewerObject* obj, S32 side, const LLUUID& asset_id, const LLGLTFMaterial* material_override)
+{
+    if (asset_id.isNull() || material_override == nullptr)
+    {
+        queueApply(obj, side, asset_id);
+    }
+    else
+    {
+        LLGLTFMaterial* material = new LLGLTFMaterial(*material_override);
+        sApplyQueue.push_back({ obj->getID(), side, asset_id, material });
+    }
+}
+
 void LLGLTFMaterialList::queueUpdate(const LLSD& data)
 {
     llassert(is_valid_update(data));
@@ -477,7 +490,7 @@ void LLGLTFMaterialList::flushUpdates(void(*done_callback)(bool))
     }
     sModifyQueue.clear();
 
-    for (auto& e : sApplyQueue)
+    for (ApplyMaterialAssetData& e : sApplyQueue)
     {
         data[i]["object_id"] = e.object_id;
         data[i]["side"] = e.side;
diff --git a/indra/newview/llgltfmateriallist.h b/indra/newview/llgltfmateriallist.h
index 85e60aa17f3793dc1a05d4330517319c6469370c..ce8781babacaf98b2c4696e1370536fbc3308825 100644
--- a/indra/newview/llgltfmateriallist.h
+++ b/indra/newview/llgltfmateriallist.h
@@ -70,6 +70,13 @@ class LLGLTFMaterialList
     // NOTE: Implicitly clears most override data if present
     static void queueApply(const LLViewerObject* obj, S32 side, const LLUUID& asset_id);
 
+    // Queue an application of a material asset we want to send to the simulator.  Call "flushUpdates" to flush pending updates.
+    //  object_id - ID of object to apply material asset to
+    //  side - TextureEntry index to apply material to, or -1 for all sides
+    //  asset_id - ID of material asset to apply, or LLUUID::null to disassociate current material asset
+    //  mat - override material, if null, will clear most override data
+    static void queueApply(const LLViewerObject* obj, S32 side, const LLUUID& asset_id, const LLGLTFMaterial* mat);
+
     // flush pending material updates to the simulator
     // Automatically called once per frame, but may be called explicitly
     // for cases that care about the done_callback forwarded to LLCoros::instance().launch
diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp
index e8411dd57387d0a468b354035d31fc6aaba59a88..67bf6827ad1a8dda51d26ca034d2f668798b50a3 100644
--- a/indra/newview/llmeshrepository.cpp
+++ b/indra/newview/llmeshrepository.cpp
@@ -1342,10 +1342,11 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry)
 	
 	if (header_size > 0)
 	{
-		const LLSD& header = header_it->second.second;
-		S32 version = header["version"].asInteger();
-		S32 offset = header_size + header["skin"]["offset"].asInteger();
-		S32 size = header["skin"]["size"].asInteger();
+		const LLMeshHeader& header = header_it->second.second;
+
+        S32 version = header.mVersion;
+		S32 offset = header_size + header.mSkinOffset;
+		S32 size = header.mSkinSize;
 
 		mHeaderMutex->unlock();
 
@@ -1456,9 +1457,9 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
 	if (header_size > 0)
 	{
 		const auto& header = header_it->second.second;
-		S32 version = header["version"].asInteger();
-		S32 offset = header_size + header["physics_convex"]["offset"].asInteger();
-		S32 size = header["physics_convex"]["size"].asInteger();
+        S32 version = header.mVersion;
+        S32 offset = header_size + header.mPhysicsConvexOffset;
+        S32 size = header.mPhysicsConvexSize;
 
 		mHeaderMutex->unlock();
 
@@ -1555,9 +1556,9 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
 	if (header_size > 0)
 	{
 		const auto& header = header_it->second.second;
-		S32 version = header["version"].asInteger();
-		S32 offset = header_size + header["physics_mesh"]["offset"].asInteger();
-		S32 size = header["physics_mesh"]["size"].asInteger();
+        S32 version = header.mVersion;
+        S32 offset = header_size + header.mPhysicsMeshOffset;
+		S32 size = header.mPhysicsMeshSize;
 
 		mHeaderMutex->unlock();
 
@@ -1753,9 +1754,9 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,
 	if (header_size > 0)
 	{
 		const auto& header = header_it->second.second;
-		S32 version = header["version"].asInteger();
-		S32 offset = header_size + header[header_lod[lod]]["offset"].asInteger();
-		S32 size = header[header_lod[lod]]["size"].asInteger();
+        S32 version = header.mVersion;
+        S32 offset = header_size + header.mLodOffset[lod];
+        S32 size = header.mLodSize[lod];
 		mHeaderMutex->unlock();
 				
 		if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0)
@@ -1857,8 +1858,10 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,
 EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size)
 {
 	const LLUUID mesh_id = mesh_params.getSculptID();
-	LLSD header;
+	LLSD header_data;
 	
+    LLMeshHeader header;
+
 	U32 header_size = 0;
 	if (data_size > 0)
 	{
@@ -1869,23 +1872,25 @@ EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mes
 
 		boost::iostreams::stream<boost::iostreams::array_source> stream(result_ptr, data_size);
 
-		if (!LLSDSerialize::fromBinary(header, stream, data_size))
+		if (!LLSDSerialize::fromBinary(header_data, stream, data_size))
 		{
 			LL_WARNS(LOG_MESH) << "Mesh header parse error.  Not a valid mesh asset!  ID:  " << mesh_id
 							   << LL_ENDL;
 			return MESH_PARSE_FAILURE;
 		}
 
-		if (!header.isMap())
+		if (!header_data.isMap())
 		{
 			LL_WARNS(LOG_MESH) << "Mesh header is invalid for ID: " << mesh_id << LL_ENDL;
 			return MESH_INVALID;
 		}
 
-		if (header.has("version") && header["version"].asInteger() > MAX_MESH_VERSION)
+        header.fromLLSD(header_data);
+
+		if (header.mVersion > MAX_MESH_VERSION)
 		{
 			LL_INFOS(LOG_MESH) << "Wrong version in header for " << mesh_id << LL_ENDL;
-			header["404"] = 1;
+			header.m404 = true;
 		}
 		// make sure there is at least one lod, function returns -1 and marks as 404 otherwise
 		else if (LLMeshRepository::getActualMeshLOD(header, 0) >= 0)
@@ -1897,7 +1902,7 @@ EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mes
 	{
 		LL_INFOS(LOG_MESH) << "Non-positive data size.  Marking header as non-existent, will not retry.  ID:  " << mesh_id
 						   << LL_ENDL;
-		header["404"] = 1;
+		header.m404 = 1;
 	}
 
 	{
@@ -1907,7 +1912,6 @@ EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mes
 			mMeshHeader[mesh_id] = { header_size, header };
             LLMeshRepository::sCacheBytesHeaders += header_size;
 		}
-
 		
 		LLMutexLock lock(mMutex); // make sure only one thread access mPendingLOD at the same time.
 
@@ -2977,7 +2981,7 @@ S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lo
 
 	if (iter != mMeshHeader.end())
 	{
-		LLSD& header = iter->second.second;
+		auto& header = iter->second.second;
 
 		return LLMeshRepository::getActualMeshLOD(header, lod);
 	}
@@ -2986,23 +2990,23 @@ S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lo
 }
 
 //static
-S32 LLMeshRepository::getActualMeshLOD(LLSD& header, S32 lod)
+S32 LLMeshRepository::getActualMeshLOD(LLMeshHeader& header, S32 lod)
 {
 	lod = llclamp(lod, 0, 3);
 
-	if (header.has("404"))
+	if (header.m404)
 	{
 		return -1;
 	}
 
-	S32 version = header["version"];
+	S32 version = header.mVersion;
 
 	if (version > MAX_MESH_VERSION)
 	{
 		return -1;
 	}
 
-	if (header[header_lod[lod]]["size"].asInteger() > 0)
+	if (header.mLodSize[lod] > 0)
 	{
 		return lod;
 	}
@@ -3010,7 +3014,7 @@ S32 LLMeshRepository::getActualMeshLOD(LLSD& header, S32 lod)
 	//search down to find the next available lower lod
 	for (S32 i = lod-1; i >= 0; --i)
 	{
-		if (header[header_lod[i]]["size"].asInteger() > 0)
+		if (header.mLodSize[i] > 0)
 		{
 			return i;
 		}
@@ -3019,15 +3023,16 @@ S32 LLMeshRepository::getActualMeshLOD(LLSD& header, S32 lod)
 	//search up to find then ext available higher lod
 	for (S32 i = lod+1; i < 4; ++i)
 	{
-		if (header[header_lod[i]]["size"].asInteger() > 0)
+		if (header.mLodSize[i] > 0)
 		{
 			return i;
 		}
 	}
 
 	//header exists and no good lod found, treat as 404
-	header["404"] = 1;
-	return -1;
+    header.m404 = true;
+
+    return -1;
 }
 
 // Handle failed or successful requests for mesh assets.
@@ -3216,7 +3221,7 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b
 	{
 		// header was successfully retrieved from sim and parsed and is in cache
 		S32 header_bytes = 0;
-		LLSD header;
+		LLMeshHeader header;
 
 		gMeshRepo.mThread->mHeaderMutex->lock();
 		LLMeshRepoThread::mesh_header_map::iterator iter = gMeshRepo.mThread->mMeshHeader.find(mesh_id);
@@ -3227,8 +3232,8 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b
 		}
 
 		if (header_bytes > 0
-			&& !header.has("404")
-			&& (!header.has("version") || header["version"].asInteger() <= MAX_MESH_VERSION))
+			&& !header.m404
+			&& (header.mVersion <= MAX_MESH_VERSION))
 		{
 			std::stringstream str;
 
@@ -3237,13 +3242,12 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b
 			for (U32 i = 0; i < LLModel::LOD_PHYSICS; ++i)
 			{
 				// figure out how many bytes we'll need to reserve in the file
-				const std::string & lod_name = header_lod[i];
-				lod_bytes = llmax(lod_bytes, header[lod_name]["offset"].asInteger()+header[lod_name]["size"].asInteger());
+				lod_bytes = llmax(lod_bytes, header.mLodOffset[i]+header.mLodSize[i]);
 			}
 		
 			// just in case skin info or decomposition is at the end of the file (which it shouldn't be)
-			lod_bytes = llmax(lod_bytes, header["skin"]["offset"].asInteger() + header["skin"]["size"].asInteger());
-			lod_bytes = llmax(lod_bytes, header["physics_convex"]["offset"].asInteger() + header["physics_convex"]["size"].asInteger());
+			lod_bytes = llmax(lod_bytes, header.mSkinOffset+header.mSkinSize);
+            lod_bytes = llmax(lod_bytes, header.mPhysicsConvexOffset + header.mPhysicsConvexSize);
 
             // Do not unlock mutex untill we are done with LLSD.
             // LLSD is smart and can work like smart pointer, is not thread safe.
@@ -4257,8 +4261,8 @@ bool LLMeshRepoThread::hasPhysicsShapeInHeader(const LLUUID& mesh_id)
     mesh_header_map::iterator iter = mMeshHeader.find(mesh_id);
     if (iter != mMeshHeader.end() && iter->second.first > 0)
     {
-        LLSD &mesh = iter->second.second;
-        if (mesh.has("physics_mesh") && mesh["physics_mesh"].has("size") && (mesh["physics_mesh"]["size"].asInteger() > 0))
+        LLMeshHeader &mesh = iter->second.second;
+        if (mesh.mPhysicsMeshSize > 0)
         {
             return true;
         }
@@ -4281,20 +4285,21 @@ void LLMeshRepository::uploadModel(std::vector<LLModelInstance>& data, LLVector3
 
 S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod)
 {
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME;
 	if (mThread && mesh_id.notNull() && LLPrimitive::NO_LOD != lod)
 	{
 		LLMutexLock lock(mThread->mHeaderMutex);
 		LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id);
 		if (iter != mThread->mMeshHeader.end() && iter->second.first > 0)
 		{
-			const LLSD& header = iter->second.second;
+			const LLMeshHeader& header = iter->second.second;
 
-			if (header.has("404"))
+			if (header.m404)
 			{
 				return -1;
 			}
 
-			S32 size = header[header_lod[lod]]["size"].asInteger();
+            S32 size = header.mLodSize[lod];
 			return size;
 		}
 
@@ -4430,11 +4435,11 @@ F32 LLMeshRepository::getStreamingCostLegacy(LLUUID mesh_id, F32 radius, S32* by
 
 // FIXME replace with calc based on LLMeshCostData
 //static
-F32 LLMeshRepository::getStreamingCostLegacy(LLSD& header, F32 radius, S32* bytes, S32* bytes_visible, S32 lod, F32 *unscaled_value)
+F32 LLMeshRepository::getStreamingCostLegacy(LLMeshHeader& header, F32 radius, S32* bytes, S32* bytes_visible, S32 lod, F32 *unscaled_value)
 {
-	if (header.has("404")
-		|| !header.has("lowest_lod")
-		|| (header.has("version") && header["version"].asInteger() > MAX_MESH_VERSION))
+	if (header.m404
+		|| header.mLodSize[0] <= 0
+		|| (header.mVersion > MAX_MESH_VERSION))
 	{
 		return 0.f;
 	}
@@ -4453,10 +4458,10 @@ F32 LLMeshRepository::getStreamingCostLegacy(LLSD& header, F32 radius, S32* byte
 	F32 minimum_size = (F32)minimum_size_ch;
 	F32 bytes_per_triangle = (F32)bytes_per_triangle_ch;
 
-	S32 bytes_lowest = header["lowest_lod"]["size"].asInteger();
-	S32 bytes_low = header["low_lod"]["size"].asInteger();
-	S32 bytes_mid = header["medium_lod"]["size"].asInteger();
-	S32 bytes_high = header["high_lod"]["size"].asInteger();
+	S32 bytes_lowest = header.mLodSize[0];
+	S32 bytes_low = header.mLodSize[1];
+	S32 bytes_mid = header.mLodSize[2];
+	S32 bytes_high = header.mLodSize[3];
 
 	if (bytes_high == 0)
 	{
@@ -4486,10 +4491,10 @@ F32 LLMeshRepository::getStreamingCostLegacy(LLSD& header, F32 radius, S32* byte
 	if (bytes)
 	{
 		*bytes = 0;
-		*bytes += header["lowest_lod"]["size"].asInteger();
-		*bytes += header["low_lod"]["size"].asInteger();
-		*bytes += header["medium_lod"]["size"].asInteger();
-		*bytes += header["high_lod"]["size"].asInteger();
+		*bytes += header.mLodSize[0];
+		*bytes += header.mLodSize[1];
+		*bytes += header.mLodSize[2];
+		*bytes += header.mLodSize[3];
 	}
 
 	if (bytes_visible)
@@ -4497,7 +4502,7 @@ F32 LLMeshRepository::getStreamingCostLegacy(LLSD& header, F32 radius, S32* byte
 		lod = LLMeshRepository::getActualMeshLOD(header, lod);
 		if (lod >= 0 && lod <= 3)
 		{
-			*bytes_visible = header[header_lod[lod]]["size"].asInteger();
+			*bytes_visible = header.mLodSize[lod];
 		}
 	}
 
@@ -4539,33 +4544,29 @@ F32 LLMeshRepository::getStreamingCostLegacy(LLSD& header, F32 radius, S32* byte
 
 LLMeshCostData::LLMeshCostData()
 {
-    mSizeByLOD.resize(4);
-    mEstTrisByLOD.resize(4);
-
     std::fill(mSizeByLOD.begin(), mSizeByLOD.end(), 0);
     std::fill(mEstTrisByLOD.begin(), mEstTrisByLOD.end(), 0.f);
 }
 
-bool LLMeshCostData::init(const LLSD& header)
+bool LLMeshCostData::init(const LLMeshHeader& header)
 {
-    mSizeByLOD.resize(4);
-    mEstTrisByLOD.resize(4);
-
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME;
+    
     std::fill(mSizeByLOD.begin(), mSizeByLOD.end(), 0);
     std::fill(mEstTrisByLOD.begin(), mEstTrisByLOD.end(), 0.f);
     
-    S32 bytes_high = header["high_lod"]["size"].asInteger();
-    S32 bytes_med = header["medium_lod"]["size"].asInteger();
+    S32 bytes_high = header.mLodSize[3];
+    S32 bytes_med = header.mLodSize[2];
     if (bytes_med == 0)
     {
         bytes_med = bytes_high;
     }
-    S32 bytes_low = header["low_lod"]["size"].asInteger();
+    S32 bytes_low = header.mLodSize[1];
     if (bytes_low == 0)
     {
         bytes_low = bytes_med;
     }
-    S32 bytes_lowest = header["lowest_lod"]["size"].asInteger();
+    S32 bytes_lowest = header.mLodSize[0];
     if (bytes_lowest == 0)
     {
         bytes_lowest = bytes_low;
@@ -4700,6 +4701,7 @@ F32 LLMeshCostData::getTriangleBasedStreamingCost()
 
 bool LLMeshRepository::getCostData(LLUUID mesh_id, LLMeshCostData& data)
 {
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME;
     data = LLMeshCostData();
     
     if (mThread && mesh_id.notNull())
@@ -4708,11 +4710,11 @@ bool LLMeshRepository::getCostData(LLUUID mesh_id, LLMeshCostData& data)
         LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id);
         if (iter != mThread->mMeshHeader.end() && iter->second.first > 0)
         {
-            LLSD& header = iter->second.second;
+            LLMeshHeader& header = iter->second.second;
 
-            bool header_invalid = (header.has("404")
-                                   || !header.has("lowest_lod")
-                                   || (header.has("version") && header["version"].asInteger() > MAX_MESH_VERSION));
+            bool header_invalid = (header.m404
+                                   || header.mLodSize[0] <= 0
+                                   || header.mVersion > MAX_MESH_VERSION);
             if (!header_invalid)
             {
                 return getCostData(header, data);
@@ -4724,7 +4726,7 @@ bool LLMeshRepository::getCostData(LLUUID mesh_id, LLMeshCostData& data)
     return false;
 }
 
-bool LLMeshRepository::getCostData(LLSD& header, LLMeshCostData& data)
+bool LLMeshRepository::getCostData(LLMeshHeader& header, LLMeshCostData& data)
 {
     data = LLMeshCostData();
 
diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h
index 6922367ff7e5ac8565002e1aaa0982a4e1ce2ce5..619e076fa6c63b8229f497221b9dc2f2dae5b6ed 100644
--- a/indra/newview/llmeshrepository.h
+++ b/indra/newview/llmeshrepository.h
@@ -194,6 +194,63 @@ class RequestStats
     LLFrameTimer mTimer;
 };
 
+class LLMeshHeader
+{
+public:
+
+    LLMeshHeader() {}
+
+    explicit LLMeshHeader(const LLSD& header)
+    {
+        fromLLSD(header);
+    }
+
+    void fromLLSD(const LLSD& header)
+    {
+        const char* lod[] =
+        {
+            "lowest_lod",
+            "low_lod",
+            "medium_lod",
+            "high_lod"
+        };
+
+        mVersion = header["version"].asInteger();
+
+        for (U32 i = 0; i < 4; ++i)
+        {
+            mLodOffset[i] = header[lod[i]]["offset"].asInteger();
+            mLodSize[i] = header[lod[i]]["size"].asInteger();
+        }
+
+        mSkinOffset = header["skin"]["offset"].asInteger();
+        mSkinSize = header["skin"]["size"].asInteger();
+
+        mPhysicsConvexOffset = header["physics_convex"]["offset"].asInteger();
+        mPhysicsConvexSize = header["physics_convex"]["size"].asInteger();
+
+        mPhysicsMeshOffset = header["physics_mesh"]["offset"].asInteger();
+        mPhysicsMeshSize = header["physics_mesh"]["size"].asInteger();
+
+        m404 = header.has("404");
+    }
+
+    S32 mVersion = -1;
+    S32 mSkinOffset = -1;
+    S32 mSkinSize = -1;
+
+    S32 mPhysicsConvexOffset = -1;
+    S32 mPhysicsConvexSize = -1;
+
+    S32 mPhysicsMeshOffset = -1;
+    S32 mPhysicsMeshSize = -1;
+
+    S32 mLodOffset[4] = { -1 };
+    S32 mLodSize[4] = { -1 };
+
+    bool m404 = false;
+};
+
 class LLMeshRepoThread : public LLThread
 {
 public:
@@ -210,7 +267,7 @@ class LLMeshRepoThread : public LLThread
 	LLCondition* mSignal;
 
 	//map of known mesh headers
-	typedef boost::unordered_map<LLUUID, std::pair<U32, LLSD>> mesh_header_map; // pair is header_size and data
+	typedef boost::unordered_map<LLUUID, std::pair<U32, LLMeshHeader>> mesh_header_map; // pair is header_size and data
 	mesh_header_map mMeshHeader;
 
 	class HeaderRequest : public RequestStats
@@ -497,7 +554,7 @@ class LLMeshCostData
 public:
     LLMeshCostData();
 
-    bool init(const LLSD& header);
+    bool init(const LLMeshHeader& header);
     
     // Size for given LOD
     S32 getSizeByLOD(S32 lod);
@@ -532,10 +589,10 @@ class LLMeshCostData
 
 private:
     // From the "size" field of the mesh header. LOD 0=lowest, 3=highest.
-    std::vector<S32> mSizeByLOD;
+    std::array<S32,4> mSizeByLOD;
 
     // Estimated triangle counts derived from the LOD sizes. LOD 0=lowest, 3=highest.
-    std::vector<F32> mEstTrisByLOD;
+    std::array<F32,4> mEstTrisByLOD;
 };
 
 class LLMeshRepository
@@ -566,9 +623,9 @@ class LLMeshRepository
     F32 getEstTrianglesMax(LLUUID mesh_id);
     F32 getEstTrianglesStreamingCost(LLUUID mesh_id);
 	F32 getStreamingCostLegacy(LLUUID mesh_id, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL);
-	static F32 getStreamingCostLegacy(LLSD& header, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL);
+	static F32 getStreamingCostLegacy(LLMeshHeader& header, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL);
     bool getCostData(LLUUID mesh_id, LLMeshCostData& data);
-    bool getCostData(LLSD& header, LLMeshCostData& data);
+    bool getCostData(LLMeshHeader& header, LLMeshCostData& data);
 
 	LLMeshRepository();
 
@@ -588,7 +645,7 @@ class LLMeshRepository
 	void notifyDecompositionReceived(LLModel::Decomposition* info);
 
 	S32 getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod);
-	static S32 getActualMeshLOD(LLSD& header, S32 lod);
+	static S32 getActualMeshLOD(LLMeshHeader& header, S32 lod);
 	const LLMeshSkinInfo* getSkinInfo(const LLUUID& mesh_id, LLVOVolume* requesting_obj = nullptr);
 	LLModel::Decomposition* getDecomposition(const LLUUID& mesh_id);
 	void fetchPhysicsShape(const LLUUID& mesh_id);
diff --git a/indra/newview/llnamelistctrl.cpp b/indra/newview/llnamelistctrl.cpp
index 8058faa5c7c24b9ca7709e48d1d87163399f275a..d7d6fa1893b2849a9ae4697d19cd48cfdf7ae29e 100644
--- a/indra/newview/llnamelistctrl.cpp
+++ b/indra/newview/llnamelistctrl.cpp
@@ -67,7 +67,9 @@ LLNameListCtrl::LLNameListCtrl(const LLNameListCtrl::Params& p)
 	mNameColumn(p.name_column.column_name),
 	mAllowCallingCardDrop(p.allow_calling_card_drop),
 	mShortNames(p.short_names),
-	mPendingLookupsRemaining(0)
+	mPendingLookupsRemaining(0),
+    mHoverIconName("Info_Small"),
+    mNameListType(INDIVIDUAL)
 {}
 
 // public
@@ -134,7 +136,12 @@ BOOL LLNameListCtrl::handleDragAndDrop(
 
 void LLNameListCtrl::showInspector(const LLUUID& avatar_id, bool is_group, bool is_experience)
 {
-	if(is_experience)
+    if (isSpecialType())
+    {
+        mIconClickedSignal(avatar_id);
+        return;
+    }
+    if(is_experience)
 	{
 		LLFloaterReg::showInstance("experience_profile", avatar_id, true);
 		return;
@@ -215,14 +222,16 @@ BOOL LLNameListCtrl::handleToolTip(S32 x, S32 y, MASK mask)
 	S32 column_index = getColumnIndexFromOffset(x);
 	LLNameListItem* hit_item = dynamic_cast<LLNameListItem*>(hitItem(x, y));
 	LLFloater* floater = gFloaterView->getParentFloater(this);
-	if (floater 
+
+
+    if (floater 
 		&& floater->isFrontmost()
 		&& hit_item
-		&& column_index == mNameColumnIndex)
+		&& ((column_index == mNameColumnIndex) || isSpecialType()))
 	{
-		// ...this is the column with the avatar name
-		LLUUID avatar_id = hit_item->getUUID();
-		if (avatar_id.notNull())
+        // ...this is the column with the avatar name
+		LLUUID item_id = isSpecialType() ? hit_item->getSpecialID() : hit_item->getUUID();
+		if (item_id.notNull())
 		{
 			// ...valid avatar id
 
@@ -230,13 +239,13 @@ BOOL LLNameListCtrl::handleToolTip(S32 x, S32 y, MASK mask)
 			if (hit_cell)
 			{
 				S32 row_index = getItemIndex(hit_item);
-				LLRect cell_rect = getCellRect(row_index, column_index);
+				LLRect cell_rect = getCellRect(row_index, isSpecialType() ? getNumColumns() - 1 : column_index);
 				// Convert rect local to screen coordinates
 				LLRect sticky_rect;
 				localRectToScreen(cell_rect, &sticky_rect);
 
 				// Spawn at right side of cell
-				LLPointer<LLUIImage> icon = LLUI::getUIImage("Info_Small");
+				LLPointer<LLUIImage> icon = LLUI::getUIImage(mHoverIconName);
 				S32 screenX = sticky_rect.mRight - info_icon_size;
 				S32 screenY = sticky_rect.mTop - (sticky_rect.getHeight() - icon->getHeight()) / 2;
 				LLCoordGL pos(screenX, screenY);
@@ -250,7 +259,7 @@ BOOL LLNameListCtrl::handleToolTip(S32 x, S32 y, MASK mask)
 
 					LLToolTip::Params params;
 					params.background_visible(false);
-					params.click_callback(boost::bind(&LLNameListCtrl::showInspector, this, avatar_id, is_group, is_experience));
+					params.click_callback(boost::bind(&LLNameListCtrl::showInspector, this, item_id, is_group, is_experience));
 					params.delay_time(0.0f);		// spawn instantly on hover
 					params.image(icon);
 					params.message("");
@@ -340,6 +349,7 @@ LLScrollListItem* LLNameListCtrl::addNameItemRow(
 
 	// use supplied name by default
 	std::string fullname = name_item.name;
+
 	switch(name_item.target)
 	{
 	case GROUP:
@@ -358,8 +368,10 @@ LLScrollListItem* LLNameListCtrl::addNameItemRow(
 		}
 		break;
 	case SPECIAL:
-		// just use supplied name
-		break;
+        {
+        item->setSpecialID(name_item.special_id());
+        return item;
+        }
 	case INDIVIDUAL:
 		{
 			LLAvatarName av_name;
@@ -370,7 +382,7 @@ LLScrollListItem* LLNameListCtrl::addNameItemRow(
 			else if (LLAvatarNameCache::get(id, &av_name))
 			{
 				if (mShortNames)
-					fullname = av_name.getDisplayName();
+					fullname = av_name.getDisplayName(true);
 				else
 					fullname = av_name.getCompleteName();
 			}
@@ -440,7 +452,8 @@ void LLNameListCtrl::removeNameItem(const LLUUID& agent_id)
 	for (item_list::iterator it = getItemList().begin(); it != getItemList().end(); it++)
 	{
 		LLScrollListItem* item = *it;
-		if (item->getUUID() == agent_id)
+        LLUUID cur_id = isSpecialType() ? dynamic_cast<LLNameListItem*>(item)->getSpecialID() : item->getUUID();
+        if (cur_id == agent_id)
 		{
 			idx = getItemIndex(item);
 			break;
@@ -471,6 +484,34 @@ LLScrollListItem* LLNameListCtrl::getNameItemByAgentId(const LLUUID& agent_id)
 	return NULL;
 }
 
+void LLNameListCtrl::selectItemBySpecialId(const LLUUID& special_id)
+{
+    if (special_id.isNull())
+    {
+        return;
+    }
+
+    for (item_list::iterator it = getItemList().begin(); it != getItemList().end(); it++)
+    {
+        LLNameListItem* item = dynamic_cast<LLNameListItem*>(*it);
+        if (item && item->getSpecialID() == special_id)
+        {
+            item->setSelected(TRUE);
+            break;
+        }
+    }
+}
+
+LLUUID LLNameListCtrl::getSelectedSpecialId()
+{
+    LLNameListItem* item = dynamic_cast<LLNameListItem*>(getFirstSelected());
+    if(item)
+    {
+        return item->getSpecialID();
+    }
+    return LLUUID();
+}
+
 void LLNameListCtrl::onAvatarNameCache(const LLUUID& agent_id,
 									   const LLAvatarName& av_name,
 									   std::string suffix,
diff --git a/indra/newview/llnamelistctrl.h b/indra/newview/llnamelistctrl.h
index 5dd5da5892ade23c01e7851e0fd8a3d3b6a1b3a3..4a4bd4ba09fd320b73ba13c0e979ebc0fa32d38d 100644
--- a/indra/newview/llnamelistctrl.h
+++ b/indra/newview/llnamelistctrl.h
@@ -46,6 +46,8 @@ class LLNameListItem : public LLScrollListItem, public LLHandleProvider<LLNameLi
 	void setIsGroup(bool is_group) { mIsGroup = is_group; }
 	bool isExperience() const { return mIsExperience; }
 	void setIsExperience(bool is_experience) { mIsExperience = is_experience; }
+    void setSpecialID(const LLUUID& special_id) { mSpecialID = special_id; }
+    const LLUUID& getSpecialID() const { return mSpecialID; }
 
 protected:
 	friend class LLNameListCtrl;
@@ -68,6 +70,8 @@ class LLNameListItem : public LLScrollListItem, public LLHandleProvider<LLNameLi
 private:
 	bool mIsGroup;
 	bool mIsExperience;
+
+    LLUUID mSpecialID;
 };
 
 
@@ -95,10 +99,12 @@ class LLNameListCtrl
 	{
 		Optional<std::string>				name;
 		Optional<ENameType, NameTypeNames>	target;
+        Optional<LLUUID>                    special_id;
 
 		NameItem()
 		:	name("name"),
-			target("target", INDIVIDUAL)
+			target("target", INDIVIDUAL),
+            special_id("special_id", LLUUID())
 		{}
 	};
 
@@ -156,6 +162,9 @@ class LLNameListCtrl
 
 	LLScrollListItem* getNameItemByAgentId(const LLUUID& agent_id);
 
+    void selectItemBySpecialId(const LLUUID& special_id);
+    LLUUID getSelectedSpecialId();
+
 	// LLView interface
 	/*virtual*/ BOOL	handleDragAndDrop(S32 x, S32 y, MASK mask,
 									  BOOL drop, EDragAndDropType cargo_type, void *cargo_data,
@@ -170,7 +179,14 @@ class LLNameListCtrl
 	/*virtual*/ void updateColumns(bool force_update);
 
 	/*virtual*/ void mouseOverHighlightNthItem( S32 index );
+
     /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask);
+
+    bool isSpecialType() { return (mNameListType == SPECIAL); }
+
+    void setNameListType(e_name_type type) { mNameListType = type; }
+    void setHoverIconName(std::string icon_name) { mHoverIconName = icon_name; }
+
 private:
 	void showInspector(const LLUUID& avatar_id, bool is_group, bool is_experience = false);
 	void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name, std::string suffix, std::string prefix, LLHandle<LLNameListItem> item);
@@ -187,6 +203,11 @@ class LLNameListCtrl
 
 	S32 mPendingLookupsRemaining;
 	namelist_complete_signal_t mNameListCompleteSignal;
+
+    std::string     mHoverIconName;
+    e_name_type     mNameListType;
+
+    boost::signals2::signal<void(const LLUUID &)> mIconClickedSignal;
 	
 public:
 	boost::signals2::connection setOnNameListCompleteCallback(boost::function<void(bool)> onNameListCompleteCallback) 
@@ -194,6 +215,10 @@ class LLNameListCtrl
 		return mNameListCompleteSignal.connect(onNameListCompleteCallback); 
 	}
 
+    boost::signals2::connection setIconClickedCallback(boost::function<void(const LLUUID &)> cb) 
+    { 
+        return mIconClickedSignal.connect(cb); 
+    }
 };
 
 
diff --git a/indra/newview/llpanelpresetspulldown.cpp b/indra/newview/llpanelpresetspulldown.cpp
index 23e4fa8887dfde2bf53e868b322d76c5b78d452c..f6e501f147b2135f7e78510be1aa7114c617db94 100644
--- a/indra/newview/llpanelpresetspulldown.cpp
+++ b/indra/newview/llpanelpresetspulldown.cpp
@@ -34,6 +34,7 @@
 #include "llbutton.h"
 #include "lltabcontainer.h"
 #include "llfloater.h"
+#include "llfloaterperformance.h"
 #include "llfloaterreg.h"
 #include "llpresetsmanager.h"
 #include "llsliderctrl.h"
@@ -50,6 +51,7 @@ LLPanelPresetsPulldown::LLPanelPresetsPulldown()
 	mHoverTimer.stop();
 
 	mCommitCallbackRegistrar.add("Presets.GoGraphicsPrefs", boost::bind(&LLPanelPresetsPulldown::onGraphicsButtonClick, this, _2));
+    mCommitCallbackRegistrar.add("Presets.GoAutofpsPrefs", boost::bind(&LLPanelPresetsPulldown::onAutofpsButtonClick, this, _2));
 	mCommitCallbackRegistrar.add("Presets.RowClick", boost::bind(&LLPanelPresetsPulldown::onRowClick, this, _2));
 
 	buildFromFile( "panel_presets_pulldown.xml");
@@ -157,3 +159,13 @@ void LLPanelPresetsPulldown::onGraphicsButtonClick(const LLSD& user_data)
 		}
 	}
 }
+
+void LLPanelPresetsPulldown::onAutofpsButtonClick(const LLSD& user_data)
+{
+    setVisible(FALSE);
+    LLFloaterPerformance* performance_floater = LLFloaterReg::showTypedInstance<LLFloaterPerformance>("performance");
+    if (performance_floater)
+    {
+        performance_floater->showAutoadjustmentsPanel();
+    }
+}
diff --git a/indra/newview/llpanelpresetspulldown.h b/indra/newview/llpanelpresetspulldown.h
index c0d32b9b21a4df5300efc26c9cf24f19ece191fb..79bd6886b1d60c3272b0e0e5cd55588eb34dc301 100644
--- a/indra/newview/llpanelpresetspulldown.h
+++ b/indra/newview/llpanelpresetspulldown.h
@@ -41,6 +41,7 @@ class LLPanelPresetsPulldown : public LLPanelPulldown
 	
  private:
 	void onGraphicsButtonClick(const LLSD& user_data);
+    void onAutofpsButtonClick(const LLSD& user_data);
 	void onRowClick(const LLSD& user_data);
 
 	std::list<std::string> mPresetNames;
diff --git a/indra/newview/llperfstats.cpp b/indra/newview/llperfstats.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..395ac0e788eedc202a49b238e6cba4157f28034f
--- /dev/null
+++ b/indra/newview/llperfstats.cpp
@@ -0,0 +1,566 @@
+/** 
+* @file llperfstats.cpp
+* @brief Statistics collection to support autotune and perf flaoter.
+*
+* $LicenseInfo:firstyear=2022&license=viewerlgpl$
+* Second Life Viewer Source Code
+* Copyright (C) 2022, Linden Research, Inc.
+* 
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation;
+* version 2.1 of the License only.
+* 
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* Lesser General Public License for more details.
+* 
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+* 
+* Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+* $/LicenseInfo$
+*/
+
+#include "llviewerprecompiledheaders.h"
+#include "llperfstats.h"
+#include "llcontrol.h"
+#include "pipeline.h"
+#include "llagentcamera.h"
+#include "llviewerwindow.h"
+#include "llvoavatar.h"
+#include "llwindow.h"
+#include "llworld.h"
+#include <llthread.h>
+
+extern LLControlGroup gSavedSettings;
+
+namespace LLPerfStats
+{
+    // avatar timing metrics in ms (updated once per mainloop iteration)
+    std::atomic<F32> sTotalAvatarTime = 0.f;
+    std::atomic<F32> sAverageAvatarTime = 0.f;
+    std::atomic<F32> sMaxAvatarTime = 0.f;
+
+    std::atomic<int64_t> tunedAvatars{0};
+    std::atomic<U64> renderAvatarMaxART_ns{(U64)(ART_UNLIMITED_NANOS)}; // highest render time we'll allow without culling features
+    bool belowTargetFPS{false};
+    U32 lastGlobalPrefChange{0}; 
+    U32 lastSleepedFrame{0};
+    U64 meanFrameTime{0};
+    std::mutex bufferToggleLock{};
+
+    F64 cpu_hertz{0.0};
+    U32 vsync_max_fps{60};
+
+    Tunables tunables;
+
+    std::atomic<int> 	StatsRecorder::writeBuffer{0};
+    bool 	            StatsRecorder::collectionEnabled{true};
+    LLUUID              StatsRecorder::focusAv{LLUUID::null};
+    bool                StatsRecorder::autotuneInit{false};
+	std::array<StatsRecorder::StatsTypeMatrix,2>  StatsRecorder::statsDoubleBuffer{ {} };
+    std::array<StatsRecorder::StatsSummaryArray,2> StatsRecorder::max{ {} };
+    std::array<StatsRecorder::StatsSummaryArray,2> StatsRecorder::sum{ {} };
+
+    void Tunables::applyUpdates()
+    {
+        assert_main_thread();
+        // these following variables are proxies for pipeline statics we do not need a two way update (no llviewercontrol handler)
+        if( tuningFlag & NonImpostors ){ gSavedSettings.setU32("IndirectMaxNonImpostors", nonImpostors); };
+        if( tuningFlag & ReflectionDetail ){ gSavedSettings.setS32("RenderReflectionDetail", reflectionDetail); };
+        if( tuningFlag & FarClip ){ gSavedSettings.setF32("RenderFarClip", farClip); };
+        if( tuningFlag & UserMinDrawDistance ){ gSavedSettings.setF32("AutoTuneRenderFarClipMin", userMinDrawDistance); };
+        if( tuningFlag & UserTargetDrawDistance ){ gSavedSettings.setF32("AutoTuneRenderFarClipTarget", userTargetDrawDistance); };
+        if( tuningFlag & UserImpostorDistance ){ gSavedSettings.setF32("AutoTuneImpostorFarAwayDistance", userImpostorDistance); };
+        if( tuningFlag & UserImpostorDistanceTuningEnabled ){ gSavedSettings.setBOOL("AutoTuneImpostorByDistEnabled", userImpostorDistanceTuningEnabled); };
+        if( tuningFlag & UserFPSTuningStrategy ){ gSavedSettings.setU32("TuningFPSStrategy", userFPSTuningStrategy); };
+        if( tuningFlag & UserAutoTuneEnabled ){ gSavedSettings.setBOOL("AutoTuneFPS", userAutoTuneEnabled); };
+        if( tuningFlag & UserAutoTuneLock ){ gSavedSettings.setBOOL("AutoTuneLock", userAutoTuneLock); };
+        if( tuningFlag & UserTargetFPS ){ gSavedSettings.setU32("TargetFPS", userTargetFPS); };
+        // Note: The Max ART slider is logarithmic and thus we have an intermediate proxy value
+        if( tuningFlag & UserARTCutoff ){ gSavedSettings.setF32("RenderAvatarMaxART", userARTCutoffSliderValue); };
+        resetChanges();
+    }
+
+    void Tunables::updateRenderCostLimitFromSettings()
+    {
+        assert_main_thread();
+        const auto newval = gSavedSettings.getF32("RenderAvatarMaxART");
+        if(newval < log10(LLPerfStats::ART_UNLIMITED_NANOS/1000))
+        {
+            LLPerfStats::renderAvatarMaxART_ns = pow(10,newval)*1000;
+        }
+        else
+        {
+            LLPerfStats::renderAvatarMaxART_ns = 0;
+        }
+    }
+
+    // static 
+    void Tunables::updateSettingsFromRenderCostLimit()
+    {
+        if( userARTCutoffSliderValue != log10( ( (F32)LLPerfStats::renderAvatarMaxART_ns )/1000 ) )
+        {
+            if( LLPerfStats::renderAvatarMaxART_ns != 0 )
+            {
+                updateUserARTCutoffSlider(log10( ( (F32)LLPerfStats::renderAvatarMaxART_ns )/1000 ) );
+            }
+            else
+            {
+                updateUserARTCutoffSlider(log10( (F32)LLPerfStats::ART_UNLIMITED_NANOS/1000 ) );
+            }
+        }        
+    }
+
+    void Tunables::initialiseFromSettings()
+    {
+        assert_main_thread();
+        // the following variables are two way and have "push" in llviewercontrol 
+        LLPerfStats::tunables.userMinDrawDistance = gSavedSettings.getF32("AutoTuneRenderFarClipMin");
+        LLPerfStats::tunables.userTargetDrawDistance = gSavedSettings.getF32("AutoTuneRenderFarClipTarget");
+        LLPerfStats::tunables.userImpostorDistance = gSavedSettings.getF32("AutoTuneImpostorFarAwayDistance");
+        LLPerfStats::tunables.userImpostorDistanceTuningEnabled = gSavedSettings.getBOOL("AutoTuneImpostorByDistEnabled");
+        LLPerfStats::tunables.userFPSTuningStrategy = gSavedSettings.getU32("TuningFPSStrategy");
+        LLPerfStats::tunables.userTargetFPS = gSavedSettings.getU32("TargetFPS");
+        LLPerfStats::tunables.vsyncEnabled = gSavedSettings.getBOOL("RenderVSyncEnable");
+
+        LLPerfStats::tunables.userAutoTuneLock = gSavedSettings.getBOOL("AutoTuneLock") && gSavedSettings.getU32("KeepAutoTuneLock");
+
+        if(gSavedSettings.getBOOL("AutoTuneLock") && !gSavedSettings.getU32("KeepAutoTuneLock"))
+        {
+            gSavedSettings.setBOOL("AutoTuneLock", FALSE);
+        }
+
+        LLPerfStats::tunables.userAutoTuneEnabled = LLPerfStats::tunables.userAutoTuneLock;
+
+        if (LLPerfStats::tunables.userAutoTuneEnabled && !gSavedSettings.getBOOL("AutoTuneFPS"))
+        {
+            gSavedSettings.setBOOL("AutoTuneFPS", TRUE);
+        }
+
+        // Note: The Max ART slider is logarithmic and thus we have an intermediate proxy value
+        updateRenderCostLimitFromSettings();
+        resetChanges();
+    }
+
+    StatsRecorder::StatsRecorder():q(1024*16)
+    {
+        // create a queue
+        tunables.initialiseFromSettings();
+        LLPerfStats::cpu_hertz = (F64)LLTrace::BlockTimer::countsPerSecond();
+        LLPerfStats::vsync_max_fps = gViewerWindow->getWindow()->getRefreshRate();
+    }
+
+    // static
+    void StatsRecorder::toggleBuffer()
+    {
+        LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
+        using ST = StatType_t;
+
+        bool unreliable{false};
+        LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_FRAME);
+        auto& sceneStats = statsDoubleBuffer[writeBuffer][static_cast<size_t>(ObjType_t::OT_GENERAL)][LLUUID::null];
+        auto& lastStats = statsDoubleBuffer[writeBuffer ^ 1][static_cast<size_t>(ObjType_t::OT_GENERAL)][LLUUID::null];
+
+        static constexpr std::initializer_list<StatType_t> sceneStatsToAvg = {
+            StatType_t::RENDER_FRAME, 
+            StatType_t::RENDER_DISPLAY, 
+            StatType_t::RENDER_HUDS,
+            StatType_t::RENDER_UI,
+            StatType_t::RENDER_SWAP,
+            // RENDER_LFS,
+            // RENDER_MESHREPO,
+            StatType_t::RENDER_IDLE };
+
+#if 0
+        static constexpr std::initializer_list<StatType_t> avatarStatsToAvg = {
+            StatType_t::RENDER_GEOMETRY, 
+            StatType_t::RENDER_SHADOWS, 
+            StatType_t::RENDER_COMBINED,
+            StatType_t::RENDER_IDLE };
+#endif
+
+
+        if( /*sceneStats[static_cast<size_t>(StatType_t::RENDER_FPSLIMIT)] != 0 ||*/ sceneStats[static_cast<size_t>(StatType_t::RENDER_SLEEP)] != 0 )
+        {
+            unreliable = true;
+            //lastStats[static_cast<size_t>(StatType_t::RENDER_FPSLIMIT)] = sceneStats[static_cast<size_t>(StatType_t::RENDER_FPSLIMIT)];
+            lastStats[static_cast<size_t>(StatType_t::RENDER_SLEEP)] = sceneStats[static_cast<size_t>(StatType_t::RENDER_SLEEP)];
+            lastStats[static_cast<size_t>(StatType_t::RENDER_FRAME)] = sceneStats[static_cast<size_t>(StatType_t::RENDER_FRAME)]; //  bring over the total frame render time to deal with region crossing overlap issues
+        }
+
+        if(!unreliable)
+        {
+            // only use these stats when things are reliable. 
+
+            for(auto & statEntry : sceneStatsToAvg)
+            {
+                auto avg = lastStats[static_cast<size_t>(statEntry)];
+                auto val = sceneStats[static_cast<size_t>(statEntry)];
+                sceneStats[static_cast<size_t>(statEntry)] = avg + (val / SMOOTHING_PERIODS) - (avg / SMOOTHING_PERIODS);
+                // LL_INFOS("scenestats") << "Scenestat: " << static_cast<size_t>(statEntry) << " before=" << avg << " new=" << val << " newavg=" << statsDoubleBuffer[writeBuffer][static_cast<size_t>(ObjType_t::OT_GENERAL)][LLUUID::null][static_cast<size_t>(statEntry)] << LL_ENDL;
+            }
+        }
+        
+        // swap the buffers
+        if(enabled())
+        {
+            std::lock_guard<std::mutex> lock{bufferToggleLock};
+            writeBuffer ^= 1;
+        }; // note we are relying on atomic updates here. The risk is low and would cause minor errors in the stats display. 
+
+        // clean the write maps in all cases.
+        auto& statsTypeMatrix = statsDoubleBuffer[writeBuffer];
+        for(auto& statsMapByType : statsTypeMatrix)
+        {
+            LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("Clear stats maps");
+            for(auto& stat_entry : statsMapByType)
+            {
+                std::fill_n(stat_entry.second.begin() ,static_cast<size_t>(ST::STATS_COUNT),0);
+            }
+            statsMapByType.clear();
+        }
+        for(int i=0; i< static_cast<size_t>(ObjType_t::OT_COUNT); i++)
+        {
+            LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("clear max/sum");
+            max[writeBuffer][i].fill(0);
+            sum[writeBuffer][i].fill(0);
+        }
+
+        // and now adjust the proxy vars so that the main thread can adjust the visuals.
+        if(autotuneInit && tunables.userAutoTuneEnabled)
+        {
+            updateAvatarParams();
+        }
+    }
+
+    // clear buffers when we change region or need a hard reset.
+    // static 
+    void StatsRecorder::clearStatsBuffers()
+    {
+        LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
+        using ST = StatType_t;
+
+        auto& statsTypeMatrix = statsDoubleBuffer[writeBuffer];
+        for(auto& statsMap : statsTypeMatrix)
+        {
+            LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("Clear stats maps");
+            for(auto& stat_entry : statsMap)
+            {
+                std::fill_n(stat_entry.second.begin() ,static_cast<size_t>(ST::STATS_COUNT),0);
+            }
+            statsMap.clear();
+        }
+        for(int i=0; i< static_cast<size_t>(ObjType_t::OT_COUNT); i++)
+        {
+            LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("clear max/sum");
+            max[writeBuffer][i].fill(0);
+            sum[writeBuffer][i].fill(0);
+        }
+        // swap the clean buffers in
+        if(enabled())
+        {
+            std::lock_guard<std::mutex> lock{bufferToggleLock};
+            writeBuffer ^= 1;
+        }; 
+        // repeat before we start processing new stuff
+        for(auto& statsMap : statsTypeMatrix)
+        {
+            LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("Clear stats maps");
+            for(auto& stat_entry : statsMap)
+            {
+                std::fill_n(stat_entry.second.begin() ,static_cast<size_t>(ST::STATS_COUNT),0);
+            }
+            statsMap.clear();
+        }
+        for(int i=0; i< static_cast<size_t>(ObjType_t::OT_COUNT); i++)
+        {
+            LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("clear max/sum");
+            max[writeBuffer][i].fill(0);
+            sum[writeBuffer][i].fill(0);
+        }
+    }
+
+    // called once per main loop iteration on main thread
+    void updateClass()
+    {
+        LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
+        
+        sTotalAvatarTime = LLVOAvatar::getTotalGPURenderTime();
+        sAverageAvatarTime = LLVOAvatar::getAverageGPURenderTime();
+        sMaxAvatarTime = LLVOAvatar::getMaxGPURenderTime();
+
+        auto general = LL::WorkQueue::getInstance("General");
+
+        if (general)
+        {
+            general->post([] { StatsRecorder::update(); });
+        }
+    }
+
+    // called once per main loop iteration on General thread
+    void StatsRecorder::update()
+    {
+        LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
+        StatsRecord upd;
+        auto& instance{ StatsRecorder::getInstance() };
+
+        while (enabled() && !LLApp::isQuitting() && instance.q.tryPop(upd))
+        {
+            instance.processUpdate(upd);
+        }
+    }
+
+    //static
+    int StatsRecorder::countNearbyAvatars(S32 distance)
+    {
+        const auto our_pos = gAgentCamera.getCameraPositionGlobal();
+
+       	std::vector<LLVector3d> positions;
+	    uuid_vec_t avatar_ids;
+        LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, our_pos, distance);
+        return positions.size();
+	}
+
+    const U32 NUM_PERIODS = 50;
+    void StatsRecorder::updateMeanFrameTime(U64 cur_frame_time_raw)
+    {
+        static std::deque<U64> frame_time_deque;
+        frame_time_deque.push_front(cur_frame_time_raw);
+        if (frame_time_deque.size() > NUM_PERIODS)
+        {
+            frame_time_deque.pop_back();
+        }
+        
+        std::vector<U64> buf(frame_time_deque.begin(), frame_time_deque.end());
+        std::sort(buf.begin(), buf.end());
+
+        LLPerfStats::meanFrameTime = (buf.size() % 2 == 0) ? (buf[buf.size() / 2 - 1] + buf[buf.size() / 2]) / 2 : buf[buf.size() / 2];
+    }
+    U64 StatsRecorder::getMeanTotalFrameTime()
+    {
+        return LLPerfStats::meanFrameTime;
+    }
+
+    // static
+    void StatsRecorder::updateAvatarParams()
+    {
+        LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
+
+        if(tunables.autoTuneTimeout)
+        {
+            LLPerfStats::lastSleepedFrame = gFrameCount;
+            tunables.autoTuneTimeout = false;
+            return;
+        }
+        // sleep time is basically forced sleep when window out of focus
+        auto tot_sleep_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_SLEEP);
+        // similar to sleep time, induced by FPS limit
+        //auto tot_limit_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_FPSLIMIT);
+
+
+        // the time spent this frame on the "doFrame" call. Treated as "tot time for frame"
+        auto tot_frame_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_FRAME);
+
+        if( tot_sleep_time_raw != 0 )
+        {
+            // Note: we do not average sleep 
+            // if at some point we need to, the averaging will need to take this into account or 
+            // we forever think we're in the background due to residuals.
+            LL_DEBUGS() << "No tuning when not in focus" << LL_ENDL;
+            LLPerfStats::lastSleepedFrame = gFrameCount;
+            return;
+        }
+
+        U32 target_fps = tunables.vsyncEnabled ? std::min(LLPerfStats::vsync_max_fps, tunables.userTargetFPS) : tunables.userTargetFPS;
+
+        if(LLPerfStats::lastSleepedFrame != 0)
+        {
+            // wait a short time after viewer regains focus
+            if((gFrameCount - LLPerfStats::lastSleepedFrame) > target_fps * 5)
+            {
+                LLPerfStats::lastSleepedFrame = 0;
+            }
+            else
+            {
+                return;
+            }
+        }
+        updateMeanFrameTime(tot_frame_time_raw);
+
+        if(tunables.userImpostorDistanceTuningEnabled)
+        {
+            // if we have less than the user's "max Non-Impostors" avatars within the desired range then adjust the limit.
+            // also adjusts back up again for nearby crowds.
+            auto count = countNearbyAvatars(std::min(LLPipeline::RenderFarClip, tunables.userImpostorDistance));
+            if( count != tunables.nonImpostors )
+            {
+                tunables.updateNonImposters( (count < LLVOAvatar::NON_IMPOSTORS_MAX_SLIDER)?count : LLVOAvatar::NON_IMPOSTORS_MAX_SLIDER );
+                LL_DEBUGS("AutoTune") << "There are " << count << "avatars within " << std::min(LLPipeline::RenderFarClip, tunables.userImpostorDistance) << "m of the camera" << LL_ENDL;
+            }
+        }
+
+        auto av_render_max_raw = ms_to_raw(sMaxAvatarTime);
+        // Is our target frame time lower than current? If so we need to take action to reduce draw overheads.
+        // cumulative avatar time (includes idle processing, attachments and base av)
+        auto tot_avatar_time_raw = ms_to_raw(sTotalAvatarTime); 
+
+        // The frametime budget we have based on the target FPS selected
+        auto target_frame_time_raw = (U64)llround(LLPerfStats::cpu_hertz / (target_fps == 0 ? 1 : target_fps));
+        // LL_INFOS() << "Effective FPS(raw):" << tot_frame_time_raw << " Target:" << target_frame_time_raw << LL_ENDL;
+        auto inferredFPS{1000/(U32)std::max(raw_to_ms(tot_frame_time_raw),1.0)};
+        U32 settingsChangeFrequency{inferredFPS > 50?inferredFPS:50};
+        /*if( tot_limit_time_raw != 0)
+        {
+            // This could be problematic.
+            tot_frame_time_raw -= tot_limit_time_raw;
+        }*/
+        
+        F64 time_buf = target_frame_time_raw * 0.1;
+
+        // 1) Is the target frame time lower than current?
+        if ((target_frame_time_raw + time_buf) <= tot_frame_time_raw)
+        {
+            if (target_frame_time_raw - time_buf >= getMeanTotalFrameTime())
+            {
+                belowTargetFPS = false;
+                LLPerfStats::lastGlobalPrefChange = gFrameCount;
+                return;
+            }
+
+            if(belowTargetFPS == false)
+            {
+                // this is the first frame under. hold fire to add a little hysteresis
+                belowTargetFPS = true;
+                LLPerfStats::lastGlobalPrefChange = gFrameCount;
+            }
+            // if so we've got work to do
+
+            // how much of the frame was spent on non avatar related work?
+            U64 non_avatar_time_raw = tot_frame_time_raw > tot_avatar_time_raw ? tot_frame_time_raw - tot_avatar_time_raw : 0;
+
+            // If the target frame time < scene time (estimated as non_avatar time)
+            U64 target_avatar_time_raw;
+            if(target_frame_time_raw < non_avatar_time_raw)
+            {
+                // we cannnot do this by avatar adjustment alone.
+                if((gFrameCount - LLPerfStats::lastGlobalPrefChange) > settingsChangeFrequency) // give  changes a short time to take effect.
+                {
+                    if(tunables.userFPSTuningStrategy != TUNE_AVATARS_ONLY)
+                    {
+                        // 1 - hack the water to opaque. all non opaque have a significant hit, this is a big boost for (arguably) a minor visual hit.
+                        // the other reflection options make comparatively little change and if this overshoots we'll be stepping back up later
+# if 0 // TODO RenderReflectionDetail went away
+                        if(LLPipeline::RenderReflectionDetail != -2)
+                        {
+                            LLPerfStats::tunables.updateReflectionDetail(-2);
+                            LLPerfStats::lastGlobalPrefChange = gFrameCount;
+                            return;
+                        }
+                        else // deliberately "else" here so we only do one of these in any given frame
+#endif
+                        {
+                            // step down the DD by 10m per update
+                            auto new_dd = (LLPipeline::RenderFarClip - DD_STEP > tunables.userMinDrawDistance)?(LLPipeline::RenderFarClip - DD_STEP) : tunables.userMinDrawDistance;
+                            if(new_dd != LLPipeline::RenderFarClip)
+                            {
+                                LLPerfStats::tunables.updateFarClip( new_dd );
+                                LLPerfStats::lastGlobalPrefChange = gFrameCount;
+                                return;
+                            }
+                        }
+                    }
+                    // if we reach here, we've no more changes to make to tune scenery so we'll resort to agressive Avatar tuning
+                    // Note: moved from outside "if changefrequency elapsed" to stop fallthrough and allow scenery changes time to take effect.
+                    target_avatar_time_raw = 0;
+                }
+                else 
+                {
+                    // we made a settings change recently so let's give it time.
+                    return;
+                }
+            }
+            else
+            {
+                // set desired avatar budget.
+                target_avatar_time_raw =  target_frame_time_raw - non_avatar_time_raw;
+            }
+
+            if ((target_avatar_time_raw < tot_avatar_time_raw) && (tunables.userFPSTuningStrategy != TUNE_SCENE_ONLY))
+            {
+                // we need to spend less time drawing avatars to meet our budget
+                auto new_render_limit_ns {LLPerfStats::raw_to_ns(av_render_max_raw)};
+                // max render this frame may be higher than the last (cos new entrants and jitter) so make sure we are heading in the right direction
+                if( new_render_limit_ns > renderAvatarMaxART_ns )
+                {
+                    new_render_limit_ns = renderAvatarMaxART_ns;
+                }
+
+                if (new_render_limit_ns > LLPerfStats::ART_MIN_ADJUST_DOWN_NANOS)
+                {
+                    new_render_limit_ns -= LLPerfStats::ART_MIN_ADJUST_DOWN_NANOS;
+                }
+
+                // bounce at the bottom to prevent "no limit" 
+                new_render_limit_ns = std::max((U64)new_render_limit_ns, (U64)LLPerfStats::ART_MINIMUM_NANOS);
+
+                // assign the new value
+                if(renderAvatarMaxART_ns != new_render_limit_ns)
+                {
+                    renderAvatarMaxART_ns = new_render_limit_ns;
+                    tunables.updateSettingsFromRenderCostLimit();
+                }
+                // LL_DEBUGS() << "AUTO_TUNE: avatar_budget adjusted to:" << new_render_limit_ns << LL_ENDL;
+            }
+            // LL_DEBUGS() << "AUTO_TUNE: Target frame time:"<< LLPerfStats::raw_to_us(target_frame_time_raw) << "usecs (non_avatar is " << LLPerfStats::raw_to_us(non_avatar_time_raw) << "usecs) Max cost limited=" << renderAvatarMaxART_ns << LL_ENDL;
+        }
+        else if(( LLPerfStats::raw_to_ns(target_frame_time_raw) > (LLPerfStats::raw_to_ns(tot_frame_time_raw) + renderAvatarMaxART_ns) ) ||
+                 (tunables.vsyncEnabled && (target_fps == LLPerfStats::vsync_max_fps) && (target_frame_time_raw > getMeanTotalFrameTime())))
+        {
+            if(belowTargetFPS == true)
+            {
+                // we reached target, force a pause
+                lastGlobalPrefChange = gFrameCount;
+                belowTargetFPS = false;
+            }
+
+            // once we're over the FPS target we slow down further
+            if((gFrameCount - lastGlobalPrefChange) > settingsChangeFrequency*3)
+            {
+                if(!tunables.userAutoTuneLock)
+                {
+                    // we've reached the target and stayed long enough to consider stable.
+                    // turn off if we are not locked.
+                    tunables.updateUserAutoTuneEnabled(false);
+                }
+                if(renderAvatarMaxART_ns != 0 && LLPerfStats::tunedAvatars > 0 && (tunables.userFPSTuningStrategy != TUNE_SCENE_ONLY) )
+                {
+                    // if we have more time to spare let's shift up little in the hope we'll restore an avatar.
+                    U64 up_step = LLPerfStats::tunedAvatars > 2 ? LLPerfStats::ART_MIN_ADJUST_UP_NANOS : LLPerfStats::ART_MIN_ADJUST_UP_NANOS * 2;
+                    renderAvatarMaxART_ns += up_step;
+                    tunables.updateSettingsFromRenderCostLimit();
+                    return;
+                }
+                if(tunables.userFPSTuningStrategy != TUNE_AVATARS_ONLY)
+                {
+                    if( LLPipeline::RenderFarClip < tunables.userTargetDrawDistance ) 
+                    {
+                        LLPerfStats::tunables.updateFarClip( std::min(LLPipeline::RenderFarClip + DD_STEP, tunables.userTargetDrawDistance) );
+                        LLPerfStats::lastGlobalPrefChange = gFrameCount;
+                        return;
+                    }
+                    if( (tot_frame_time_raw * 1.5) < target_frame_time_raw )
+                    {
+                        // if everything else is "max" and we have >50% headroom let's knock the water quality up a notch at a time.
+# if 0 // RenderReflectionDetail went away
+                        LLPerfStats::tunables.updateReflectionDetail( std::min(LLPipeline::RenderReflectionDetail + 1, tunables.userTargetReflections) );
+#endif
+                    }
+                }
+            }
+        }
+   }
+}
diff --git a/indra/newview/llperfstats.h b/indra/newview/llperfstats.h
new file mode 100644
index 0000000000000000000000000000000000000000..bb5677f237e8b57c031af7213eaa42bb5e2e0d9f
--- /dev/null
+++ b/indra/newview/llperfstats.h
@@ -0,0 +1,350 @@
+/** 
+* @file llperfstats.h
+* @brief Statistics collection to support autotune and perf flaoter.
+*
+* $LicenseInfo:firstyear=2022&license=viewerlgpl$
+* Second Life Viewer Source Code
+* Copyright (C) 2022, 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$
+*/
+#pragma once
+#ifndef LL_PERFSTATS_H_INCLUDED
+#define LL_PERFSTATS_H_INCLUDED
+
+#include <atomic>
+#include <chrono>
+#include <array>
+#include <unordered_map>
+#include <mutex>
+#include "lluuid.h"
+#include "llfasttimer.h"
+#include "llapp.h"
+#include "llprofiler.h"
+#include "pipeline.h"
+
+extern U32 gFrameCount;
+extern LLUUID gAgentID;
+namespace LLPerfStats
+{
+
+    // called once per main loop iteration
+    void updateClass();
+
+// Note if changing these, they should correspond with the log range of the correpsonding sliders
+    static constexpr U64 ART_UNLIMITED_NANOS{50000000};
+    static constexpr U64 ART_MINIMUM_NANOS{100000};
+    static constexpr U64 ART_MIN_ADJUST_UP_NANOS{5000};
+    static constexpr U64 ART_MIN_ADJUST_DOWN_NANOS{10000}; 
+
+    static constexpr F32 PREFERRED_DD{180};
+    static constexpr U32 SMOOTHING_PERIODS{50};
+    static constexpr U32 DD_STEP{10};
+
+    static constexpr U32 TUNE_AVATARS_ONLY{0};
+    static constexpr U32 TUNE_SCENE_AND_AVATARS{1};
+    static constexpr U32 TUNE_SCENE_ONLY{2};
+
+    extern F64 cpu_hertz;
+
+    extern std::atomic<int64_t> tunedAvatars;
+    extern std::atomic<U64> renderAvatarMaxART_ns;
+    extern bool belowTargetFPS;
+    extern U32 lastGlobalPrefChange;
+    extern U32 lastSleepedFrame;
+    extern U64 meanFrameTime;
+    extern std::mutex bufferToggleLock;
+
+    enum class ObjType_t{
+        OT_GENERAL=0, // Also Unknown. Used for n/a type stats such as scenery
+        OT_COUNT
+    };
+    enum class StatType_t{
+        RENDER_GEOMETRY=0,
+        RENDER_SHADOWS,
+        RENDER_HUDS,
+        RENDER_UI,
+        RENDER_COMBINED,
+        RENDER_SWAP,
+        RENDER_FRAME,
+        RENDER_DISPLAY,
+        RENDER_SLEEP,
+        RENDER_LFS,
+        RENDER_MESHREPO,
+        //RENDER_FPSLIMIT,
+        RENDER_FPS,
+        RENDER_IDLE,
+        RENDER_DONE, // toggle buffer & clearbuffer (see processUpdate for hackery)
+        STATS_COUNT
+    };
+
+    struct StatsRecord
+    { 
+        StatType_t  statType;
+        ObjType_t   objType;
+        LLUUID      avID;
+        LLUUID      objID;
+        uint64_t    time;
+        bool        isRigged;
+        bool        isHUD;
+    };
+
+    struct Tunables
+    {
+        static constexpr U32 Nothing{0};
+        static constexpr U32 NonImpostors{1};
+        static constexpr U32 ReflectionDetail{2};
+        static constexpr U32 FarClip{4};
+        static constexpr U32 UserMinDrawDistance{8};
+        static constexpr U32 UserTargetDrawDistance{16};
+        static constexpr U32 UserImpostorDistance{32};
+        static constexpr U32 UserImpostorDistanceTuningEnabled{64};
+        static constexpr U32 UserFPSTuningStrategy{128};
+        static constexpr U32 UserAutoTuneEnabled{256};
+        static constexpr U32 UserTargetFPS{512};
+        static constexpr U32 UserARTCutoff{1024};
+        static constexpr U32 UserAutoTuneLock{4096};
+
+        U32 tuningFlag{0}; // bit mask for changed settings
+
+        // proxy variables, used to pas the new value to be set via the mainthread
+        U32 nonImpostors{0}; 
+        S32 reflectionDetail{0}; 
+        F32 farClip{0.0}; 
+        F32 userMinDrawDistance{0.0}; 
+        F32 userTargetDrawDistance{0.0};
+        F32 userImpostorDistance{0.0};
+        bool userImpostorDistanceTuningEnabled{false};
+        U32 userFPSTuningStrategy{0};
+        bool userAutoTuneEnabled{false};
+        bool userAutoTuneLock{true};
+        U32 userTargetFPS{0};
+        F32 userARTCutoffSliderValue{0};
+        S32 userTargetReflections{0};
+        bool autoTuneTimeout{true};
+        bool vsyncEnabled{true};
+
+        void updateNonImposters(U32 nv){nonImpostors=nv; tuningFlag |= NonImpostors;};
+        void updateReflectionDetail(S32 nv){reflectionDetail=nv; tuningFlag |= ReflectionDetail;};
+        void updateFarClip(F32 nv){farClip=nv; tuningFlag |= FarClip;};
+        void updateUserMinDrawDistance(F32 nv){userMinDrawDistance=nv; tuningFlag |= UserMinDrawDistance;};
+        void updateUserTargetDrawDistance(F32 nv){userTargetDrawDistance=nv; tuningFlag |= UserTargetDrawDistance;};
+        void updateImposterDistance(F32 nv){userImpostorDistance=nv; tuningFlag |= UserImpostorDistance;};
+        void updateImposterDistanceTuningEnabled(bool nv){userImpostorDistanceTuningEnabled=nv; tuningFlag |= UserImpostorDistanceTuningEnabled;};
+        void updateUserFPSTuningStrategy(U32 nv){userFPSTuningStrategy=nv; tuningFlag |= UserFPSTuningStrategy;};
+        void updateTargetFps(U32 nv){userTargetFPS=nv; tuningFlag |= UserTargetFPS;};
+        void updateUserARTCutoffSlider(F32 nv){userARTCutoffSliderValue=nv; tuningFlag |= UserARTCutoff;};
+        void updateUserAutoTuneEnabled(bool nv){userAutoTuneEnabled=nv; tuningFlag |= UserAutoTuneEnabled;};
+        void updateUserAutoTuneLock(bool nv){userAutoTuneLock=nv; tuningFlag |= UserAutoTuneLock;};
+
+        void resetChanges(){tuningFlag=Nothing;};
+        void initialiseFromSettings();
+        void updateRenderCostLimitFromSettings();
+        void updateSettingsFromRenderCostLimit();
+        void applyUpdates();
+    };
+
+    extern Tunables tunables;
+
+    class StatsRecorder{
+        using Queue = LLThreadSafeQueue<StatsRecord>;
+    public:
+
+        // called once per main loop iteration on General thread
+        static void update();
+
+        static inline StatsRecorder& getInstance()
+        {
+            static StatsRecorder instance;
+            return instance;
+        }
+        static inline void setFocusAv(const LLUUID& avID){focusAv = avID;};
+        static inline const LLUUID& getFocusAv(){return focusAv;};
+        static inline void setAutotuneInit(){autotuneInit = true;};
+        static inline void send(StatsRecord && upd){StatsRecorder::getInstance().q.pushFront(std::move(upd));};
+        static void endFrame(){StatsRecorder::getInstance().q.pushFront(StatsRecord{StatType_t::RENDER_DONE, ObjType_t::OT_GENERAL, LLUUID::null, LLUUID::null, 0});};
+        static void clearStats(){StatsRecorder::getInstance().q.pushFront(StatsRecord{StatType_t::RENDER_DONE, ObjType_t::OT_GENERAL, LLUUID::null, LLUUID::null, 1});};
+
+        static inline void setEnabled(bool on_or_off){collectionEnabled=on_or_off;};
+        static inline void enable()     { collectionEnabled=true; };
+        static inline void disable()    { collectionEnabled=false; };
+        static inline bool enabled()    { return collectionEnabled; };
+
+        static inline int getReadBufferIndex() { return (writeBuffer ^ 1); };
+        // static inline const StatsTypeMatrix& getCurrentStatsMatrix(){ return statsDoubleBuffer[getReadBufferIndex()];}
+        static inline uint64_t get(ObjType_t otype, LLUUID id, StatType_t type)
+        {
+            return statsDoubleBuffer[getReadBufferIndex()][static_cast<size_t>(otype)][id][static_cast<size_t>(type)];
+        }
+        static inline uint64_t getSceneStat(StatType_t type)
+        {
+            return statsDoubleBuffer[getReadBufferIndex()][static_cast<size_t>(ObjType_t::OT_GENERAL)][LLUUID::null][static_cast<size_t>(type)];
+        }
+
+        static inline uint64_t getSum(ObjType_t otype, StatType_t type)
+        {
+            return sum[getReadBufferIndex()][static_cast<size_t>(otype)][static_cast<size_t>(type)];
+        }
+        static inline uint64_t getMax(ObjType_t otype, StatType_t type)
+        {
+            return max[getReadBufferIndex()][static_cast<size_t>(otype)][static_cast<size_t>(type)];
+        }
+        static void updateAvatarParams();
+    private:
+        StatsRecorder();
+
+        static int countNearbyAvatars(S32 distance);
+        static U64 getMeanTotalFrameTime();
+        static void updateMeanFrameTime(U64 tot_frame_time_raw);
+// StatsArray is a uint64_t for each possible statistic type.
+        using StatsArray    = std::array<uint64_t, static_cast<size_t>(LLPerfStats::StatType_t::STATS_COUNT)>;
+        using StatsMap      = std::unordered_map<LLUUID, StatsArray, boost::hash<LLUUID>>;
+        using StatsTypeMatrix = std::array<StatsMap, static_cast<size_t>(LLPerfStats::ObjType_t::OT_COUNT)>;
+        using StatsSummaryArray = std::array<StatsArray, static_cast<size_t>(LLPerfStats::ObjType_t::OT_COUNT)>;
+
+        static std::atomic<int> writeBuffer;
+        static LLUUID focusAv;
+        static bool autotuneInit;
+        static std::array<StatsTypeMatrix,2> statsDoubleBuffer;
+        static std::array<StatsSummaryArray,2> max;
+        static std::array<StatsSummaryArray,2> sum;
+        static bool collectionEnabled;
+
+
+        void processUpdate(const StatsRecord& upd) const
+        {
+            LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
+            // LL_INFOS("perfstats") << "processing update:" << LL_ENDL;
+            // Note: nullptr is used as the key for global stats
+
+            if (upd.statType == StatType_t::RENDER_DONE && upd.objType == ObjType_t::OT_GENERAL && upd.time == 0)
+            {
+                // LL_INFOS("perfstats") << "End of Frame Toggle Buffer:" << gFrameCount << LL_ENDL;
+                toggleBuffer();
+                return;
+            }
+            if (upd.statType == StatType_t::RENDER_DONE && upd.objType == ObjType_t::OT_GENERAL && upd.time == 1)
+            {
+                // LL_INFOS("perfstats") << "New region - clear buffers:" << gFrameCount << LL_ENDL;
+                clearStatsBuffers();
+                return;
+            }
+
+            auto ot{upd.objType};
+            auto& key{upd.objID};
+            auto type {upd.statType};
+            auto val {upd.time};
+
+            if (ot == ObjType_t::OT_GENERAL)
+            {
+                // LL_INFOS("perfstats") << "General update:" << LL_ENDL;
+                doUpd(key, ot, type,val);
+                return;
+            }
+        }
+
+        static inline void doUpd(const LLUUID& key, ObjType_t ot, StatType_t type, uint64_t val)
+        {
+            LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
+            using ST = StatType_t;
+            StatsMap& stm {statsDoubleBuffer[writeBuffer][static_cast<size_t>(ot)]};
+            auto& thisAsset = stm[key];
+
+            thisAsset[static_cast<size_t>(type)] += val;
+            thisAsset[static_cast<size_t>(ST::RENDER_COMBINED)] += val;
+
+            sum[writeBuffer][static_cast<size_t>(ot)][static_cast<size_t>(type)] += val;
+            sum[writeBuffer][static_cast<size_t>(ot)][static_cast<size_t>(ST::RENDER_COMBINED)] += val;
+
+            if(max[writeBuffer][static_cast<size_t>(ot)][static_cast<size_t>(type)] < thisAsset[static_cast<size_t>(type)])
+            {
+                max[writeBuffer][static_cast<size_t>(ot)][static_cast<size_t>(type)] = thisAsset[static_cast<size_t>(type)];
+            }
+            if(max[writeBuffer][static_cast<size_t>(ot)][static_cast<size_t>(ST::RENDER_COMBINED)] < thisAsset[static_cast<size_t>(ST::RENDER_COMBINED)])
+            {
+                max[writeBuffer][static_cast<size_t>(ot)][static_cast<size_t>(ST::RENDER_COMBINED)] = thisAsset[static_cast<size_t>(ST::RENDER_COMBINED)];
+            }
+        }
+
+        static void toggleBuffer();
+        static void clearStatsBuffers();
+
+        Queue q;
+
+        ~StatsRecorder() = default;
+        StatsRecorder(const StatsRecorder&) = delete;
+        StatsRecorder& operator=(const StatsRecorder&) = delete;
+
+    };
+
+    template <enum ObjType_t ObjTypeDiscriminator>
+    class RecordTime
+    {
+
+    private:
+        RecordTime(const RecordTime&) = delete;
+        RecordTime() = delete;
+        U64 start;
+    public:
+        StatsRecord stat;
+
+        RecordTime( const LLUUID& av, const LLUUID& id, StatType_t type, bool isRiggedAtt=false, bool isHUDAtt=false):
+                    start{LLTrace::BlockTimer::getCPUClockCount64()},
+                    stat{type, ObjTypeDiscriminator, std::move(av), std::move(id), 0, isRiggedAtt, isHUDAtt}
+        {
+            //LL_PROFILE_ZONE_COLOR(tracy::Color::Orange);
+        };
+
+        template < ObjType_t OD = ObjTypeDiscriminator,
+                   std::enable_if_t<OD == ObjType_t::OT_GENERAL> * = nullptr>
+        explicit RecordTime( StatType_t type ):RecordTime<ObjTypeDiscriminator>(LLUUID::null, LLUUID::null, type )
+        {
+            LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS;
+        };
+
+        ~RecordTime()
+        { 
+            if(!LLPerfStats::StatsRecorder::enabled())
+            {
+                return;
+            }
+
+            //LL_PROFILE_ZONE_COLOR(tracy::Color::Red);
+
+            stat.time = LLTrace::BlockTimer::getCPUClockCount64() - start;
+            StatsRecorder::send(std::move(stat));
+        };
+    };
+
+
+    inline double raw_to_ns(U64 raw)    { return (static_cast<double>(raw) * 1000000000.0) / LLPerfStats::cpu_hertz; };
+    inline double raw_to_us(U64 raw)    { return (static_cast<double>(raw) *    1000000.0) / LLPerfStats::cpu_hertz; };
+    inline double raw_to_ms(U64 raw)    { return (static_cast<double>(raw) *       1000.0) / LLPerfStats::cpu_hertz; };
+
+    inline U64 ns_to_raw(double ns)     { return (U64)(LLPerfStats::cpu_hertz * (ns / 1000000000.0)); }
+    inline U64 us_to_raw(double us)     { return (U64)(LLPerfStats::cpu_hertz * (us / 1000000.0)); }
+    inline U64 ms_to_raw(double ms)     { return (U64)(LLPerfStats::cpu_hertz * (ms / 1000.0));
+
+    }
+    
+
+    using RecordSceneTime = RecordTime<ObjType_t::OT_GENERAL>;
+
+};// namespace LLPerfStats
+
+#endif
diff --git a/indra/newview/llreflectionmap.h b/indra/newview/llreflectionmap.h
index 803f7bdc97dd312666987519de9429784adaf348..7ea0fe6187ca448c9ff2c29ef4d8b5f182d658de 100644
--- a/indra/newview/llreflectionmap.h
+++ b/indra/newview/llreflectionmap.h
@@ -78,8 +78,12 @@ class alignas(16) LLReflectionMap : public LLRefCount
     // point at which environment map was last generated from (in agent space)
     LLVector4a mOrigin;
     
-    // distance from viewer camera
-    F32 mDistance;
+    // distance from main viewer camera
+    F32 mDistance = -1.f;
+
+    // Minimum and maximum depth in current render camera
+    F32 mMinDepth = -1.f;
+    F32 mMaxDepth = -1.f;
 
     // radius of this probe's affected area
     F32 mRadius = 16.f;
diff --git a/indra/newview/llreflectionmapmanager.cpp b/indra/newview/llreflectionmapmanager.cpp
index 88edbc9224348da20742e48e17dce65145a4963f..2235453e470657ed14092ee30f29f96b78d3a04b 100644
--- a/indra/newview/llreflectionmapmanager.cpp
+++ b/indra/newview/llreflectionmapmanager.cpp
@@ -45,10 +45,13 @@ extern U32 nhpo2(U32 v);
 
 static void touch_default_probe(LLReflectionMap* probe)
 {
-    LLVector3 origin = LLViewerCamera::getInstance()->getOrigin();
-    origin.mV[2] += 64.f;
+    if (LLViewerCamera::getInstance())
+    {
+        LLVector3 origin = LLViewerCamera::getInstance()->getOrigin();
+        origin.mV[2] += 64.f;
 
-    probe->mOrigin.load3(origin.mV);
+        probe->mOrigin.load3(origin.mV);
+    }
 }
 
 LLReflectionMapManager::LLReflectionMapManager()
@@ -58,17 +61,17 @@ LLReflectionMapManager::LLReflectionMapManager()
 
 void LLReflectionMapManager::initCubeFree()
 {
+    // start at 1 because index 0 is reserved for mDefaultProbe
     for (int i = 1; i < LL_MAX_REFLECTION_PROBE_COUNT; ++i)
     {
-        mCubeFree[i] = true;
+        mCubeFree.push_back(i);
     }
-
-    // cube index 0 is reserved for the fallback probe
-    mCubeFree[0] = false;
 }
 
 struct CompareProbeDistance
 {
+    LLReflectionMap* mDefaultProbe;
+
     bool operator()(const LLPointer<LLReflectionMap>& lhs, const LLPointer<LLReflectionMap>& rhs)
     {
         return lhs->mDistance < rhs->mDistance;
@@ -83,7 +86,15 @@ static F32 update_score(LLReflectionMap* p)
 // return true if a is higher priority for an update than b
 static bool check_priority(LLReflectionMap* a, LLReflectionMap* b)
 {
-    if (!a->mComplete && !b->mComplete)
+    if (a->mCubeIndex == -1)
+    { // not a candidate for updating
+        return false;
+    }
+    else if (b->mCubeIndex == -1)
+    { // certainly higher priority than b
+        return true;
+    }
+    else if (!a->mComplete && !b->mComplete)
     { //neither probe is complete, use distance
         return a->mDistance < b->mDistance;
     }
@@ -133,14 +144,7 @@ void LLReflectionMapManager::update()
         }
     }
 
-
-    if (mDefaultProbe.isNull())
-    {
-        mDefaultProbe = addProbe();
-        mDefaultProbe->mDistance = -4096.f; // hack to make sure the default probe is always first in sort order
-        mDefaultProbe->mRadius = 4096.f;
-        touch_default_probe(mDefaultProbe);
-    }
+    llassert(mProbes[0] == mDefaultProbe);
     
     LLVector4a camera_pos;
     camera_pos.load3(LLViewerCamera::instance().getOrigin().mV);
@@ -170,6 +174,7 @@ void LLReflectionMapManager::update()
         return;
     }
 
+
     bool did_update = false;
     
     static LLCachedControl<S32> sDetail(gSavedSettings, "RenderReflectionProbeDetail", -1);
@@ -188,7 +193,46 @@ void LLReflectionMapManager::update()
         doProbeUpdate();
     }
 
-    //LL_INFOS() << mProbes.size() << LL_ENDL;
+    // update distance to camera for all probes
+    std::sort(mProbes.begin()+1, mProbes.end(), CompareProbeDistance());
+    llassert(mProbes[0] == mDefaultProbe);
+    llassert(mProbes[0]->mCubeArray == mTexture);
+    llassert(mProbes[0]->mCubeIndex == 0);
+
+    // make sure we're assigning cube slots to the closest probes
+
+    // first free any cube indices for distant probes
+    for (U32 i = mReflectionProbeCount; i < mProbes.size(); ++i)
+    {
+        LLReflectionMap* probe = mProbes[i];
+        llassert(probe != nullptr);
+
+        if (probe->mCubeIndex != -1 && mUpdatingProbe != probe)
+        { // free this index
+            mCubeFree.push_back(probe->mCubeIndex);
+
+            probe->mCubeArray = nullptr;
+            probe->mCubeIndex = -1;
+            probe->mComplete = false;
+        }
+    }
+
+    // next distribute the free indices
+    U32 count = llmin(mReflectionProbeCount, (U32)mProbes.size());
+
+    for (S32 i = 1; i < count && !mCubeFree.empty(); ++i)
+    {
+        // find the closest probe that needs a cube index
+        LLReflectionMap* probe = mProbes[i];
+
+        if (probe->mCubeIndex == -1)
+        {
+            S32 idx = allocateCubeIndex();
+            llassert(idx > 0); //if we're still in this loop, mCubeFree should not be empty and allocateCubeIndex should be returning good indices
+            probe->mCubeArray = mTexture;
+            probe->mCubeIndex = idx;
+        }
+    }
 
     for (int i = 0; i < mProbes.size(); ++i)
     {
@@ -205,8 +249,6 @@ void LLReflectionMapManager::update()
             continue;
         }
 
-        probe->mProbeIndex = i;
-
         LLVector4a d;
 
         if (probe != mDefaultProbe)
@@ -219,6 +261,10 @@ void LLReflectionMapManager::update()
             // make default probe have a distance of 64m for the purposes of prioritization (if it's already been generated once)
             probe->mDistance = 64.f;
         }
+        else
+        {
+            probe->mDistance = -4096.f; //boost priority of default probe when it's not complete
+        }
 
         if (probe->mComplete)
         {
@@ -288,13 +334,8 @@ void LLReflectionMapManager::update()
     if (!did_update && oldestProbe != nullptr)
     {
         LLReflectionMap* probe = oldestProbe;
-        if (probe->mCubeIndex == -1)
-        {
-            probe->mCubeArray = mTexture;
-
-            probe->mCubeIndex = probe == mDefaultProbe ? 0 : allocateCubeIndex();
-        }
-
+        llassert(probe->mCubeIndex != -1);
+        
         probe->autoAdjustOrigin();
 
         mUpdatingProbe = probe;
@@ -307,10 +348,6 @@ void LLReflectionMapManager::update()
         oldestOccluded->autoAdjustOrigin();
         oldestOccluded->mLastUpdateTime = gFrameTimeSeconds;
     }
-
-    // update distance to camera for all probes
-    mDefaultProbe->mDistance = -4096.f; // make default probe always end up at index 0
-    std::sort(mProbes.begin(), mProbes.end(), CompareProbeDistance());
 }
 
 LLReflectionMap* LLReflectionMapManager::addProbe(LLSpatialGroup* group)
@@ -318,6 +355,14 @@ LLReflectionMap* LLReflectionMapManager::addProbe(LLSpatialGroup* group)
     LLReflectionMap* probe = new LLReflectionMap();
     probe->mGroup = group;
 
+    if (mDefaultProbe.isNull())
+    {  //safety check to make sure default probe is always first probe added
+        mDefaultProbe = new LLReflectionMap();
+        mProbes.push_back(mDefaultProbe);
+    }
+
+    llassert(mProbes[0] == mDefaultProbe);
+
     if (group)
     {
         probe->mOrigin = group->getOctreeNode()->getCenter();
@@ -335,10 +380,22 @@ LLReflectionMap* LLReflectionMapManager::addProbe(LLSpatialGroup* group)
     return probe;
 }
 
+struct CompareProbeDepth
+{
+    bool operator()(const LLReflectionMap* lhs, const LLReflectionMap* rhs)
+    {
+        return lhs->mMinDepth < rhs->mMinDepth;
+    }
+};
+
 void LLReflectionMapManager::getReflectionMaps(std::vector<LLReflectionMap*>& maps)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
 
+    LLMatrix4a modelview;
+    modelview.loadu(gGLModelView);
+    LLVector4a oa; // scratch space for transformed origin
+
     U32 count = 0;
     U32 lastIdx = 0;
     for (U32 i = 0; count < maps.size() && i < mProbes.size(); ++i)
@@ -348,8 +405,10 @@ void LLReflectionMapManager::getReflectionMaps(std::vector<LLReflectionMap*>& ma
         {
             if (!mProbes[i]->mOccluded && mProbes[i]->mComplete)
             {
-                mProbes[i]->mProbeIndex = count;
                 maps[count++] = mProbes[i];
+                modelview.affineTransform(mProbes[i]->mOrigin, oa);
+                mProbes[i]->mMinDepth = -oa.getF32ptr()[2] - mProbes[i]->mRadius;
+                mProbes[i]->mMaxDepth = -oa.getF32ptr()[2] + mProbes[i]->mRadius;
             }
         }
         else
@@ -365,6 +424,16 @@ void LLReflectionMapManager::getReflectionMaps(std::vector<LLReflectionMap*>& ma
         mProbes[i]->mProbeIndex = -1;
     }
 
+    if (count > 1)
+    {
+        std::sort(maps.begin(), maps.begin() + count, CompareProbeDepth());
+    }
+
+    for (U32 i = 0; i < count; ++i)
+    {
+        maps[i]->mProbeIndex = i;
+    }
+
     // null terminate list
     if (count < maps.size())
     {
@@ -407,32 +476,15 @@ LLReflectionMap* LLReflectionMapManager::registerViewerObject(LLViewerObject* vo
     return probe;
 }
 
-
 S32 LLReflectionMapManager::allocateCubeIndex()
 {
-    for (int i = 0; i < mReflectionProbeCount; ++i)
+    if (!mCubeFree.empty())
     {
-        if (mCubeFree[i])
-        {
-            mCubeFree[i] = false;
-            return i;
-        }
+        S32 ret = mCubeFree.front();
+        mCubeFree.pop_front();
+        return ret;
     }
 
-    // no cubemaps free, steal one from the back of the probe list
-    for (int i = mProbes.size() - 1; i >= mReflectionProbeCount; --i)
-    {
-        if (mProbes[i]->mCubeIndex != -1)
-        {
-            S32 ret = mProbes[i]->mCubeIndex;
-            mProbes[i]->mCubeIndex = -1;
-            mProbes[i]->mCubeArray = nullptr;
-            mProbes[i]->mComplete = false;
-            return ret;
-        }
-    }
-
-    llassert(false); // should never fail to allocate, something is probably wrong with mCubeFree
     return -1;
 }
 
@@ -445,7 +497,7 @@ void LLReflectionMapManager::deleteProbe(U32 i)
 
     if (probe->mCubeIndex != -1)
     { // mark the cube index used by this probe as being free
-        mCubeFree[probe->mCubeIndex] = true;
+        mCubeFree.push_back(probe->mCubeIndex);
     }
     if (mUpdatingProbe == probe)
     {
@@ -776,13 +828,14 @@ void LLReflectionMapManager::updateNeighbors(LLReflectionMap* probe)
     }
 
     // search for new neighbors
+    if (probe->isRelevant())
     {
         LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("rmmun - search");
         for (auto& other : mProbes)
         {
             if (other != mDefaultProbe && other != probe)
             {
-                if (probe->intersects(other))
+                if (other->isRelevant() && probe->intersects(other))
                 {
                     probe->mNeighbors.push_back(other);
                     other->mNeighbors.push_back(probe);
@@ -816,6 +869,7 @@ void LLReflectionMapManager::updateUniforms()
         //  x - irradiance scale
         //  y - radiance scale
         //  z - fade in
+        //  w - znear
         LLVector4 refParams[LL_MAX_REFLECTION_PROBE_COUNT];
 
         // indices used by probe:
@@ -828,6 +882,7 @@ void LLReflectionMapManager::updateUniforms()
         // list of neighbor indices
         GLint refNeighbor[4096]; 
 
+        GLint refBucket[256][4]; //lookup table for which index to start with for the given Z depth
         // numbrer of active refmaps
         GLint refmapCount;  
     };
@@ -837,6 +892,17 @@ void LLReflectionMapManager::updateUniforms()
 
     ReflectionProbeData rpd;
 
+    F32 minDepth[256];
+
+    for (int i = 0; i < 256; ++i)
+    {
+        rpd.refBucket[i][0] = mReflectionProbeCount;
+        rpd.refBucket[i][1] = mReflectionProbeCount;
+        rpd.refBucket[i][2] = mReflectionProbeCount;
+        rpd.refBucket[i][3] = mReflectionProbeCount;
+        minDepth[i] = FLT_MAX;
+    }
+
     // load modelview matrix into matrix 4a
     LLMatrix4a modelview;
     modelview.loadu(gGLModelView);
@@ -861,6 +927,28 @@ void LLReflectionMapManager::updateUniforms()
             break;
         }
 
+        if (refmap != mDefaultProbe)
+        {
+            // bucket search data
+            // theory of operation:
+            //      1. Determine minimum and maximum depth of each influence volume and store in mDepth (done in getReflectionMaps)
+            //      2. Sort by minimum depth
+            //      3. Prepare a bucket for each 1m of depth out to 256m
+            //      4. For each bucket, store the index of the nearest probe that might influence pixels in that bucket
+            //      5. In the shader, lookup the bucket for the pixel depth to get the index of the first probe that could possibly influence
+            //          the current pixel.
+            int depth_min = llclamp(llfloor(refmap->mMinDepth), 0, 255);
+            int depth_max = llclamp(llfloor(refmap->mMaxDepth), 0, 255);
+            for (U32 i = depth_min; i <= depth_max; ++i)
+            {
+                if (refmap->mMinDepth < minDepth[i])
+                {
+                    minDepth[i] = refmap->mMinDepth;
+                    rpd.refBucket[i][0] = refmap->mProbeIndex;
+                }
+            }
+        }
+
         llassert(refmap->mProbeIndex == count);
         llassert(mReflectionMaps[refmap->mProbeIndex] == refmap);
 
@@ -890,7 +978,11 @@ void LLReflectionMapManager::updateUniforms()
             rpd.refIndex[count][3] = -rpd.refIndex[count][3];
         }
 
-        rpd.refParams[count].set(llmax(minimum_ambiance, refmap->getAmbiance())*ambscale, radscale, refmap->mFadeIn, 0.f);
+        rpd.refParams[count].set(
+            llmax(minimum_ambiance, refmap->getAmbiance())*ambscale, // ambiance scale
+            radscale, // radiance scale
+            refmap->mFadeIn, // fade in weight
+            oa.getF32ptr()[2] - refmap->mRadius); // z near
 
         S32 ni = nc; // neighbor ("index") - index into refNeighbor to write indices for current reflection probe's neighbors
         {
@@ -907,7 +999,7 @@ void LLReflectionMapManager::updateUniforms()
                 }
 
                 GLint idx = neighbor->mProbeIndex;
-                if (idx == -1 || neighbor->mOccluded)
+                if (idx == -1 || neighbor->mOccluded || neighbor->mCubeIndex == -1)
                 {
                     continue;
                 }
@@ -944,6 +1036,30 @@ void LLReflectionMapManager::updateUniforms()
         count++;
     }
 
+#if 0
+    {
+        // fill in gaps in refBucket
+        S32 probe_idx = mReflectionProbeCount;
+        
+        for (int i = 0; i < 256; ++i)
+        {
+            if (i < count)
+            { // for debugging, store depth of mReflectionsMaps[i]
+                rpd.refBucket[i][1] = (S32) (mReflectionMaps[i]->mDepth * 10);
+            }
+
+            if (rpd.refBucket[i][0] == mReflectionProbeCount)
+            {
+                rpd.refBucket[i][0] = probe_idx;
+            }
+            else
+            {
+                probe_idx = rpd.refBucket[i][0];
+            }
+        }
+    }
+#endif
+
     rpd.refmapCount = count;
 
     //copy rpd into uniform buffer object
@@ -958,6 +1074,21 @@ void LLReflectionMapManager::updateUniforms()
         glBufferData(GL_UNIFORM_BUFFER, sizeof(ReflectionProbeData), &rpd, GL_STREAM_DRAW);
         glBindBuffer(GL_UNIFORM_BUFFER, 0);
     }
+
+#if 0
+    if (!gCubeSnapshot)
+    {
+        for (auto& probe : mProbes)
+        {
+            LLViewerObject* vobj = probe->mViewerObject;
+            if (vobj)
+            {
+                F32 time = (F32)gFrameTimeSeconds - probe->mLastUpdateTime;
+                vobj->setDebugText(llformat("%d/%d/%d/%.1f - %.1f/%.1f", probe->mCubeIndex, probe->mProbeIndex, (U32) probe->mNeighbors.size(), probe->mMinDepth, probe->mMaxDepth, time), time > 1.f ? LLColor4::white : LLColor4::green);
+            }
+        }
+    }
+#endif
 }
 
 void LLReflectionMapManager::setUniforms()
@@ -977,37 +1108,40 @@ void LLReflectionMapManager::setUniforms()
 
 void renderReflectionProbe(LLReflectionMap* probe)
 {
-    F32* po = probe->mOrigin.getF32ptr();
-
-    //draw orange line from probe to neighbors
-    gGL.flush();
-    gGL.diffuseColor4f(1, 0.5f, 0, 1);
-    gGL.begin(gGL.LINES);
-    for (auto& neighbor : probe->mNeighbors)
+    if (probe->isRelevant())
     {
-        if (probe->mViewerObject && neighbor->mViewerObject)
-        {
-            continue;
-        }
-        
-        gGL.vertex3fv(po);
-        gGL.vertex3fv(neighbor->mOrigin.getF32ptr());
-    }
-    gGL.end();
-    gGL.flush();
+        F32* po = probe->mOrigin.getF32ptr();
 
-    gGL.diffuseColor4f(1, 1, 0, 1);
-    gGL.begin(gGL.LINES);
-    for (auto& neighbor : probe->mNeighbors)
-    {
-        if (probe->mViewerObject && neighbor->mViewerObject)
+        //draw orange line from probe to neighbors
+        gGL.flush();
+        gGL.diffuseColor4f(1, 0.5f, 0, 1);
+        gGL.begin(gGL.LINES);
+        for (auto& neighbor : probe->mNeighbors)
         {
+            if (probe->mViewerObject && neighbor->mViewerObject)
+            {
+                continue;
+            }
+
             gGL.vertex3fv(po);
             gGL.vertex3fv(neighbor->mOrigin.getF32ptr());
         }
+        gGL.end();
+        gGL.flush();
+
+        gGL.diffuseColor4f(1, 1, 0, 1);
+        gGL.begin(gGL.LINES);
+        for (auto& neighbor : probe->mNeighbors)
+        {
+            if (probe->mViewerObject && neighbor->mViewerObject)
+            {
+                gGL.vertex3fv(po);
+                gGL.vertex3fv(neighbor->mOrigin.getF32ptr());
+            }
+        }
+        gGL.end();
+        gGL.flush();
     }
-    gGL.end();
-    gGL.flush();
 
 #if 0
     LLSpatialGroup* group = probe->mGroup;
@@ -1095,7 +1229,6 @@ void LLReflectionMapManager::initReflectionMaps()
         mUpdatingProbe = nullptr;
         mRadiancePass = false;
         mRealtimeRadiancePass = false;
-        mDefaultProbe = nullptr;
 
         for (auto& probe : mProbes)
         {
@@ -1104,14 +1237,28 @@ void LLReflectionMapManager::initReflectionMaps()
             probe->mProbeIndex = -1;
             probe->mCubeArray = nullptr;
             probe->mCubeIndex = -1;
+            probe->mNeighbors.clear();
         }
 
-        for (bool& is_free : mCubeFree)
+        mCubeFree.clear();
+        initCubeFree();
+
+        if (mDefaultProbe.isNull())
         {
-            is_free = true;
+            llassert(mProbes.empty()); // default probe MUST be the first probe created
+            mDefaultProbe = new LLReflectionMap();
+            mProbes.push_back(mDefaultProbe);
         }
 
-        mCubeFree[0] = false;
+        llassert(mProbes[0] == mDefaultProbe);
+
+        mDefaultProbe->mCubeIndex = 0;
+        mDefaultProbe->mCubeArray = mTexture;
+        mDefaultProbe->mDistance = 64.f;
+        mDefaultProbe->mRadius = 4096.f;
+        mDefaultProbe->mProbeIndex = 0;
+        touch_default_probe(mDefaultProbe);
+
     }
 
     if (mVertexBuffer.isNull())
diff --git a/indra/newview/llreflectionmapmanager.h b/indra/newview/llreflectionmapmanager.h
index 234bde51a81bdb53a9035296dc9eee8b723816ae..c0618de41077cd4f5e1bea34e0609a67128647b2 100644
--- a/indra/newview/llreflectionmapmanager.h
+++ b/indra/newview/llreflectionmapmanager.h
@@ -110,7 +110,7 @@ class alignas(16) LLReflectionMapManager
     void deleteProbe(U32 i);
 
     // get a free cube index
-    // if no cube indices are free, free one starting from the back of the probe list
+    // returns -1 if allocation failed
     S32 allocateCubeIndex();
 
     // update the neighbors of the given probe 
@@ -137,12 +137,9 @@ class alignas(16) LLReflectionMapManager
     // storage for reflection probe irradiance maps
     LLPointer<LLCubeMapArray> mIrradianceMaps;
 
-    // array indicating if a particular cubemap is free
-    bool mCubeFree[LL_MAX_REFLECTION_PROBE_COUNT];
+    // list of free cubemap indices
+    std::list<S32> mCubeFree;
 
-    // start tracking the given spatial group
-    void trackGroup(LLSpatialGroup* group);
-    
     // perform an update on the currently updating Probe
     void doProbeUpdate();
 
diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp
index 22c1176b05e0392354f1802b7d960a8ad402451a..71dcc56197f7bd87ee37d405535555225f256a70 100644
--- a/indra/newview/llselectmgr.cpp
+++ b/indra/newview/llselectmgr.cpp
@@ -2188,8 +2188,8 @@ void LLSelectMgr::selectionRevertGLTFMaterials()
                 // Enqueue update to server
                 if (asset_id.notNull())
                 {
-                    // Restore overrides
-                    LLGLTFMaterialList::queueModify(objectp, te, nodep->mSavedGLTFOverrideMaterials[te]);
+                    // Restore overrides and base material
+                    LLGLTFMaterialList::queueApply(objectp, te, asset_id, nodep->mSavedGLTFOverrideMaterials[te]);
                 } 
                 else
                 {
@@ -5797,7 +5797,7 @@ void LLSelectMgr::processObjectProperties(LLMessageSystem* msg, void** user_data
                 if (can_copy && can_transfer && node->getObject()->getVolume())
                 {
                     uuid_vec_t material_ids;
-                    gltf_materials_vec_t materials;
+                    gltf_materials_vec_t override_materials;
                     LLVOVolume* vobjp = (LLVOVolume*)node->getObject();
                     for (int i = 0; i < vobjp->getNumTEs(); ++i)
                     {
@@ -5812,18 +5812,16 @@ void LLSelectMgr::processObjectProperties(LLMessageSystem* msg, void** user_data
                         if (old_override)
                         {
                             LLPointer<LLGLTFMaterial> mat = new LLGLTFMaterial(*old_override);
-                            materials.push_back(mat);
+                            override_materials.push_back(mat);
                         }
                         else
                         {
-                            materials.push_back(nullptr);
+                            override_materials.push_back(nullptr);
                         }
                     }
-                    node->saveGLTFMaterialIds(material_ids);
-
                     // processObjectProperties does not include overrides so this
                     // might need to be moved to LLGLTFMaterialOverrideDispatchHandler
-                    node->saveGLTFOverrideMaterials(materials);
+                    node->saveGLTFMaterials(material_ids, override_materials);
                 }
 			}
 
@@ -6576,8 +6574,7 @@ LLSelectNode::LLSelectNode(const LLSelectNode& nodep)
 	}
 	
 	saveTextures(nodep.mSavedTextures);
-    saveGLTFMaterialIds(nodep.mSavedGLTFMaterialIds);
-    saveGLTFOverrideMaterials(nodep.mSavedGLTFOverrideMaterials);
+    saveGLTFMaterials(nodep.mSavedGLTFMaterialIds, nodep.mSavedGLTFOverrideMaterials);
 }
 
 LLSelectNode::~LLSelectNode()
@@ -6711,28 +6708,21 @@ void LLSelectNode::saveTextures(const uuid_vec_t& textures)
 	}
 }
 
-void LLSelectNode::saveGLTFMaterialIds(const uuid_vec_t& materials)
+void LLSelectNode::saveGLTFMaterials(const uuid_vec_t& materials, const gltf_materials_vec_t& override_materials)
 {
     if (mObject.notNull())
     {
         mSavedGLTFMaterialIds.clear();
+        mSavedGLTFOverrideMaterials.clear();
 
         for (uuid_vec_t::const_iterator materials_it = materials.begin();
             materials_it != materials.end(); ++materials_it)
         {
             mSavedGLTFMaterialIds.push_back(*materials_it);
         }
-    }
-}
-
-void LLSelectNode::saveGLTFOverrideMaterials(const gltf_materials_vec_t& materials)
-{
-    if (mObject.notNull())
-    {
-        mSavedGLTFOverrideMaterials.clear();
 
-        for (gltf_materials_vec_t::const_iterator mat_it = materials.begin();
-            mat_it != materials.end(); ++mat_it)
+        for (gltf_materials_vec_t::const_iterator mat_it = override_materials.begin();
+            mat_it != override_materials.end(); ++mat_it)
         {
             mSavedGLTFOverrideMaterials.push_back(*mat_it);
         }
@@ -7833,7 +7823,7 @@ S32 LLObjectSelection::getSelectedObjectRenderCost()
 				   for (LLVOVolume::texture_cost_t::iterator iter = textures.begin(); iter != textures.end(); ++iter)
 				   {
 					   // add the cost of each individual texture in the linkset
-					   cost += iter->second;
+					   cost += LLVOVolume::getTextureCost(*iter);
 				   }
 
 				   textures.clear();
@@ -7855,7 +7845,7 @@ S32 LLObjectSelection::getSelectedObjectRenderCost()
 			for (LLVOVolume::texture_cost_t::iterator iter = textures.begin(); iter != textures.end(); ++iter)
 			{
 				// add the cost of each individual texture in the linkset
-				cost += iter->second;
+				cost += LLVOVolume::getTextureCost(*iter);
 			}
 
 			textures.clear();
diff --git a/indra/newview/llselectmgr.h b/indra/newview/llselectmgr.h
index 3ee78f9a5828a2859d1ebb211cce267afb95e061..ca9a32f0db7e6c831177db2733125416a42d1734 100644
--- a/indra/newview/llselectmgr.h
+++ b/indra/newview/llselectmgr.h
@@ -197,8 +197,7 @@ class LLSelectNode
     // final gltf material that users see.
     // Ids get applied and restored by tools floater,
     // overrides get applied in live material editor
-    void saveGLTFMaterialIds(const uuid_vec_t& materials);
-    void saveGLTFOverrideMaterials(const gltf_materials_vec_t& materials);
+    void saveGLTFMaterials(const uuid_vec_t& materials, const gltf_materials_vec_t& override_materials);
 
 	BOOL allowOperationOnNode(PermissionBit op, U64 group_proxy_power) const;
 
diff --git a/indra/newview/llsettingsvo.cpp b/indra/newview/llsettingsvo.cpp
index 1427dbefa135c5fc45a167823a9eacd2b6171c1d..a7a5a9223c9cc510088f20f1e2af18e70d4fce46 100644
--- a/indra/newview/llsettingsvo.cpp
+++ b/indra/newview/llsettingsvo.cpp
@@ -574,11 +574,11 @@ void LLSettingsVOSky::convertAtmosphericsToLegacy(LLSD& legacy, LLSD& settings)
         legacy[SETTING_BLUE_DENSITY] = ensure_array_4(legacyhaze[SETTING_BLUE_DENSITY], 1.0);
         legacy[SETTING_BLUE_HORIZON] = ensure_array_4(legacyhaze[SETTING_BLUE_HORIZON], 1.0);
 
-        legacy[SETTING_DENSITY_MULTIPLIER] = LLSDArray(legacyhaze[SETTING_DENSITY_MULTIPLIER].asReal())(0.0f)(0.0f)(1.0f);
-        legacy[SETTING_DISTANCE_MULTIPLIER] = LLSDArray(legacyhaze[SETTING_DISTANCE_MULTIPLIER].asReal())(0.0f)(0.0f)(1.0f);
+        legacy[SETTING_DENSITY_MULTIPLIER]  = llsd::array(legacyhaze[SETTING_DENSITY_MULTIPLIER].asReal(), 0.0f, 0.0f, 1.0f);
+        legacy[SETTING_DISTANCE_MULTIPLIER] = llsd::array(legacyhaze[SETTING_DISTANCE_MULTIPLIER].asReal(), 0.0f, 0.0f, 1.0f);
 
-        legacy[SETTING_HAZE_DENSITY]        = LLSDArray(legacyhaze[SETTING_HAZE_DENSITY])(0.0f)(0.0f)(1.0f);
-        legacy[SETTING_HAZE_HORIZON]        = LLSDArray(legacyhaze[SETTING_HAZE_HORIZON])(0.0f)(0.0f)(1.0f);
+        legacy[SETTING_HAZE_DENSITY]        = llsd::array(legacyhaze[SETTING_HAZE_DENSITY], 0.0f, 0.0f, 1.0f);
+        legacy[SETTING_HAZE_HORIZON]        = llsd::array(legacyhaze[SETTING_HAZE_HORIZON], 0.0f, 0.0f, 1.0f);
     }
 }
 
@@ -592,15 +592,15 @@ LLSD LLSettingsVOSky::convertToLegacy(const LLSettingsSky::ptr_t &psky, bool isA
     legacy[SETTING_CLOUD_COLOR] = ensure_array_4(settings[SETTING_CLOUD_COLOR], 1.0);
     legacy[SETTING_CLOUD_POS_DENSITY1] = ensure_array_4(settings[SETTING_CLOUD_POS_DENSITY1], 1.0);
     legacy[SETTING_CLOUD_POS_DENSITY2] = ensure_array_4(settings[SETTING_CLOUD_POS_DENSITY2], 1.0);
-    legacy[SETTING_CLOUD_SCALE] = LLSDArray(settings[SETTING_CLOUD_SCALE])(LLSD::Real(0.0))(LLSD::Real(0.0))(LLSD::Real(1.0));       
+    legacy[SETTING_CLOUD_SCALE] = llsd::array(settings[SETTING_CLOUD_SCALE], LLSD::Real(0.0), LLSD::Real(0.0), LLSD::Real(1.0));
     legacy[SETTING_CLOUD_SCROLL_RATE] = settings[SETTING_CLOUD_SCROLL_RATE];
-    legacy[SETTING_LEGACY_ENABLE_CLOUD_SCROLL] = LLSDArray(LLSD::Boolean(!is_approx_zero(settings[SETTING_CLOUD_SCROLL_RATE][0].asReal())))
-        (LLSD::Boolean(!is_approx_zero(settings[SETTING_CLOUD_SCROLL_RATE][1].asReal())));     
-    legacy[SETTING_CLOUD_SHADOW] = LLSDArray(settings[SETTING_CLOUD_SHADOW].asReal())(0.0f)(0.0f)(1.0f);    
-    legacy[SETTING_GAMMA] = LLSDArray(settings[SETTING_GAMMA])(0.0f)(0.0f)(1.0f);
+    legacy[SETTING_LEGACY_ENABLE_CLOUD_SCROLL] = llsd::array(LLSD::Boolean(!is_approx_zero(settings[SETTING_CLOUD_SCROLL_RATE][0].asReal())),
+        LLSD::Boolean(!is_approx_zero(settings[SETTING_CLOUD_SCROLL_RATE][1].asReal())));     
+    legacy[SETTING_CLOUD_SHADOW] = llsd::array(settings[SETTING_CLOUD_SHADOW].asReal(), 0.0f, 0.0f, 1.0f);    
+    legacy[SETTING_GAMMA] = llsd::array(settings[SETTING_GAMMA], 0.0f, 0.0f, 1.0f);
     legacy[SETTING_GLOW] = ensure_array_4(settings[SETTING_GLOW], 1.0);
     legacy[SETTING_LIGHT_NORMAL] = ensure_array_4(psky->getLightDirection().getValue(), 0.0f);
-    legacy[SETTING_MAX_Y] = LLSDArray(settings[SETTING_MAX_Y])(0.0f)(0.0f)(1.0f);
+    legacy[SETTING_MAX_Y] = llsd::array(settings[SETTING_MAX_Y], 0.0f, 0.0f, 1.0f);
     legacy[SETTING_STAR_BRIGHTNESS] = settings[SETTING_STAR_BRIGHTNESS].asReal() / 250.0f; // convert from 0-500 -> 0-2 ala pre-FS-compat changes
     legacy[SETTING_SUNLIGHT_COLOR] = ensure_array_4(settings[SETTING_SUNLIGHT_COLOR], 1.0f);
     
@@ -1113,7 +1113,7 @@ LLSettingsDay::ptr_t LLSettingsVODay::buildFromLegacyPreset(const std::string &n
 
     newsettings[SETTING_NAME] = name;
 
-    LLSD watertrack = LLSDArray(
+    LLSD watertrack = llsd::array(
         LLSDMap(SETTING_KEYKFRAME, LLSD::Real(0.0f))
         (SETTING_KEYNAME, "water:Default"));
 
@@ -1128,7 +1128,7 @@ LLSettingsDay::ptr_t LLSettingsVODay::buildFromLegacyPreset(const std::string &n
         skytrack.append(entry);
     }
 
-    newsettings[SETTING_TRACKS] = LLSDArray(watertrack)(skytrack);
+    newsettings[SETTING_TRACKS] = llsd::array(watertrack, skytrack);
 
     LLSD frames(LLSD::emptyMap());
 
@@ -1216,7 +1216,7 @@ LLSettingsDay::ptr_t LLSettingsVODay::buildFromLegacyMessage(const LLUUID &regio
     watersettings[SETTING_NAME] = watername;
     frames[watername] = watersettings;
 
-    LLSD watertrack = LLSDArray(
+    LLSD watertrack = llsd::array(
             LLSDMap(SETTING_KEYKFRAME, LLSD::Real(0.0f))
             (SETTING_KEYNAME, watername));
 
@@ -1230,7 +1230,7 @@ LLSettingsDay::ptr_t LLSettingsVODay::buildFromLegacyMessage(const LLUUID &regio
 
     LLSD newsettings = LLSDMap
         ( SETTING_NAME, "Region (legacy)" )
-        ( SETTING_TRACKS, LLSDArray(watertrack)(skytrack))
+        ( SETTING_TRACKS, llsd::array(watertrack, skytrack))
         ( SETTING_FRAMES, frames )
         ( SETTING_TYPE, "daycycle" );
 
@@ -1411,7 +1411,7 @@ LLSD LLSettingsVODay::convertToLegacy(const LLSettingsVODay::ptr_t &pday)
         skys[name.str()] = std::static_pointer_cast<LLSettingsSky>((*it).second);
         
         F32 frame = ((tracksky.size() == 1) && (it == tracksky.begin())) ? -1.0f : (*it).first;
-        llsdcycle.append( LLSDArray(LLSD::Real(frame))(name.str()) );
+        llsdcycle.append( llsd::array(LLSD::Real(frame), name.str()) );
     }
 
     LLSD llsdskylist(LLSD::emptyMap());
@@ -1424,7 +1424,7 @@ LLSD LLSettingsVODay::convertToLegacy(const LLSettingsVODay::ptr_t &pday)
         llsdskylist[(*its).first] = llsdsky;
     }
 
-    return LLSDArray(LLSD::emptyMap())(llsdcycle)(llsdskylist)(llsdwater);
+    return llsd::array(LLSD::emptyMap(), llsdcycle, llsdskylist, llsdwater);
 }
 
 LLSettingsSkyPtr_t  LLSettingsVODay::getDefaultSky() const
diff --git a/indra/newview/llspatialpartition.cpp b/indra/newview/llspatialpartition.cpp
index 4d99ee13864bccf0e18f2c4112cb6136c6c28696..f7df4286fe8f5e22ee77d87b83f52bf6ce22c03d 100644
--- a/indra/newview/llspatialpartition.cpp
+++ b/indra/newview/llspatialpartition.cpp
@@ -1826,116 +1826,6 @@ void renderUpdateType(LLDrawable* drawablep)
 	}
 }
 
-void renderComplexityDisplay(LLDrawable* drawablep)
-{
-	LLViewerObject* vobj = drawablep->getVObj();
-	if (!vobj)
-	{
-		return;
-	}
-
-	LLVOVolume *voVol = dynamic_cast<LLVOVolume*>(vobj);
-
-	if (!voVol)
-	{
-		return;
-	}
-
-	if (!voVol->isRoot())
-	{
-		return;
-	}
-
-	LLVOVolume::texture_cost_t textures;
-	F32 cost = (F32) voVol->getRenderCost(textures);
-
-	// add any child volumes
-	LLViewerObject::const_child_list_t children = voVol->getChildren();
-	for (LLViewerObject::const_child_list_t::const_iterator iter = children.begin(); iter != children.end(); ++iter)
-	{
-		const LLViewerObject *child = *iter;
-		const LLVOVolume *child_volume = dynamic_cast<const LLVOVolume*>(child);
-		if (child_volume)
-		{
-			cost += child_volume->getRenderCost(textures);
-		}
-	}
-
-	// add texture cost
-	for (LLVOVolume::texture_cost_t::iterator iter = textures.begin(); iter != textures.end(); ++iter)
-	{
-		// add the cost of each individual texture in the linkset
-		cost += iter->second;
-	}
-
-	F32 cost_max = (F32) LLVOVolume::getRenderComplexityMax();
-
-
-
-	// allow user to set a static color scale
-	if (gSavedSettings.getS32("RenderComplexityStaticMax") > 0)
-	{
-		cost_max = gSavedSettings.getS32("RenderComplexityStaticMax");
-	}
-
-	F32 cost_ratio = cost / cost_max;
-	
-	// cap cost ratio at 1.0f in case cost_max is at a low threshold
-	cost_ratio = cost_ratio > 1.0f ? 1.0f : cost_ratio;
-	
-	LLGLEnable blend(GL_BLEND);
-
-	LLColor4 color;
-	const LLColor4 color_min = gSavedSettings.getColor4("RenderComplexityColorMin");
-	const LLColor4 color_mid = gSavedSettings.getColor4("RenderComplexityColorMid");
-	const LLColor4 color_max = gSavedSettings.getColor4("RenderComplexityColorMax");
-
-	if (cost_ratio < 0.5f)
-	{
-		color = color_min * (1 - cost_ratio * 2) + color_mid * (cost_ratio * 2);
-	}
-	else
-	{
-		color = color_mid * (1 - (cost_ratio - 0.5) * 2) + color_max * ((cost_ratio - 0.5) * 2);
-	}
-
-	LLSD color_val = color.getValue();
-
-	// don't highlight objects below the threshold
-	if (cost > gSavedSettings.getS32("RenderComplexityThreshold"))
-	{
-		glColor4f(color[0],color[1],color[2],0.5f);
-
-
-		S32 num_faces = drawablep->getNumFaces();
-		if (num_faces)
-		{
-			for (S32 i = 0; i < num_faces; ++i)
-			{
-				pushVerts(drawablep->getFace(i));
-			}
-		}
-		LLViewerObject::const_child_list_t children = voVol->getChildren();
-		for (LLViewerObject::const_child_list_t::const_iterator iter = children.begin(); iter != children.end(); ++iter)
-		{
-			const LLViewerObject *child = *iter;
-			if (child)
-			{
-				num_faces = child->getNumFaces();
-				if (num_faces)
-				{
-					for (S32 i = 0; i < num_faces; ++i)
-					{
-						pushVerts(child->mDrawable->getFace(i));
-					}
-				}
-			}
-		}
-	}
-	
-	voVol->setDebugText(llformat("%4.0f", cost));	
-}
-
 void renderBoundingBox(LLDrawable* drawable, BOOL set_color = TRUE)
 {
 	if (set_color)
@@ -3261,10 +3151,6 @@ class LLOctreeRenderNonOccluded : public OctreeTraveler
 			{
 				renderUpdateType(drawable);
 			}
-			if(gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_RENDER_COMPLEXITY))
-			{
-				renderComplexityDisplay(drawable);
-			}
 			if(gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY))
 			{
 				renderTexelDensity(drawable);
@@ -3575,7 +3461,6 @@ void LLSpatialPartition::renderDebug()
 									  LLPipeline::RENDER_DEBUG_AGENT_TARGET |
 									  //LLPipeline::RENDER_DEBUG_BUILD_QUEUE |
 									  LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA |
-									  LLPipeline::RENDER_DEBUG_RENDER_COMPLEXITY |
 									  LLPipeline::RENDER_DEBUG_TEXEL_DENSITY))
 	{
 		return;
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 6bcee31b72d5842da482ce3508dff85aba4d7032..65fdd400bf212f1df6419c62ed19199764a2a348 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -208,6 +208,7 @@
 #include "llstacktrace.h"
 
 #include "threadpool.h"
+#include "llperfstats.h"
 
 
 #if LL_WINDOWS
@@ -1502,6 +1503,8 @@ bool idle_startup()
 			LLViewerParcelAskPlay::getInstance()->loadSettings();
 		}
 
+		gAgent.addRegionChangedCallback(boost::bind(&LLPerfStats::StatsRecorder::clearStats));
+
 		// *Note: this is where gWorldMap used to be initialized.
 
 		// register null callbacks for audio until the audio system is initialized
@@ -2346,6 +2349,8 @@ bool idle_startup()
 
 		LLUIUsage::instance().clear();
 
+        LLPerfStats::StatsRecorder::setAutotuneInit();
+
 		return TRUE;
 	}
 
@@ -3408,6 +3413,9 @@ bool process_login_success_response()
 	if(!text.empty()) gAgentID.set(text);
 	gDebugInfo["AgentID"] = text;
 	
+	LLPerfStats::StatsRecorder::setEnabled(gSavedSettings.getBOOL("PerfStatsCaptureEnabled"));
+	LLPerfStats::StatsRecorder::setFocusAv(gAgentID);
+
 	// Agent id needed for parcel info request in LLUrlEntryParcel
 	// to resolve parcel name.
 	LLUrlEntryParcel::setAgentID(gAgentID);
diff --git a/indra/newview/lltoolbarview.cpp b/indra/newview/lltoolbarview.cpp
index 01d799dcd50d62415c521a23ab9532cf81ccfbf2..4f47c465c4d5bcfc7079a2cb121676ad7c60dedb 100644
--- a/indra/newview/lltoolbarview.cpp
+++ b/indra/newview/lltoolbarview.cpp
@@ -44,6 +44,7 @@
 #include "llagent.h"  // HACK for destinations guide on startup
 #include "llfloaterreg.h"  // HACK for destinations guide on startup
 #include "llviewercontrol.h"  // HACK for destinations guide on startup
+#include "llinventorymodel.h" // HACK to disable starter avatars button for NUX
 
 #include <boost/foreach.hpp>
 
@@ -319,6 +320,25 @@ bool LLToolBarView::loadToolbars(bool force_default)
 			}
 		}
 	}
+
+    // SL-18581: Don't show the starter avatar toolbar button for NUX users
+    LLViewerInventoryCategory* my_outfits_cat = gInventory.getCategory(gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS));
+    if (gAgent.isFirstLogin())
+    {
+        LL_WARNS() << "First login: checking for NUX user." << LL_ENDL;
+        if (my_outfits_cat != NULL && my_outfits_cat->getDescendentCount() > 0)
+        {
+            LL_WARNS() << "First login: My Outfits folder is not empty, removing the avatar picker button." << LL_ENDL;
+            for (S32 i = LLToolBarEnums::TOOLBAR_FIRST; i <= LLToolBarEnums::TOOLBAR_LAST; i++)
+            {
+                if (mToolbars[i])
+                {
+                    mToolbars[i]->removeCommand(LLCommandId("avatar"));
+                }
+            }
+        }
+    }
+
 	mToolbarsLoaded = true;
 	return true;
 }
diff --git a/indra/newview/lltooldraganddrop.cpp b/indra/newview/lltooldraganddrop.cpp
index 1b19ba33a35eabf0a3b3df2694dda2d24bd676e7..6633951db392ece822a1e9cc5b3d48b52e7842d4 100644
--- a/indra/newview/lltooldraganddrop.cpp
+++ b/indra/newview/lltooldraganddrop.cpp
@@ -2150,12 +2150,14 @@ EAcceptance LLToolDragAndDrop::dad3dApplyToObject(
                 if (nodep)
                 {
                     uuid_vec_t material_ids;
+                    gltf_materials_vec_t override_materials;
                     S32 num_faces = obj->getNumTEs();
                     for (S32 face = 0; face < num_faces; face++)
                     {
                         material_ids.push_back(obj->getRenderMaterialID(face));
+                        override_materials.push_back(nullptr);
                     }
-                    nodep->saveGLTFMaterialIds(material_ids);
+                    nodep->saveGLTFMaterials(material_ids, override_materials);
                 }
             }
             else
@@ -2169,6 +2171,7 @@ EAcceptance LLToolDragAndDrop::dad3dApplyToObject(
                     && nodep->mSavedGLTFMaterialIds.size() > face)
                 {
                     nodep->mSavedGLTFMaterialIds[face] = obj->getRenderMaterialID(face);
+                    nodep->mSavedGLTFOverrideMaterials[face] = nullptr;
                 }
             }
         }
diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp
index bbdae95b7f7f455a5fb23e8ae670b758efe86b18..faeebc33ee98ca86c01849c58d5452f7f6565ca0 100644
--- a/indra/newview/llviewercontrol.cpp
+++ b/indra/newview/llviewercontrol.cpp
@@ -74,6 +74,7 @@
 #include "llspellcheck.h"
 #include "llslurl.h"
 #include "llstartup.h"
+#include "llperfstats.h"
 
 // Third party library includes
 #include <boost/algorithm/string.hpp>
@@ -247,8 +248,15 @@ static bool handleAnisotropicChanged(const LLSD& newvalue)
 
 static bool handleVSyncChanged(const LLSD& newvalue)
 {
+    LLPerfStats::tunables.vsyncEnabled = newvalue.asBoolean();
     gViewerWindow->getWindow()->toggleVSync(newvalue.asBoolean());
 
+    if(newvalue.asBoolean() == true)
+    {
+        U32 current_target = gSavedSettings.getU32("TargetFPS");
+        gSavedSettings.setU32("TargetFPS", std::min((U32)gViewerWindow->getWindow()->getRefreshRate(), current_target));
+    }
+
     return true;
 }
 
@@ -585,6 +593,71 @@ bool toggle_show_object_render_cost(const LLSD& newvalue)
 }
 
 void handleRenderAutoMuteByteLimitChanged(const LLSD& new_value);
+
+void handleTargetFPSChanged(const LLSD& newValue)
+{
+    const auto targetFPS = gSavedSettings.getU32("TargetFPS");
+
+    U32 frame_rate_limit = gViewerWindow->getWindow()->getRefreshRate();
+    if(LLPerfStats::tunables.vsyncEnabled && (targetFPS > frame_rate_limit))
+    {
+        gSavedSettings.setU32("TargetFPS", std::min(frame_rate_limit, targetFPS));
+    }
+    else
+    {
+        LLPerfStats::tunables.userTargetFPS = targetFPS;
+    }
+}
+
+void handleAutoTuneLockChanged(const LLSD& newValue)
+{
+    const auto newval = gSavedSettings.getBOOL("AutoTuneLock");
+    LLPerfStats::tunables.userAutoTuneLock = newval;
+
+    gSavedSettings.setBOOL("AutoTuneFPS", newval);
+}
+
+void handleAutoTuneFPSChanged(const LLSD& newValue)
+{
+    const auto newval = gSavedSettings.getBOOL("AutoTuneFPS");
+    LLPerfStats::tunables.userAutoTuneEnabled = newval;
+    if(newval && LLPerfStats::renderAvatarMaxART_ns == 0) // If we've enabled autotune we override "unlimited" to max
+    {
+        gSavedSettings.setF32("RenderAvatarMaxART",log10(LLPerfStats::ART_UNLIMITED_NANOS-1000));//triggers callback to update static var
+    }
+}
+
+void handleRenderAvatarMaxARTChanged(const LLSD& newValue)
+{
+    LLPerfStats::tunables.updateRenderCostLimitFromSettings();
+}
+
+void handleUserTargetDrawDistanceChanged(const LLSD& newValue)
+{
+    const auto newval = gSavedSettings.getF32("AutoTuneRenderFarClipTarget");
+    LLPerfStats::tunables.userTargetDrawDistance = newval;
+}
+
+void handlePerformanceStatsEnabledChanged(const LLSD& newValue)
+{
+    const auto newval = gSavedSettings.getBOOL("PerfStatsCaptureEnabled");
+    LLPerfStats::StatsRecorder::setEnabled(newval);
+}
+void handleUserImpostorByDistEnabledChanged(const LLSD& newValue)
+{
+    const auto newval = gSavedSettings.getBOOL("AutoTuneImpostorByDistEnabled");
+    LLPerfStats::tunables.userImpostorDistanceTuningEnabled = newval;
+}
+void handleUserImpostorDistanceChanged(const LLSD& newValue)
+{
+    const auto newval = gSavedSettings.getF32("AutoTuneImpostorFarAwayDistance");
+    LLPerfStats::tunables.userImpostorDistance = newval;
+}
+void handleFPSTuningStrategyChanged(const LLSD& newValue)
+{
+    const auto newval = gSavedSettings.getU32("TuningFPSStrategy");
+    LLPerfStats::tunables.userFPSTuningStrategy = newval;
+}
 ////////////////////////////////////////////////////////////////////////////
 
 LLPointer<LLControlVariable> setting_get_control(LLControlGroup& group, const std::string& setting)
@@ -618,142 +691,152 @@ void setting_setup_signal_listener(LLControlGroup& group, const std::string& set
 void settings_setup_listeners()
 {
     setting_setup_signal_listener(gSavedSettings, "FirstPersonAvatarVisible", handleRenderAvatarMouselookChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderFarClip", handleRenderFarClipChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderTerrainDetail", handleTerrainDetailChanged);
-	setting_setup_signal_listener(gSavedSettings, "OctreeStaticObjectSizeFactor", handleRepartition);
-	setting_setup_signal_listener(gSavedSettings, "OctreeDistanceFactor", handleRepartition);
-	setting_setup_signal_listener(gSavedSettings, "OctreeMaxNodeCapacity", handleRepartition);
-	setting_setup_signal_listener(gSavedSettings, "OctreeAlphaDistanceFactor", handleRepartition);
-	setting_setup_signal_listener(gSavedSettings, "OctreeAttachmentSizeFactor", handleRepartition);
-	setting_setup_signal_listener(gSavedSettings, "RenderMaxTextureIndex", handleSetShaderChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderUIBuffer", handleWindowResized);
-	setting_setup_signal_listener(gSavedSettings, "RenderDepthOfField", handleReleaseGLBufferChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderFSAASamples", handleReleaseGLBufferChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderSpecularResX", handleLUTBufferChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderSpecularResY", handleLUTBufferChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderSpecularExponent", handleLUTBufferChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderAnisotropic", handleAnisotropicChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderShadowResolutionScale", handleShadowsResized);
-	setting_setup_signal_listener(gSavedSettings, "RenderGlow", handleReleaseGLBufferChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderGlow", handleSetShaderChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderGlowResolutionPow", handleReleaseGLBufferChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderGammaFull", handleSetShaderChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderVolumeLODFactor", handleVolumeLODChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderAvatarLODFactor", handleAvatarLODChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderAvatarPhysicsLODFactor", handleAvatarPhysicsLODChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderTerrainLODFactor", handleTerrainLODChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderTreeLODFactor", handleTreeLODChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderFlexTimeFactor", handleFlexLODChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderGamma", handleGammaChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderFogRatio", handleFogRatioChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderMaxPartCount", handleMaxPartCountChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderDynamicLOD", handleRenderDynamicLODChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderLocalLights", handleRenderLocalLightsChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderFarClip", handleRenderFarClipChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderTerrainDetail", handleTerrainDetailChanged);
+    setting_setup_signal_listener(gSavedSettings, "OctreeStaticObjectSizeFactor", handleRepartition);
+    setting_setup_signal_listener(gSavedSettings, "OctreeDistanceFactor", handleRepartition);
+    setting_setup_signal_listener(gSavedSettings, "OctreeMaxNodeCapacity", handleRepartition);
+    setting_setup_signal_listener(gSavedSettings, "OctreeAlphaDistanceFactor", handleRepartition);
+    setting_setup_signal_listener(gSavedSettings, "OctreeAttachmentSizeFactor", handleRepartition);
+    setting_setup_signal_listener(gSavedSettings, "RenderMaxTextureIndex", handleSetShaderChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderUIBuffer", handleWindowResized);
+    setting_setup_signal_listener(gSavedSettings, "RenderDepthOfField", handleReleaseGLBufferChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderFSAASamples", handleReleaseGLBufferChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderSpecularResX", handleLUTBufferChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderSpecularResY", handleLUTBufferChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderSpecularExponent", handleLUTBufferChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderAnisotropic", handleAnisotropicChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderShadowResolutionScale", handleShadowsResized);
+    setting_setup_signal_listener(gSavedSettings, "RenderGlow", handleReleaseGLBufferChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderGlow", handleSetShaderChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderGlowResolutionPow", handleReleaseGLBufferChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderGammaFull", handleSetShaderChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderVolumeLODFactor", handleVolumeLODChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderAvatarLODFactor", handleAvatarLODChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderAvatarPhysicsLODFactor", handleAvatarPhysicsLODChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderTerrainLODFactor", handleTerrainLODChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderTreeLODFactor", handleTreeLODChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderFlexTimeFactor", handleFlexLODChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderGamma", handleGammaChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderFogRatio", handleFogRatioChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderMaxPartCount", handleMaxPartCountChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderDynamicLOD", handleRenderDynamicLODChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderLocalLights", handleRenderLocalLightsChanged);
 	setting_setup_signal_listener(gSavedSettings, "RenderVSyncEnable", handleVSyncChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderDeferredNoise", handleReleaseGLBufferChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderDebugPipeline", handleRenderDebugPipelineChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderResolutionDivisor", handleRenderResolutionDivisorChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderDeferredNoise", handleReleaseGLBufferChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderDebugPipeline", handleRenderDebugPipelineChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderResolutionDivisor", handleRenderResolutionDivisorChanged);
     setting_setup_signal_listener(gSavedSettings, "RenderReflectionProbeLevel", handleReflectionProbeDetailChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderReflectionProbeDetail", handleReflectionProbeDetailChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderReflectionProbeDetail", handleReflectionProbeDetailChanged);
     setting_setup_signal_listener(gSavedSettings, "RenderReflectionsEnabled", handleReflectionProbeDetailChanged);
     setting_setup_signal_listener(gSavedSettings, "RenderScreenSpaceReflections", handleReflectionProbeDetailChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderShadowDetail", handleSetShaderChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderDeferredSSAO", handleSetShaderChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderPerformanceTest", handleRenderPerfTestChanged);
-	setting_setup_signal_listener(gSavedSettings, "ChatFontSize", handleChatFontSizeChanged);
-	setting_setup_signal_listener(gSavedSettings, "ChatPersistTime", handleChatPersistTimeChanged);
-	setting_setup_signal_listener(gSavedSettings, "ConsoleMaxLines", handleConsoleMaxLinesChanged);
-	setting_setup_signal_listener(gSavedSettings, "UploadBakedTexOld", handleUploadBakedTexOldChanged);
-	setting_setup_signal_listener(gSavedSettings, "UseOcclusion", handleUseOcclusionChanged);
-	setting_setup_signal_listener(gSavedSettings, "AudioLevelMaster", handleAudioVolumeChanged);
-	setting_setup_signal_listener(gSavedSettings, "AudioLevelSFX", handleAudioVolumeChanged);
-	setting_setup_signal_listener(gSavedSettings, "AudioLevelUI", handleAudioVolumeChanged);
-	setting_setup_signal_listener(gSavedSettings, "AudioLevelAmbient", handleAudioVolumeChanged);
-	setting_setup_signal_listener(gSavedSettings, "AudioLevelMusic", handleAudioVolumeChanged);
-	setting_setup_signal_listener(gSavedSettings, "AudioLevelMedia", handleAudioVolumeChanged);
-	setting_setup_signal_listener(gSavedSettings, "AudioLevelVoice", handleAudioVolumeChanged);
-	setting_setup_signal_listener(gSavedSettings, "AudioLevelDoppler", handleAudioVolumeChanged);
-	setting_setup_signal_listener(gSavedSettings, "AudioLevelRolloff", handleAudioVolumeChanged);
-	setting_setup_signal_listener(gSavedSettings, "AudioLevelUnderwaterRolloff", handleAudioVolumeChanged);
-	setting_setup_signal_listener(gSavedSettings, "MuteAudio", handleAudioVolumeChanged);
-	setting_setup_signal_listener(gSavedSettings, "MuteMusic", handleAudioVolumeChanged);
-	setting_setup_signal_listener(gSavedSettings, "MuteMedia", handleAudioVolumeChanged);
-	setting_setup_signal_listener(gSavedSettings, "MuteVoice", handleAudioVolumeChanged);
-	setting_setup_signal_listener(gSavedSettings, "MuteAmbient", handleAudioVolumeChanged);
-	setting_setup_signal_listener(gSavedSettings, "MuteUI", handleAudioVolumeChanged);
-	setting_setup_signal_listener(gSavedSettings, "WLSkyDetail", handleWLSkyDetailChanged);
-	setting_setup_signal_listener(gSavedSettings, "JoystickAxis0", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "JoystickAxis1", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "JoystickAxis2", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "JoystickAxis3", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "JoystickAxis4", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "JoystickAxis5", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "JoystickAxis6", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale0", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale1", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale2", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale3", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale4", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale5", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale6", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone0", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone1", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone2", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone3", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone4", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone5", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone6", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale0", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale1", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale2", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale3", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale4", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale5", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone0", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone1", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone2", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone3", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone4", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone5", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "BuildAxisScale0", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "BuildAxisScale1", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "BuildAxisScale2", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "BuildAxisScale3", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "BuildAxisScale4", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "BuildAxisScale5", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone0", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone1", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone2", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone3", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone4", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone5", handleJoystickChanged);
-	setting_setup_signal_listener(gSavedSettings, "DebugViews", handleDebugViewsChanged);
-	setting_setup_signal_listener(gSavedSettings, "UserLogFile", handleLogFileChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderHideGroupTitle", handleHideGroupTitleChanged);
-	setting_setup_signal_listener(gSavedSettings, "HighResSnapshot", handleHighResSnapshotChanged);
-	setting_setup_signal_listener(gSavedSettings, "EnableVoiceChat", handleVoiceClientPrefsChanged);
-	setting_setup_signal_listener(gSavedSettings, "PTTCurrentlyEnabled", handleVoiceClientPrefsChanged);
-	setting_setup_signal_listener(gSavedSettings, "PushToTalkButton", handleVoiceClientPrefsChanged);
-	setting_setup_signal_listener(gSavedSettings, "PushToTalkToggle", handleVoiceClientPrefsChanged);
-	setting_setup_signal_listener(gSavedSettings, "VoiceEarLocation", handleVoiceClientPrefsChanged);
-	setting_setup_signal_listener(gSavedSettings, "VoiceInputAudioDevice", handleVoiceClientPrefsChanged);
-	setting_setup_signal_listener(gSavedSettings, "VoiceOutputAudioDevice", handleVoiceClientPrefsChanged);
-	setting_setup_signal_listener(gSavedSettings, "AudioLevelMic", handleVoiceClientPrefsChanged);
-	setting_setup_signal_listener(gSavedSettings, "LipSyncEnabled", handleVoiceClientPrefsChanged);	
-	setting_setup_signal_listener(gSavedSettings, "VelocityInterpolate", handleVelocityInterpolate);
-	setting_setup_signal_listener(gSavedSettings, "QAMode", show_debug_menus);
-	setting_setup_signal_listener(gSavedSettings, "UseDebugMenus", show_debug_menus);
-	setting_setup_signal_listener(gSavedSettings, "AgentPause", toggle_agent_pause);
-	setting_setup_signal_listener(gSavedSettings, "ShowNavbarNavigationPanel", toggle_show_navigation_panel);
-	setting_setup_signal_listener(gSavedSettings, "ShowMiniLocationPanel", toggle_show_mini_location_panel);
-	setting_setup_signal_listener(gSavedSettings, "ShowObjectRenderingCost", toggle_show_object_render_cost);
-	setting_setup_signal_listener(gSavedSettings, "ForceShowGrid", handleForceShowGrid);
-	setting_setup_signal_listener(gSavedSettings, "RenderTransparentWater", handleRenderTransparentWaterChanged);
-	setting_setup_signal_listener(gSavedSettings, "SpellCheck", handleSpellCheckChanged);
-	setting_setup_signal_listener(gSavedSettings, "SpellCheckDictionary", handleSpellCheckChanged);
-	setting_setup_signal_listener(gSavedSettings, "LoginLocation", handleLoginLocationChanged);
-	setting_setup_signal_listener(gSavedSettings, "DebugAvatarJoints", handleDebugAvatarJointsChanged);
-	setting_setup_signal_listener(gSavedSettings, "RenderAutoMuteByteLimit", handleRenderAutoMuteByteLimitChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderShadowDetail", handleSetShaderChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderDeferredSSAO", handleSetShaderChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderPerformanceTest", handleRenderPerfTestChanged);
+    setting_setup_signal_listener(gSavedSettings, "ChatFontSize", handleChatFontSizeChanged);
+    setting_setup_signal_listener(gSavedSettings, "ChatPersistTime", handleChatPersistTimeChanged);
+    setting_setup_signal_listener(gSavedSettings, "ConsoleMaxLines", handleConsoleMaxLinesChanged);
+    setting_setup_signal_listener(gSavedSettings, "UploadBakedTexOld", handleUploadBakedTexOldChanged);
+    setting_setup_signal_listener(gSavedSettings, "UseOcclusion", handleUseOcclusionChanged);
+    setting_setup_signal_listener(gSavedSettings, "AudioLevelMaster", handleAudioVolumeChanged);
+    setting_setup_signal_listener(gSavedSettings, "AudioLevelSFX", handleAudioVolumeChanged);
+    setting_setup_signal_listener(gSavedSettings, "AudioLevelUI", handleAudioVolumeChanged);
+    setting_setup_signal_listener(gSavedSettings, "AudioLevelAmbient", handleAudioVolumeChanged);
+    setting_setup_signal_listener(gSavedSettings, "AudioLevelMusic", handleAudioVolumeChanged);
+    setting_setup_signal_listener(gSavedSettings, "AudioLevelMedia", handleAudioVolumeChanged);
+    setting_setup_signal_listener(gSavedSettings, "AudioLevelVoice", handleAudioVolumeChanged);
+    setting_setup_signal_listener(gSavedSettings, "AudioLevelDoppler", handleAudioVolumeChanged);
+    setting_setup_signal_listener(gSavedSettings, "AudioLevelRolloff", handleAudioVolumeChanged);
+    setting_setup_signal_listener(gSavedSettings, "AudioLevelUnderwaterRolloff", handleAudioVolumeChanged);
+    setting_setup_signal_listener(gSavedSettings, "MuteAudio", handleAudioVolumeChanged);
+    setting_setup_signal_listener(gSavedSettings, "MuteMusic", handleAudioVolumeChanged);
+    setting_setup_signal_listener(gSavedSettings, "MuteMedia", handleAudioVolumeChanged);
+    setting_setup_signal_listener(gSavedSettings, "MuteVoice", handleAudioVolumeChanged);
+    setting_setup_signal_listener(gSavedSettings, "MuteAmbient", handleAudioVolumeChanged);
+    setting_setup_signal_listener(gSavedSettings, "MuteUI", handleAudioVolumeChanged);
+    setting_setup_signal_listener(gSavedSettings, "WLSkyDetail", handleWLSkyDetailChanged);
+    setting_setup_signal_listener(gSavedSettings, "JoystickAxis0", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "JoystickAxis1", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "JoystickAxis2", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "JoystickAxis3", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "JoystickAxis4", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "JoystickAxis5", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "JoystickAxis6", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale0", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale1", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale2", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale3", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale4", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale5", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale6", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone0", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone1", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone2", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone3", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone4", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone5", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone6", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale0", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale1", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale2", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale3", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale4", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale5", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone0", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone1", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone2", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone3", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone4", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone5", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "BuildAxisScale0", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "BuildAxisScale1", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "BuildAxisScale2", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "BuildAxisScale3", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "BuildAxisScale4", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "BuildAxisScale5", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone0", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone1", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone2", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone3", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone4", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone5", handleJoystickChanged);
+    setting_setup_signal_listener(gSavedSettings, "DebugViews", handleDebugViewsChanged);
+    setting_setup_signal_listener(gSavedSettings, "UserLogFile", handleLogFileChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderHideGroupTitle", handleHideGroupTitleChanged);
+    setting_setup_signal_listener(gSavedSettings, "HighResSnapshot", handleHighResSnapshotChanged);
+    setting_setup_signal_listener(gSavedSettings, "EnableVoiceChat", handleVoiceClientPrefsChanged);
+    setting_setup_signal_listener(gSavedSettings, "PTTCurrentlyEnabled", handleVoiceClientPrefsChanged);
+    setting_setup_signal_listener(gSavedSettings, "PushToTalkButton", handleVoiceClientPrefsChanged);
+    setting_setup_signal_listener(gSavedSettings, "PushToTalkToggle", handleVoiceClientPrefsChanged);
+    setting_setup_signal_listener(gSavedSettings, "VoiceEarLocation", handleVoiceClientPrefsChanged);
+    setting_setup_signal_listener(gSavedSettings, "VoiceInputAudioDevice", handleVoiceClientPrefsChanged);
+    setting_setup_signal_listener(gSavedSettings, "VoiceOutputAudioDevice", handleVoiceClientPrefsChanged);
+    setting_setup_signal_listener(gSavedSettings, "AudioLevelMic", handleVoiceClientPrefsChanged);
+    setting_setup_signal_listener(gSavedSettings, "LipSyncEnabled", handleVoiceClientPrefsChanged);	
+    setting_setup_signal_listener(gSavedSettings, "VelocityInterpolate", handleVelocityInterpolate);
+    setting_setup_signal_listener(gSavedSettings, "QAMode", show_debug_menus);
+    setting_setup_signal_listener(gSavedSettings, "UseDebugMenus", show_debug_menus);
+    setting_setup_signal_listener(gSavedSettings, "AgentPause", toggle_agent_pause);
+    setting_setup_signal_listener(gSavedSettings, "ShowNavbarNavigationPanel", toggle_show_navigation_panel);
+    setting_setup_signal_listener(gSavedSettings, "ShowMiniLocationPanel", toggle_show_mini_location_panel);
+    setting_setup_signal_listener(gSavedSettings, "ShowObjectRenderingCost", toggle_show_object_render_cost);
+    setting_setup_signal_listener(gSavedSettings, "ForceShowGrid", handleForceShowGrid);
+    setting_setup_signal_listener(gSavedSettings, "RenderTransparentWater", handleRenderTransparentWaterChanged);
+    setting_setup_signal_listener(gSavedSettings, "SpellCheck", handleSpellCheckChanged);
+    setting_setup_signal_listener(gSavedSettings, "SpellCheckDictionary", handleSpellCheckChanged);
+    setting_setup_signal_listener(gSavedSettings, "LoginLocation", handleLoginLocationChanged);
+    setting_setup_signal_listener(gSavedSettings, "DebugAvatarJoints", handleDebugAvatarJointsChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderAutoMuteByteLimit", handleRenderAutoMuteByteLimitChanged);
+
+    setting_setup_signal_listener(gSavedSettings, "TargetFPS", handleTargetFPSChanged);
+    setting_setup_signal_listener(gSavedSettings, "AutoTuneFPS", handleAutoTuneFPSChanged);
+    setting_setup_signal_listener(gSavedSettings, "AutoTuneLock", handleAutoTuneLockChanged);
+    setting_setup_signal_listener(gSavedSettings, "RenderAvatarMaxART", handleRenderAvatarMaxARTChanged);
+    setting_setup_signal_listener(gSavedSettings, "PerfStatsCaptureEnabled", handlePerformanceStatsEnabledChanged);
+    setting_setup_signal_listener(gSavedSettings, "AutoTuneRenderFarClipTarget", handleUserTargetDrawDistanceChanged);
+    setting_setup_signal_listener(gSavedSettings, "AutoTuneImpostorFarAwayDistance", handleUserImpostorDistanceChanged);
+    setting_setup_signal_listener(gSavedSettings, "AutoTuneImpostorByDistEnabled", handleUserImpostorByDistEnabledChanged);
+    setting_setup_signal_listener(gSavedSettings, "TuningFPSStrategy", handleFPSTuningStrategyChanged);
 
     setting_setup_signal_listener(gSavedPerAccountSettings, "AvatarHoverOffsetZ", handleAvatarHoverOffsetChanged);
 }
diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp
index 1730b02017ee3d6e08c9433999a3852d3ada7d4b..a2938eaefea5cc8b9230b7d1085cec27ba1047e7 100644
--- a/indra/newview/llviewerdisplay.cpp
+++ b/indra/newview/llviewerdisplay.cpp
@@ -79,6 +79,7 @@
 #include "llscenemonitor.h"
 
 #include "llenvironment.h"
+#include "llperfstats.h"
 
 extern LLPointer<LLViewerTexture> gStartTexture;
 extern bool gShiftFrame;
@@ -243,6 +244,7 @@ void display_stats()
 // Paint the display!
 void display(BOOL rebuild, F32 zoom_factor, int subfield, BOOL for_snapshot)
 {
+    LLPerfStats::RecordSceneTime T (LLPerfStats::StatType_t::RENDER_DISPLAY); // render time capture - This is the main stat for overall rendering.
     LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Render");
 
 	if (gWindowResized)
@@ -1090,7 +1092,8 @@ void display_cube_face()
 
 void render_hud_attachments()
 {
-	gGL.matrixMode(LLRender::MM_PROJECTION);
+    LLPerfStats::RecordSceneTime T ( LLPerfStats::StatType_t::RENDER_HUDS); // render time capture - Primary contributor to HUDs (though these end up in render batches)
+    gGL.matrixMode(LLRender::MM_PROJECTION);
 	gGL.pushMatrix();
 	gGL.matrixMode(LLRender::MM_MODELVIEW);
 	gGL.pushMatrix();
@@ -1289,7 +1292,8 @@ bool setup_hud_matrices(const LLRect& screen_region)
 
 void render_ui(F32 zoom_factor, int subfield)
 {
-	LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; //LL_RECORD_BLOCK_TIME(FTM_RENDER_UI);
+    LLPerfStats::RecordSceneTime T ( LLPerfStats::StatType_t::RENDER_UI ); // render time capture - Primary UI stat can have HUD time overlap (TODO)
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; //LL_RECORD_BLOCK_TIME(FTM_RENDER_UI);
     LL_PROFILE_GPU_ZONE("ui");
 	LLGLState::checkStates();
 	
@@ -1368,6 +1372,7 @@ void render_ui(F32 zoom_factor, int subfield)
 
 void swap()
 {
+    LLPerfStats::RecordSceneTime T ( LLPerfStats::StatType_t::RENDER_SWAP ); // render time capture - Swap buffer time - can signify excessive data transfer to/from GPU
     LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Swap");
     LL_PROFILE_GPU_ZONE("swap");
 	if (gDisplaySwapBuffers)
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index 14d5a7c2760c981cc2e9cb1b67d39593e27f68e1..ca87ff0092fbc84ef19411d4e8898aeee799f06f 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -108,9 +108,11 @@
 #include "llfloaterpathfindingconsole.h"
 #include "llfloaterpathfindinglinksets.h"
 #include "llfloaterpay.h"
+#include "llfloaterperformance.h"
 #include "llfloaterperms.h"
 #include "llfloaterpostprocess.h"
 #include "llfloaterpreference.h"
+#include "llfloaterpreferencesgraphicsadvanced.h"
 #include "llfloaterpreferenceviewadvanced.h"
 #include "llfloaterpreviewtrash.h"
 #include "llfloaterprofile.h"
@@ -408,6 +410,7 @@ void LLViewerFloaterReg::registerFloaters()
 	LLFloaterReg::add("pathfinding_linksets", "floater_pathfinding_linksets.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterPathfindingLinksets>);
 	LLFloaterReg::add("pathfinding_console", "floater_pathfinding_console.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterPathfindingConsole>);
 	LLFloaterReg::add("people", "floater_people.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSidePanelContainer>);
+	LLFloaterReg::add("performance", "floater_performance.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterPerformance>);
 	LLFloaterReg::add("perms_default", "floater_perms_default.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterPermsDefault>);
 	LLFloaterReg::add("places", "floater_places.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSidePanelContainer>);
 	LLFloaterReg::add("preferences", "floater_preferences.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterPreference>);
diff --git a/indra/newview/llviewerjointmesh.cpp b/indra/newview/llviewerjointmesh.cpp
index be2a1da9685c18a113e7974687e3bc81193f37fe..5ce8f4023d2bdd1ace08e3abd1102a1d731f2ccc 100644
--- a/indra/newview/llviewerjointmesh.cpp
+++ b/indra/newview/llviewerjointmesh.cpp
@@ -55,6 +55,7 @@
 #include "m3math.h"
 #include "m4math.h"
 #include "llmatrix4a.h"
+#include "llperfstats.h" 
 
 #if !LL_DARWIN && !LL_LINUX
 extern PFNGLWEIGHTPOINTERARBPROC glWeightPointerARB;
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index 25efe189cb81247224654e8e26e37a39dec7fbec..d22ffc784a9911ee7e49da201267791bea6e7afd 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -3281,6 +3281,8 @@ class LLAvatarCheckImpostorMode : public view_listener_t
 				return (avatar->getVisualMuteSettings() == LLVOAvatar::AV_DO_NOT_RENDER);
 			case 2:
 				return (avatar->getVisualMuteSettings() == LLVOAvatar::AV_ALWAYS_RENDER);
+            case 4:
+                return (avatar->getVisualMuteSettings() != LLVOAvatar::AV_RENDER_NORMALLY);
 			default:
 				return false;
 		}
diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp
index 100c73377fadf0b0be4f0581baead25f64588a84..77b4804076b5fe02b69134423249620a59daccda 100644
--- a/indra/newview/llviewerobject.cpp
+++ b/indra/newview/llviewerobject.cpp
@@ -369,7 +369,7 @@ LLViewerObject::~LLViewerObject()
     }
 
 	// Delete memory associated with extra parameters.
-	std::map<U16, ExtraParameter*>::iterator iter;
+	std::unordered_map<U16, ExtraParameter*>::iterator iter;
 	for (iter = mExtraParameterList.begin(); iter != mExtraParameterList.end(); ++iter)
 	{
 		if(iter->second != NULL)
@@ -1555,7 +1555,7 @@ U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys,
 				unpackParticleSource(block_num, owner_id);
 
 				// Mark all extra parameters not used
-				std::map<U16, ExtraParameter*>::iterator iter;
+				std::unordered_map<U16, ExtraParameter*>::iterator iter;
 				for (iter = mExtraParameterList.begin(); iter != mExtraParameterList.end(); ++iter)
 				{
 					iter->second->in_use = FALSE;
@@ -1947,7 +1947,7 @@ U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys,
 				}
 				
 				// Mark all extra parameters not used
-				std::map<U16, ExtraParameter*>::iterator iter;
+				std::unordered_map<U16, ExtraParameter*>::iterator iter;
 				for (iter = mExtraParameterList.begin(); iter != mExtraParameterList.end(); ++iter)
 				{
 					iter->second->in_use = FALSE;
@@ -3965,6 +3965,7 @@ U32 LLViewerObject::recursiveGetTriangleCount(S32* vcount) const
 // prim's scale. Should revisit at some point.
 F32 LLViewerObject::recursiveGetScaledSurfaceArea() const
 {
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME;
     F32 area = 0.f;
     const LLDrawable* drawable = mDrawable;
     if (drawable)
@@ -5689,7 +5690,7 @@ S32 LLViewerObject::countInventoryContents(LLAssetType::EType type)
 	return count;
 }
 
-void LLViewerObject::setDebugText(const std::string &utf8text)
+void LLViewerObject::setDebugText(const std::string &utf8text, const LLColor4& color)
 {
 	if (utf8text.empty() && !mText)
 	{
@@ -5700,7 +5701,7 @@ void LLViewerObject::setDebugText(const std::string &utf8text)
 	{
 	    initHudText();
 	}
-	mText->setColor(LLColor4::white);
+	mText->setColor(color);
 	mText->setString(utf8text);
 	mText->setZCompare(FALSE);
 	mText->setDoFade(FALSE);
@@ -6241,7 +6242,8 @@ LLViewerObject::ExtraParameter* LLViewerObject::createNewParameterEntry(U16 para
 
 LLViewerObject::ExtraParameter* LLViewerObject::getExtraParameterEntry(U16 param_type) const
 {
-	std::map<U16, ExtraParameter*>::const_iterator itor = mExtraParameterList.find(param_type);
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_VIEWER;
+	std::unordered_map<U16, ExtraParameter*>::const_iterator itor = mExtraParameterList.find(param_type);
 	if (itor != mExtraParameterList.end())
 	{
 		return itor->second;
diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h
index e647fdd0450c24cb106c1f503cdc4213036ee0a5..a18d07d970aa95fc6358b036e72c9f8c21df99d8 100644
--- a/indra/newview/llviewerobject.h
+++ b/indra/newview/llviewerobject.h
@@ -28,6 +28,7 @@
 #define LL_LLVIEWEROBJECT_H
 
 #include <map>
+#include <unordered_map>
 
 #include "llassetstorage.h"
 //#include "llhudicon.h"
@@ -122,7 +123,7 @@ class LLViewerObject
 		BOOL in_use;
 		LLNetworkData *data;
 	};
-	std::map<U16, ExtraParameter*> mExtraParameterList;
+	std::unordered_map<U16, ExtraParameter*> mExtraParameterList;
 
 public:
 	typedef std::list<LLPointer<LLViewerObject> > child_list_t;
@@ -441,7 +442,7 @@ class LLViewerObject
 
 	void sendMaterialUpdate() const;
 
-	void setDebugText(const std::string &utf8text);
+	void setDebugText(const std::string &utf8text, const LLColor4& color = LLColor4::white);
 	void appendDebugText(const std::string &utf8text);
 	void initHudText();
 	void restoreHudText();
@@ -941,6 +942,10 @@ class LLViewerObject
     // reflection probe state
     bool mIsReflectionProbe = false;  // if true, this object should register itself with LLReflectionProbeManager
     LLPointer<LLReflectionMap> mReflectionProbe = nullptr; // reflection probe coupled to this viewer object.  If not null, should be deregistered when this object is destroyed
+
+    // the amount of GPU time (in ms) it took to render this object according to LLPipeline::profileAvatar
+    // -1.f if no profile data available
+    F32 mGPURenderTime = -1.f;
 };
 
 ///////////////////
diff --git a/indra/newview/llviewerpartsim.cpp b/indra/newview/llviewerpartsim.cpp
index e5265f1dcd50f029c3d710206b3fe17b4e1bdab8..449fd4ba43c8b253ad0969bfaad7ef3d5f2ca8df 100644
--- a/indra/newview/llviewerpartsim.cpp
+++ b/indra/newview/llviewerpartsim.cpp
@@ -716,7 +716,7 @@ void LLViewerPartSim::updateSimulation()
 
 			if (upd && vobj && (vobj->getPCode() == LL_PCODE_VOLUME))
 			{
-				if(vobj->getAvatar() && vobj->getAvatar()->isTooComplex())
+				if(vobj->getAvatar() && vobj->getAvatar()->isTooComplex() && vobj->getAvatar()->isTooSlow())
 				{
 					upd = FALSE;
 				}
diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp
index 63cc7ba62349a2da5013b0b73bacaf0a1878c4b0..4ceef07e726e31c3b44ecb0361d4567e596b12bd 100644
--- a/indra/newview/llviewershadermgr.cpp
+++ b/indra/newview/llviewershadermgr.cpp
@@ -188,6 +188,8 @@ LLGLSLShader			gDeferredPostProgram;
 LLGLSLShader			gDeferredCoFProgram;
 LLGLSLShader			gDeferredDoFCombineProgram;
 LLGLSLShader			gDeferredPostGammaCorrectProgram;
+LLGLSLShader            gNoPostGammaCorrectProgram;
+LLGLSLShader            gLegacyPostGammaCorrectProgram;
 LLGLSLShader			gExposureProgram;
 LLGLSLShader			gLuminanceProgram;
 LLGLSLShader			gFXAAProgram;
@@ -295,6 +297,8 @@ LLViewerShaderMgr::LLViewerShaderMgr() :
     mShaderList.push_back(&gHUDPBRAlphaProgram);
     mShaderList.push_back(&gDeferredSkinnedPBRAlphaProgram);
     mShaderList.push_back(&gDeferredPostGammaCorrectProgram); // for gamma
+    mShaderList.push_back(&gNoPostGammaCorrectProgram);
+    mShaderList.push_back(&gLegacyPostGammaCorrectProgram);
 
 }
 
@@ -1010,6 +1014,8 @@ BOOL LLViewerShaderMgr::loadShadersDeferred()
         gExposureProgram.unload();
         gLuminanceProgram.unload();
 		gDeferredPostGammaCorrectProgram.unload();
+        gNoPostGammaCorrectProgram.unload();
+        gLegacyPostGammaCorrectProgram.unload();
 		gFXAAProgram.unload();
 		gDeferredWLSkyProgram.unload();
 		gDeferredWLCloudProgram.unload();
@@ -2338,11 +2344,10 @@ BOOL LLViewerShaderMgr::loadShadersDeferred()
         gDeferredShadowGLTFAlphaMaskProgram.mName = "Deferred GLTF Shadow Alpha Mask Shader";
         gDeferredShadowGLTFAlphaMaskProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels;
         gDeferredShadowGLTFAlphaMaskProgram.mShaderFiles.clear();
-        gDeferredShadowGLTFAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/shadowAlphaMaskV.glsl", GL_VERTEX_SHADER));
-        gDeferredShadowGLTFAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/shadowAlphaMaskF.glsl", GL_FRAGMENT_SHADER));
+        gDeferredShadowGLTFAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/pbrShadowAlphaMaskV.glsl", GL_VERTEX_SHADER));
+        gDeferredShadowGLTFAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/pbrShadowAlphaMaskF.glsl", GL_FRAGMENT_SHADER));
         gDeferredShadowGLTFAlphaMaskProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED];
         gDeferredShadowGLTFAlphaMaskProgram.clearPermutations();
-        gDeferredShadowGLTFAlphaMaskProgram.addPermutation("GLTF", "1");
         success = make_rigged_variant(gDeferredShadowGLTFAlphaMaskProgram, gDeferredSkinnedShadowGLTFAlphaMaskProgram);
         success = success && gDeferredShadowGLTFAlphaMaskProgram.createShader(NULL, NULL);
         llassert(success);
@@ -2537,6 +2542,37 @@ BOOL LLViewerShaderMgr::loadShadersDeferred()
 		success = gDeferredPostGammaCorrectProgram.createShader(NULL, NULL);
 		llassert(success);
 	}
+    
+    if (success)
+    {
+        gNoPostGammaCorrectProgram.mName = "No Post Gamma Correction Post Process";
+        gNoPostGammaCorrectProgram.mFeatures.hasSrgb = true;
+        gNoPostGammaCorrectProgram.mFeatures.isDeferred = true;
+        gNoPostGammaCorrectProgram.mShaderFiles.clear();
+        gNoPostGammaCorrectProgram.clearPermutations();
+        gNoPostGammaCorrectProgram.addPermutation("NO_POST", "1");
+        gNoPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER));
+        gNoPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredGammaCorrect.glsl", GL_FRAGMENT_SHADER));
+        gNoPostGammaCorrectProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED];
+        success = gNoPostGammaCorrectProgram.createShader(NULL, NULL);
+        llassert(success);
+    }
+
+    if (success)
+    {
+        gLegacyPostGammaCorrectProgram.mName = "Legacy Gamma Correction Post Process";
+        gLegacyPostGammaCorrectProgram.mFeatures.hasSrgb = true;
+        gLegacyPostGammaCorrectProgram.mFeatures.isDeferred = true;
+        gLegacyPostGammaCorrectProgram.mShaderFiles.clear();
+        gLegacyPostGammaCorrectProgram.clearPermutations();
+        gLegacyPostGammaCorrectProgram.addPermutation("LEGACY_GAMMA", "1");
+        gLegacyPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER));
+        gLegacyPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredGammaCorrect.glsl", GL_FRAGMENT_SHADER));
+        gLegacyPostGammaCorrectProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED];
+        success = gLegacyPostGammaCorrectProgram.createShader(NULL, NULL);
+        llassert(success);
+    }
+
 
 	if (success && gGLManager.mGLVersion > 3.9f)
 	{
diff --git a/indra/newview/llviewershadermgr.h b/indra/newview/llviewershadermgr.h
index 129802aca5966cca9b0adf909c7952b2b79477ff..492e60b840a62274d48a80263e8df52e6d88669f 100644
--- a/indra/newview/llviewershadermgr.h
+++ b/indra/newview/llviewershadermgr.h
@@ -235,6 +235,8 @@ extern LLGLSLShader			gDeferredDoFCombineProgram;
 extern LLGLSLShader			gFXAAProgram;
 extern LLGLSLShader			gDeferredPostNoDoFProgram;
 extern LLGLSLShader			gDeferredPostGammaCorrectProgram;
+extern LLGLSLShader         gNoPostGammaCorrectProgram;
+extern LLGLSLShader         gLegacyPostGammaCorrectProgram;
 extern LLGLSLShader			gExposureProgram;
 extern LLGLSLShader			gLuminanceProgram;
 extern LLGLSLShader			gDeferredAvatarShadowProgram;
diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp
index 986319f26d4789b4710e97ee8593b5bedb9ab282..37999e58ff50997756899e8cf1a8ca1e72086a14 100644
--- a/indra/newview/llviewerstats.cpp
+++ b/indra/newview/llviewerstats.cpp
@@ -58,6 +58,7 @@
 #include "llfeaturemanager.h"
 #include "llviewernetwork.h"
 #include "llmeshrepository.h" //for LLMeshRepository::sBytesReceived
+#include "llperfstats.h"
 #include "llsdserialize.h"
 #include "llsdutil.h"
 #include "llcorehttputil.h"
@@ -254,6 +255,13 @@ LLTrace::EventStatHandle<F64Seconds >	AVATAR_EDIT_TIME("avataredittime", "Second
 LLTrace::EventStatHandle<LLUnit<F32, LLUnits::Percent> > OBJECT_CACHE_HIT_RATE("object_cache_hits");
 
 LLTrace::EventStatHandle<F64Seconds >	TEXTURE_FETCH_TIME("texture_fetch_time");
+
+LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> >  SCENERY_FRAME_PCT("scenery_frame_pct");
+LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> >  AVATAR_FRAME_PCT("avatar_frame_pct");
+LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> >  HUDS_FRAME_PCT("huds_frame_pct");
+LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> >  UI_FRAME_PCT("ui_frame_pct");
+LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> >  SWAP_FRAME_PCT("swap_frame_pct");
+LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> >  IDLE_FRAME_PCT("idle_frame_pct");
 }
 
 LLViewerStats::LLViewerStats() 
@@ -452,6 +460,89 @@ void update_statistics()
 			texture_stats_timer.reset();
 		}
 	}
+
+    if (LLFloaterReg::instanceVisible("scene_load_stats"))
+    {
+        static const F32 perf_stats_freq = 1;
+        static LLFrameTimer perf_stats_timer;
+        if (perf_stats_timer.getElapsedTimeF32() >= perf_stats_freq)
+        {
+            LLStringUtil::format_map_t args;
+            LLPerfStats::bufferToggleLock.lock(); // prevent toggle for a moment
+
+            auto tot_frame_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_FRAME);
+            // cumulative avatar time (includes idle processing, attachments and base av)
+            auto tot_avatar_time_raw = LLPerfStats::us_to_raw(LLVOAvatar::getTotalGPURenderTime());
+            // cumulative avatar render specific time (a bit arbitrary as the processing is too.)
+            // auto tot_av_idle_time_raw = LLPerfStats::StatsRecorder::getSum(AvType, LLPerfStats::StatType_t::RENDER_IDLE);
+            // auto tot_avatar_render_time_raw = tot_avatar_time_raw - tot_av_idle_time_raw;
+            // the time spent this frame on the "display()" call. Treated as "tot time rendering"
+            auto tot_render_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_DISPLAY);
+            // sleep time is basically forced sleep when window out of focus 
+            auto tot_sleep_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_SLEEP);
+            // time spent on UI
+            auto tot_ui_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_UI);
+            // cumulative time spent rendering HUDS
+            auto tot_huds_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_HUDS);
+            // "idle" time. This is the time spent in the idle poll section of the main loop
+            auto tot_idle_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_IDLE);
+            // similar to sleep time, induced by FPS limit
+            //auto tot_limit_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_FPSLIMIT);
+            // swap time is time spent in swap buffer
+            auto tot_swap_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_SWAP);
+
+            LLPerfStats::bufferToggleLock.unlock();
+
+             auto tot_frame_time_ns = LLPerfStats::raw_to_ns(tot_frame_time_raw);
+            auto tot_avatar_time_ns = LLPerfStats::raw_to_ns(tot_avatar_time_raw);
+            auto tot_huds_time_ns = LLPerfStats::raw_to_ns(tot_huds_time_raw);
+            // UI time includes HUD time so dedut that before we calc percentages
+            auto tot_ui_time_ns = LLPerfStats::raw_to_ns(tot_ui_time_raw - tot_huds_time_raw);
+
+            // auto tot_sleep_time_ns          = LLPerfStats::raw_to_ns( tot_sleep_time_raw );
+            // auto tot_limit_time_ns          = LLPerfStats::raw_to_ns( tot_limit_time_raw );
+
+            // auto tot_render_time_ns         = LLPerfStats::raw_to_ns( tot_render_time_raw );
+            auto tot_idle_time_ns = LLPerfStats::raw_to_ns(tot_idle_time_raw);
+            auto tot_swap_time_ns = LLPerfStats::raw_to_ns(tot_swap_time_raw);
+            auto tot_scene_time_ns = LLPerfStats::raw_to_ns(tot_render_time_raw - tot_avatar_time_raw - tot_swap_time_raw - tot_ui_time_raw);
+            // auto tot_overhead_time_ns  = LLPerfStats::raw_to_ns( tot_frame_time_raw - tot_render_time_raw - tot_idle_time_raw );
+
+            // // remove time spent sleeping for fps limit or out of focus.
+            // tot_frame_time_ns -= tot_limit_time_ns;
+            // tot_frame_time_ns -= tot_sleep_time_ns;
+
+            if (tot_frame_time_ns != 0)
+            {
+                auto pct_avatar_time = (tot_avatar_time_ns * 100) / tot_frame_time_ns;
+                auto pct_huds_time = (tot_huds_time_ns * 100) / tot_frame_time_ns;
+                auto pct_ui_time = (tot_ui_time_ns * 100) / tot_frame_time_ns;
+                auto pct_idle_time = (tot_idle_time_ns * 100) / tot_frame_time_ns;
+                auto pct_swap_time = (tot_swap_time_ns * 100) / tot_frame_time_ns;
+                auto pct_scene_render_time = (tot_scene_time_ns * 100) / tot_frame_time_ns;
+                pct_avatar_time = llclamp(pct_avatar_time, 0., 100.);
+                pct_huds_time = llclamp(pct_huds_time, 0., 100.);
+                pct_ui_time = llclamp(pct_ui_time, 0., 100.);
+                pct_idle_time = llclamp(pct_idle_time, 0., 100.);
+                pct_swap_time = llclamp(pct_swap_time, 0., 100.);
+                pct_scene_render_time = llclamp(pct_scene_render_time, 0., 100.);
+                if (tot_sleep_time_raw == 0)
+                {
+                    sample(LLStatViewer::SCENERY_FRAME_PCT, (U32)llround(pct_scene_render_time));
+                    sample(LLStatViewer::AVATAR_FRAME_PCT, (U32)llround(pct_avatar_time));
+                    sample(LLStatViewer::HUDS_FRAME_PCT, (U32)llround(pct_huds_time));
+                    sample(LLStatViewer::UI_FRAME_PCT, (U32)llround(pct_ui_time));
+                    sample(LLStatViewer::SWAP_FRAME_PCT, (U32)llround(pct_swap_time));
+                    sample(LLStatViewer::IDLE_FRAME_PCT, (U32)llround(pct_idle_time));
+                }
+            }
+            else
+            {
+                LL_WARNS("performance") << "Scene time 0. Skipping til we have data." << LL_ENDL;
+            }
+            perf_stats_timer.reset();
+        }
+    }
 }
 
 /*
@@ -551,6 +642,7 @@ void send_viewer_stats(bool include_preferences)
 
 	system["gpu"] = gpu_desc;
 	system["gpu_class"] = (S32)LLFeatureManager::getInstance()->getGPUClass();
+    system["gpu_memory_bandwidth"] = LLFeatureManager::getInstance()->getGPUMemoryBandwidth();
 	system["gpu_vendor"] = gGLManager.mGLVendorShort;
 	system["gpu_version"] = gGLManager.mDriverVersionVendorString;
 	system["opengl_version"] = gGLManager.mGLVersionString;
@@ -737,12 +829,11 @@ void send_viewer_stats(bool include_preferences)
 
 
 	LL_INFOS("LogViewerStatsPacket") << "Sending viewer statistics: " << body << LL_ENDL;
-	if (debugLoggingEnabled("LogViewerStatsPacket"))
-	{
-		std::string filename("viewer_stats_packet.xml");
-		llofstream of(filename.c_str());
-		LLSDSerialize::toPrettyXML(body,of);
-	}
+	LL_DEBUGS("LogViewerStatsPacket");
+	std::string filename("viewer_stats_packet.xml");
+	llofstream of(filename.c_str());
+	LLSDSerialize::toPrettyXML(body,of);
+	LL_ENDL;
 
 	// The session ID token must never appear in logs
 	body["session_id"] = gAgentSessionID;
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index 5adb3f9ede4c69a8d89cdc1686893890b38188d3..af65588709a10550847a2f3b4371e06dcc4bb40b 100644
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -113,6 +113,8 @@
 #include "llrendersphere.h"
 #include "llskinningutil.h"
 
+#include "llperfstats.h"
+
 #include <boost/lexical_cast.hpp>
 
 extern F32 SPEED_ADJUST_MAX;
@@ -205,6 +207,8 @@ const F64 HUD_OVERSIZED_TEXTURE_DATA_SIZE = 1024 * 1024;
 
 const F32 MAX_TEXTURE_WAIT_TIME_SEC = 60;
 
+const S32 MIN_NONTUNED_AVS = 5;
+
 enum ERenderName
 {
 	RENDER_NAME_NEVER,
@@ -616,6 +620,8 @@ F32 LLVOAvatar::sUnbakedUpdateTime = 0.f;
 F32 LLVOAvatar::sGreyTime = 0.f;
 F32 LLVOAvatar::sGreyUpdateTime = 0.f;
 LLPointer<LLViewerTexture> LLVOAvatar::sCloudTexture = NULL;
+std::vector<LLUUID> LLVOAvatar::sAVsIgnoringARTLimit;
+S32 LLVOAvatar::sAvatarsNearby = 0;
 
 //-----------------------------------------------------------------------------
 // Helper functions
@@ -814,6 +820,14 @@ LLVOAvatar::~LLVOAvatar()
 		debugAvatarRezTime("AvatarRezLeftNotification","left sometime after declouding");
 	}
 
+    if(mTuned)
+    {
+        LLPerfStats::tunedAvatars--;
+        mTuned = false;
+    }
+    sAVsIgnoringARTLimit.erase(std::remove(sAVsIgnoringARTLimit.begin(), sAVsIgnoringARTLimit.end(), mID), sAVsIgnoringARTLimit.end());
+
+
 	logPendingPhases();
 	
 	LL_DEBUGS("Avatar") << "LLVOAvatar Destructor (0x" << this << ") id:" << mID << LL_ENDL;
@@ -1434,7 +1448,7 @@ void LLVOAvatar::calculateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax)
                                 continue;
                             }
                         }
-                        if (vol && vol->isRiggedMesh())
+                        if (vol && vol->isRiggedMeshFast())
                         {
                             continue;
                         }
@@ -2547,11 +2561,17 @@ void LLVOAvatar::idleUpdate(LLAgent &agent, const F64 &time)
 		LL_INFOS() << "Warning!  Idle on dead avatar" << LL_ENDL;
 		return;
 	}
+    // record time and refresh "tooSlow" status
+    updateTooSlow();
 
 	static LLCachedControl<bool> disable_all_render_types(gSavedSettings, "DisableAllRenderTypes");
 	if (!(gPipeline.hasRenderType(mIsControlAvatar ? LLPipeline::RENDER_TYPE_CONTROL_AV : LLPipeline::RENDER_TYPE_AVATAR))
 		&& !disable_all_render_types && !isSelf())
 	{
+        if (!mIsControlAvatar)
+        {
+            idleUpdateNameTag( mLastRootPos );
+        }
 		return;
 	}
 
@@ -2676,6 +2696,10 @@ void LLVOAvatar::idleUpdate(LLAgent &agent, const F64 &time)
 
     if ((LLFrameTimer::getFrameCount() + mID.mData[0]) % compl_upd_freq == 0)
     {
+        // DEPRECATED 
+        // replace with LLPipeline::profileAvatar?
+        // Avatar profile takes ~ 0.5ms while idleUpdateRenderComplexity takes ~5ms
+        // (both are unacceptably costly)
         idleUpdateRenderComplexity();
     }
     idleUpdateDebugInfo();
@@ -3131,7 +3155,7 @@ void LLVOAvatar::idleUpdateLoadingEffect()
 																 LLPartData::LL_PART_TARGET_POS_MASK );
 			
 			// do not generate particles for dummy or overly-complex avatars
-			if (!mIsDummy && !isTooComplex())
+			if (!mIsDummy && !isTooComplex() && !isTooSlow())
 			{
 				setParticleSource(particle_parameters, getID());
 			}
@@ -3216,11 +3240,9 @@ void LLVOAvatar::idleUpdateNameTag(const LLVector3& root_pos_last)
     static LLCachedControl<F32> FADE_DURATION(gSavedSettings, "RenderNameFadeDuration"); // seconds
     static LLCachedControl<bool> use_chat_bubbles(gSavedSettings, "UseChatBubbles");
 
-	bool visible_avatar = isVisible() || mNeedsAnimUpdate;
 	bool visible_chat = use_chat_bubbles && (mChats.size() || mTyping);
 	bool render_name =	visible_chat ||
-		(visible_avatar &&
-		 ((sRenderName == RENDER_NAME_ALWAYS) ||
+		(((sRenderName == RENDER_NAME_ALWAYS) ||
 		  (sRenderName == RENDER_NAME_FADE && time_visible < NAME_SHOW_TIME)));
 	// If it's your own avatar, don't draw in mouselook, and don't
 	// draw if we're specifically hiding our own name.
@@ -3715,7 +3737,7 @@ bool LLVOAvatar::isVisuallyMuted()
         }
 		else 
 		{
-			muted = isTooComplex();
+			muted = isTooComplex() || isTooSlow();
 		}
 	}
 
@@ -8275,6 +8297,86 @@ bool LLVOAvatar::isTooComplex() const
 	return too_complex;
 }
 
+bool LLVOAvatar::isTooSlow() const
+{
+    static LLCachedControl<bool> always_render_friends(gSavedSettings, "AlwaysRenderFriends");
+    bool render_friend =  (LLAvatarTracker::instance().isBuddy(getID()) && always_render_friends);
+
+    if (render_friend || mVisuallyMuteSetting == AV_ALWAYS_RENDER)
+    {
+        return false;
+    }
+    return mTooSlow;
+}
+
+// Udpate Avatar state based on render time
+void LLVOAvatar::updateTooSlow()
+{
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
+    static LLCachedControl<bool> alwaysRenderFriends(gSavedSettings, "AlwaysRenderFriends");
+    static LLCachedControl<bool> allowSelfImpostor(gSavedSettings, "AllowSelfImpostor");
+    const auto id = getID();
+
+    // mTooSlow - Is the avatar flagged as being slow (includes shadow time)
+    // mTooSlowWithoutShadows - Is the avatar flagged as being slow even with shadows removed.
+    
+    // get max render time in ms
+    F32 max_art_ms = (F32) (LLPerfStats::renderAvatarMaxART_ns / 1000000.0);
+
+	bool autotune = LLPerfStats::tunables.userAutoTuneEnabled && !mIsControlAvatar && !isSelf();
+
+	bool ignore_tune = false;
+    if (autotune && sAVsIgnoringARTLimit.size() > 0)
+    {
+        auto it = std::find(sAVsIgnoringARTLimit.begin(), sAVsIgnoringARTLimit.end(), mID);
+        if (it != sAVsIgnoringARTLimit.end())
+        {
+            S32 index = it - sAVsIgnoringARTLimit.begin();
+            ignore_tune = (index < (MIN_NONTUNED_AVS - sAvatarsNearby + 1 + LLPerfStats::tunedAvatars));
+        }
+    }
+
+	bool exceeds_max_ART =
+        ((LLPerfStats::renderAvatarMaxART_ns > 0) && 
+            (mGPURenderTime >= max_art_ms)); // NOTE: don't use getGPURenderTime accessor here to avoid "isTooSlow" feedback loop
+
+    if (exceeds_max_ART && !ignore_tune)
+    {
+        mTooSlow = true;
+        
+        if(!mTooSlowWithoutShadows) // if we were not previously above the full impostor cap
+        {
+            bool render_friend_or_exception =  	( alwaysRenderFriends && LLAvatarTracker::instance().isBuddy( id ) ) ||
+                ( getVisualMuteSettings() == LLVOAvatar::AV_ALWAYS_RENDER ); 
+            if( (!isSelf() || allowSelfImpostor) && !render_friend_or_exception  )
+            {
+                // Note: slow rendering Friends still get their shadows zapped.
+                mTooSlowWithoutShadows = getGPURenderTime()*2.f >= max_art_ms;  // NOTE: assumes shadow rendering doubles render time
+            }
+        }
+    }
+    else
+    {
+        mTooSlow = false;
+        mTooSlowWithoutShadows = false;
+
+		if (ignore_tune)
+		{
+            return;
+		}
+    }
+    if(mTooSlow && !mTuned)
+    {
+        LLPerfStats::tunedAvatars++; // increment the number of avatars that have been tweaked.
+        mTuned = true;
+    }
+    else if(!mTooSlow && mTuned)
+    {
+        LLPerfStats::tunedAvatars--;
+        mTuned = false;
+    }
+}
+
 //-----------------------------------------------------------------------------
 // findMotion()
 //-----------------------------------------------------------------------------
@@ -10536,6 +10638,64 @@ void LLVOAvatar::idleUpdateRenderComplexity()
 
     // Render Complexity
     calculateUpdateRenderComplexity(); // Update mVisualComplexity if needed	
+
+	bool autotune = LLPerfStats::tunables.userAutoTuneEnabled && !mIsControlAvatar && !isSelf();
+    if (autotune && !isDead())
+    {
+        static LLCachedControl<F32> render_far_clip(gSavedSettings, "RenderFarClip", 64);
+        F32 radius = render_far_clip * render_far_clip;
+
+        bool is_nearby = true;
+        if ((dist_vec_squared(getPositionGlobal(), gAgent.getPositionGlobal()) > radius) &&
+            (dist_vec_squared(getPositionGlobal(), gAgentCamera.getCameraPositionGlobal()) > radius))
+        {
+            is_nearby = false;
+        }
+
+        if (is_nearby && (sAVsIgnoringARTLimit.size() < MIN_NONTUNED_AVS))
+        {
+            if (std::count(sAVsIgnoringARTLimit.begin(), sAVsIgnoringARTLimit.end(), mID) == 0)
+            {
+                sAVsIgnoringARTLimit.push_back(mID);
+            }
+        }
+        else if (!is_nearby)
+        {
+            sAVsIgnoringARTLimit.erase(std::remove(sAVsIgnoringARTLimit.begin(), sAVsIgnoringARTLimit.end(), mID),
+                                       sAVsIgnoringARTLimit.end());
+        }
+        updateNearbyAvatarCount();
+    }
+}
+
+void LLVOAvatar::updateNearbyAvatarCount()
+{
+    static LLFrameTimer agent_update_timer;
+
+	if (agent_update_timer.getElapsedTimeF32() > 1.0f)
+    {
+        S32 avs_nearby = 0;
+        static LLCachedControl<F32> render_far_clip(gSavedSettings, "RenderFarClip", 64);
+        F32 radius = render_far_clip * render_far_clip;
+        std::vector<LLCharacter *>::iterator char_iter = LLCharacter::sInstances.begin();
+        while (char_iter != LLCharacter::sInstances.end())
+        {
+            LLVOAvatar *avatar = dynamic_cast<LLVOAvatar *>(*char_iter);
+            if (avatar && !avatar->isDead() && !avatar->isControlAvatar())
+            {
+                if ((dist_vec_squared(avatar->getPositionGlobal(), gAgent.getPositionGlobal()) > radius) &&
+                    (dist_vec_squared(avatar->getPositionGlobal(), gAgentCamera.getCameraPositionGlobal()) > radius))
+                {
+                    char_iter++;
+                    continue;
+                }
+                avs_nearby++;
+            }
+            char_iter++;
+        }
+        sAvatarsNearby = avs_nearby;
+        agent_update_timer.reset();
+    }
 }
 
 void LLVOAvatar::idleUpdateDebugInfo()
@@ -10626,6 +10786,7 @@ void LLVOAvatar::updateVisualComplexity()
 	mVisualComplexityStale = true;
 }
 
+
 // Account for the complexity of a single top-level object associated
 // with an avatar. This will be either an attached object or an animated
 // object.
@@ -10634,137 +10795,149 @@ void LLVOAvatar::accountRenderComplexityForObject(
     const F32 max_attachment_complexity,
     LLVOVolume::texture_cost_t& textures,
     U32& cost,
-    hud_complexity_list_t& hud_complexity_list)
+    hud_complexity_list_t& hud_complexity_list,
+    object_complexity_list_t& object_complexity_list)
 {
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
     if (attached_object && !attached_object->isHUDAttachment())
-		{
+    {
         mAttachmentVisibleTriangleCount += attached_object->recursiveGetTriangleCount();
         mAttachmentEstTriangleCount += attached_object->recursiveGetEstTrianglesMax();
         mAttachmentSurfaceArea += attached_object->recursiveGetScaledSurfaceArea();
 
-					textures.clear();
-					const LLDrawable* drawable = attached_object->mDrawable;
-					if (drawable)
-					{
-						const LLVOVolume* volume = drawable->getVOVolume();
-						if (volume)
-						{
-                            F32 attachment_total_cost = 0;
-                            F32 attachment_volume_cost = 0;
-                            F32 attachment_texture_cost = 0;
-                            F32 attachment_children_cost = 0;
+        textures.clear();
+        const LLDrawable* drawable = attached_object->mDrawable;
+        if (drawable)
+        {
+            const LLVOVolume* volume = drawable->getVOVolume();
+            if (volume)
+            {
+                F32 attachment_total_cost = 0;
+                F32 attachment_volume_cost = 0;
+                F32 attachment_texture_cost = 0;
+                F32 attachment_children_cost = 0;
                 const F32 animated_object_attachment_surcharge = 1000;
 
-                if (attached_object->isAnimatedObject())
+                if (volume->isAnimatedObjectFast())
                 {
                     attachment_volume_cost += animated_object_attachment_surcharge;
                 }
-							attachment_volume_cost += volume->getRenderCost(textures);
+                attachment_volume_cost += volume->getRenderCost(textures);
 
-							const_child_list_t children = volume->getChildren();
-							for (const_child_list_t::const_iterator child_iter = children.begin();
-								  child_iter != children.end();
-								  ++child_iter)
-							{
-								LLViewerObject* child_obj = *child_iter;
-								LLVOVolume *child = dynamic_cast<LLVOVolume*>( child_obj );
-								if (child)
-								{
-									attachment_children_cost += child->getRenderCost(textures);
-								}
-							}
+                const_child_list_t children = volume->getChildren();
+                for (const_child_list_t::const_iterator child_iter = children.begin();
+                    child_iter != children.end();
+                    ++child_iter)
+                {
+                    LLViewerObject* child_obj = *child_iter;
+                    LLVOVolume* child = dynamic_cast<LLVOVolume*>(child_obj);
+                    if (child)
+                    {
+                        attachment_children_cost += child->getRenderCost(textures);
+                    }
+                }
 
-							for (LLVOVolume::texture_cost_t::iterator volume_texture = textures.begin();
-								 volume_texture != textures.end();
-								 ++volume_texture)
-							{
-								// add the cost of each individual texture in the linkset
-								attachment_texture_cost += volume_texture->second;
-							}
-                            attachment_total_cost = attachment_volume_cost + attachment_texture_cost + attachment_children_cost;
-                            LL_DEBUGS("ARCdetail") << "Attachment costs " << attached_object->getAttachmentItemID()
-                                                   << " total: " << attachment_total_cost
-                                                   << ", volume: " << attachment_volume_cost
-                                                   << ", " << textures.size()
-                                                   << " textures: " << attachment_texture_cost
-                                                   << ", " << volume->numChildren()
-                                                   << " children: " << attachment_children_cost
-                                                   << LL_ENDL;
-                            // Limit attachment complexity to avoid signed integer flipping of the wearer's ACI
-                            cost += (U32)llclamp(attachment_total_cost, MIN_ATTACHMENT_COMPLEXITY, max_attachment_complexity);
-						}
-					}
-				}
-                if (isSelf()
-                    && attached_object
-                    && attached_object->isHUDAttachment()
-                    && !attached_object->isTempAttachment()
-                    && attached_object->mDrawable)
+                for (LLVOVolume::texture_cost_t::iterator volume_texture = textures.begin();
+                    volume_texture != textures.end();
+                    ++volume_texture)
                 {
-                    textures.clear();
-                    BOOL is_rigged_mesh = attached_object->isRiggedMesh();
+                    // add the cost of each individual texture in the linkset
+                    attachment_texture_cost += LLVOVolume::getTextureCost(*volume_texture);
+                }
+                attachment_total_cost = attachment_volume_cost + attachment_texture_cost + attachment_children_cost;
+                LL_DEBUGS("ARCdetail") << "Attachment costs " << attached_object->getAttachmentItemID()
+                    << " total: " << attachment_total_cost
+                    << ", volume: " << attachment_volume_cost
+                    << ", " << textures.size()
+                    << " textures: " << attachment_texture_cost
+                    << ", " << volume->numChildren()
+                    << " children: " << attachment_children_cost
+                    << LL_ENDL;
+                // Limit attachment complexity to avoid signed integer flipping of the wearer's ACI
+                cost += (U32)llclamp(attachment_total_cost, MIN_ATTACHMENT_COMPLEXITY, max_attachment_complexity);
+
+                if (isSelf())
+                {
+                    LLObjectComplexity object_complexity;
+                    object_complexity.objectName = attached_object->getAttachmentItemName();
+                    object_complexity.objectId = attached_object->getAttachmentItemID();
+                    object_complexity.objectCost = attachment_total_cost;
+                    object_complexity_list.push_back(object_complexity);
+                }
+            }
+        }
+    }
+    if (isSelf()
+        && attached_object
+        && attached_object->isHUDAttachment()
+        && !attached_object->isTempAttachment()
+        && attached_object->mDrawable)
+    {
+        textures.clear();
         mAttachmentSurfaceArea += attached_object->recursiveGetScaledSurfaceArea();
 
-                    const LLVOVolume* volume = attached_object->mDrawable->getVOVolume();
-                    if (volume)
-                    {
-                        LLHUDComplexity hud_object_complexity;
-                        hud_object_complexity.objectName = attached_object->getAttachmentItemName();
-                        hud_object_complexity.objectId = attached_object->getAttachmentItemID();
-                        std::string joint_name;
-                        gAgentAvatarp->getAttachedPointName(attached_object->getAttachmentItemID(), joint_name);
-                        hud_object_complexity.jointName = joint_name;
-                        // get cost and individual textures
-                        hud_object_complexity.objectsCost += volume->getRenderCost(textures);
-                        hud_object_complexity.objectsCount++;
-
-                        LLViewerObject::const_child_list_t& child_list = attached_object->getChildren();
-                        for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
-                            iter != child_list.end(); ++iter)
-                        {
-                            LLViewerObject* childp = *iter;
-                            is_rigged_mesh |= childp->isRiggedMesh();
-                            const LLVOVolume* chld_volume = dynamic_cast<LLVOVolume*>(childp);
-                            if (chld_volume)
-                            {
-                                // get cost and individual textures
-                                hud_object_complexity.objectsCost += chld_volume->getRenderCost(textures);
-                                hud_object_complexity.objectsCount++;
-                            }
-                        }
-                        if (is_rigged_mesh && !attached_object->mRiggedAttachedWarned)
-                        {
-                            LLSD args;                            
-                            LLViewerInventoryItem* itemp = gInventory.getItem(attached_object->getAttachmentItemID());
-                            args["NAME"] = itemp ? itemp->getName() : LLTrans::getString("Unknown");
-                            args["POINT"] = LLTrans::getString(getTargetAttachmentPoint(attached_object)->getName());
-                            LLNotificationsUtil::add("RiggedMeshAttachedToHUD", args);
+        const LLVOVolume* volume = attached_object->mDrawable->getVOVolume();
+        if (volume)
+        {
+            BOOL is_rigged_mesh = volume->isRiggedMeshFast();
+            LLHUDComplexity hud_object_complexity;
+            hud_object_complexity.objectName = attached_object->getAttachmentItemName();
+            hud_object_complexity.objectId = attached_object->getAttachmentItemID();
+            std::string joint_name;
+            gAgentAvatarp->getAttachedPointName(attached_object->getAttachmentItemID(), joint_name);
+            hud_object_complexity.jointName = joint_name;
+            // get cost and individual textures
+            hud_object_complexity.objectsCost += volume->getRenderCost(textures);
+            hud_object_complexity.objectsCount++;
+
+            LLViewerObject::const_child_list_t& child_list = attached_object->getChildren();
+            for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin();
+                iter != child_list.end(); ++iter)
+            {
+                LLViewerObject* childp = *iter;
+                const LLVOVolume* chld_volume = dynamic_cast<LLVOVolume*>(childp);
+                if (chld_volume)
+                {
+                    is_rigged_mesh = is_rigged_mesh || chld_volume->isRiggedMeshFast();
+                    // get cost and individual textures
+                    hud_object_complexity.objectsCost += chld_volume->getRenderCost(textures);
+                    hud_object_complexity.objectsCount++;
+                }
+            }
+            if (is_rigged_mesh && !attached_object->mRiggedAttachedWarned)
+            {
+                LLSD args;
+                LLViewerInventoryItem* itemp = gInventory.getItem(attached_object->getAttachmentItemID());
+                args["NAME"] = itemp ? itemp->getName() : LLTrans::getString("Unknown");
+                args["POINT"] = LLTrans::getString(getTargetAttachmentPoint(attached_object)->getName());
+                LLNotificationsUtil::add("RiggedMeshAttachedToHUD", args);
 
-                            attached_object->mRiggedAttachedWarned = true;
-                        }
+                attached_object->mRiggedAttachedWarned = true;
+            }
 
-                        hud_object_complexity.texturesCount += textures.size();
+            hud_object_complexity.texturesCount += textures.size();
 
-                        for (LLVOVolume::texture_cost_t::iterator volume_texture = textures.begin();
-                            volume_texture != textures.end();
-                            ++volume_texture)
-                        {
-                            // add the cost of each individual texture (ignores duplicates)
-                            hud_object_complexity.texturesCost += volume_texture->second;
-                            LLViewerFetchedTexture *tex = LLViewerTextureManager::getFetchedTexture(volume_texture->first);
-                            if (tex)
-                            {
-                                // Note: Texture memory might be incorect since texture might be still loading.
-                                hud_object_complexity.texturesMemoryTotal += tex->getTextureMemory();
-                                if (tex->getOriginalHeight() * tex->getOriginalWidth() >= HUD_OVERSIZED_TEXTURE_DATA_SIZE)
-                                {
-                                    hud_object_complexity.largeTexturesCount++;
-                                }
-                            }
-                        }
-                        hud_complexity_list.push_back(hud_object_complexity);
+            for (LLVOVolume::texture_cost_t::iterator volume_texture = textures.begin();
+                volume_texture != textures.end();
+                ++volume_texture)
+            {
+                // add the cost of each individual texture (ignores duplicates)
+                hud_object_complexity.texturesCost += LLVOVolume::getTextureCost(*volume_texture);
+                const LLViewerTexture* img = *volume_texture;
+                if (img->getType() == LLViewerTexture::FETCHED_TEXTURE)
+                {
+                    LLViewerFetchedTexture* tex = (LLViewerFetchedTexture*)img;
+                    // Note: Texture memory might be incorect since texture might be still loading.
+                    hud_object_complexity.texturesMemoryTotal += tex->getTextureMemory();
+                    if (tex->getOriginalHeight() * tex->getOriginalWidth() >= HUD_OVERSIZED_TEXTURE_DATA_SIZE)
+                    {
+                        hud_object_complexity.largeTexturesCount++;
                     }
                 }
+            }
+            hud_complexity_list.push_back(hud_object_complexity);
+        }
+    }
 }
 
 // Calculations for mVisualComplexity value
@@ -10776,19 +10949,22 @@ void LLVOAvatar::calculateUpdateRenderComplexity()
      * everyone. If you have suggested improvements, submit them to
      * the official viewer for consideration.
      *****************************************************************/
-	static const U32 COMPLEXITY_BODY_PART_COST = 200;
-	static LLCachedControl<F32> max_complexity_setting(gSavedSettings,"MaxAttachmentComplexity");
-	F32 max_attachment_complexity = max_complexity_setting;
-	max_attachment_complexity = llmax(max_attachment_complexity, DEFAULT_MAX_ATTACHMENT_COMPLEXITY);
-
-	// Diagnostic list of all textures on our avatar
-	static std::set<LLUUID> all_textures;
-
     if (mVisualComplexityStale)
 	{
+        LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
+
+        static const U32 COMPLEXITY_BODY_PART_COST = 200;
+        static LLCachedControl<F32> max_complexity_setting(gSavedSettings, "MaxAttachmentComplexity");
+        F32 max_attachment_complexity = max_complexity_setting;
+        max_attachment_complexity = llmax(max_attachment_complexity, DEFAULT_MAX_ATTACHMENT_COMPLEXITY);
+
+        // Diagnostic list of all textures on our avatar
+        static std::unordered_set<const LLViewerTexture*> all_textures;
+
 		U32 cost = VISUAL_COMPLEXITY_UNKNOWN;
 		LLVOVolume::texture_cost_t textures;
 		hud_complexity_list_t hud_complexity_list;
+        object_complexity_list_t object_complexity_list;
 
 		for (U8 baked_index = 0; baked_index < BAKED_NUM_INDICES; baked_index++)
 		{
@@ -10832,7 +11008,7 @@ void LLVOAvatar::calculateUpdateRenderComplexity()
             if (volp && !volp->isAttachment())
             {
                 accountRenderComplexityForObject(volp, max_attachment_complexity,
-                                                 textures, cost, hud_complexity_list);
+                                                 textures, cost, hud_complexity_list, object_complexity_list);
             }
         }
 
@@ -10848,45 +11024,7 @@ void LLVOAvatar::calculateUpdateRenderComplexity()
 			{
                 LLViewerObject* attached_object = attachment_iter->get();
                 accountRenderComplexityForObject(attached_object, max_attachment_complexity,
-                                                 textures, cost, hud_complexity_list);
-			}
-		}
-
-		// Diagnostic output to identify all avatar-related textures.
-		// Does not affect rendering cost calculation.
-		if (isSelf() && debugLoggingEnabled("ARCdetail"))
-		{
-			// print any attachment textures we didn't already know about.
-			for (LLVOVolume::texture_cost_t::iterator it = textures.begin(); it != textures.end(); ++it)
-			{
-				LLUUID image_id = it->first;
-				if( ! (image_id.isNull() || image_id == IMG_DEFAULT || image_id == IMG_DEFAULT_AVATAR)
-				   && (all_textures.find(image_id) == all_textures.end()))
-				{
-					// attachment texture not previously seen.
-					LL_DEBUGS("ARCdetail") << "attachment_texture: " << image_id.asString() << LL_ENDL;
-					all_textures.insert(image_id);
-				}
-			}
-
-			// print any avatar textures we didn't already know about
-		    for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = LLAvatarAppearance::getDictionary()->getTextures().begin();
-			 iter != LLAvatarAppearance::getDictionary()->getTextures().end();
-				 ++iter)
-			{
-			    const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second;
-				// TODO: MULTI-WEARABLE: handle multiple textures for self
-				const LLViewerTexture* te_image = getImage(iter->first,0);
-				if (!te_image)
-					continue;
-				LLUUID image_id = te_image->getID();
-				if( image_id.isNull() || image_id == IMG_DEFAULT || image_id == IMG_DEFAULT_AVATAR)
-					continue;
-				if (all_textures.find(image_id) == all_textures.end())
-				{
-					LL_DEBUGS("ARCdetail") << "local_texture: " << texture_dict->mName << ": " << image_id << LL_ENDL;
-					all_textures.insert(image_id);
-				}
+                                                 textures, cost, hud_complexity_list, object_complexity_list);
 			}
 		}
 
@@ -10913,10 +11051,25 @@ void LLVOAvatar::calculateUpdateRenderComplexity()
         {
             // Avatar complexity
             LLAvatarRenderNotifier::getInstance()->updateNotificationAgent(mVisualComplexity);
-
+            LLAvatarRenderNotifier::getInstance()->setObjectComplexityList(object_complexity_list);
             // HUD complexity
             LLHUDRenderNotifier::getInstance()->updateNotificationHUD(hud_complexity_list);
         }
+
+        //schedule an update to ART next frame if needed
+        if (LLPerfStats::tunables.userAutoTuneEnabled && 
+            LLPerfStats::tunables.userFPSTuningStrategy != LLPerfStats::TUNE_SCENE_ONLY &&
+            !isVisuallyMuted())
+        {
+            LLUUID id = getID(); // <== use id to make sure this avatar didn't get deleted between frames
+            LL::WorkQueue::getInstance("mainloop")->post([this, id]()
+                {
+                    if (gObjectList.findObject(id) != nullptr)
+                    {
+                        gPipeline.profileAvatar(this);
+                    }
+                });
+        }
     }
 }
 
@@ -11088,7 +11241,7 @@ LLVOAvatar::AvatarOverallAppearance LLVOAvatar::getOverallAppearance() const
 		{	// Always want to see this AV as an impostor
 			result = AOA_JELLYDOLL;
 		}
-		else if (isTooComplex())
+		else if (isTooComplex() || isTooSlow())
 		{
 			result = AOA_JELLYDOLL;
 		}
@@ -11115,7 +11268,7 @@ void LLVOAvatar::calcMutedAVColor()
         new_color = LLColor4::grey4;
         change_msg = " blocked: color is grey4";
     }
-    else if (!isTooComplex())
+    else if (!isTooComplex() && !isTooSlow())
     {
         new_color = LLColor4::white;
         change_msg = " simple imposter ";
@@ -11251,3 +11404,110 @@ BOOL LLVOAvatar::isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type,
 	// non-self avatars don't have wearables
 	return FALSE;
 }
+
+void LLVOAvatar::placeProfileQuery()
+{
+    if (mGPUTimerQuery == 0)
+    {
+        glGenQueries(1, &mGPUTimerQuery);
+    }
+
+    glBeginQuery(GL_TIME_ELAPSED, mGPUTimerQuery);
+}
+
+void LLVOAvatar::readProfileQuery(S32 retries)
+{
+    if (!mGPUProfilePending)
+    {
+        glEndQuery(GL_TIME_ELAPSED);
+        mGPUProfilePending = true;
+    }
+
+    GLuint64 result = 0;
+    glGetQueryObjectui64v(mGPUTimerQuery, GL_QUERY_RESULT_AVAILABLE, &result);
+
+    if (result == GL_TRUE || --retries <= 0)
+    { // query available, readback result
+        GLuint64 time_elapsed = 0;
+        glGetQueryObjectui64v(mGPUTimerQuery, GL_QUERY_RESULT, &time_elapsed);
+        mGPURenderTime = time_elapsed / 1000000.f;
+        mGPUProfilePending = false;
+
+        setDebugText(llformat("%d", (S32)(mGPURenderTime * 1000.f)));
+
+    }
+    else
+    { // wait until next frame
+        LLUUID id = getID();
+
+        LL::WorkQueue::getInstance("mainloop")->post([id, retries] {
+            LLVOAvatar* avatar = (LLVOAvatar*) gObjectList.findObject(id);
+            avatar->readProfileQuery(retries);
+            });
+    }
+}
+
+
+F32 LLVOAvatar::getGPURenderTime()
+{
+    return isVisuallyMuted() ? 0.f : mGPURenderTime;
+}
+
+// static
+F32 LLVOAvatar::getTotalGPURenderTime()
+{
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
+
+    F32 ret = 0.f;
+
+    for (LLCharacter* iter : LLCharacter::sInstances)
+    {
+        LLVOAvatar* inst = (LLVOAvatar*) iter;
+        ret += inst->getGPURenderTime();
+    }
+
+    return ret;
+}
+
+F32 LLVOAvatar::getMaxGPURenderTime()
+{
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
+
+    F32 ret = 0.f;
+
+    for (LLCharacter* iter : LLCharacter::sInstances)
+    {
+        LLVOAvatar* inst = (LLVOAvatar*)iter;
+        ret = llmax(inst->getGPURenderTime(), ret);
+    }
+
+    return ret;
+}
+
+F32 LLVOAvatar::getAverageGPURenderTime()
+{
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
+
+    F32 ret = 0.f;
+
+    S32 count = 0;
+
+    for (LLCharacter* iter : LLCharacter::sInstances)
+    {
+        LLVOAvatar* inst = (LLVOAvatar*)iter;
+        if (!inst->isTooSlow())
+        {
+            ret += inst->getGPURenderTime();
+            ++count;
+        }
+    }
+
+    if (count > 0)
+    {
+        ret /= count;
+    }
+
+    return ret;
+}
+
+
diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h
index 7cc333724303dacd872637a161dbff5f377a1da0..e8604464ae3d2def40dbcdca8e936f99db07fc86 100644
--- a/indra/newview/llvoavatar.h
+++ b/indra/newview/llvoavatar.h
@@ -299,13 +299,39 @@ class LLVOAvatar :
                                                      const F32 max_attachment_complexity,
                                                      LLVOVolume::texture_cost_t& textures,
                                                      U32& cost,
-                                                     hud_complexity_list_t& hud_complexity_list);
+                                                     hud_complexity_list_t& hud_complexity_list,
+                                                     object_complexity_list_t& object_complexity_list);
 	void			calculateUpdateRenderComplexity();
 	static const U32 VISUAL_COMPLEXITY_UNKNOWN;
 	void			updateVisualComplexity();
 	
-	U32				getVisualComplexity()			{ return mVisualComplexity;				};		// Numbers calculated here by rendering AV
-	F32				getAttachmentSurfaceArea()		{ return mAttachmentSurfaceArea;		};		// estimated surface area of attachments
+    void placeProfileQuery();
+    void readProfileQuery(S32 retries);
+
+    // get the GPU time in ms of rendering this avatar including all attachments
+    // returns 0.f if this avatar has not been profiled using gPipeline.profileAvatar
+    // or the avatar is visually muted
+    F32             getGPURenderTime();
+
+    // get the total GPU render time in ms of all avatars that have been benched
+    static F32      getTotalGPURenderTime();
+
+    // get the max GPU render time in ms of all avatars that have been benched
+    static F32      getMaxGPURenderTime();
+
+    // get the average GPU render time in ms of all avatars that have been benched
+    static F32      getAverageGPURenderTime();
+
+    // get the CPU time in ms of rendering this avatar including all attachments
+    // return 0.f if this avatar has not been profiled using gPipeline.mProfileAvatar
+    F32             getCPURenderTime() { return mCPURenderTime; }
+
+    
+    // avatar render cost
+	U32				getVisualComplexity()			{ return mVisualComplexity;				};
+
+    // surface area calculation
+	F32				getAttachmentSurfaceArea()		{ return mAttachmentSurfaceArea;		};
 
 	U32				getReportedVisualComplexity()					{ return mReportedVisualComplexity;				};	// Numbers as reported by the SL server
 	void			setReportedVisualComplexity(U32 value)			{ mReportedVisualComplexity = value;			};
@@ -316,6 +342,8 @@ class LLVOAvatar :
 
 	void 			idleUpdateBelowWater();
 
+	static void updateNearbyAvatarCount();
+
 	//--------------------------------------------------------------------
 	// Static preferences (controlled by user settings/menus)
 	//--------------------------------------------------------------------
@@ -340,6 +368,9 @@ class LLVOAvatar :
 
     static LLPointer<LLViewerTexture>  sCloudTexture;
 
+	static std::vector<LLUUID> sAVsIgnoringARTLimit;
+    static S32 sAvatarsNearby;
+
 	//--------------------------------------------------------------------
 	// Region state
 	//--------------------------------------------------------------------
@@ -351,6 +382,16 @@ class LLVOAvatar :
 	//--------------------------------------------------------------------
 public:
 	BOOL			isFullyLoaded() const;
+
+    // check and return current state relative to limits
+    // default will test only the geometry (combined=false).
+    // this allows us to disable shadows separately on complex avatars.
+
+    inline bool 	isTooSlowWithoutShadows() const {return mTooSlowWithoutShadows;};
+    bool 	isTooSlow() const;
+
+    void 			updateTooSlow();
+
 	bool 			isTooComplex() const;
 	bool 			visualParamWeightsAreDefault();
 	virtual bool	getIsCloud() const;
@@ -370,7 +411,7 @@ class LLVOAvatar :
 	void 			logMetricsTimerRecord(const std::string& phase_name, F32 elapsed, bool completed);
 
     void            calcMutedAVColor();
-
+    
 protected:
 	LLViewerStats::PhaseMap& getPhases() { return mPhases; }
 	BOOL			updateIsFullyLoaded();
@@ -391,6 +432,12 @@ class LLVOAvatar :
 	LLFrameTimer	mFullyLoadedTimer;
 	LLFrameTimer	mRuthTimer;
 
+    // variables to hold "slowness" status
+    bool			mTooSlow{false};
+    bool			mTooSlowWithoutShadows{false};
+
+    bool            mTuned{false};
+
 private:
 	LLViewerStats::PhaseMap mPhases;
 
@@ -495,6 +542,7 @@ class LLVOAvatar :
 	S32			mSpecialRenderMode; // special lighting
         
 private:
+    friend class LLPipeline;
 	AvatarOverallAppearance mOverallAppearance;
 	F32			mAttachmentSurfaceArea; //estimated surface area of attachments
     U32			mAttachmentVisibleTriangleCount;
@@ -507,7 +555,20 @@ class LLVOAvatar :
 	S32	 		mUpdatePeriod;
 	S32  		mNumInitFaces; //number of faces generated when creating the avatar drawable, does not inculde splitted faces due to long vertex buffer.
 
+    // profile handle
+    U32 mGPUTimerQuery = 0;
+
+    // profile results
+
+    // GPU render time in ms
+    F32 mGPURenderTime = 0.f;
+    bool mGPUProfilePending = false;
+
+    // CPU render time in ms
+    F32 mCPURenderTime = 0.f;
+
 	// the isTooComplex method uses these mutable values to avoid recalculating too frequently
+    // DEPRECATED -- obsolete avatar render cost values
 	mutable U32  mVisualComplexity;
 	mutable bool mVisualComplexityStale;
 	U32          mReportedVisualComplexity; // from other viewers through the simulator
@@ -623,7 +684,7 @@ class LLVOAvatar :
 	//--------------------------------------------------------------------
 public:
 	virtual LLViewerTexture::EBoostLevel 	getAvatarBoostLevel() const { return LLGLTexture::BOOST_AVATAR; }
-	virtual LLViewerTexture::EBoostLevel 	getAvatarBakedBoostLevel() const { return LLGLTexture::BOOST_AVATAR_BAKED; }
+	virtual LLViewerTexture::EBoostLevel 	getAvatarBakedBoostLevel() const { return LLGLTexture::BOOST_AVATAR_BAKED_SELF; }
 	virtual S32 						getTexImageSize() const;
 	/*virtual*/ S32						getTexImageArea() const { return getTexImageSize()*getTexImageSize(); }
 
diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp
index d8b82d31142ab2d175296f68c2773edea1c69e61..82dfb1ca2a0d06bb1ba48a8f00de707da766244f 100644
--- a/indra/newview/llvoavatarself.cpp
+++ b/indra/newview/llvoavatarself.cpp
@@ -1155,6 +1155,7 @@ LLViewerObject* LLVOAvatarSelf::getWornAttachment(const LLUUID& inv_item_id)
 
 bool LLVOAvatarSelf::getAttachedPointName(const LLUUID& inv_item_id, std::string& name) const
 {
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
 	if (!gInventory.getItem(inv_item_id))
 	{
 		name = "ATTACHMENT_MISSING_ITEM";
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index a73d149b105148e92ecbcb654737cf50473b3747..aa60578cee75940cf063901f7b665a3b269799d7 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -1603,7 +1603,9 @@ BOOL LLVOVolume::updateLOD()
 	{
 		return FALSE;
 	}
-	
+
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME;
+
 	BOOL lod_changed = FALSE;
 
 	if (!LLSculptIDSize::instance().isUnloaded(getVolume()->getParams().getSculptID())) 
@@ -1617,16 +1619,6 @@ BOOL LLVOVolume::updateLOD()
 
 	if (lod_changed)
 	{
-        if (debugLoggingEnabled("AnimatedObjectsLinkset"))
-        {
-            if (isAnimatedObject() && isRiggedMesh())
-            {
-                std::string vobj_name = llformat("Vol%p", this);
-                F32 est_tris = getEstTrianglesMax();
-                LL_DEBUGS("AnimatedObjectsLinkset") << vobj_name << " updateLOD to " << getLOD() << ", tris " << est_tris << LL_ENDL; 
-            }
-        }
-
 		gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME, FALSE);
 		mLODChanged = TRUE;
 	}
@@ -3198,7 +3190,13 @@ void LLVOVolume::setLightCutoff(F32 cutoff)
 
 BOOL LLVOVolume::getIsLight() const
 {
-	return getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT);
+    mIsLight = getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT);
+    return mIsLight;
+}
+
+bool LLVOVolume::getIsLightFast() const
+{
+    return mIsLight;
 }
 
 LLColor3 LLVOVolume::getLightSRGBBaseColor() const
@@ -3584,6 +3582,31 @@ BOOL LLVOVolume::hasLightTexture() const
 	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)
@@ -3744,8 +3767,8 @@ bool LLVOVolume::canBeAnimatedObject() const
 bool LLVOVolume::isAnimatedObject() const
 {
     LLVOVolume *root_vol = (LLVOVolume*)getRootEdit();
-    bool root_is_animated_flag = root_vol->getExtendedMeshFlags() & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG;
-    return root_is_animated_flag;
+    mIsAnimatedObject = root_vol->getExtendedMeshFlags() & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG;
+    return mIsAnimatedObject;
 }
 
 // Called any time parenting changes for a volume. Update flags and
@@ -3932,12 +3955,41 @@ const LLMatrix4 LLVOVolume::getRenderMatrix() const
 	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<const LLViewerFetchedTexture*>(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
@@ -3947,17 +3999,16 @@ U32 LLVOVolume::getRenderCost(texture_cost_t &textures) const
 
 	// 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).
-	BOOL has_volume = (getVolume() != NULL);
-	LLVolumeParams volume_params;
-	LLPathParams path_params;
-	LLProfileParams profile_params;
+    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_TEXTURE_COST = 16; // multiplier for texture resolution - performance tested
 	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 
 
@@ -3992,45 +4043,41 @@ U32 LLVOVolume::getRenderCost(texture_cost_t &textures) const
 	const LLDrawable* drawablep = mDrawable;
 	U32 num_faces = drawablep->getNumFaces();
 
-	if (has_volume)
-	{
-		volume_params = getVolume()->getParams();
-		path_params = volume_params.getPathParams();
-		profile_params = volume_params.getProfileParams();
+	const LLVolumeParams& volume_params = getVolume()->getParams();
 
-        LLMeshCostData costs;
-		if (getCostData(costs))
-		{
-            if (isAnimatedObject() && isRiggedMesh())
-            {
-                // 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);
-            }
-		}
+    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 (isSculpted())
+	if (isSculptedFast())
 	{
-		if (isMesh())
+		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 (isRiggedMesh())
+				if (isRiggedMeshFast())
 				{
 					// weighted attachment - 1 point for every 3 bytes
 					weighted_mesh = 1;
@@ -4044,21 +4091,15 @@ U32 LLVOVolume::getRenderCost(texture_cost_t &textures) const
 		}
 		else
 		{
-			const LLSculptParams *sculpt_params = (LLSculptParams *) getParameterEntry(LLNetworkData::PARAMS_SCULPT);
-			LLUUID sculpt_id = sculpt_params->getSculptTexture();
-			if (textures.find(sculpt_id) == textures.end())
+            LLViewerFetchedTexture* texture = mSculptTexture;
+			if (texture && textures.find(texture) == textures.end())
 			{
-				LLViewerFetchedTexture *texture = LLViewerTextureManager::getFetchedTexture(sculpt_id);
-				if (texture)
-				{
-					S32 texture_cost = 256 + (S32)(ARC_TEXTURE_COST * (texture->getFullHeight() / 128.f + texture->getFullWidth() / 128.f));
-					textures.insert(texture_cost_t::value_type(sculpt_id, texture_cost));
-				}
+                textures.insert(texture);
 			}
 		}
 	}
 
-	if (isFlexible())
+	if (isFlexibleFast())
 	{
 		flexi = 1;
 	}
@@ -4067,85 +4108,66 @@ U32 LLVOVolume::getRenderCost(texture_cost_t &textures) const
 		particles = 1;
 	}
 
-	if (getIsLight())
+	if (getIsLightFast())
 	{
 		produces_light = 1;
 	}
 
-	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();
+    {
+        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)
-		{
-			if (textures.find(img->getID()) == textures.end())
-			{
-                S32 texture_cost = 0;
-                S8 type = img->getType();
-                if (type == LLViewerTexture::FETCHED_TEXTURE || type == LLViewerTexture::LOD_TEXTURE)
+            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())
                 {
-                    const LLViewerFetchedTexture* fetched_texturep = static_cast<const LLViewerFetchedTexture*>(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;
-                    }
+                    // bump is a multiplier, don't add per-face
+                    bump = 1;
                 }
-                if (texture_cost == 0)
+                if (te->getShiny())
                 {
-                    texture_cost = 256 + (S32)(ARC_TEXTURE_COST * (img->getFullHeight() / 128.f + img->getFullWidth() / 128.f));
+                    // shiny is a multiplier, don't add per-face
+                    shiny = 1;
                 }
-				textures.insert(texture_cost_t::value_type(img->getID(), texture_cost));
-			}
-		}
-
-		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;
-			}
-		}
-	}
+                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;
@@ -4224,7 +4246,7 @@ U32 LLVOVolume::getRenderCost(texture_cost_t &textures) const
     // 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 (isAnimatedObject() && isRootEdit())
+    if (isAnimatedObjectFast() && isRootEdit())
     {
         shame += (ANIMATED_OBJECT_BASE_COST/0.06) * 5.0f;
     }
@@ -4239,7 +4261,7 @@ U32 LLVOVolume::getRenderCost(texture_cost_t &textures) const
 
 F32 LLVOVolume::getEstTrianglesMax() const
 {
-	if (isMesh() && getVolume())
+	if (isMeshFast() && getVolume())
 	{
 		return gMeshRepo.getEstTrianglesMax(getVolume()->getParams().getSculptID());
 	}
@@ -4248,7 +4270,7 @@ F32 LLVOVolume::getEstTrianglesMax() const
 
 F32 LLVOVolume::getEstTrianglesStreamingCost() const
 {
-	if (isMesh() && getVolume())
+	if (isMeshFast() && getVolume())
 	{
 		return gMeshRepo.getEstTrianglesStreamingCost(getVolume()->getParams().getSculptID());
 	}
@@ -4263,7 +4285,7 @@ F32 LLVOVolume::getStreamingCost() const
     LLMeshCostData costs;
     if (getCostData(costs))
     {
-        if (isAnimatedObject() && isRootEdit())
+        if (isRootEdit() && isAnimatedObject())
         {
             // Root object of an animated object has this to account for skeleton overhead.
             linkset_base_cost = ANIMATED_OBJECT_BASE_COST;
@@ -4293,7 +4315,9 @@ F32 LLVOVolume::getStreamingCost() const
 // virtual
 bool LLVOVolume::getCostData(LLMeshCostData& costs) const
 {
-    if (isMesh())
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME;
+
+    if (isMeshFast())
     {
         return gMeshRepo.getCostData(getVolume()->getParams().getSculptID(), costs);
     }
@@ -4303,11 +4327,11 @@ bool LLVOVolume::getCostData(LLMeshCostData& costs) const
 		S32 counts[4];
 		LLVolume::getLoDTriangleCounts(volume->getParams(), counts);
 
-		LLSD header;
-		header["lowest_lod"]["size"] = counts[0] * 10;
-		header["low_lod"]["size"] = counts[1] * 10;
-		header["medium_lod"]["size"] = counts[2] * 10;
-		header["high_lod"]["size"] = counts[3] * 10;
+        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);
     }
@@ -4532,16 +4556,6 @@ const LLMatrix4& LLVOVolume::getWorldMatrix(LLXformMatrix* xform) const
 
 void LLVOVolume::markForUpdate(BOOL priority)
 { 
-    if (debugLoggingEnabled("AnimatedObjectsLinkset"))
-    {
-        if (isAnimatedObject() && isRiggedMesh())
-        {
-            std::string vobj_name = llformat("Vol%p", this);
-            F32 est_tris = getEstTrianglesMax();
-            LL_DEBUGS("AnimatedObjectsLinkset") << vobj_name << " markForUpdate, tris " << est_tris << LL_ENDL; 
-        }
-    }
-
     if (mDrawable)
     {
         shrinkWrap();
@@ -5454,7 +5468,7 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep,
 			}
 		}
 		
-		if (type == LLRenderPass::PASS_ALPHA)
+		// if (type == LLRenderPass::PASS_ALPHA) // always populate the draw_info ptr
 		{ //for alpha sorting
 			facep->setDrawInfo(draw_info);
 		}
@@ -5826,7 +5840,7 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group)
 					const LLTextureEntry* te = facep->getTextureEntry();
 					LLViewerTexture* tex = facep->getTexture();
 
-					if (te->getGlow() >= 1.f/255.f)
+					if (te->getGlow() > 0.f)
 					{
 						emissive = true;
 					}
@@ -5910,7 +5924,7 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group)
 						{
                             LLGLTFMaterial* gltf_mat = te->getGLTFRenderMaterial();
 
-							if (gltf_mat != nullptr || (te->getMaterialParams().notNull()  && !te->getMaterialID().isNull()))
+							if (gltf_mat != nullptr || (te->getMaterialParams().notNull()))
 							{
                                 if (gltf_mat != nullptr)
                                 {
@@ -6088,7 +6102,7 @@ void LLVolumeGeometryManager::rebuildMesh(LLSpatialGroup* group)
 
 			U32 buffer_count = 0;
 
-			for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter)
+            for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter)
 			{
 				LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable();
 
@@ -6097,16 +6111,7 @@ void LLVolumeGeometryManager::rebuildMesh(LLSpatialGroup* group)
 					LLVOVolume* vobj = drawablep->getVOVolume();
 					
 					if (!vobj) continue;
-					
-					if (debugLoggingEnabled("AnimatedObjectsLinkset"))
-					{
-						if (vobj->isAnimatedObject() && vobj->isRiggedMesh())
-						{
-							std::string vobj_name = llformat("Vol%p", vobj);
-							F32 est_tris = vobj->getEstTrianglesMax();
-							LL_DEBUGS("AnimatedObjectsLinkset") << vobj_name << " rebuildMesh, tris " << est_tris << LL_ENDL;
-						}
-					}
+
 					if (vobj->isNoLOD()) continue;
 
 					vobj->preRebuild();
@@ -6466,10 +6471,11 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace
 		U32 indices_index = 0;
 		U16 index_offset = 0;
 
-		while (face_iter < i)
+        while (face_iter < i)
 		{
 			//update face indices for new buffer
 			facep = *face_iter;
+
 			if (buffer.isNull())
 			{
 				// Bulk allocation failed
diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h
index 9cfd90a940fbf3c8346e6d637a32c46aefad62f8..d509a7e2ab7764637fcf328d78030d87f3e33690 100644
--- a/indra/newview/llvovolume.h
+++ b/indra/newview/llvovolume.h
@@ -34,8 +34,8 @@
 #include "lllocalbitmaps.h"
 #include "m3math.h"		// LLMatrix3
 #include "m4math.h"		// LLMatrix4
-#include <map>
-#include <set>
+#include <unordered_map>
+#include <unordered_set>
 
 
 class LLViewerTextureAnim;
@@ -146,7 +146,8 @@ class LLVOVolume : public LLViewerObject
 	const LLMatrix4&	getRelativeXform() const				{ return mRelativeXform; }
 	const LLMatrix3&	getRelativeXformInvTrans() const		{ return mRelativeXformInvTrans; }
 	/*virtual*/	const LLMatrix4	getRenderMatrix() const override;
-				typedef std::map<LLUUID, S32> texture_cost_t;
+				typedef std::unordered_set<const LLViewerTexture*> texture_cost_t;
+                static S32 getTextureCost(const LLViewerTexture* img);
 				U32 	getRenderCost(texture_cost_t &textures) const;
     /*virtual*/	F32		getEstTrianglesMax() const override;
     /*virtual*/	F32		getEstTrianglesStreamingCost() const override;
@@ -267,6 +268,7 @@ class LLVOVolume : public LLViewerObject
 	void setSpotLightParams(LLVector3 params);
 
 	BOOL getIsLight() const;
+    bool getIsLightFast() const;
 
 
     // Get the light color in sRGB color space NOT scaled by intensity.
@@ -315,7 +317,15 @@ class LLVOVolume : public LLViewerObject
 	virtual BOOL isRiggedMesh() const override;
 	virtual BOOL hasLightTexture() const override;
 
-    
+    // fast variants above that use state that is filled in later
+    //  not reliable early in the life of an object, but should be used after
+    //  object is loaded
+    bool isFlexibleFast() const;
+    bool isSculptedFast() const;
+    bool isMeshFast() const;
+    bool isRiggedMeshFast() const;
+    bool isAnimatedObjectFast() const;
+
 	BOOL isVolumeGlobal() const;
 	BOOL canBeFlexible() const;
 	BOOL setIsFlexible(BOOL is_flexible);
@@ -461,6 +471,13 @@ class LLVOVolume : public LLViewerObject
 	S32 mIndexInTex[LLRender::NUM_VOLUME_TEXTURE_CHANNELS];
 	S32 mMDCImplCount;
 
+    // cached value of getIsLight to avoid redundant map lookups
+    // accessed by getIsLightFast
+    mutable bool mIsLight = false;
+
+    // cached value of getIsAnimatedObject to avoid redundant map lookups
+    // accessed by getIsAnimatedObjectFast
+    mutable bool mIsAnimatedObject = false;
 	bool mResetDebugText;
 
 	LLPointer<LLRiggedVolume> mRiggedVolume;
@@ -475,7 +492,6 @@ class LLVOVolume : public LLViewerObject
 
 	static LLPointer<LLObjectMediaDataClient> sObjectMediaClient;
 	static LLPointer<LLObjectMediaNavigateClient> sObjectMediaNavigateClient;
-
 protected:
 	static S32 sNumLODChanges;
 
diff --git a/indra/newview/llworld.cpp b/indra/newview/llworld.cpp
index 9fa3d18a4016d481649ee254d999b99a8c164213..0e0dbdc07174fe5a335bd60c7a076ad08e7dcdae 100644
--- a/indra/newview/llworld.cpp
+++ b/indra/newview/llworld.cpp
@@ -33,6 +33,7 @@
 #include "llstl.h"
 
 #include "llagent.h"
+#include "llagentcamera.h"
 #include "llviewercontrol.h"
 #include "lldrawpool.h"
 #include "llglheaders.h"
@@ -1423,6 +1424,36 @@ void LLWorld::getAvatars(uuid_vec_t* avatar_ids, std::vector<LLVector3d>* positi
 	}
 }
 
+F32 LLWorld::getNearbyAvatarsAndMaxGPUTime(std::vector<LLCharacter*> &valid_nearby_avs)
+{
+    static LLCachedControl<F32> render_far_clip(gSavedSettings, "RenderFarClip", 64);
+    F32 nearby_max_complexity = 0;
+    F32 radius = render_far_clip * render_far_clip;
+    std::vector<LLCharacter*>::iterator char_iter = LLCharacter::sInstances.begin();
+    while (char_iter != LLCharacter::sInstances.end())
+    {
+        LLVOAvatar* avatar = dynamic_cast<LLVOAvatar*>(*char_iter);
+        if (avatar && !avatar->isDead() && !avatar->isControlAvatar())
+        {
+            if ((dist_vec_squared(avatar->getPositionGlobal(), gAgent.getPositionGlobal()) > radius) &&
+                (dist_vec_squared(avatar->getPositionGlobal(), gAgentCamera.getCameraPositionGlobal()) > radius))
+            {
+                char_iter++;
+                continue;
+            }
+
+            if (!avatar->isTooSlow())
+            {
+                gPipeline.profileAvatar(avatar);
+            }
+            nearby_max_complexity = llmax(nearby_max_complexity, avatar->getGPURenderTime());
+            valid_nearby_avs.push_back(*char_iter);
+        }
+        char_iter++;
+    }
+    return nearby_max_complexity;
+}
+
 bool LLWorld::isRegionListed(const LLViewerRegion* region) const
 {
 	region_list_t::const_iterator it = find(mRegionList.begin(), mRegionList.end(), region);
diff --git a/indra/newview/llworld.h b/indra/newview/llworld.h
index 5dee8eea0fc767a4afbd1e20d9b502ede7bf9af2..f78cbcaa4852e4390597d4e2ccf794bf34edc033 100644
--- a/indra/newview/llworld.h
+++ b/indra/newview/llworld.h
@@ -51,6 +51,7 @@ class LLHost;
 class LLViewerObject;
 class LLSurfacePatch;
 
+class LLCharacter;
 class LLCloudPuff;
 class LLCloudGroup;
 class LLVOAvatar;
@@ -171,6 +172,10 @@ class LLWorld : public LLSimpleton<LLWorld>
 	// or if the circuit to this simulator had been lost.
 	bool isRegionListed(const LLViewerRegion* region) const;
 
+    // profile nearby avatars using gPipeline.profileAvatar and update their render times
+    // return max GPU time
+    F32 getNearbyAvatarsAndMaxGPUTime(std::vector<LLCharacter*> &valid_nearby_avs);
+
 private:
     void clearHoleWaterObjects();
     void clearEdgeWaterObjects();
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index 283be64af4baa8cf292168db4cdecc63330f3450..327a51de8570d23725bb50b713140da7ea23696c 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -43,6 +43,7 @@
 #include "llui.h" 
 #include "llglheaders.h"
 #include "llrender.h"
+#include "llstartup.h"
 #include "llwindow.h"	// swapBuffers()
 
 // newview includes
@@ -127,6 +128,7 @@ U32 LLPipeline::RenderFSAASamples;
 U32 LLPipeline::RenderResolutionDivisor;
 bool LLPipeline::RenderUIBuffer;
 S32 LLPipeline::RenderShadowDetail;
+S32 LLPipeline::RenderShadowSplits;
 bool LLPipeline::RenderDeferredSSAO;
 F32 LLPipeline::RenderShadowResolutionScale;
 bool LLPipeline::RenderLocalLights;
@@ -200,6 +202,7 @@ LLTrace::EventStatHandle<S64> LLPipeline::sStatBatchSize("renderbatchsize");
 
 const F32 BACKLIGHT_DAY_MAGNITUDE_OBJECT = 0.1f;
 const F32 BACKLIGHT_NIGHT_MAGNITUDE_OBJECT = 0.08f;
+const F32 ALPHA_BLEND_CUTOFF = 0.598f;
 const F32 DEFERRED_LIGHT_FALLOFF = 0.5f;
 const U32 DEFERRED_VB_MASK = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_TEXCOORD1;
 
@@ -486,6 +489,7 @@ void LLPipeline::init()
 	connectRefreshCachedSettingsSafe("RenderResolutionDivisor");
 	connectRefreshCachedSettingsSafe("RenderUIBuffer");
 	connectRefreshCachedSettingsSafe("RenderShadowDetail");
+    connectRefreshCachedSettingsSafe("RenderShadowSplits");
 	connectRefreshCachedSettingsSafe("RenderDeferredSSAO");
 	connectRefreshCachedSettingsSafe("RenderShadowResolutionScale");
 	connectRefreshCachedSettingsSafe("RenderLocalLights");
@@ -559,7 +563,6 @@ void LLPipeline::init()
 
 LLPipeline::~LLPipeline()
 {
-
 }
 
 void LLPipeline::cleanup()
@@ -971,10 +974,11 @@ void LLPipeline::refreshCachedSettings()
     WindLightUseAtmosShaders = TRUE; // DEPRECATED -- gSavedSettings.getBOOL("WindLightUseAtmosShaders");
     RenderDeferred = TRUE; // DEPRECATED -- gSavedSettings.getBOOL("RenderDeferred");
 	RenderDeferredSunWash = gSavedSettings.getF32("RenderDeferredSunWash");
-	RenderFSAASamples = gSavedSettings.getU32("RenderFSAASamples");
+	RenderFSAASamples = LLFeatureManager::getInstance()->isFeatureAvailable("RenderFSAASamples") ? gSavedSettings.getU32("RenderFSAASamples") : 0;
 	RenderResolutionDivisor = gSavedSettings.getU32("RenderResolutionDivisor");
 	RenderUIBuffer = gSavedSettings.getBOOL("RenderUIBuffer");
 	RenderShadowDetail = gSavedSettings.getS32("RenderShadowDetail");
+    RenderShadowSplits = gSavedSettings.getS32("RenderShadowSplits");
 	RenderDeferredSSAO = gSavedSettings.getBOOL("RenderDeferredSSAO");
 	RenderShadowResolutionScale = gSavedSettings.getF32("RenderShadowResolutionScale");
 	RenderLocalLights = gSavedSettings.getBOOL("RenderLocalLights");
@@ -2250,12 +2254,18 @@ bool LLPipeline::getVisibleExtents(LLCamera& camera, LLVector3& min, LLVector3&
 
 static LLTrace::BlockTimerStatHandle FTM_CULL("Object Culling");
 
+// static
+bool LLPipeline::isWaterClip()
+{
+	return (!sRenderTransparentWater || gCubeSnapshot) && !sRenderingHUDs;
+}
+
 void LLPipeline::updateCull(LLCamera& camera, LLCullResult& result)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; //LL_RECORD_BLOCK_TIME(FTM_CULL);
     LL_PROFILE_GPU_ZONE("updateCull"); // should always be zero GPU time, but drop a timer to flush stuff out
 
-    bool water_clip = !sRenderTransparentWater && !sRenderingHUDs;
+	bool water_clip = isWaterClip();
 
     if (water_clip)
     {
@@ -3027,18 +3037,6 @@ void LLPipeline::markRebuild(LLDrawable *drawablep, LLDrawable::EDrawableFlags f
 {
 	if (drawablep && !drawablep->isDead() && assertInitialized())
 	{
-        if (debugLoggingEnabled("AnimatedObjectsLinkset"))
-        {
-            LLVOVolume *vol_obj = drawablep->getVOVolume();
-            if (vol_obj && vol_obj->isAnimatedObject() && vol_obj->isRiggedMesh())
-            {
-                std::string vobj_name = llformat("Vol%p", vol_obj);
-                F32 est_tris = vol_obj->getEstTrianglesMax();
-                LL_DEBUGS("AnimatedObjectsLinkset") << vobj_name << " markRebuild, tris " << est_tris 
-                                                    << " priority " << (S32) priority << " flag " << std::hex << flag << LL_ENDL; 
-            }
-        }
-    
 		if (!drawablep->isState(LLDrawable::BUILT))
 		{
 			priority = true;
@@ -5492,7 +5490,7 @@ void LLPipeline::calcNearbyLights(LLCamera& camera)
 			LLDrawable* drawable = light->drawable;
             const LLViewerObject *vobj = light->drawable->getVObj();
             if(vobj && vobj->getAvatar() 
-               && (vobj->getAvatar()->isTooComplex() || vobj->getAvatar()->isInMuteList())
+               && (vobj->getAvatar()->isTooComplex() || vobj->getAvatar()->isInMuteList() || vobj->getAvatar()->isTooSlow())
                )
             {
                 drawable->clearState(LLDrawable::NEARBY_LIGHT);
@@ -5571,7 +5569,7 @@ void LLPipeline::calcNearbyLights(LLCamera& camera)
 				continue;
 			}
             LLVOAvatar * av = light->getAvatar();
-            if (av && (av->isTooComplex() || av->isInMuteList()))
+            if (av && (av->isTooComplex() || av->isInMuteList() || av->isTooSlow()))
             {
                 // avatars that are already in the list will be removed by removeMutedAVsLights
                 continue;
@@ -6776,6 +6774,8 @@ void LLPipeline::renderAlphaObjects(bool rigged)
     assertInitialized();
     gGL.loadMatrix(gGLModelView);
     gGLLastMatrix = NULL;
+    S32 sun_up = LLEnvironment::instance().getIsSunUp() ? 1 : 0;
+    U32 target_width = LLRenderTarget::sCurResX;
     U32 type = LLRenderPass::PASS_ALPHA;
     LLVOAvatar* lastAvatar = nullptr;
     U64 lastMeshId = 0;
@@ -6798,10 +6798,18 @@ void LLPipeline::renderAlphaObjects(bool rigged)
         {
             if (pparams->mGLTFMaterial)
             {
+                gDeferredShadowGLTFAlphaMaskProgram.bind(rigged);
+                LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up);
+                LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width);
+                LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(ALPHA_BLEND_CUTOFF);
                 mSimplePool->pushRiggedGLTFBatch(*pparams, lastAvatar, lastMeshId);
             }
             else
             {
+                gDeferredShadowAlphaMaskProgram.bind(rigged);
+                LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up);
+                LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width);
+                LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(ALPHA_BLEND_CUTOFF);
                 if (lastAvatar != pparams->mAvatar || lastMeshId != pparams->mSkinInfo->mHash)
                 {
                     mSimplePool->uploadMatrixPalette(*pparams);
@@ -6809,18 +6817,26 @@ void LLPipeline::renderAlphaObjects(bool rigged)
                     lastMeshId = pparams->mSkinInfo->mHash;
                 }
 
-                mSimplePool->pushBatch(*pparams, true, true, true);
+                mSimplePool->pushBatch(*pparams, true, true);
             }
         }
         else
         {
             if (pparams->mGLTFMaterial)
             {
+                gDeferredShadowGLTFAlphaMaskProgram.bind(rigged);
+                LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up);
+                LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width);
+                LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(ALPHA_BLEND_CUTOFF);
                 mSimplePool->pushGLTFBatch(*pparams);
             }
             else
             {
-                mSimplePool->pushBatch(*pparams, true, true, true);
+                gDeferredShadowAlphaMaskProgram.bind(rigged);
+                LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up);
+                LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width);
+                LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(ALPHA_BLEND_CUTOFF);
+                mSimplePool->pushBatch(*pparams, true, true);
             }
         }
     }
@@ -6837,11 +6853,11 @@ void LLPipeline::renderMaskedObjects(U32 type, bool texture, bool batch_texture,
 	gGLLastMatrix = NULL;
     if (rigged)
     {
-        mAlphaMaskPool->pushRiggedMaskBatches(type+1, texture, batch_texture, true);
+        mAlphaMaskPool->pushRiggedMaskBatches(type+1, texture, batch_texture);
     }
     else
     {
-        mAlphaMaskPool->pushMaskBatches(type, texture, batch_texture, true);
+        mAlphaMaskPool->pushMaskBatches(type, texture, batch_texture);
     }
 	gGL.loadMatrix(gGLModelView);
 	gGLLastMatrix = NULL;		
@@ -6855,11 +6871,11 @@ void LLPipeline::renderFullbrightMaskedObjects(U32 type, bool texture, bool batc
 	gGLLastMatrix = NULL;
     if (rigged)
     {
-        mFullbrightAlphaMaskPool->pushRiggedMaskBatches(type+1, texture, batch_texture, true);
+        mFullbrightAlphaMaskPool->pushRiggedMaskBatches(type+1, texture, batch_texture);
     }
     else
     {
-        mFullbrightAlphaMaskPool->pushMaskBatches(type, texture, batch_texture, true);
+        mFullbrightAlphaMaskPool->pushMaskBatches(type, texture, batch_texture);
     }
 	gGL.loadMatrix(gGLModelView);
 	gGLLastMatrix = NULL;		
@@ -6927,7 +6943,8 @@ void LLPipeline::bindScreenToTexture()
 
 static LLTrace::BlockTimerStatHandle FTM_RENDER_BLOOM("Bloom");
 
-void LLPipeline::visualizeBuffers(LLRenderTarget* src, LLRenderTarget* dst, U32 bufferIndex) {
+void LLPipeline::visualizeBuffers(LLRenderTarget* src, LLRenderTarget* dst, U32 bufferIndex)
+{
 	dst->bindTarget();
 	gDeferredBufferVisualProgram.bind();
 	gDeferredBufferVisualProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, src, false, LLTexUnit::TFO_BILINEAR, bufferIndex);
@@ -6944,7 +6961,8 @@ void LLPipeline::visualizeBuffers(LLRenderTarget* src, LLRenderTarget* dst, U32
 	dst->flush();
 }
 
-void LLPipeline::generateLuminance(LLRenderTarget* src, LLRenderTarget* dst) {
+void LLPipeline::generateLuminance(LLRenderTarget* src, LLRenderTarget* dst)
+{
 	// luminance sample and mipmap generation
 	{
 		LL_PROFILE_GPU_ZONE("luminance sample");
@@ -7020,9 +7038,19 @@ void LLPipeline::generateExposure(LLRenderTarget* src, LLRenderTarget* dst) {
 		static LLCachedControl<F32> dynamic_exposure_min(gSavedSettings, "RenderDynamicExposureMin", 0.125f);
 		static LLCachedControl<F32> dynamic_exposure_max(gSavedSettings, "RenderDynamicExposureMax", 1.3f);
 
+        F32 exposure_max = dynamic_exposure_max;
+        LLSettingsSky::ptr_t sky = LLEnvironment::instance().getCurrentSky();
+
+        if (sky->getReflectionProbeAmbiance() > 0.f)
+        { //not a legacy sky, use gamma as a boost to max exposure
+            exposure_max = llmax(exposure_max - 1.f, 0.f);
+            exposure_max *= sky->getGamma();
+            exposure_max += 1.f;
+        }
+
 		gExposureProgram.uniform1f(dt, gFrameIntervalSeconds);
 		gExposureProgram.uniform2f(noiseVec, ll_frand() * 2.0 - 1.0, ll_frand() * 2.0 - 1.0);
-		gExposureProgram.uniform3f(dynamic_exposure_params, dynamic_exposure_coefficient, dynamic_exposure_min, dynamic_exposure_max);
+		gExposureProgram.uniform3f(dynamic_exposure_params, dynamic_exposure_coefficient, dynamic_exposure_min, exposure_max);
 
 		mScreenTriangleVB->setBuffer();
 		mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3);
@@ -7039,18 +7067,25 @@ void LLPipeline::gammaCorrect(LLRenderTarget* src, LLRenderTarget* dst) {
 	{
 		LL_PROFILE_GPU_ZONE("gamma correct");
 
+        static LLCachedControl<bool> no_post(gSavedSettings, "RenderDisablePostProcessing", false);
+
 		LLGLDepthTest depth(GL_FALSE, GL_FALSE);
 
 		// Apply gamma correction to the frame here.
-		gDeferredPostGammaCorrectProgram.bind();
+
+        LLGLSLShader& shader = no_post && gFloaterTools->isAvailable() ? gNoPostGammaCorrectProgram : // no post (no gamma, no exposure, no tonemapping)
+            LLEnvironment::instance().getCurrentSky()->getReflectionProbeAmbiance() == 0.f ? gLegacyPostGammaCorrectProgram :
+            gDeferredPostGammaCorrectProgram;
+        
+        shader.bind();
 
 		S32 channel = 0;
 
-		gDeferredPostGammaCorrectProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, src, false, LLTexUnit::TFO_POINT);
+        shader.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, src, false, LLTexUnit::TFO_POINT);
 
-		gDeferredPostGammaCorrectProgram.bindTexture(LLShaderMgr::EXPOSURE_MAP, &mExposureMap);
+        shader.bindTexture(LLShaderMgr::EXPOSURE_MAP, &mExposureMap);
 
-		gDeferredPostGammaCorrectProgram.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, src->getWidth(), src->getHeight());
+        shader.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, src->getWidth(), src->getHeight());
 
 		static LLCachedControl<F32> exposure(gSavedSettings, "RenderExposure", 1.f);
 
@@ -7058,18 +7093,19 @@ void LLPipeline::gammaCorrect(LLRenderTarget* src, LLRenderTarget* dst) {
 
 		static LLStaticHashedString s_exposure("exposure");
 
-		gDeferredPostGammaCorrectProgram.uniform1f(s_exposure, e);
+        shader.uniform1f(s_exposure, e);
 
 		mScreenTriangleVB->setBuffer();
 		mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3);
 
 		gGL.getTexUnit(channel)->unbind(src->getUsage());
-		gDeferredPostGammaCorrectProgram.unbind();
+        shader.unbind();
 	}
 	dst->flush();
 }
 
-void LLPipeline::copyScreenSpaceReflections(LLRenderTarget* src, LLRenderTarget* dst) {
+void LLPipeline::copyScreenSpaceReflections(LLRenderTarget* src, LLRenderTarget* dst) 
+{
 
 	if (RenderScreenSpaceReflections && !gCubeSnapshot)
 	{
@@ -7095,7 +7131,8 @@ void LLPipeline::copyScreenSpaceReflections(LLRenderTarget* src, LLRenderTarget*
 	}
 }
 
-void LLPipeline::generateGlow(LLRenderTarget* src) {
+void LLPipeline::generateGlow(LLRenderTarget* src) 
+{
 	if (sRenderGlow)
 	{
 		LL_PROFILE_GPU_ZONE("glow");
@@ -7192,7 +7229,8 @@ void LLPipeline::generateGlow(LLRenderTarget* src) {
 	}
 }
 
-void LLPipeline::applyFXAA(LLRenderTarget* src, LLRenderTarget* dst) {
+void LLPipeline::applyFXAA(LLRenderTarget* src, LLRenderTarget* dst) 
+{
 	{
 		llassert(!gCubeSnapshot);
 		bool multisample = RenderFSAASamples > 1 && mRT->fxaaBuffer.isComplete();
@@ -7272,7 +7310,8 @@ void LLPipeline::applyFXAA(LLRenderTarget* src, LLRenderTarget* dst) {
 	}
 }
 
-void LLPipeline::copyRenderTarget(LLRenderTarget* src, LLRenderTarget* dst) {
+void LLPipeline::copyRenderTarget(LLRenderTarget* src, LLRenderTarget* dst)
+{
 
 	LL_PROFILE_GPU_ZONE("copyRenderTarget");
 	dst->bindTarget();
@@ -7292,7 +7331,8 @@ void LLPipeline::copyRenderTarget(LLRenderTarget* src, LLRenderTarget* dst) {
 	dst->flush();
 }
 
-void LLPipeline::combineGlow(LLRenderTarget* src, LLRenderTarget* dst) {
+void LLPipeline::combineGlow(LLRenderTarget* src, LLRenderTarget* dst)
+{
 	// Go ahead and do our glow combine here in our destination.  We blit this later into the front buffer.
 
 	dst->bindTarget();
@@ -7311,7 +7351,8 @@ void LLPipeline::combineGlow(LLRenderTarget* src, LLRenderTarget* dst) {
 	dst->flush();
 }
 
-void LLPipeline::renderDoF(LLRenderTarget* src, LLRenderTarget* dst) {
+void LLPipeline::renderDoF(LLRenderTarget* src, LLRenderTarget* dst)
+{
 	{
 		bool dof_enabled =
 			(RenderDepthOfFieldInEditMode || !LLToolMgr::getInstance()->inBuildMode()) &&
@@ -7477,10 +7518,12 @@ void LLPipeline::renderDoF(LLRenderTarget* src, LLRenderTarget* dst) {
 			{ // combine result based on alpha
 				
 				dst->bindTarget();
-				if (RenderFSAASamples > 1 && mRT->fxaaBuffer.isComplete()) {
+				if (RenderFSAASamples > 1 && mRT->fxaaBuffer.isComplete())
+                {
 					glViewport(0, 0, dst->getWidth(), dst->getHeight());
 				}
-				else {
+				else
+                {
 					gGLViewport[0] = gViewerWindow->getWorldViewRectRaw().mLeft;
 					gGLViewport[1] = gViewerWindow->getWorldViewRectRaw().mBottom;
 					gGLViewport[2] = gViewerWindow->getWorldViewRectRaw().getWidth();
@@ -7515,6 +7558,7 @@ void LLPipeline::renderDoF(LLRenderTarget* src, LLRenderTarget* dst) {
 
 void LLPipeline::renderFinalize()
 {
+    llassert(!gCubeSnapshot);
     LLVertexBuffer::unbind();
     LLGLState::checkStates();
 
@@ -7535,22 +7579,20 @@ void LLPipeline::renderFinalize()
     gGL.setColorMask(true, true);
     glClearColor(0, 0, 0, 0);
 
-    if (!gCubeSnapshot)
-    {
-		copyScreenSpaceReflections(&mRT->screen, &mSceneMap);
+    
+    copyScreenSpaceReflections(&mRT->screen, &mSceneMap);
 
-		generateLuminance(&mRT->screen, &mLuminanceMap);
+    generateLuminance(&mRT->screen, &mLuminanceMap);
 
-		generateExposure(&mLuminanceMap, &mExposureMap);
+    generateExposure(&mLuminanceMap, &mExposureMap);
 
-		gammaCorrect(&mRT->screen, &mPostMap);
+    gammaCorrect(&mRT->screen, &mPostMap);
 
-        LLVertexBuffer::unbind();
-    }
+    LLVertexBuffer::unbind();
 
-	generateGlow(&mPostMap);
+    generateGlow(&mPostMap);
 
-	combineGlow(&mPostMap, &mRT->screen);
+    combineGlow(&mPostMap, &mRT->screen);
 
 	gGLViewport[0] = gViewerWindow->getWorldViewRectRaw().mLeft;
 	gGLViewport[1] = gViewerWindow->getWorldViewRectRaw().mBottom;
@@ -7562,7 +7604,8 @@ void LLPipeline::renderFinalize()
 
 	applyFXAA(&mPostMap, &mRT->screen);
 	LLRenderTarget* finalBuffer = &mRT->screen;
-	if (RenderBufferVisualization > -1) {
+	if (RenderBufferVisualization > -1)
+    {
 		finalBuffer = &mPostMap;
 		switch (RenderBufferVisualization)
 		{
@@ -7663,10 +7706,18 @@ void LLPipeline::bindShadowMaps(LLGLSLShader& shader)
 
 void LLPipeline::bindDeferredShaderFast(LLGLSLShader& shader)
 {
-    shader.bind();
-    bindLightFunc(shader);
-    bindShadowMaps(shader);
-    bindReflectionProbes(shader);
+    if (shader.mCanBindFast)
+    { // was previously fully bound, use fast path
+        shader.bind();
+        bindLightFunc(shader);
+        bindShadowMaps(shader);
+        bindReflectionProbes(shader);
+    }
+    else
+    { //wasn't previously bound, use slow path
+        bindDeferredShader(shader);
+        shader.mCanBindFast = true;
+    }
 }
 
 void LLPipeline::bindDeferredShader(LLGLSLShader& shader, LLRenderTarget* light_target)
@@ -8826,8 +8877,6 @@ void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera
 
     stop_glerror();
 
-    LLEnvironment& environment = LLEnvironment::instance();
-
     struct CompareVertexBuffer
     {
         bool operator()(const LLDrawInfo* const& lhs, const LLDrawInfo* const& rhs)
@@ -8842,7 +8891,6 @@ void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera
     {
         bool rigged = j == 1;
         gDeferredShadowProgram.bind(rigged);
-        LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, environment.getIsSunUp() ? 1 : 0);
 
         gGL.diffuseColor4f(1, 1, 1, 1);
 
@@ -8887,27 +8935,25 @@ void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera
     {
         LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow alpha");
         LL_PROFILE_GPU_ZONE("shadow alpha");
-
+        const S32 sun_up = LLEnvironment::instance().getIsSunUp() ? 1 : 0;
         U32 target_width = LLRenderTarget::sCurResX;
 
         for (int i = 0; i < 2; ++i)
         {
             bool rigged = i == 1;
 
-            gDeferredShadowAlphaMaskProgram.bind(rigged);
-            LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width);
-            LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, environment.getIsSunUp() ? 1 : 0);
-
             {
                 LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow alpha masked");
                 LL_PROFILE_GPU_ZONE("shadow alpha masked");
+                gDeferredShadowAlphaMaskProgram.bind(rigged);
+                LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up);
+                LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width);
                 renderMaskedObjects(LLRenderPass::PASS_ALPHA_MASK, true, true, rigged);
             }
 
             {
                 LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow alpha blend");
                 LL_PROFILE_GPU_ZONE("shadow alpha blend");
-                LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(0.598f);
                 renderAlphaObjects(rigged);
             }
 
@@ -8915,8 +8961,8 @@ void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera
                 LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow fullbright alpha masked");
                 LL_PROFILE_GPU_ZONE("shadow alpha masked");
                 gDeferredShadowFullbrightAlphaMaskProgram.bind(rigged);
+                LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up);
                 LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width);
-                LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, environment.getIsSunUp() ? 1 : 0);
                 renderFullbrightMaskedObjects(LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK, true, true, rigged);
             }
 
@@ -8924,9 +8970,10 @@ void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera
                 LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow alpha grass");
                 LL_PROFILE_GPU_ZONE("shadow alpha grass");
                 gDeferredTreeShadowProgram.bind(rigged);
+                LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(ALPHA_BLEND_CUTOFF);
+
                 if (i == 0)
                 {
-                    LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(0.598f);
                     renderObjects(LLRenderPass::PASS_GRASS, true);
                 }
 
@@ -8945,8 +8992,8 @@ void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera
         {
             bool rigged = i == 1;
             gDeferredShadowGLTFAlphaMaskProgram.bind(rigged);
+            LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up);
             LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width);
-            LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, environment.getIsSunUp() ? 1 : 0);
             
             gGL.loadMatrix(gGLModelView);
             gGLLastMatrix = NULL;
@@ -9449,14 +9496,7 @@ void LLPipeline::generateSunShadow(LLCamera& camera)
 	
 	if (mSunDiffuse == LLColor4::black)
 	{ //sun diffuse is totally black shadows don't matter
-		LLGLDepthTest depth(GL_TRUE);
-
-		for (S32 j = 0; j < 4; j++)
-		{
-			mRT->shadow[j].bindTarget();
-			mRT->shadow[j].clear();
-			mRT->shadow[j].flush();
-		}
+        skipRenderingShadows();
 	}
 	else
 	{
@@ -9512,7 +9552,8 @@ void LLPipeline::generateSunShadow(LLCamera& camera)
 
 			std::vector<LLVector3> fp;
 
-			if (!gPipeline.getVisiblePointCloud(shadow_cam, min, max, fp, lightDir))
+			if (!gPipeline.getVisiblePointCloud(shadow_cam, min, max, fp, lightDir)
+                || j > RenderShadowSplits)
 			{
 				//no possible shadow receivers
                 if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA) && !gCubeSnapshot)
@@ -10051,11 +10092,79 @@ void LLPipeline::renderRiggedGroups(LLRenderPass* pass, U32 type, bool texture)
     }
 }
 
-static LLTrace::BlockTimerStatHandle FTM_GENERATE_IMPOSTOR("Generate Impostor");
+void LLPipeline::profileAvatar(LLVOAvatar* avatar, bool profile_attachments)
+{
+    if (gGLManager.mGLVersion < 3.25f)
+    { // profiling requires GL 3.3 or later
+        return;
+    }
+
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE;
+
+    // don't continue to profile an avatar that is known to be too slow
+    llassert(!avatar->isTooSlow());
+
+    LLGLSLShader* cur_shader = LLGLSLShader::sCurBoundShaderPtr;
+
+    mRT->deferredScreen.bindTarget();
+    mRT->deferredScreen.clear();
+
+    if (!profile_attachments)
+    {
+        // profile entire avatar all at once and readback asynchronously
+        avatar->placeProfileQuery();
+
+        LLTimer cpu_timer;
+
+        generateImpostor(avatar, false, true);
+
+        avatar->mCPURenderTime = (F32)cpu_timer.getElapsedTimeF32() * 1000.f;
+
+        avatar->readProfileQuery(5); // allow up to 5 frames of latency
+    }
+    else 
+    { 
+        // profile attachments one at a time
+        LLVOAvatar::attachment_map_t::iterator iter;
+        LLVOAvatar::attachment_map_t::iterator begin = avatar->mAttachmentPoints.begin();
+        LLVOAvatar::attachment_map_t::iterator end = avatar->mAttachmentPoints.end();
+
+        for (iter = begin;
+            iter != end;
+            ++iter)
+        {
+            LLViewerJointAttachment* attachment = iter->second;
+            for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+                attachment_iter != attachment->mAttachedObjects.end();
+                ++attachment_iter)
+            {
+                LLViewerObject* attached_object = attachment_iter->get();
+                if (attached_object)
+                {
+                    // use gDebugProgram to do the GPU queries
+                    gDebugProgram.clearStats();
+                    gDebugProgram.placeProfileQuery(true);
+
+                    generateImpostor(avatar, false, true, attached_object);
+                    gDebugProgram.readProfileQuery(true, true);
+
+                    attached_object->mGPURenderTime = gDebugProgram.mTimeElapsed / 1000000.f;
+                }
+            }
+        }
+    }
+
+    mRT->deferredScreen.flush();
 
-void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar)
+    if (cur_shader)
+    {
+        cur_shader->bind();
+    }
+}
+
+void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar, bool for_profile, LLViewerObject* specific_attachment)
 {
-    LL_RECORD_BLOCK_TIME(FTM_GENERATE_IMPOSTOR);
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE;
     LL_PROFILE_GPU_ZONE("generateImpostor");
 	LLGLState::checkStates();
 
@@ -10073,19 +10182,19 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar)
 	assertInitialized();
 
     // previews can't be muted or impostered
-	bool visually_muted = !preview_avatar && avatar->isVisuallyMuted();
+	bool visually_muted = !for_profile && !preview_avatar && avatar->isVisuallyMuted();
     LL_DEBUGS_ONCE("AvatarRenderPipeline") << "Avatar " << avatar->getID()
                               << " is " << ( visually_muted ? "" : "not ") << "visually muted"
                               << LL_ENDL;
-	bool too_complex = !preview_avatar && avatar->isTooComplex();
+	bool too_complex = !for_profile && !preview_avatar && avatar->isTooComplex();
     LL_DEBUGS_ONCE("AvatarRenderPipeline") << "Avatar " << avatar->getID()
                               << " is " << ( too_complex ? "" : "not ") << "too complex"
                               << LL_ENDL;
 
-	pushRenderTypeMask();
-	
-	if (visually_muted || too_complex)
-	{
+    pushRenderTypeMask();
+
+    if (visually_muted || too_complex)
+    {
         // only show jelly doll geometry
 		andRenderTypeMask(LLPipeline::RENDER_TYPE_AVATAR,
 							LLPipeline::RENDER_TYPE_CONTROL_AV,
@@ -10103,6 +10212,7 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar)
             RENDER_TYPE_TREE,
             RENDER_TYPE_VOIDWATER,
             RENDER_TYPE_WATER,
+            RENDER_TYPE_ALPHA_POST_WATER,
             RENDER_TYPE_PASS_GRASS,
             RENDER_TYPE_HUD,
             RENDER_TYPE_PARTICLES,
@@ -10112,6 +10222,11 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar)
          );
 	}
 	
+    if (specific_attachment && specific_attachment->isHUDAttachment())
+    { //enable HUD rendering
+        setRenderTypeMask(RENDER_TYPE_HUD, END_RENDER_TYPES);
+    }
+
 	S32 occlusion = sUseOcclusion;
 	sUseOcclusion = 0;
 
@@ -10168,20 +10283,30 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar)
         }
         else
         {
-            LLVOAvatar::attachment_map_t::iterator iter;
-            for (iter = avatar->mAttachmentPoints.begin();
-                iter != avatar->mAttachmentPoints.end();
-                ++iter)
+            if (specific_attachment)
             {
-                LLViewerJointAttachment *attachment = iter->second;
-                for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
-                    attachment_iter != attachment->mAttachedObjects.end();
-                    ++attachment_iter)
+                markVisible(specific_attachment->mDrawable->getSpatialBridge(), *viewer_camera);
+            }
+            else
+            {
+                LLVOAvatar::attachment_map_t::iterator iter;
+                LLVOAvatar::attachment_map_t::iterator begin = avatar->mAttachmentPoints.begin();
+                LLVOAvatar::attachment_map_t::iterator end = avatar->mAttachmentPoints.end();
+
+                for (iter = begin;
+                    iter != end;
+                    ++iter)
                 {
-                    LLViewerObject* attached_object = attachment_iter->get();
-                    if (attached_object)
+                    LLViewerJointAttachment* attachment = iter->second;
+                    for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin();
+                        attachment_iter != attachment->mAttachedObjects.end();
+                        ++attachment_iter)
                     {
-                        markVisible(attached_object->mDrawable->getSpatialBridge(), *viewer_camera);
+                        LLViewerObject* attached_object = attachment_iter->get();
+                        if (attached_object)
+                        {
+                            markVisible(attached_object->mDrawable->getSpatialBridge(), *viewer_camera);
+                        }
                     }
                 }
             }
@@ -10251,25 +10376,28 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar)
 		resY = llmin(nhpo2((U32) (fov*pa)), (U32) 512);
 		resX = llmin(nhpo2((U32) (atanf(tdim.mV[0]/distance)*2.f*RAD_TO_DEG*pa)), (U32) 512);
 
-		if (!avatar->mImpostor.isComplete())
-		{
-            avatar->mImpostor.allocate(resX, resY, GL_RGBA, true);
+        if (!for_profile)
+        {
+            if (!avatar->mImpostor.isComplete())
+            {
+                avatar->mImpostor.allocate(resX, resY, GL_RGBA, true);
 
-			if (LLPipeline::sRenderDeferred)
-			{
-				addDeferredAttachments(avatar->mImpostor, true);
-			}
-		
-			gGL.getTexUnit(0)->bind(&avatar->mImpostor);
-			gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT);
-			gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
-		}
-		else if(resX != avatar->mImpostor.getWidth() || resY != avatar->mImpostor.getHeight())
-		{
-			avatar->mImpostor.resize(resX,resY);
-		}
+                if (LLPipeline::sRenderDeferred)
+                {
+                    addDeferredAttachments(avatar->mImpostor, true);
+                }
+
+                gGL.getTexUnit(0)->bind(&avatar->mImpostor);
+                gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT);
+                gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+            }
+            else if (resX != avatar->mImpostor.getWidth() || resY != avatar->mImpostor.getHeight())
+            {
+                avatar->mImpostor.resize(resX, resY);
+            }
 
-		avatar->mImpostor.bindTarget();
+            avatar->mImpostor.bindTarget();
+        }
 	}
 
 	F32 old_alpha = LLDrawPoolAvatar::sMinimumAlpha;
@@ -10279,9 +10407,9 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar)
 		LLDrawPoolAvatar::sMinimumAlpha = 0.f;
 	}
 
-    if (preview_avatar)
+    if (preview_avatar || for_profile)
     {
-        // previews don't care about imposters
+        // previews and profiles don't care about imposters
         renderGeomDeferred(camera);
         renderGeomPostDeferred(camera);
     }
@@ -10310,6 +10438,7 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar)
 
 	LLDrawPoolAvatar::sMinimumAlpha = old_alpha;
 
+    if (!for_profile)
 	{ //create alpha mask based on depth buffer (grey out if muted)
 		if (LLPipeline::sRenderDeferred)
 		{
@@ -10371,7 +10500,7 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar)
 		gGL.popMatrix();
 	}
 
-    if (!preview_avatar)
+    if (!preview_avatar && !for_profile)
     {
         avatar->mImpostor.flush();
         avatar->setImpostorDim(tdim);
@@ -10388,7 +10517,7 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar)
 	gGL.matrixMode(LLRender::MM_MODELVIEW);
 	gGL.popMatrix();
 
-    if (!preview_avatar)
+    if (!preview_avatar && !for_profile)
     {
         avatar->mNeedsImpostorUpdate = FALSE;
         avatar->cacheImpostorValues();
@@ -10713,9 +10842,32 @@ void LLPipeline::restoreHiddenObject( const LLUUID& id )
 	}
 }
 
+void LLPipeline::skipRenderingShadows()
+{
+    LLGLDepthTest depth(GL_TRUE);
+
+    for (S32 j = 0; j < 4; j++)
+    {
+        mRT->shadow[j].bindTarget();
+        mRT->shadow[j].clear();
+        mRT->shadow[j].flush();
+    }
+}
+
+void LLPipeline::handleShadowDetailChanged()
+{
+    if (RenderShadowDetail > gSavedSettings.getS32("RenderShadowDetail"))
+    {
+        skipRenderingShadows();
+    }
+    else
+    {
+        LLViewerShaderMgr::instance()->setShaders();
+    }
+}
+
 void LLPipeline::overrideEnvironmentMap()
 {
     //mReflectionMapManager.mProbes.clear();
     //mReflectionMapManager.addProbe(LLViewerCamera::instance().getOrigin());
 }
-
diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h
index 64a26c264a4e54081505680e28ee2015a6b8b172..4654f056941b8604de6578d9c1875da8aaebc604 100644
--- a/indra/newview/pipeline.h
+++ b/indra/newview/pipeline.h
@@ -132,7 +132,17 @@ class LLPipeline
     bool allocateShadowBuffer(U32 resX, U32 resY);
 
 	void resetVertexBuffers(LLDrawable* drawable);
-	void generateImpostor(LLVOAvatar* avatar, bool preview_avatar = false);
+
+    // perform a profile of the given avatar
+    // if profile_attachments is true, run a profile for each attachment
+    void profileAvatar(LLVOAvatar* avatar, bool profile_attachments = false);
+
+    // generate an impostor for the given avatar
+    //  preview_avatar - if true, a preview window render is being performed
+    //  for_profile - if true, a profile is being performed, do not update actual impostor
+    //  specific_attachment - specific attachment to profile, or nullptr to profile entire avatar
+	void generateImpostor(LLVOAvatar* avatar, bool preview_avatar = false, bool for_profile = false, LLViewerObject* specific_attachment = nullptr);
+
 	void bindScreenToTexture();
 	void renderFinalize();
 	void copyScreenSpaceReflections(LLRenderTarget* src, LLRenderTarget* dst);
@@ -366,6 +376,8 @@ class LLPipeline
 	bool hasRenderType(const U32 type) const;
 	bool hasAnyRenderType(const U32 type, ...) const;
 
+	static bool isWaterClip();
+
 	void setRenderTypeMask(U32 type, ...);
 	// This is equivalent to 'setRenderTypeMask'
 	//void orRenderTypeMask(U32 type, ...);
@@ -435,6 +447,7 @@ class LLPipeline
 	void skipRenderingOfTerrain( bool flag );
 	void hideObject( const LLUUID& id );
 	void restoreHiddenObject( const LLUUID& id );
+    void handleShadowDetailChanged();
 
     LLReflectionMapManager mReflectionMapManager;
     void overrideEnvironmentMap();
@@ -449,6 +462,7 @@ class LLPipeline
 	void connectRefreshCachedSettingsSafe(const std::string name);
 	void hideDrawable( LLDrawable *pDrawable );
 	void unhideDrawable( LLDrawable *pDrawable );
+    void skipRenderingShadows();
 public:
 	enum {GPU_CLASS_MAX = 3 };
 
@@ -591,7 +605,6 @@ class LLPipeline
 		RENDER_DEBUG_PHYSICS_SHAPES     =  0x02000000,
 		RENDER_DEBUG_NORMALS	        =  0x04000000,
 		RENDER_DEBUG_LOD_INFO	        =  0x08000000,
-		RENDER_DEBUG_RENDER_COMPLEXITY  =  0x10000000,
 		RENDER_DEBUG_ATTACHMENT_BYTES	=  0x20000000, // not used
 		RENDER_DEBUG_TEXEL_DENSITY		=  0x40000000,
 		RENDER_DEBUG_TRIANGLE_COUNT		=  0x80000000,
@@ -765,7 +778,6 @@ class LLPipeline
 	U64						mOldRenderDebugMask;
 	std::stack<U32>			mRenderDebugFeatureStack;
 
-	
 	/////////////////////////////////////////////
 	//
 	//
@@ -959,6 +971,7 @@ class LLPipeline
 	static U32 RenderResolutionDivisor;
 	static bool RenderUIBuffer;
 	static S32 RenderShadowDetail;
+    static S32 RenderShadowSplits;
 	static bool RenderDeferredSSAO;
 	static F32 RenderShadowResolutionScale;
 	static bool RenderLocalLights;
diff --git a/indra/newview/skins/default/colors.xml b/indra/newview/skins/default/colors.xml
index 8bb3feaeb0df1d2fff093b8d57edf80e46951d3f..6efe302f307a7158b3bfa8ae04b113d2497d102c 100644
--- a/indra/newview/skins/default/colors.xml
+++ b/indra/newview/skins/default/colors.xml
@@ -983,12 +983,15 @@
     name="OutfitGalleryItemUnselected"
     value="0.4 0.4 0.4 1" />
   <color
-    name="AddPaymentPanel"
+    name="PanelGray"
     value="0.27 0.27 0.27 1" />
+  <color
+    name="PerformanceMid"
+    value="1 0.8 0 1" />
   <color
     name="OutfitSnapshotMacMask"
     value="0.115 0.115 0.115 1"/>
- <color
-   name="OutfitSnapshotMacMask2"
-   value="0.1 0.1 0.1 1"/>
+  <color
+    name="OutfitSnapshotMacMask2"
+    value="0.1 0.1 0.1 1"/>
 </colors>
diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml
index bdf12d73fcaa0a9515b0d8bde5ea97a8a7707b75..61257466aad0c9a83c9265304f690b6728cccf7b 100644
--- a/indra/newview/skins/default/textures/textures.xml
+++ b/indra/newview/skins/default/textures/textures.xml
@@ -150,6 +150,7 @@ with the same filename but different name
   <texture name="Command_Move_Icon"         file_name="toolbar_icons/move.png"         preload="true" />
   <texture name="Command_Environments_Icon" file_name="toolbar_icons/environments.png" preload="true" />
   <texture name="Command_People_Icon"       file_name="toolbar_icons/people.png"       preload="true" />
+  <texture name="Command_Performance_Icon"  file_name="toolbar_icons/performance.png"  preload="true" />
   <texture name="Command_Picks_Icon"        file_name="toolbar_icons/picks.png"        preload="true" />
   <texture name="Command_Places_Icon"       file_name="toolbar_icons/places.png"       preload="true" />
   <texture name="Command_Preferences_Icon"  file_name="toolbar_icons/preferences.png"  preload="true" />
diff --git a/indra/newview/skins/default/textures/toolbar_icons/performance.png b/indra/newview/skins/default/textures/toolbar_icons/performance.png
new file mode 100644
index 0000000000000000000000000000000000000000..91baf849c88d434ba7c4ece7518fa2c5afb585c1
Binary files /dev/null and b/indra/newview/skins/default/textures/toolbar_icons/performance.png differ
diff --git a/indra/newview/skins/default/xui/en/floater_add_payment_method.xml b/indra/newview/skins/default/xui/en/floater_add_payment_method.xml
index 1f980564d4d2499f2d38eafc486d821add8c321f..ac88263aa197975fc12c7b47b0dd22afdfcac566 100644
--- a/indra/newview/skins/default/xui/en/floater_add_payment_method.xml
+++ b/indra/newview/skins/default/xui/en/floater_add_payment_method.xml
@@ -19,7 +19,7 @@
   </floater.string>
   <panel
    background_opaque="false"
-   bg_alpha_color="AddPaymentPanel"
+   bg_alpha_color="PanelGray"
    border_visible="false"
    background_visible="true"
    label="wrapper_panel"
diff --git a/indra/newview/skins/default/xui/en/floater_adjust_environment.xml b/indra/newview/skins/default/xui/en/floater_adjust_environment.xml
index ca5c7fe264305bb5a09693b815a994b807ca3545..aef923feb2d36bc41d23e5a9e4b8418a0424e4d2 100644
--- a/indra/newview/skins/default/xui/en/floater_adjust_environment.xml
+++ b/indra/newview/skins/default/xui/en/floater_adjust_environment.xml
@@ -239,7 +239,7 @@
                           layout="topleft"
                           left_delta="-5"
                           top_pad="15"
-                          width="80">Scene Gamma:</text>
+                          width="80">Brightness:</text>
                     <slider decimal_digits="2"
                             follows="left|top"
                             height="16"
diff --git a/indra/newview/skins/default/xui/en/floater_avatar_render_settings.xml b/indra/newview/skins/default/xui/en/floater_avatar_render_settings.xml
index e088d4d2a16348091ff7a4a37f5ae44d16086147..d222dca98bbc5569c90e54e27ceec1146be70044 100644
--- a/indra/newview/skins/default/xui/en/floater_avatar_render_settings.xml
+++ b/indra/newview/skins/default/xui/en/floater_avatar_render_settings.xml
@@ -10,7 +10,7 @@
  save_rect="true"
  single_instance="true"
  reuse_instance="true"
- title="AVATAR RENDER SETTINGS"
+ title="AVATAR DISPLAY EXCEPTIONS"
  width="300">
     <string
      name="av_never_render"
@@ -18,53 +18,45 @@
     <string
      name="av_always_render"
      value="Always"/>
-    <filter_editor
-     follows="left|top|right"
-     height="23"
-     layout="topleft"
-     left="8"
-     right="-47"
-     label="Filter People"
-     max_length_chars="300"
-     name="people_filter_input"
-     text_color="Black"
-     text_pad_left="10"
-     top="4" />
-    <menu_button
-     follows="top|right"
-     height="25"
-     image_hover_unselected="Toolbar_Middle_Over"
-     image_overlay="AddItem_Off"
-     image_selected="Toolbar_Middle_Selected"
-     image_unselected="Toolbar_Middle_Off"
-     layout="topleft"
-     left_pad="7"
-     menu_filename="menu_avatar_rendering_settings_add.xml"
-     menu_position="bottomleft"
-     name="plus_btn"
-     tool_tip="Actions on selected person"
-     top="3"
-     width="31" />
     <name_list
-     bottom="-8"
+     bottom="-33"
      draw_heading="true"
      follows="all"
      left="8"
      multi_select="false"
      name="render_settings_list"
      right="-8"
-     top="32">
+     top="0">
         <name_list.columns
          label="Name"
          name="name"
-         relative_width="0.5" />
+         relative_width="0.65" />
         <name_list.columns
-         label="Render setting"
+         label="Full detail"
          name="setting"
-         relative_width="0.25" />
-        <name_list.columns
-         label="Date added"
-         name="timestamp"
-         relative_width="0.25" />
+         relative_width="0.35" />
      </name_list>
+    <panel
+     bg_alpha_color="ScrollBgWriteableColor"
+     background_visible="true"
+     background_opaque="false"
+     bevel_style="none"
+     follows="bottom|left|right"
+     name="add_subpanel"
+     layout="topleft"
+     height="28"
+     top_pad="0">
+     <menu_button
+       follows="bottom|left"
+       height="25"
+       label="Add someone..."
+       layout="topleft"
+       menu_filename="menu_avatar_rendering_settings_add.xml"
+       menu_position="bottomleft"
+       name="plus_btn"
+       tool_tip="Actions on selected person"
+       top="1"
+       left="8"
+       width="120" />
+     </panel>
 </floater>
diff --git a/indra/newview/skins/default/xui/en/floater_performance.xml b/indra/newview/skins/default/xui/en/floater_performance.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d1a1119f77c4c27de7643dd36f717d766aad3484
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_performance.xml
@@ -0,0 +1,368 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ height="642"
+ layout="topleft"
+ name="performance"
+ save_rect="true"
+ reuse_instance="true"
+ title="IMPROVE GRAPHICS SPEED"
+ width="580">
+  <string
+   name="fps_text"
+   value="frames per second"/>
+  <string
+   name="max_text"
+   value=" (maximum)"/>
+  <panel
+   bevel_style="none"
+   follows="left|top"
+   height="540"
+   width="580"
+   name="panel_top"
+   visible="true"
+   layout="topleft"
+   left="0"   
+   top="0">
+    <panel
+     bg_alpha_color="black"
+     background_visible="true"
+     background_opaque="false"
+     border="false"
+     bevel_style="none"
+     follows="left|top"
+     height="40"
+     width="560"
+     name="fps_subpanel"
+     layout="topleft"
+     left="10"
+     top="5">
+      <text
+       follows="left|top"
+       font="SansSerifHuge"
+       text_color="White"
+       height="20"
+       layout="topleft"
+       left="10"
+       top="8"
+       name="fps_value"
+       width="42">
+          167
+      </text>
+      <text
+       follows="left|top"
+       font="SansSerifLarge"
+       text_color="White"
+       height="20"
+       layout="topleft"
+       left_pad="3"
+       top="13"
+       name="fps_lbl"
+       width="450">
+          frames per second
+      </text>
+      <text
+       follows="left|top"
+       text_color="White"
+       height="20"
+       layout="topleft"
+       left="395"
+       top="7"
+       name="fps_desc1_lbl"
+       width="150">
+        Allow 5-10 seconds for
+      </text>
+      <text
+       follows="left|top"
+       text_color="White"
+       height="20"
+       layout="topleft"
+       top_pad="-3"
+       name="fps_desc2_lbl"
+       width="150">
+        changes to take full effect.
+      </text>
+    </panel>
+  </panel>
+  <panel
+   bevel_style="none"
+   follows="left|top"
+   height="540"
+   width="580"
+   name="panel_performance_main"
+   visible="true"
+   layout="topleft"
+   left="0"   
+   top="60">    
+    <panel
+     bg_alpha_color="PanelGray"
+     background_visible="true"
+     background_opaque="false"
+     border="true"
+     bevel_style="none"
+     follows="left|top"
+     height="50"
+     width="560"
+     name="autoadjustments_subpanel"
+     layout="topleft"
+     left="10"
+     top="5">
+        <text
+         follows="left|top"
+         font="SansSerifBoldLarge"
+         text_color="White"
+         height="20"
+         layout="topleft"
+         left="10"
+         name="auto_adj_lbl"
+         top="7"
+         width="375">
+         Auto-adjust settings (recommended)
+        </text>
+        <text
+         follows="left|top"
+         font="SansSerif"
+         text_color="White"
+         height="20"
+         layout="topleft"
+         left="10"
+         name="auto_adj_desc"
+         top_pad="0"
+         width="485">
+         Allow automatic adjustments to reach your preferred frame rate.
+        </text>
+        <icon
+         height="16"
+         width="16"
+         image_name="Arrow_Right_Off"
+         mouse_opaque="true"
+         name="icon_arrow4"
+         follows="right|top"
+         top="19"
+         right="-20"/>
+    </panel>
+    <panel
+     bg_alpha_color="PanelGray"
+     background_visible="true"
+     background_opaque="false"
+     border="true"
+     bevel_style="none"
+     follows="left|top"
+     height="50"
+     width="560"
+     name="settings_subpanel"
+     layout="topleft"
+     top_pad="10">
+        <text
+         follows="left|top"
+         font="SansSerifLarge"
+         text_color="White"
+         height="20"
+         layout="topleft"
+         left="10"
+         name="settings_lbl"
+         top="7"
+         width="180">
+          Graphics settings
+        </text>
+        <text
+         follows="left|top"
+         font="SansSerif"
+         text_color="White"
+         height="20"
+         layout="topleft"
+         left="10"
+         name="settings_desc"
+         top_pad="0"
+         width="395">
+          Choose settings for distance, water, lighting and more.
+        </text>
+        <icon
+         height="16"
+         width="16"
+         image_name="Arrow_Right_Off"
+         mouse_opaque="true"
+         name="icon_arrow3"
+         follows="right|top"
+         top="19"
+         right="-20"/>
+      </panel>
+    <panel
+     bg_alpha_color="PanelGray"
+     background_visible="true"
+     background_opaque="false"
+     border="true"
+     bevel_style="none"
+     follows="left|top"
+     height="50"
+     width="560"
+     name="nearby_subpanel"
+     layout="topleft"
+     top_pad="10">
+      <text
+       follows="left|top"
+       font="SansSerifLarge"
+       text_color="White"
+       height="20"
+       layout="topleft"
+       left="10"
+       name="avatars_nearby_lbl"
+       top="7"
+       width="205">
+          Avatars nearby
+      </text>
+      <text
+       follows="left|top"
+       font="SansSerif"
+       text_color="White"
+       height="20"
+       layout="topleft"
+       left="10"
+       name="avatars_nearby_desc"
+       top_pad="0"
+       width="395">
+          Manage which nearby avatars are fully displayed.
+      </text>
+      <icon
+       height="16"
+       width="16"
+       image_name="Arrow_Right_Off"
+       mouse_opaque="true"
+       name="icon_arrow2"
+       follows="right|top"
+       top="19"
+       right="-20"/>
+    </panel>
+    <panel
+     bg_alpha_color="PanelGray"
+     background_visible="true"
+     background_opaque="false"
+     border="true"
+     bevel_style="none"
+     follows="left|top"
+     height="50"
+     width="560"
+     name="complexity_subpanel"
+     layout="topleft"
+     top_pad="10">
+      <text
+       follows="left|top"
+       font="SansSerifLarge"
+       text_color="White"
+       height="20"
+       layout="topleft"
+       left="10"
+       name="complexity_lbl"
+       top="7"
+       width="180">
+          Your avatar complexity
+      </text>
+      <text
+       follows="left|top"
+       font="SansSerif"
+       text_color="White"
+       height="20"
+       layout="topleft"
+       left="10"
+       name="complexity_info"
+       top_pad="0"
+       width="455">
+          Reduce the complexity of your avatar if you aren't satisfied with current FPS.
+      </text>
+      <icon
+       height="16"
+       width="16"
+       image_name="Arrow_Right_Off"
+       mouse_opaque="true"
+       name="icon_arrow4"
+       follows="right|top"
+       top="19"
+       right="-20"/>
+    </panel>
+    <panel
+     bg_alpha_color="PanelGray"
+     background_visible="true"
+     background_opaque="false"
+     border="true"
+     bevel_style="none"
+     follows="left|top"
+     height="50"
+     width="560"
+     name="huds_subpanel"
+     layout="topleft"
+     top_pad="10">
+      <text
+       follows="left|top"
+       font="SansSerifLarge"
+       text_color="White"
+       height="20"
+       layout="topleft"
+       left="10"
+       name="huds_lbl"
+       top="7"
+       width="135">
+          Your active HUDs
+      </text>
+      <text
+       follows="left|top"
+       font="SansSerif"
+       text_color="White"
+       height="20"
+       layout="topleft"
+       left="10"
+       name="huds_desc"
+       top_pad="0"
+       width="395">
+          Removing HUDs you are not using can improve speed.
+      </text>
+      <icon
+       height="16"
+       width="16"
+       image_name="Arrow_Right_Off"
+       mouse_opaque="true"
+       name="icon_arrow4"
+       follows="right|top"
+       top="19"
+       right="-20"/>
+    </panel>
+  </panel>
+  <panel
+    filename="panel_performance_nearby.xml"
+    follows="all"
+    layout="topleft"
+    left="0"
+    name="panel_performance_nearby"
+    visible="false"
+    top="55" />
+  <panel
+    filename="panel_performance_complexity.xml"
+    follows="all"
+    layout="topleft"
+    left="0"
+    name="panel_performance_complexity"
+    visible="false"
+    top="55" />
+  <panel
+    filename="panel_performance_preferences.xml"
+    follows="all"
+    layout="topleft"
+    left="0"
+    name="panel_performance_preferences"
+    visible="false"
+    top="55" />
+  <panel
+    filename="panel_performance_huds.xml"
+    follows="all"
+    layout="topleft"
+    left="0"
+    name="panel_performance_huds"
+    visible="false"
+    top="55" />
+  <panel
+    filename="panel_performance_autoadjustments.xml"
+    follows="all"
+    layout="topleft"
+    left="0"
+    name="panel_performance_autoadjustments"
+    visible="false"
+    top="55" />
+</floater>
diff --git a/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml b/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml
index 22e889a99c22b09308c203bd2711d784d70ff8ad..fd806739031949c98841f649f8f901c05161c9bd 100644
--- a/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml
+++ b/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml
@@ -39,7 +39,8 @@
     max_val="512"
     name="DrawDistance"
     top_delta="16"
-    width="330" />
+    width="330">
+    </slider>
   <text
     type="string"
     length="1"
@@ -459,8 +460,8 @@
     label_width="185"
     layout="topleft"
     left="420"
-    min_val="1"
-    max_val="2"
+    min_val="0"
+    max_val="4"
     name="ObjectMeshDetail"
     show_text="false"
     top_delta="16"
diff --git a/indra/newview/skins/default/xui/en/floater_scene_load_stats.xml b/indra/newview/skins/default/xui/en/floater_scene_load_stats.xml
index 2abd8ec5c05fee2ca1e92dabe272d88aa5e0b2f1..37efbe654e6a80e69658a94ea1bd4e6f6fab01e8 100644
--- a/indra/newview/skins/default/xui/en/floater_scene_load_stats.xml
+++ b/indra/newview/skins/default/xui/en/floater_scene_load_stats.xml
@@ -410,6 +410,59 @@
           </stat_view>
 			  </stat_view>
 		  </stat_view>
+      <stat_view 
+        name="frame_stats"
+        label="Frame breakdown"
+        show_label="true">
+          <stat_bar name="packet_loss"
+            label="Scenery"
+            orientation="horizontal"
+            unit_label=" %"
+            stat="scenery_frame_pct"
+            bar_max="100"
+            tick_spacing="0.5"
+            show_bar="false"/>
+          <stat_bar name="packet_loss"
+            label="Avatar"
+            orientation="horizontal"
+            unit_label=" %"
+            stat="avatar_frame_pct"
+            bar_max="100"
+            tick_spacing="0.5"
+            show_bar="false"/>
+          <stat_bar name="packet_loss"
+            label="UI"
+            orientation="horizontal"
+            unit_label=" %"
+            stat="ui_frame_pct"
+            bar_max="100"
+            tick_spacing="0.5"
+            show_bar="false"/>
+          <stat_bar name="packet_loss"
+            label="HUDs"
+            orientation="horizontal"
+            unit_label=" %"
+            stat="huds_frame_pct"
+            bar_max="100"
+            tick_spacing="0.5"
+            show_bar="false"/>
+        <stat_bar name="packet_loss"
+            label="Swap"
+            orientation="horizontal"
+            unit_label=" %"
+            stat="swap_frame_pct"
+            bar_max="100"
+            tick_spacing="0.5"
+            show_bar="false"/>
+        <stat_bar name="packet_loss"
+            label="Tasks"
+            orientation="horizontal"
+            unit_label=" %"
+            stat="idle_frame_pct"
+            bar_max="100"
+            tick_spacing="0.5"
+            show_bar="false"/>
+      </stat_view>
     </container_view>
   </scroll_container>
 </floater>
diff --git a/indra/newview/skins/default/xui/en/menu_attachment_other.xml b/indra/newview/skins/default/xui/en/menu_attachment_other.xml
index 7ad692038ee0759e1b8811469e5f983fde17dab0..22006c287fae8a4cfff3dac204525ae241850b50 100644
--- a/indra/newview/skins/default/xui/en/menu_attachment_other.xml
+++ b/indra/newview/skins/default/xui/en/menu_attachment_other.xml
@@ -131,22 +131,12 @@
 
    <menu_item_separator />
       <context_menu
-       label="Render Avatar"
+       label="Display this avatar"
        layout="topleft"
-        name="Render Avatar">
-      <menu_item_check
-        name="RenderNormally"
-        label="Default">
-        <menu_item_check.on_check
-          function="Avatar.CheckImpostorMode"
-          parameter="0" />
-	    <menu_item_check.on_click
-	      function="Avatar.SetImpostorMode"
-	      parameter="0" />
-      </menu_item_check>
+       name="Render Avatar">
       <menu_item_check
         name="AlwaysRenderFully"
-        label="Always">
+        label="Always full detail">
         <menu_item_check.on_check
           function="Avatar.CheckImpostorMode"
           parameter="2" />
@@ -156,7 +146,7 @@
       </menu_item_check>
       <menu_item_check
         name="DoNotRender"
-        label="Never">
+        label="Never full detail">
         <menu_item_check.on_check
           function="Avatar.CheckImpostorMode"
           parameter="1" />
@@ -164,6 +154,16 @@
 	      function="Avatar.SetImpostorMode"
 	      parameter="1" />
       </menu_item_check>
+      <menu_item_call
+        name="RenderNormally"
+        label="Remove from exceptions">
+          <menu_item_call.on_visible
+            function="Avatar.CheckImpostorMode"
+            parameter="4" />
+          <menu_item_call.on_click
+            function="Avatar.SetImpostorMode"
+            parameter="0" />
+      </menu_item_call>
       <menu_item_separator />
       <menu_item_call
         label="Exceptions..."
diff --git a/indra/newview/skins/default/xui/en/menu_avatar_other.xml b/indra/newview/skins/default/xui/en/menu_avatar_other.xml
index acbb9b860d1070939492880eb3af6088150ced43..665eb9a82fdc1bf91c7ddcc4b323f450dcc08f62 100644
--- a/indra/newview/skins/default/xui/en/menu_avatar_other.xml
+++ b/indra/newview/skins/default/xui/en/menu_avatar_other.xml
@@ -122,39 +122,39 @@
    <menu_item_separator />
     
  <context_menu
-       label="Render Avatar"
+       label="Display this avatar"
        layout="topleft"
-        name="Render Avatar">
-      <menu_item_check
-        name="RenderNormally"
-        label="Default">
-        <menu_item_check.on_check
-          function="Avatar.CheckImpostorMode"
-          parameter="0" />
-	    <menu_item_check.on_click
-	      function="Avatar.SetImpostorMode"
-	      parameter="0" />
-      </menu_item_check>
-      <menu_item_check
-        name="AlwaysRenderFully"
-        label="Always">
-        <menu_item_check.on_check
-          function="Avatar.CheckImpostorMode"
-          parameter="2" />
-	    <menu_item_check.on_click
-	      function="Avatar.SetImpostorMode"
-	      parameter="2" />
-      </menu_item_check>
-      <menu_item_check
-        name="DoNotRender"
-        label="Never">
-        <menu_item_check.on_check
-          function="Avatar.CheckImpostorMode"
-          parameter="1" />
-	    <menu_item_check.on_click
-	      function="Avatar.SetImpostorMode"
-	      parameter="1" />
-      </menu_item_check>
+       name="Render Avatar">
+       <menu_item_check
+         name="AlwaysRenderFully"
+         label="Always full detail">
+         <menu_item_check.on_check
+           function="Avatar.CheckImpostorMode"
+           parameter="2" />
+         <menu_item_check.on_click
+           function="Avatar.SetImpostorMode"
+           parameter="2" />
+       </menu_item_check>
+       <menu_item_check
+         name="DoNotRender"
+         label="Never full detail">
+         <menu_item_check.on_check
+           function="Avatar.CheckImpostorMode"
+           parameter="1" />
+         <menu_item_check.on_click
+           function="Avatar.SetImpostorMode"
+           parameter="1" />
+       </menu_item_check>
+       <menu_item_call
+         name="RenderNormally"
+         label="Remove from exceptions">
+         <menu_item_call.on_visible
+           function="Avatar.CheckImpostorMode"
+           parameter="4" />
+         <menu_item_call.on_click
+           function="Avatar.SetImpostorMode"
+           parameter="0" />
+       </menu_item_call>
       <menu_item_separator />
       <menu_item_call
         label="Exceptions..."
diff --git a/indra/newview/skins/default/xui/en/menu_avatar_rendering_settings.xml b/indra/newview/skins/default/xui/en/menu_avatar_rendering_settings.xml
index 5163cd3115b35bf4695c89501ed8a77fb694e2db..1a18483418d484ad56fd86feadacdfa39c6801db 100644
--- a/indra/newview/skins/default/xui/en/menu_avatar_rendering_settings.xml
+++ b/indra/newview/skins/default/xui/en/menu_avatar_rendering_settings.xml
@@ -3,24 +3,25 @@
  layout="topleft"
  name="Settings">
     <menu_item_check
-     label="Default"
-     layout="topleft"
-     name="default">
-        <on_click function="Settings.SetRendering" parameter="default"/>
-	<on_check function="Settings.IsSelected" parameter="default" />  
-    </menu_item_check>
-    <menu_item_check
-     label="Always render"
+     label="Always full detail"
      layout="topleft"
      name="always_render">
         <on_click function="Settings.SetRendering" parameter="always"/>
-	<on_check function="Settings.IsSelected" parameter="always" />  
+        <on_check function="Settings.IsSelected" parameter="always" />  
     </menu_item_check>
     <menu_item_check
-     label="Never render"
+     label="Never full detail"
      layout="topleft"
      name="never_render">
         <on_click function="Settings.SetRendering" parameter="never"/>
-	<on_check function="Settings.IsSelected" parameter="never" />  
-    </menu_item_check>  
+        <on_check function="Settings.IsSelected" parameter="never" />
+    </menu_item_check>
+    <menu_item_check
+     label="Remove from exceptions"
+     layout="topleft"
+     name="default">
+        <on_click function="Settings.SetRendering" parameter="default"/>
+        <on_check function="Settings.IsSelected" parameter="default" />
+        <on_visible function="Settings.IsSelected" parameter="non_default" />
+  </menu_item_check>
 </context_menu>
diff --git a/indra/newview/skins/default/xui/en/menu_avatar_rendering_settings_add.xml b/indra/newview/skins/default/xui/en/menu_avatar_rendering_settings_add.xml
index c64b24ed70c0b35608ca902b1311f00860f63502..6e09eb5981f5fa8517f1627fe3b3db35a2604026 100644
--- a/indra/newview/skins/default/xui/en/menu_avatar_rendering_settings_add.xml
+++ b/indra/newview/skins/default/xui/en/menu_avatar_rendering_settings_add.xml
@@ -4,13 +4,13 @@
      left="0" bottom="0" visible="false"
      mouse_opaque="false">
   <menu_item_call
-   label="Always Render a Resident..."
+   label="Always full detail..."
    name="add_avatar_always_render">
       <on_click
        function="Settings.AddNewEntry" parameter="always"/>
   </menu_item_call>
   <menu_item_call
-   label="Never Render a Resident..."
+   label="Never full detail..."
    name="add_avatar_never_render">
       <on_click
        function="Settings.AddNewEntry"  parameter="never"/>
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index 4a92bdc601579c008a2aee305f89d8efe755711c..df067d2b88d1401d8fc66c02cb602b5dc384893b 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -862,7 +862,16 @@
                parameter="UseDebugMenus" />
           </menu_item_check>
         </menu>
-
+        <menu_item_check
+          label="Improve graphics speed..."
+          name="Performance">
+          <menu_item_check.on_click
+           function="Floater.Toggle"
+           parameter="performance" />
+          <menu_item_check.on_check
+           function="Floater.Visible"
+           parameter="performance" />
+        </menu_item_check>
         <menu_item_separator/>
     <!--    <menu_item_check
          label="Show Navigation Bar"
@@ -1492,7 +1501,28 @@ function="World.EnvPreset"
                      function="ToggleControl"
                      parameter="ShowSelectionBeam" />
                 </menu_item_check>
-
+                <menu_item_check
+                       label="Highlight Transparent"
+                       name="Highlight Transparent"
+                       shortcut="control|alt|T"
+                       use_mac_ctrl="true">
+                  <menu_item_check.on_check
+                   function="View.CheckHighlightTransparent" />
+                  <menu_item_check.on_click
+                   function="View.HighlightTransparent" />
+                </menu_item_check>
+        <menu_item_separator/>
+          
+          <menu_item_check
+            label="No Post"
+            name="No Post">
+            <menu_item_check.on_check
+             control="RenderDisablePostProcessing" />
+            <menu_item_check.on_click
+             function="ToggleControl"
+             parameter="RenderDisablePostProcessing" />
+          </menu_item_check>
+          
         <menu_item_separator/>
 
                 <menu_item_check
@@ -1901,16 +1931,6 @@ function="World.EnvPreset"
                  function="ToggleControl"
                  parameter="HideSelectedObjects" />
             </menu_item_check>
-            <menu_item_check
-             label="Highlight Transparent"
-             name="Highlight Transparent"
-             shortcut="control|alt|T"
-             use_mac_ctrl="true">
-                <menu_item_check.on_check
-                 function="View.CheckHighlightTransparent" />
-                <menu_item_check.on_click
-                 function="View.HighlightTransparent" />
-            </menu_item_check>
             <menu_item_check
              label="Show Mouselook Crosshairs"
              name="ShowCrosshairs">
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index bf67118eacbb929e0559d05cf0dded952a8066aa..4949075f2d3b99872ae8264f8439e4e420bf5d76 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -1898,6 +1898,41 @@ Graphics Quality can be raised in Preferences &gt; Graphics.
   <tag>fail</tag>
   </notification>
 
+  <notification
+   icon="alertmodal.tga"
+   name="AutoFPSConfirmDisable"
+   type="alertmodal">
+Changing this setting will disable automatic adjustment and turn off 'Automatic settings'.
+Are you sure you want to continue?
+    <tag>confirm</tag>
+    <usetemplate
+     name="okcancelbuttons"
+     notext="Cancel"
+     yestext="Continue"/>
+  </notification>
+  <notification
+   icon="alertmodal.tga"
+   name="AdvancedLightingConfirm"
+   type="alertmodal">
+To turn on advanced lighting, we need to increase quality to level 4.
+    <tag>confirm</tag>
+  <usetemplate
+   name="okcancelbuttons"
+   notext="Cancel"
+   yestext="OK"/>
+  </notification>
+  <notification
+ icon="alertmodal.tga"
+   name="ShadowsConfirm"
+   type="alertmodal">
+To enable shadows, we need to increase quality to level 4.
+    <tag>confirm</tag>
+    <usetemplate
+     name="okcancelbuttons"
+     notext="Cancel"
+     yestext="OK"/>
+  </notification>
+
   <notification
    icon="alertmodal.tga"
    name="RegionNoTerraforming"
@@ -12029,6 +12064,20 @@ If you want others to see this object, remove it and re-attach it to an avatar a
         yestext="OK"/>
   </notification>
 
+  <notification
+   icon="alertmodal.tga"
+   name="EnableAutoFPSWarning"
+   type="alertmodal">
+You are about to enable AutoFPS. All unsaved graphics settings will be lost.
+
+Would you like to save them first?
+      <tag>confirm</tag>
+      <usetemplate
+       name="okcancelbuttons"
+       notext="No"
+       yestext="Yes"/>
+  </notification>
+    
 <notification
  icon="notifytip.tga"
  name="MaterialCreated"
diff --git a/indra/newview/skins/default/xui/en/panel_performance_autoadjustments.xml b/indra/newview/skins/default/xui/en/panel_performance_autoadjustments.xml
new file mode 100644
index 0000000000000000000000000000000000000000..904ce1cc523b8c9befc2fe6af7fd18ec1eeddea4
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/panel_performance_autoadjustments.xml
@@ -0,0 +1,387 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<panel
+ bevel_style="none"
+ follows="left|top"
+ height="580"
+ width="580"
+ name="panel_performance_autoadjustments"
+ layout="topleft"
+ left="0"
+ top="0">
+  <button
+    height="16"
+    width="16"
+    layout="topleft"
+    mouse_opaque="true"
+    follows="left|top"
+    name="back_btn"
+    top="7"
+    image_selected="Arrow_Left_Off"
+    image_pressed="Arrow_Left_Off"
+    image_unselected="Arrow_Left_Off"
+    left="15"
+    is_toggle="true">
+  </button>
+  <text
+   follows="left|top"
+   height="18"
+   layout="topleft"
+   left_pad="0"
+   valign="center"
+   halign="center"
+   top="6"
+   name="back_lbl"
+   width="32">
+    Back
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifLarge"
+   text_color="white"
+   height="20"
+   layout="topleft"
+   left="20"
+   top_pad="15"
+   name="settings_title"
+   width="300">
+    Auto-adjust settings
+  </text>
+  <button
+   follows="top|left"
+   height="23"
+   label="Reset to recommended settings"
+   layout="topleft"
+   left="360"
+   name="defaults_btn"
+   top_delta="0"
+   width="200"/>
+  <view_border
+   bevel_style="in"
+   height="0"
+   layout="topleft"
+   name="border0"
+   top_pad="15"
+   left="20"
+   width="540"/>
+  <text
+   follows="left|top"
+   font="SansSerif"
+   text_color="White"
+   height="20"
+   layout="topleft"
+   left="20"
+   name="targetfps_desc"
+   wrap="true"
+   width="115"
+   top_pad="20">
+    Desired frame rate
+  </text>
+  <spinner
+   name="target_fps"
+   control_name="TargetFPS"
+   font="SansSerifLarge"
+   tool_tip="The viewer will attempt to achieve this by adjusting your graphics settings."
+   layout="topleft"
+   follows="left|top"
+   left_pad="25"
+   top_delta="0"
+   height="25"
+   visible="true"
+   decimal_digits="0"
+   increment="1"
+   initial_value="25"
+   max_val="300"
+   min_val="1"
+   width="48"
+   label_width="0" />
+ <text
+   follows="left|top"
+   text_color="White"
+   height="20"
+   layout="topleft"
+   name="display_desc"
+   top_delta="5"
+   left_pad="15"
+   wrap="true"
+   width="225">
+  Your display supports up to [FPS_LIMIT] fps.
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerif"
+   text_color="White"
+   height="20"
+   layout="topleft"
+   left="20"
+   name="settings_desc"
+   top_pad="15"
+   wrap="true"
+   width="115">
+    Settings affect
+  </text>
+  <combo_box
+   follows="top|left"
+   font="SansSerif"
+   height="20"
+   layout="topleft"
+   left_pad="25"
+   control_name="TuningFPSStrategy"
+   name="TuningFPSStrategy"
+   width="160">
+    <combo_box.item
+     label="Avatars Only"
+     name="av_only"
+     value="0" />
+    <combo_box.item
+     label="Avatars and World"
+     name="av_and_scene"
+     value="1" />
+	<combo_box.item
+     label="World Only"
+     name="scene_only"
+     value="2" />
+  </combo_box>
+  <button
+   follows="top|left"
+   height="22"
+   image_pressed="PushButton_Press"
+   image_pressed_selected="PushButton_Selected_Press"
+   image_selected="PushButton_Selected_Press"
+   label="Auto-adjust now"
+   layout="topleft"
+   top_pad="15"
+   left="20"
+   name="start_autotune"
+   tool_tip="The viewer will attempt to adjust settings to meet the target FPS then stop."
+   width="124"/>
+  <button
+   follows="top|left"
+   height="22"
+   image_pressed="PushButton_Press"
+   image_pressed_selected="PushButton_Selected_Press"
+   image_selected="PushButton_Selected_Press"
+   label="Cancel"
+   layout="topleft"
+   left_pad="15"
+   name="stop_autotune"
+   tool_tip="Stop adjusting settings."
+   width="90"/>
+  <text
+   follows="left|top"
+   text_color="Yellow"
+   height="20"
+   layout="topleft"
+   name="wip_desc"
+   top_delta="5"
+   left_pad="20"
+   wrap="true"
+   width="115">
+  Working on it...
+  </text>
+  <check_box
+   control_name="AutoTuneLock"
+   follows="top|left"
+   height="20"
+   initial_value="true"
+   image_pressed="PushButton_Press"
+   image_pressed_selected="PushButton_Selected_Press"
+   image_selected="PushButton_Selected_Press"
+   is_toggle="true"
+   label="Adjust continuously"
+   layout="topleft"
+   left="17"
+   top_pad="10"
+   name="AutoTuneContinuous"
+   tool_tip="The viewer will continually adapt the settings to meet the target FPS until stopped even with the floater closed."
+   width="64">
+  </check_box>
+  <radio_group
+   control_name="KeepAutoTuneLock"
+   enabled_control="AutoTuneLock"
+   height="50"
+   layout="topleft"
+   follows="top|left"
+   name="autotune_lock_type"
+   top_pad="5"
+   left_delta="15"
+   width="120">
+    <radio_item
+     height="16"
+     label="This login session only"
+     layout="topleft"
+     name="one_session_lock"
+     value="0"
+     width="120" />
+    <radio_item
+     height="16"
+     label="Future login sessions"
+     layout="topleft"
+     name="next_session_lock"
+     value="1"
+     width="120" />
+  </radio_group>
+  <view_border
+   bevel_style="in"
+   height="0"
+   layout="topleft"
+   name="border_vsync"
+   top_pad="3"
+   left="20"
+   width="540"/>
+  <check_box
+   control_name="RenderVSyncEnable"
+   height="16"
+   left="17"
+   initial_value="true"
+   label="Enable VSync"
+   label_text.text_color="White"
+   layout="topleft"
+   top_pad="12"
+   name="vsync"
+   tool_tip="Enable Vertical synchronization to reduce screen tearing and stuttering."
+   width="315" />
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   left="20"
+   top_pad="15"
+   name="vsync_desc"
+   width="580">
+    Matches monitor refresh rate with frame rate.
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_pad="3"
+   left="20"
+   name="vsync_desc_limit"
+   width="580">
+    Note: Turning on VSync limits frame rate to [FPS_LIMIT] fps.
+  </text>
+  <view_border
+   bevel_style="in"
+   height="0"
+   layout="topleft"
+   name="border1"
+   top_pad="10"
+   left="20"
+   width="540"/>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   left="20"
+   top_pad="15"
+   name="simplify_dist_desc"
+   width="580">
+    Reducing detail shown on avatars that are far away will improve graphics speed.
+  </text>
+  <check_box
+   control_name="AutoTuneImpostorByDistEnabled"
+   height="19"
+   label="Simplify avatars beyond"
+   label_text.text_color="White"
+   layout="topleft"
+   follows="top|left"
+   name="AutoTuneImpostorByDistEnabled"
+   tool_tip="When enabled the viewer will adjust the MaxNonImpostors setting to limit fully rendered avatars to those within the defined radius."
+   top_pad="15"
+   width="190" />
+  <spinner
+   control_name="AutoTuneImpostorFarAwayDistance"
+   height="20"
+   layout="topleft"
+   follows="top|left"
+   name="ffa_autotune"
+   left_pad="20"
+   decimal_digits="2"
+   min_val="16"
+   max_val="256"
+   width="60" >
+  </spinner>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   left_pad="10"
+   name="dist_meters"
+   width="70">
+    meters
+  </text>
+  <view_border
+   bevel_style="in"
+   height="0"
+   layout="topleft"
+   name="border2"
+   top_pad="20"
+   left="20"
+   width="540"/>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   left="20"
+   top_pad="15"
+   name="dist_limits_desc"
+   width="580">
+    Choose the distance range that automatic settings will affect.
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_pad="15"
+   name="min_dist_lbl"
+   width="120">
+    Minimum distance
+  </text>
+  <spinner
+   control_name="AutoTuneRenderFarClipMin"
+   height="20"
+   layout="topleft"
+   left_pad="15"
+   follows="top|left"
+   name="min_dd_autotune"
+   decimal_digits="2"
+   min_val="32"
+   max_val="256"
+   width="60">
+  </spinner>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_pad="15"
+   left="20"
+   name="pref_dist_lbl"
+   width="120">
+    Maximum distance
+  </text>
+  <spinner
+   control_name="AutoTuneRenderFarClipTarget"
+   height="20"
+   layout="topleft"
+   follows="top|left"
+   name="pref_dd_autotune"
+   left_pad="15"
+   min_val="32"
+   max_val="256"
+   width="60">
+  </spinner>
+</panel>
diff --git a/indra/newview/skins/default/xui/en/panel_performance_complexity.xml b/indra/newview/skins/default/xui/en/panel_performance_complexity.xml
new file mode 100644
index 0000000000000000000000000000000000000000..cd3f610a923643091ca26c8d608f43ff70e9e75f
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/panel_performance_complexity.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<panel
+ bevel_style="none"
+ follows="left|top"
+ height="580"
+ width="580"
+ name="panel_performance_complexity"
+ layout="topleft"
+ left="0"
+ top="0">
+  <button
+    height="16"
+    width="16"
+    layout="topleft"
+    mouse_opaque="true"
+    follows="left|top"
+    name="back_btn"
+    top="7"
+    image_selected="Arrow_Left_Off"
+    image_pressed="Arrow_Left_Off"
+    image_unselected="Arrow_Left_Off"
+    left="15"
+    is_toggle="true">
+  </button>
+  <text
+   follows="left|top"
+   height="18"
+   layout="topleft"
+   left_pad="0"
+   valign="center"
+   halign="center"
+   top="6"
+   name="back_lbl"
+   width="32">
+    Back
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifLarge"
+   text_color="white"
+   height="20"
+   layout="topleft"
+   left="20"
+   top_pad="15"
+   name="attachments_title"
+   width="195">
+    Your avatar complexity 
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_pad="5"
+   left="20"
+   name="attachments_desc1"
+   width="580">
+    Attachments make your avatar more complex. If your avatar is very complex, some other 
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_pad="3"
+   left="20"
+   name="attachments_desc2"
+   width="580">
+    people may not see you in full detail, and your graphics speed may be reduced. Removing
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_pad="3"
+   left="20"
+   name="attachments_desc3"
+   width="580">
+    heavy attachments that you don’t need can help.
+  </text>
+  <name_list
+    column_padding="0"
+    draw_stripes="true"
+    height="429"
+    follows="left|top"
+    layout="topleft"
+    name="obj_list"
+    top_pad="10"
+    width="540">
+      <name_list.columns
+       label=""
+       name="complex_visual"
+       width="90" />
+      <name_list.columns
+       label=""
+       name="complex_value"
+       width="40" />
+      <name_list.columns
+       label=""
+       name="name"/>
+  </name_list>
+</panel>
diff --git a/indra/newview/skins/default/xui/en/panel_performance_huds.xml b/indra/newview/skins/default/xui/en/panel_performance_huds.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2fddcb3b9edea80fb7fa4eb7fd8ef4024f163edb
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/panel_performance_huds.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<panel
+ bevel_style="none"
+ follows="left|top"
+ height="580"
+ width="580"
+ name="panel_performance_huds"
+ layout="topleft"
+ left="0"
+ top="0">
+  <button
+    height="16"
+    width="16"
+    layout="topleft"
+    mouse_opaque="true"
+    follows="left|top"
+    name="back_btn"
+    top="7"
+    image_selected="Arrow_Left_Off"
+    image_pressed="Arrow_Left_Off"
+    image_unselected="Arrow_Left_Off"
+    left="15"
+    is_toggle="true">
+  </button>
+  <text
+   follows="left|top"
+   height="18"
+   layout="topleft"
+   left_pad="0"
+   valign="center"
+   halign="center"
+   top="6"
+   name="back_lbl"
+   width="32">
+    Back
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifLarge"
+   text_color="White"
+   height="20"
+   layout="topleft"
+   left="20"
+   top_pad="15"
+   name="huds_title"
+   width="135">
+    Your active HUDs
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_pad="5"
+   left="20"
+   name="huds_desc1"
+   width="540">
+    Detaching HUDs you aren't using saves memory and can make Second Life run faster.
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_pad="3"
+   left="20"
+   name="huds_desc2"
+   width="540">
+    Note: Using a HUD's minimize button does not detach it. 
+  </text>
+  <name_list
+    column_padding="0"
+    draw_stripes="true"
+    height="450"
+    follows="left|top"
+    layout="topleft"
+    name="hud_list"
+    top_pad="10"
+    width="540">
+        <name_list.columns
+         label=""
+         name="complex_visual"
+         width="90" />
+        <name_list.columns
+         label=""
+         name="complex_value"
+         width="40" />
+        <name_list.columns
+         label=""
+         name="name"/>
+  </name_list>
+</panel>
+
diff --git a/indra/newview/skins/default/xui/en/panel_performance_nearby.xml b/indra/newview/skins/default/xui/en/panel_performance_nearby.xml
new file mode 100644
index 0000000000000000000000000000000000000000..cb795e59a91db91f7c3870daf8816e5fc2be3acf
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/panel_performance_nearby.xml
@@ -0,0 +1,216 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<panel
+ bevel_style="none"
+ follows="left|top"
+ height="530"
+ width="580"
+ name="panel_performance_nearby"
+ layout="topleft"
+ left="0"
+ top="0">
+  <button
+    height="16"
+    width="16"
+    layout="topleft"
+    mouse_opaque="true"
+    follows="left|top"
+    name="back_btn"
+    top="7"
+    image_selected="Arrow_Left_Off"
+    image_pressed="Arrow_Left_Off"
+    image_unselected="Arrow_Left_Off"
+    left="15"
+    is_toggle="true">
+  </button>
+  <text
+   follows="left|top"
+   height="18"
+   layout="topleft"
+   left_pad="0"
+   valign="center"
+   halign="center"
+   top="6"
+   name="back_lbl"
+   width="32">
+    Back
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifLarge"
+   text_color="White"
+   height="20"
+   layout="topleft"
+   left="20"
+   top_pad="15"
+   name="av_nearby_title"
+   width="205">
+    Avatars nearby
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   left="20"
+   top_pad="5"
+   name="av_nearby_desc"
+   width="580">
+    Hide the most complex avatars to boost speed.
+  </text>
+  <slider
+    control_name="RenderAvatarMaxART"
+    tool_tip="Controls when a visually complex avatar is considered to be taking too long to render (unit: microseconds)"
+    follows="left|top"
+    height="16"
+    initial_value="4.7"
+    increment="0.01"
+    label="Maximum render time (μs)"
+    text_color="White"
+    label_width="165"
+    layout="topleft"
+    min_val="2"
+    max_val="4.7"
+    name="RenderAvatarMaxART"
+    show_text="false"
+    top_pad="10"
+    width="490">
+  </slider>
+  <text
+    type="string"
+    length="1"
+    follows="left|top"
+    height="16"
+    layout="topleft"
+    top_delta="0"
+    left_pad="5"
+    text_color="White"
+    name="RenderAvatarMaxARTText"
+    width="65">
+    no limit
+  </text>
+  <name_list
+    column_padding="0"
+    draw_stripes="true"
+    height="280"
+    left="20"
+    follows="left|top"
+    layout="topleft"
+    sort_column="complex_value"
+    short_names="true"
+    name="nearby_list"
+    name_column="name"
+    top_pad="10"
+    width="540">
+        <name_list.columns
+         label=""
+         name="complex_visual"
+         width="90" />
+        <name_list.columns
+         label=""
+         name="complex_value"
+         width="50" />
+        <name_list.columns
+         label=""
+         name="name"/>
+  </name_list>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   left="20"
+   top_pad="10"
+   name="av_nearby_desc2"
+   width="580">
+     You can also right-click on an avatar in-world to control display.
+  </text>
+  <button
+    height="23"
+    label="Exceptions..."
+    layout="topleft"
+    left="460"
+    top_delta="2"
+    name="exceptions_btn"
+    width="100">
+  </button>
+  <check_box
+    control_name="AlwaysRenderFriends"
+    height="16"
+    initial_value="true"
+    label="Always display friends in full detail"
+    label_text.text_color="White"
+    layout="topleft"
+    name="display_friends"
+    top_pad="3"
+    left="18"
+    width="256">
+  </check_box>
+  <view_border
+    bevel_style="in"
+    height="0"
+    layout="topleft"
+    name="border"
+    top_pad="15"
+    left="20"
+    width="540"/>
+  <check_box
+    height="16"
+    initial_value="true"
+    label="Hide avatars completely (good for landscape photos)"
+    layout="topleft"
+    name="hide_avatars"
+    top_delta="15"
+    left="18"
+    width="280">
+   </check_box>
+  <text
+   type="string"
+   length="1"
+   follows="left|top"
+   height="15"
+   layout="topleft"
+   left="20"
+   name="name_tags_textbox"
+   top_pad="10"
+   width="400">
+    Name tags:
+  </text>
+  <radio_group
+    control_name="AvatarNameTagMode"
+    height="20"
+    layout="topleft"
+    left="120"
+    top_delta="0"
+    name="name_tag_mode">
+    <radio_item
+     label="Off"
+     name="radio"
+     top_delta="20"
+     layout="topleft"
+     height="16"
+     left="0"
+     value="0"
+     width="75" />
+    <radio_item
+     label="On"
+     left_pad="0"
+     layout="topleft"
+     top_delta="0"
+     height="16"
+     name="radio2"
+     value="1"
+     width="75" />
+    <radio_item
+     label="Show briefly"
+     left_pad="0"
+     name="radio3"
+     height="16"
+     layout="topleft"
+     top_delta="0"
+     value="2"
+     width="160" />
+  </radio_group>
+ 
+</panel>
diff --git a/indra/newview/skins/default/xui/en/panel_performance_preferences.xml b/indra/newview/skins/default/xui/en/panel_performance_preferences.xml
new file mode 100644
index 0000000000000000000000000000000000000000..83db17b679c5c145adb97222908eb591af64c87e
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/panel_performance_preferences.xml
@@ -0,0 +1,494 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<panel
+ bevel_style="none"
+ follows="left|top"
+ height="580"
+ width="580"
+ name="panel_performance_preferences"
+ layout="topleft"
+ left="0"
+ top="0">
+  <button
+    height="16"
+    width="16"
+    layout="topleft"
+    mouse_opaque="true"
+    follows="left|top"
+    name="back_btn"
+    top="7"
+    image_selected="Arrow_Left_Off"
+    image_pressed="Arrow_Left_Off"
+    image_unselected="Arrow_Left_Off"
+    left="15"
+    is_toggle="true">
+  </button>
+  <text
+   follows="left|top"
+   height="18"
+   layout="topleft"
+   left_pad="0"
+   valign="center"
+   halign="center"
+   top="6"
+   name="back_lbl"
+   width="32">
+    Back
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifLarge"
+   text_color="white"
+   height="20"
+   layout="topleft"
+   left="20"
+   top_pad="15"
+   name="settings_title"
+   width="300">
+    Graphics settings
+  </text>
+  <button
+   follows="top|left"
+   height="23"
+   label="Open Advanced Settings"
+   layout="topleft"
+   left="360"
+   name="advanced_btn"
+   top_delta="-35"
+   width="200"/>
+  <button
+   follows="top|left"
+   height="23"
+   label="Reset to recommended settings"
+   layout="topleft"
+   left="350"
+   name="defaults_btn"
+   top_pad="10"
+   width="210"/>
+  <view_border
+   bevel_style="in"
+   height="0"
+   layout="topleft"
+   name="border0"
+   top_pad="8"
+   left="20"
+   width="540"/>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_pad="30"
+   name="quality_lbl"
+   width="100">
+    Quality &amp; Speed
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   left_pad="40"
+   name="fastest_lbl"
+   width="40">
+    Fastest
+  </text>
+  <radio_group
+   control_name="RenderQualityPerformance"
+   follows="top|left"
+   draw_border="false"
+   height="25"
+   layout="topleft"
+   left_pad="5"
+   name="graphics_quality"
+   top_delta="0"
+   width="243">
+    <radio_item
+     height="16"
+     layout="topleft"
+     left="3"
+     name="0"
+     top="0"
+     width="7" />
+    <radio_item
+     height="16"
+     layout="topleft"
+     left_pad="30"
+     name="1"
+     width="7" />
+    <radio_item
+     height="16"
+     layout="topleft"
+     left_pad="30"
+     name="2"
+     width="7" />
+    <radio_item
+     height="16"
+     layout="topleft"
+     left_pad="30"
+     name="3"
+     width="7" />
+    <radio_item
+     height="16"
+     layout="topleft"
+     left_pad="30"
+     name="4"
+     width="7" />
+    <radio_item
+     height="16"
+     layout="topleft"
+     left_pad="30"
+     name="5"
+     width="7" />
+    <radio_item
+     height="16"
+     layout="topleft"
+     left_pad="30"
+     name="6"
+     width="7" />
+  </radio_group>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   left_pad="10"
+   top_delta="1"
+   name="quality_lbl"
+   width="70">
+    Best quality
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_pad="15"
+   left="160"
+   name="quality_desc"
+   width="380">
+    Choosing a shortcut will reset all manual changes you have made.
+  </text>
+  <view_border
+    bevel_style="in"
+    height="0"
+    layout="topleft"
+    name="border2"
+    top_pad="5"
+    left="20"
+    width="540"/>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_pad="20"
+   left="20"
+   name="distance_lbl"
+   width="100">
+    Visibility distance
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   left_pad="40"
+   name="faster_lbl"
+   width="40">
+    Faster
+  </text>
+  <slider
+    control_name="RenderFarClip"
+    decimal_digits="0"
+    follows="left|top"
+    top_delta="-1"
+    height="16"
+    increment="8"
+    initial_value="160"
+    label_width="90"
+    layout="topleft"
+    min_val="64"
+    max_val="512"
+    name="draw_distance"
+    left_pad="5"
+    width="250">
+  </slider>
+  <text
+    type="string"
+    length="1"
+    follows="left|top"
+    height="12"
+    layout="topleft"
+    left_pad="1"
+    top_delta="0"
+    name="draw_distance_m"
+    width="20">
+    m
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   left_pad="10"
+   top_delta="1"
+   name="farther_lbl"
+   width="40">
+    Farther
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_pad="15"
+   left="160"
+   name="distance_desc1"
+   width="350">
+    To see more land when you zoom out, increase the distance.
+  </text>
+  <view_border
+    bevel_style="in"
+    height="0"
+    layout="topleft"
+    name="border3"
+    top_pad="5"
+    left="20"
+    width="540"/>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_pad="20"
+   left="20"
+   name="environment_lbl"
+   width="100">
+    Shadows
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_delta="0"
+   left="160"
+   name="enhancements_desc"
+   width="350">
+    Shadows significantly improve visual quality but can reduce speed.
+  </text>
+  <text
+    type="string"
+    length="1"
+    follows="left|top"
+    height="16"
+    layout="topleft"
+    left="160"
+    name="RenderShadowDetailText"
+    text_readonly_color="LabelDisabledColor"
+    top_pad="10"
+    width="128">
+    Shadow Detail:
+  </text>
+  <combo_box
+   control_name="RenderShadowDetail"
+   height="18"
+   layout="topleft"
+   left_delta="150"
+   top_delta="0"
+   name="ShadowDetail"
+   width="150">
+    <combo_box.item
+      label="None"
+      name="0"
+      value="0"/>
+    <combo_box.item
+      label="Sun/Moon"
+      name="1"
+      value="1"/>
+    <combo_box.item
+      label="Sun/Moon + Projectors"
+      name="2"
+      value="2"/>
+  </combo_box>
+  <view_border
+    bevel_style="in"
+    height="0"
+    layout="topleft"
+    name="border3"
+    top_pad="7"
+    left="20"
+    width="540"/>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_pad="20"
+   left="20"
+   name="water_lbl"
+   width="100">
+    Water
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_delta="0"
+   left="160"
+   name="water_desc"
+   width="380">
+    Reducing or turning off transparent water may improve frame rate.
+  </text>
+  <check_box
+    control_name="RenderTransparentWater"
+    height="16"
+    initial_value="true"
+    label="Transparent Water"
+    layout="topleft"
+    name="TransparentWater"
+    top_delta="24"
+    left="157"
+    width="280">
+  </check_box>
+  <view_border
+    bevel_style="in"
+    height="0"
+    layout="topleft"
+    name="border4"
+    top_pad="7"
+    left="20"
+    width="540"/>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_pad="20"
+   left="20"
+   name="photo_lbl"
+   width="100">
+    Photography
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   text_color="White"
+   height="18"
+   layout="topleft"
+   top_delta="0"
+   left="160"
+   name="photo_desc"
+   width="350">
+    Maximum detail is good for photos, but can slow frame rate.
+  </text>
+  <spinner
+   control_name="RenderVolumeLODFactor"
+   follows="left|top"
+   height="23"
+   increment="0.125"
+   label="Distance detail:"
+   label_width="95"
+   layout="topleft"
+   max_val="4"
+   min_val="0"
+   name="render_volume_lod"
+   top_pad="10"
+   width="150" />
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   height="18"
+   layout="topleft"
+   top_delta="3"
+   left_pad="10"
+   name="photo_desc"
+   width="180">
+      (Enter value between 0.0 and 4.0)
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   height="18"
+   layout="topleft"
+   top="80"
+   left="213"
+   name="1_lbl"
+   width="7">
+    1
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   height="18"
+   layout="topleft"
+   left_pad="31"
+   name="2_lbl"
+   width="7">
+    2
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   height="18"
+   layout="topleft"
+   left_pad="30"
+   name="3_lbl"
+   width="7">
+    3
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   height="18"
+   layout="topleft"
+   left_pad="30"
+   name="4_lbl"
+   width="7">
+    4
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   height="18"
+   layout="topleft"
+   left_pad="30"
+   name="5_lbl"
+   width="7">
+    5
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   height="18"
+   layout="topleft"
+   left_pad="30"
+   name="6_lbl"
+   width="7">
+      6
+  </text>
+  <text
+   follows="left|top"
+   font="SansSerifSmall"
+   height="18"
+   layout="topleft"
+   left_pad="30"
+   name="7_lbl"
+   width="7">
+    7
+  </text>
+</panel>
diff --git a/indra/newview/skins/default/xui/en/panel_preferences_graphics1.xml b/indra/newview/skins/default/xui/en/panel_preferences_graphics1.xml
index 38d364cf9a612646772acf64042a96e34e45ba7b..74a44b6b090948ca3d9309acd124fbc804da6035 100644
--- a/indra/newview/skins/default/xui/en/panel_preferences_graphics1.xml
+++ b/indra/newview/skins/default/xui/en/panel_preferences_graphics1.xml
@@ -231,6 +231,18 @@
      m
   </text>
 
+  <button
+  height="23"
+  label="Automatic adjustments settings"
+  layout="topleft"
+  left="30"
+  name="AutoAdjustmentsButton"
+  top_delta="30"
+  width="200">
+    <button.commit_callback
+      function="Pref.AutoAdjustments"/>
+  </button>
+  
   <slider
     control_name="IndirectMaxComplexity"
     tool_tip="Controls at what point a visually complex avatar is drawn as a JellyDoll"
@@ -246,7 +258,7 @@
     max_val="101"
     name="IndirectMaxComplexity"
     show_text="false"
-    top_delta="36"
+    top_delta="40"
     width="300">
     <slider.commit_callback
       function="Pref.UpdateIndirectMaxComplexity"
diff --git a/indra/newview/skins/default/xui/en/panel_presets_pulldown.xml b/indra/newview/skins/default/xui/en/panel_presets_pulldown.xml
index b87dda2315e9f2819c0e2d0c4c82da0ac17392af..b3d165c4fd2e824c03486f7489fd425aaffa7a89 100644
--- a/indra/newview/skins/default/xui/en/panel_presets_pulldown.xml
+++ b/indra/newview/skins/default/xui/en/panel_presets_pulldown.xml
@@ -8,7 +8,7 @@
  border="false"
  chrome="true"
  follows="bottom"
- height="155"
+ height="185"
  layout="topleft"
  name="presets_pulldown"
  width="225">
@@ -57,7 +57,7 @@
     width="215" />
   <button
     name="open_prefs_btn"
-    label="Open Graphics Preferences"
+    label="Graphics Preferences"
     tool_tip = "Bring up graphics prefs"
     top_delta="5"
     left="15"
@@ -66,4 +66,15 @@
     <button.commit_callback
       function="Presets.GoGraphicsPrefs" />
   </button>
+  <button
+    name="open_autofps_btn"
+    label="Auto-FPS settings"
+    tool_tip = "Bring up auto-adjust settings"
+    top_pad="5"
+    left="15"
+    height="20"
+    width="200">
+    <button.commit_callback
+     function="Presets.GoAutofpsPrefs" />
+  </button>
 </panel>
diff --git a/indra/newview/skins/default/xui/en/panel_settings_sky_atmos.xml b/indra/newview/skins/default/xui/en/panel_settings_sky_atmos.xml
index 622c9992543f0510899f56c1d92947feafa9411a..e90e6caf8423e75d47e76b599d937717e5407317 100644
--- a/indra/newview/skins/default/xui/en/panel_settings_sky_atmos.xml
+++ b/indra/newview/skins/default/xui/en/panel_settings_sky_atmos.xml
@@ -223,7 +223,7 @@
                             left_delta="-5"
                             top_delta="25"
                             width="80">
-                        Scene Gamma:
+                        Brightness:
                     </text>
                     <slider
                             decimal_digits="2"
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index 62b26da55289e237b71399df180fbed2df0d1a55..9c634b661a204733e0d87f0a45c4e40643419aef 100644
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -4163,6 +4163,7 @@ name="Command_360_Capture_Label">360 snapshot</string>
   <string name="Command_MiniMap_Label">Mini-map</string>
   <string name="Command_Move_Label">Walk / run / fly</string>
   <string name="Command_People_Label">People</string>
+  <string name="Command_Performance_Label">Graphics speed</string>
   <string name="Command_Picks_Label">Picks</string>
   <string name="Command_Places_Label">Places</string>
   <string name="Command_Preferences_Label">Preferences</string>
@@ -4195,6 +4196,7 @@ name="Command_360_Capture_Tooltip">Capture a 360 equirectangular image</string>
   <string name="Command_MiniMap_Tooltip">Show nearby people</string>
   <string name="Command_Move_Tooltip">Moving your avatar</string>
   <string name="Command_People_Tooltip">Friends, groups, and nearby people</string>
+  <string name="Command_Performance_Tooltip">Improve graphics speed</string>
   <string name="Command_Picks_Tooltip">Places to show as favorites in your profile</string>
   <string name="Command_Places_Tooltip">Places you've saved</string>
   <string name="Command_Preferences_Tooltip">Preferences</string>
@@ -4296,7 +4298,7 @@ name="Command_360_Capture_Tooltip">Capture a 360 equirectangular image</string>
   <string name="preset_combo_label">-Empty list-</string>
   <string name="Default">Default</string>
   <string name="none_paren_cap">(None)</string>
-  <string name="no_limit">No Limit</string>
+  <string name="no_limit">No limit</string>
   
   <string name="Mav_Details_MAV_FOUND_DEGENERATE_TRIANGLES">
       The physics shape contains triangles which are too small. Try simplifying the physics model.
diff --git a/indra/test/llsdutil_tut.cpp b/indra/test/llsdutil_tut.cpp
index 6fce53f335143b5e57eec287a14dbed3684aa716..22efd5439a3f66784434aea23008643741539bb3 100644
--- a/indra/test/llsdutil_tut.cpp
+++ b/indra/test/llsdutil_tut.cpp
@@ -404,28 +404,28 @@ namespace tut
             ensure("hash: equivalent values but different types do not match.", boost::hash<LLSD>{}(data_r1) != boost::hash<LLSD>{}(data_i1));
         }
         {
-            LLSD data_a1 = LLSDArray("A")("B")("C");
-            LLSD data_a2 = LLSDArray("A")("B")("C");
+            LLSD data_a1 = llsd::array("A", "B", "C");
+            LLSD data_a2 = llsd::array("A", "B", "C");
 
             ensure("hash: identical arrays produce identical results", boost::hash<LLSD>{}(data_a1) == boost::hash<LLSD>{}(data_a2));
 
-            data_a2.append(LLSDArray(1)(2));
+            data_a2.append(llsd::array(1, 2));
 
             ensure("hash: changing the array changes the hash.", boost::hash<LLSD>{}(data_a1) != boost::hash<LLSD>{}(data_a2));
 
-            data_a1.append(LLSDArray(1)(2));
+            data_a1.append(llsd::array(1, 2));
             ensure("hash: identical arrays produce identical results with nested arrays", boost::hash<LLSD>{}(data_a1) == boost::hash<LLSD>{}(data_a2));
         }
         {
-            LLSD data_m1 = LLSDMap("key1", LLSD::Real(3.0))("key2", "value2")("key3", LLSDArray(1)(2)(3));
-            LLSD data_m2 = LLSDMap("key1", LLSD::Real(3.0))("key2", "value2")("key3", LLSDArray(1)(2)(3));
+            LLSD data_m1 = LLSDMap("key1", LLSD::Real(3.0))("key2", "value2")("key3", llsd::array(1, 2, 3));
+            LLSD data_m2 = LLSDMap("key1", LLSD::Real(3.0))("key2", "value2")("key3", llsd::array(1, 2, 3));
 
             ensure("hash: identical maps produce identical results", boost::hash<LLSD>{}(data_m1) == boost::hash<LLSD>{}(data_m2));
 
-            LLSD data_m3 = LLSDMap("key1", LLSD::Real(5.0))("key2", "value2")("key3", LLSDArray(1)(2)(3));
+            LLSD data_m3 = LLSDMap("key1", LLSD::Real(5.0))("key2", "value2")("key3", llsd::array(1, 2, 3));
             ensure("hash: Different values in the map produce different hashes.", boost::hash<LLSD>{}(data_m1) != boost::hash<LLSD>{}(data_m3));
 
-            LLSD data_m4 = LLSDMap("keyA", LLSD::Real(3.0))("key2", "value2")("key3", LLSDArray(1)(2)(3));
+            LLSD data_m4 = LLSDMap("keyA", LLSD::Real(3.0))("key2", "value2")("key3", llsd::array(1, 2, 3));
             ensure("hash: Different keys in the map produce different hashes.", boost::hash<LLSD>{}(data_m1) != boost::hash<LLSD>{}(data_m4));
 
         }