diff options
| author | McGroarty <git@mcgroarty.me> | 2026-07-27 12:59:10 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-27 12:59:10 -0700 |
| commit | 21939024cdee694bf8e98b79c3ba929ffc2fef5e (patch) | |
| tree | 450fbbd27c07fdcc7236d1e782af0da7bb88a022 | |
| parent | d1796fba2500a30bd3c71b0588efd18c1a8625bf (diff) | |
| parent | 1c81813e79c4acc917d75b47117778dd277e5b1f (diff) | |
Merge pull request #1 from secondlife/develop
Fix UI issues, improve warnings, and enhance process handling
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" |
