diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index ca8b5e946f4ad17cc91e946b100b49344d691878..4dbf1282c4acef3bad30c7f9f2e4f94627d3f366 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -29,6 +29,7 @@ include_directories(
 #     ${LLCOMMON_LIBRARIES})
 
 set(llcommon_SOURCE_FILES
+    commoncontrol.cpp
     indra_constants.cpp
     llallocator.cpp
     llallocator_heap_profile.cpp
@@ -129,6 +130,7 @@ set(llcommon_HEADER_FILES
     CMakeLists.txt
 
     chrono.h
+    commoncontrol.h
     ctype_workaround.h
     fix_macros.h
     indra_constants.h
diff --git a/indra/llcommon/commoncontrol.cpp b/indra/llcommon/commoncontrol.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2c2a2abeb0e8f0d2dff1297fddacd923175b3ab5
--- /dev/null
+++ b/indra/llcommon/commoncontrol.cpp
@@ -0,0 +1,104 @@
+/**
+ * @file   commoncontrol.cpp
+ * @author Nat Goodspeed
+ * @date   2022-06-08
+ * @brief  Implementation for commoncontrol.
+ * 
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Copyright (c) 2022, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "commoncontrol.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+#include "llevents.h"
+#include "llsdutil.h"
+
+LLSD LL::CommonControl::access(const LLSD& params)
+{
+    // We can't actually introduce a link-time dependency on llxml, or on any
+    // global LLControlGroup (*koff* gSavedSettings *koff*) but we can issue a
+    // runtime query. If we're running as part of a viewer with
+    // LLViewerControlListener, we can use that to interact with any
+    // instantiated LLControGroup.
+    LLSD response;
+    {
+        LLEventStream reply("reply");
+        LLTempBoundListener connection = reply.listen("listener",
+                     [&response] (const LLSD& event)
+                     {
+                         response = event;
+                         return false;
+                     });
+        LLSD rparams{ params };
+        rparams["reply"] = reply.getName();
+        LLEventPumps::instance().obtain("LLViewerControl").post(rparams);
+    }
+    // LLViewerControlListener responds immediately. If it's listening at all,
+    // it will already have set response.
+    if (! response.isDefined())
+    {
+        LLTHROW(NoListener("No LLViewerControl listener instantiated"));
+    }
+    LLSD error{ response["error"] };
+    if (error.isDefined())
+    {
+        LLTHROW(ParamError(error));
+    }
+    return response;
+}
+
+/// set control group.key to defined default value
+LLSD LL::CommonControl::set_default(const std::string& group, const std::string& key)
+{
+    return access(llsd::map("op", "set",
+                            "group", group, "key", key))["value"];
+}
+
+/// set control group.key to specified value
+LLSD LL::CommonControl::set(const std::string& group, const std::string& key, const LLSD& value)
+{
+    return access(llsd::map("op", "set",
+                            "group", group, "key", key, "value", value))["value"];
+}
+
+/// toggle boolean control group.key
+LLSD LL::CommonControl::toggle(const std::string& group, const std::string& key)
+{
+    return access(llsd::map("op", "toggle",
+                            "group", group, "key", key))["value"];
+}
+
+/// get the definition for control group.key, (! isDefined()) if bad
+/// ["name"], ["type"], ["value"], ["comment"]
+LLSD LL::CommonControl::get_def(const std::string& group, const std::string& key)
+{
+    return access(llsd::map("op", "get",
+                            "group", group, "key", key));
+}
+
+/// get the value of control group.key
+LLSD LL::CommonControl::get(const std::string& group, const std::string& key)
+{
+    return access(llsd::map("op", "get",
+                            "group", group, "key", key))["value"];
+}
+
+/// get defined groups
+std::vector<std::string> LL::CommonControl::get_groups()
+{
+    auto groups{ access(llsd::map("op", "groups"))["groups"] };
+    return { groups.beginArray(), groups.endArray() };
+}
+
+/// get definitions for all variables in group
+LLSD LL::CommonControl::get_vars(const std::string& group)
+{
+    return access(llsd::map("op", "vars", "group", group))["vars"];
+}
diff --git a/indra/llcommon/commoncontrol.h b/indra/llcommon/commoncontrol.h
new file mode 100644
index 0000000000000000000000000000000000000000..07d4a45ac5adc15351a847398b5711d7c119e359
--- /dev/null
+++ b/indra/llcommon/commoncontrol.h
@@ -0,0 +1,75 @@
+/**
+ * @file   commoncontrol.h
+ * @author Nat Goodspeed
+ * @date   2022-06-08
+ * @brief  Access LLViewerControl LLEventAPI, if process has one.
+ * 
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Copyright (c) 2022, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_COMMONCONTROL_H)
+#define LL_COMMONCONTROL_H
+
+#include <vector>
+#include "llexception.h"
+#include "llsd.h"
+
+namespace LL
+{
+    class CommonControl
+    {
+    public:
+        struct Error: public LLException
+        {
+            Error(const std::string& what): LLException(what) {}
+        };
+
+        /// Exception thrown if there's no LLViewerControl LLEventAPI
+        struct NoListener: public Error
+        {
+            NoListener(const std::string& what): Error(what) {}
+        };
+
+        struct ParamError: public Error
+        {
+            ParamError(const std::string& what): Error(what) {}
+        };
+
+        /// set control group.key to defined default value
+        static
+        LLSD set_default(const std::string& group, const std::string& key);
+
+        /// set control group.key to specified value
+        static
+        LLSD set(const std::string& group, const std::string& key, const LLSD& value);
+
+        /// toggle boolean control group.key
+        static
+        LLSD toggle(const std::string& group, const std::string& key);
+
+        /// get the definition for control group.key, (! isDefined()) if bad
+        /// ["name"], ["type"], ["value"], ["comment"]
+        static
+        LLSD get_def(const std::string& group, const std::string& key);
+
+        /// get the value of control group.key
+        static
+        LLSD get(const std::string& group, const std::string& key);
+
+        /// get defined groups
+        static
+        std::vector<std::string> get_groups();
+
+        /// get definitions for all variables in group
+        static
+        LLSD get_vars(const std::string& group);
+
+    private:
+        static
+        LLSD access(const LLSD& params);
+    };
+} // namespace LL
+
+#endif /* ! defined(LL_COMMONCONTROL_H) */
diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp
index ba914035e200a9ecfaee13c010e2f6fe32863af1..e8daf549efd42a6c207f757c13205d1fad80c440 100644
--- a/indra/llcommon/threadpool.cpp
+++ b/indra/llcommon/threadpool.cpp
@@ -17,14 +17,17 @@
 // std headers
 // external library headers
 // other Linden headers
+#include "commoncontrol.h"
 #include "llerror.h"
 #include "llevents.h"
+#include "llsd.h"
 #include "stringize.h"
 
 LL::ThreadPool::ThreadPool(const std::string& name, size_t threads, size_t capacity):
+    super(name),
     mQueue(name, capacity),
     mName("ThreadPool:" + name),
-    mThreadCount(threads)
+    mThreadCount(getConfiguredWidth(name, threads))
 {}
 
 void LL::ThreadPool::start()
@@ -86,3 +89,49 @@ void LL::ThreadPool::run()
 {
     mQueue.runUntilClose();
 }
+
+//static
+size_t LL::ThreadPool::getConfiguredWidth(const std::string& name, size_t dft)
+{
+    LLSD poolSizes{ LL::CommonControl::get("Global", "ThreadPoolSizes") };
+    // "ThreadPoolSizes" is actually a map containing the sizes of interest --
+    // or should be, if this process has an "LLViewerControl" LLEventAPI
+    // instance and its settings include "ThreadPoolSizes". If we failed to
+    // retrieve it, perhaps we're in a program that doesn't define that, or
+    // perhaps there's no such setting, or perhaps we're asking too early,
+    // before the LLEventAPI itself has been instantiated. In any of those
+    // cases, it seems worth warning.
+    if (! poolSizes.isDefined())
+    {
+        // Note: we don't warn about absence of an override key for a
+        // particular ThreadPool name, that's fine. This warning is about
+        // complete absence of a ThreadPoolSizes setting, which we expect in a
+        // normal viewer session.
+        LL_WARNS("ThreadPool") << "No 'ThreadPoolSizes' setting for ThreadPool '"
+                               << name << "'" << LL_ENDL;
+    }
+    else
+    {
+        LL_DEBUGS("ThreadPool") << "ThreadPoolSizes = " << poolSizes << LL_ENDL;
+    }
+    // LLSD treats an undefined value as an empty map when asked to retrieve a
+    // key, so we don't need this to be conditional.
+    LLSD sizeSpec{ poolSizes[name] };
+    // We retrieve sizeSpec as LLSD, rather than immediately as LLSD::Integer,
+    // so we can distinguish the case when it's undefined.
+    return sizeSpec.isInteger() ? sizeSpec.asInteger() : dft;
+}
+
+//static
+size_t LL::ThreadPool::getWidth(const std::string& name, size_t dft)
+{
+    auto instance{ getInstance(name) };
+    if (instance)
+    {
+        return instance->getWidth();
+    }
+    else
+    {
+        return getConfiguredWidth(name, dft);
+    }
+}
diff --git a/indra/llcommon/threadpool.h b/indra/llcommon/threadpool.h
index b79c9b90903e6cec030608f5dc097bf5cba00575..b49d511257b627fd4176f49564d10399bb4d3544 100644
--- a/indra/llcommon/threadpool.h
+++ b/indra/llcommon/threadpool.h
@@ -22,14 +22,25 @@
 namespace LL
 {
 
-    class ThreadPool
+    class ThreadPool: public LLInstanceTracker<ThreadPool, std::string>
     {
+    private:
+        using super = LLInstanceTracker<ThreadPool, std::string>;
     public:
         /**
          * Pass ThreadPool a string name. This can be used to look up the
          * relevant WorkQueue.
+         *
+         * The number of threads you pass sets the compile-time default. But
+         * if the user has overridden the LLSD map in the "ThreadPoolSizes"
+         * setting with a key matching this ThreadPool name, that setting
+         * overrides this parameter.
+         *
+         * Pass an explicit capacity to limit the size of the queue.
+         * Constraining the queue can cause a submitter to block. Do not
+         * constrain any ThreadPool accepting work from the main thread.
          */
-        ThreadPool(const std::string& name, size_t threads=1, size_t capacity=1024);
+        ThreadPool(const std::string& name, size_t threads=1, size_t capacity=1024*1024);
         virtual ~ThreadPool();
 
         /**
@@ -57,6 +68,25 @@ namespace LL
          */
         virtual void run();
 
+        /**
+         * getConfiguredWidth() returns the setting, if any, for the specified
+         * ThreadPool name. Returns dft if the "ThreadPoolSizes" map does not
+         * contain the specified name.
+         */
+        static
+        size_t getConfiguredWidth(const std::string& name, size_t dft=0);
+
+        /**
+         * This getWidth() returns the width of the instantiated ThreadPool
+         * with the specified name, if any. If no instance exists, returns its
+         * getConfiguredWidth() if any. If there's no instance and no relevant
+         * override, return dft. Presumably dft should match the threads
+         * parameter passed to the ThreadPool constructor call that will
+         * eventually instantiate the ThreadPool with that name.
+         */
+        static
+        size_t getWidth(const std::string& name, size_t dft);
+
     private:
         void run(const std::string& name);
 
diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp
index 46f0e1d69ed438383dbcc2e471802270d1f447a0..ae10142e7a9a081a7cbd584317a3a3825ce70e36 100644
--- a/indra/llrender/llimagegl.cpp
+++ b/indra/llrender/llimagegl.cpp
@@ -2444,10 +2444,8 @@ void LLImageGL::checkActiveThread()
 */  
 
 LLImageGLThread::LLImageGLThread(LLWindow* window)
-    // We want exactly one thread, but a very large capacity: we never want
-    // anyone, especially inner-loop render code, to have to block on post()
-    // because we're full.
-    : ThreadPool("LLImageGL", 1, 1024*1024)
+    // We want exactly one thread.
+    : ThreadPool("LLImageGL", 1)
     , mWindow(window)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt
index 826f5ce030e080c48cef80aae0c10216f954a285..09a7391e4e373ff475cb5093c88cbbbc7210dc27 100644
--- a/indra/newview/VIEWER_VERSION.txt
+++ b/indra/newview/VIEWER_VERSION.txt
@@ -1 +1 @@
-6.6.0
+6.6.1
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 6426964190975c52476eb063b9239f3514e290bc..3201848f38dd8ec91b664b46de39da65ff72d32f 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -12645,7 +12645,7 @@
       <key>Value</key>
       <map>
         <key>General</key>
-        <integer>4</integer>
+        <integer>1</integer>
       </map>
     </map>
     <key>ThrottleBandwidthKBPS</key>
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index d7ed2bb4df282c2438b909c517d652534d0e5b5b..bc38a96ef28b2e630191fe66e79cb18bc3e32cac 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -2174,14 +2174,7 @@ void LLAppViewer::initGeneralThread()
         return;
     }
 
-    LLSD poolSizes{ gSavedSettings.getLLSD("ThreadPoolSizes") };
-    LLSD sizeSpec{ poolSizes["General"] };
-    LLSD::Integer poolSize{ sizeSpec.isInteger() ? sizeSpec.asInteger() : 3 };
-    LL_DEBUGS("ThreadPool") << "Instantiating General pool with "
-        << poolSize << " threads" << LL_ENDL;
-    // We don't want anyone, especially the main thread, to have to block
-    // due to this ThreadPool being full.
-    mGeneralThreadPool = new LL::ThreadPool("General", poolSize, 1024 * 1024);
+    mGeneralThreadPool = new LL::ThreadPool("General", 3);
     mGeneralThreadPool->start();
 }