summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMcGroarty <git@mcgroarty.me>2026-07-27 12:59:10 -0700
committerGitHub <noreply@github.com>2026-07-27 12:59:10 -0700
commit21939024cdee694bf8e98b79c3ba929ffc2fef5e (patch)
tree450fbbd27c07fdcc7236d1e782af0da7bb88a022
parentd1796fba2500a30bd3c71b0588efd18c1a8625bf (diff)
parent1c81813e79c4acc917d75b47117778dd277e5b1f (diff)
Merge pull request #1 from secondlife/develop
Fix UI issues, improve warnings, and enhance process handling
-rw-r--r--indra/llcommon/llapr.cpp30
-rw-r--r--indra/llcommon/llprocess.cpp92
-rw-r--r--indra/llcommon/tests/llprocess_test.cpp250
-rw-r--r--indra/llrender/CMakeLists.txt5
-rw-r--r--indra/llrender/llgl.cpp9
-rw-r--r--indra/llrender/llimagegl.cpp309
-rw-r--r--indra/llrender/llimagegl.h17
-rw-r--r--indra/llrender/tests/llimagegl_prepare_test.cpp118
-rw-r--r--indra/llui/lltoolbar.cpp52
-rw-r--r--indra/llui/lltoolbar.h1
-rw-r--r--indra/newview/llfloaterland.cpp30
-rw-r--r--indra/newview/llfloaterland.h2
-rw-r--r--indra/newview/llfloatersnapshot.cpp77
-rw-r--r--indra/newview/llfloatersnapshot.h2
-rw-r--r--indra/newview/llgltfmateriallist.cpp2
-rw-r--r--indra/newview/llgroupmgr.cpp10
-rw-r--r--indra/newview/llimprocessing.cpp3
-rw-r--r--indra/newview/lllogininstance.cpp2
-rw-r--r--indra/newview/llpanelgrouproles.cpp6
-rw-r--r--indra/newview/llpanellandmedia.cpp7
-rw-r--r--indra/newview/llpanellogin.cpp2
-rw-r--r--indra/newview/llpanelsnapshot.cpp13
-rw-r--r--indra/newview/llviewerassetstorage.cpp6
-rw-r--r--indra/newview/llviewernetwork.cpp3
-rw-r--r--indra/newview/llviewertexture.cpp52
-rw-r--r--indra/newview/skins/default/xui/en/floater_about_land.xml24
-rw-r--r--indra/newview/skins/default/xui/en/floater_snapshot.xml1
27 files changed, 884 insertions, 241 deletions
diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp
index eeff2694a7..06eb4c389f 100644
--- a/indra/llcommon/llapr.cpp
+++ b/indra/llcommon/llapr.cpp
@@ -231,9 +231,35 @@ bool _ll_apr_warn_status(apr_status_t status, const char* file, int line)
{
if(APR_SUCCESS == status) return false;
#if !LL_LINUX
- char buf[MAX_STRING]; /* Flawfinder: ignore */
+ char buf[MAX_STRING];
apr_strerror(status, buf, sizeof(buf));
- LL_WARNS("APR") << "APR: " << file << ":" << line << " " << buf << LL_ENDL;
+
+#ifdef LL_WINDOWS
+ // On Windows, APR error strings may be in the system's ANSI code page (e.g., Cyrillic)
+ // Convert to UTF-8 for proper logging
+ std::string error_msg = buf;
+ int wlen = MultiByteToWideChar(CP_ACP, 0, buf, -1, nullptr, 0);
+ if (wlen > 0)
+ {
+ std::wstring wbuf(wlen, L'\0');
+ MultiByteToWideChar(CP_ACP, 0, buf, -1, &wbuf[0], wlen);
+
+ int utf8len = WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), -1, nullptr, 0, nullptr, nullptr);
+ if (utf8len > 0)
+ {
+ std::string utf8buf(utf8len, '\0');
+ WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), -1, &utf8buf[0], utf8len, nullptr, nullptr);
+ error_msg = utf8buf.c_str(); // Remove null terminator
+ }
+ LL_WARNS("APR") << "APR: " << file << ":" << line << " " << error_msg << " (0x" << std::hex << status << std::dec << ")" << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("APR") << "APR: " << file << ":" << line << " " << buf << " (0x" << std::hex << status << std::dec << ")" << LL_ENDL;
+ }
+#else
+ LL_WARNS("APR") << "APR: " << file << ":" << line << " " << buf << " (0x" << std::hex << status << std::dec << ")" << LL_ENDL;
+#endif
#endif
return true;
}
diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp
index 730c85ef5f..a71589a2bf 100644
--- a/indra/llcommon/llprocess.cpp
+++ b/indra/llcommon/llprocess.cpp
@@ -63,6 +63,87 @@
#include <sys/wait.h>
#endif
+#if LL_WINDOWS
+#include <windows.h>
+#include "llwin32headers.h"
+#include <mutex>
+
+namespace {
+ // Global job object that will kill all child processes when parent terminates
+ HANDLE g_jobObject = NULL;
+ bool g_jobObjectInitialized = false;
+ std::mutex g_jobObjectMutex;
+
+ void InitializeJobObject()
+ {
+ if (g_jobObjectInitialized)
+ return;
+ std::lock_guard<std::mutex> lock(g_jobObjectMutex);
+ if (g_jobObjectInitialized)
+ return;
+
+ g_jobObjectInitialized = true;
+
+ // Create a job object
+ g_jobObject = ::CreateJobObjectW(NULL, NULL);
+ if (g_jobObject == NULL)
+ {
+ LL_WARNS("LLProcess") << "Failed to create job object: " << ::GetLastError() << LL_ENDL;
+ return;
+ }
+
+ // Configure the job to kill all processes when the last handle closes
+ // (i.e., when the parent process exits)
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
+ jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+
+ // Windows 8+ supports nested jobs (for VS studio)
+ jeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK;
+
+ if (!::SetInformationJobObject(g_jobObject, JobObjectExtendedLimitInformation,
+ &jeli, sizeof(jeli)))
+ {
+ LL_WARNS("LLProcess") << "Failed to set job object limits: " << ::GetLastError() << LL_ENDL;
+ ::CloseHandle(g_jobObject);
+ g_jobObject = NULL;
+ return;
+ }
+
+ LL_INFOS("LLProcess") << "Job object created - child processes will terminate with parent" << LL_ENDL;
+ }
+
+ void AssignProcessToJob(HANDLE hProcess, const std::string& desc)
+ {
+ if (!g_jobObjectInitialized)
+ InitializeJobObject();
+
+ if (g_jobObject != NULL)
+ {
+ if (!::AssignProcessToJobObject(g_jobObject, hProcess))
+ {
+ DWORD error = ::GetLastError();
+ // ERROR_ACCESS_DENIED (5) means the process is already in a job
+ // This can happen if the parent viewer is itself in a job
+ if (error == ERROR_ACCESS_DENIED)
+ {
+ LL_WARNS("LLProcess") << "Autokill requested but process " << desc
+ << " is already in a job object (ERROR_ACCESS_DENIED)"
+ << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("LLProcess") << "Failed to assign process " << desc
+ << " to job object: error " << error << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_DEBUGS("LLProcess") << "Process " << desc << " assigned to job object" << LL_ENDL;
+ }
+ }
+ }
+}
+#endif
namespace bp = boost::process::v2;
namespace asio = boost::asio;
@@ -653,7 +734,16 @@ void LLProcess::launch(const LLSDOrParams& params)
<< ": " << ex.what()));
}
-#if !LL_WINDOWS
+#if LL_WINDOWS
+ // Add the process to the job object so it terminates when parent dies.
+ // This is done for all processes with autokill=true (the default).
+ // Job objects are the Windows-recommended way to ensure child processes
+ // don't become orphaned if the parent crashes or is killed.
+ if (mAutokill && mChild)
+ {
+ AssignProcessToJob(mChild->native_handle(), mDesc);
+ }
+#else
// boost::process v2 may install a SIGCHLD handler via boost::asio
// without SA_RESTART when mIOContext is passed to bp::process.
// Without SA_RESTART, blocking waitpid() calls elsewhere in the
diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp
index 84f6c131e7..00dcd52d5d 100644
--- a/indra/llcommon/tests/llprocess_test.cpp
+++ b/indra/llcommon/tests/llprocess_test.cpp
@@ -110,6 +110,19 @@ static std::string readfile(const std::string& pathname, const std::string& desc
return output;
}
+#if LL_WINDOWS
+static std::string readfile_if_present(const std::string& pathname)
+{
+ std::ifstream inf(pathname.c_str());
+ if (!inf.is_open())
+ {
+ return "";
+ }
+ return std::string((std::istreambuf_iterator<char>(inf)),
+ std::istreambuf_iterator<char>());
+}
+#endif
+
/// Looping on LLProcess::isRunning() must now be accompanied by pumping
/// "mainloop" -- otherwise the status won't update and you get an infinite
/// loop.
@@ -251,6 +264,215 @@ struct PythonProcessLauncher
NamedExtTempFile mScript;
};
+#if LL_WINDOWS
+namespace
+{
+ static constexpr const char* AUTOKILL_HELPER_SCRIPT_ENV = "LLPROCESS_AUTOKILL_HELPER_SCRIPT";
+ static constexpr const char* AUTOKILL_HELPER_PIDFILE_ENV = "LLPROCESS_AUTOKILL_HELPER_PIDFILE";
+ static constexpr const char* AUTOKILL_HELPER_RELEASE_ENV = "LLPROCESS_AUTOKILL_HELPER_RELEASE";
+ static constexpr const char* AUTOKILL_HELPER_COUNT_ENV = "LLPROCESS_AUTOKILL_HELPER_COUNT";
+ static constexpr int AUTOKILL_HELPER_RELEASE_TIMEOUT_SECONDS = 60;
+ static constexpr int AUTOKILL_HELPER_PID_TIMEOUT_SECONDS = 15;
+ static constexpr DWORD AUTOKILL_HELPER_POLL_INTERVAL_MS = 100;
+ static constexpr DWORD AUTOKILL_CHILD_TERMINATION_TIMEOUT_MS = 5000;
+ static constexpr int AUTOKILL_HELPER_INVALID_ENV_EXIT = 2;
+ static constexpr int AUTOKILL_HELPER_INVALID_COUNT_EXIT = 3;
+ static constexpr int AUTOKILL_HELPER_LAUNCH_FAILURE_EXIT = 4;
+
+ struct ScopedEnvironmentVariable
+ {
+ ScopedEnvironmentVariable(const char* name, const std::string& value):
+ mName(name),
+ mHadValue(false)
+ {
+ DWORD size = GetEnvironmentVariableA(name, nullptr, 0);
+ if (size > 0)
+ {
+ std::vector<char> buffer(size);
+ DWORD copied = GetEnvironmentVariableA(name, buffer.data(), size);
+ if (copied > 0)
+ {
+ mHadValue = true;
+ mOldValue.assign(buffer.data(), copied);
+ }
+ }
+ SetEnvironmentVariableA(name, value.c_str());
+ }
+
+ ~ScopedEnvironmentVariable()
+ {
+ SetEnvironmentVariableA(mName.c_str(), mHadValue ? mOldValue.c_str() : nullptr);
+ }
+
+ std::string mName;
+ std::string mOldValue;
+ bool mHadValue;
+ };
+
+ std::string get_current_executable_path()
+ {
+ std::vector<char> buffer(MAX_PATH);
+ for (;;)
+ {
+ DWORD length = GetModuleFileNameA(nullptr, buffer.data(), static_cast<DWORD>(buffer.size()));
+ tut::ensure("GetModuleFileNameA() failed", length > 0);
+ if (length < buffer.size() &&
+ (length < buffer.size() - 1 || buffer[length] == '\0'))
+ {
+ return std::string(buffer.data(), length);
+ }
+ buffer.resize(buffer.size() * 2);
+ }
+ }
+
+ void run_autokill_helper_from_environment()
+ {
+ const std::string script = LLStringUtil::getenv(AUTOKILL_HELPER_SCRIPT_ENV);
+ if (script.empty())
+ {
+ return;
+ }
+
+ const std::string pidfile = LLStringUtil::getenv(AUTOKILL_HELPER_PIDFILE_ENV);
+ const std::string releasefile = LLStringUtil::getenv(AUTOKILL_HELPER_RELEASE_ENV);
+ const std::string countstr = LLStringUtil::getenv(AUTOKILL_HELPER_COUNT_ENV);
+ const std::string python = LLStringUtil::getenv("PYTHON");
+ int child_count = 1;
+ if (!countstr.empty())
+ {
+ try
+ {
+ child_count = std::stoi(countstr);
+ }
+ catch (const std::exception& err)
+ {
+ LL_WARNS("LLProcess") << "Invalid autokill helper child count '"
+ << countstr << "': " << err.what() << LL_ENDL;
+ std::exit(AUTOKILL_HELPER_INVALID_COUNT_EXIT);
+ }
+ }
+
+ if (pidfile.empty() || releasefile.empty() || python.empty() || child_count < 1)
+ {
+ std::exit(AUTOKILL_HELPER_INVALID_ENV_EXIT);
+ }
+
+ std::vector<LLProcessPtr> children;
+ children.reserve(child_count);
+ std::ofstream out(pidfile.c_str(), std::ios::trunc);
+ for (int i = 0; i < child_count; ++i)
+ {
+ LLProcess::Params params;
+ params.executable = python;
+ params.args.add(script);
+ params.autokill = true;
+ params.attached = false;
+ LLProcessPtr child = LLProcess::create(params);
+ if (!child)
+ {
+ std::exit(AUTOKILL_HELPER_LAUNCH_FAILURE_EXIT);
+ }
+ children.push_back(child);
+ out << child->getProcessID() << '\n';
+ }
+ out.flush();
+ out.close();
+
+ for (DWORD elapsed_ms = 0;
+ elapsed_ms < AUTOKILL_HELPER_RELEASE_TIMEOUT_SECONDS * 1000;
+ elapsed_ms += AUTOKILL_HELPER_POLL_INTERVAL_MS)
+ {
+ if (readfile_if_present(releasefile) == "exit")
+ {
+ break;
+ }
+ Sleep(AUTOKILL_HELPER_POLL_INTERVAL_MS);
+ }
+
+ // Exit the helper process itself so the job handle closes and Windows
+ // terminates the autokilled children.
+ std::exit(0);
+ }
+
+ std::vector<DWORD> wait_for_helper_pids(
+ const std::string& pidfile,
+ int expected_count,
+ int timeout = AUTOKILL_HELPER_PID_TIMEOUT_SECONDS)
+ {
+ for (int i = 0;
+ i < (timeout * 1000) / static_cast<int>(AUTOKILL_HELPER_POLL_INTERVAL_MS);
+ ++i)
+ {
+ std::ifstream inf(pidfile.c_str());
+ std::vector<DWORD> pids;
+ DWORD pid = 0;
+ while (inf >> pid)
+ {
+ pids.push_back(pid);
+ }
+ if (static_cast<int>(pids.size()) == expected_count)
+ {
+ return pids;
+ }
+ Sleep(AUTOKILL_HELPER_POLL_INTERVAL_MS);
+ LLEventPumps::instance().obtain("mainloop").post(LLSD());
+ }
+ tut::ensure(STRINGIZE("expected " << expected_count
+ << " child pids within " << timeout
+ << " seconds"), false);
+ return {};
+ }
+
+ void verify_autokill_on_helper_exit(const std::string& desc, int child_count)
+ {
+ NamedExtTempFile child_script("py",
+ "import time\n"
+ "time.sleep(30)\n");
+ NamedTempFile pidfile("pid", "");
+ NamedTempFile releasefile("release", "");
+
+ ScopedEnvironmentVariable helper_script(AUTOKILL_HELPER_SCRIPT_ENV, child_script.getName());
+ ScopedEnvironmentVariable helper_pidfile(AUTOKILL_HELPER_PIDFILE_ENV, pidfile.getName());
+ ScopedEnvironmentVariable helper_release(AUTOKILL_HELPER_RELEASE_ENV, releasefile.getName());
+ ScopedEnvironmentVariable helper_count(AUTOKILL_HELPER_COUNT_ENV, std::to_string(child_count));
+
+ LLProcess::Params params;
+ params.executable = get_current_executable_path();
+ params.desc = desc + " helper";
+ LLProcessPtr helper = LLProcess::create(params);
+ tut::ensure("helper launched", bool(helper));
+
+ std::vector<DWORD> pids = wait_for_helper_pids(pidfile.getName(), child_count);
+ std::vector<HANDLE> handles;
+ handles.reserve(pids.size());
+ for (DWORD pid : pids)
+ {
+ // SYNCHRONIZE lets the test wait for the child to terminate, while
+ // PROCESS_QUERY_LIMITED_INFORMATION keeps the requested access minimal.
+ HANDLE handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
+ tut::ensure(STRINGIZE("opened child process handle for pid " << pid), handle != nullptr);
+ handles.push_back(handle);
+ }
+
+ {
+ std::ofstream out(releasefile.getName().c_str(), std::ios::trunc);
+ out << "exit";
+ }
+
+ waitfor(*helper);
+ tut::ensure_equals("helper exited", helper->getStatus().mState, LLProcess::EXITED);
+
+ for (HANDLE handle : handles)
+ {
+ tut::ensure_equals("autokilled child exited",
+ WaitForSingleObject(handle, AUTOKILL_CHILD_TERMINATION_TIMEOUT_MS),
+ WAIT_OBJECT_0);
+ CloseHandle(handle);
+ }
+ }
+}
+#endif
+
/// convenience function for PythonProcessLauncher::run()
template <typename CONTENT>
static void python(const std::string& desc, const CONTENT& script)
@@ -303,6 +525,13 @@ namespace tut
{
struct llprocess_data
{
+ llprocess_data()
+ {
+#if LL_WINDOWS
+ run_autokill_helper_from_environment();
+#endif
+ }
+
LLAPRPool pool;
};
typedef test_group<llprocess_data> llprocess_group;
@@ -1815,4 +2044,25 @@ namespace tut
elapsed_ms < 75);
}
+ template<> template<>
+ void object::test<36>()
+ {
+ set_test_name("autokill ensures child termination on parent exit");
+#if !LL_WINDOWS
+ skip("Windows-specific test");
+#else
+ verify_autokill_on_helper_exit(get_test_name(), 1);
+#endif
+ }
+
+ template<> template<>
+ void object::test<37>()
+ {
+ set_test_name("multiple processes with autokill");
+#if !LL_WINDOWS
+ skip("Windows-specific test");
+#else
+ verify_autokill_on_helper_exit(get_test_name(), 2);
+#endif
+ }
} // namespace tut
diff --git a/indra/llrender/CMakeLists.txt b/indra/llrender/CMakeLists.txt
index fcd287bbb3..149b50e866 100644
--- a/indra/llrender/CMakeLists.txt
+++ b/indra/llrender/CMakeLists.txt
@@ -106,3 +106,8 @@ target_link_libraries(llrender
OpenGL::GLU
)
+if (LL_TESTS)
+ include(LLAddBuildTest)
+ set(test_libs llrender llimage)
+ LL_ADD_INTEGRATION_TEST(llimagegl_prepare "" "${test_libs}")
+endif (LL_TESTS)
diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp
index 4584ed1d86..a577a729b0 100644
--- a/indra/llrender/llgl.cpp
+++ b/indra/llrender/llgl.cpp
@@ -1053,11 +1053,6 @@ void LLGLManager::initWGL()
{
LL_WARNS("RenderInit") << "No ARB WGL PBuffer extensions" << LL_ENDL;
}
-
- if( !glh_init_extensions("WGL_ARB_render_texture") )
- {
- LL_WARNS("RenderInit") << "No ARB WGL render texture extensions" << LL_ENDL;
- }
#endif
}
@@ -1201,7 +1196,7 @@ bool LLGLManager::initGL()
}
if (mVRAM != 0)
{
- LL_WARNS("RenderInit") << "VRAM Detected (AMDAssociations):" << mVRAM << LL_ENDL;
+ LL_INFOS("RenderInit") << "VRAM Detected (AMDAssociations):" << mVRAM << LL_ENDL;
}
}
else if (mHasNVXGpuMemoryInfo)
@@ -1212,7 +1207,7 @@ bool LLGLManager::initGL()
if (mVRAM != 0)
{
- LL_WARNS("RenderInit") << "VRAM Detected (NVXGpuMemoryInfo):" << mVRAM << LL_ENDL;
+ LL_INFOS("RenderInit") << "VRAM Detected (NVXGpuMemoryInfo):" << mVRAM << LL_ENDL;
}
}
#endif
diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp
index 4a3d32c7ff..31298b03a3 100644
--- a/indra/llrender/llimagegl.cpp
+++ b/indra/llrender/llimagegl.cpp
@@ -42,6 +42,7 @@
#include "llwindow.h"
#include "llframetimer.h"
#include <unordered_set>
+#include <utility>
extern LL_COMMON_API bool on_main_thread();
@@ -541,6 +542,7 @@ void LLImageGL::init(bool usemipmaps, bool allow_compression)
mIsMask = false;
mNeedsAlphaAndPickMask = true ;
+ mUploadPreparation.reset();
mAlphaStride = 0 ;
mAlphaOffset = 0 ;
@@ -588,6 +590,7 @@ void LLImageGL::cleanup()
destroyGLTexture();
}
freePickMask();
+ discardUploadPreparation();
mSaveData = NULL; // deletes data
}
@@ -744,6 +747,32 @@ bool LLImageGL::setImage(const U8* data_in, bool data_hasmips /* = false */, S32
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
+ bool alpha_prepared = false;
+ bool pick_mask_prepared = false;
+ if (mUploadPreparation)
+ {
+ TextureUploadPreparation preparation = std::move(*mUploadPreparation);
+ mUploadPreparation.reset();
+ alpha_prepared = preparation.mAlphaAnalyzed;
+ pick_mask_prepared = preparation.mPickMaskPrepared;
+ if (alpha_prepared)
+ {
+ mIsMask = preparation.mIsMask;
+ }
+
+ if (pick_mask_prepared)
+ {
+ freePickMask();
+ mPickMaskWidth = preparation.mPickMaskWidth;
+ mPickMaskHeight = preparation.mPickMaskHeight;
+ if (!preparation.mPickMask.empty())
+ {
+ mPickMask = new U8[preparation.mPickMask.size()];
+ memcpy(mPickMask, preparation.mPickMask.data(), preparation.mPickMask.size());
+ }
+ }
+ }
+
const bool is_compressed = isCompressed();
if (mUseMipMaps)
@@ -803,11 +832,14 @@ bool LLImageGL::setImage(const U8* data_in, bool data_hasmips /* = false */, S32
}
LLImageGL::setManualImage(mTarget, gl_level, mFormatInternal, w, h, mFormatPrimary, GL_UNSIGNED_BYTE, (GLvoid*)data_in, mAllowCompression);
- if (gl_level == 0)
+ if (gl_level == 0 && !alpha_prepared)
{
analyzeAlpha(data_in, w, h);
}
- updatePickMask(w, h, data_in);
+ if (!pick_mask_prepared)
+ {
+ updatePickMask(w, h, data_in);
+ }
if(mFormatSwapBytes)
{
@@ -849,10 +881,16 @@ bool LLImageGL::setImage(const U8* data_in, bool data_hasmips /* = false */, S32
w, h,
mFormatPrimary, mFormatType,
data_in, mAllowCompression);
- analyzeAlpha(data_in, w, h);
+ if (!alpha_prepared)
+ {
+ analyzeAlpha(data_in, w, h);
+ }
stop_glerror();
- updatePickMask(w, h, data_in);
+ if (!pick_mask_prepared)
+ {
+ updatePickMask(w, h, data_in);
+ }
if(mFormatSwapBytes)
{
@@ -950,12 +988,12 @@ bool LLImageGL::setImage(const U8* data_in, bool data_hasmips /* = false */, S32
}
LLImageGL::setManualImage(mTarget, m, mFormatInternal, w, h, mFormatPrimary, mFormatType, cur_mip_data, mAllowCompression);
- if (m == 0)
+ if (m == 0 && !alpha_prepared)
{
analyzeAlpha(data_in, w, h);
}
stop_glerror();
- if (m == 0)
+ if (m == 0 && !pick_mask_prepared)
{
updatePickMask(w, h, cur_mip_data);
}
@@ -1007,9 +1045,15 @@ bool LLImageGL::setImage(const U8* data_in, bool data_hasmips /* = false */, S32
LLImageGL::setManualImage(mTarget, 0, mFormatInternal, w, h,
mFormatPrimary, mFormatType, (GLvoid *)data_in, mAllowCompression);
- analyzeAlpha(data_in, w, h);
+ if (!alpha_prepared)
+ {
+ analyzeAlpha(data_in, w, h);
+ }
- updatePickMask(w, h, data_in);
+ if (!pick_mask_prepared)
+ {
+ updatePickMask(w, h, data_in);
+ }
stop_glerror();
@@ -2114,6 +2158,153 @@ void LLImageGL::setNeedsAlphaAndPickMask(bool need_mask)
}
}
+namespace
+{
+bool analyze_alpha_mask(const U8* data, U32 width, U32 height, S32 alpha_stride, S32 alpha_offset)
+{
+ U32 length = width * height;
+ U32 alpha_total = 0;
+ U32 sample[16] = {};
+
+ // Generate a histogram of quantized alpha.
+ // Also add the histogram of a 2x2 box-sampled version. The idea is
+ // to mid-skew the data (and thus reduce the chance of treating it as
+ // a mask) for high-frequency alpha maps, which suffer the worst from
+ // aliasing when used as alpha masks.
+ if (width >= 2 && height >= 2 && width % 2 == 0 && height % 2 == 0)
+ {
+ const U8* row_start = data + alpha_offset;
+ for (U32 y = 0; y < height; y += 2)
+ {
+ const U8* current = row_start;
+ for (U32 x = 0; x < width; x += 2)
+ {
+ const U32 s1 = current[0];
+ alpha_total += s1;
+ const U32 s2 = current[width * alpha_stride];
+ alpha_total += s2;
+ current += alpha_stride;
+ const U32 s3 = current[0];
+ alpha_total += s3;
+ const U32 s4 = current[width * alpha_stride];
+ alpha_total += s4;
+ current += alpha_stride;
+
+ ++sample[s1 / 16];
+ ++sample[s2 / 16];
+ ++sample[s3 / 16];
+ ++sample[s4 / 16];
+
+ const U32 average_sum = s1 + s2 + s3 + s4;
+ alpha_total += average_sum;
+ sample[average_sum / (16 * 4)] += 4;
+ }
+
+ row_start += 2 * width * alpha_stride;
+ }
+ length *= 2; // We sampled everything twice, essentially.
+ }
+ else
+ {
+ const U8* current = data + alpha_offset;
+ for (U32 i = 0; i < length; ++i)
+ {
+ const U32 alpha = *current;
+ alpha_total += alpha;
+ ++sample[alpha / 16];
+ current += alpha_stride;
+ }
+ }
+
+ // Too many mid-range alpha samples make the texture unsuitable for a
+ // 1-bit mask. Likewise, if all samples are clumped in one half of the
+ // range (but not at an absolute extreme), treat that as an intentional
+ // effect rather than a mask.
+ U32 midrange_total = 0;
+ for (U32 i = 2; i < 13; ++i)
+ {
+ midrange_total += sample[i];
+ }
+ U32 lower_half_total = 0;
+ for (U32 i = 0; i < 8; ++i)
+ {
+ lower_half_total += sample[i];
+ }
+ U32 upper_half_total = 0;
+ for (U32 i = 8; i < 16; ++i)
+ {
+ upper_half_total += sample[i];
+ }
+
+ return midrange_total <= length / 48 &&
+ (lower_half_total != length || alpha_total == 0) &&
+ (upper_half_total != length || alpha_total == 255 * length);
+}
+}
+
+LLImageGL::TextureUploadPreparation LLImageGL::prepareForUpload(const LLImageRaw* image)
+{
+ LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("prepare texture upload");
+
+ TextureUploadPreparation result;
+ if (!image || image->isBufferInvalid())
+ {
+ return result;
+ }
+
+ const S32 width = image->getWidth();
+ const S32 height = image->getHeight();
+ const S32 components = image->getComponents();
+ const U8* data = image->getData();
+ if (!data || (components != 1 && components != 2 && components != 4))
+ {
+ return result;
+ }
+
+ if (!sSkipAnalyzeAlpha)
+ {
+ result.mAlphaAnalyzed = true;
+ result.mIsMask = analyze_alpha_mask(data, width, height, components, components - 1);
+ }
+
+ if (components == 4)
+ {
+ const U32 pick_width = (static_cast<U32>(width) + 1) / 2;
+ const U32 pick_height = (static_cast<U32>(height) + 1) / 2;
+ result.mPickMaskPrepared = true;
+ result.mPickMaskWidth = static_cast<U16>(pick_width);
+ result.mPickMaskHeight = static_cast<U16>(pick_height);
+ const U32 bit_count = pick_width * pick_height;
+ result.mPickMask.resize((bit_count + 7) / 8);
+
+ const S32 alpha_offset = components - 1;
+ U32 pick_bit = 0;
+ for (S32 y = 0; y < height; y += 2)
+ {
+ for (S32 x = 0; x < width; x += 2)
+ {
+ if (data[(y * width + x) * components + alpha_offset] > 32)
+ {
+ result.mPickMask[pick_bit / 8] |= 1 << (pick_bit % 8);
+ }
+ ++pick_bit;
+ }
+ }
+ }
+
+ return result;
+}
+
+void LLImageGL::applyUploadPreparation(TextureUploadPreparation&& preparation)
+{
+ mUploadPreparation = std::make_unique<TextureUploadPreparation>(std::move(preparation));
+}
+
+void LLImageGL::discardUploadPreparation()
+{
+ mUploadPreparation.reset();
+}
+
void LLImageGL::calcAlphaChannelOffsetAndStride()
{
if(mAlphaOffset == INVALID_OFFSET)//do not need alpha mask
@@ -2195,98 +2386,7 @@ void LLImageGL::analyzeAlpha(const void* data_in, U32 w, U32 h)
}
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
-
- U32 length = w * h;
- U32 alphatotal = 0;
-
- U32 sample[16];
- memset(sample, 0, sizeof(U32)*16);
-
- // generate histogram of quantized alpha.
- // also add-in the histogram of a 2x2 box-sampled version. The idea is
- // this will mid-skew the data (and thus increase the chances of not
- // being used as a mask) from high-frequency alpha maps which
- // suffer the worst from aliasing when used as alpha masks.
- if (w >= 2 && h >= 2)
- {
- llassert(w % 2 == 0);
- llassert(h % 2 == 0);
- const GLubyte* rowstart = ((const GLubyte*) data_in) + mAlphaOffset;
- for (U32 y = 0; y < h; y += 2)
- {
- const GLubyte* current = rowstart;
- for (U32 x = 0; x < w; x += 2)
- {
- const U32 s1 = current[0];
- alphatotal += s1;
- const U32 s2 = current[w * mAlphaStride];
- alphatotal += s2;
- current += mAlphaStride;
- const U32 s3 = current[0];
- alphatotal += s3;
- const U32 s4 = current[w * mAlphaStride];
- alphatotal += s4;
- current += mAlphaStride;
-
- ++sample[s1/16];
- ++sample[s2/16];
- ++sample[s3/16];
- ++sample[s4/16];
-
- const U32 asum = (s1+s2+s3+s4);
- alphatotal += asum;
- sample[asum/(16*4)] += 4;
- }
-
- rowstart += 2 * w * mAlphaStride;
- }
- length *= 2; // we sampled everything twice, essentially
- }
- else
- {
- const GLubyte* current = ((const GLubyte*) data_in) + mAlphaOffset;
- for (U32 i = 0; i < length; i++)
- {
- const U32 s1 = *current;
- alphatotal += s1;
- ++sample[s1/16];
- current += mAlphaStride;
- }
- }
-
- // if more than 1/16th of alpha samples are mid-range, this
- // shouldn't be treated as a 1-bit mask
-
- // also, if all of the alpha samples are clumped on one half
- // of the range (but not at an absolute extreme), then consider
- // this to be an intentional effect and don't treat as a mask.
-
- U32 midrangetotal = 0;
- for (U32 i = 2; i < 13; i++)
- {
- midrangetotal += sample[i];
- }
- U32 lowerhalftotal = 0;
- for (U32 i = 0; i < 8; i++)
- {
- lowerhalftotal += sample[i];
- }
- U32 upperhalftotal = 0;
- for (U32 i = 8; i < 16; i++)
- {
- upperhalftotal += sample[i];
- }
-
- if (midrangetotal > length/48 || // lots of midrange, or
- (lowerhalftotal == length && alphatotal != 0) || // all close to transparent but not all totally transparent, or
- (upperhalftotal == length && alphatotal != 255*length)) // all close to opaque but not all totally opaque
- {
- mIsMask = false; // not suitable for masking
- }
- else
- {
- mIsMask = true;
- }
+ mIsMask = analyze_alpha_mask(static_cast<const U8*>(data_in), w, h, mAlphaStride, mAlphaOffset);
}
//----------------------------------------------------------------------------
@@ -2294,14 +2394,14 @@ U32 LLImageGL::createPickMask(S32 pWidth, S32 pHeight)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
freePickMask();
- U32 pick_width = pWidth/2 + 1;
- U32 pick_height = pHeight/2 + 1;
+ U32 pick_width = (static_cast<U32>(pWidth) + 1) / 2;
+ U32 pick_height = (static_cast<U32>(pHeight) + 1) / 2;
U32 size = pick_width * pick_height;
size = (size + 7) / 8; // pixelcount-to-bits
mPickMask = new U8[size];
- mPickMaskWidth = pick_width - 1;
- mPickMaskHeight = pick_height - 1;
+ mPickMaskWidth = static_cast<U16>(pick_width);
+ mPickMaskHeight = static_cast<U16>(pick_height);
memset(mPickMask, 0, sizeof(U8) * size);
@@ -2641,4 +2741,3 @@ void LLImageGLThread::run()
gGL.shutdown();
mWindow->destroySharedContext(mContext);
}
-
diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h
index 6b4492c09e..adff2c6a10 100644
--- a/indra/llrender/llimagegl.h
+++ b/indra/llrender/llimagegl.h
@@ -39,7 +39,9 @@
#include "llrender.h"
#include "threadpool.h"
#include "workqueue.h"
+#include <memory>
#include <unordered_set>
+#include <vector>
#define LL_IMAGEGL_THREAD_CHECK 0 //set to 1 to enable thread debugging for ImageGL
@@ -61,6 +63,16 @@ class LLImageGL : public LLRefCount
{
friend class LLTexUnit;
public:
+ struct TextureUploadPreparation
+ {
+ bool mAlphaAnalyzed = false;
+ bool mIsMask = false;
+ bool mPickMaskPrepared = false;
+ U16 mPickMaskWidth = 0;
+ U16 mPickMaskHeight = 0;
+ std::vector<U8> mPickMask;
+ };
+
// call once per frame
static void updateClass();
@@ -207,6 +219,10 @@ public:
virtual void cleanup(); // Clean up the LLImageGL so it can be reinitialized. Be careful when using this in derived class destructors
void setNeedsAlphaAndPickMask(bool need_mask);
+ bool getNeedsAlphaAndPickMask() const { return mNeedsAlphaAndPickMask; }
+ static TextureUploadPreparation prepareForUpload(const LLImageRaw* image);
+ void applyUploadPreparation(TextureUploadPreparation&& preparation);
+ void discardUploadPreparation();
#if LL_IMAGEGL_THREAD_CHECK
// thread debugging
@@ -242,6 +258,7 @@ private:
bool mIsMask;
bool mNeedsAlphaAndPickMask;
+ std::unique_ptr<TextureUploadPreparation> mUploadPreparation;
S8 mAlphaStride ;
S8 mAlphaOffset ;
diff --git a/indra/llrender/tests/llimagegl_prepare_test.cpp b/indra/llrender/tests/llimagegl_prepare_test.cpp
new file mode 100644
index 0000000000..96df21bcc5
--- /dev/null
+++ b/indra/llrender/tests/llimagegl_prepare_test.cpp
@@ -0,0 +1,118 @@
+/**
+ * @file llimagegl_prepare_test.cpp
+ * @brief Tests for CPU-side texture upload preparation.
+ *
+ * $LicenseInfo:firstyear=2026&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2026, 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 "linden_common.h"
+
+#include "../test/lltut.h"
+
+#include "llimage.h"
+#include "llimagegl.h"
+
+namespace tut
+{
+ struct llimagegl_prepare_data
+ {
+ static LLPointer<LLImageRaw> make_image(U16 width, U16 height, S8 components)
+ {
+ LLPointer<LLImageRaw> image = new LLImageRaw(width, height, components);
+ memset(image->getData(), 0, image->getDataSize());
+ return image;
+ }
+ };
+
+ typedef test_group<llimagegl_prepare_data> llimagegl_prepare_test;
+ typedef llimagegl_prepare_test::object llimagegl_prepare_object;
+ llimagegl_prepare_test llimagegl_prepare_test_factory("LLImageGL upload preparation");
+
+ template<> template<>
+ void llimagegl_prepare_object::test<1>()
+ {
+ LLPointer<LLImageRaw> image = make_image(3, 5, 4);
+ U8* data = image->getData();
+ for (S32 y = 0; y < image->getHeight(); y += 2)
+ {
+ data[(y * image->getWidth()) * image->getComponents() + 3] = 255;
+ }
+
+ LLImageGL::TextureUploadPreparation result = LLImageGL::prepareForUpload(image);
+
+ ensure("alpha analysis is prepared", result.mAlphaAnalyzed);
+ ensure("binary alpha is classified as a mask", result.mIsMask);
+ ensure("RGBA pick mask is prepared", result.mPickMaskPrepared);
+ ensure_equals("odd width uses ceiling half-width", result.mPickMaskWidth, U16(2));
+ ensure_equals("odd height uses ceiling half-height", result.mPickMaskHeight, U16(3));
+ ensure_equals("alternating pick bits", result.mPickMask[0], U8(0x15));
+ }
+
+ template<> template<>
+ void llimagegl_prepare_object::test<2>()
+ {
+ LLPointer<LLImageRaw> image = make_image(15, 2, 4);
+ U8* data = image->getData();
+ for (S32 x = 0; x < image->getWidth(); x += 4)
+ {
+ data[x * image->getComponents() + 3] = 255;
+ }
+
+ LLImageGL::TextureUploadPreparation result = LLImageGL::prepareForUpload(image);
+
+ ensure("RGBA pick mask is prepared", result.mPickMaskPrepared);
+ ensure_equals("pick width matches samples written", result.mPickMaskWidth, U16(8));
+ ensure_equals("pick height matches samples written", result.mPickMaskHeight, U16(1));
+ ensure_equals("exactly eight bits need one byte", result.mPickMask.size(), size_t(1));
+ ensure_equals("all eight sample positions map correctly", result.mPickMask[0], U8(0x55));
+ }
+
+ template<> template<>
+ void llimagegl_prepare_object::test<3>()
+ {
+ LLPointer<LLImageRaw> image = make_image(3, 3, 2);
+ U8* data = image->getData();
+ for (S32 pixel = 0; pixel < image->getWidth() * image->getHeight(); ++pixel)
+ {
+ data[pixel * image->getComponents() + 1] = 255;
+ }
+
+ LLImageGL::TextureUploadPreparation result = LLImageGL::prepareForUpload(image);
+
+ ensure("luminance-alpha analysis is prepared", result.mAlphaAnalyzed);
+ ensure("opaque alpha is classified as a mask", result.mIsMask);
+ ensure("unsupported pick-mask layout falls back to setImage", !result.mPickMaskPrepared);
+ ensure("no unsupported pick-mask data is produced", result.mPickMask.empty());
+ }
+
+ template<> template<>
+ void llimagegl_prepare_object::test<4>()
+ {
+ LLPointer<LLImageRaw> image = make_image(2, 2, 3);
+
+ LLImageGL::TextureUploadPreparation result = LLImageGL::prepareForUpload(image);
+
+ ensure("RGB has no alpha preparation", !result.mAlphaAnalyzed);
+ ensure("RGB has no pick-mask preparation", !result.mPickMaskPrepared);
+ ensure("RGB has no prepared pick-mask data", result.mPickMask.empty());
+ }
+}
diff --git a/indra/llui/lltoolbar.cpp b/indra/llui/lltoolbar.cpp
index 785dc85448..2a9bf2585d 100644
--- a/indra/llui/lltoolbar.cpp
+++ b/indra/llui/lltoolbar.cpp
@@ -792,6 +792,11 @@ void LLToolBar::updateLayoutAsNeeded()
mNeedsLayout = false;
}
+bool LLToolBar::postBuild()
+{
+ mCaretIcon = getChild<LLIconCtrl>("caret");
+ return LLUICtrl::postBuild();
+}
void LLToolBar::draw()
{
@@ -835,35 +840,36 @@ void LLToolBar::draw()
LLUI::translate((F32)getRect().mLeft, (F32)getRect().mBottom);
// Position the caret
- if (!mCaretIcon)
- {
- mCaretIcon = getChild<LLIconCtrl>("caret");
- }
-
- LLIconCtrl* caret = mCaretIcon;
- caret->setVisible(false);
- if (mDragAndDropTarget && !mButtonCommands.empty())
+ // Todo: This shouldn't be on draw, but, as example, on hover
+ if (mCaretIcon)
{
- LLRect caret_rect = caret->getRect();
- if (getOrientation(mSideType) == LLLayoutStack::HORIZONTAL)
- {
- caret->setRect(LLRect(mDragx-caret_rect.getWidth()/2+1,
- mDragy,
- mDragx+caret_rect.getWidth()/2+1,
- mDragy-mDragGirth));
- }
- else
+ mCaretIcon->setVisible(false);
+ if (mDragAndDropTarget && !mButtonCommands.empty())
{
- caret->setRect(LLRect(mDragx,
- mDragy+caret_rect.getHeight()/2,
- mDragx+mDragGirth,
- mDragy-caret_rect.getHeight()/2));
+ LLRect caret_rect = mCaretIcon->getRect();
+ if (getOrientation(mSideType) == LLLayoutStack::HORIZONTAL)
+ {
+ mCaretIcon->setRect(LLRect(mDragx - caret_rect.getWidth() / 2 + 1,
+ mDragy,
+ mDragx + caret_rect.getWidth() / 2 + 1,
+ mDragy - mDragGirth));
+ }
+ else
+ {
+ mCaretIcon->setRect(LLRect(mDragx,
+ mDragy + caret_rect.getHeight() / 2,
+ mDragx + mDragGirth,
+ mDragy - caret_rect.getHeight() / 2));
+ }
+ mCaretIcon->setVisible(true);
}
- caret->setVisible(true);
}
LLUICtrl::draw();
- caret->setVisible(false);
+ if (mCaretIcon)
+ {
+ mCaretIcon->setVisible(false);
+ }
mDragAndDropTarget = false;
}
diff --git a/indra/llui/lltoolbar.h b/indra/llui/lltoolbar.h
index abf44f259a..b4fb3fab02 100644
--- a/indra/llui/lltoolbar.h
+++ b/indra/llui/lltoolbar.h
@@ -213,6 +213,7 @@ public:
};
// virtuals
+ bool postBuild();
void draw();
void reshape(S32 width, S32 height, bool called_from_parent = true);
bool handleRightMouseDown(S32 x, S32 y, MASK mask);
diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp
index 5c5219bcdd..750d218f52 100644
--- a/indra/newview/llfloaterland.cpp
+++ b/indra/newview/llfloaterland.cpp
@@ -466,9 +466,6 @@ bool LLPanelLandGeneral::postBuild()
mContentRating = getChild<LLTextBox>("ContentRatingText");
mLandType = getChild<LLTextBox>("LandTypeText");
- mBtnProfile = getChild<LLButton>("Profile...");
- mBtnProfile->setClickedCallback(boost::bind(&LLPanelLandGeneral::onClickProfile, this));
-
mTextGroupLabel = getChild<LLTextBox>("Group:");
mTextGroup = getChild<LLTextBox>("GroupText");
@@ -599,8 +596,6 @@ void LLPanelLandGeneral::refresh()
mTextOwner->setText(LLStringUtil::null);
mContentRating->setText(LLStringUtil::null);
mLandType->setText(LLStringUtil::null);
- mBtnProfile->setLabel(getString("profile_text"));
- mBtnProfile->setEnabled(false);
mTextClaimDate->setText(LLStringUtil::null);
mTextGroup->setText(LLStringUtil::null);
@@ -682,7 +677,6 @@ void LLPanelLandGeneral::refresh()
mTextSalePending->setEnabled(false);
mTextOwner->setText(getString("public_text"));
mTextOwner->setEnabled(false);
- mBtnProfile->setEnabled(false);
mTextClaimDate->setText(LLStringUtil::null);
mTextClaimDate->setEnabled(false);
mTextGroup->setText(getString("none_text"));
@@ -711,21 +705,14 @@ void LLPanelLandGeneral::refresh()
//refreshNames();
mTextOwner->setEnabled(true);
- // We support both group and personal profiles
- mBtnProfile->setEnabled(true);
-
if (parcel->getGroupID().isNull())
{
- // Not group owned, so "Profile"
- mBtnProfile->setLabel(getString("profile_text"));
mTextGroup->setText(getString("none_text"));
mTextGroup->setEnabled(false);
}
else
{
- // Group owned, so "Info"
- mBtnProfile->setLabel(getString("info_text"));
//mTextGroup->setText("HIPPOS!");//parcel->getGroupName());
mTextGroup->setEnabled(true);
@@ -959,23 +946,6 @@ void LLPanelLandGeneral::onClickSetGroup()
}
}
-void LLPanelLandGeneral::onClickProfile()
-{
- LLParcel* parcel = mParcel->getParcel();
- if (!parcel) return;
-
- if (parcel->getIsGroupOwned())
- {
- const LLUUID& group_id = parcel->getGroupID();
- LLGroupActions::show(group_id);
- }
- else
- {
- const LLUUID& avatar_id = parcel->getOwnerID();
- LLAvatarActions::showProfile(avatar_id);
- }
-}
-
// public
void LLPanelLandGeneral::setGroup(const LLUUID& group_id)
{
diff --git a/indra/newview/llfloaterland.h b/indra/newview/llfloaterland.h
index 8af0caab33..79e5da042f 100644
--- a/indra/newview/llfloaterland.h
+++ b/indra/newview/llfloaterland.h
@@ -146,7 +146,6 @@ public:
virtual void draw();
void setGroup(const LLUUID& group_id);
- void onClickProfile();
void onClickSetGroup();
static void onClickDeed(void*);
static void onClickBuyLand(void* data);
@@ -193,7 +192,6 @@ protected:
LLTextBox* mTextOwnerLabel;
LLTextBox* mTextOwner;
- LLButton* mBtnProfile;
LLTextBox* mContentRating;
LLTextBox* mLandType;
diff --git a/indra/newview/llfloatersnapshot.cpp b/indra/newview/llfloatersnapshot.cpp
index 83d7a92846..8fa279dace 100644
--- a/indra/newview/llfloatersnapshot.cpp
+++ b/indra/newview/llfloatersnapshot.cpp
@@ -96,13 +96,13 @@ LLSnapshotModel::ESnapshotFormat LLFloaterSnapshot::Impl::getImageFormat(LLFloat
LLSpinCtrl* LLFloaterSnapshot::Impl::getWidthSpinner(LLFloaterSnapshotBase* floater)
{
LLPanelSnapshot* active_panel = getActivePanel(floater);
- return active_panel ? active_panel->getWidthSpinner() : floater->getChild<LLSpinCtrl>("snapshot_width");
+ return active_panel ? active_panel->getWidthSpinner() : floater->findChild<LLSpinCtrl>("snapshot_width");
}
LLSpinCtrl* LLFloaterSnapshot::Impl::getHeightSpinner(LLFloaterSnapshotBase* floater)
{
LLPanelSnapshot* active_panel = getActivePanel(floater);
- return active_panel ? active_panel->getHeightSpinner() : floater->getChild<LLSpinCtrl>("snapshot_height");
+ return active_panel ? active_panel->getHeightSpinner() : floater->findChild<LLSpinCtrl>("snapshot_height");
}
void LLFloaterSnapshot::Impl::enableAspectRatioCheckbox(LLFloaterSnapshotBase* floater, bool enable)
@@ -278,7 +278,7 @@ void LLFloaterSnapshot::Impl::updateControls(LLFloaterSnapshotBase* floater)
LLSpinCtrl* height_ctrl = getHeightSpinner(floater);
// Initialize spinners.
- if (width_ctrl->getValue().asInteger() == 0)
+ if (width_ctrl && width_ctrl->getValue().asInteger() == 0)
{
S32 w = gViewerWindow->getWindowWidthRaw();
LL_DEBUGS() << "Initializing width spinner (" << width_ctrl->getName() << "): " << w << LL_ENDL;
@@ -288,7 +288,7 @@ void LLFloaterSnapshot::Impl::updateControls(LLFloaterSnapshotBase* floater)
width_ctrl->setIncrement((F32)(w >> 1));
}
}
- if (height_ctrl->getValue().asInteger() == 0)
+ if (height_ctrl && height_ctrl->getValue().asInteger() == 0)
{
S32 h = gViewerWindow->getWindowHeightRaw();
LL_DEBUGS() << "Initializing height spinner (" << height_ctrl->getName() << "): " << h << LL_ENDL;
@@ -686,8 +686,8 @@ void LLFloaterSnapshot::Impl::updateResolution(LLUICtrl* ctrl, void* data, bool
LLSnapshotLivePreview* previewp = getPreviewView();
if (previewp && combobox->getCurrentIndex() >= 0)
{
- S32 original_width = 0 , original_height = 0 ;
- previewp->getSize(original_width, original_height) ;
+ S32 original_width = 0, original_height = 0;
+ previewp->getSize(original_width, original_height);
if (gSavedSettings.getBOOL("RenderUIInSnapshot") || gSavedSettings.getBOOL("RenderHUDInSnapshot"))
{ //clamp snapshot resolution to window size when showing UI or HUD in snapshot
@@ -737,24 +737,31 @@ void LLFloaterSnapshot::Impl::updateResolution(LLUICtrl* ctrl, void* data, bool
previewp->setSize(width, height);
}
- checkAspectRatio(view, width) ;
+ checkAspectRatio(view, width);
previewp->getSize(width, height);
- // We use the height spinner here because we come here via the aspect ratio
- // checkbox as well and we want height always changing to width by default.
- // If we use the width spinner we would change width according to height by
- // default, that is not what we want.
- updateSpinners(view, previewp, width, height, !getHeightSpinner(view)->isDirty()); // may change width and height
+ LLSpinCtrl* height_ctrl = getHeightSpinner(view);
+ if (height_ctrl)
+ {
+ // We use the height spinner here because we come here via the aspect ratio
+ // checkbox as well and we want height always changing to width by default.
+ // If we use the width spinner we would change width according to height by
+ // default, that is not what we want.
+ updateSpinners(view, previewp, width, height, !height_ctrl->isDirty()); // may change width and height
+ }
- if(getWidthSpinner(view)->getValue().asInteger() != width || getHeightSpinner(view)->getValue().asInteger() != height)
+ LLSpinCtrl* width_ctrl = getWidthSpinner(view);
+ if (width_ctrl
+ && height_ctrl
+ && (width_ctrl->getValue().asInteger() != width || height_ctrl->getValue().asInteger() != height))
{
- getWidthSpinner(view)->setValue(width);
- getHeightSpinner(view)->setValue(height);
+ width_ctrl->setValue(width);
+ height_ctrl->setValue(height);
if (getActiveSnapshotType(view) == LLSnapshotModel::SNAPSHOT_TEXTURE)
{
- getWidthSpinner(view)->setIncrement((F32)(width >> 1));
- getHeightSpinner(view)->setIncrement((F32)(height >> 1));
+ width_ctrl->setIncrement((F32)(width >> 1));
+ height_ctrl->setIncrement((F32)(height >> 1));
}
}
@@ -824,7 +831,7 @@ void LLFloaterSnapshot::Impl::comboSetCustom(LLFloaterSnapshotBase* floater, con
}
// Update supplied width and height according to the constrain proportions flag; limit them by max_val.
-bool LLFloaterSnapshot::Impl::checkImageSize(LLSnapshotLivePreview* previewp, S32& width, S32& height, bool isWidthChanged, S32 max_value)
+bool LLFloaterSnapshot::Impl::checkImageSize(LLSnapshotLivePreview* previewp, S32& width, S32& height, bool isWidthChanged, S32 max_value) const
{
S32 w = width ;
S32 h = height ;
@@ -870,19 +877,31 @@ bool LLFloaterSnapshot::Impl::checkImageSize(LLSnapshotLivePreview* previewp, S3
void LLFloaterSnapshot::Impl::setImageSizeSpinnersValues(LLFloaterSnapshotBase* view, S32 width, S32 height)
{
- getWidthSpinner(view)->forceSetValue(width);
- getHeightSpinner(view)->forceSetValue(height);
+ LLSpinCtrl* width_ctrl = getWidthSpinner(view);
+ LLSpinCtrl* height_ctrl = getHeightSpinner(view);
+ if (!height_ctrl || !width_ctrl)
+ {
+ return;
+ }
+ width_ctrl->forceSetValue(width);
+ height_ctrl->forceSetValue(height);
if (getActiveSnapshotType(view) == LLSnapshotModel::SNAPSHOT_TEXTURE)
{
- getWidthSpinner(view)->setIncrement((F32)(width >> 1));
- getHeightSpinner(view)->setIncrement((F32)(height >> 1));
+ width_ctrl->setIncrement((F32)(width >> 1));
+ height_ctrl->setIncrement((F32)(height >> 1));
}
}
void LLFloaterSnapshot::Impl::updateSpinners(LLFloaterSnapshotBase* view, LLSnapshotLivePreview* previewp, S32& width, S32& height, bool is_width_changed)
{
- getWidthSpinner(view)->resetDirty();
- getHeightSpinner(view)->resetDirty();
+ LLSpinCtrl* width_ctrl = getWidthSpinner(view);
+ LLSpinCtrl* height_ctrl = getHeightSpinner(view);
+ if (!height_ctrl || !width_ctrl)
+ {
+ return;
+ }
+ width_ctrl->resetDirty();
+ height_ctrl->resetDirty();
if (checkImageSize(previewp, width, height, is_width_changed, previewp->getMaxImageSize()))
{
setImageSizeSpinnersValues(view, width, height);
@@ -903,7 +922,15 @@ void LLFloaterSnapshot::Impl::applyCustomResolution(LLFloaterSnapshotBase* view,
if (w != curw || h != curh)
{
//if to upload a snapshot, process spinner input in a special way.
- previewp->setMaxImageSize((S32) getWidthSpinner(view)->getMaxValue()) ;
+ LLSpinCtrl* width_ctrl = getWidthSpinner(view);
+ if (width_ctrl)
+ {
+ previewp->setMaxImageSize((S32)width_ctrl->getMaxValue());
+ }
+ else
+ {
+ previewp->setMaxImageSize((S32)2048);
+ }
previewp->setSize(w,h);
checkAutoSnapshot(previewp, false);
diff --git a/indra/newview/llfloatersnapshot.h b/indra/newview/llfloatersnapshot.h
index 186d9c41cf..703ce9e57b 100644
--- a/indra/newview/llfloatersnapshot.h
+++ b/indra/newview/llfloatersnapshot.h
@@ -194,7 +194,7 @@ public:
void onImageFormatChange(LLFloaterSnapshotBase* view);
void applyCustomResolution(LLFloaterSnapshotBase* view, S32 w, S32 h);
static void onSendingPostcardFinished(LLFloaterSnapshotBase* floater, bool status);
- bool checkImageSize(LLSnapshotLivePreview* previewp, S32& width, S32& height, bool isWidthChanged, S32 max_value);
+ bool checkImageSize(LLSnapshotLivePreview* previewp, S32& width, S32& height, bool isWidthChanged, S32 max_value) const;
void setImageSizeSpinnersValues(LLFloaterSnapshotBase *view, S32 width, S32 height);
void updateSpinners(LLFloaterSnapshotBase* view, LLSnapshotLivePreview* previewp, S32& width, S32& height, bool is_width_changed);
static void onSnapshotUploadFinished(LLFloaterSnapshotBase* floater, bool status);
diff --git a/indra/newview/llgltfmateriallist.cpp b/indra/newview/llgltfmateriallist.cpp
index b784419780..4036544343 100644
--- a/indra/newview/llgltfmateriallist.cpp
+++ b/indra/newview/llgltfmateriallist.cpp
@@ -525,7 +525,7 @@ void LLGLTFMaterialList::onAssetLoadComplete(const LLUUID& id, LLAssetType::ETyp
if (status != LL_ERR_NOERR)
{
- LL_WARNS("GLTF") << "Error getting material asset data: " << LLAssetStorage::getErrorString(status) << " (" << status << ")" << LL_ENDL;
+ LL_WARNS("GLTF") << "Error getting material asset data: " << LLAssetStorage::getErrorString(status) << " (" << status << ") for asset " << id << LL_ENDL;
asset_data->mMaterial->materialComplete(false);
delete asset_data;
}
diff --git a/indra/newview/llgroupmgr.cpp b/indra/newview/llgroupmgr.cpp
index e0da762279..6081b36918 100644
--- a/indra/newview/llgroupmgr.cpp
+++ b/indra/newview/llgroupmgr.cpp
@@ -869,19 +869,15 @@ void LLGroupMgr::removeObserver(LLGroupMgrObserver* observer)
{
return;
}
- observer_multimap_t::iterator it;
- it = mObservers.find(observer->getID());
- while (it != mObservers.end())
+ observer_multimap_t::iterator it = mObservers.lower_bound(observer->getID());
+ observer_multimap_t::iterator end = mObservers.upper_bound(observer->getID());
+ for (; it != end; ++it)
{
if (it->second == observer)
{
mObservers.erase(it);
break;
}
- else
- {
- ++it;
- }
}
}
diff --git a/indra/newview/llimprocessing.cpp b/indra/newview/llimprocessing.cpp
index 3f60dc5d26..9ba939d35b 100644
--- a/indra/newview/llimprocessing.cpp
+++ b/indra/newview/llimprocessing.cpp
@@ -1582,7 +1582,8 @@ void LLIMProcessing::requestOfflineMessagesCoro(std::string url)
if (!contents.size())
{
- LL_WARNS("Messaging") << "No contents received for offline messages via capability " << url << LL_ENDL;
+ // Received no offline messages on login.
+ LL_INFOS("Messaging") << "No contents received for offline messages via capability " << url << LL_ENDL;
return;
}
diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp
index 02f013ad3c..2cdaf40be8 100644
--- a/indra/newview/lllogininstance.cpp
+++ b/indra/newview/lllogininstance.cpp
@@ -96,6 +96,8 @@ LLLoginInstance::LLLoginInstance() :
mDispatcher.add("connect", "", boost::bind(&LLLoginInstance::handleLoginSuccess, this, _1));
mDispatcher.add("disconnect", "", boost::bind(&LLLoginInstance::handleDisconnect, this, _1));
mDispatcher.add("indeterminate", "", boost::bind(&LLLoginInstance::handleIndeterminate, this, _1));
+ // Todo, implement "authenticating"?
+ mDispatcher.add("authenticating", "", boost::bind(&LLLoginInstance::handleIndeterminate, this, _1));
}
void LLLoginInstance::setPlatformInfo(const std::string platform,
diff --git a/indra/newview/llpanelgrouproles.cpp b/indra/newview/llpanelgrouproles.cpp
index 426a89fe6c..b4adbe0819 100644
--- a/indra/newview/llpanelgrouproles.cpp
+++ b/indra/newview/llpanelgrouproles.cpp
@@ -3233,6 +3233,12 @@ void LLPanelGroupBanListSubTab::setBanCount(U32 ban_count)
void LLPanelGroupBanListSubTab::populateBanList()
{
+ if (mGroupID.isNull())
+ {
+ mBanList->deleteAllItems();
+ return;
+ }
+
LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID);
if(!gdatap)
{
diff --git a/indra/newview/llpanellandmedia.cpp b/indra/newview/llpanellandmedia.cpp
index 294bd4021d..feea97c4a9 100644
--- a/indra/newview/llpanellandmedia.cpp
+++ b/indra/newview/llpanellandmedia.cpp
@@ -135,8 +135,6 @@ void LLPanelLandMedia::refresh()
mMediaURLEdit->setText(parcel->getMediaURL());
mMediaURLEdit->setEnabled( false );
- getChild<LLUICtrl>("current_url")->setValue(parcel->getMediaCurrentURL());
-
mMediaDescEdit->setText(parcel->getMediaDesc());
mMediaDescEdit->setEnabled( can_change_media );
@@ -234,12 +232,9 @@ void LLPanelLandMedia::setMediaURL(const std::string& media_url)
LLParcel *parcel = mParcel->getParcel();
if(parcel)
parcel->setMediaCurrentURL(media_url);
- // LLViewerMedia::navigateHome();
mMediaURLEdit->onCommit();
- // LLViewerParcelMedia::sendMediaNavigateMessage(media_url);
- getChild<LLUICtrl>("current_url")->setValue(media_url);
}
std::string LLPanelLandMedia::getMediaURL()
{
@@ -322,8 +317,6 @@ void LLPanelLandMedia::onResetBtn(void *userdata)
LLParcel* parcel = self->mParcel->getParcel();
// LLViewerMedia::navigateHome();
self->refresh();
- self->getChild<LLUICtrl>("current_url")->setValue(parcel->getMediaURL());
- // LLViewerParcelMedia::sendMediaNavigateMessage(parcel->getMediaURL());
}
diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp
index 15cc8b421c..65520013aa 100644
--- a/indra/newview/llpanellogin.cpp
+++ b/indra/newview/llpanellogin.cpp
@@ -807,6 +807,8 @@ void LLPanelLogin::onUpdateStartSLURL(const LLSLURL& new_start_slurl)
case LLSLURL::HOME_LOCATION:
//location_combo->setCurrentByIndex(0); // home location
break;
+ case LLSLURL::LAST_LOCATION:
+ break;
default:
LL_WARNS("AppInit")<<"invalid login slurl, using home"<<LL_ENDL;
diff --git a/indra/newview/llpanelsnapshot.cpp b/indra/newview/llpanelsnapshot.cpp
index 56c0294dbe..d76f19617f 100644
--- a/indra/newview/llpanelsnapshot.cpp
+++ b/indra/newview/llpanelsnapshot.cpp
@@ -64,7 +64,12 @@ bool LLPanelSnapshot::postBuild()
{
S32 w = getTypedPreviewWidth();
S32 h = getTypedPreviewHeight();
- getChild<LLUICtrl>("save_btn")->setLabelArg("[UPLOAD_COST]", std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost(w, h)));
+ LLUICtrl *save_btn = findChild<LLUICtrl>("save_btn");
+ if (save_btn)
+ {
+ // Not all snapshot floaters have a save button
+ save_btn->setLabelArg("[UPLOAD_COST]", std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost(w, h)));
+ }
getChild<LLUICtrl>(getImageSizeComboName())->setCommitCallback(boost::bind(&LLPanelSnapshot::onResolutionComboCommit, this, _1));
if (!getWidthSpinnerName().empty())
{
@@ -193,7 +198,11 @@ void LLPanelSnapshot::updateImageQualityLevel()
quality_lvl = LLTrans::getString("snapshot_quality_very_high");
}
- getChild<LLTextBox>("image_quality_level")->setTextArg("[QLVL]", quality_lvl);
+ LLTextBox* quality_lvl_ctrl = getChild<LLTextBox>("image_quality_level");
+ if (quality_lvl_ctrl)
+ {
+ quality_lvl_ctrl->setTextArg("[QLVL]", quality_lvl);
+ }
}
void LLPanelSnapshot::goBack()
diff --git a/indra/newview/llviewerassetstorage.cpp b/indra/newview/llviewerassetstorage.cpp
index fd462fb225..657fb0ffc1 100644
--- a/indra/newview/llviewerassetstorage.cpp
+++ b/indra/newview/llviewerassetstorage.cpp
@@ -506,7 +506,7 @@ void LLViewerAssetStorage::assetRequestCoro(
if (!gAgent.getRegion()->capabilitiesReceived())
{
- LL_WARNS_ONCE("ViewerAsset") << "Waiting for capabilities" << LL_ENDL;
+ LL_INFOS_ONCE("ViewerAsset") << "Waiting for capabilities" << LL_ENDL;
LLEventStream capsRecv("waitForCaps", true);
@@ -533,8 +533,8 @@ void LLViewerAssetStorage::assetRequestCoro(
return;
}
- LL_WARNS_ONCE("ViewerAsset") << "capsRecv got event" << LL_ENDL;
- LL_WARNS_ONCE("ViewerAsset") << "region " << gAgent.getRegion() << " mViewerAssetUrl " << mViewerAssetUrl << LL_ENDL;
+ LL_INFOS_ONCE("ViewerAsset") << "capsRecv got event" << LL_ENDL;
+ LL_INFOS_ONCE("ViewerAsset") << "region " << gAgent.getRegion() << " mViewerAssetUrl " << mViewerAssetUrl << LL_ENDL;
}
if (mViewerAssetUrl.empty() && gAgent.getRegion())
{
diff --git a/indra/newview/llviewernetwork.cpp b/indra/newview/llviewernetwork.cpp
index 8ec2a6d661..fb52d0a8e6 100644
--- a/indra/newview/llviewernetwork.cpp
+++ b/indra/newview/llviewernetwork.cpp
@@ -135,7 +135,8 @@ void LLGridManager::initialize(const std::string& grid_file)
LLSD other_grids;
llifstream llsd_xml;
- if (!grid_file.empty())
+ // grids.xml is not supplied by default
+ if (!grid_file.empty() && LLFile::isfile(grid_file))
{
LL_INFOS("GridManager")<<"Grid configuration file '"<<grid_file<<"'"<<LL_ENDL;
llsd_xml.open( grid_file.c_str(), std::ios::in | std::ios::binary );
diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp
index 0f23596c9a..db20658497 100644
--- a/indra/newview/llviewertexture.cpp
+++ b/indra/newview/llviewertexture.cpp
@@ -63,6 +63,8 @@
#include "lltexturecache.h"
#include "llviewerwindow.h"
#include "llwindow.h"
+
+#include <utility>
///////////////////////////////////////////////////////////////////////////////
// statics
@@ -1581,6 +1583,7 @@ bool LLViewerFetchedTexture::createTexture(S32 usename/*= 0*/)
void LLViewerFetchedTexture::postCreateTexture()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
+ mGLTexturep->discardUploadPreparation();
if (!mNeedsCreateTexture)
{
return;
@@ -1679,6 +1682,54 @@ void LLViewerFetchedTexture::scheduleCreateTexture()
unref();
});
}
+ else if (!mGLTexturep->getHasExplicitFormat() &&
+ mGLTexturep->getNeedsAlphaAndPickMask() &&
+ mRawImage->getComponents() != 3)
+ {
+ auto main_queue = mMainQueue.lock();
+ auto general_queue = LL::WorkQueue::getInstance("General");
+ if (main_queue && general_queue)
+ {
+ LLPointer<LLImageRaw> raw_image = mRawImage;
+ ref();
+ const bool posted = main_queue->postTo(
+ general_queue,
+ [raw_image]()
+ {
+ return LLImageGL::prepareForUpload(raw_image);
+ },
+ [this, raw_image](LLImageGL::TextureUploadPreparation preparation)
+ {
+ if (mNeedsCreateTexture)
+ {
+ if (mRawImage == raw_image)
+ {
+ mGLTexturep->applyUploadPreparation(std::move(preparation));
+ }
+ if (!mCreatePending)
+ {
+ mCreatePending = true;
+ gTextureList.mCreateTextureList.push(this);
+ }
+ }
+ unref();
+ });
+ if (!posted)
+ {
+ unref();
+ if (!mCreatePending)
+ {
+ mCreatePending = true;
+ gTextureList.mCreateTextureList.push(this);
+ }
+ }
+ }
+ else if (!mCreatePending)
+ {
+ mCreatePending = true;
+ gTextureList.mCreateTextureList.push(this);
+ }
+ }
else
{
if (!mCreatePending)
@@ -4108,4 +4159,3 @@ void LLTexturePipelineTester::LLTextureTestSession::reset()
//----------------------------------------------------------------------------------------------
//end of LLTexturePipelineTester
//----------------------------------------------------------------------------------------------
-
diff --git a/indra/newview/skins/default/xui/en/floater_about_land.xml b/indra/newview/skins/default/xui/en/floater_about_land.xml
index ffb9c0f78e..eeac4ed25f 100644
--- a/indra/newview/skins/default/xui/en/floater_about_land.xml
+++ b/indra/newview/skins/default/xui/en/floater_about_land.xml
@@ -253,17 +253,9 @@
image_pressed="Info_Press"
image_unselected="Info_Over"
left_pad="3"
- name="info_btn"
+ name="info_btn_owner"
top_delta="-2"
width="16" />
- <!-- <button
- follows="left|top"
- height="23"
- label="Profile"
- layout="topleft"
- left_pad="4"
- name="Profile..."
- width="90" />-->
<text
type="string"
length="1"
@@ -272,11 +264,10 @@
layout="topleft"
left="10"
name="Group:"
- top_pad="7"
+ top_pad="13"
width="100">
Group:
</text>
- <!--TODO: HOOK UP GROUP ICON-->
<text
follows="left|top"
height="16"
@@ -287,15 +278,6 @@
width="240">
TestString PleaseIgnore
</text>
- <button
- follows="right"
- height="23"
- image_pressed="Info_Press"
- image_unselected="Info_Over"
- left_pad="3"
- name="info_btn"
- top_delta="-2"
- width="16" />
<button
follows="left|top"
height="23"
@@ -305,7 +287,7 @@
right="-154"
name="Set..."
width="90"
- top_delta="-2"/>
+ top_delta="-5"/>
<check_box
enabled="false"
height="16"
diff --git a/indra/newview/skins/default/xui/en/floater_snapshot.xml b/indra/newview/skins/default/xui/en/floater_snapshot.xml
index acdccdc03a..7f1a59217f 100644
--- a/indra/newview/skins/default/xui/en/floater_snapshot.xml
+++ b/indra/newview/skins/default/xui/en/floater_snapshot.xml
@@ -226,7 +226,6 @@
Filter:
</text>
<combo_box
- control_name="PhotoFilters"
follows="left|right|top"
name="filters_combobox"
tool_tip="Image filters"