diff options
author | Erik Kundiman <erik@megapahit.org> | 2025-03-06 09:36:23 +0800 |
---|---|---|
committer | Erik Kundiman <erik@megapahit.org> | 2025-03-06 09:36:23 +0800 |
commit | d13a43c74f9c7e450f87edab0bcd9db39ee7c56e (patch) | |
tree | d40e4e3c1f5ce0cf80b11b2d03568c48cfc5e88f | |
parent | 5bf93b728ca7c671b6b812e753a40dcdb55b5d0d (diff) | |
parent | aa060ae56c62869bb1d0f590e9a6eb681aabcefc (diff) |
Merge remote-tracking branch 'secondlife/release/2025.03' into 2025.03
93 files changed, 2248 insertions, 1026 deletions
diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml index 013adc9ca8..7866f943b5 100644 --- a/.github/workflows/cla.yaml +++ b/.github/workflows/cla.yaml @@ -23,4 +23,4 @@ jobs: path-to-signatures: signatures.json remote-organization-name: secondlife remote-repository-name: cla-signatures - allowlist: callum@mbp.localdomain + allowlist: callum@mbp.localdomain,rye@lindenlab.com diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index 95d55c835f..3d66809ed6 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -799,7 +799,6 @@ void LLAvatarAppearance::buildCharacter() bool status = loadAvatar(); stop_glerror(); -// gPrintMessagesThisFrame = true; LL_DEBUGS() << "Avatar load took " << timer.getElapsedTimeF32() << " seconds." << LL_ENDL; if (!status) diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index 3d18864b80..57f5a112d9 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -282,7 +282,7 @@ public: LLRunner& getRunner() { return mRunner; } #ifdef LL_WINDOWS - virtual void reportCrashToBugsplat(void* pExcepInfo /*EXCEPTION_POINTERS*/) { } + virtual bool reportCrashToBugsplat(void* pExcepInfo /*EXCEPTION_POINTERS*/) { return false; } #endif public: diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index 3fe7e09e7e..ae0d6e694d 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -303,6 +303,62 @@ std::string LLCoros::launch(const std::string& prefix, const callable_t& callabl return name; } +namespace +{ + +#if LL_WINDOWS + +static const U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific + +U32 exception_filter(U32 code, struct _EXCEPTION_POINTERS* exception_infop) +{ + if (LLApp::instance()->reportCrashToBugsplat((void*)exception_infop)) + { + // Handled + return EXCEPTION_CONTINUE_SEARCH; + } + else if (code == STATUS_MSC_EXCEPTION) + { + // C++ exception, go on + return EXCEPTION_CONTINUE_SEARCH; + } + else + { + // handle it, convert to std::exception + return EXCEPTION_EXECUTE_HANDLER; + } + + return EXCEPTION_CONTINUE_SEARCH; +} + +void sehandle(const LLCoros::callable_t& callable) +{ + __try + { + callable(); + } + __except (exception_filter(GetExceptionCode(), GetExceptionInformation())) + { + // convert to C++ styled exception + // Note: it might be better to use _se_set_translator + // if you want exception to inherit full callstack + char integer_string[512]; + sprintf(integer_string, "SEH, code: %lu\n", GetExceptionCode()); + throw std::exception(integer_string); + } +} + +#else // ! LL_WINDOWS + +inline void sehandle(const LLCoros::callable_t& callable) +{ + callable(); +} + +#endif // ! LL_WINDOWS + +} // anonymous namespace + // Top-level wrapper around caller's coroutine callable. // Normally we like to pass strings and such by const reference -- but in this // case, we WANT to copy both the name and the callable to our local stack! @@ -330,6 +386,7 @@ void LLCoros::toplevel(std::string name, callable_t callable) // viewer will carry on. LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << name)); } +#ifndef LL_WINDOWS catch (...) { // Stash any OTHER kind of uncaught exception in the rethrow() queue @@ -338,6 +395,7 @@ void LLCoros::toplevel(std::string name, callable_t callable) << name << LL_ENDL; LLCoros::instance().saveException(name, std::current_exception()); } +#endif // ! LL_WINDOWS } //static diff --git a/indra/llinventory/llsettingsbase.cpp b/indra/llinventory/llsettingsbase.cpp index 41162963f2..97979d64e3 100644 --- a/indra/llinventory/llsettingsbase.cpp +++ b/indra/llinventory/llsettingsbase.cpp @@ -130,17 +130,6 @@ void LLSettingsBase::saveValuesIfNeeded() } //========================================================================= -void LLSettingsBase::lerpSettings(LLSettingsBase &other, F64 mix) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; - saveValuesIfNeeded(); - stringset_t skip = getSkipInterpolateKeys(); - stringset_t slerps = getSlerpKeys(); - mSettings = interpolateSDMap(mSettings, other.getSettings(), other.getParameterMap(), mix, skip, slerps); - setDirtyFlag(true); - loadValuesFromLLSD(); -} - void LLSettingsBase::lerpVector2(LLVector2& a, const LLVector2& b, F32 mix) { a.mV[0] = lerp(a.mV[0], b.mV[0], mix); diff --git a/indra/llinventory/llsettingsbase.h b/indra/llinventory/llsettingsbase.h index 816ff3e111..7de71588ef 100644 --- a/indra/llinventory/llsettingsbase.h +++ b/indra/llinventory/llsettingsbase.h @@ -348,13 +348,8 @@ protected: LLSettingsBase(); LLSettingsBase(const LLSD setting); - static LLSD settingValidation(LLSD settings); - typedef std::set<std::string> stringset_t; - // combining settings objects. Customize for specific setting types - virtual void lerpSettings(LLSettingsBase &other, BlendFactor mix); - // combining settings maps where it can based on mix rate // @settings initial value (mix==0) // @other target value (mix==1) diff --git a/indra/llmessage/llcoproceduremanager.cpp b/indra/llmessage/llcoproceduremanager.cpp index 263670bdac..6a663a8e97 100644 --- a/indra/llmessage/llcoproceduremanager.cpp +++ b/indra/llmessage/llcoproceduremanager.cpp @@ -301,12 +301,12 @@ LLCoprocedurePool::LLCoprocedurePool(const std::string &poolName, size_t size): mPoolSize(size), mActiveCoprocsCount(0), mPending(0), - mPendingCoprocs(std::make_shared<CoprocQueue_t>(LLCoprocedureManager::DEFAULT_QUEUE_SIZE)), mHTTPPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID), mCoroMapping() { try { + mPendingCoprocs = std::make_shared<CoprocQueue_t>(LLCoprocedureManager::DEFAULT_QUEUE_SIZE); // store in our LLTempBoundListener so that when the LLCoprocedurePool is // destroyed, we implicitly disconnect from this LLEventPump // Monitores application status @@ -339,6 +339,11 @@ LLCoprocedurePool::LLCoprocedurePool(const std::string &poolName, size_t size): llassert(0); // Fix Me! Ignoring missing listener! } + catch (std::bad_alloc&) + { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS("CoProcMgr") << "Bad memory allocation in LLCoprocedurePool::LLCoprocedurePool!" << LL_ENDL; + } for (size_t count = 0; count < mPoolSize; ++count) { diff --git a/indra/llmessage/llcorehttputil.cpp b/indra/llmessage/llcorehttputil.cpp index 918a69be6f..992e145758 100644 --- a/indra/llmessage/llcorehttputil.cpp +++ b/indra/llmessage/llcorehttputil.cpp @@ -295,7 +295,15 @@ void HttpCoroHandler::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespons } else { - result = this->handleSuccess(response, status); + try + { + result = this->handleSuccess(response, status); + } + catch (std::bad_alloc&) + { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS("CoreHTTP") << "Failed to allocate memory for response handling." << LL_ENDL; + } } buildStatusEntry(response, status, result); diff --git a/indra/llmessage/llpacketbuffer.cpp b/indra/llmessage/llpacketbuffer.cpp index dc5c7a73cb..0b04a560be 100644 --- a/indra/llmessage/llpacketbuffer.cpp +++ b/indra/llmessage/llpacketbuffer.cpp @@ -32,8 +32,6 @@ #include "lltimer.h" #include "llhost.h" -/////////////////////////////////////////////////////////// - LLPacketBuffer::LLPacketBuffer(const LLHost &host, const char *datap, const S32 size) : mHost(host) { mSize = 0; @@ -41,7 +39,7 @@ LLPacketBuffer::LLPacketBuffer(const LLHost &host, const char *datap, const S32 if (size > NET_BUFFER_SIZE) { - LL_ERRS() << "Sending packet > " << NET_BUFFER_SIZE << " of size " << size << LL_ENDL; + LL_ERRS() << "Constructing packet with size=" << size << " > " << NET_BUFFER_SIZE << LL_ENDL; } else { @@ -51,7 +49,6 @@ LLPacketBuffer::LLPacketBuffer(const LLHost &host, const char *datap, const S32 mSize = size; } } - } LLPacketBuffer::LLPacketBuffer (S32 hSocket) @@ -59,18 +56,29 @@ LLPacketBuffer::LLPacketBuffer (S32 hSocket) init(hSocket); } -/////////////////////////////////////////////////////////// - LLPacketBuffer::~LLPacketBuffer () { } -/////////////////////////////////////////////////////////// - -void LLPacketBuffer::init (S32 hSocket) +void LLPacketBuffer::init(S32 hSocket) { mSize = receive_packet(hSocket, mData); mHost = ::get_sender(); mReceivingIF = ::get_receiving_interface(); } +void LLPacketBuffer::init(const char* buffer, S32 data_size, const LLHost& host) +{ + if (data_size > NET_BUFFER_SIZE) + { + LL_ERRS() << "Initializing packet with size=" << data_size << " > " << NET_BUFFER_SIZE << LL_ENDL; + } + else + { + memcpy(mData, buffer, data_size); + mSize = data_size; + mHost = host; + mReceivingIF = ::get_receiving_interface(); + } +} + diff --git a/indra/llmessage/llpacketbuffer.h b/indra/llmessage/llpacketbuffer.h index a2d2973fb0..ac4012d330 100644 --- a/indra/llmessage/llpacketbuffer.h +++ b/indra/llmessage/llpacketbuffer.h @@ -35,20 +35,22 @@ class LLPacketBuffer { public: LLPacketBuffer(const LLHost &host, const char *datap, const S32 size); - LLPacketBuffer(S32 hSocket); // receive a packet + LLPacketBuffer(S32 hSocket); // receive a packet ~LLPacketBuffer(); S32 getSize() const { return mSize; } const char *getData() const { return mData; } LLHost getHost() const { return mHost; } LLHost getReceivingInterface() const { return mReceivingIF; } + void init(S32 hSocket); + void init(const char* buffer, S32 data_size, const LLHost& host); protected: - char mData[NET_BUFFER_SIZE]; // packet data /* Flawfinder : ignore */ - S32 mSize; // size of buffer in bytes - LLHost mHost; // source/dest IP and port - LLHost mReceivingIF; // source/dest IP and port + char mData[NET_BUFFER_SIZE]; // packet data /* Flawfinder : ignore */ + S32 mSize; // size of buffer in bytes + LLHost mHost; // source/dest IP and port + LLHost mReceivingIF; // source/dest IP and port }; #endif diff --git a/indra/llmessage/llpacketring.cpp b/indra/llmessage/llpacketring.cpp index be838770a8..da3c502e9d 100644 --- a/indra/llmessage/llpacketring.cpp +++ b/indra/llmessage/llpacketring.cpp @@ -1,6 +1,6 @@ /** * @file llpacketring.cpp - * @brief implementation of LLPacketRing class for a packet. + * @brief implementation of LLPacketRing class. * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code @@ -43,329 +43,317 @@ #include "message.h" #include "u64.h" -/////////////////////////////////////////////////////////// -LLPacketRing::LLPacketRing () : - mUseInThrottle(false), - mUseOutThrottle(false), - mInThrottle(256000.f), - mOutThrottle(64000.f), - mActualBitsIn(0), - mActualBitsOut(0), - mMaxBufferLength(64000), - mInBufferLength(0), - mOutBufferLength(0), - mDropPercentage(0.0f), - mPacketsToDrop(0x0) +constexpr S16 MAX_BUFFER_RING_SIZE = 1024; +constexpr S16 DEFAULT_BUFFER_RING_SIZE = 256; + +LLPacketRing::LLPacketRing () + : mPacketRing(DEFAULT_BUFFER_RING_SIZE, nullptr) { + LLHost invalid_host; + for (size_t i = 0; i < mPacketRing.size(); ++i) + { + mPacketRing[i] = new LLPacketBuffer(invalid_host, nullptr, 0); + } } -/////////////////////////////////////////////////////////// LLPacketRing::~LLPacketRing () { - cleanup(); + for (auto packet : mPacketRing) + { + delete packet; + } + mPacketRing.clear(); + mNumBufferedPackets = 0; + mNumBufferedBytes = 0; + mHeadIndex = 0; } -/////////////////////////////////////////////////////////// -void LLPacketRing::cleanup () +S32 LLPacketRing::receivePacket (S32 socket, char *datap) { - LLPacketBuffer *packetp; + bool drop = computeDrop(); + return (mNumBufferedPackets > 0) ? + receiveOrDropBufferedPacket(datap, drop) : + receiveOrDropPacket(socket, datap, drop); +} - while (!mReceiveQueue.empty()) +bool send_packet_helper(int socket, const char * datap, S32 data_size, LLHost host) +{ + if (!LLProxy::isSOCKSProxyEnabled()) { - packetp = mReceiveQueue.front(); - delete packetp; - mReceiveQueue.pop(); + return send_packet(socket, datap, data_size, host.getAddress(), host.getPort()); } - while (!mSendQueue.empty()) - { - packetp = mSendQueue.front(); - delete packetp; - mSendQueue.pop(); - } -} + char headered_send_buffer[NET_BUFFER_SIZE + SOCKS_HEADER_SIZE]; -/////////////////////////////////////////////////////////// -void LLPacketRing::dropPackets (U32 num_to_drop) -{ - mPacketsToDrop += num_to_drop; -} + proxywrap_t *socks_header = static_cast<proxywrap_t*>(static_cast<void*>(&headered_send_buffer)); + socks_header->rsv = 0; + socks_header->addr = host.getAddress(); + socks_header->port = htons(host.getPort()); + socks_header->atype = ADDRESS_IPV4; + socks_header->frag = 0; -/////////////////////////////////////////////////////////// -void LLPacketRing::setDropPercentage (F32 percent_to_drop) -{ - mDropPercentage = percent_to_drop; -} + memcpy(headered_send_buffer + SOCKS_HEADER_SIZE, datap, data_size); -void LLPacketRing::setUseInThrottle(const bool use_throttle) -{ - mUseInThrottle = use_throttle; + return send_packet( socket, + headered_send_buffer, + data_size + SOCKS_HEADER_SIZE, + LLProxy::getInstance()->getUDPProxy().getAddress(), + LLProxy::getInstance()->getUDPProxy().getPort()); } -void LLPacketRing::setUseOutThrottle(const bool use_throttle) +bool LLPacketRing::sendPacket(int socket, const char * datap, S32 data_size, LLHost host) { - mUseOutThrottle = use_throttle; + mActualBytesOut += data_size; + return send_packet_helper(socket, datap, data_size, host); } -void LLPacketRing::setInBandwidth(const F32 bps) +void LLPacketRing::dropPackets (U32 num_to_drop) { - mInThrottle.setRate(bps); + mPacketsToDrop += num_to_drop; } -void LLPacketRing::setOutBandwidth(const F32 bps) +void LLPacketRing::setDropPercentage (F32 percent_to_drop) { - mOutThrottle.setRate(bps); + mDropPercentage = percent_to_drop; } -/////////////////////////////////////////////////////////// -S32 LLPacketRing::receiveFromRing (S32 socket, char *datap) -{ - if (mInThrottle.checkOverflow(0)) - { - // We don't have enough bandwidth, don't give them a packet. - return 0; - } - - LLPacketBuffer *packetp = NULL; - if (mReceiveQueue.empty()) +bool LLPacketRing::computeDrop() +{ + bool drop= (mDropPercentage > 0.0f && (ll_frand(100.f) < mDropPercentage)); + if (drop) { - // No packets on the queue, don't give them any. - return 0; + ++mPacketsToDrop; } - - S32 packet_size = 0; - packetp = mReceiveQueue.front(); - mReceiveQueue.pop(); - packet_size = packetp->getSize(); - if (packetp->getData() != NULL) + if (mPacketsToDrop > 0) { - memcpy(datap, packetp->getData(), packet_size); /*Flawfinder: ignore*/ + --mPacketsToDrop; + drop = true; } - // need to set sender IP/port!! - mLastSender = packetp->getHost(); - mLastReceivingIF = packetp->getReceivingInterface(); - delete packetp; - - this->mInBufferLength -= packet_size; - - // Adjust the throttle - mInThrottle.throttleOverflow(packet_size * 8.f); - return packet_size; + return drop; } -/////////////////////////////////////////////////////////// -S32 LLPacketRing::receivePacket (S32 socket, char *datap) +S32 LLPacketRing::receiveOrDropPacket(S32 socket, char *datap, bool drop) { S32 packet_size = 0; - // If using the throttle, simulate a limited size input buffer. - if (mUseInThrottle) + // pull straight from socket + if (LLProxy::isSOCKSProxyEnabled()) { - bool done = false; - - // push any current net packet (if any) onto delay ring - while (!done) + char buffer[NET_BUFFER_SIZE + SOCKS_HEADER_SIZE]; /* Flawfinder ignore */ + packet_size = receive_packet(socket, buffer); + if (packet_size > 0) { - LLPacketBuffer *packetp; - packetp = new LLPacketBuffer(socket); - - if (packetp->getSize()) - { - mActualBitsIn += packetp->getSize() * 8; - - // Fake packet loss - if (mDropPercentage && (ll_frand(100.f) < mDropPercentage)) - { - mPacketsToDrop++; - } - - if (mPacketsToDrop) - { - delete packetp; - packetp = NULL; - packet_size = 0; - mPacketsToDrop--; - } - } + mActualBytesIn += packet_size; + } - // If we faked packet loss, then we don't have a packet - // to use for buffer overflow testing - if (packetp) + if (packet_size > SOCKS_HEADER_SIZE) + { + if (drop) { - if (mInBufferLength + packetp->getSize() > mMaxBufferLength) - { - // Toss it. - LL_WARNS() << "Throwing away packet, overflowing buffer" << LL_ENDL; - delete packetp; - packetp = NULL; - } - else if (packetp->getSize()) - { - mReceiveQueue.push(packetp); - mInBufferLength += packetp->getSize(); - } - else - { - delete packetp; - packetp = NULL; - done = true; - } + packet_size = 0; } else { - // No packetp, keep going? - no packetp == faked packet loss - } - } - - // Now, grab data off of the receive queue according to our - // throttled bandwidth settings. - packet_size = receiveFromRing(socket, datap); - } - else - { - // no delay, pull straight from net - if (LLProxy::isSOCKSProxyEnabled()) - { - U8 buffer[NET_BUFFER_SIZE + SOCKS_HEADER_SIZE]; - packet_size = receive_packet(socket, static_cast<char*>(static_cast<void*>(buffer))); - - if (packet_size > SOCKS_HEADER_SIZE) - { // *FIX We are assuming ATYP is 0x01 (IPv4), not 0x03 (hostname) or 0x04 (IPv6) - memcpy(datap, buffer + SOCKS_HEADER_SIZE, packet_size - SOCKS_HEADER_SIZE); + packet_size -= SOCKS_HEADER_SIZE; // The unwrapped packet size + memcpy(datap, buffer + SOCKS_HEADER_SIZE, packet_size); proxywrap_t * header = static_cast<proxywrap_t*>(static_cast<void*>(buffer)); mLastSender.setAddress(header->addr); mLastSender.setPort(ntohs(header->port)); - - packet_size -= SOCKS_HEADER_SIZE; // The unwrapped packet size - } - else - { - packet_size = 0; + mLastReceivingIF = ::get_receiving_interface(); } } else { - packet_size = receive_packet(socket, datap); - mLastSender = ::get_sender(); + packet_size = 0; } - - mLastReceivingIF = ::get_receiving_interface(); - - if (packet_size) // did we actually get a packet? + } + else + { + packet_size = receive_packet(socket, datap); + if (packet_size > 0) { - if (mDropPercentage && (ll_frand(100.f) < mDropPercentage)) + mActualBytesIn += packet_size; + if (drop) { - mPacketsToDrop++; + packet_size = 0; } - - if (mPacketsToDrop) + else { - packet_size = 0; - mPacketsToDrop--; + mLastSender = ::get_sender(); + mLastReceivingIF = ::get_receiving_interface(); } } } - return packet_size; } -bool LLPacketRing::sendPacket(int h_socket, char * send_buffer, S32 buf_size, LLHost host) +S32 LLPacketRing::receiveOrDropBufferedPacket(char *datap, bool drop) { - bool status = true; - if (!mUseOutThrottle) + assert(mNumBufferedPackets > 0); + S32 packet_size = 0; + + S16 ring_size = (S16)(mPacketRing.size()); + S16 packet_index = (mHeadIndex + ring_size - mNumBufferedPackets) % ring_size; + LLPacketBuffer* packet = mPacketRing[packet_index]; + packet_size = packet->getSize(); + mLastSender = packet->getHost(); + mLastReceivingIF = packet->getReceivingInterface(); + + --mNumBufferedPackets; + mNumBufferedBytes -= packet_size; + if (mNumBufferedPackets == 0) { - return sendPacketImpl(h_socket, send_buffer, buf_size, host ); + assert(mNumBufferedBytes == 0); + } + + if (!drop) + { + assert(packet_size > 0); + memcpy(datap, packet->getData(), packet_size); } else { - mActualBitsOut += buf_size * 8; - LLPacketBuffer *packetp = NULL; - // See if we've got enough throttle to send a packet. - while (!mOutThrottle.checkOverflow(0.f)) - { - // While we have enough bandwidth, send a packet from the queue or the current packet + packet_size = 0; + } + return packet_size; +} + +S32 LLPacketRing::bufferInboundPacket(S32 socket) +{ + if (mNumBufferedPackets == mPacketRing.size() && mNumBufferedPackets < MAX_BUFFER_RING_SIZE) + { + expandRing(); + } - S32 packet_size = 0; - if (!mSendQueue.empty()) + LLPacketBuffer* packet = mPacketRing[mHeadIndex]; + S32 old_packet_size = packet->getSize(); + S32 packet_size = 0; + if (LLProxy::isSOCKSProxyEnabled()) + { + char buffer[NET_BUFFER_SIZE + SOCKS_HEADER_SIZE]; /* Flawfinder ignore */ + packet_size = receive_packet(socket, buffer); + if (packet_size > 0) + { + mActualBytesIn += packet_size; + if (packet_size > SOCKS_HEADER_SIZE) { - // Send a packet off of the queue - LLPacketBuffer *packetp = mSendQueue.front(); - mSendQueue.pop(); + // *FIX We are assuming ATYP is 0x01 (IPv4), not 0x03 (hostname) or 0x04 (IPv6) - mOutBufferLength -= packetp->getSize(); - packet_size = packetp->getSize(); + proxywrap_t * header = static_cast<proxywrap_t*>(static_cast<void*>(buffer)); + LLHost sender; + sender.setAddress(header->addr); + sender.setPort(ntohs(header->port)); - status = sendPacketImpl(h_socket, packetp->getData(), packet_size, packetp->getHost()); + packet_size -= SOCKS_HEADER_SIZE; // The unwrapped packet size + packet->init(buffer + SOCKS_HEADER_SIZE, packet_size, sender); - delete packetp; - // Update the throttle - mOutThrottle.throttleOverflow(packet_size * 8.f); + mHeadIndex = (mHeadIndex + 1) % (S16)(mPacketRing.size()); + if (mNumBufferedPackets < MAX_BUFFER_RING_SIZE) + { + ++mNumBufferedPackets; + mNumBufferedBytes += packet_size; + } + else + { + // we overwrote an older packet + mNumBufferedBytes += packet_size - old_packet_size; + } } else { - // If the queue's empty, we can just send this packet right away. - status = sendPacketImpl(h_socket, send_buffer, buf_size, host ); - packet_size = buf_size; - - // Update the throttle - mOutThrottle.throttleOverflow(packet_size * 8.f); - - // This was the packet we're sending now, there are no other packets - // that we need to send - return status; + packet_size = 0; } - - } - - // We haven't sent the incoming packet, add it to the queue - if (mOutBufferLength + buf_size > mMaxBufferLength) - { - // Nuke this packet, we overflowed the buffer. - // Toss it. - LL_WARNS() << "Throwing away outbound packet, overflowing buffer" << LL_ENDL; } - else + } + else + { + packet->init(socket); + packet_size = packet->getSize(); + if (packet_size > 0) { - static LLTimer queue_timer; - if ((mOutBufferLength > 4192) && queue_timer.getElapsedTimeF32() > 1.f) + mActualBytesIn += packet_size; + + mHeadIndex = (mHeadIndex + 1) % (S16)(mPacketRing.size()); + if (mNumBufferedPackets < MAX_BUFFER_RING_SIZE) { - // Add it to the queue - LL_INFOS() << "Outbound packet queue " << mOutBufferLength << " bytes" << LL_ENDL; - queue_timer.reset(); + ++mNumBufferedPackets; + mNumBufferedBytes += packet_size; + } + else + { + // we overwrote an older packet + mNumBufferedBytes += packet_size - old_packet_size; } - packetp = new LLPacketBuffer(host, send_buffer, buf_size); - - mOutBufferLength += packetp->getSize(); - mSendQueue.push(packetp); } } - - return status; + return packet_size; } -bool LLPacketRing::sendPacketImpl(int h_socket, const char * send_buffer, S32 buf_size, LLHost host) +S32 LLPacketRing::drainSocket(S32 socket) { + // drain into buffer + S32 packet_size = 1; + S32 num_loops = 0; + S32 old_num_packets = mNumBufferedPackets; + while (packet_size > 0) + { + packet_size = bufferInboundPacket(socket); + ++num_loops; + } + S32 num_dropped_packets = (num_loops - 1 + old_num_packets) - mNumBufferedPackets; + if (num_dropped_packets > 0) + { + // It will eventually be accounted by mDroppedPackets + // and mPacketsLost, but track it here for logging purposes. + mNumDroppedPackets += num_dropped_packets; + } + return (S32)(mNumBufferedPackets); +} - if (!LLProxy::isSOCKSProxyEnabled()) +bool LLPacketRing::expandRing() +{ + // compute larger size + constexpr S16 BUFFER_RING_EXPANSION = 256; + S16 old_size = (S16)(mPacketRing.size()); + S16 new_size = llmin(old_size + BUFFER_RING_EXPANSION, MAX_BUFFER_RING_SIZE); + if (new_size == old_size) { - return send_packet(h_socket, send_buffer, buf_size, host.getAddress(), host.getPort()); + // mPacketRing is already maxed out + return false; } - char headered_send_buffer[NET_BUFFER_SIZE + SOCKS_HEADER_SIZE]; + // make a larger ring and copy packet pointers + std::vector<LLPacketBuffer*> new_ring(new_size, nullptr); + for (S16 i = 0; i < old_size; ++i) + { + S16 j = (mHeadIndex + i) % old_size; + new_ring[i] = mPacketRing[j]; + } - proxywrap_t *socks_header = static_cast<proxywrap_t*>(static_cast<void*>(&headered_send_buffer)); - socks_header->rsv = 0; - socks_header->addr = host.getAddress(); - socks_header->port = htons(host.getPort()); - socks_header->atype = ADDRESS_IPV4; - socks_header->frag = 0; + // allocate new packets for the remainder of new_ring + LLHost invalid_host; + for (S16 i = old_size; i < new_size; ++i) + { + new_ring[i] = new LLPacketBuffer(invalid_host, nullptr, 0); + } - memcpy(headered_send_buffer + SOCKS_HEADER_SIZE, send_buffer, buf_size); + // swap the rings and reset mHeadIndex + mPacketRing.swap(new_ring); + mHeadIndex = mNumBufferedPackets; + return true; +} - return send_packet( h_socket, - headered_send_buffer, - buf_size + SOCKS_HEADER_SIZE, - LLProxy::getInstance()->getUDPProxy().getAddress(), - LLProxy::getInstance()->getUDPProxy().getPort()); +void LLPacketRing::dumpPacketRingStats() +{ + mNumDroppedPacketsTotal += mNumDroppedPackets; + LL_INFOS("Messaging") << "Packet ring stats: " << std::endl + << "Buffered packets: " << mNumBufferedPackets << std::endl + << "Buffered bytes: " << mNumBufferedBytes << std::endl + << "Dropped packets current: " << mNumDroppedPackets << std::endl + << "Dropped packets total: " << mNumDroppedPacketsTotal << std::endl + << "Dropped packets percentage: " << mDropPercentage << "%" << std::endl + << "Actual in bytes: " << mActualBytesIn << std::endl + << "Actual out bytes: " << mActualBytesOut << LL_ENDL; + mNumDroppedPackets = 0; } diff --git a/indra/llmessage/llpacketring.h b/indra/llmessage/llpacketring.h index f0e95f8524..237efc12e0 100644 --- a/indra/llmessage/llpacketring.h +++ b/indra/llmessage/llpacketring.h @@ -25,16 +25,14 @@ * $/LicenseInfo$ */ -#ifndef LL_LLPACKETRING_H -#define LL_LLPACKETRING_H +#pragma once -#include <queue> +#include <vector> #include "llhost.h" #include "llpacketbuffer.h" -#include "llproxy.h" #include "llthrottle.h" -#include "net.h" + class LLPacketRing { @@ -42,60 +40,70 @@ public: LLPacketRing(); ~LLPacketRing(); - void cleanup(); + // receive one packet: either buffered or from the socket + S32 receivePacket (S32 socket, char *datap); + + // send one packet + bool sendPacket(int h_socket, const char * send_buffer, S32 buf_size, LLHost host); + + // drains packets from socket and returns final mNumBufferedPackets + S32 drainSocket(S32 socket); void dropPackets(U32); void setDropPercentage (F32 percent_to_drop); - void setUseInThrottle(const bool use_throttle); - void setUseOutThrottle(const bool use_throttle); - void setInBandwidth(const F32 bps); - void setOutBandwidth(const F32 bps); - S32 receivePacket (S32 socket, char *datap); - S32 receiveFromRing (S32 socket, char *datap); - bool sendPacket(int h_socket, char * send_buffer, S32 buf_size, LLHost host); + inline LLHost getLastSender() const; + inline LLHost getLastReceivingInterface() const; - inline LLHost getLastSender(); - inline LLHost getLastReceivingInterface(); + S32 getActualInBytes() const { return mActualBytesIn; } + S32 getActualOutBytes() const { return mActualBytesOut; } + S32 getAndResetActualInBits() { S32 bits = mActualBytesIn * 8; mActualBytesIn = 0; return bits;} + S32 getAndResetActualOutBits() { S32 bits = mActualBytesOut * 8; mActualBytesOut = 0; return bits;} - S32 getAndResetActualInBits() { S32 bits = mActualBitsIn; mActualBitsIn = 0; return bits;} - S32 getAndResetActualOutBits() { S32 bits = mActualBitsOut; mActualBitsOut = 0; return bits;} -protected: - bool mUseInThrottle; - bool mUseOutThrottle; + S32 getNumBufferedPackets() const { return (S32)(mNumBufferedPackets); } + S32 getNumBufferedBytes() const { return mNumBufferedBytes; } + S32 getNumDroppedPackets() const { return mNumDroppedPacketsTotal + mNumDroppedPackets; } - // For simulating a lower-bandwidth connection - BPS - LLThrottle mInThrottle; - LLThrottle mOutThrottle; + void dumpPacketRingStats(); +protected: + // returns 'true' if we should intentionally drop a packet + bool computeDrop(); - S32 mActualBitsIn; - S32 mActualBitsOut; - S32 mMaxBufferLength; // How much data can we queue up before dropping data. - S32 mInBufferLength; // Current incoming buffer length - S32 mOutBufferLength; // Current outgoing buffer length + // returns packet_size of received packet, zero or less if no packet found + S32 receiveOrDropPacket(S32 socket, char *datap, bool drop); + S32 receiveOrDropBufferedPacket(char *datap, bool drop); - F32 mDropPercentage; // % of packets to drop - U32 mPacketsToDrop; // drop next n packets + // returns packet_size of packet buffered + S32 bufferInboundPacket(S32 socket); - std::queue<LLPacketBuffer *> mReceiveQueue; - std::queue<LLPacketBuffer *> mSendQueue; + // returns 'true' if ring was expanded + bool expandRing(); +protected: + std::vector<LLPacketBuffer*> mPacketRing; + S16 mHeadIndex { 0 }; + S16 mNumBufferedPackets { 0 }; + S32 mNumDroppedPackets { 0 }; + S32 mNumDroppedPacketsTotal { 0 }; + S32 mNumBufferedBytes { 0 }; + + S32 mActualBytesIn { 0 }; + S32 mActualBytesOut { 0 }; + F32 mDropPercentage { 0.0f }; // % of inbound packets to drop + U32 mPacketsToDrop { 0 }; // drop next inbound n packets + + // These are the sender and receiving_interface for the last packet delivered by receivePacket() LLHost mLastSender; LLHost mLastReceivingIF; - -private: - bool sendPacketImpl(int h_socket, const char * send_buffer, S32 buf_size, LLHost host); }; -inline LLHost LLPacketRing::getLastSender() +inline LLHost LLPacketRing::getLastSender() const { return mLastSender; } -inline LLHost LLPacketRing::getLastReceivingInterface() +inline LLHost LLPacketRing::getLastReceivingInterface() const { return mLastReceivingIF; } - -#endif diff --git a/indra/llmessage/message.cpp b/indra/llmessage/message.cpp index cfa5178fc6..ad1ff86807 100644 --- a/indra/llmessage/message.cpp +++ b/indra/llmessage/message.cpp @@ -656,8 +656,7 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count ) // UseCircuitCode is allowed in even from an invalid circuit, so that // we can toss circuits around. - if( - valid_packet && + else if ( !cdp && (mTemplateMessageReader->getMessageName() != _PREHASH_UseCircuitCode)) @@ -667,8 +666,7 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count ) valid_packet = false; } - if( - valid_packet && + if ( valid_packet && cdp && !cdp->getTrusted() && mTemplateMessageReader->isTrusted()) @@ -680,7 +678,7 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count ) valid_packet = false; } - if( valid_packet ) + if ( valid_packet ) { logValidMsg(cdp, host, recv_reliable, recv_resent, acks>0 ); valid_packet = mTemplateMessageReader->readMessage(buffer, host); @@ -726,6 +724,7 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count ) // Check to see if we need to print debug info if ((mt_sec - mCircuitPrintTime) > mCircuitPrintFreq) { + mPacketRing.dumpPacketRingStats(); dumpCircuitInfo(); mCircuitPrintTime = mt_sec; } @@ -821,6 +820,11 @@ void LLMessageSystem::processAcks(LockMessageChecker&, F32 collect_time) } } +S32 LLMessageSystem::drainUdpSocket() +{ + return mPacketRing.drainSocket(mSocket); +} + void LLMessageSystem::copyMessageReceivedToSend() { // NOTE: babbage: switch builder to match reader to avoid diff --git a/indra/llmessage/message.h b/indra/llmessage/message.h index b4b0d94021..1844d5e7cd 100644 --- a/indra/llmessage/message.h +++ b/indra/llmessage/message.h @@ -417,6 +417,9 @@ public: bool checkMessages(LockMessageChecker&, S64 frame_count = 0 ); void processAcks(LockMessageChecker&, F32 collect_time = 0.f); + // returns total number of buffered packets after the drain + S32 drainUdpSocket(); + bool isMessageFast(const char *msg); bool isMessage(const char *msg) { diff --git a/indra/llmessage/net.cpp b/indra/llmessage/net.cpp index f153c938cf..2be5a9e5b6 100644 --- a/indra/llmessage/net.cpp +++ b/indra/llmessage/net.cpp @@ -76,14 +76,8 @@ static U32 gsnReceivingIFAddr = INVALID_HOST_IP_ADDRESS; // Address to which dat const char* LOOPBACK_ADDRESS_STRING = "127.0.0.1"; const char* BROADCAST_ADDRESS_STRING = "255.255.255.255"; -#if LL_DARWIN - // macOS returns an error when trying to set these to 400000. Smaller values succeed. - const int SEND_BUFFER_SIZE = 200000; - const int RECEIVE_BUFFER_SIZE = 200000; -#else // LL_DARWIN - const int SEND_BUFFER_SIZE = 400000; - const int RECEIVE_BUFFER_SIZE = 400000; -#endif // LL_DARWIN +const int SEND_BUFFER_SIZE = 200000; +const int RECEIVE_BUFFER_SIZE = 800000; // universal functions (cross-platform) diff --git a/indra/llrender/llfontbitmapcache.cpp b/indra/llrender/llfontbitmapcache.cpp index 83f5d31186..6a3af1e608 100644 --- a/indra/llrender/llfontbitmapcache.cpp +++ b/indra/llrender/llfontbitmapcache.cpp @@ -141,6 +141,7 @@ bool LLFontBitmapCache::nextOpenPos(S32 width, S32& pos_x, S32& pos_y, EFontGlyp bitmap_num = getNumBitmaps(bitmap_type) - 1; mCurrentOffsetX[bitmap_idx] += width + 1; + mGeneration++; return true; } @@ -168,6 +169,7 @@ void LLFontBitmapCache::reset() mBitmapWidth = 0; mBitmapHeight = 0; + mGeneration++; } //static diff --git a/indra/llrender/llfontbitmapcache.h b/indra/llrender/llfontbitmapcache.h index f2dfd87877..0ae4e6bed0 100644 --- a/indra/llrender/llfontbitmapcache.h +++ b/indra/llrender/llfontbitmapcache.h @@ -63,6 +63,7 @@ public: U32 getNumBitmaps(EFontGlyphType bitmapType) const { return (bitmapType < EFontGlyphType::Count) ? static_cast<U32>(mImageRawVec[static_cast<U32>(bitmapType)].size()) : 0U; } S32 getBitmapWidth() const { return mBitmapWidth; } S32 getBitmapHeight() const { return mBitmapHeight; } + S32 getCacheGeneration() const { return mGeneration; } protected: static U32 getNumComponents(EFontGlyphType bitmap_type); @@ -74,6 +75,7 @@ private: S32 mCurrentOffsetY[static_cast<U32>(EFontGlyphType::Count)] = { 1 }; S32 mMaxCharWidth = 0; S32 mMaxCharHeight = 0; + S32 mGeneration = 0; std::vector<LLPointer<LLImageRaw>> mImageRawVec[static_cast<U32>(EFontGlyphType::Count)]; std::vector<LLPointer<LLImageGL>> mImageGLVec[static_cast<U32>(EFontGlyphType::Count)]; }; diff --git a/indra/llrender/llfontfreetype.cpp b/indra/llrender/llfontfreetype.cpp index 067b13bfb9..723f0d5062 100644 --- a/indra/llrender/llfontfreetype.cpp +++ b/indra/llrender/llfontfreetype.cpp @@ -148,7 +148,6 @@ LLFontFreetype::LLFontFreetype() mIsFallback(false), mFTFace(NULL), mRenderGlyphCount(0), - mAddGlyphCount(0), mStyle(0), mPointSize(0) { @@ -576,7 +575,6 @@ LLFontGlyphInfo* LLFontFreetype::addGlyphFromFont(const LLFontFreetype *fontp, l S32 pos_x, pos_y; U32 bitmap_num; mFontBitmapCachep->nextOpenPos(width, pos_x, pos_y, bitmap_glyph_type, bitmap_num); - mAddGlyphCount++; LLFontGlyphInfo* gi = new LLFontGlyphInfo(glyph_index, requested_glyph_type); gi->mXBitmapOffset = pos_x; diff --git a/indra/llrender/llfontfreetype.h b/indra/llrender/llfontfreetype.h index a2d925c5f6..783bf4a4b3 100644 --- a/indra/llrender/llfontfreetype.h +++ b/indra/llrender/llfontfreetype.h @@ -148,7 +148,6 @@ public: void setStyle(U8 style); U8 getStyle() const; - S32 getAddedGlyphs() const { return mAddGlyphCount; } private: void resetBitmapCache(); @@ -188,7 +187,6 @@ private: mutable LLFontBitmapCache* mFontBitmapCachep; mutable S32 mRenderGlyphCount; - mutable S32 mAddGlyphCount; }; #endif // LL_FONTFREETYPE_H diff --git a/indra/llrender/llfontgl.cpp b/indra/llrender/llfontgl.cpp index 4037c036e5..16eec1fdd2 100644 --- a/indra/llrender/llfontgl.cpp +++ b/indra/llrender/llfontgl.cpp @@ -110,9 +110,10 @@ S32 LLFontGL::getNumFaces(const std::string& filename) return mFontFreetype->getNumFaces(filename); } -S32 LLFontGL::getKnownGlyphCount() const +S32 LLFontGL::getCacheGeneration() const { - return mFontFreetype ? mFontFreetype->getAddedGlyphs() : 0; + const LLFontBitmapCache* font_bitmap_cache = mFontFreetype->getFontBitmapCache(); + return font_bitmap_cache->getCacheGeneration(); } S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, const LLRect& rect, const LLColor4 &color, HAlign halign, VAlign valign, U8 style, diff --git a/indra/llrender/llfontgl.h b/indra/llrender/llfontgl.h index 73efc6b1eb..1c8e036f58 100644 --- a/indra/llrender/llfontgl.h +++ b/indra/llrender/llfontgl.h @@ -90,7 +90,7 @@ public: bool loadFace(const std::string& filename, F32 point_size, const F32 vert_dpi, const F32 horz_dpi, bool is_fallback, S32 face_n); S32 getNumFaces(const std::string& filename); - S32 getKnownGlyphCount() const; + S32 getCacheGeneration() const; S32 render(const LLWString &text, S32 begin_offset, const LLRect& rect, diff --git a/indra/llrender/llfontvertexbuffer.cpp b/indra/llrender/llfontvertexbuffer.cpp index b53a841a87..a223509d30 100644 --- a/indra/llrender/llfontvertexbuffer.cpp +++ b/indra/llrender/llfontvertexbuffer.cpp @@ -148,7 +148,7 @@ S32 LLFontVertexBuffer::render( || mLastHorizDPI != LLFontGL::sHorizDPI || mLastOrigin != LLFontGL::sCurOrigin || mLastResGeneration != LLFontGL::sResolutionGeneration - || mLastFontGlyphCount != fontp->getKnownGlyphCount()) + || mLastFontCacheGen != fontp->getCacheGeneration()) { genBuffers(fontp, text, begin_offset, x, y, color, halign, valign, style, shadow, max_chars, max_pixels, right_x, use_ellipses, use_color); @@ -180,6 +180,9 @@ void LLFontVertexBuffer::genBuffers( { // todo: add a debug build assert if this triggers too often for to long? mBufferList.clear(); + // Save before rendreing, it can change mid-render, + // so will need to rerender previous characters + mLastFontCacheGen = fontp->getCacheGeneration(); gGL.beginList(&mBufferList); mChars = fontp->render(text, begin_offset, x, y, color, halign, valign, @@ -204,7 +207,6 @@ void LLFontVertexBuffer::genBuffers( mLastHorizDPI = LLFontGL::sHorizDPI; mLastOrigin = LLFontGL::sCurOrigin; mLastResGeneration = LLFontGL::sResolutionGeneration; - mLastFontGlyphCount = fontp->getKnownGlyphCount(); if (right_x) { diff --git a/indra/llrender/llfontvertexbuffer.h b/indra/llrender/llfontvertexbuffer.h index d5e1280dbf..a9e1e2337c 100644 --- a/indra/llrender/llfontvertexbuffer.h +++ b/indra/llrender/llfontvertexbuffer.h @@ -122,7 +122,7 @@ private: // Adding new characters to bitmap cache can alter value from getBitmapWidth(); // which alters whole string. So rerender when new characters were added to cache. - S32 mLastFontGlyphCount = 0; + S32 mLastFontCacheGen = 0; static bool sEnableBufferCollection; }; diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index 17fa8f14a9..3cdbb139be 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -2475,12 +2475,15 @@ void LLGLState::checkStates(GLboolean writeAlpha) return; } - GLint src; - GLint dst; - glGetIntegerv(GL_BLEND_SRC, &src); - glGetIntegerv(GL_BLEND_DST, &dst); - llassert_always(src == GL_SRC_ALPHA); - llassert_always(dst == GL_ONE_MINUS_SRC_ALPHA); + GLint srcRGB, dstRGB, srcAlpha, dstAlpha; + glGetIntegerv(GL_BLEND_SRC_RGB, &srcRGB); + glGetIntegerv(GL_BLEND_DST_RGB, &dstRGB); + glGetIntegerv(GL_BLEND_SRC_ALPHA, &srcAlpha); + glGetIntegerv(GL_BLEND_DST_ALPHA, &dstAlpha); + llassert_always(srcRGB == GL_SRC_ALPHA); + llassert_always(srcAlpha == GL_SRC_ALPHA); + llassert_always(dstRGB == GL_ONE_MINUS_SRC_ALPHA); + llassert_always(dstAlpha == GL_ONE_MINUS_SRC_ALPHA); // disable for now until usage is consistent //GLboolean colorMask[4]; diff --git a/indra/llui/llfolderview.cpp b/indra/llui/llfolderview.cpp index a059c4d741..b664065532 100644 --- a/indra/llui/llfolderview.cpp +++ b/indra/llui/llfolderview.cpp @@ -2011,7 +2011,7 @@ void LLFolderView::onIdleUpdateMenu(void* user_data) self->updateMenuOptions(menu); menu->needsArrange(); // update menu height if needed } - gIdleCallbacks.deleteFunction(onIdleUpdateMenu, NULL); + gIdleCallbacks.deleteFunction(onIdleUpdateMenu, self); } bool LLFolderView::isFolderSelected() diff --git a/indra/llxml/llxmlnode.cpp b/indra/llxml/llxmlnode.cpp index 48805f8240..8a2c36a307 100644 --- a/indra/llxml/llxmlnode.cpp +++ b/indra/llxml/llxmlnode.cpp @@ -309,6 +309,7 @@ void LLXMLNode::addChild(LLXMLNodePtr& new_child) // virtual LLXMLNodePtr LLXMLNode::createChild(const char* name, bool is_attribute) { + // Todo: validate to make sure node name is valid? (no spaces, etc) return createChild(gStringTable.addStringEntry(name), is_attribute); } diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index 0f9f025fe4..991d8e5c5f 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -7.1.12 +7.1.13 diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 1aec56447d..7b764d47b3 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -7860,6 +7860,17 @@ <key>Value</key> <integer>0</integer> </map> + <key>RenderTextureVRAMDivisor</key> + <map> + <key>Comment</key> + <string>Divisor for maximum amount of VRAM the viewer will use for textures. 1 = all the VRAM. Used in conjunction with RenderMaxVRAMBudget.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>U32</string> + <key>Value</key> + <integer>2</integer> + </map> <key>RenderMinFreeMainMemoryThreshold</key> <map> <key>Comment</key> @@ -11684,7 +11695,7 @@ <key>Type</key> <string>F32</string> <key>Value</key> - <real>0.04</real> + <real>0.0095</real> </map> <key>TextureScaleMaxAreaFactor</key> <map> diff --git a/indra/newview/app_settings/shaders/class1/deferred/deferredUtil.glsl b/indra/newview/app_settings/shaders/class1/deferred/deferredUtil.glsl index d32455d70c..fa94fb98f0 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/deferredUtil.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/deferredUtil.glsl @@ -80,6 +80,17 @@ vec3 atmosFragLightingLinear(vec3 light, vec3 additive, vec3 atten); vec4 decodeNormal(vec4 norm); +vec3 clampHDRRange(vec3 color) +{ + // Why do this? + // There are situations where the color range will go to something insane - potentially producing infs and NaNs even. + // This is a safety measure to prevent that. + // As to the specific number there - allegedly some HDR displays expect values to be in the 0-11.2 range. Citation needed. + // -Geenz 2025-03-05 + color = mix(color, vec3(0.0), isnan(color)); + return clamp(color, vec3(0.0), vec3(11.2)); +} + float calcLegacyDistanceAttenuation(float distance, float falloff) { float dist_atten = 1.0 - clamp((distance + falloff)/(1.0 + falloff), 0.0, 1.0); diff --git a/indra/newview/app_settings/shaders/class1/deferred/postDeferredF.glsl b/indra/newview/app_settings/shaders/class1/deferred/postDeferredF.glsl index 9797bcd2ce..4e737492a7 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/postDeferredF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/postDeferredF.glsl @@ -69,6 +69,8 @@ void dofSampleNear(inout vec4 diff, inout float w, float min_sc, vec2 tc) w += wg; } +vec3 clampHDRRange(vec3 color); + void main() { vec2 tc = vary_fragcoord.xy; @@ -120,5 +122,6 @@ void main() diff /= w; } + diff.rgb = clampHDRRange(diff.rgb); frag_color = diff; } diff --git a/indra/newview/app_settings/shaders/class1/deferred/postDeferredGammaCorrect.glsl b/indra/newview/app_settings/shaders/class1/deferred/postDeferredGammaCorrect.glsl index befd2ae6da..4ccc6f54a8 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/postDeferredGammaCorrect.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/postDeferredGammaCorrect.glsl @@ -43,6 +43,8 @@ vec3 legacyGamma(vec3 color) return c; } +vec3 clampHDRRange(vec3 color); + void main() { //this is the one of the rare spots where diffuseRect contains linear color values (not sRGB) @@ -53,6 +55,7 @@ void main() diff.rgb = legacyGamma(diff.rgb); #endif - frag_color = max(diff, vec4(0)); + diff.rgb = clampHDRRange(diff.rgb); + frag_color = diff; } diff --git a/indra/newview/app_settings/shaders/class1/deferred/postDeferredNoDoFF.glsl b/indra/newview/app_settings/shaders/class1/deferred/postDeferredNoDoFF.glsl index 32b0a1ac8e..c05b4eed7a 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/postDeferredNoDoFF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/postDeferredNoDoFF.glsl @@ -71,6 +71,7 @@ float noise(vec2 x) { //============================= +vec3 clampHDRRange(vec3 color); void main() @@ -84,6 +85,7 @@ void main() diff.rgb += nz*0.003; #endif + diff.rgb = clampHDRRange(diff.rgb); frag_color = diff; gl_FragDepth = texture(depthMap, vary_fragcoord.xy).r; diff --git a/indra/newview/app_settings/shaders/class1/deferred/postDeferredTonemap.glsl b/indra/newview/app_settings/shaders/class1/deferred/postDeferredTonemap.glsl index c4610bffac..1f01c7f16a 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/postDeferredTonemap.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/postDeferredTonemap.glsl @@ -34,6 +34,8 @@ in vec2 vary_fragcoord; vec3 linear_to_srgb(vec3 cl); vec3 toneMap(vec3 color); +vec3 clampHDRRange(vec3 color); + void main() { //this is the one of the rare spots where diffuseRect contains linear color values (not sRGB) @@ -45,6 +47,7 @@ void main() diff.rgb = clamp(diff.rgb, vec3(0.0), vec3(1.0)); #endif + diff.rgb = clampHDRRange(diff.rgb); //debugExposure(diff.rgb); frag_color = max(diff, vec4(0)); } diff --git a/indra/newview/app_settings/shaders/class3/deferred/softenLightF.glsl b/indra/newview/app_settings/shaders/class3/deferred/softenLightF.glsl index 2e288184bf..6cec65ad83 100644 --- a/indra/newview/app_settings/shaders/class3/deferred/softenLightF.glsl +++ b/indra/newview/app_settings/shaders/class3/deferred/softenLightF.glsl @@ -104,6 +104,7 @@ vec3 pbrBaseLight(vec3 diffuseColor, vec3 atten); GBufferInfo getGBuffer(vec2 screenpos); +vec3 clampHDRRange(vec3 color); void adjustIrradiance(inout vec3 irradiance, float ambocc) { @@ -278,6 +279,7 @@ void main() float final_scale = 1; if (classic_mode > 0) final_scale = 1.1; - frag_color.rgb = max(color.rgb * final_scale, vec3(0)); //output linear since local lights will be added to this shader's results + + frag_color.rgb = clampHDRRange(color.rgb * final_scale); //output linear since local lights will be added to this shader's results frag_color.a = 0.0; } diff --git a/indra/newview/app_settings/shaders/class3/environment/waterF.glsl b/indra/newview/app_settings/shaders/class3/environment/waterF.glsl index 18dc367568..a194832b1d 100644 --- a/indra/newview/app_settings/shaders/class3/environment/waterF.glsl +++ b/indra/newview/app_settings/shaders/class3/environment/waterF.glsl @@ -255,7 +255,7 @@ void main() shadow = sampleDirectionalShadow(pos.xyz, norm.xyz, distort); #endif - vec3 sunlit_linear = srgb_to_linear(sunlit); + vec3 sunlit_linear = sunlit; float fade = 1.0; #ifdef TRANSPARENT_WATER float depth = texture(depthMap, distort).r; @@ -263,7 +263,12 @@ void main() vec3 refPos = getPositionWithNDC(vec3(distort*2.0-vec2(1.0), depth*2.0-1.0)); // Calculate some distance fade in the water to better assist with refraction blending and reducing the refraction texture's "disconnect". - fade = max(0.0,min(1.0, (pos.z - refPos.z) / 10.0)) * water_mask; +#ifdef SHORELINE_FADE + fade = max(0.0,min(1.0, (pos.z - refPos.z) / 10.0)) +#else + fade = 1.0 * water_mask; +#endif + distort2 = mix(distort, distort2, min(1.0, fade * 10.0)); depth = texture(depthMap, distort2).r; @@ -317,7 +322,7 @@ void main() pbrPunctual(diffuseColor, specularColor, perceptualRoughness, metallic, normalize(wavef+up*max(dist, 32.0)/32.0*(1.0-vdu)), v, normalize(light_dir), nl, diffPunc, specPunc); - vec3 punctual = clamp(nl * (diffPunc + specPunc), vec3(0), vec3(10)) * sunlit_linear * shadow; + vec3 punctual = clamp(nl * (diffPunc + specPunc), vec3(0), vec3(10)) * sunlit_linear * shadow * atten; radiance *= df2.y; //radiance = toneMapNoExposure(radiance); vec3 color = vec3(0); diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp index aca0775fe8..43e8fa3e3c 100644 --- a/indra/newview/gltfscenemanager.cpp +++ b/indra/newview/gltfscenemanager.cpp @@ -356,8 +356,9 @@ void GLTFSceneManager::addGLTFObject(LLViewerObject* obj, LLUUID gltf_id) llassert(obj->getVolume()->getParams().getSculptID() == gltf_id); llassert(obj->getVolume()->getParams().getSculptType() == LL_SCULPT_TYPE_GLTF); - if (obj->mGLTFAsset) - { // object already has a GLTF asset, don't reload it + if (obj->mGLTFAsset || obj->mIsGLTFAssetMissing ) + { + // object already has a GLTF asset or load failed, don't reload it // TODO: below assertion fails on dupliate requests for assets -- possibly need to touch up asset loading state machine // llassert(std::find(mObjects.begin(), mObjects.end(), obj) != mObjects.end()); @@ -398,16 +399,19 @@ void GLTFSceneManager::onGLTFBinLoadComplete(const LLUUID& id, LLAssetType::ETyp } else { - LL_WARNS("GLTF") << "Failed to prepare GLTF asset: " << id << LL_ENDL; + LL_WARNS("GLTF") << "Failed to prepare GLTF asset: " << id << ". Marking as missing." << LL_ENDL; + obj->mIsGLTFAssetMissing = true; obj->mGLTFAsset = nullptr; } } } + obj->unref(); // todo: use LLPointer } } else { - LL_WARNS("GLTF") << "Failed to load GLTF asset: " << id << LL_ENDL; + LL_WARNS("GLTF") << "Failed to load GLTF asset: " << id << ". Marking as missing." << LL_ENDL; + obj->mIsGLTFAssetMissing = true; obj->unref(); } }); @@ -446,7 +450,8 @@ void GLTFSceneManager::onGLTFLoadComplete(const LLUUID& id, LLAssetType::EType a } else { - LL_WARNS("GLTF") << "Buffer URI is not a valid UUID: " << buffer.mUri << LL_ENDL; + LL_WARNS("GLTF") << "Buffer URI is not a valid UUID: " << buffer.mUri << " for asset id: " << id << ". Marking as missing." << LL_ENDL; + obj->mIsGLTFAssetMissing = true; obj->unref(); return; } @@ -455,7 +460,8 @@ void GLTFSceneManager::onGLTFLoadComplete(const LLUUID& id, LLAssetType::EType a } else { - LL_WARNS("GLTF") << "Failed to load GLTF asset: " << id << LL_ENDL; + LL_WARNS("GLTF") << "Failed to load GLTF asset: " << id << ". Marking as missing." << LL_ENDL; + obj->mIsGLTFAssetMissing = true; obj->unref(); } } @@ -517,6 +523,7 @@ void GLTFSceneManager::update() if (mUploadingObject) { mUploadingObject->mGLTFAsset = nullptr; + mUploadingObject->mIsGLTFAssetMissing = false; mUploadingObject->setGLTFAsset(assetId); mUploadingObject->markForUpdate(); mUploadingObject = nullptr; diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 8756baa04a..a076594e0a 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -2501,7 +2501,10 @@ void LLAgent::endAnimationUpdateUI() gAgentAvatarp->updateAttachmentVisibility(gAgentCamera.getCameraMode()); } - gFloaterTools->dirty(); + if (gFloaterTools) + { + gFloaterTools->dirty(); + } // Don't let this be called more than once if the camera // mode hasn't changed. --JC diff --git a/indra/newview/llappdelegate-objc.mm b/indra/newview/llappdelegate-objc.mm index 1090888c1c..d4b05dde72 100644 --- a/indra/newview/llappdelegate-objc.mm +++ b/indra/newview/llappdelegate-objc.mm @@ -301,7 +301,8 @@ struct AttachmentInfo AttachmentInfo(metadata.logFilePathname, "text/plain"), AttachmentInfo(metadata.userSettingsPathname, "text/xml"), AttachmentInfo(metadata.accountSettingsPathname, "text/xml"), - AttachmentInfo(metadata.staticDebugPathname, "text/xml") + AttachmentInfo(metadata.staticDebugPathname, "text/xml"), + AttachmentInfo(metadata.attributesPathname, "text/xml") }; secondLogPath = metadata.secondLogFilePathname; diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 430b42acf6..43ea083adf 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -358,10 +358,6 @@ std::string gLastVersionChannel; LLVector3 gWindVec(3.0, 3.0, 0.0); LLVector3 gRelativeWindVec(0.0, 0.0, 0.0); -U32 gPacketsIn = 0; - -bool gPrintMessagesThisFrame = false; - bool gRandomizeFramerate = false; bool gPeriodicSlowFrame = false; @@ -370,6 +366,7 @@ bool gLLErrorActivated = false; bool gLogoutInProgress = false; bool gSimulateMemLeak = false; +bool gDoDisconnect = false; // We don't want anyone, especially threads working on the graphics pipeline, // to have to block due to this WorkQueue being full. @@ -383,7 +380,6 @@ const std::string MARKER_FILE_NAME("SecondLife.exec_marker"); const std::string START_MARKER_FILE_NAME("SecondLife.start_marker"); const std::string ERROR_MARKER_FILE_NAME("SecondLife.error_marker"); const std::string LOGOUT_MARKER_FILE_NAME("SecondLife.logout_marker"); -static bool gDoDisconnect = false; static std::string gLaunchFileOnQuit; // Used on Win32 for other apps to identify our window (eg, win_setup) @@ -1533,9 +1529,9 @@ bool LLAppViewer::doFrame() { LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df pauseMainloopTimeout"); - pingMainloopTimeout("Main:Sleep"); + pingMainloopTimeout("Main:Sleep"); - pauseMainloopTimeout(); + pauseMainloopTimeout(); } // Sleep and run background threads @@ -4297,7 +4293,7 @@ U32 LLAppViewer::getTextureCacheVersion() U32 LLAppViewer::getDiskCacheVersion() { // Viewer disk cache version intorduced in Simple Cache Viewer, change if the cache format changes. - const U32 DISK_CACHE_VERSION = 1; + const U32 DISK_CACHE_VERSION = 2; return DISK_CACHE_VERSION ; } @@ -4956,6 +4952,20 @@ void LLAppViewer::idle() if (gTeleportDisplay) { + if (gAgent.getTeleportState() == LLAgent::TELEPORT_ARRIVING) + { + // Teleported, but waiting for things to load, start processing surface data + { + LL_RECORD_BLOCK_TIME(FTM_NETWORK); + gVLManager.unpackData(); + } + { + LL_RECORD_BLOCK_TIME(FTM_REGION_UPDATE); + const F32 max_region_update_time = .001f; // 1ms + LLWorld::getInstance()->updateRegions(max_region_update_time); + } + } + return; } @@ -5392,12 +5402,9 @@ void LLAppViewer::idleNameCache() // Handle messages, and all message related stuff // -#define TIME_THROTTLE_MESSAGES -#ifdef TIME_THROTTLE_MESSAGES -#define CHECK_MESSAGES_DEFAULT_MAX_TIME .020f // 50 ms = 50 fps (just for messages!) +constexpr F32 CHECK_MESSAGES_DEFAULT_MAX_TIME = 0.020f; // 50 ms = 50 fps (just for messages!) static F32 CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME; -#endif static LLTrace::BlockTimerStatHandle FTM_IDLE_NETWORK("Idle Network"); static LLTrace::BlockTimerStatHandle FTM_MESSAGE_ACKS("Message Acks"); @@ -5424,6 +5431,7 @@ void LLAppViewer::idleNetwork() F32 total_time = 0.0f; { + bool needs_drain = false; LockMessageChecker lmc(gMessageSystem); while (lmc.checkAllMessages(frame_count, gServicePump)) { @@ -5436,54 +5444,44 @@ void LLAppViewer::idleNetwork() } total_decoded++; - gPacketsIn++; if (total_decoded > MESSAGE_MAX_PER_FRAME) { + needs_drain = true; break; } -#ifdef TIME_THROTTLE_MESSAGES // Prevent slow packets from completely destroying the frame rate. // This usually happens due to clumps of avatars taking huge amount // of network processing time (which needs to be fixed, but this is // a good limit anyway). total_time = check_message_timer.getElapsedTimeF32(); if (total_time >= CheckMessagesMaxTime) + { + needs_drain = true; break; -#endif + } + } + if (needs_drain || gMessageSystem->mPacketRing.getNumBufferedPackets() > 0) + { + // Rather than allow packets to silently backup on the socket + // we drain them into our own buffer so we know how many exist. + S32 num_buffered_packets = gMessageSystem->drainUdpSocket(); + if (num_buffered_packets > 0) + { + // Increase CheckMessagesMaxTime so that we will eventually catch up + CheckMessagesMaxTime *= 1.035f; // 3.5% ~= 2x in 20 frames, ~8x in 60 frames + } + } + else + { + // Reset CheckMessagesMaxTime to default value + CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME; } // Handle per-frame message system processing. lmc.processAcks(gSavedSettings.getF32("AckCollectTime")); } - -#ifdef TIME_THROTTLE_MESSAGES - if (total_time >= CheckMessagesMaxTime) - { - // Increase CheckMessagesMaxTime so that we will eventually catch up - CheckMessagesMaxTime *= 1.035f; // 3.5% ~= x2 in 20 frames, ~8x in 60 frames - } - else - { - // Reset CheckMessagesMaxTime to default value - CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME; - } -#endif - - // Decode enqueued messages... - S32 remaining_possible_decodes = MESSAGE_MAX_PER_FRAME - total_decoded; - - if( remaining_possible_decodes <= 0 ) - { - LL_INFOS() << "Maxed out number of messages per frame at " << MESSAGE_MAX_PER_FRAME << LL_ENDL; - } - - if (gPrintMessagesThisFrame) - { - LL_INFOS() << "Decoded " << total_decoded << " msgs this frame!" << LL_ENDL; - gPrintMessagesThisFrame = false; - } } add(LLStatViewer::NUM_NEW_OBJECTS, gObjectList.mNumNewObjects); @@ -5664,6 +5662,27 @@ void LLAppViewer::forceErrorCoroutineCrash() LLCoros::instance().launch("LLAppViewer::crashyCoro", [] {throw LLException("A deliberate crash from LLCoros"); }); } +void LLAppViewer::forceErrorCoroprocedureCrash() +{ + LL_WARNS() << "Forcing a crash in LLCoprocedureManager" << LL_ENDL; + LLCoprocedureManager::instance().enqueueCoprocedure("Upload", "DeliberateCrash", + [](LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t&, const LLUUID&) + { + LL_WARNS() << "Forcing a deliberate bad memory access from LLCoprocedureManager" << LL_ENDL; + S32* crash = NULL; + *crash = 0xDEADBEEF; + }); +} + +void LLAppViewer::forceErrorWorkQueueCrash() +{ + LL::WorkQueue::ptr_t workqueue = LL::WorkQueue::getInstance("General"); + if (workqueue) + { + workqueue->post([]() { throw LLException("This is a deliberate crash from General Queue"); }); + } +} + void LLAppViewer::forceErrorThreadCrash() { class LLCrashTestThread : public LLThread diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index b4756eecd6..3da0246ccf 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -157,9 +157,6 @@ public: void loadNameCache(); void saveNameCache(); - void loadExperienceCache(); - void saveExperienceCache(); - void removeMarkerFiles(); void recordSessionToMarker(); @@ -175,6 +172,8 @@ public: virtual void forceErrorOSSpecificException(); virtual void forceErrorDriverCrash(); virtual void forceErrorCoroutineCrash(); + virtual void forceErrorCoroprocedureCrash(); + virtual void forceErrorWorkQueueCrash(); virtual void forceErrorThreadCrash(); // The list is found in app_settings/settings_files.xml @@ -411,11 +410,10 @@ extern std::string gLastVersionChannel; extern LLVector3 gWindVec; extern LLVector3 gRelativeWindVec; -extern U32 gPacketsIn; -extern bool gPrintMessagesThisFrame; extern bool gRandomizeFramerate; extern bool gPeriodicSlowFrame; +extern bool gDoDisconnect; extern bool gSimulateMemLeak; diff --git a/indra/newview/llappviewermacosx-for-objc.h b/indra/newview/llappviewermacosx-for-objc.h index 5a69cd93fc..c3f1e35872 100644 --- a/indra/newview/llappviewermacosx-for-objc.h +++ b/indra/newview/llappviewermacosx-for-objc.h @@ -41,6 +41,7 @@ void clearDumpLogsDir(); struct CrashMetadata { std::string logFilePathname; + std::string attributesPathname; std::string userSettingsPathname; std::string accountSettingsPathname; std::string staticDebugPathname; diff --git a/indra/newview/llappviewermacosx.cpp b/indra/newview/llappviewermacosx.cpp index 4162c0479a..aab6d00573 100644 --- a/indra/newview/llappviewermacosx.cpp +++ b/indra/newview/llappviewermacosx.cpp @@ -172,6 +172,8 @@ CrashMetadataSingleton::CrashMetadataSingleton() // Note: we depend on being able to read the static_debug_info.log file // from the *previous* run before we overwrite it with the new one for // *this* run. LLAppViewer initialization must happen in the Right Order. + + // Todo: consider converting static file into bugspalt attributes file staticDebugPathname = *gViewerAppPtr->getStaticDebugFile(); std::ifstream static_file(staticDebugPathname); LLSD info; @@ -215,7 +217,32 @@ CrashMetadataSingleton::CrashMetadataSingleton() } } } + + // Populate bugsplat attributes + LLXMLNodePtr out_node = new LLXMLNode("XmlCrashContext", false); + + out_node->createChild("OS", false)->setValue(OSInfo); + out_node->createChild("AppState", false)->setValue(info["StartupState"].asString()); + out_node->createChild("GraphicsCard", false)->setValue(info["GraphicsCard"].asString()); + out_node->createChild("GLVersion", false)->setValue(info["GLInfo"]["GLVersion"].asString()); + out_node->createChild("GLRenderer", false)->setValue(info["GLInfo"]["GLRenderer"].asString()); + out_node->createChild("RAM", false)->setValue(info["RAMInfo"]["Physical"].asString()); + + if (!out_node->isNull()) + { + attributesPathname = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "CrashContext.xml"); + LLFILE* fp = LLFile::fopen(attributesPathname, "w"); + + if (fp != NULL) + { + LLXMLNode::writeHeaderToFile(fp); + out_node->writeToFile(fp); + + fclose(fp); + } + } } + // else Todo: consider fillig at least some values, like OS } // Avoid having to compile all of our LLSingleton machinery in Objective-C++. diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 4d2c58bdab..6e2f4ef670 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -776,14 +776,16 @@ bool LLAppViewerWin32::cleanup() return result; } -void LLAppViewerWin32::reportCrashToBugsplat(void* pExcepInfo) +bool LLAppViewerWin32::reportCrashToBugsplat(void* pExcepInfo) { #if defined(LL_BUGSPLAT) if (sBugSplatSender) { sBugSplatSender->createReport((EXCEPTION_POINTERS*)pExcepInfo); + return true; } #endif // LL_BUGSPLAT + return false; } void LLAppViewerWin32::initLoggingAndGetLastDuration() diff --git a/indra/newview/llappviewerwin32.h b/indra/newview/llappviewerwin32.h index 2242c95b06..250e72edf3 100644 --- a/indra/newview/llappviewerwin32.h +++ b/indra/newview/llappviewerwin32.h @@ -43,7 +43,7 @@ public: bool init() override; // Override to do application initialization bool cleanup() override; - void reportCrashToBugsplat(void* pExcepInfo) override; + bool reportCrashToBugsplat(void* pExcepInfo) override; protected: void initLoggingAndGetLastDuration() override; // Override to clean stack_trace info. diff --git a/indra/newview/llavatarpropertiesprocessor.cpp b/indra/newview/llavatarpropertiesprocessor.cpp index b14d1d7d26..a2e81ed255 100644 --- a/indra/newview/llavatarpropertiesprocessor.cpp +++ b/indra/newview/llavatarpropertiesprocessor.cpp @@ -671,6 +671,7 @@ void LLAvatarPropertiesProcessor::sendClassifiedInfoUpdate(const LLAvatarClassif void LLAvatarPropertiesProcessor::sendPickInfoRequest(const LLUUID& creator_id, const LLUUID& pick_id) { + LL_DEBUGS("PickInfo") << " Requiesting pick info for " << pick_id << LL_ENDL; // Must ask for a pick based on the creator id because // the pick database is distributed to the inventory cluster. JC std::vector<std::string> request_params{ creator_id.asString(), pick_id.asString() }; diff --git a/indra/newview/lldrawpoolwater.cpp b/indra/newview/lldrawpoolwater.cpp index 32de0e5ee7..7d58511d41 100644 --- a/indra/newview/lldrawpoolwater.cpp +++ b/indra/newview/lldrawpoolwater.cpp @@ -213,7 +213,7 @@ void LLDrawPoolWater::renderPostDeferred(S32 pass) else if (tex_b && !tex_a) { shader->bindTexture(LLViewerShaderMgr::BUMP_MAP, tex_b); - tex_a->setFilteringOption(filter_mode); + tex_b->setFilteringOption(filter_mode); blend_factor = 0; // only one tex provided, no blending } else if (tex_b != tex_a) diff --git a/indra/newview/llfloaterlagmeter.cpp b/indra/newview/llfloaterlagmeter.cpp index 28fa8dea9a..1377526f69 100644 --- a/indra/newview/llfloaterlagmeter.cpp +++ b/indra/newview/llfloaterlagmeter.cpp @@ -200,6 +200,7 @@ void LLFloaterLagMeter::determineNetwork() // the network handlers are de-synched from the rendering. F32Milliseconds client_frame_time = frame_recording.getPeriodMean(LLStatViewer::FRAME_STACKTIME); + // Todo: account for LLPacketRing dropped packets? viewer drops those when it can't keep up if(packet_loss >= mNetworkPacketLossCritical) { mNetworkButton->setImageUnselected(LLUI::getUIImage(LAG_CRITICAL_IMAGE_NAME)); diff --git a/indra/newview/llgiveinventory.cpp b/indra/newview/llgiveinventory.cpp index 1259b65ebe..57dd203f2f 100644 --- a/indra/newview/llgiveinventory.cpp +++ b/indra/newview/llgiveinventory.cpp @@ -414,7 +414,11 @@ void LLGiveInventory::commitGiveInventoryItem(const LLUUID& to_agent, effectp->setTargetObject(gObjectList.findObject(to_agent)); effectp->setDuration(LL_HUD_DUR_SHORT); effectp->setColor(LLColor4U(gAgent.getEffectColor())); - gFloaterTools->dirty(); + + if (gFloaterTools) + { + gFloaterTools->dirty(); + } LLMuteList::getInstance()->autoRemove(to_agent, LLMuteList::AR_INVENTORY); @@ -572,7 +576,11 @@ bool LLGiveInventory::commitGiveInventoryCategory(const LLUUID& to_agent, effectp->setTargetObject(gObjectList.findObject(to_agent)); effectp->setDuration(LL_HUD_DUR_SHORT); effectp->setColor(LLColor4U(gAgent.getEffectColor())); - gFloaterTools->dirty(); + + if (gFloaterTools) + { + gFloaterTools->dirty(); + } LLMuteList::getInstance()->autoRemove(to_agent, LLMuteList::AR_INVENTORY); diff --git a/indra/newview/llheroprobemanager.cpp b/indra/newview/llheroprobemanager.cpp index cf606e8863..343bb01072 100644 --- a/indra/newview/llheroprobemanager.cpp +++ b/indra/newview/llheroprobemanager.cpp @@ -80,6 +80,17 @@ void LLHeroProbeManager::update() return; } + // Part of a hacky workaround to fix #3331. + // For some reason clearing shaders will cause mirrors to actually work. + // There's likely some deeper state issue that needs to be resolved. + // - Geenz 2025-02-25 + if (!mInitialized && LLStartUp::getStartupState() > STATE_PRECACHE) + { + LLViewerShaderMgr::instance()->clearShaderCache(); + LLViewerShaderMgr::instance()->setShaders(); + mInitialized = true; + } + LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; llassert(!gCubeSnapshot); // assert a snapshot is not in progress if (LLAppViewer::instance()->logoutRequestSent()) diff --git a/indra/newview/llheroprobemanager.h b/indra/newview/llheroprobemanager.h index 58a94a3de8..2737ec5ddf 100644 --- a/indra/newview/llheroprobemanager.h +++ b/indra/newview/llheroprobemanager.h @@ -144,6 +144,7 @@ private: std::vector<LLPointer<LLVOVolume>> mHeroVOList; LLPointer<LLVOVolume> mNearestHero; - + // Part of a hacky workaround to fix #3331. + bool mInitialized = false; }; diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 93453f507c..e1fa84b4d8 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -255,12 +255,12 @@ // mSkinInfoQ mMutex rw.repo.mMutex, rw.main.mMutex [5] (was: [0]) // mDecompositionRequests mMutex rw.repo.mMutex, ro.repo.none [5] // mPhysicsShapeRequests mMutex rw.repo.mMutex, ro.repo.none [5] -// mDecompositionQ mMutex rw.repo.mMutex, rw.main.mMutex [5] (was: [0]) +// mDecompositionQ mMutex rw.repo.mLoadedMutex, rw.main.mLoadedMutex [5] (was: [0]) // mHeaderReqQ mMutex ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex // mLODReqQ mMutex ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex -// mUnavailableQ mMutex rw.repo.none [0], ro.main.none [5], rw.main.mMutex -// mLoadedQ mMutex rw.repo.mMutex, ro.main.none [5], rw.main.mMutex -// mPendingLOD mMutex rw.repo.mMutex, rw.any.mMutex +// mUnavailableQ mMutex rw.repo.none [0], ro.main.none [5], rw.main.mLoadedMutex +// mLoadedQ mMutex rw.repo.mLoadedMutex, ro.main.none [5], rw.main.mLoadedMutex +// mPendingLOD mMutex rw.repo.mPendingMutex, rw.any.mPendingMutex // mGetMeshCapability mMutex rw.main.mMutex, ro.repo.mMutex (was: [0]) // mGetMesh2Capability mMutex rw.main.mMutex, ro.repo.mMutex (was: [0]) // mGetMeshVersion mMutex rw.main.mMutex, ro.repo.mMutex @@ -343,20 +343,22 @@ static LLFastTimer::DeclareTimer FTM_MESH_FETCH("Mesh Fetch"); LLMeshRepository gMeshRepo; -const S32 MESH_HEADER_SIZE = 4096; // Important: assumption is that headers fit in this space +constexpr U32 CACHE_PREAMBLE_VERSION = 1; +constexpr S32 CACHE_PREAMBLE_SIZE = sizeof(U32) * 3; //version, header_size, flags +constexpr S32 MESH_HEADER_SIZE = 4096; // Important: assumption is that headers fit in this space -const S32 REQUEST2_HIGH_WATER_MIN = 32; // Limits for GetMesh2 regions -const S32 REQUEST2_HIGH_WATER_MAX = 100; -const S32 REQUEST2_LOW_WATER_MIN = 16; -const S32 REQUEST2_LOW_WATER_MAX = 50; +constexpr S32 REQUEST2_HIGH_WATER_MIN = 32; // Limits for GetMesh2 regions +constexpr S32 REQUEST2_HIGH_WATER_MAX = 100; +constexpr S32 REQUEST2_LOW_WATER_MIN = 16; +constexpr S32 REQUEST2_LOW_WATER_MAX = 50; -const U32 LARGE_MESH_FETCH_THRESHOLD = 1U << 21; // Size at which requests goes to narrow/slow queue -const long SMALL_MESH_XFER_TIMEOUT = 120L; // Seconds to complete xfer, small mesh downloads -const long LARGE_MESH_XFER_TIMEOUT = 600L; // Seconds to complete xfer, large downloads +constexpr U32 LARGE_MESH_FETCH_THRESHOLD = 1U << 21; // Size at which requests goes to narrow/slow queue +constexpr long SMALL_MESH_XFER_TIMEOUT = 120L; // Seconds to complete xfer, small mesh downloads +constexpr long LARGE_MESH_XFER_TIMEOUT = 600L; // Seconds to complete xfer, large downloads -const U32 DOWNLOAD_RETRY_LIMIT = 8; -const F32 DOWNLOAD_RETRY_DELAY = 0.5f; // seconds +constexpr U32 DOWNLOAD_RETRY_LIMIT = 8; +constexpr F32 DOWNLOAD_RETRY_DELAY = 0.5f; // seconds // Would normally like to retry on uploads as some // retryable failures would be recoverable. Unfortunately, @@ -366,7 +368,7 @@ const F32 DOWNLOAD_RETRY_DELAY = 0.5f; // seconds // cap which then produces a 404 on retry destroying some // (occasionally) useful error information. We'll leave // upload retries to the user as in the past. SH-4667. -const long UPLOAD_RETRY_LIMIT = 0L; +constexpr long UPLOAD_RETRY_LIMIT = 0L; // Maximum mesh version to support. Three least significant digits are reserved for the minor version, // with major version changes indicating a format change that is not backwards compatible and should not @@ -374,7 +376,7 @@ const long UPLOAD_RETRY_LIMIT = 0L; // present, the version is 0.001. A viewer that can parse version 0.001 can also parse versions up to 0.999, // but not 1.0 (integer 1000). // See wiki at https://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format -const S32 MAX_MESH_VERSION = 999; +constexpr S32 MAX_MESH_VERSION = 999; U32 LLMeshRepository::sBytesReceived = 0; U32 LLMeshRepository::sMeshRequestCount = 0; @@ -386,12 +388,12 @@ U32 LLMeshRepository::sLODProcessing = 0; U32 LLMeshRepository::sLODPending = 0; U32 LLMeshRepository::sCacheBytesRead = 0; -U32 LLMeshRepository::sCacheBytesWritten = 0; +std::atomic<U32> LLMeshRepository::sCacheBytesWritten = 0; U32 LLMeshRepository::sCacheBytesHeaders = 0; U32 LLMeshRepository::sCacheBytesSkins = 0; U32 LLMeshRepository::sCacheBytesDecomps = 0; U32 LLMeshRepository::sCacheReads = 0; -U32 LLMeshRepository::sCacheWrites = 0; +std::atomic<U32> LLMeshRepository::sCacheWrites = 0; U32 LLMeshRepository::sMaxLockHoldoffs = 0; LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, false); // true -> gather cpu metrics @@ -550,6 +552,7 @@ LLViewerFetchedTexture* LLMeshUploadThread::FindViewerTexture(const LLImportMate std::atomic<S32> LLMeshRepoThread::sActiveHeaderRequests = 0; std::atomic<S32> LLMeshRepoThread::sActiveLODRequests = 0; +std::atomic<S32> LLMeshRepoThread::sActiveSkinRequests = 0; U32 LLMeshRepoThread::sMaxConcurrentRequests = 1; S32 LLMeshRepoThread::sRequestLowWater = REQUEST2_LOW_WATER_MIN; S32 LLMeshRepoThread::sRequestHighWater = REQUEST2_HIGH_WATER_MIN; @@ -584,6 +587,7 @@ public: : LLCore::HttpHandler(), mMeshParams(), mProcessed(false), + mHasDataOwnership(true), mHttpHandle(LLCORE_HTTP_HANDLE_INVALID), mOffset(offset), mRequestedBytes(requested_bytes) @@ -607,6 +611,9 @@ public: LLCore::HttpHandle mHttpHandle; U32 mOffset; U32 mRequestedBytes; + +protected: + bool mHasDataOwnership = true; }; @@ -659,6 +666,9 @@ public: virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); virtual void processFailure(LLCore::HttpStatus status); +private: + void processLod(U8* data, S32 data_size); + public: S32 mLOD; }; @@ -674,13 +684,17 @@ public: LLMeshSkinInfoHandler(const LLUUID& id, U32 offset, U32 requested_bytes) : LLMeshHandlerBase(offset, requested_bytes), mMeshID(id) - {} + { + LLMeshRepoThread::incActiveSkinRequests(); + } virtual ~LLMeshSkinInfoHandler(); protected: LLMeshSkinInfoHandler(const LLMeshSkinInfoHandler &); // Not defined void operator=(const LLMeshSkinInfoHandler &); // Not defined + void processSkin(U8* data, S32 data_size); + public: virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); virtual void processFailure(LLCore::HttpStatus status); @@ -823,6 +837,14 @@ void log_upload_error(LLCore::HttpStatus status, const LLSD& content, gMeshRepo.uploadError(args); } +void write_preamble(LLFileSystem &file, S32 header_bytes, S32 flags) +{ + LLMeshRepository::sCacheBytesWritten += CACHE_PREAMBLE_SIZE; + file.write((U8*)&CACHE_PREAMBLE_VERSION, sizeof(U32)); + file.write((U8*)&header_bytes, sizeof(U32)); + file.write((U8*)&flags, sizeof(U32)); +} + LLMeshRepoThread::LLMeshRepoThread() : LLThread("mesh repo"), mHttpRequest(NULL), @@ -837,6 +859,9 @@ LLMeshRepoThread::LLMeshRepoThread() mMutex = new LLMutex(); mHeaderMutex = new LLMutex(); + mLoadedMutex = new LLMutex(); + mPendingMutex = new LLMutex(); + mSkinMapMutex = new LLMutex(); mSignal = new LLCondition(); mHttpRequest = new LLCore::HttpRequest; mHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); @@ -849,6 +874,11 @@ LLMeshRepoThread::LLMeshRepoThread() mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_VND_LL_MESH); mHttpPolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_MESH2); mHttpLargePolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_LARGE_MESH); + + // Lod processing is expensive due to the number of requests + // and a need to do expensive cacheOptimize(). + mMeshThreadPool.reset(new LL::ThreadPool("MeshLodProcessing", 2)); + mMeshThreadPool->start(); } @@ -875,13 +905,21 @@ LLMeshRepoThread::~LLMeshRepoThread() } delete mHttpRequest; - mHttpRequest = NULL; + mHttpRequest = nullptr; delete mMutex; - mMutex = NULL; + mMutex = nullptr; delete mHeaderMutex; - mHeaderMutex = NULL; + mHeaderMutex = nullptr; + delete mLoadedMutex; + mLoadedMutex = nullptr; + delete mPendingMutex; + mPendingMutex = nullptr; + delete mSkinMapMutex; + mSkinMapMutex = nullptr; delete mSignal; - mSignal = NULL; + mSignal = nullptr; + delete[] mDiskCacheBuffer; + mDiskCacheBuffer = nullptr; } void LLMeshRepoThread::run() @@ -908,6 +946,7 @@ void LLMeshRepoThread::run() // On the other hand, this may actually be an effective and efficient scheme... mSignal->wait(); + LL_PROFILE_ZONE_NAMED("mesh_thread_loop") if (LLApp::isExiting()) { @@ -925,11 +964,55 @@ void LLMeshRepoThread::run() } sRequestWaterLevel = static_cast<S32>(mHttpRequestSet.size()); // Stats data update - // NOTE: order of queue processing intentionally favors LOD requests over header requests + // NOTE: order of queue processing intentionally favors LOD and Skin requests over header requests // Todo: we are processing mLODReqQ, mHeaderReqQ, mSkinRequests, mDecompositionRequests and mPhysicsShapeRequests // in relatively similar manners, remake code to simplify/unify the process, // like processRequests(&requestQ, fetchFunction); which does same thing for each element + if (mHttpRequestSet.size() < sRequestHighWater + && !mSkinRequests.empty()) + { + if (!mSkinRequests.empty()) + { + std::list<UUIDBasedRequest> incomplete; + while (!mSkinRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) + { + + mMutex->lock(); + auto req = mSkinRequests.front(); + mSkinRequests.pop_front(); + mMutex->unlock(); + if (req.isDelayed()) + { + incomplete.emplace_back(req); + } + else if (!fetchMeshSkinInfo(req.mId)) + { + if (req.canRetry()) + { + req.updateTime(); + incomplete.emplace_back(req); + } + else + { + LLMutexLock locker(mLoadedMutex); + mSkinUnavailableQ.push_back(req); + LL_DEBUGS() << "mSkinReqQ failed: " << req.mId << LL_ENDL; + } + } + } + + if (!incomplete.empty()) + { + LLMutexLock locker(mMutex); + for (const auto& req : incomplete) + { + mSkinRequests.push_back(req); + } + } + } + } + if (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) { std::list<LODRequest> incomplete; @@ -950,7 +1033,7 @@ void LLMeshRepoThread::run() // failed to load before, wait a bit incomplete.push_front(req); } - else if (!fetchMeshLOD(req.mMeshParams, req.mLOD, req.canRetry())) + else if (!fetchMeshLOD(req.mMeshParams, req.mLOD)) { if (req.canRetry()) { @@ -961,7 +1044,7 @@ void LLMeshRepoThread::run() else { // too many fails - LLMutexLock lock(mMutex); + LLMutexLock lock(mLoadedMutex); mUnavailableQ.push_back(req); LL_WARNS() << "Failed to load " << req.mMeshParams << " , skip" << LL_ENDL; } @@ -998,7 +1081,7 @@ void LLMeshRepoThread::run() // failed to load before, wait a bit incomplete.push_front(req); } - else if (!fetchMeshHeader(req.mMeshParams, req.canRetry())) + else if (!fetchMeshHeader(req.mMeshParams)) { if (req.canRetry()) { @@ -1028,54 +1111,13 @@ void LLMeshRepoThread::run() // performing long-duration actions. if (mHttpRequestSet.size() < sRequestHighWater - && (!mSkinRequests.empty() - || !mDecompositionRequests.empty() + && (!mDecompositionRequests.empty() || !mPhysicsShapeRequests.empty())) { // Something to do probably, lock and double-check. We don't want // to hold the lock long here. That will stall main thread activities // so we bounce it. - if (!mSkinRequests.empty()) - { - std::list<UUIDBasedRequest> incomplete; - while (!mSkinRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - - mMutex->lock(); - auto req = mSkinRequests.front(); - mSkinRequests.pop_front(); - mMutex->unlock(); - if (req.isDelayed()) - { - incomplete.emplace_back(req); - } - else if (!fetchMeshSkinInfo(req.mId, req.canRetry())) - { - if (req.canRetry()) - { - req.updateTime(); - incomplete.emplace_back(req); - } - else - { - LLMutexLock locker(mMutex); - mSkinUnavailableQ.push_back(req); - LL_DEBUGS() << "mSkinReqQ failed: " << req.mId << LL_ENDL; - } - } - } - - if (!incomplete.empty()) - { - LLMutexLock locker(mMutex); - for (const auto& req : incomplete) - { - mSkinRequests.push_back(req); - } - } - } - // holding lock, try next list // *TODO: For UI/debug-oriented lists, we might drop the fine- // grained locking as there's a lowered expectation of smoothness @@ -1103,7 +1145,7 @@ void LLMeshRepoThread::run() } else { - LL_DEBUGS() << "mDecompositionRequests failed: " << req.mId << LL_ENDL; + LL_DEBUGS(LOG_MESH) << "mDecompositionRequests failed: " << req.mId << LL_ENDL; } } } @@ -1139,7 +1181,7 @@ void LLMeshRepoThread::run() } else { - LL_DEBUGS() << "mPhysicsShapeRequests failed: " << req.mId << LL_ENDL; + LL_DEBUGS(LOG_MESH) << "mPhysicsShapeRequests failed: " << req.mId << LL_ENDL; } } } @@ -1195,40 +1237,91 @@ void LLMeshRepoThread::lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32 } } - void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod) { //could be called from any thread const LLUUID& mesh_id = mesh_params.getSculptID(); - LLMutexLock lock(mMutex); - LLMutexLock header_lock(mHeaderMutex); - mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); - if (iter != mMeshHeader.end()) + loadMeshLOD(mesh_id, mesh_params, lod); +} + +void LLMeshRepoThread::loadMeshLOD(const LLUUID& mesh_id, const LLVolumeParams& mesh_params, S32 lod) +{ + if (hasHeader(mesh_id)) { //if we have the header, request LOD byte range LODRequest req(mesh_params, lod); { + LLMutexLock lock(mMutex); mLODReqQ.push(req); LLMeshRepository::sLODProcessing++; } } else { + LLMutexLock lock(mPendingMutex); HeaderRequest req(mesh_params); pending_lod_map::iterator pending = mPendingLOD.find(mesh_id); if (pending != mPendingLOD.end()) - { //append this lod request to existing header request - pending->second.push_back(lod); - llassert(pending->second.size() <= LLModel::NUM_LODS); + { + //append this lod request to existing header request + if (lod < LLModel::NUM_LODS && lod >= 0) + { + pending->second[lod]++; + } + else + { + LL_WARNS(LOG_MESH) << "Invalid LOD request: " << lod << "for mesh" << mesh_id << LL_ENDL; + } + llassert_msg(lod < LLModel::NUM_LODS, "Requested lod is out of bounds"); } else - { //if no header request is pending, fetch header + { + //if no header request is pending, fetch header + auto& array = mPendingLOD[mesh_id]; + std::fill(array.begin(), array.end(), 0); + array[lod]++; + + LLMutexLock lock(mMutex); mHeaderReqQ.push(req); - mPendingLOD[mesh_id].push_back(lod); } } } +U8* LLMeshRepoThread::getDiskCacheBuffer(S32 size) +{ + if (mDiskCacheBufferSize < size) + { + const S32 MINIMUM_BUFFER_SIZE = 8192; // a minimum to avoid frequent early reallocations + size = llmax(MINIMUM_BUFFER_SIZE, size); + delete[] mDiskCacheBuffer; + try + { + mDiskCacheBuffer = new U8[size]; + } + catch (std::bad_alloc&) + { + LL_WARNS(LOG_MESH) << "Failed to allocate memory for mesh thread's buffer, size: " << size << LL_ENDL; + mDiskCacheBuffer = NULL; + + // Not sure what size is reasonable + // but if 30MB allocation failed, we definitely have issues + const S32 MAX_SIZE = 30 * 1024 * 1024; //30MB + if (size < MAX_SIZE) + { + LLAppViewer::instance()->outOfMemorySoftQuit(); + } // else ignore failures for anomalously large data + } + mDiskCacheBufferSize = size; + } + else + { + // reusing old buffer, reset heading bytes to ensure + // old content won't be parsable if something fails. + memset(mDiskCacheBuffer, 0, 16); + } + return mDiskCacheBuffer; +} + // Mutex: must be holding mMutex when called void LLMeshRepoThread::setGetMeshCap(const std::string & mesh_cap) { @@ -1327,7 +1420,7 @@ LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url, } -bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry) +bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) { LL_PROFILE_ZONE_SCOPED; if (!mHeaderMutex) @@ -1337,7 +1430,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry) mHeaderMutex->lock(); - auto header_it = mMeshHeader.find(mesh_id); + mesh_header_map::const_iterator header_it = mMeshHeader.find(mesh_id); if (header_it == mMeshHeader.end()) { //we have no header info for this mesh, do nothing mHeaderMutex->unlock(); @@ -1346,23 +1439,24 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry) ++LLMeshRepository::sMeshRequestCount; bool ret = true; - U32 header_size = header_it->second.first; + const LLMeshHeader& header = header_it->second; + U32 header_size = header.mHeaderSize; if (header_size > 0) { - const LLMeshHeader& header = header_it->second.second; - S32 version = header.mVersion; S32 offset = header_size + header.mSkinOffset; S32 size = header.mSkinSize; + bool in_cache = header.mSkinInCache; mHeaderMutex->unlock(); if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) { //check cache for mesh skin info + S32 disk_ofset = offset + CACHE_PREAMBLE_SIZE; LLFileSystem file(mesh_id, LLAssetType::AT_MESH); - if (file.getSize() >= offset + size) + if (in_cache && file.getSize() >= disk_ofset + size) { U8* buffer = new(std::nothrow) U8[size]; if (!buffer) @@ -1370,19 +1464,19 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry) LL_WARNS(LOG_MESH) << "Failed to allocate memory for skin info, size: " << size << LL_ENDL; // Not sure what size is reasonable for skin info, - // but if 20MB allocation failed, we definetely have issues + // but if 30MB allocation failed, we definitely have issues const S32 MAX_SIZE = 30 * 1024 * 1024; //30MB if (size < MAX_SIZE) { LLAppViewer::instance()->outOfMemorySoftQuit(); } // else ignore failures for anomalously large data - LLMutexLock locker(mMutex); + LLMutexLock locker(mLoadedMutex); mSkinUnavailableQ.emplace_back(mesh_id); return true; } LLMeshRepository::sCacheBytesRead += size; ++LLMeshRepository::sCacheReads; - file.seek(offset); + file.seek(disk_ofset); file.read(buffer, size); //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) @@ -1393,14 +1487,67 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry) } if (!zero) - { //attempt to parse - if (skinInfoReceived(mesh_id, buffer, size)) + { + //attempt to parse + bool posted = mMeshThreadPool->getQueue().post( + [mesh_id, buffer, size] + () + { + if (!gMeshRepo.mThread->skinInfoReceived(mesh_id, buffer, size)) + { + // either header is faulty or something else overwrote the cache + S32 header_size = 0; + U32 header_flags = 0; + { + LL_DEBUGS(LOG_MESH) << "Mesh header for ID " << mesh_id << " cache mismatch." << LL_ENDL; + + LLMutexLock lock(gMeshRepo.mThread->mHeaderMutex); + + auto header_it = gMeshRepo.mThread->mMeshHeader.find(mesh_id); + if (header_it != gMeshRepo.mThread->mMeshHeader.end()) + { + LLMeshHeader& header = header_it->second; + // for safety just mark everything as missing + header.mSkinInCache = false; + header.mPhysicsConvexInCache = false; + header.mPhysicsMeshInCache = false; + for (S32 i = 0; i < LLModel::NUM_LODS; ++i) + { + header.mLodInCache[i] = false; + } + header_size = header.mHeaderSize; + header_flags = header.getFlags(); + } + } + + if (header_size > 0) + { + LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); + if (file.getMaxSize() >= CACHE_PREAMBLE_SIZE) + { + write_preamble(file, header_size, header_flags); + } + } + + { + LLMutexLock lock(gMeshRepo.mThread->mMutex); + UUIDBasedRequest req(mesh_id); + gMeshRepo.mThread->mSkinRequests.push_back(req); + } + } + delete[] buffer; + }); + if (posted) + { + // lambda owns buffer + return true; + } + else if (skinInfoReceived(mesh_id, buffer, size)) { delete[] buffer; return true; } } - delete[] buffer; } @@ -1420,26 +1567,21 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry) << LL_ENDL; ret = false; } - else if(can_retry) + else { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); } - else - { - LLMutexLock locker(mMutex); - mSkinUnavailableQ.emplace_back(mesh_id); - } } else { - LLMutexLock locker(mMutex); + LLMutexLock locker(mLoadedMutex); mSkinUnavailableQ.emplace_back(mesh_id); } } else { - LLMutexLock locker(mMutex); + LLMutexLock locker(mLoadedMutex); mSkinUnavailableQ.emplace_back(mesh_id); } } @@ -1470,42 +1612,35 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) } ++LLMeshRepository::sMeshRequestCount; - U32 header_size = header_it->second.first; + const auto& header = header_it->second; + U32 header_size = header.mHeaderSize; bool ret = true; if (header_size > 0) { - const auto& header = header_it->second.second; S32 version = header.mVersion; S32 offset = header_size + header.mPhysicsConvexOffset; S32 size = header.mPhysicsConvexSize; + bool in_cache = header.mPhysicsConvexInCache; mHeaderMutex->unlock(); if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) { - //check cache for mesh skin info + // check cache for mesh decomposition + S32 disk_ofset = offset + CACHE_PREAMBLE_SIZE; LLFileSystem file(mesh_id, LLAssetType::AT_MESH); - if (file.getSize() >= offset+size) + if (in_cache && file.getSize() >= disk_ofset + size) { - U8* buffer = new(std::nothrow) U8[size]; + U8* buffer = getDiskCacheBuffer(size); if (!buffer) { - LL_WARNS(LOG_MESH) << "Failed to allocate memory for mesh decomposition, size: " << size << LL_ENDL; - - // Not sure what size is reasonable for decomposition - // but if 20MB allocation failed, we definetely have issues - const S32 MAX_SIZE = 30 * 1024 * 1024; //30MB - if (size < MAX_SIZE) - { - LLAppViewer::instance()->outOfMemorySoftQuit(); - } // else ignore failures for anomalously large decompositiions return true; } LLMeshRepository::sCacheBytesRead += size; ++LLMeshRepository::sCacheReads; - file.seek(offset); + file.seek(disk_ofset); file.read(buffer, size); //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) @@ -1519,12 +1654,9 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) { //attempt to parse if (decompositionReceived(mesh_id, buffer, size)) { - delete[] buffer; return true; } } - - delete[] buffer; } //reading from cache failed for whatever reason, fetch from sim @@ -1578,41 +1710,36 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) } ++LLMeshRepository::sMeshRequestCount; - U32 header_size = header_it->second.first; + const auto& header = header_it->second; + U32 header_size = header.mHeaderSize; bool ret = true; if (header_size > 0) { - const auto& header = header_it->second.second; S32 version = header.mVersion; S32 offset = header_size + header.mPhysicsMeshOffset; S32 size = header.mPhysicsMeshSize; + bool in_cache = header.mPhysicsMeshInCache; mHeaderMutex->unlock(); + // todo: check header.mHasPhysicsMesh if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) { //check cache for mesh physics shape info + S32 disk_ofset = offset + CACHE_PREAMBLE_SIZE; LLFileSystem file(mesh_id, LLAssetType::AT_MESH); - if (file.getSize() >= offset+size) + if (in_cache && file.getSize() >= disk_ofset +size) { LLMeshRepository::sCacheBytesRead += size; ++LLMeshRepository::sCacheReads; - file.seek(offset); - U8* buffer = new(std::nothrow) U8[size]; + + U8* buffer = getDiskCacheBuffer(size); if (!buffer) { - LL_WARNS(LOG_MESH) << "Failed to allocate memory for mesh decomposition, size: " << size << LL_ENDL; - - // Not sure what size is reasonable for physcis - // but if 20MB allocation failed, we definetely have issues - const S32 MAX_SIZE = 30 * 1024 * 1024; //30MB - if (size < MAX_SIZE) - { - LLAppViewer::instance()->outOfMemorySoftQuit(); - } // else ignore failures for anomalously large data return true; } + file.seek(disk_ofset); file.read(buffer, size); //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) @@ -1626,12 +1753,9 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) { //attempt to parse if (physicsShapeReceived(mesh_id, buffer, size) == MESH_OK) { - delete[] buffer; return true; } } - - delete[] buffer; } //reading from cache failed for whatever reason, fetch from sim @@ -1699,8 +1823,22 @@ void LLMeshRepoThread::decActiveHeaderRequests() --LLMeshRepoThread::sActiveHeaderRequests; } +//static +void LLMeshRepoThread::incActiveSkinRequests() +{ + LLMutexLock lock(gMeshRepo.mThread->mMutex); + ++LLMeshRepoThread::sActiveSkinRequests; +} + +//static +void LLMeshRepoThread::decActiveSkinRequests() +{ + LLMutexLock lock(gMeshRepo.mThread->mMutex); + --LLMeshRepoThread::sActiveSkinRequests; +} + //return false if failed to get header -bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool can_retry) +bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params) { LL_PROFILE_ZONE_SCOPED; ++LLMeshRepository::sMeshRequestCount; @@ -1714,17 +1852,34 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool c if (size > 0) { // *NOTE: if the header size is ever more than 4KB, this will break - U8 buffer[MESH_HEADER_SIZE]; - S32 bytes = llmin(size, MESH_HEADER_SIZE); + constexpr S32 DISK_MINIMAL_READ = 4096; + U8 buffer[DISK_MINIMAL_READ * 2]; + S32 bytes = llmin(size, DISK_MINIMAL_READ); LLMeshRepository::sCacheBytesRead += bytes; ++LLMeshRepository::sCacheReads; + file.read(buffer, bytes); - if (headerReceived(mesh_params, buffer, bytes) == MESH_OK) + + U32 version = 0; + memcpy(&version, buffer, sizeof(U32)); + if (version == CACHE_PREAMBLE_VERSION) { - LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mesh_params.getSculptID() << " - was retrieved from the cache." << LL_ENDL; + S32 header_size = 0; + memcpy(&header_size, buffer + sizeof(U32), sizeof(S32)); + if (header_size + CACHE_PREAMBLE_SIZE > DISK_MINIMAL_READ) + { + bytes = llmin(size , DISK_MINIMAL_READ * 2); + file.read(buffer + DISK_MINIMAL_READ, bytes - DISK_MINIMAL_READ); + } + U32 flags = 0; + memcpy(&flags, buffer + 2 * sizeof(U32), sizeof(U32)); + if (headerReceived(mesh_params, buffer + CACHE_PREAMBLE_SIZE, bytes - CACHE_PREAMBLE_SIZE, flags) == MESH_OK) + { + LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mesh_params.getSculptID() << " - was retrieved from the cache." << LL_ENDL; - // Found mesh in cache - return true; + // Found mesh in cache + return true; + } } } } @@ -1753,7 +1908,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool c << LL_ENDL; retval = false; } - else if (can_retry) + else { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); @@ -1764,7 +1919,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool c } //return false if failed to get mesh lod. -bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, bool can_retry) +bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod) { LL_PROFILE_ZONE_SCOPED; if (!mHeaderMutex) @@ -1784,41 +1939,43 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, ++LLMeshRepository::sMeshRequestCount; bool retval = true; - U32 header_size = header_it->second.first; + const auto& header = header_it->second; + U32 header_size = header.mHeaderSize; if (header_size > 0) { - const auto& header = header_it->second.second; S32 version = header.mVersion; S32 offset = header_size + header.mLodOffset[lod]; S32 size = header.mLodSize[lod]; + bool in_cache = header.mLodInCache[lod]; mHeaderMutex->unlock(); if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) { - + S32 disk_ofset = offset + CACHE_PREAMBLE_SIZE; //check cache for mesh asset LLFileSystem file(mesh_id, LLAssetType::AT_MESH); - if (file.getSize() >= offset+size) + if (in_cache && (file.getSize() >= disk_ofset + size)) { - U8* buffer = new(std::nothrow) U8[size]; + U8* buffer = new(std::nothrow) U8[size]; // todo, make buffer thread local and read in thread? if (!buffer) { LL_WARNS(LOG_MESH) << "Can't allocate memory for mesh " << mesh_id << " LOD " << lod << ", size: " << size << LL_ENDL; // Not sure what size is reasonable for a mesh, - // but if 20MB allocation failed, we definetely have issues + // but if 30MB allocation failed, we definitely have issues const S32 MAX_SIZE = 30 * 1024 * 1024; //30MB if (size < MAX_SIZE) { LLAppViewer::instance()->outOfMemorySoftQuit(); } // else ignore failures for anomalously large data - LLMutexLock lock(mMutex); + + LLMutexLock lock(mLoadedMutex); mUnavailableQ.push_back(LODRequest(mesh_params, lod)); return true; } LLMeshRepository::sCacheBytesRead += size; ++LLMeshRepository::sCacheReads; - file.seek(offset); + file.seek(disk_ofset); file.read(buffer, size); //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) @@ -1829,15 +1986,76 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, } if (!zero) - { //attempt to parse - if (lodReceived(mesh_params, lod, buffer, size) == MESH_OK) + { + //attempt to parse + const LLVolumeParams params(mesh_params); + bool posted = mMeshThreadPool->getQueue().post( + [params, mesh_id, lod, buffer, size] + () { + if (gMeshRepo.mThread->lodReceived(params, lod, buffer, size) == MESH_OK) + { + LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mesh_id << " - was retrieved from the cache." << LL_ENDL; + } + else + { + // either header is faulty or something else overwrote the cache + S32 header_size = 0; + U32 header_flags = 0; + { + LL_DEBUGS(LOG_MESH) << "Mesh header for ID " << mesh_id << " cache mismatch." << LL_ENDL; + + LLMutexLock lock(gMeshRepo.mThread->mHeaderMutex); + + auto header_it = gMeshRepo.mThread->mMeshHeader.find(mesh_id); + if (header_it != gMeshRepo.mThread->mMeshHeader.end()) + { + LLMeshHeader& header = header_it->second; + // for safety just mark everything as missing + header.mSkinInCache = false; + header.mPhysicsConvexInCache = false; + header.mPhysicsMeshInCache = false; + for (S32 i = 0; i < LLModel::NUM_LODS; ++i) + { + header.mLodInCache[i] = false; + } + header_size = header.mHeaderSize; + header_flags = header.getFlags(); + } + } + + if (header_size > 0) + { + LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); + if (file.getMaxSize() >= CACHE_PREAMBLE_SIZE) + { + write_preamble(file, header_size, header_flags); + } + } + + { + LLMutexLock lock(gMeshRepo.mThread->mMutex); + LODRequest req(params, lod); + gMeshRepo.mThread->mLODReqQ.push(req); + LLMeshRepository::sLODProcessing++; + } + } delete[] buffer; + }); + if (posted) + { + // now lambda owns buffer + return true; + } + else if (lodReceived(mesh_params, lod, buffer, size) == MESH_OK) + { + delete[] buffer; LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mesh_id << " - was retrieved from the cache." << LL_ENDL; return true; } + } delete[] buffer; @@ -1861,27 +2079,22 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, << LL_ENDL; retval = false; } - else if (can_retry) + else { + // we already made a request, store the handle handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); - // *NOTE: Allowing a re-request, not marking as unavailable. Is that correct? - } - else - { - LLMutexLock lock(mMutex); - mUnavailableQ.push_back(LODRequest(mesh_params, lod)); } } else { - LLMutexLock lock(mMutex); + LLMutexLock lock(mLoadedMutex); mUnavailableQ.push_back(LODRequest(mesh_params, lod)); } } else { - LLMutexLock lock(mMutex); + LLMutexLock lock(mLoadedMutex); mUnavailableQ.push_back(LODRequest(mesh_params, lod)); } } @@ -1893,14 +2106,19 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, return retval; } -EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size) +EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size, U32 flags) { + LL_PROFILE_ZONE_SCOPED; const LLUUID mesh_id = mesh_params.getSculptID(); LLSD header_data; LLMeshHeader header; llssize header_size = 0; + S32 skin_offset = -1; + S32 skin_size = -1; + S32 lod_offset[LLModel::NUM_LODS] = { -1 }; + S32 lod_size[LLModel::NUM_LODS] = { -1 }; if (data_size > 0) { llssize dsize = data_size; @@ -1933,7 +2151,40 @@ EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mes // make sure there is at least one lod, function returns -1 and marks as 404 otherwise else if (LLMeshRepository::getActualMeshLOD(header, 0) >= 0) { - header_size += stream.tellg(); + header.mHeaderSize = (S32)stream.tellg(); + header_size += header.mHeaderSize; + skin_offset = header.mSkinOffset; + skin_size = header.mSkinSize; + + memcpy(lod_offset, header.mLodOffset, sizeof(lod_offset)); + memcpy(lod_size, header.mLodSize, sizeof(lod_size)); + + if (flags != 0) + { + header.setFromFlags(flags); + } + else + { + if (header.mSkinSize > 0 && header_size + header.mSkinOffset + header.mSkinSize < data_size) + { + header.mSkinInCache = true; + } + if (header.mPhysicsConvexSize > 0 && header_size + header.mPhysicsConvexOffset + header.mPhysicsConvexSize < data_size) + { + header.mPhysicsConvexInCache = true; + } + if (header.mPhysicsMeshSize > 0 && header_size + header.mPhysicsMeshOffset + header.mPhysicsMeshSize < data_size) + { + header.mPhysicsMeshInCache = true; + } + for (S32 i = 0; i < LLModel::NUM_LODS; ++i) + { + if (lod_size[i] > 0 && header_size + lod_offset[i] + lod_size[i] < data_size) + { + header.mLodInCache[i] = true; + } + } + } } } else @@ -1947,35 +2198,84 @@ EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mes { LLMutexLock lock(mHeaderMutex); - mMeshHeader[mesh_id] = { (U32)header_size, header }; + mMeshHeader[mesh_id] = header; LLMeshRepository::sCacheBytesHeaders += (U32)header_size; } // immediately request SkinInfo since we'll need it before we can render any LoD if it is present + if (skin_offset >= 0 && skin_size > 0) { - LLMutexLock lock(gMeshRepo.mMeshMutex); + { + LLMutexLock lock(gMeshRepo.mMeshMutex); - if (gMeshRepo.mLoadingSkins.find(mesh_id) == gMeshRepo.mLoadingSkins.end()) + if (gMeshRepo.mLoadingSkins.find(mesh_id) == gMeshRepo.mLoadingSkins.end()) + { + gMeshRepo.mLoadingSkins[mesh_id] = {}; // add an empty vector to indicate to main thread that we are loading skin info + } + } + + S32 offset = (S32)header_size + skin_offset; + bool request_skin = true; + if (offset + skin_size < data_size) + { + request_skin = !skinInfoReceived(mesh_id, data + offset, skin_size); + } + if (request_skin) { - gMeshRepo.mLoadingSkins[mesh_id] = {}; // add an empty vector to indicate to main thread that we are loading skin info + mSkinRequests.push_back(UUIDBasedRequest(mesh_id)); } } - fetchMeshSkinInfo(mesh_id); - - LLMutexLock lock(mMutex); // make sure only one thread access mPendingLOD at the same time. + std::array<S32, LLModel::NUM_LODS> pending_lods; + bool has_pending_lods = false; + { + LLMutexLock lock(mPendingMutex); // make sure only one thread access mPendingLOD at the same time. + pending_lod_map::iterator iter = mPendingLOD.find(mesh_id); + if (iter != mPendingLOD.end()) + { + pending_lods = iter->second; + mPendingLOD.erase(iter); + has_pending_lods = true; + } + } //check for pending requests - pending_lod_map::iterator iter = mPendingLOD.find(mesh_id); - if (iter != mPendingLOD.end()) + if (has_pending_lods) { - for (U32 i = 0; i < iter->second.size(); ++i) + for (S32 i = 0; i < pending_lods.size(); ++i) { - LODRequest req(mesh_params, iter->second[i]); - mLODReqQ.push(req); - LLMeshRepository::sLODProcessing++; + if (pending_lods[i] > 1) + { + // mLoadingMeshes should be protecting from dupplciates, but looks + // like this is possible if object rezzes, unregisterMesh, then + // rezzes again before first request completes. + // mLoadingMeshes might need to change a bit to not rerequest if + // mesh is already pending. + // + // Todo: Improve mLoadingMeshes and once done turn this into an assert. + // Low priority since such situation should be relatively rare + LL_INFOS(LOG_MESH) << "Multiple dupplicate requests for mesd ID: " << mesh_id << " LOD: " << i + << LL_ENDL; + } + if (pending_lods[i] > 0 && lod_size[i] > 0) + { + // try to load from data we just received + bool request_lod = true; + S32 offset = (S32)header_size + lod_offset[i]; + if (offset + lod_size[i] <= data_size) + { + // initial request is 4096 bytes, it's big enough to fit this lod + request_lod = lodReceived(mesh_params, i, data + offset, lod_size[i]) != MESH_OK; + } + if (request_lod) + { + LLMutexLock lock(mMutex); + LODRequest req(mesh_params, i); + mLODReqQ.push(req); + LLMeshRepository::sLODProcessing++; + } + } } - mPendingLOD.erase(iter); } } @@ -1995,8 +2295,16 @@ EMeshProcessingResult LLMeshRepoThread::lodReceived(const LLVolumeParams& mesh_p if (volume->getNumFaces() > 0) { // if we have a valid SkinInfo, cache per-joint bounding boxes for this LOD - LLMeshSkinInfo* skin_info = mSkinMap[mesh_params.getSculptID()]; - if (skin_info && isAgentAvatarValid()) + LLPointer<LLMeshSkinInfo> skin_info = nullptr; + { + LLMutexLock lock(mSkinMapMutex); + skin_map::iterator iter = mSkinMap.find(mesh_params.getSculptID()); + if (iter != mSkinMap.end()) + { + skin_info = iter->second; + } + } + if (skin_info.notNull() && isAgentAvatarValid()) { for (S32 i = 0; i < volume->getNumFaces(); ++i) { @@ -2008,7 +2316,7 @@ EMeshProcessingResult LLMeshRepoThread::lodReceived(const LLVolumeParams& mesh_p LoadedMesh mesh(volume, mesh_params, lod); { - LLMutexLock lock(mMutex); + LLMutexLock lock(mLoadedMutex); mLoadedQ.push_back(mesh); // LLPointer is not thread safe, since we added this pointer into // threaded list, make sure counter gets decreased inside mutex lock @@ -2026,6 +2334,7 @@ EMeshProcessingResult LLMeshRepoThread::lodReceived(const LLVolumeParams& mesh_p bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size) { + LL_PROFILE_ZONE_SCOPED; LLSD skin; if (data_size > 0) @@ -2060,12 +2369,15 @@ bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 dat // copy the skin info for the background thread so we can use it // to calculate per-joint bounding boxes when volumes are loaded - mSkinMap[mesh_id] = new LLMeshSkinInfo(*info); + { + LLMutexLock lock(mSkinMapMutex); + mSkinMap[mesh_id] = new LLMeshSkinInfo(*info); + } { // Move the LLPointer in to the skin info queue to avoid reference // count modification after we leave the lock - LLMutexLock lock(mMutex); + LLMutexLock lock(mLoadedMutex); mSkinInfoQ.emplace_back(std::move(info)); } } @@ -2075,6 +2387,7 @@ bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 dat bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S32 data_size) { + LL_PROFILE_ZONE_SCOPED; LLSD decomp; if (data_size > 0) @@ -2101,7 +2414,7 @@ bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S3 LLModel::Decomposition* d = new LLModel::Decomposition(decomp); d->mMeshID = mesh_id; { - LLMutexLock lock(mMutex); + LLMutexLock lock(mLoadedMutex); mDecompositionQ.push_back(d); } } @@ -2111,6 +2424,7 @@ bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S3 EMeshProcessingResult LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32 data_size) { + LL_PROFILE_ZONE_SCOPED; LLSD physics_shape; LLModel::Decomposition* d = new LLModel::Decomposition(); @@ -2150,7 +2464,7 @@ EMeshProcessingResult LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_ } { - LLMutexLock lock(mMutex); + LLMutexLock lock(mLoadedMutex); mDecompositionQ.push_back(d); } return MESH_OK; @@ -2202,7 +2516,6 @@ LLMeshUploadThread::~LLMeshUploadThread() mHttpRequest = NULL; delete mMutex; mMutex = NULL; - } LLMeshUploadThread::DecompRequest::DecompRequest(LLModel* mdl, LLModel* base_model, LLMeshUploadThread* thread) @@ -2943,15 +3256,17 @@ void LLMeshRepoThread::notifyLoadedMeshes() return; } + LL_PROFILE_ZONE_SCOPED; + if (!mLoadedQ.empty()) { std::deque<LoadedMesh> loaded_queue; - mMutex->lock(); + mLoadedMutex->lock(); if (!mLoadedQ.empty()) { loaded_queue.swap(mLoadedQ); - mMutex->unlock(); + mLoadedMutex->unlock(); update_metrics = true; @@ -2960,40 +3275,47 @@ void LLMeshRepoThread::notifyLoadedMeshes() { if (mesh.mVolume->getNumVolumeFaces() > 0) { - gMeshRepo.notifyMeshLoaded(mesh.mMeshParams, mesh.mVolume); + gMeshRepo.notifyMeshLoaded(mesh.mMeshParams, mesh.mVolume, mesh.mLOD); } else { - gMeshRepo.notifyMeshUnavailable(mesh.mMeshParams, - LLVolumeLODGroup::getVolumeDetailFromScale(mesh.mVolume->getDetail())); + gMeshRepo.notifyMeshUnavailable(mesh.mMeshParams, mesh.mLOD, LLVolumeLODGroup::getVolumeDetailFromScale(mesh.mVolume->getDetail())); } } } + else + { + mLoadedMutex->unlock(); + } } if (!mUnavailableQ.empty()) { std::deque<LODRequest> unavil_queue; - mMutex->lock(); + mLoadedMutex->lock(); if (!mUnavailableQ.empty()) { unavil_queue.swap(mUnavailableQ); - mMutex->unlock(); + mLoadedMutex->unlock(); update_metrics = true; // Process the elements free of the lock for (const auto& req : unavil_queue) { - gMeshRepo.notifyMeshUnavailable(req.mMeshParams, req.mLOD); + gMeshRepo.notifyMeshUnavailable(req.mMeshParams, req.mLOD, req.mLOD); } } + else + { + mLoadedMutex->unlock(); + } } if (!mSkinInfoQ.empty() || !mSkinUnavailableQ.empty() || ! mDecompositionQ.empty()) { - if (mMutex->trylock()) + if (mLoadedMutex->trylock()) { std::deque<LLPointer<LLMeshSkinInfo>> skin_info_q; std::deque<UUIDBasedRequest> skin_info_unavail_q; @@ -3014,7 +3336,7 @@ void LLMeshRepoThread::notifyLoadedMeshes() decomp_q.swap(mDecompositionQ); } - mMutex->unlock(); + mLoadedMutex->unlock(); // Process the elements free of the lock while (! skin_info_q.empty()) @@ -3051,9 +3373,11 @@ S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lo if (iter != mMeshHeader.end()) { - auto& header = iter->second.second; - - return LLMeshRepository::getActualMeshLOD(header, lod); + auto& header = iter->second; + if (header.mHeaderSize > 0) + { + return LLMeshRepository::getActualMeshLOD(header, lod); + } } return lod; @@ -3220,7 +3544,10 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo processData(body, body_offset, data, static_cast<S32>(data_size) - body_offset); - delete [] data; + if (mHasDataOwnership) + { + delete [] data; + } } // Release handler @@ -3253,7 +3580,7 @@ void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status) << LL_ENDL; // Can't get the header so none of the LODs will be available - LLMutexLock lock(gMeshRepo.mThread->mMutex); + LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex); for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i) { gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); @@ -3263,6 +3590,7 @@ void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status) void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, U8 * data, S32 data_size) { + LL_PROFILE_ZONE_SCOPED; LLUUID mesh_id = mMeshParams.getSculptID(); bool success = (!MESH_HEADER_PROCESS_FAILED) && ((data != NULL) == (data_size > 0)); // if we have data but no size or have size but no data, something is wrong; @@ -3282,7 +3610,7 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b << LL_ENDL; // Can't get the header so none of the LODs will be available - LLMutexLock lock(gMeshRepo.mThread->mMutex); + LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex); for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i) { gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); @@ -3298,8 +3626,8 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b LLMeshRepoThread::mesh_header_map::iterator iter = gMeshRepo.mThread->mMeshHeader.find(mesh_id); if (iter != gMeshRepo.mThread->mMeshHeader.end()) { - header_bytes = (S32)iter->second.first; - header = iter->second.second; + header = iter->second; + header_bytes = header.mHeaderSize; } if (header_bytes > 0 @@ -3324,7 +3652,7 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b // LLSD is smart and can work like smart pointer, is not thread safe. gMeshRepo.mThread->mHeaderMutex->unlock(); - S32 bytes = lod_bytes + header_bytes; + S32 bytes = lod_bytes + header_bytes + CACHE_PREAMBLE_SIZE; // It's possible for the remote asset to have more data than is needed for the local cache @@ -3337,6 +3665,11 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b LLMeshRepository::sCacheBytesWritten += data_size; ++LLMeshRepository::sCacheWrites; + // write preamble + U32 flags = header.getFlags(); + write_preamble(file, header_bytes, flags); + + // write header file.write(data, data_size); S32 remaining = bytes - file.tell(); @@ -3359,7 +3692,7 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* b gMeshRepo.mThread->mHeaderMutex->unlock(); // headerReceived() parsed header, but header's data is invalid so none of the LODs will be available - LLMutexLock lock(gMeshRepo.mThread->mMutex); + LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex); for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i) { gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); @@ -3388,9 +3721,64 @@ void LLMeshLODHandler::processFailure(LLCore::HttpStatus status) << " (" << status.toTerseString() << "). Not retrying." << LL_ENDL; - LLMutexLock lock(gMeshRepo.mThread->mMutex); + LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex); gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); } +void LLMeshLODHandler::processLod(U8* data, S32 data_size) +{ + EMeshProcessingResult result = gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size); + if (result == MESH_OK) + { + // good fetch from sim, write to cache + LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); + + S32 offset = mOffset + CACHE_PREAMBLE_SIZE; + S32 size = mRequestedBytes; + + if (file.getSize() >= offset + size) + { + S32 header_bytes = 0; + U32 flags = 0; + { + LLMutexLock lock(gMeshRepo.mThread->mHeaderMutex); + + LLMeshRepoThread::mesh_header_map::iterator header_it = gMeshRepo.mThread->mMeshHeader.find(mMeshParams.getSculptID()); + if (header_it != gMeshRepo.mThread->mMeshHeader.end()) + { + LLMeshHeader& header = header_it->second; + // update header + if (!header.mLodInCache[mLOD]) + { + header.mLodInCache[mLOD] = true; + header_bytes = header.mHeaderSize; + flags = header.getFlags(); + } + // todo: handle else because we shouldn't have requested twice? + } + } + if (flags > 0) + { + write_preamble(file, header_bytes, flags); + } + + file.seek(offset, 0); + file.write(data, size); + LLMeshRepository::sCacheBytesWritten += size; + ++LLMeshRepository::sCacheWrites; + } + } + else + { + LL_WARNS(LOG_MESH) << "Error during mesh LOD processing. ID: " << mMeshParams.getSculptID() + << ", Reason: " << result + << " LOD: " << mLOD + << " Data size: " << data_size + << " Not retrying." + << LL_ENDL; + LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex); + gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); + } +} void LLMeshLODHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, U8 * data, S32 data_size) @@ -3399,33 +3787,26 @@ void LLMeshLODHandler::processData(LLCore::BufferArray * /* body */, S32 /* body if ((!MESH_LOD_PROCESS_FAILED) && ((data != NULL) == (data_size > 0))) // if we have data but no size or have size but no data, something is wrong { - EMeshProcessingResult result = gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size); - if (result == MESH_OK) + LLMeshHandlerBase::ptr_t shrd_handler = shared_from_this(); + bool posted = gMeshRepo.mThread->mMeshThreadPool->getQueue().post( + [shrd_handler, data, data_size] + () { - // good fetch from sim, write to cache - LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); - - S32 offset = mOffset; - S32 size = mRequestedBytes; + LLMeshLODHandler* handler = (LLMeshLODHandler * )shrd_handler.get(); + handler->processLod(data, data_size); + delete[] data; + }); - if (file.getSize() >= offset+size) - { - file.seek(offset); - file.write(data, size); - LLMeshRepository::sCacheBytesWritten += size; - ++LLMeshRepository::sCacheWrites; - } + if (posted) + { + // ownership of data was passed to the lambda + mHasDataOwnership = false; } else { - LL_WARNS(LOG_MESH) << "Error during mesh LOD processing. ID: " << mMeshParams.getSculptID() - << ", Reason: " << result - << " LOD: " << mLOD - << " Data size: " << data_size - << " Not retrying." - << LL_ENDL; - LLMutexLock lock(gMeshRepo.mThread->mMutex); - gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); + // mesh thread dies later than event queue, so this is normal + LL_INFOS_ONCE(LOG_MESH) << "Failed to post work into mMeshThreadPool" << LL_ENDL; + processLod(data, data_size); } } else @@ -3435,7 +3816,7 @@ void LLMeshLODHandler::processData(LLCore::BufferArray * /* body */, S32 /* body << " LOD: " << mLOD << " Data size: " << data_size << LL_ENDL; - LLMutexLock lock(gMeshRepo.mThread->mMutex); + LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex); gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); } } @@ -3446,6 +3827,7 @@ LLMeshSkinInfoHandler::~LLMeshSkinInfoHandler() { LL_WARNS(LOG_MESH) << "deleting unprocessed request handler (may be ok on exit)" << LL_ENDL; } + LLMeshRepoThread::decActiveSkinRequests(); } void LLMeshSkinInfoHandler::processFailure(LLCore::HttpStatus status) @@ -3454,38 +3836,98 @@ void LLMeshSkinInfoHandler::processFailure(LLCore::HttpStatus status) << ", Reason: " << status.toString() << " (" << status.toTerseString() << "). Not retrying." << LL_ENDL; - LLMutexLock lock(gMeshRepo.mThread->mMutex); + LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex); gMeshRepo.mThread->mSkinUnavailableQ.emplace_back(mMeshID); } -void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, - U8 * data, S32 data_size) +void LLMeshSkinInfoHandler::processSkin(U8* data, S32 data_size) { - LL_PROFILE_ZONE_SCOPED; - if ((!MESH_SKIN_INFO_PROCESS_FAILED) - && ((data != NULL) == (data_size > 0)) // if we have data but no size or have size but no data, something is wrong - && gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size)) + if (gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size)) { // good fetch from sim, write to cache LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); - S32 offset = mOffset; + S32 offset = mOffset + CACHE_PREAMBLE_SIZE; S32 size = mRequestedBytes; - if (file.getSize() >= offset+size) + if (file.getSize() >= offset + size) { LLMeshRepository::sCacheBytesWritten += size; ++LLMeshRepository::sCacheWrites; - file.seek(offset); + + S32 header_bytes = 0; + U32 flags = 0; + { + LLMutexLock lock(gMeshRepo.mThread->mHeaderMutex); + + LLMeshRepoThread::mesh_header_map::iterator header_it = gMeshRepo.mThread->mMeshHeader.find(mMeshID); + if (header_it != gMeshRepo.mThread->mMeshHeader.end()) + { + LLMeshHeader& header = header_it->second; + // update header + if (!header.mSkinInCache) + { + header.mSkinInCache = true; + header_bytes = header.mHeaderSize; + flags = header.getFlags(); + } + // todo: handle else because we shouldn't have requested twice? + } + } + if (flags > 0) + { + write_preamble(file, header_bytes, flags); + } + + file.seek(offset, 0); file.write(data, size); } } else { LL_WARNS(LOG_MESH) << "Error during mesh skin info processing. ID: " << mMeshID + << ", Unknown reason. Not retrying." + << LL_ENDL; + LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex); + gMeshRepo.mThread->mSkinUnavailableQ.emplace_back(mMeshID); + } +} + +void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, + U8 * data, S32 data_size) +{ + LL_PROFILE_ZONE_SCOPED; + if ((!MESH_SKIN_INFO_PROCESS_FAILED) + && ((data != NULL) == (data_size > 0))) // if we have data but no size or have size but no data, something is wrong + { + LLMeshHandlerBase::ptr_t shrd_handler = shared_from_this(); + bool posted = gMeshRepo.mThread->mMeshThreadPool->getQueue().post( + [shrd_handler, data, data_size] + () + { + LLMeshSkinInfoHandler* handler = (LLMeshSkinInfoHandler*)shrd_handler.get(); + handler->processSkin(data, data_size); + delete[] data; + }); + + if (posted) + { + // ownership of data was passed to the lambda + mHasDataOwnership = false; + } + else + { + // mesh thread dies later than event queue, so this is normal + LL_INFOS_ONCE(LOG_MESH) << "Failed to post work into mMeshThreadPool" << LL_ENDL; + processSkin(data, data_size); + } + } + else + { + LL_WARNS(LOG_MESH) << "Error during mesh skin info processing. ID: " << mMeshID << ", Unknown reason. Not retrying." << LL_ENDL; - LLMutexLock lock(gMeshRepo.mThread->mMutex); + LLMutexLock lock(gMeshRepo.mThread->mLoadedMutex); gMeshRepo.mThread->mSkinUnavailableQ.emplace_back(mMeshID); } } @@ -3519,14 +3961,39 @@ void LLMeshDecompositionHandler::processData(LLCore::BufferArray * /* body */, S // good fetch from sim, write to cache LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); - S32 offset = mOffset; + S32 offset = mOffset + CACHE_PREAMBLE_SIZE; S32 size = mRequestedBytes; if (file.getSize() >= offset+size) { LLMeshRepository::sCacheBytesWritten += size; ++LLMeshRepository::sCacheWrites; - file.seek(offset); + + S32 header_bytes = 0; + U32 flags = 0; + { + LLMutexLock lock(gMeshRepo.mThread->mHeaderMutex); + + LLMeshRepoThread::mesh_header_map::iterator header_it = gMeshRepo.mThread->mMeshHeader.find(mMeshID); + if (header_it != gMeshRepo.mThread->mMeshHeader.end()) + { + LLMeshHeader& header = header_it->second; + // update header + if (!header.mPhysicsConvexInCache) + { + header.mPhysicsConvexInCache = true; + header_bytes = header.mHeaderSize; + flags = header.getFlags(); + } + // todo: handle else because we shouldn't have requested twice? + } + } + if (flags > 0) + { + write_preamble(file, header_bytes, flags); + } + + file.seek(offset, 0); file.write(data, size); } } @@ -3567,14 +4034,39 @@ void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * /* body */, S3 // good fetch from sim, write to cache for caching LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); - S32 offset = mOffset; + S32 offset = mOffset + CACHE_PREAMBLE_SIZE; S32 size = mRequestedBytes; if (file.getSize() >= offset+size) { LLMeshRepository::sCacheBytesWritten += size; ++LLMeshRepository::sCacheWrites; - file.seek(offset); + + S32 header_bytes = 0; + U32 flags = 0; + { + LLMutexLock lock(gMeshRepo.mThread->mHeaderMutex); + + LLMeshRepoThread::mesh_header_map::iterator header_it = gMeshRepo.mThread->mMeshHeader.find(mMeshID); + if (header_it != gMeshRepo.mThread->mMeshHeader.end()) + { + LLMeshHeader& header = header_it->second; + // update header + if (!header.mPhysicsMeshInCache) + { + header.mPhysicsMeshInCache = true; + header_bytes = header.mHeaderSize; + flags = header.getFlags(); + } + // todo: handle else because we shouldn't have requested twice? + } + } + if (flags > 0) + { + write_preamble(file, header_bytes, flags); + } + + file.seek(offset, 0); file.write(data, size); } } @@ -3636,6 +4128,7 @@ void LLMeshRepository::shutdown() } mThread->mSignal->broadcast(); + mThread->mMeshThreadPool->close(); while (!mThread->isStopped()) { @@ -3710,24 +4203,24 @@ void LLMeshRepository::unregisterMesh(LLVOVolume* vobj) } } -S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail, S32 last_lod) +S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 new_lod, S32 last_lod) { LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH); // Manage time-to-load metrics for mesh download operations. metricsProgress(1); - if (detail < 0 || detail >= LLVolumeLODGroup::NUM_LODS) + if (new_lod < 0 || new_lod >= LLVolumeLODGroup::NUM_LODS) { - return detail; + return new_lod; } { LLMutexLock lock(mMeshMutex); //add volume to list of loading meshes const auto& mesh_id = mesh_params.getSculptID(); - mesh_load_map::iterator iter = mLoadingMeshes[detail].find(mesh_id); - if (iter != mLoadingMeshes[detail].end()) + mesh_load_map::iterator iter = mLoadingMeshes[new_lod].find(mesh_id); + if (iter != mLoadingMeshes[new_lod].end()) { //request pending for this mesh, append volume id to list auto it = std::find(iter->second.begin(), iter->second.end(), vobj); if (it == iter->second.end()) { @@ -3737,8 +4230,8 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para else { //first request for this mesh - mLoadingMeshes[detail][mesh_id].push_back(vobj); - mPendingRequests.push_back(LLMeshRepoThread::LODRequest(mesh_params, detail)); + mLoadingMeshes[new_lod][mesh_id].push_back(vobj); + mPendingRequests.emplace_back(new PendingRequestLOD(mesh_params, new_lod)); LLMeshRepository::sLODPending++; } } @@ -3767,7 +4260,7 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para } //next, see what the next lowest LOD available might be - for (S32 i = detail-1; i >= 0; --i) + for (S32 i = new_lod -1; i >= 0; --i) { LLVolume* lod = group->refLOD(i); if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0) @@ -3780,7 +4273,7 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para } //no lower LOD is a available, is a higher lod available? - for (S32 i = detail+1; i < LLVolumeLODGroup::NUM_LODS; ++i) + for (S32 i = new_lod+1; i < LLVolumeLODGroup::NUM_LODS; ++i) { LLVolume* lod = group->refLOD(i); if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0) @@ -3794,7 +4287,51 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para } } - return detail; + return new_lod; +} + +F32 calculate_score(LLVOVolume* object) +{ + if (!object) + { + return -1.f; + } + LLDrawable* drawable = object->mDrawable; + if (!drawable) + { + return -1; + } + if (drawable->isState(LLDrawable::RIGGED) || object->isAttachment()) + { + LLVOAvatar* avatar = object->getAvatar(); + LLDrawable* av_drawable = avatar ? avatar->mDrawable : nullptr; + if (avatar && av_drawable) + { + // See LLVOVolume::calcLOD() + F32 radius; + if (avatar->isControlAvatar()) + { + const LLVector3* box = avatar->getLastAnimExtents(); + LLVector3 diag = box[1] - box[0]; + radius = diag.magVec() * 0.5f; + } + else + { + // Volume in a rigged mesh attached to a regular avatar. + const LLVector3* box = avatar->getLastAnimExtents(); + LLVector3 diag = box[1] - box[0]; + radius = diag.magVec(); + + if (!avatar->isSelf() && !avatar->hasFirstFullAttachmentData()) + { + // slightly deprioritize avatars that are still receiving data + radius *= 0.9f; + } + } + return radius / llmax(av_drawable->mDistanceWRTCamera, 1.f); + } + } + return drawable->getRadius() / llmax(drawable->mDistanceWRTCamera, 1.f); } void LLMeshRepository::notifyLoadedMeshes() @@ -3918,6 +4455,7 @@ void LLMeshRepository::notifyLoadedMeshes() // erase from background thread mThread->mWorkQueue.post([=, this]() { + LLMutexLock(mThread->mSkinMapMutex); mThread->mSkinMap.erase(id); }); } @@ -3974,8 +4512,12 @@ void LLMeshRepository::notifyLoadedMeshes() mUploadErrorQ.pop(); } - S32 active_count = LLMeshRepoThread::sActiveHeaderRequests + LLMeshRepoThread::sActiveLODRequests; - if (active_count < LLMeshRepoThread::sRequestLowWater) + // mPendingRequests go into queues, queues go into active http requests. + // Checking sRequestHighWater to keep queues at least somewhat populated + // for faster transition into http + S32 active_count = LLMeshRepoThread::sActiveHeaderRequests + LLMeshRepoThread::sActiveLODRequests + LLMeshRepoThread::sActiveSkinRequests; + active_count += (S32)(mThread->mLODReqQ.size() + mThread->mHeaderReqQ.size() + mThread->mSkinInfoQ.size()); + if (active_count < LLMeshRepoThread::sRequestHighWater) { S32 push_count = LLMeshRepoThread::sRequestHighWater - active_count; @@ -3996,50 +4538,69 @@ void LLMeshRepository::notifyLoadedMeshes() F32 max_score = 0.f; for (auto obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter) { - LLVOVolume* object = *obj_iter; - if (object) + F32 cur_score = calculate_score(*obj_iter); + if (cur_score >= 0.f) { - LLDrawable* drawable = object->mDrawable; - if (drawable) - { - F32 cur_score = drawable->getRadius()/llmax(drawable->mDistanceWRTCamera, 1.f); - max_score = llmax(max_score, cur_score); - } + max_score = llmax(max_score, cur_score); } } score_map[iter->first] = max_score; } } + for (mesh_load_map::iterator iter = mLoadingSkins.begin(); iter != mLoadingSkins.end(); ++iter) + { + F32 max_score = 0.f; + for (auto obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter) + { + F32 cur_score = calculate_score(*obj_iter); + if (cur_score >= 0.f) + { + max_score = llmax(max_score, cur_score); + } + } + + score_map[iter->first] = max_score; + } //set "score" for pending requests - for (std::vector<LLMeshRepoThread::LODRequest>::iterator iter = mPendingRequests.begin(); iter != mPendingRequests.end(); ++iter) + for (std::unique_ptr<PendingRequestBase>& req_p : mPendingRequests) { - iter->mScore = score_map[iter->mMeshParams.getSculptID()]; + req_p->setScore(score_map[req_p->getId()]); } //sort by "score" std::partial_sort(mPendingRequests.begin(), mPendingRequests.begin() + push_count, - mPendingRequests.end(), LLMeshRepoThread::CompareScoreGreater()); + mPendingRequests.end(), PendingRequestBase::CompareScoreGreater()); } - while (!mPendingRequests.empty() && push_count > 0) { - LLMeshRepoThread::LODRequest& request = mPendingRequests.front(); - mThread->loadMeshLOD(request.mMeshParams, request.mLOD); + std::unique_ptr<PendingRequestBase>& req_p = mPendingRequests.front(); + switch (req_p->getRequestType()) + { + case MESH_REQUEST_LOD: + { + PendingRequestLOD* lod = (PendingRequestLOD*)req_p.get(); + mThread->loadMeshLOD(lod->mMeshParams, lod->mLOD); + LLMeshRepository::sLODPending--; + break; + } + case MESH_REQUEST_SKIN: + { + PendingRequestUUID* skin = (PendingRequestUUID*)req_p.get(); + mThread->loadMeshSkinInfo(skin->getId()); + break; + } + + default: + LL_ERRS() << "Unknown request type in LLMeshRepository::notifyLoadedMeshes" << LL_ENDL; + break; + } mPendingRequests.erase(mPendingRequests.begin()); - LLMeshRepository::sLODPending--; push_count--; } } - //send skin info requests - while (!mPendingSkinRequests.empty()) - { - mThread->loadMeshSkinInfo(mPendingSkinRequests.front()); - mPendingSkinRequests.pop(); - } - //send decomposition requests while (!mPendingDecompositionRequests.empty()) { @@ -4116,15 +4677,14 @@ void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decom } } -void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume) +void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume, S32 lod) { //called from main thread - S32 detail = LLVolumeLODGroup::getVolumeDetailFromScale(volume->getDetail()); //get list of objects waiting to be notified this mesh is loaded const auto& mesh_id = mesh_params.getSculptID(); - mesh_load_map::iterator obj_iter = mLoadingMeshes[detail].find(mesh_id); + mesh_load_map::iterator obj_iter = mLoadingMeshes[lod].find(mesh_id); - if (volume && obj_iter != mLoadingMeshes[detail].end()) + if (volume && obj_iter != mLoadingMeshes[lod].end()) { //make sure target volume is still valid if (volume->getNumVolumeFaces() <= 0) @@ -4134,6 +4694,7 @@ void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVol } { //update system volume + S32 detail = LLVolumeLODGroup::getVolumeDetailFromScale(volume->getDetail()); LLVolume* sys_volume = LLPrimitive::getVolumeManager()->refVolume(mesh_params, detail); if (sys_volume) { @@ -4157,22 +4718,22 @@ void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVol } } - mLoadingMeshes[detail].erase(obj_iter); + mLoadingMeshes[lod].erase(obj_iter); LLViewerStatsRecorder::instance().meshLoaded(); } } -void LLMeshRepository::notifyMeshUnavailable(const LLVolumeParams& mesh_params, S32 lod) +void LLMeshRepository::notifyMeshUnavailable(const LLVolumeParams& mesh_params, S32 request_lod, S32 volume_lod) { //called from main thread //get list of objects waiting to be notified this mesh is loaded const auto& mesh_id = mesh_params.getSculptID(); - mesh_load_map::iterator obj_iter = mLoadingMeshes[lod].find(mesh_id); - if (obj_iter != mLoadingMeshes[lod].end()) + mesh_load_map::iterator obj_iter = mLoadingMeshes[request_lod].find(mesh_id); + if (obj_iter != mLoadingMeshes[request_lod].end()) { - F32 detail = LLVolumeLODGroup::getVolumeScaleFromDetail(lod); + F32 detail = LLVolumeLODGroup::getVolumeScaleFromDetail(volume_lod); - LLVolume* sys_volume = LLPrimitive::getVolumeManager()->refVolume(mesh_params, lod); + LLVolume* sys_volume = LLPrimitive::getVolumeManager()->refVolume(mesh_params, volume_lod); if (sys_volume) { sys_volume->setMeshAssetUnavaliable(true); @@ -4189,12 +4750,12 @@ void LLMeshRepository::notifyMeshUnavailable(const LLVolumeParams& mesh_params, obj_volume->getDetail() == detail && obj_volume->getParams() == mesh_params) { //should force volume to find most appropriate LOD - vobj->setVolume(obj_volume->getParams(), lod); + vobj->setVolume(obj_volume->getParams(), volume_lod); } } } - mLoadingMeshes[lod].erase(obj_iter); + mLoadingMeshes[request_lod].erase(obj_iter); } } @@ -4231,7 +4792,7 @@ const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, LLVOV { //first request for this mesh mLoadingSkins[mesh_id].push_back(requesting_obj); - mPendingSkinRequests.push(mesh_id); + mPendingRequests.emplace_back(new PendingRequestUUID(mesh_id, MESH_REQUEST_SKIN)); } } } @@ -4358,7 +4919,7 @@ bool LLMeshRepository::hasSkinInfo(const LLUUID& mesh_id) return false; } -bool LLMeshRepository::hasHeader(const LLUUID& mesh_id) +bool LLMeshRepository::hasHeader(const LLUUID& mesh_id) const { if (mesh_id.isNull()) { @@ -4368,13 +4929,13 @@ bool LLMeshRepository::hasHeader(const LLUUID& mesh_id) return mThread->hasHeader(mesh_id); } -bool LLMeshRepoThread::hasPhysicsShapeInHeader(const LLUUID& mesh_id) +bool LLMeshRepoThread::hasPhysicsShapeInHeader(const LLUUID& mesh_id) const { LLMutexLock lock(mHeaderMutex); - mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); - if (iter != mMeshHeader.end() && iter->second.first > 0) + mesh_header_map::const_iterator iter = mMeshHeader.find(mesh_id); + if (iter != mMeshHeader.end() && iter->second.mHeaderSize > 0) { - LLMeshHeader &mesh = iter->second.second; + const LLMeshHeader &mesh = iter->second; if (mesh.mPhysicsMeshSize > 0) { return true; @@ -4384,13 +4945,13 @@ bool LLMeshRepoThread::hasPhysicsShapeInHeader(const LLUUID& mesh_id) return false; } -bool LLMeshRepoThread::hasSkinInfoInHeader(const LLUUID& mesh_id) +bool LLMeshRepoThread::hasSkinInfoInHeader(const LLUUID& mesh_id) const { LLMutexLock lock(mHeaderMutex); - mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); - if (iter != mMeshHeader.end() && iter->second.first > 0) + mesh_header_map::const_iterator iter = mMeshHeader.find(mesh_id); + if (iter != mMeshHeader.end() && iter->second.mHeaderSize > 0) { - LLMeshHeader& mesh = iter->second.second; + const LLMeshHeader& mesh = iter->second; if (mesh.mSkinOffset >= 0 && mesh.mSkinSize > 0) { @@ -4401,10 +4962,10 @@ bool LLMeshRepoThread::hasSkinInfoInHeader(const LLUUID& mesh_id) return false; } -bool LLMeshRepoThread::hasHeader(const LLUUID& mesh_id) +bool LLMeshRepoThread::hasHeader(const LLUUID& mesh_id) const { LLMutexLock lock(mHeaderMutex); - mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); + mesh_header_map::const_iterator iter = mMeshHeader.find(mesh_id); return iter != mMeshHeader.end(); } @@ -4419,16 +4980,16 @@ void LLMeshRepository::uploadModel(std::vector<LLModelInstance>& data, LLVector3 mUploadWaitList.push_back(thread); } -S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod) +S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod) const { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; if (mThread && mesh_id.notNull() && LLPrimitive::NO_LOD != lod) { LLMutexLock lock(mThread->mHeaderMutex); - LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id); - if (iter != mThread->mMeshHeader.end() && iter->second.first > 0) + LLMeshRepoThread::mesh_header_map::const_iterator iter = mThread->mMeshHeader.find(mesh_id); + if (iter != mThread->mMeshHeader.end() && iter->second.mHeaderSize > 0) { - const LLMeshHeader& header = iter->second.second; + const LLMeshHeader& header = iter->second; if (header.m404) { @@ -4532,9 +5093,9 @@ F32 LLMeshRepository::getStreamingCostLegacy(LLUUID mesh_id, F32 radius, S32* by { LLMutexLock lock(mThread->mHeaderMutex); LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id); - if (iter != mThread->mMeshHeader.end() && iter->second.first > 0) + if (iter != mThread->mMeshHeader.end() && iter->second.mHeaderSize > 0) { - result = getStreamingCostLegacy(iter->second.second, radius, bytes, bytes_visible, lod, unscaled_value); + result = getStreamingCostLegacy(iter->second, radius, bytes, bytes_visible, lod, unscaled_value); } } if (result > 0.f) @@ -4726,7 +5287,7 @@ bool LLMeshCostData::init(const LLMeshHeader& header) } -S32 LLMeshCostData::getSizeByLOD(S32 lod) +S32 LLMeshCostData::getSizeByLOD(S32 lod) const { if (llclamp(lod,0,3) != lod) { @@ -4735,12 +5296,12 @@ S32 LLMeshCostData::getSizeByLOD(S32 lod) return mSizeByLOD[lod]; } -S32 LLMeshCostData::getSizeTotal() +S32 LLMeshCostData::getSizeTotal() const { return mSizeByLOD[0] + mSizeByLOD[1] + mSizeByLOD[2] + mSizeByLOD[3]; } -F32 LLMeshCostData::getEstTrisByLOD(S32 lod) +F32 LLMeshCostData::getEstTrisByLOD(S32 lod) const { if (llclamp(lod,0,3) != lod) { @@ -4749,12 +5310,12 @@ F32 LLMeshCostData::getEstTrisByLOD(S32 lod) return mEstTrisByLOD[lod]; } -F32 LLMeshCostData::getEstTrisMax() +F32 LLMeshCostData::getEstTrisMax() const { return llmax(mEstTrisByLOD[0], mEstTrisByLOD[1], mEstTrisByLOD[2], mEstTrisByLOD[3]); } -F32 LLMeshCostData::getRadiusWeightedTris(F32 radius) +F32 LLMeshCostData::getRadiusWeightedTris(F32 radius) const { F32 max_distance = 512.f; @@ -4798,7 +5359,7 @@ F32 LLMeshCostData::getRadiusWeightedTris(F32 radius) return weighted_avg; } -F32 LLMeshCostData::getEstTrisForStreamingCost() +F32 LLMeshCostData::getEstTrisForStreamingCost() const { LL_DEBUGS("StreamingCost") << "tris_by_lod: " << mEstTrisByLOD[0] << ", " @@ -4808,7 +5369,7 @@ F32 LLMeshCostData::getEstTrisForStreamingCost() F32 charged_tris = mEstTrisByLOD[3]; F32 allowed_tris = mEstTrisByLOD[3]; - const F32 ENFORCE_FLOOR = 64.0f; + constexpr F32 ENFORCE_FLOOR = 64.0f; for (S32 i=2; i>=0; i--) { // How many tris can we have in this LOD without affecting land impact? @@ -4825,13 +5386,13 @@ F32 LLMeshCostData::getEstTrisForStreamingCost() return charged_tris; } -F32 LLMeshCostData::getRadiusBasedStreamingCost(F32 radius) +F32 LLMeshCostData::getRadiusBasedStreamingCost(F32 radius) const { static LLCachedControl<U32> mesh_triangle_budget(gSavedSettings, "MeshTriangleBudget"); return getRadiusWeightedTris(radius)/mesh_triangle_budget*15000.f; } -F32 LLMeshCostData::getTriangleBasedStreamingCost() +F32 LLMeshCostData::getTriangleBasedStreamingCost() const { F32 result = ANIMATED_OBJECT_COST_PER_KTRI * 0.001f * getEstTrisForStreamingCost(); return result; @@ -4846,9 +5407,9 @@ bool LLMeshRepository::getCostData(LLUUID mesh_id, LLMeshCostData& data) { LLMutexLock lock(mThread->mHeaderMutex); LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id); - if (iter != mThread->mMeshHeader.end() && iter->second.first > 0) + if (iter != mThread->mMeshHeader.end() && iter->second.mHeaderSize > 0) { - LLMeshHeader& header = iter->second.second; + LLMeshHeader& header = iter->second; bool header_invalid = (header.m404 || header.mLodSize[0] <= 0 diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 3d25a4dd78..0d9da32e27 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -63,6 +63,16 @@ typedef enum e_mesh_processing_result_enum MESH_UNKNOWN } EMeshProcessingResult; +typedef enum e_mesh_request_type_enum +{ + MESH_REQUEST_HEADER, + MESH_REQUEST_LOD, + MESH_REQUEST_SKIN, + MESH_REQUEST_DECOMPOSITION, + MESH_REQUEST_PHYSICS, + MESH_REQUEST_UKNOWN +} EMeshRequestType; + class LLMeshUploadData { public: @@ -183,7 +193,8 @@ public: class RequestStats { public: - RequestStats() : mRetries(0) {}; + + RequestStats() :mRetries(0) {}; void updateTime(); bool canRetry() const; @@ -195,6 +206,67 @@ private: LLFrameTimer mTimer; }; + +class PendingRequestBase +{ +public: + struct CompareScoreGreater + { + bool operator()(const std::unique_ptr<PendingRequestBase>& lhs, const std::unique_ptr<PendingRequestBase>& rhs) + { + return lhs->mScore > rhs->mScore; // greatest = first + } + }; + + PendingRequestBase() : mScore(0.f) {}; + virtual ~PendingRequestBase() {} + + bool operator<(const PendingRequestBase& rhs) const + { + return mId < rhs.mId; + } + + void setScore(F32 score) { mScore = score; } + F32 getScore() const { return mScore; } + LLUUID getId() const { return mId; } + virtual EMeshRequestType getRequestType() const = 0; + +protected: + F32 mScore; + LLUUID mId; +}; + +class PendingRequestLOD : public PendingRequestBase +{ +public: + LLVolumeParams mMeshParams; + S32 mLOD; + + PendingRequestLOD(const LLVolumeParams& mesh_params, S32 lod) + : PendingRequestBase(), mMeshParams(mesh_params), mLOD(lod) + { + mId = mMeshParams.getSculptID(); + } + + EMeshRequestType getRequestType() const override { return MESH_REQUEST_LOD; } +}; + +class PendingRequestUUID : public PendingRequestBase +{ +public: + + PendingRequestUUID(const LLUUID& id, EMeshRequestType type) + : PendingRequestBase(), mRequestType(type) + { + mId = id; + } + + EMeshRequestType getRequestType() const override { return mRequestType; } + +private: + EMeshRequestType mRequestType; +}; + class LLMeshHeader { public: @@ -235,19 +307,67 @@ public: m404 = header.has("404"); } +private: + + enum EDiskCacheFlags { + FLAG_SKIN = 1 << LLModel::NUM_LODS, + FLAG_PHYSCONVEX = 1 << (LLModel::NUM_LODS + 1), + FLAG_PHYSMESH = 1 << (LLModel::NUM_LODS + 2), + }; +public: + U32 getFlags() + { + U32 flags = 0; + for (U32 i = 0; i < LLModel::NUM_LODS; i++) + { + if (mLodInCache[i]) + { + flags |= 1 << i; + } + } + if (mSkinInCache) + { + flags |= FLAG_SKIN; + } + if (mPhysicsConvexInCache) + { + flags |= FLAG_PHYSCONVEX; + } + if (mPhysicsMeshInCache) + { + flags |= FLAG_PHYSMESH; + } + return flags; + } + + void setFromFlags(U32 flags) + { + for (U32 i = 0; i < LLModel::NUM_LODS; i++) + { + mLodInCache[i] = (flags & (1 << i)) != 0; + } + mSkinInCache = (flags & FLAG_SKIN) != 0; + mPhysicsConvexInCache = (flags & FLAG_PHYSCONVEX) != 0; + mPhysicsMeshInCache = (flags & FLAG_PHYSMESH) != 0; + } S32 mVersion = -1; S32 mSkinOffset = -1; S32 mSkinSize = -1; + bool mSkinInCache = false; S32 mPhysicsConvexOffset = -1; S32 mPhysicsConvexSize = -1; + bool mPhysicsConvexInCache = false; S32 mPhysicsMeshOffset = -1; S32 mPhysicsMeshSize = -1; + bool mPhysicsMeshInCache = false; - S32 mLodOffset[4] = { -1 }; - S32 mLodSize[4] = { -1 }; + S32 mLodOffset[LLModel::NUM_LODS] = { -1 }; + S32 mLodSize[LLModel::NUM_LODS] = { -1 }; + bool mLodInCache[LLModel::NUM_LODS] = { false }; + S32 mHeaderSize = -1; bool m404 = false; }; @@ -258,6 +378,7 @@ public: static std::atomic<S32> sActiveHeaderRequests; static std::atomic<S32> sActiveLODRequests; + static std::atomic<S32> sActiveSkinRequests; static U32 sMaxConcurrentRequests; static S32 sRequestLowWater; static S32 sRequestHighWater; @@ -265,10 +386,13 @@ public: LLMutex* mMutex; LLMutex* mHeaderMutex; + LLMutex* mLoadedMutex; + LLMutex* mPendingMutex; + LLMutex* mSkinMapMutex; LLCondition* mSignal; //map of known mesh headers - typedef boost::unordered_map<LLUUID, std::pair<U32, LLMeshHeader>> mesh_header_map; // pair is header_size and data + typedef boost::unordered_map<LLUUID, LLMeshHeader> mesh_header_map; // pair is header_size and data mesh_header_map mMeshHeader; class HeaderRequest : public RequestStats @@ -292,19 +416,10 @@ public: public: LLVolumeParams mMeshParams; S32 mLOD; - F32 mScore; LODRequest(const LLVolumeParams& mesh_params, S32 lod) - : RequestStats(), mMeshParams(mesh_params), mLOD(lod), mScore(0.f) - { - } - }; - - struct CompareScoreGreater - { - bool operator()(const LODRequest& lhs, const LODRequest& rhs) + : RequestStats(), mMeshParams(mesh_params), mLOD(lod) { - return lhs.mScore > rhs.mScore; // greatest = first } }; @@ -369,7 +484,7 @@ public: std::deque<LoadedMesh> mLoadedQ; //map of pending header requests and currently desired LODs - typedef std::unordered_map<LLUUID, std::vector<S32> > pending_lod_map; + typedef std::unordered_map<LLUUID, std::array<S32, LLModel::NUM_LODS> > pending_lod_map; pending_lod_map mPendingLOD; // map of mesh ID to skin info (mirrors LLMeshRepository::mSkinMap) @@ -379,6 +494,8 @@ public: // workqueue for processing generic requests LL::WorkQueue mWorkQueue; + // lods have their own thread due to costly cacheOptimize() calls + std::unique_ptr<LL::ThreadPool> mMeshThreadPool; // llcorehttp library interface objects. LLCore::HttpStatus mHttpStatus; @@ -402,16 +519,16 @@ public: void lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32 lod); void loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod); - bool fetchMeshHeader(const LLVolumeParams& mesh_params, bool can_retry = true); - bool fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, bool can_retry = true); - EMeshProcessingResult headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size); + bool fetchMeshHeader(const LLVolumeParams& mesh_params); + bool fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod); + EMeshProcessingResult headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size, U32 flags = 0); EMeshProcessingResult lodReceived(const LLVolumeParams& mesh_params, S32 lod, U8* data, S32 data_size); bool skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size); bool decompositionReceived(const LLUUID& mesh_id, U8* data, S32 data_size); EMeshProcessingResult physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32 data_size); - bool hasPhysicsShapeInHeader(const LLUUID& mesh_id); - bool hasSkinInfoInHeader(const LLUUID& mesh_id); - bool hasHeader(const LLUUID& mesh_id); + bool hasPhysicsShapeInHeader(const LLUUID& mesh_id) const; + bool hasSkinInfoInHeader(const LLUUID& mesh_id) const; + bool hasHeader(const LLUUID& mesh_id) const; void notifyLoadedMeshes(); S32 getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod); @@ -422,7 +539,7 @@ public: //send request for skin info, returns true if header info exists // (should hold onto mesh_id and try again later if header info does not exist) - bool fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry = true); + bool fetchMeshSkinInfo(const LLUUID& mesh_id); //send request for decomposition, returns true if header info exists // (should hold onto mesh_id and try again later if header info does not exist) @@ -436,6 +553,8 @@ public: static void decActiveLODRequests(); static void incActiveHeaderRequests(); static void decActiveHeaderRequests(); + static void incActiveSkinRequests(); + static void decActiveSkinRequests(); // Set the caps strings and preferred version for constructing // mesh fetch URLs. @@ -456,6 +575,14 @@ private: LLCore::HttpHandle getByteRange(const std::string & url, size_t offset, size_t len, const LLCore::HttpHandler::ptr_t &handler); + + // Mutex: acquires mPendingMutex, mMutex and mHeaderMutex as needed + void loadMeshLOD(const LLUUID &mesh_id, const LLVolumeParams& mesh_params, S32 lod); + + // Threads: Repo thread only + U8* getDiskCacheBuffer(S32 size); + S32 mDiskCacheBufferSize = 0; + U8* mDiskCacheBuffer = nullptr; }; @@ -568,35 +695,35 @@ public: bool init(const LLMeshHeader& header); // Size for given LOD - S32 getSizeByLOD(S32 lod); + S32 getSizeByLOD(S32 lod) const; // Sum of all LOD sizes. - S32 getSizeTotal(); + S32 getSizeTotal() const; // Estimated triangle counts for the given LOD. - F32 getEstTrisByLOD(S32 lod); + F32 getEstTrisByLOD(S32 lod) const; // Estimated triangle counts for the largest LOD. Typically this // is also the "high" LOD, but not necessarily. - F32 getEstTrisMax(); + F32 getEstTrisMax() const; // Triangle count as computed by original streaming cost // formula. Triangles in each LOD are weighted based on how // frequently they will be seen. // This was called "unscaled_value" in the original getStreamingCost() functions. - F32 getRadiusWeightedTris(F32 radius); + F32 getRadiusWeightedTris(F32 radius) const; // Triangle count used by triangle-based cost formula. Based on // triangles in highest LOD plus potentially partial charges for // lower LODs depending on complexity. - F32 getEstTrisForStreamingCost(); + F32 getEstTrisForStreamingCost() const; // Streaming cost. This should match the server-side calculation // for the corresponding volume. - F32 getRadiusBasedStreamingCost(F32 radius); + F32 getRadiusBasedStreamingCost(F32 radius) const; // New streaming cost formula, currently only used for animated objects. - F32 getTriangleBasedStreamingCost(); + F32 getTriangleBasedStreamingCost() const; private: // From the "size" field of the mesh header. LOD 0=lowest, 3=highest. @@ -620,12 +747,12 @@ public: static U32 sLODPending; static U32 sLODProcessing; static U32 sCacheBytesRead; - static U32 sCacheBytesWritten; + static std::atomic<U32> sCacheBytesWritten; static U32 sCacheBytesHeaders; static U32 sCacheBytesSkins; static U32 sCacheBytesDecomps; static U32 sCacheReads; - static U32 sCacheWrites; + static std::atomic<U32> sCacheWrites; static U32 sMaxLockHoldoffs; // Maximum sequential locking failures static LLDeadmanTimer sQuiescentTimer; // Time-to-complete-mesh-downloads after significant events @@ -646,11 +773,11 @@ public: void unregisterMesh(LLVOVolume* volume); //mesh management functions - S32 loadMesh(LLVOVolume* volume, const LLVolumeParams& mesh_params, S32 detail = 0, S32 last_lod = -1); + S32 loadMesh(LLVOVolume* volume, const LLVolumeParams& mesh_params, S32 new_lod = 0, S32 last_lod = -1); void notifyLoadedMeshes(); - void notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume); - void notifyMeshUnavailable(const LLVolumeParams& mesh_params, S32 lod); + void notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume, S32 lod); + void notifyMeshUnavailable(const LLVolumeParams& mesh_params, S32 request_lod, S32 volume_lod); void notifySkinInfoReceived(LLMeshSkinInfo* info); void notifySkinInfoUnavailable(const LLUUID& info); void notifyDecompositionReceived(LLModel::Decomposition* info); @@ -662,7 +789,7 @@ public: void fetchPhysicsShape(const LLUUID& mesh_id); bool hasPhysicsShape(const LLUUID& mesh_id); bool hasSkinInfo(const LLUUID& mesh_id); - bool hasHeader(const LLUUID& mesh_id); + bool hasHeader(const LLUUID& mesh_id) const; void buildHull(const LLVolumeParams& params, S32 detail); void buildPhysicsMesh(LLModel::Decomposition& decomp); @@ -676,7 +803,7 @@ public: LLHandle<LLWholeModelFeeObserver> fee_observer= (LLHandle<LLWholeModelFeeObserver>()), LLHandle<LLWholeModelUploadObserver> upload_observer = (LLHandle<LLWholeModelUploadObserver>())); - S32 getMeshSize(const LLUUID& mesh_id, S32 lod); + S32 getMeshSize(const LLUUID& mesh_id, S32 lod) const; // Quiescent timer management, main thread only. static void metricsStart(); @@ -684,7 +811,7 @@ public: static void metricsProgress(unsigned int count); static void metricsUpdate(); - typedef boost::unordered_map<LLUUID, std::vector<LLVOVolume*> > mesh_load_map; + typedef std::unordered_map<LLUUID, std::vector<LLVOVolume*> > mesh_load_map; mesh_load_map mLoadingMeshes[4]; typedef std::unordered_map<LLUUID, LLPointer<LLMeshSkinInfo>> skin_map; @@ -695,15 +822,13 @@ public: LLMutex* mMeshMutex; - std::vector<LLMeshRepoThread::LODRequest> mPendingRequests; + typedef std::vector <std::unique_ptr<PendingRequestBase> > pending_requests_vec; + pending_requests_vec mPendingRequests; //list of mesh ids awaiting skin info - typedef boost::unordered_map<LLUUID, std::vector<LLVOVolume*> > skin_load_map; + typedef std::unordered_map<LLUUID, std::vector<LLVOVolume*> > skin_load_map; skin_load_map mLoadingSkins; - //list of mesh ids that need to send skin info fetch requests - std::queue<LLUUID> mPendingSkinRequests; - //list of mesh ids awaiting decompositions std::unordered_set<LLUUID> mLoadingDecompositions; diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp index 74fb2f0f93..cc2c7f2a99 100644 --- a/indra/newview/llpanelface.cpp +++ b/indra/newview/llpanelface.cpp @@ -420,6 +420,7 @@ bool LLPanelFace::postBuild() mCtrlColorTransp->setFollowsLeft(); getChildSetCommitCallback(mCheckFullbright, "checkbox fullbright", [&](LLUICtrl*, const LLSD&) { onCommitFullbright(); }); + getChildSetCommitCallback(mCheckHideWater, "checkbox_hide_water", [&](LLUICtrl*, const LLSD&) { onCommitHideWater(); }); mLabelTexGen = getChild<LLTextBox>("tex gen"); getChildSetCommitCallback(mComboTexGen, "combobox texgen", [&](LLUICtrl*, const LLSD&) { onCommitTexGen(); }); @@ -1024,6 +1025,13 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/) LLSelectedTEMaterial::getNormalID(normmap_id, identical_norm); LLSelectedTEMaterial::getSpecularID(specmap_id, identical_spec); + LLColor4 color = LLColor4::white; + bool identical_color = false; + + LLSelectedTE::getColor(color, identical_color); + F32 transparency = (1.f - color.mV[VALPHA]) * 100.f; + mExcludeWater = (id == IMG_ALPHA_GRAD) && normmap_id.isNull() && specmap_id.isNull() && (transparency == 0); + static S32 selected_te = -1; static LLUUID prev_obj_id; if ((LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool()) && @@ -1098,12 +1106,26 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/) updateVisibility(objectp); + // Water exclusion + { + mCheckHideWater->setEnabled(editable && !has_pbr_material && !isMediaTexSelected()); + mCheckHideWater->set(mExcludeWater); + if (mExcludeWater && !has_pbr_material) + { + mComboMatMedia->selectNthItem(MATMEDIA_MATERIAL); + } + editable &= !mExcludeWater; + + // disable controls for water exclusion face after updateVisibility, so the whole panel is not hidden + mComboMatMedia->setEnabled(editable); + mRadioMaterialType->setEnabled(editable); + mRadioPbrType->setEnabled(editable); + mCheckSyncSettings->setEnabled(editable); + } + // Color swatch mLabelColor->setEnabled(editable); - LLColor4 color = LLColor4::white; - bool identical_color = false; - LLSelectedTE::getColor(color, identical_color); LLColor4 prev_color = mColorSwatch->get(); mColorSwatch->setOriginal(color); mColorSwatch->set(color, force_set_values || (prev_color != color) || !editable); @@ -1114,7 +1136,6 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/) // Color transparency mLabelColorTransp->setEnabled(editable); - F32 transparency = (1.f - color.mV[VALPHA]) * 100.f; mCtrlColorTransp->setValue(editable ? transparency : 0); mCtrlColorTransp->setEnabled(editable && has_material); @@ -1986,7 +2007,8 @@ void LLPanelFace::updateCopyTexButton() mMenuClipboardTexture->setEnabled(objectp && objectp->getPCode() == LL_PCODE_VOLUME && objectp->permModify() && !objectp->isPermanentEnforced() && !objectp->isInventoryPending() && (LLSelectMgr::getInstance()->getSelection()->getObjectCount() == 1) - && LLMaterialEditor::canClipboardObjectsMaterial()); + && LLMaterialEditor::canClipboardObjectsMaterial() + && !mExcludeWater); std::string tooltip = (objectp && objectp->isInventoryPending()) ? LLTrans::getString("LoadingContents") : getString("paste_options"); mMenuClipboardTexture->setToolTip(tooltip); } @@ -3027,6 +3049,37 @@ void LLPanelFace::onCommitFullbright() sendFullbright(); } +void LLPanelFace::onCommitHideWater() +{ + if (mCheckHideWater->get()) + { + LLHandle<LLPanel> handle = getHandle(); + LLNotificationsUtil::add("WaterExclusionSurfacesWarning", LLSD(), LLSD(), + [handle](const LLSD& notification, const LLSD& response) + { + if(LLPanelFace* panel = (LLPanelFace*)handle.get()) + { + if (LLNotificationsUtil::getSelectedOption(notification, response) == 1) + { + panel->mCheckHideWater->setValue(false); + return; + } + // apply invisiprim texture and reset related params to set water exclusion surface + panel->sendBump(0); + panel->sendShiny(0); + LLSelectMgr::getInstance()->selectionSetAlphaOnly(1.f); + LLSelectMgr::getInstance()->selectionSetImage(IMG_ALPHA_GRAD); + LLSelectedTEMaterial::setDiffuseAlphaMode(panel, LLMaterial::DIFFUSE_ALPHA_MODE_BLEND); + } + }); + } + else + { + // reset texture to default plywood + LLSelectMgr::getInstance()->selectionSetImage(DEFAULT_OBJECT_TEXTURE); + } +} + void LLPanelFace::onCommitGlow() { sendGlow(); @@ -4891,6 +4944,26 @@ bool LLPanelFace::isIdenticalPlanarTexgen() return (identical_texgen && (selected_texgen == LLTextureEntry::TEX_GEN_PLANAR)); } +bool LLPanelFace::isMediaTexSelected() +{ + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); + if (LLViewerObject* objectp = node->getObject()) + { + S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); + for (S32 te = 0; te < num_tes; ++te) + { + if (node->isTESelected(te)) + { + if (objectp->getTE(te) && objectp->getTE(te)->hasMedia()) + { + return true; + } + } + } + } + return false; +} + void LLPanelFace::LLSelectedTE::getFace(LLFace*& face_to_return, bool& identical_face) { struct LLSelectedTEGetFace : public LLSelectedTEGetFunctor<LLFace *> diff --git a/indra/newview/llpanelface.h b/indra/newview/llpanelface.h index 4d2ce208b4..1ee9bf2cf7 100644 --- a/indra/newview/llpanelface.h +++ b/indra/newview/llpanelface.h @@ -139,6 +139,8 @@ protected: void updateMediaSettings(); void updateMediaTitle(); + bool isMediaTexSelected(); + void getState(); void sendTexture(); // applies and sends texture @@ -238,6 +240,7 @@ protected: void onCommitShiny(); void onCommitAlphaMode(); void onCommitFullbright(); + void onCommitHideWater(); void onCommitGlow(); void onCommitPlanarAlign(); void onCommitRepeatsPerMeter(); @@ -308,6 +311,7 @@ private: LLRadioGroup* mRadioPbrType { nullptr }; LLCheckBoxCtrl* mCheckFullbright { nullptr }; + LLCheckBoxCtrl* mCheckHideWater{ nullptr }; LLTextBox* mLabelColorTransp { nullptr }; LLSpinCtrl* mCtrlColorTransp { nullptr }; // transparency = 1 - alpha @@ -555,6 +559,7 @@ private: LLMenuButton* mMenuClipboardTexture; bool mIsAlpha; + bool mExcludeWater { false }; LLSD mClipboardParams; diff --git a/indra/newview/llpanelprimmediacontrols.cpp b/indra/newview/llpanelprimmediacontrols.cpp index b8c12ce0b9..b00b9d1ad1 100644 --- a/indra/newview/llpanelprimmediacontrols.cpp +++ b/indra/newview/llpanelprimmediacontrols.cpp @@ -294,7 +294,7 @@ void LLPanelPrimMediaControls::updateShape() LLViewerMediaImpl* media_impl = getTargetMediaImpl(); LLViewerObject* objectp = getTargetObject(); - if(!media_impl || gFloaterTools->getVisible()) + if(!media_impl || (gFloaterTools && gFloaterTools->getVisible())) { setVisible(false); return; diff --git a/indra/newview/llpanelprofilepicks.cpp b/indra/newview/llpanelprofilepicks.cpp index 08f3d3af5a..09b8011ce4 100644 --- a/indra/newview/llpanelprofilepicks.cpp +++ b/indra/newview/llpanelprofilepicks.cpp @@ -55,6 +55,8 @@ static LLPanelInjector<LLPanelProfilePicks> t_panel_profile_picks("panel_profile_picks"); static LLPanelInjector<LLPanelProfilePick> t_panel_profile_pick("panel_profile_pick"); +constexpr F32 REQUEST_TIMOUT = 60; +constexpr F32 LOCATION_CACHE_TIMOUT = 900; class LLPickHandler : public LLCommandHandler { @@ -306,6 +308,7 @@ void LLPanelProfilePicks::processProperties(void* data, EAvatarProcessorType typ void LLPanelProfilePicks::processProperties(const LLAvatarData* avatar_picks) { + LL_DEBUGS("PickInfo") << "Processing picks for avatar " << getAvatarId() << LL_ENDL; LLUUID selected_id = mPickToSelectOnLoad; bool has_selection = false; if (mPickToSelectOnLoad.isNull()) @@ -320,6 +323,25 @@ void LLPanelProfilePicks::processProperties(const LLAvatarData* avatar_picks) } } + // Avoid pointlesly requesting parcel data, + // store previous values + std::map<LLUUID, std::string> parcelid_location_map; + std::map<LLUUID, LLUUID> pickid_parcelid_map; + + for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx) + { + LLPanelProfilePick* pick_panel = dynamic_cast<LLPanelProfilePick*>(mTabContainer->getPanelByIndex(tab_idx)); + if (pick_panel && pick_panel->getPickId().notNull()) + { + std::string location = pick_panel->getPickLocation(); + if (!location.empty()) + { + parcelid_location_map[pick_panel->getParcelID()] = pick_panel->getPickLocation(); + pickid_parcelid_map[pick_panel->getPickId()] = pick_panel->getParcelID(); + } + } + } + mTabContainer->deleteAllTabs(); LLAvatarData::picks_list_t::const_iterator it = avatar_picks->picks_list.begin(); @@ -334,6 +356,15 @@ void LLPanelProfilePicks::processProperties(const LLAvatarData* avatar_picks) pick_panel->setPickName(pick_name); pick_panel->setAvatarId(getAvatarId()); + std::map<LLUUID, LLUUID>::const_iterator found_pick = pickid_parcelid_map.find(pick_id); + if (found_pick != pickid_parcelid_map.end()) + { + std::map<LLUUID, std::string>::const_iterator found = parcelid_location_map.find(found_pick->second); + if (found != parcelid_location_map.end() && !found->second.empty()) + { + pick_panel->setPickLocation(found_pick->second, found->second); + } + } mTabContainer->addTabPanel( LLTabContainer::TabPanelParams(). panel(pick_panel). @@ -353,6 +384,11 @@ void LLPanelProfilePicks::processProperties(const LLAvatarData* avatar_picks) LLPanelProfilePick* pick_panel = LLPanelProfilePick::create(); pick_panel->setAvatarId(getAvatarId()); + std::map<LLUUID, std::string>::const_iterator found = parcelid_location_map.find(data.parcel_id); + if (found != parcelid_location_map.end() && !found->second.empty()) + { + pick_panel->setPickLocation(data.parcel_id, found->second); + } pick_panel->processProperties(&data); mTabContainer->addTabPanel( LLTabContainer::TabPanelParams(). @@ -638,9 +674,14 @@ void LLPanelProfilePick::processProperties(void* data, EAvatarProcessorType type void LLPanelProfilePick::processProperties(const LLPickData* pick_info) { + LL_DEBUGS("PickInfo") << "Processing properties for pick " << mPickId << LL_ENDL; mIsEditing = false; mPickDescription->setParseHTML(true); - mParcelId = pick_info->parcel_id; + if (mParcelId != pick_info->parcel_id) + { + mParcelId = pick_info->parcel_id; + mPickLocationStr.clear(); + } setSnapshotId(pick_info->snapshot_id); if (!getSelfProfile()) { @@ -650,8 +691,11 @@ void LLPanelProfilePick::processProperties(const LLPickData* pick_info) setPickDesc(pick_info->desc); setPosGlobal(pick_info->pos_global); - // Send remote parcel info request to get parcel name and sim (region) name. - sendParcelInfoRequest(); + if (mPickLocationStr.empty() || mLastRequestTimer.getElapsedTimeF32() > LOCATION_CACHE_TIMOUT) + { + // Send remote parcel info request to get parcel name and sim (region) name. + sendParcelInfoRequest(); + } // *NOTE dzaporozhan // We want to keep listening to APT_PICK_INFO because user may @@ -691,9 +735,17 @@ void LLPanelProfilePick::setPickDesc(const std::string& desc) mPickDescription->setValue(desc); } +void LLPanelProfilePick::setPickLocation(const LLUUID &parcel_id, const std::string& location) +{ + setParcelID(parcel_id); // resets mPickLocationStr + setPickLocation(location); +} + void LLPanelProfilePick::setPickLocation(const std::string& location) { getChild<LLUICtrl>("pick_location")->setValue(location); + mPickLocationStr = location; + mLastRequestTimer.reset(); } void LLPanelProfilePick::onClickMap() @@ -790,16 +842,19 @@ std::string LLPanelProfilePick::getLocationNotice() void LLPanelProfilePick::sendParcelInfoRequest() { - if (mParcelId != mRequestedId) + if (mParcelId != mRequestedId || mLastRequestTimer.getElapsedTimeF32() > REQUEST_TIMOUT) { if (mRequestedId.notNull()) { LLRemoteParcelInfoProcessor::getInstance()->removeObserver(mRequestedId, this); } + LL_DEBUGS("PickInfo") << "Sending parcel request " << mParcelId << " for pick " << mPickId << LL_ENDL; LLRemoteParcelInfoProcessor::getInstance()->addObserver(mParcelId, this); LLRemoteParcelInfoProcessor::getInstance()->sendParcelInfoRequest(mParcelId); mRequestedId = mParcelId; + mLastRequestTimer.reset(); + mPickLocationStr.clear(); } } @@ -816,6 +871,20 @@ void LLPanelProfilePick::processParcelInfo(const LLParcelData& parcel_data) } } +void LLPanelProfilePick::setParcelID(const LLUUID& parcel_id) +{ + if (mParcelId != parcel_id) + { + mParcelId = parcel_id; + mPickLocationStr.clear(); + } + if (mRequestedId.notNull() && mRequestedId != parcel_id) + { + LLRemoteParcelInfoProcessor::getInstance()->removeObserver(mRequestedId, this); + mRequestedId.setNull(); + } +} + void LLPanelProfilePick::sendUpdate() { LLPickData pick_data; diff --git a/indra/newview/llpanelprofilepicks.h b/indra/newview/llpanelprofilepicks.h index e3f50f5576..b4d3eb010e 100644 --- a/indra/newview/llpanelprofilepicks.h +++ b/indra/newview/llpanelprofilepicks.h @@ -117,6 +117,8 @@ public: virtual void setPickName(const std::string& name); const std::string getPickName(); + virtual void setPickLocation(const LLUUID& parcel_id, const std::string& location); + std::string getPickLocation() { return mPickLocationStr; }; void processProperties(void* data, EAvatarProcessorType type) override; void processProperties(const LLPickData* pick_data); @@ -135,7 +137,8 @@ public: //This stuff we got from LLRemoteParcelObserver, in the last one we intentionally do nothing void processParcelInfo(const LLParcelData& parcel_data) override; - void setParcelID(const LLUUID& parcel_id) override { mParcelId = parcel_id; } + void setParcelID(const LLUUID& parcel_id) override; + LLUUID getParcelID() const { return mParcelId; } void setErrorStatus(S32 status, const std::string& reason) override {}; protected: @@ -230,6 +233,8 @@ protected: LLUUID mPickId; LLUUID mRequestedId; std::string mPickNameStr; + std::string mPickLocationStr; + LLTimer mLastRequestTimer; boost::signals2::connection mRegionCallbackConnection; boost::signals2::connection mParcelCallbackConnection; diff --git a/indra/newview/llscrollingpanelparam.h b/indra/newview/llscrollingpanelparam.h index 3aba4e4e40..93daf22e76 100644 --- a/indra/newview/llscrollingpanelparam.h +++ b/indra/newview/llscrollingpanelparam.h @@ -81,7 +81,6 @@ public: protected: LLTimer mMouseDownTimer; // timer for how long mouse has been held down on a hint. F32 mLastHeldTime; - bool mAllowModify; LLButton* mLessBtn; LLButton* mMoreBtn; diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp index 03cf79c28c..01eb76c1a3 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -7257,7 +7257,10 @@ void dialog_refresh_all() // *TODO: Eliminate all calls into outside classes below, make those // objects register with the update signal. - gFloaterTools->dirty(); + if (gFloaterTools) + { + gFloaterTools->dirty(); + } gMenuObject->needsArrange(); @@ -7486,7 +7489,8 @@ void LLSelectMgr::updatePointAt() LLVector3 select_offset; const LLPickInfo& pick = gViewerWindow->getLastPick(); LLViewerObject *click_object = pick.getObject(); - if (click_object && click_object->isSelected()) + bool was_hud = pick.mPickHUD && !click_object->isHUDAttachment(); + if (click_object && click_object->isSelected() && !was_hud) { // clicked on another object in our selection group, use that as target select_offset.setVec(pick.mObjectOffset); diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 6bf203c140..de673b591f 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -304,16 +304,62 @@ void callback_cache_name(const LLUUID& id, const std::string& full_name, bool is // exported functionality // +void do_startup_frame() +{ + // Until after STATE_AGENT_SEND we don't get very many UDP packets to poll the socket, + // and after STATE_PRECACHE the LLAppViewer::idleNetwork() will do UDP processing, + // so we only bother to process between those two states. + EStartupState state = LLStartUp::getStartupState(); + if (state > STATE_AGENT_SEND && state < STATE_PRECACHE) + { + // drain the UDP socket... + U64 t0 = totalTime(); + constexpr U64 MAX_STARTUP_FRAME_TIME = 2000; // usec + constexpr U64 MAX_STARTUP_FRAME_MESSAGES = 100; + S32 num_messages = 0; + bool needs_drain = false; + LockMessageChecker lmc(gMessageSystem); + while (lmc.checkAllMessages(gFrameCount, gServicePump)) + { + if (gDoDisconnect) + { + // We're disconnecting, don't process any more messages from the server + // We're usually disconnecting due to either network corruption or a + // server going down, so this is OK. + break; + } + if (++num_messages >= MAX_STARTUP_FRAME_MESSAGES + || (totalTime() - t0) > MAX_STARTUP_FRAME_TIME) + { + needs_drain = true; + break; + } + } + if (needs_drain || gMessageSystem->mPacketRing.getNumBufferedPackets() > 0) + { + gMessageSystem->drainUdpSocket(); + } + lmc.processAcks(); + } + // ...then call display_startup() + display_startup(); +} + void pump_idle_startup_network(void) { + // while there are message to process: + // process one then call display_startup() + S32 num_messages = 0; { LockMessageChecker lmc(gMessageSystem); while (lmc.checkAllMessages(gFrameCount, gServicePump)) { display_startup(); + ++num_messages; } lmc.processAcks(); } + // finally call one last display_startup() display_startup(); } @@ -630,21 +676,6 @@ bool idle_startup() F32 dropPercent = gSavedSettings.getF32("PacketDropPercentage"); msg->mPacketRing.setDropPercentage(dropPercent); - - F32 inBandwidth = gSavedSettings.getF32("InBandwidth"); - F32 outBandwidth = gSavedSettings.getF32("OutBandwidth"); - if (inBandwidth != 0.f) - { - LL_DEBUGS("AppInit") << "Setting packetring incoming bandwidth to " << inBandwidth << LL_ENDL; - msg->mPacketRing.setUseInThrottle(true); - msg->mPacketRing.setInBandwidth(inBandwidth); - } - if (outBandwidth != 0.f) - { - LL_DEBUGS("AppInit") << "Setting packetring outgoing bandwidth to " << outBandwidth << LL_ENDL; - msg->mPacketRing.setUseOutThrottle(true); - msg->mPacketRing.setOutBandwidth(outBandwidth); - } } LL_INFOS("AppInit") << "Message System Initialized." << LL_ENDL; @@ -770,7 +801,7 @@ bool idle_startup() LL_DEBUGS("AppInit") << "STATE_BROWSER_INIT" << LL_ENDL; std::string msg = LLTrans::getString("LoginInitializingBrowser"); set_startup_status(0.03f, msg.c_str(), gAgent.mMOTD.c_str()); - display_startup(); + do_startup_frame(); // LLViewerMedia::initBrowser(); LLStartUp::setStartupState( STATE_LOGIN_SHOW ); return false; @@ -835,7 +866,7 @@ bool idle_startup() LL_DEBUGS("AppInit") << "FirstLoginThisInstall off" << LL_ENDL; } } - display_startup(); + do_startup_frame(); LLStartUp::setStartupState( STATE_LOGIN_WAIT ); // Wait for user input } else @@ -866,7 +897,7 @@ bool idle_startup() } LL_DEBUGS("AppInit") << "PeekMessage processed" << LL_ENDL; #endif - display_startup(); + do_startup_frame(); timeout.reset(); return false; } @@ -881,7 +912,7 @@ bool idle_startup() // Don't do anything. Wait for the login view to call the login_callback, // which will push us to the next state. - // display() function will be the one to run display_startup() + // display() function will be the one to run do_startup_frame() // Sleep so we don't spin the CPU ms_sleep(1); return false; @@ -1063,7 +1094,7 @@ bool idle_startup() auth_desc = LLTrans::getString("LoginInProgress"); set_startup_status(progress, auth_desc, auth_message); progress += 0.02f; - display_startup(); + do_startup_frame(); // Setting initial values... LLLoginInstance* login = LLLoginInstance::getInstance(); @@ -1100,7 +1131,7 @@ bool idle_startup() emsg << LLTrans::getString("LoginFailedHeader") << "\n"; if(LLLoginInstance::getInstance()->authFailure()) { - LL_INFOS("LLStartup") << "Login failed, LLLoginInstance::getResponse(): " + LL_INFOS("LLStartUp") << "Login failed, LLLoginInstance::getResponse(): " << LLLoginInstance::getInstance()->getResponse() << LL_ENDL; LLSD response = LLLoginInstance::getInstance()->getResponse(); // Still have error conditions that may need some @@ -1172,7 +1203,7 @@ bool idle_startup() // If optional was skipped this case shouldn't // be reached. - LL_INFOS("LLStartup") << "Forcing a quit due to update." << LL_ENDL; + LL_INFOS("LLStartUp") << "Forcing a quit due to update." << LL_ENDL; LLLoginInstance::getInstance()->disconnect(); LLAppViewer::instance()->forceQuit(); } @@ -1200,7 +1231,7 @@ bool idle_startup() } catch (LLCertException &cert_exception) { - LL_WARNS("LLStartup", "SECAPI") << "Caught " << cert_exception.what() << " certificate expception on getCertificate("<< response["certificate"] << ")" << LL_ENDL; + LL_WARNS("LLStartUp", "SECAPI") << "Caught " << cert_exception.what() << " certificate expception on getCertificate("<< response["certificate"] << ")" << LL_ENDL; LLSD args; args["REASON"] = LLTrans::getString(cert_exception.what()); @@ -1252,7 +1283,7 @@ bool idle_startup() // notificatioin message. LLSD args; args["ERROR_MESSAGE"] = emsg.str(); - LL_INFOS("LLStartup") << "Notification: " << args << LL_ENDL; + LL_INFOS("LLStartUp") << "Notification: " << args << LL_ENDL; LLNotificationsUtil::add("ErrorMessage", args, LLSD(), login_alert_done); } } @@ -1275,7 +1306,7 @@ bool idle_startup() { LLSD args; args["ERROR_MESSAGE"] = emsg.str(); - LL_INFOS("LLStartup") << "Notification: " << args << LL_ENDL; + LL_INFOS("LLStartUp") << "Notification: " << args << LL_ENDL; LLNotificationsUtil::add("ErrorMessage", args, LLSD(), login_alert_done); transition_back_to_login_panel(emsg.str()); show_connect_box = true; @@ -1291,71 +1322,71 @@ bool idle_startup() if (STATE_WORLD_INIT == LLStartUp::getStartupState()) { set_startup_status(0.30f, LLTrans::getString("LoginInitializingWorld"), gAgent.mMOTD); - display_startup(); + do_startup_frame(); // We should have an agent id by this point. llassert(!(gAgentID == LLUUID::null)); // Finish agent initialization. (Requires gSavedSettings, builds camera) gAgent.init(); - display_startup(); + do_startup_frame(); gAgentCamera.init(); - display_startup(); - display_startup(); + do_startup_frame(); + do_startup_frame(); // Since we connected, save off the settings so the user doesn't have to // type the name/password again if we crash. gSavedSettings.saveToFile(gSavedSettings.getString("ClientSettingsFile"), true); LLUIColorTable::instance().saveUserSettings(); - display_startup(); + do_startup_frame(); // // Initialize classes w/graphics stuff. // LLSurface::initClasses(); - display_startup(); + do_startup_frame(); - display_startup(); + do_startup_frame(); LLDrawable::initClass(); - display_startup(); + do_startup_frame(); // init the shader managers LLPostProcess::initClass(); - display_startup(); + do_startup_frame(); LLAvatarAppearance::initClass("avatar_lad.xml","avatar_skeleton.xml"); - display_startup(); + do_startup_frame(); LLViewerObject::initVOClasses(); - display_startup(); + do_startup_frame(); // Initialize all our tools. Must be done after saved settings loaded. // NOTE: This also is where gToolMgr used to be instantiated before being turned into a singleton. LLToolMgr::getInstance()->initTools(); - display_startup(); + do_startup_frame(); // Pre-load floaters, like the world map, that are slow to spawn // due to XML complexity. gViewerWindow->initWorldUI(); - display_startup(); + do_startup_frame(); // This is where we used to initialize gWorldp. Original comment said: // World initialization must be done after above window init // User might have overridden far clip LLWorld::getInstance()->setLandFarClip(gAgentCamera.mDrawDistance); - display_startup(); + do_startup_frame(); // Before we create the first region, we need to set the agent's mOriginGlobal // This is necessary because creating objects before this is set will result in a // bad mPositionAgent cache. gAgent.initOriginGlobal(from_region_handle(gFirstSimHandle)); - display_startup(); + do_startup_frame(); LLWorld::getInstance()->addRegion(gFirstSimHandle, gFirstSim); - display_startup(); + do_startup_frame(); LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(gFirstSimHandle); LL_INFOS("AppInit") << "Adding initial simulator " << regionp->getOriginGlobal() << LL_ENDL; @@ -1364,18 +1395,18 @@ bool idle_startup() << gFirstSimSeedCap << LL_ENDL; regionp->setSeedCapability(gFirstSimSeedCap); LL_DEBUGS("AppInit") << "Waiting for seed grant ...." << LL_ENDL; - display_startup(); + do_startup_frame(); // Set agent's initial region to be the one we just created. gAgent.setRegion(regionp); - display_startup(); + do_startup_frame(); // Set agent's initial position, which will be read by LLVOAvatar when the avatar // object is created. I think this must be done after setting the region. JC gAgent.setPositionAgent(agent_start_position_region); - display_startup(); + do_startup_frame(); LLStartUp::initExperiences(); - display_startup(); + do_startup_frame(); // If logging should be enebled, turns it on and loads history from disk // Note: does not happen on init of singleton because preferences can use @@ -1396,7 +1427,7 @@ bool idle_startup() { LLStartUp::multimediaInit(); LLStartUp::setStartupState( STATE_FONT_INIT ); - display_startup(); + do_startup_frame(); return false; } @@ -1405,7 +1436,7 @@ bool idle_startup() { LLStartUp::fontInit(); LLStartUp::setStartupState( STATE_SEED_GRANTED_WAIT ); - display_startup(); + do_startup_frame(); return false; } @@ -1477,7 +1508,7 @@ bool idle_startup() set_startup_status(0.4f, LLTrans::getString("LoginRequestSeedCapGrant"), gAgent.mMOTD.c_str()); } } - display_startup(); + do_startup_frame(); return false; } @@ -1488,7 +1519,7 @@ bool idle_startup() //--------------------------------------------------------------------- if (STATE_SEED_CAP_GRANTED == LLStartUp::getStartupState()) { - display_startup(); + do_startup_frame(); // These textures are not warrantied to be cached, so needs // to hapen with caps granted @@ -1497,9 +1528,9 @@ bool idle_startup() // will init images, should be done with caps, but before gSky.init() LLEnvironment::getInstance()->initSingleton(); - display_startup(); + do_startup_frame(); update_texture_fetch(); - display_startup(); + do_startup_frame(); if ( gViewerWindow != NULL) { // This isn't the first logon attempt, so show the UI @@ -1507,15 +1538,15 @@ bool idle_startup() } gLoginMenuBarView->setVisible( false ); gLoginMenuBarView->setEnabled( false ); - display_startup(); + do_startup_frame(); // direct logging to the debug console's line buffer LLError::logToFixedBuffer(gDebugView->mDebugConsolep); - display_startup(); + do_startup_frame(); // set initial visibility of debug console gDebugView->mDebugConsolep->setVisible(gSavedSettings.getBOOL("ShowDebugConsole")); - display_startup(); + do_startup_frame(); // // Set message handlers @@ -1524,23 +1555,23 @@ bool idle_startup() // register callbacks for messages. . . do this after initial handshake to make sure that we don't catch any unwanted register_viewer_callbacks(gMessageSystem); - display_startup(); + do_startup_frame(); // Debugging info parameters gMessageSystem->setMaxMessageTime( 0.5f ); // Spam if decoding all msgs takes more than 500 ms - display_startup(); + do_startup_frame(); #ifndef LL_RELEASE_FOR_DOWNLOAD gMessageSystem->setTimeDecodes( true ); // Time the decode of each msg gMessageSystem->setTimeDecodesSpamThreshold( 0.05f ); // Spam if a single msg takes over 50ms to decode #endif - display_startup(); + do_startup_frame(); gXferManager->registerCallbacks(gMessageSystem); - display_startup(); + do_startup_frame(); LLStartUp::initNameCache(); - display_startup(); + do_startup_frame(); // update the voice settings *after* gCacheName initialization // so that we can construct voice UI that relies on the name cache @@ -1548,7 +1579,7 @@ bool idle_startup() { LLVoiceClient::getInstance()->updateSettings(); } - display_startup(); + do_startup_frame(); // create a container's instance for start a controlling conversation windows // by the voice's events @@ -1569,12 +1600,12 @@ bool idle_startup() // register null callbacks for audio until the audio system is initialized gMessageSystem->setHandlerFuncFast(_PREHASH_SoundTrigger, null_message_callback, NULL); gMessageSystem->setHandlerFuncFast(_PREHASH_AttachedSound, null_message_callback, NULL); - display_startup(); + do_startup_frame(); //reset statistics LLViewerStats::instance().resetStats(); - display_startup(); + do_startup_frame(); // // Set up region and surface defaults // @@ -1599,7 +1630,7 @@ bool idle_startup() LLViewerCamera::getInstance()->setAspect(gViewerWindow->getWorldViewAspectRatio()); // Initialize FOV LLViewerCamera::getInstance()->setDefaultFOV(gSavedSettings.getF32("CameraAngle")); - display_startup(); + do_startup_frame(); // Move agent to starting location. The position handed to us by // the space server is in global coordinates, but the agent frame @@ -1610,7 +1641,7 @@ bool idle_startup() gAgent.resetAxes(gAgentStartLookAt); gAgentCamera.stopCameraAnimation(); gAgentCamera.resetCamera(); - display_startup(); + do_startup_frame(); // Initialize global class data needed for surfaces (i.e. textures) LL_DEBUGS("AppInit") << "Initializing sky..." << LL_ENDL; @@ -1621,7 +1652,7 @@ bool idle_startup() LLGLState::checkStates(); - display_startup(); + do_startup_frame(); LL_DEBUGS("AppInit") << "Decoding images..." << LL_ENDL; // For all images pre-loaded into viewer cache, init @@ -1635,12 +1666,12 @@ bool idle_startup() { F32 frac = (F32)i / (F32)DECODE_TIME_SEC; set_startup_status(0.45f + frac*0.1f, LLTrans::getString("LoginDecodingImages"), gAgent.mMOTD); - display_startup(); + do_startup_frame(); gTextureList.decodeAllImages(1.f); } LLStartUp::setStartupState( STATE_WORLD_WAIT ); - display_startup(); + do_startup_frame(); // JC - Do this as late as possible to increase likelihood Purify // will run. @@ -1669,7 +1700,7 @@ bool idle_startup() NULL); timeout.reset(); - display_startup(); + do_startup_frame(); return false; } @@ -1696,7 +1727,7 @@ bool idle_startup() { LL_DEBUGS("AppInit") << "Connecting to region..." << LL_ENDL; set_startup_status(0.60f, LLTrans::getString("LoginConnectingToRegion"), gAgent.mMOTD); - display_startup(); + do_startup_frame(); // register with the message system so it knows we're // expecting this message LLMessageSystem* msg = gMessageSystem; @@ -1710,7 +1741,7 @@ bool idle_startup() gAssetStorage->setUpstream(regionp->getHost()); gCacheName->setUpstream(regionp->getHost()); } - display_startup(); + do_startup_frame(); // Create login effect // But not on first login, because you can't see your avatar then @@ -1725,7 +1756,7 @@ bool idle_startup() LLStartUp::setStartupState( STATE_AGENT_WAIT ); // Go to STATE_AGENT_WAIT timeout.reset(); - display_startup(); + do_startup_frame(); return false; } @@ -1734,35 +1765,13 @@ bool idle_startup() //--------------------------------------------------------------------- if (STATE_AGENT_WAIT == LLStartUp::getStartupState()) { - { - LockMessageChecker lmc(gMessageSystem); - while (lmc.checkAllMessages(gFrameCount, gServicePump)) - { - if (gAgentMovementCompleted) - { - // Sometimes we have more than one message in the - // queue. break out of this loop and continue - // processing. If we don't, then this could skip one - // or more login steps. - break; - } - else - { - LL_DEBUGS("AppInit") << "Awaiting AvatarInitComplete, got " - << gMessageSystem->getMessageName() << LL_ENDL; - } - display_startup(); - } - lmc.processAcks(); - } - - display_startup(); + do_startup_frame(); if (gAgentMovementCompleted) { LLStartUp::setStartupState( STATE_INVENTORY_SEND ); } - display_startup(); + do_startup_frame(); if (!gAgentMovementCompleted && timeout.getElapsedTimeF32() > STATE_AGENT_WAIT_TIMEOUT) { @@ -1795,7 +1804,7 @@ bool idle_startup() if (STATE_INVENTORY_SEND == LLStartUp::getStartupState()) { LL_PROFILE_ZONE_NAMED("State inventory send") - display_startup(); + do_startup_frame(); // request mute list LL_INFOS() << "Requesting Mute List" << LL_ENDL; @@ -1805,12 +1814,12 @@ bool idle_startup() LL_INFOS() << "Requesting Money Balance" << LL_ENDL; LLStatusBar::sendMoneyBalanceRequest(); - display_startup(); + do_startup_frame(); // Inform simulator of our language preference LLAgentLanguage::update(); - display_startup(); + do_startup_frame(); // unpack thin inventory LLSD response = LLLoginInstance::getInstance()->getResponse(); //bool dump_buffer = false; @@ -1825,7 +1834,7 @@ bool idle_startup() gInventory.setLibraryRootFolderID(id.asUUID()); } } - display_startup(); + do_startup_frame(); LLSD inv_lib_owner = response["inventory-lib-owner"]; if(inv_lib_owner.isDefined()) @@ -1837,9 +1846,9 @@ bool idle_startup() gInventory.setLibraryOwnerID(LLUUID(id.asUUID())); } } - display_startup(); + do_startup_frame(); LLStartUp::setStartupState(STATE_INVENTORY_SKEL); - display_startup(); + do_startup_frame(); return false; } @@ -1858,7 +1867,7 @@ bool idle_startup() LL_WARNS("AppInit") << "Problem loading inventory-skel-lib" << LL_ENDL; } } - display_startup(); + do_startup_frame(); LLSD inv_skeleton = response["inventory-skeleton"]; if (inv_skeleton.isDefined()) @@ -1869,9 +1878,9 @@ bool idle_startup() LL_WARNS("AppInit") << "Problem loading inventory-skel-targets" << LL_ENDL; } } - display_startup(); + do_startup_frame(); LLStartUp::setStartupState(STATE_INVENTORY_SEND2); - display_startup(); + do_startup_frame(); return false; } @@ -1917,7 +1926,7 @@ bool idle_startup() list[agent_id] = new LLRelationship(given_rights, has_rights, false); } LLAvatarTracker::instance().addBuddyList(list); - display_startup(); + do_startup_frame(); } bool show_hud = false; @@ -1945,7 +1954,7 @@ bool idle_startup() //} } } - display_startup(); + do_startup_frame(); // Either we want to show tutorial because this is the first login // to a Linden Help Island or the user quit with the tutorial @@ -1954,21 +1963,21 @@ bool idle_startup() { LLFloaterReg::showInstance("hud", LLSD(), false); } - display_startup(); + do_startup_frame(); LLSD event_notifications = response["event_notifications"]; if(event_notifications.isDefined()) { gEventNotifier.load(event_notifications); } - display_startup(); + do_startup_frame(); LLSD classified_categories = response["classified_categories"]; if(classified_categories.isDefined()) { LLClassifiedInfo::loadCategories(classified_categories); } - display_startup(); + do_startup_frame(); // This method MUST be called before gInventory.findCategoryUUIDForType because of // gInventory.mIsAgentInvUsable is set to true in the gInventory.buildParentChildMap. @@ -1987,7 +1996,7 @@ bool idle_startup() LLInventoryModelBackgroundFetch::instance().start(); gInventory.createCommonSystemCategories(); LLStartUp::setStartupState(STATE_INVENTORY_CALLBACKS ); - display_startup(); + do_startup_frame(); return false; } @@ -1999,7 +2008,7 @@ bool idle_startup() { if (!LLInventoryModel::isSysFoldersReady()) { - display_startup(); + do_startup_frame(); return false; } @@ -2024,7 +2033,7 @@ bool idle_startup() gInventory.addChangedMask(LLInventoryObserver::ALL, LLUUID::null); gInventory.notifyObservers(); - display_startup(); + do_startup_frame(); // set up callbacks LL_INFOS() << "Registering Callbacks" << LL_ENDL; @@ -2035,18 +2044,18 @@ bool idle_startup() LLAvatarTracker::instance().registerCallbacks(msg); LL_INFOS() << " Landmark" << LL_ENDL; LLLandmark::registerCallbacks(msg); - display_startup(); + do_startup_frame(); // request all group information LL_INFOS() << "Requesting Agent Data" << LL_ENDL; gAgent.sendAgentDataUpdateRequest(); - display_startup(); + do_startup_frame(); // Create the inventory views LL_INFOS() << "Creating Inventory Views" << LL_ENDL; LLFloaterReg::getInstance("inventory"); - display_startup(); + do_startup_frame(); LLStartUp::setStartupState( STATE_MISC ); - display_startup(); + do_startup_frame(); return false; } @@ -2096,7 +2105,7 @@ bool idle_startup() gSavedSettings.setBOOL("ShowStartLocation", true); } - display_startup(); + do_startup_frame(); // Load stored local environment if needed. LLEnvironment::instance().loadFromSettings(); @@ -2104,7 +2113,7 @@ bool idle_startup() // *TODO : Uncomment that line once the whole grid migrated to SLM and suppress it from LLAgent::handleTeleportFinished() (llagent.cpp) //check_merchant_status(); - display_startup(); + do_startup_frame(); if (gSavedSettings.getBOOL("HelpFloaterOpen")) { @@ -2112,7 +2121,7 @@ bool idle_startup() LLViewerHelp::instance().showTopic(""); } - display_startup(); + do_startup_frame(); // We're successfully logged in. gSavedSettings.setBOOL("FirstLoginThisInstall", false); @@ -2121,12 +2130,12 @@ bool idle_startup() LLFloaterGridStatus::getInstance()->startGridStatusTimer(); - display_startup(); + do_startup_frame(); - display_startup(); + do_startup_frame(); // JC: Initializing audio requests many sounds for download. init_audio(); - display_startup(); + do_startup_frame(); // JC: Initialize "active" gestures. This may also trigger // many gesture downloads, if this is the user's first @@ -2164,7 +2173,7 @@ bool idle_startup() LLGestureMgr::instance().startFetch(); } gDisplaySwapBuffers = true; - display_startup(); + do_startup_frame(); LLMessageSystem* msg = gMessageSystem; msg->setHandlerFuncFast(_PREHASH_SoundTrigger, process_sound_trigger); @@ -2242,10 +2251,10 @@ bool idle_startup() } } - display_startup(); + do_startup_frame(); //DEV-17797. get null folder. Any items found here moved to Lost and Found LLInventoryModelBackgroundFetch::instance().findLostItems(); - display_startup(); + do_startup_frame(); LLStartUp::setStartupState( STATE_PRECACHE ); timeout.reset(); @@ -2254,7 +2263,7 @@ bool idle_startup() if (STATE_PRECACHE == LLStartUp::getStartupState()) { - display_startup(); + do_startup_frame(); F32 timeout_frac = timeout.getElapsedTimeF32()/PRECACHING_DELAY; // We now have an inventory skeleton, so if this is a user's first @@ -2280,7 +2289,7 @@ bool idle_startup() callAfterCOFFetch(set_flags_and_update_appearance); } - display_startup(); + do_startup_frame(); // wait precache-delay and for agent's avatar or a lot longer. if ((timeout_frac > 1.f) && isAgentAvatarValid()) @@ -2305,7 +2314,7 @@ bool idle_startup() set_startup_status(0.60f + 0.30f * timeout_frac, LLTrans::getString("LoginPrecaching"), gAgent.mMOTD.c_str()); - display_startup(); + do_startup_frame(); } return true; @@ -2333,7 +2342,7 @@ bool idle_startup() LLStartUp::setStartupState( STATE_CLEANUP ); } - display_startup(); + do_startup_frame(); if (gAgent.isOutfitChosen() && (wearables_time > MAX_WEARABLES_TIME)) { @@ -2374,7 +2383,7 @@ bool idle_startup() if (STATE_CLEANUP == LLStartUp::getStartupState()) { set_startup_status(1.0, "", ""); - display_startup(); + do_startup_frame(); if (!mBenefitsSuccessfullyInit) { @@ -2395,7 +2404,7 @@ bool idle_startup() //gViewerWindow->revealIntroPanel(); gViewerWindow->setStartupComplete(); gViewerWindow->setProgressCancelButtonVisible(false); - display_startup(); + do_startup_frame(); // We're not away from keyboard, even though login might have taken // a while. JC @@ -2427,7 +2436,7 @@ bool idle_startup() // LLUserAuth::getInstance()->reset(); LLStartUp::setStartupState( STATE_STARTED ); - display_startup(); + do_startup_frame(); // Unmute audio if desired and setup volumes. // This is a not-uncommon crash site, so surround it with @@ -2443,7 +2452,7 @@ bool idle_startup() LLAgentPicksInfo::getInstance()->requestNumberOfPicks(); - display_startup(); + do_startup_frame(); llassert(LLPathfindingManager::getInstance() != NULL); LLPathfindingManager::getInstance()->initSystem(); @@ -3056,9 +3065,7 @@ std::string LLStartUp::startupStateToString(EStartupState state) // static void LLStartUp::setStartupState( EStartupState state ) { - LL_INFOS("AppInit") << "Startup state changing from " << - getStartupStateString() << " to " << - startupStateToString(state) << LL_ENDL; + LL_INFOS("AppInit") << getStartupStateString() << " --> " << startupStateToString(state) << LL_ENDL; getPhases().stopPhase(getStartupStateString()); gStartupState = state; @@ -3122,7 +3129,7 @@ void LLStartUp::multimediaInit() LL_DEBUGS("AppInit") << "Initializing Multimedia...." << LL_ENDL; std::string msg = LLTrans::getString("LoginInitializingMultimedia"); set_startup_status(0.42f, msg.c_str(), gAgent.mMOTD.c_str()); - display_startup(); + do_startup_frame(); } void LLStartUp::fontInit() @@ -3130,7 +3137,7 @@ void LLStartUp::fontInit() LL_DEBUGS("AppInit") << "Initializing fonts...." << LL_ENDL; std::string msg = LLTrans::getString("LoginInitializingFonts"); set_startup_status(0.45f, msg.c_str(), gAgent.mMOTD.c_str()); - display_startup(); + do_startup_frame(); LLFontGL::loadDefaultFonts(); } @@ -3827,14 +3834,14 @@ bool process_login_success_response() { // We got an answer from the grid -> use that for map for the current session gSavedSettings.setString("CurrentMapServerURL", map_server_url); - LL_INFOS("LLStartup") << "map-server-url : we got an answer from the grid : " << map_server_url << LL_ENDL; + LL_INFOS("LLStartUp") << "map-server-url : we got an answer from the grid : " << map_server_url << LL_ENDL; } else { // No answer from the grid -> use the default setting for current session map_server_url = gSavedSettings.getString("MapServerURL"); gSavedSettings.setString("CurrentMapServerURL", map_server_url); - LL_INFOS("LLStartup") << "map-server-url : no map-server-url answer, we use the default setting for the map : " << map_server_url << LL_ENDL; + LL_INFOS("LLStartUp") << "map-server-url : no map-server-url answer, we use the default setting for the map : " << map_server_url << LL_ENDL; } // Default male and female avatars allowing the user to choose their avatar on first login. diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp index bda53f66eb..78d930c05c 100644 --- a/indra/newview/lltextureview.cpp +++ b/indra/newview/lltextureview.cpp @@ -642,7 +642,7 @@ void LLGLTexMemBar::draw() text = llformat("Mesh: Reqs(Tot/Htp/Big): %u/%u/%u Rtr/Err: %u/%u Cread/Cwrite: %u/%u Low/At/High: %d/%d/%d", LLMeshRepository::sMeshRequestCount, LLMeshRepository::sHTTPRequestCount, LLMeshRepository::sHTTPLargeRequestCount, LLMeshRepository::sHTTPRetryCount, LLMeshRepository::sHTTPErrorCount, - LLMeshRepository::sCacheReads, LLMeshRepository::sCacheWrites, + (U32)LLMeshRepository::sCacheReads, (U32)LLMeshRepository::sCacheWrites, LLMeshRepoThread::sRequestLowWater, LLMeshRepoThread::sRequestWaterLevel, LLMeshRepoThread::sRequestHighWater); LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*2, text_color, LLFontGL::LEFT, LLFontGL::TOP); diff --git a/indra/newview/lltoolbrush.cpp b/indra/newview/lltoolbrush.cpp index 2fe81df4fb..cf7b123fa7 100644 --- a/indra/newview/lltoolbrush.cpp +++ b/indra/newview/lltoolbrush.cpp @@ -447,7 +447,10 @@ void LLToolBrushLand::handleSelect() { gEditMenuHandler = this; - gFloaterTools->setStatusText("modifyland"); + if (gFloaterTools) + { + gFloaterTools->setStatusText("modifyland"); + } // if (!mBrushSelected) { mBrushSelected = true; diff --git a/indra/newview/lltooldraganddrop.cpp b/indra/newview/lltooldraganddrop.cpp index 4efa289141..75adb722f3 100644 --- a/indra/newview/lltooldraganddrop.cpp +++ b/indra/newview/lltooldraganddrop.cpp @@ -1253,6 +1253,7 @@ void LLToolDragAndDrop::dropMaterial(LLViewerObject* hit_obj, // If user dropped a material onto face it implies // applying texture now without cancel, save to selection if (nodep + && gFloaterTools && gFloaterTools->getVisible() && nodep->mSavedGLTFMaterialIds.size() > hit_face) { @@ -1429,10 +1430,10 @@ void LLToolDragAndDrop::dropTexture(LLViewerObject* hit_obj, // If user dropped a texture onto face it implies // applying texture now without cancel, save to selection - LLPanelFace* panel_face = gFloaterTools->getPanelFace(); + LLPanelFace* panel_face = gFloaterTools ? gFloaterTools->getPanelFace() : nullptr; if (nodep - && gFloaterTools->getVisible() && panel_face + && gFloaterTools->getVisible() && panel_face->getTextureDropChannel() == 0 /*texture*/ && nodep->mSavedTextures.size() > hit_face) { @@ -1488,8 +1489,8 @@ void LLToolDragAndDrop::dropTextureOneFace(LLViewerObject* hit_obj, if (allow_adding_to_override) { LLGLTFMaterial::TextureInfo drop_channel = LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR; - LLPanelFace* panel_face = gFloaterTools->getPanelFace(); - if (gFloaterTools->getVisible() && panel_face) + LLPanelFace* panel_face = gFloaterTools ? gFloaterTools->getPanelFace() : nullptr; + if (panel_face && gFloaterTools->getVisible()) { drop_channel = panel_face->getPBRDropChannel(); } @@ -1514,9 +1515,9 @@ void LLToolDragAndDrop::dropTextureOneFace(LLViewerObject* hit_obj, LLTextureEntry* tep = hit_obj->getTE(hit_face); - LLPanelFace* panel_face = gFloaterTools->getPanelFace(); + LLPanelFace* panel_face = gFloaterTools ? gFloaterTools->getPanelFace() : nullptr; - if (gFloaterTools->getVisible() && panel_face) + if (panel_face && gFloaterTools->getVisible()) { tex_channel = (tex_channel > -1) ? tex_channel : panel_face->getTextureDropChannel(); switch (tex_channel) @@ -1611,7 +1612,10 @@ void LLToolDragAndDrop::dropScript(LLViewerObject* hit_obj, } } hit_obj->saveScript(new_script, active, true); - gFloaterTools->dirty(); + if (gFloaterTools) + { + gFloaterTools->dirty(); + } // VEFFECT: SetScript LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); @@ -1844,7 +1848,10 @@ void LLToolDragAndDrop::dropInventory(LLViewerObject* hit_obj, effectp->setTargetObject(hit_obj); effectp->setDuration(LL_HUD_DUR_SHORT); effectp->setColor(LLColor4U(gAgent.getEffectColor())); - gFloaterTools->dirty(); + if (gFloaterTools) + { + gFloaterTools->dirty(); + } } // accessor that looks at permissions, copyability, and names of diff --git a/indra/newview/lltoolplacer.cpp b/indra/newview/lltoolplacer.cpp index b15bb5efd5..0d141d7545 100644 --- a/indra/newview/lltoolplacer.cpp +++ b/indra/newview/lltoolplacer.cpp @@ -527,7 +527,10 @@ bool LLToolPlacer::handleHover(S32 x, S32 y, MASK mask) void LLToolPlacer::handleSelect() { - gFloaterTools->setStatusText("place"); + if (gFloaterTools) + { + gFloaterTools->setStatusText("place"); + } } void LLToolPlacer::handleDeselect() diff --git a/indra/newview/lltoolselectland.cpp b/indra/newview/lltoolselectland.cpp index 88553c7557..331581fd88 100644 --- a/indra/newview/lltoolselectland.cpp +++ b/indra/newview/lltoolselectland.cpp @@ -207,7 +207,10 @@ void LLToolSelectLand::render() void LLToolSelectLand::handleSelect() { - gFloaterTools->setStatusText("selectland"); + if (gFloaterTools) + { + gFloaterTools->setStatusText("selectland"); + } } diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index bdae400f1d..1f8f960300 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -291,6 +291,8 @@ void force_error_software_exception(); void force_error_os_exception(); void force_error_driver_crash(); void force_error_coroutine_crash(); +void force_error_coroprocedure_crash(); +void force_error_work_queue_crash(); void force_error_thread_crash(); void handle_force_delete(); @@ -2646,6 +2648,24 @@ class LLAdvancedForceErrorCoroutineCrash : public view_listener_t } }; +class LLAdvancedForceErrorCoroprocedureCrash : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + force_error_coroprocedure_crash(); + return true; + } +}; + +class LLAdvancedForceErrorWorkQueueCrash : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + force_error_work_queue_crash(); + return true; + } +}; + class LLAdvancedForceErrorThreadCrash : public view_listener_t { bool handleEvent(const LLSD& userdata) @@ -3169,7 +3189,11 @@ void handle_object_edit() LLFloaterReg::showInstance("build"); LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); - gFloaterTools->setEditTool( LLToolCompTranslate::getInstance() ); + + if (gFloaterTools) + { + gFloaterTools->setEditTool( LLToolCompTranslate::getInstance() ); + } LLViewerJoystick::getInstance()->moveObjects(true); LLViewerJoystick::getInstance()->setNeedsReset(true); @@ -8771,6 +8795,16 @@ void force_error_coroutine_crash() LLAppViewer::instance()->forceErrorCoroutineCrash(); } +void force_error_coroprocedure_crash() +{ + LLAppViewer::instance()->forceErrorCoroprocedureCrash(); +} + +void force_error_work_queue_crash() +{ + LLAppViewer::instance()->forceErrorWorkQueueCrash(); +} + void force_error_thread_crash() { LLAppViewer::instance()->forceErrorThreadCrash(); @@ -9978,6 +10012,8 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedForceErrorSoftwareExceptionCoro(), "Advanced.ForceErrorSoftwareExceptionCoro"); view_listener_t::addMenu(new LLAdvancedForceErrorDriverCrash(), "Advanced.ForceErrorDriverCrash"); view_listener_t::addMenu(new LLAdvancedForceErrorCoroutineCrash(), "Advanced.ForceErrorCoroutineCrash"); + view_listener_t::addMenu(new LLAdvancedForceErrorCoroprocedureCrash(), "Advanced.ForceErrorCoroprocedureCrash"); + view_listener_t::addMenu(new LLAdvancedForceErrorWorkQueueCrash(), "Advanced.ForceErrorWorkQueueCrash"); view_listener_t::addMenu(new LLAdvancedForceErrorThreadCrash(), "Advanced.ForceErrorThreadCrash"); view_listener_t::addMenu(new LLAdvancedForceErrorDisconnectViewer(), "Advanced.ForceErrorDisconnectViewer"); diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 1d4828fd33..5fd820f91d 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -3147,7 +3147,9 @@ void send_agent_update(bool force_send, bool send_reliable) LL_PROFILE_ZONE_SCOPED; llassert(!gCubeSnapshot); - if (gAgent.getTeleportState() != LLAgent::TELEPORT_NONE) + LLAgent::ETeleportState tp_state = gAgent.getTeleportState(); + if (tp_state != LLAgent::TELEPORT_NONE + && tp_state != LLAgent::TELEPORT_ARRIVING) { // We don't care if they want to send an agent update, they're not allowed // until the target simulator is ready to receive them @@ -3322,7 +3324,36 @@ void send_agent_update(bool force_send, bool send_reliable) msg->addVector3Fast(_PREHASH_CameraAtAxis, camera_at); msg->addVector3Fast(_PREHASH_CameraLeftAxis, LLViewerCamera::getInstance()->getLeftAxis()); msg->addVector3Fast(_PREHASH_CameraUpAxis, LLViewerCamera::getInstance()->getUpAxis()); - msg->addF32Fast(_PREHASH_Far, gAgentCamera.mDrawDistance); + + static F32 last_draw_disatance_step = 1024; + if (tp_state == LLAgent::TELEPORT_ARRIVING || LLStartUp::getStartupState() < STATE_MISC) + { + // Inform interest list, prioritize closer area. + // Reason: currently server doesn't distance sort attachments, by restricting range + // we reduce the number of attachments sent to the viewer, thus prioritizing + // closer ones. + // Todo: revise and remove once server gets distance sorting. + last_draw_disatance_step = llmax((F32)(gAgentCamera.mDrawDistance / 2.f), 50.f); + msg->addF32Fast(_PREHASH_Far, last_draw_disatance_step); + } + else if (last_draw_disatance_step < gAgentCamera.mDrawDistance) + { + static LLFrameTimer last_step_time; + if (last_step_time.getElapsedTimeF32() > 1.f) + { + // gradually increase draw distance + // Idealy this should be not per second, but based on how loaded + // mesh thread is, but hopefully this is temporary. + last_step_time.reset(); + F32 step = gAgentCamera.mDrawDistance * 0.1f; + last_draw_disatance_step = llmin(last_draw_disatance_step + step, gAgentCamera.mDrawDistance); + } + msg->addF32Fast(_PREHASH_Far, last_draw_disatance_step); + } + else + { + msg->addF32Fast(_PREHASH_Far, gAgentCamera.mDrawDistance); + } msg->addU32Fast(_PREHASH_ControlFlags, control_flags); diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 41b2c4b44b..8aa9bebb74 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -1290,7 +1290,7 @@ U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys, #endif //clear cost and linkset cost setObjectCostStale(); - if (isSelected()) + if (isSelected() && gFloaterTools) { gFloaterTools->dirty(); } @@ -1729,7 +1729,7 @@ U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys, #endif setObjectCostStale(); - if (isSelected()) + if (isSelected() && gFloaterTools) { gFloaterTools->dirty(); } @@ -3885,7 +3885,7 @@ void LLViewerObject::setObjectCost(F32 cost) mObjectCost = cost; mCostStale = false; - if (isSelected()) + if (isSelected() && gFloaterTools) { gFloaterTools->dirty(); } @@ -3905,7 +3905,7 @@ void LLViewerObject::setLinksetCost(F32 cost) iter++; } - if (needs_refresh) + if (needs_refresh && gFloaterTools) { gFloaterTools->dirty(); } @@ -3916,7 +3916,7 @@ void LLViewerObject::setPhysicsCost(F32 cost) mPhysicsCost = cost; mCostStale = false; - if (isSelected()) + if (isSelected() && gFloaterTools) { gFloaterTools->dirty(); } @@ -3927,7 +3927,7 @@ void LLViewerObject::setLinksetPhysicsCost(F32 cost) mLinksetPhysicsCost = cost; mCostStale = false; - if (isSelected()) + if (isSelected() && gFloaterTools) { gFloaterTools->dirty(); } diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h index 9c9d2f62cf..63458e60ea 100644 --- a/indra/newview/llviewerobject.h +++ b/indra/newview/llviewerobject.h @@ -759,6 +759,7 @@ public: // Associated GLTF Asset std::shared_ptr<LL::GLTF::Asset> mGLTFAsset; + bool mIsGLTFAssetMissing = false; // Pipeline classes LLPointer<LLDrawable> mDrawable; diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 609ad38e96..a0723db479 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -489,7 +489,12 @@ void LLViewerTexture::updateClass() } LLViewerMediaTexture::updateClass(); - + // This is a divisor used to determine how much VRAM from our overall VRAM budget to use. + // This is **cumulative** on whatever the detected or manually set VRAM budget is. + // If we detect 2048MB of VRAM, this will, by default, only use 1024. + // If you set 1024MB of VRAM, this will, by default, use 512. + // -Geenz 2025-03-03 + static LLCachedControl<U32> tex_vram_divisor(gSavedSettings, "RenderTextureVRAMDivisor", 2); static LLCachedControl<U32> max_vram_budget(gSavedSettings, "RenderMaxVRAMBudget", 0); F64 texture_bytes_alloc = LLImageGL::getTextureBytesAllocated() / 1024.0 / 512.0; @@ -500,6 +505,7 @@ void LLViewerTexture::updateClass() F32 used = (F32)ll_round(texture_bytes_alloc + vertex_bytes_alloc); F32 budget = max_vram_budget == 0 ? (F32)gGLManager.mVRAM : (F32)max_vram_budget; + budget /= tex_vram_divisor; // Try to leave at least half a GB for everyone else and for bias, // but keep at least 768MB for ourselves diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index a4a001eceb..6b4878e7fa 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -30,6 +30,7 @@ #include "llviewertexturelist.h" +#include "llagent.h" #include "llgl.h" // fot gathering stats from GL #include "llimagegl.h" #include "llimagebmp.h" @@ -825,10 +826,19 @@ void LLViewerTextureList::updateImages(F32 max_time) clearFetchingRequests(); gPipeline.clearRebuildGroups(); cleared = true; + return; } - return; + // ARRIVING is a delay to let things decode, cache and process, + // so process textures like normal despite gTeleportDisplay + if (gAgent.getTeleportState() != LLAgent::TELEPORT_ARRIVING) + { + return; + } + } + else + { + cleared = false; } - cleared = false; LLAppViewer::getTextureFetch()->setTextureBandwidth((F32)LLTrace::get_frame_recording().getPeriodMeanPerSec(LLStatViewer::TEXTURE_NETWORK_DATA_RECEIVED).value()); @@ -901,7 +911,7 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag if (imagep->getBoostLevel() < LLViewerFetchedTexture::BOOST_HIGH) // don't bother checking face list for boosted textures { - static LLCachedControl<F32> texture_scale_min(gSavedSettings, "TextureScaleMinAreaFactor", 0.04f); + static LLCachedControl<F32> texture_scale_min(gSavedSettings, "TextureScaleMinAreaFactor", 0.0095f); static LLCachedControl<F32> texture_scale_max(gSavedSettings, "TextureScaleMaxAreaFactor", 25.f); F32 max_vsize = 0.f; @@ -943,8 +953,9 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag // Scale desired texture resolution higher or lower depending on texture scale // - // Minimum usage examples: a 1024x1024 texture with aplhabet, runing string - // shows one letter at a time + // Minimum usage examples: a 1024x1024 texture with aplhabet (texture atlas), + // runing string shows one letter at a time. If texture has ten 100px symbols + // per side, minimal scale is (100/1024)^2 = 0.0095 // // Maximum usage examples: huge chunk of terrain repeats texture // TODO: make this work with the GLTF texture transforms diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 82a67d50dc..2a71cc05c1 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -6180,7 +6180,7 @@ void LLPickInfo::fetchResults() mObjectOffset = gAgentCamera.calcFocusOffset(objectp, v_intersection, mPickPt.mX, mPickPt.mY); mObjectID = objectp->mID; mObjectFace = (te_offset == NO_FACE) ? -1 : (S32)te_offset; - + mPickHUD = objectp->isHUDAttachment(); mPosGlobal = gAgent.getPosGlobalFromAgent(v_intersection); diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h index fbc2c58fbf..ac0dfa3fe4 100644 --- a/indra/newview/llviewerwindow.h +++ b/indra/newview/llviewerwindow.h @@ -133,6 +133,7 @@ public: bool mPickParticle; bool mPickUnselectable; bool mPickReflectionProbe = false; + bool mPickHUD{ false }; void getSurfaceInfo(); private: diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index f5dfcca873..1cca2161fe 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -683,7 +683,7 @@ LLVOAvatar::LLVOAvatar(const LLUUID& id, mFullyLoaded(false), mPreviousFullyLoaded(false), mFullyLoadedInitialized(false), - mLastCloudAttachmentCount(0), + mLastCloudAttachmentCount(-1), mVisualComplexity(VISUAL_COMPLEXITY_UNKNOWN), mLoadedCallbacksPaused(false), mLoadedCallbackTextures(0), @@ -2837,7 +2837,10 @@ void LLVOAvatar::idleUpdate(LLAgent &agent, const F64 &time) } // attach objects that were waiting for a drawable - lazyAttach(); + if (!mPendingAttachment.empty()) + { + lazyAttach(); + } // animate the character // store off last frame's root position to be consistent with camera position @@ -3269,6 +3272,7 @@ void LLVOAvatar::idleUpdateLoadingEffect() if (mFirstFullyVisible) { mFirstFullyVisible = false; + mLastCloudAttachmentCount = (S32)mSimAttachments.size(); mFirstDecloudTime = mFirstAppearanceMessageTimer.getElapsedTimeF32(); if (isSelf()) { @@ -7753,6 +7757,64 @@ void LLVOAvatar::cleanupAttachedMesh( LLViewerObject* pVO ) } } +bool check_object_for_mesh_loading(LLViewerObject* objectp) +{ + if (!objectp || !objectp->getVolume()) + { + return false; + } + LLVolume* volp = objectp->getVolume(); + const LLUUID& mesh_id = volp->getParams().getSculptID(); + if (mesh_id.isNull()) + { + // No mesh nor skin info needed + return false; + } + + if (volp->isMeshAssetUnavaliable()) + { + // Mesh failed to load, do not expect it + return false; + } + + if (!objectp->mDrawable) + { + return false; + } + + LLVOVolume* pvobj = objectp->mDrawable->getVOVolume(); + if (pvobj) + { + if (!pvobj->isMesh()) + { + // Not a mesh + return false; + } + + if (!volp->isMeshAssetLoaded()) + { + // Waiting for mesh + return true; + } + + const LLMeshSkinInfo* skin_data = pvobj->getSkinInfo(); + if (skin_data) + { + // Skin info present, done + return false; + } + + if (pvobj->isSkinInfoUnavaliable()) + { + // Load failed or info not present, don't expect it + return false; + } + } + + // object is not ready + return true; +} + bool LLVOAvatar::hasPendingAttachedMeshes() { for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); @@ -7767,62 +7829,20 @@ bool LLVOAvatar::hasPendingAttachedMeshes() ++attachment_iter) { LLViewerObject* objectp = attachment_iter->get(); - if (objectp) + if (objectp && !objectp->isDead()) { + if (check_object_for_mesh_loading(objectp)) + { + return true; + } LLViewerObject::const_child_list_t& child_list = objectp->getChildren(); for (LLViewerObject::child_list_t::const_iterator iter1 = child_list.begin(); iter1 != child_list.end(); ++iter1) { LLViewerObject* objectchild = *iter1; - if (objectchild && objectchild->getVolume()) + if (check_object_for_mesh_loading(objectchild)) { - const LLUUID& mesh_id = objectchild->getVolume()->getParams().getSculptID(); - if (mesh_id.isNull()) - { - // No mesh nor skin info needed - continue; - } - - if (objectchild->getVolume()->isMeshAssetUnavaliable()) - { - // Mesh failed to load, do not expect it - continue; - } - - if (objectchild->mDrawable) - { - LLVOVolume* pvobj = objectchild->mDrawable->getVOVolume(); - if (pvobj) - { - if (!pvobj->isMesh()) - { - // Not a mesh - continue; - } - - if (!objectchild->getVolume()->isMeshAssetLoaded()) - { - // Waiting for mesh - return true; - } - - const LLMeshSkinInfo* skin_data = pvobj->getSkinInfo(); - if (skin_data) - { - // Skin info present, done - continue; - } - - if (pvobj->isSkinInfoUnavaliable()) - { - // Load failed or info not present, don't expect it - continue; - } - } - - // objectchild is not ready - return true; - } + return true; } } } @@ -8425,7 +8445,7 @@ bool LLVOAvatar::updateIsFullyLoaded() ); // compare amount of attachments to one reported by simulator - if (!loading && !isSelf() && rez_status < 4 && mLastCloudAttachmentCount < mSimAttachments.size()) + if (!isSelf() && mLastCloudAttachmentCount < mSimAttachments.size() && mSimAttachments.size() > 0) { S32 attachment_count = getAttachmentCount(); if (mLastCloudAttachmentCount != attachment_count) @@ -8443,6 +8463,11 @@ bool LLVOAvatar::updateIsFullyLoaded() // waiting loading = true; } + else if (!loading) + { + // for hasFirstFullAttachmentData + mLastCloudAttachmentCount = (S32)mSimAttachments.size(); + } } } updateRezzedStatusTimers(rez_status); @@ -8556,6 +8581,12 @@ bool LLVOAvatar::isFullyLoaded() const return (mRenderUnloadedAvatar || mFullyLoaded); } +bool LLVOAvatar::hasFirstFullAttachmentData() const +{ + return !mFirstFullyVisible // Avatar is fully visible, have all data + || mLastCloudAttachmentCount >= (S32)mSimAttachments.size(); +} + bool LLVOAvatar::isTooComplex() const { bool too_complex; diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index dd1725c322..263c3dadf6 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -385,6 +385,7 @@ public: //-------------------------------------------------------------------- public: bool isFullyLoaded() const; + bool hasFirstFullAttachmentData() const; F32 getFirstDecloudTime() const {return mFirstDecloudTime;} // check and return current state relative to limits diff --git a/indra/newview/llvosky.cpp b/indra/newview/llvosky.cpp index ab8d0d2564..38c702c4ca 100644 --- a/indra/newview/llvosky.cpp +++ b/indra/newview/llvosky.cpp @@ -100,8 +100,16 @@ LLSkyTex::LLSkyTex() : void LLSkyTex::init(bool isShiny) { mIsShiny = isShiny; - mSkyData = new LLColor4[(U32)(SKYTEX_RESOLUTION * SKYTEX_RESOLUTION)]; - mSkyDirs = new LLVector3[(U32)(SKYTEX_RESOLUTION * SKYTEX_RESOLUTION)]; + try + { + mSkyData = new LLColor4[(U32)(SKYTEX_RESOLUTION * SKYTEX_RESOLUTION)]; + mSkyDirs = new LLVector3[(U32)(SKYTEX_RESOLUTION * SKYTEX_RESOLUTION)]; + } + catch (std::bad_alloc&) + { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS() << "Failed to allocate memory for sky texture data" << LL_ENDL; + } for (S32 i = 0; i < 2; ++i) { diff --git a/indra/newview/llworld.cpp b/indra/newview/llworld.cpp index e0d8c25731..899733ccc3 100644 --- a/indra/newview/llworld.cpp +++ b/indra/newview/llworld.cpp @@ -838,6 +838,7 @@ void LLWorld::printPacketsLost() << " packets lost: " << cdp->getPacketsLost() << LL_ENDL; } } + LL_INFOS() << "Packets dropped by Packet Ring: " << gMessageSystem->mPacketRing.getNumDroppedPackets() << LL_ENDL; } void LLWorld::processCoarseUpdate(LLMessageSystem* msg, void** user_data) diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index e441e189ad..b40c9fa6db 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -1412,7 +1412,18 @@ void LLPipeline::createLUTBuffers() { U32 lightResX = gSavedSettings.getU32("RenderSpecularResX"); U32 lightResY = gSavedSettings.getU32("RenderSpecularResY"); - F32* ls = new F32[lightResX*lightResY]; + F32* ls = nullptr; + try + { + ls = new F32[lightResX*lightResY]; + } + catch (std::bad_alloc&) + { + LLError::LLUserWarningMsg::showOutOfMemory(); + // might be better to set the error into mFatalMessage and rethrow + LL_ERRS() << "Bad memory allocation in createLUTBuffers! lightResX: " + << lightResX << " lightResY: " << lightResY << LL_ENDL; + } F32 specExp = gSavedSettings.getF32("RenderSpecularExponent"); // Calculate the (normalized) blinn-phong specular lookup texture. (with a few tweaks) for (U32 y = 0; y < lightResY; ++y) @@ -7239,7 +7250,7 @@ void LLPipeline::tonemap(LLRenderTarget* src, LLRenderTarget* dst) LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - bool no_post = gSnapshotNoPost || psky->getReflectionProbeAmbiance(should_auto_adjust) == 0.f || (buildNoPost && gFloaterTools->isAvailable()); + bool no_post = gSnapshotNoPost || psky->getReflectionProbeAmbiance(should_auto_adjust) == 0.f || (buildNoPost && gFloaterTools && gFloaterTools->isAvailable()); LLGLSLShader& shader = no_post ? gNoPostTonemapProgram : gDeferredPostTonemapProgram; shader.bind(); diff --git a/indra/newview/skins/default/xui/da/floater_avatar_textures.xml b/indra/newview/skins/default/xui/da/floater_avatar_textures.xml index d0d766eaab..e7fe66c73f 100644 --- a/indra/newview/skins/default/xui/da/floater_avatar_textures.xml +++ b/indra/newview/skins/default/xui/da/floater_avatar_textures.xml @@ -14,7 +14,7 @@ teksturer teksturer </text> <button label="Vis IDs på skærm" label_selected="Dump" name="Dump"/> - <panel name="scroll_content_panel"> + <panel name="scroll_content_panel2"> <texture_picker label="Hår" name="hair-baked"/> <texture_picker label="Hår" name="hair_grain"/> <texture_picker label="Alpha - hår" name="hair_alpha"/> diff --git a/indra/newview/skins/default/xui/de/floater_avatar_textures.xml b/indra/newview/skins/default/xui/de/floater_avatar_textures.xml index aed9aa6e78..915ef3067f 100644 --- a/indra/newview/skins/default/xui/de/floater_avatar_textures.xml +++ b/indra/newview/skins/default/xui/de/floater_avatar_textures.xml @@ -8,7 +8,7 @@ Texturen</text> <text name="composite_label">Zusammengesetzte Texturen</text> <button label="IDs an Konsole ausgeben" label_selected="Abladen" name="Dump"/> - <panel name="scroll_content_panel"> + <panel name="scroll_content_panel2"> <texture_picker label="Haare" name="hair-baked"/> <texture_picker label="Haare" name="hair_grain"/> <texture_picker label="Alpha: Haare" name="hair_alpha"/> diff --git a/indra/newview/skins/default/xui/en/floater_avatar_textures.xml b/indra/newview/skins/default/xui/en/floater_avatar_textures.xml index 09026884b9..831368f4de 100644 --- a/indra/newview/skins/default/xui/en/floater_avatar_textures.xml +++ b/indra/newview/skins/default/xui/en/floater_avatar_textures.xml @@ -70,13 +70,13 @@ Textures width="150" /> <panel - name="scroll_content_panel" + name="scroll_content_panel2" follows="left|top" min_height="300" layout="topleft" - top="43" + top="60" background_visible="false" - height="930" + height="1113" left="0" width="1230"> @@ -86,7 +86,7 @@ Textures layout="topleft" left="10" name="hair-baked" - top="17" + top="3" width="92" /> <texture_picker height="103" diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 607c7698c3..15dacddb80 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -2811,7 +2811,7 @@ function="World.EnvPreset" function="Advanced.ForceErrorOSException" /> <menu_item_call.on_visible function="Advanced.EnableErrorOSException" /> - </menu_item_call> + </menu_item_call> <menu_item_call label="Force a Crash in a Coroutine" name="Force a Crash in a Coroutine"> @@ -2819,6 +2819,18 @@ function="World.EnvPreset" function="Advanced.ForceErrorCoroutineCrash" /> </menu_item_call> <menu_item_call + label="Force a Crash in a Coroprocedure" + name="Force a Crash in a Coroprocedure"> + <menu_item_call.on_click + function="Advanced.ForceErrorCoroprocedureCrash" /> + </menu_item_call> + <menu_item_call + label="Force a Crash in a Work Queue" + name="Force a Crash in a Work Queue"> + <menu_item_call.on_click + function="Advanced.ForceErrorWorkQueueCrash" /> + </menu_item_call> + <menu_item_call label="Force a Crash in a Thread" name="Force a Crash in a Thread"> <menu_item_call.on_click diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index b035360206..4439e476a2 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -12592,4 +12592,16 @@ Select the "use as favorite folder" from a folder's menu to set it as the favori name="okbutton" yestext="OK"/> </notification> + + <notification + icon="alertmodal.tga" + name="WaterExclusionSurfacesWarning" + type="alertmodal"> + Checking the hide water box will overwrite the texture, bumpiness, and shininess choices. + <tag>confirm</tag> + <usetemplate + name="okcancelbuttons" + notext="Cancel" + yestext="Continue"/> + </notification> </notifications> diff --git a/indra/newview/skins/default/xui/en/panel_tools_texture.xml b/indra/newview/skins/default/xui/en/panel_tools_texture.xml index e80f315259..9a19d06432 100644 --- a/indra/newview/skins/default/xui/en/panel_tools_texture.xml +++ b/indra/newview/skins/default/xui/en/panel_tools_texture.xml @@ -123,6 +123,14 @@ name="checkbox fullbright" top_pad="4" width="81" /> + <check_box + height="19" + label="Hide water" + layout="topleft" + left="172" + top_delta="0" + name="checkbox_hide_water" + width="81" /> <view_border bevel_style="none" follows="top|left" diff --git a/indra/newview/skins/default/xui/es/floater_avatar_textures.xml b/indra/newview/skins/default/xui/es/floater_avatar_textures.xml index 0a52a70ff1..b043662975 100644 --- a/indra/newview/skins/default/xui/es/floater_avatar_textures.xml +++ b/indra/newview/skins/default/xui/es/floater_avatar_textures.xml @@ -6,7 +6,7 @@ <text name="label">Texturas obtenidas mediante bake</text> <text name="composite_label">Texturas compuestas</text> <button label="Volcar IDs a la consola" label_selected="Volcado" name="Dump"/> - <panel name="scroll_content_panel"> + <panel name="scroll_content_panel2"> <texture_picker label="Pelo" name="hair-baked"/> <texture_picker label="Pelo" name="hair_grain"/> <texture_picker label="Alfa del pelo" name="hair_alpha"/> diff --git a/indra/newview/skins/default/xui/fr/floater_avatar_textures.xml b/indra/newview/skins/default/xui/fr/floater_avatar_textures.xml index a1f6d85836..e910632e50 100644 --- a/indra/newview/skins/default/xui/fr/floater_avatar_textures.xml +++ b/indra/newview/skins/default/xui/fr/floater_avatar_textures.xml @@ -6,7 +6,7 @@ <text name="label">Textures figées</text> <text name="composite_label">Textures composites</text> <button label="Vider les ID dans la console" label_selected="Vider" name="Dump"/> - <panel name="scroll_content_panel"> + <panel name="scroll_content_panel2"> <texture_picker label="Cheveux" name="hair-baked"/> <texture_picker label="Cheveux" name="hair_grain"/> <texture_picker label="Alpha cheveux" name="hair_alpha"/> diff --git a/indra/newview/skins/default/xui/it/floater_avatar_textures.xml b/indra/newview/skins/default/xui/it/floater_avatar_textures.xml index 288b0a2fea..c0e56805f3 100644 --- a/indra/newview/skins/default/xui/it/floater_avatar_textures.xml +++ b/indra/newview/skins/default/xui/it/floater_avatar_textures.xml @@ -8,7 +8,7 @@ texture</text> <text name="composite_label">Composito Texture</text> <button label="Memorizza gli ID sulla console" label_selected="Dump" name="Dump"/> - <panel name="scroll_content_panel"> + <panel name="scroll_content_panel2"> <texture_picker label="Capigliature" name="hair-baked"/> <texture_picker label="Capigliature" name="hair_grain"/> <texture_picker label="Alpha dei capelli" name="hair_alpha"/> diff --git a/indra/newview/skins/default/xui/ja/floater_avatar_textures.xml b/indra/newview/skins/default/xui/ja/floater_avatar_textures.xml index e71ed5c599..5de3486ea1 100644 --- a/indra/newview/skins/default/xui/ja/floater_avatar_textures.xml +++ b/indra/newview/skins/default/xui/ja/floater_avatar_textures.xml @@ -13,7 +13,7 @@ 合成テクスチャ </text> <button label="IDをコンソールにダンプ" label_selected="ダンプ" name="Dump"/> - <panel name="scroll_content_panel"> + <panel name="scroll_content_panel2"> <texture_picker label="髪" name="hair-baked"/> <texture_picker label="髪" name="hair_grain"/> <texture_picker label="髪のアルファ" name="hair_alpha"/> diff --git a/indra/newview/skins/default/xui/pl/floater_avatar_textures.xml b/indra/newview/skins/default/xui/pl/floater_avatar_textures.xml index 18f5a40ea5..ee5e4d277c 100644 --- a/indra/newview/skins/default/xui/pl/floater_avatar_textures.xml +++ b/indra/newview/skins/default/xui/pl/floater_avatar_textures.xml @@ -14,7 +14,7 @@ prerenderowane kompozytowe </text> <button label="Zrzuć ID do Konsoli" label_selected="Zrzuć" name="Dump" /> - <panel name="scroll_content_panel"> + <panel name="scroll_content_panel2"> <texture_picker label="Włosy" name="hair-baked" /> <texture_picker label="Włosy" name="hair_grain" /> <texture_picker label="Alpha włosów" name="hair_alpha" /> diff --git a/indra/newview/skins/default/xui/pt/floater_avatar_textures.xml b/indra/newview/skins/default/xui/pt/floater_avatar_textures.xml index 6cdf66cb14..3a0c2febd7 100644 --- a/indra/newview/skins/default/xui/pt/floater_avatar_textures.xml +++ b/indra/newview/skins/default/xui/pt/floater_avatar_textures.xml @@ -8,7 +8,7 @@ Texturas</text> <text name="composite_label">Compósito: Texturas</text> <button label="Enviar IDs para painel" label_selected="Dump" name="Dump"/> - <panel name="scroll_content_panel"> + <panel name="scroll_content_panel2"> <texture_picker label="Cabelo" name="hair-baked"/> <texture_picker label="Cabelo" name="hair_grain"/> <texture_picker label="Cabelo alpha" name="hair_alpha"/> diff --git a/indra/newview/skins/default/xui/ru/floater_avatar_textures.xml b/indra/newview/skins/default/xui/ru/floater_avatar_textures.xml index cb39f66247..b8906eebbd 100644 --- a/indra/newview/skins/default/xui/ru/floater_avatar_textures.xml +++ b/indra/newview/skins/default/xui/ru/floater_avatar_textures.xml @@ -14,7 +14,7 @@ Текстуры </text> <button label="Вывод ID на консоль" label_selected="Вывод" name="Dump"/> - <panel name="scroll_content_panel"> + <panel name="scroll_content_panel2"> <texture_picker label="Волосы" name="hair-baked"/> <texture_picker label="Волосы" name="hair_grain"/> <texture_picker label="Альфа волос" name="hair_alpha"/> diff --git a/indra/newview/skins/default/xui/tr/floater_avatar_textures.xml b/indra/newview/skins/default/xui/tr/floater_avatar_textures.xml index b519cfa1a9..2dabab2bca 100644 --- a/indra/newview/skins/default/xui/tr/floater_avatar_textures.xml +++ b/indra/newview/skins/default/xui/tr/floater_avatar_textures.xml @@ -14,7 +14,7 @@ Dokular Dokular </text> <button label="Kimlikleri Konsole Yığ" label_selected="Yığ" name="Dump"/> - <panel name="scroll_content_panel"> + <panel name="scroll_content_panel2"> <texture_picker label="Saç" name="hair-baked"/> <texture_picker label="Saç" name="hair_grain"/> <texture_picker label="Saç Alfası" name="hair_alpha"/> diff --git a/indra/newview/skins/default/xui/zh/floater_avatar_textures.xml b/indra/newview/skins/default/xui/zh/floater_avatar_textures.xml index 69309c96de..260216fb15 100644 --- a/indra/newview/skins/default/xui/zh/floater_avatar_textures.xml +++ b/indra/newview/skins/default/xui/zh/floater_avatar_textures.xml @@ -14,7 +14,7 @@ 材質 </text> <button label="傾印 ID 到控制臺" label_selected="傾印" name="Dump"/> - <panel name="scroll_content_panel"> + <panel name="scroll_content_panel2"> <texture_picker label="頭髮" name="hair-baked"/> <texture_picker label="頭髮" name="hair_grain"/> <texture_picker label="頭髮半透明" name="hair_alpha"/> |