diff --git a/autobuild.xml b/autobuild.xml index 2e4a47a0bf..aeed17e424 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -656,9 +656,9 @@ archive hash - 51d9ce98279709854b0be5d0f450ba63 + 96dd770f246917589b776300a2d07f9e url - http://3p.firestormviewer.org/curl-7.54.1.180841943-linux64-180841943.tar.bz2 + http://3p.firestormviewer.org/curl-7.54.1.212891029-linux64-212891029.tar.bz2 name linux64 @@ -2968,9 +2968,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 7920fce93d9addf63a420d86f91c5749 + ea82e634334bccf088daf3d15eab07b7 url - http://3p.firestormviewer.org/openssl-1.0.2l.180841936-linux64-180841936.tar.bz2 + http://3p.firestormviewer.org/openssl-1.1.1l.212872015-linux64-212872015.tar.bz2 name linux64 @@ -3378,9 +3378,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - b639d0035f4a8c9b4973be428a1b7e61 + 5e553a4358203f283c74744aed2fcd8c url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/69569/671323/vlc_bin-3.0.9.549888-darwin64-549888.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54836/510036/vlc_bin-2.2.8.538966-darwin64-538966.tar.bz2 name darwin64 @@ -3414,9 +3414,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 4f50b0c47daa081dd4fcb83763d5b0b2 + ca84b7c5f86e702fb35727eed8f0c8c4 url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/69567/671314/vlc_bin-3.0.9.549888-windows-549888.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54958/511725/vlc_bin-2.2.8.538966-windows-538966.tar.bz2 name windows @@ -3426,16 +3426,16 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - c2f8c01fb6c261b72beb07f0c4cd423f + 93cd88d90cb8aedbed5cd90ff9262409 url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/69568/671315/vlc_bin-3.0.9.549888-windows64-549888.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/54954/511718/vlc_bin-2.2.8.538966-windows64-538966.tar.bz2 name windows64 version - 3.0.9.549888 + 2.2.8.538966 xmlrpc-epi diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake index cbaa8c6c1c..6909a94e4b 100644 --- a/indra/cmake/00-Common.cmake +++ b/indra/cmake/00-Common.cmake @@ -165,6 +165,11 @@ endif (WINDOWS) if (LINUX) set(CMAKE_SKIP_RPATH TRUE) + + if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 10.0.0 ) + message( FATAL_ERROR "GCC greater 9.4.0 is not supported. Recompile boost for support of GCC 10.0.0 and up." ) + endif() + # # And another hack for FORTIFY_SOURCE. Some distributions (for example Gentoo) define FORTIFY_SOURCE by default. # Check if this is the case, if yes, do not define it again. diff --git a/indra/cmake/OpenSSL.cmake b/indra/cmake/OpenSSL.cmake index 32400f5e4e..f43ba7c26e 100644 --- a/indra/cmake/OpenSSL.cmake +++ b/indra/cmake/OpenSSL.cmake @@ -17,7 +17,7 @@ else (USESYSTEMLIBS) endif (USESYSTEMLIBS) if (LINUX) - set(CRYPTO_LIBRARIES crypto dl) + set(CRYPTO_LIBRARIES crypto dl pthread) elseif (DARWIN) set(CRYPTO_LIBRARIES crypto) endif (LINUX) diff --git a/indra/llaudio/llstreamingaudio_fmodstudio.cpp b/indra/llaudio/llstreamingaudio_fmodstudio.cpp index ff7dcbd995..7ba65c4cec 100644 --- a/indra/llaudio/llstreamingaudio_fmodstudio.cpp +++ b/indra/llaudio/llstreamingaudio_fmodstudio.cpp @@ -72,7 +72,8 @@ mSystem(system), mCurrentInternetStreamp(NULL), mStreamGroup(NULL), mFMODInternetStreamChannelp(NULL), -mGain(1.0f) +mGain(1.0f), +mWasAlreadyPlaying(false) { // Number of milliseconds of audio to buffer for the audio card. // Must be larger than the usual Second Life frame stutter time. @@ -158,18 +159,14 @@ void LLStreamingAudio_FMODSTUDIO::update() if (Check_FMOD_Error(mCurrentInternetStreamp->getOpenState(open_state, &progress, &starving, &diskbusy), "FMOD::Sound::getOpenState")) { LL_WARNS() << "Internet stream openstate error: open_state = " << open_state << " - progress = " << progress << " - starving = " << starving << " - diskbusy = " << diskbusy << LL_ENDL; + bool was_playing = mWasAlreadyPlaying; stop(); - return; - } - else if (open_state == FMOD_OPENSTATE_ERROR) - { - // Actually we might not get into this case at all since according to the - // FMOD API doc, one should check the result of getOpenState for further - // details, which most likely means if open_state is FMOD_OPENSTATE_ERROR, - // calling getOpenState will return anything but FMOD_OK and we end up in - // the if-case above. - LL_WARNS() << "Internet stream openstate error: progress = " << progress << " - starving = " << starving << " - diskbusy = " << diskbusy << LL_ENDL; - stop(); + // Try to restart previously playing stream on socket error + if (open_state == FMOD_OPENSTATE_ERROR && was_playing) + { + LL_WARNS() << "Stream was playing before - trying to restart" << LL_ENDL; + start(mURL); + } return; } else if (open_state == FMOD_OPENSTATE_READY) @@ -183,6 +180,14 @@ void LLStreamingAudio_FMODSTUDIO::update() // Reset volume to previously set volume setGain(getGain()); Check_FMOD_Error(mFMODInternetStreamChannelp->setPaused(false), "FMOD::Channel::setPaused"); + mWasAlreadyPlaying = true; + } + } + else if (open_state == FMOD_OPENSTATE_PLAYING) + { + if (!mWasAlreadyPlaying) + { + mWasAlreadyPlaying = true; } } @@ -317,6 +322,7 @@ void LLStreamingAudio_FMODSTUDIO::update() void LLStreamingAudio_FMODSTUDIO::stop() { mPendingURL.clear(); + mWasAlreadyPlaying = false; if (mFMODInternetStreamChannelp) { diff --git a/indra/llaudio/llstreamingaudio_fmodstudio.h b/indra/llaudio/llstreamingaudio_fmodstudio.h index 7ec5a0dbf4..43e8ef9c81 100644 --- a/indra/llaudio/llstreamingaudio_fmodstudio.h +++ b/indra/llaudio/llstreamingaudio_fmodstudio.h @@ -80,6 +80,8 @@ private: bool mNewMetadata; LLSD mMetadata; // Streamtitle display + + bool mWasAlreadyPlaying; }; diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 2e79e487c1..02b1d38fa9 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -256,13 +256,14 @@ set(llcommon_HEADER_FILES StackWalker.h ) -# Add all nd* files. memory pool, intrinsics, ... -# Tracy Profiler support -list(APPEND llcommon_SOURCE_FILES fstelemetry.cpp) -if (USE_TRACY_PROFILER) - list(APPEND llcommon_SOURCE_FILES fstracyclient.cpp) -endif() - + # Tracy Profiler support + list(APPEND llcommon_SOURCE_FILES fstelemetry.cpp) + if (USE_TRACY_PROFILER) + list(APPEND llcommon_SOURCE_FILES fstracyclient.cpp) + endif() + # Tracy Profiler support + + # Add all nd* files. memory pool, intrinsics, ... SET( llcommon_ND_SOURCE_FILES nd/ndexceptions.cpp nd/ndlogthrottle.cpp diff --git a/indra/llcommon/blockingconcurrentqueue.h b/indra/llcommon/blockingconcurrentqueue.h new file mode 100644 index 0000000000..66579b6caf --- /dev/null +++ b/indra/llcommon/blockingconcurrentqueue.h @@ -0,0 +1,582 @@ +// Provides an efficient blocking version of moodycamel::ConcurrentQueue. +// ©2015-2020 Cameron Desrochers. Distributed under the terms of the simplified +// BSD license, available at the top of concurrentqueue.h. +// Also dual-licensed under the Boost Software License (see LICENSE.md) +// Uses Jeff Preshing's semaphore implementation (under the terms of its +// separate zlib license, see lightweightsemaphore.h). + +#pragma once + +#include "concurrentqueue.h" +#include "lightweightsemaphore.h" + +#include +#include +#include +#include +#include + +namespace moodycamel +{ +// This is a blocking version of the queue. It has an almost identical interface to +// the normal non-blocking version, with the addition of various wait_dequeue() methods +// and the removal of producer-specific dequeue methods. +template +class BlockingConcurrentQueue +{ +private: + typedef ::moodycamel::ConcurrentQueue ConcurrentQueue; + typedef ::moodycamel::LightweightSemaphore LightweightSemaphore; + +public: + typedef typename ConcurrentQueue::producer_token_t producer_token_t; + typedef typename ConcurrentQueue::consumer_token_t consumer_token_t; + + typedef typename ConcurrentQueue::index_t index_t; + typedef typename ConcurrentQueue::size_t size_t; + typedef typename std::make_signed::type ssize_t; + + static const size_t BLOCK_SIZE = ConcurrentQueue::BLOCK_SIZE; + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = ConcurrentQueue::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD; + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::EXPLICIT_INITIAL_INDEX_SIZE; + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::IMPLICIT_INITIAL_INDEX_SIZE; + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = ConcurrentQueue::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = ConcurrentQueue::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE; + static const size_t MAX_SUBQUEUE_SIZE = ConcurrentQueue::MAX_SUBQUEUE_SIZE; + +public: + // Creates a queue with at least `capacity` element slots; note that the + // actual number of elements that can be inserted without additional memory + // allocation depends on the number of producers and the block size (e.g. if + // the block size is equal to `capacity`, only a single block will be allocated + // up-front, which means only a single producer will be able to enqueue elements + // without an extra allocation -- blocks aren't shared between producers). + // This method is not thread safe -- it is up to the user to ensure that the + // queue is fully constructed before it starts being used by other threads (this + // includes making the memory effects of construction visible, possibly with a + // memory barrier). + explicit BlockingConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE) + : inner(capacity), sema(create(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy) + { + assert(reinterpret_cast((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member"); + if (!sema) { + MOODYCAMEL_THROW(std::bad_alloc()); + } + } + + BlockingConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) + : inner(minCapacity, maxExplicitProducers, maxImplicitProducers), sema(create(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy) + { + assert(reinterpret_cast((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member"); + if (!sema) { + MOODYCAMEL_THROW(std::bad_alloc()); + } + } + + // Disable copying and copy assignment + BlockingConcurrentQueue(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + BlockingConcurrentQueue& operator=(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + + // Moving is supported, but note that it is *not* a thread-safe operation. + // Nobody can use the queue while it's being moved, and the memory effects + // of that move must be propagated to other threads before they can use it. + // Note: When a queue is moved, its tokens are still valid but can only be + // used with the destination queue (i.e. semantically they are moved along + // with the queue itself). + BlockingConcurrentQueue(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + : inner(std::move(other.inner)), sema(std::move(other.sema)) + { } + + inline BlockingConcurrentQueue& operator=(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + { + return swap_internal(other); + } + + // Swaps this queue's state with the other's. Not thread-safe. + // Swapping two queues does not invalidate their tokens, however + // the tokens that were created for one queue must be used with + // only the swapped queue (i.e. the tokens are tied to the + // queue's movable state, not the object itself). + inline void swap(BlockingConcurrentQueue& other) MOODYCAMEL_NOEXCEPT + { + swap_internal(other); + } + +private: + BlockingConcurrentQueue& swap_internal(BlockingConcurrentQueue& other) + { + if (this == &other) { + return *this; + } + + inner.swap(other.inner); + sema.swap(other.sema); + return *this; + } + +public: + // Enqueues a single item (by copying it). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T const& item) + { + if ((details::likely)(inner.enqueue(item))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by moving it, if possible). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T&& item) + { + if ((details::likely)(inner.enqueue(std::move(item)))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T const& item) + { + if ((details::likely)(inner.enqueue(token, item))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T&& item) + { + if ((details::likely)(inner.enqueue(token, std::move(item)))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues several items. + // Allocates memory if required. Only fails if memory allocation fails (or + // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved instead of copied. + // Thread-safe. + template + inline bool enqueue_bulk(It itemFirst, size_t count) + { + if ((details::likely)(inner.enqueue_bulk(std::forward(itemFirst), count))) { + sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); + return true; + } + return false; + } + + // Enqueues several items using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails + // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + inline bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + if ((details::likely)(inner.enqueue_bulk(token, std::forward(itemFirst), count))) { + sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); + return true; + } + return false; + } + + // Enqueues a single item (by copying it). + // Does not allocate memory. Fails if not enough room to enqueue (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0). + // Thread-safe. + inline bool try_enqueue(T const& item) + { + if (inner.try_enqueue(item)) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by moving it, if possible). + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Thread-safe. + inline bool try_enqueue(T&& item) + { + if (inner.try_enqueue(std::move(item))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T const& item) + { + if (inner.try_enqueue(token, item)) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T&& item) + { + if (inner.try_enqueue(token, std::move(item))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues several items. + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + inline bool try_enqueue_bulk(It itemFirst, size_t count) + { + if (inner.try_enqueue_bulk(std::forward(itemFirst), count)) { + sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); + return true; + } + return false; + } + + // Enqueues several items using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + inline bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + if (inner.try_enqueue_bulk(token, std::forward(itemFirst), count)) { + sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); + return true; + } + return false; + } + + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline bool try_dequeue(U& item) + { + if (sema->tryWait()) { + while (!inner.try_dequeue(item)) { + continue; + } + return true; + } + return false; + } + + // Attempts to dequeue from the queue using an explicit consumer token. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline bool try_dequeue(consumer_token_t& token, U& item) + { + if (sema->tryWait()) { + while (!inner.try_dequeue(token, item)) { + continue; + } + return true; + } + return false; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline size_t try_dequeue_bulk(It itemFirst, size_t max) + { + size_t count = 0; + max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max); + while (count != max) { + count += inner.template try_dequeue_bulk(itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) + { + size_t count = 0; + max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max); + while (count != max) { + count += inner.template try_dequeue_bulk(token, itemFirst, max - count); + } + return count; + } + + + + // Blocks the current thread until there's something to dequeue, then + // dequeues it. + // Never allocates. Thread-safe. + template + inline void wait_dequeue(U& item) + { + while (!sema->wait()) { + continue; + } + while (!inner.try_dequeue(item)) { + continue; + } + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout (specified in microseconds) expires. Returns false + // without setting `item` if the timeout expires, otherwise assigns + // to `item` and returns true. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue. + // Never allocates. Thread-safe. + template + inline bool wait_dequeue_timed(U& item, std::int64_t timeout_usecs) + { + if (!sema->wait(timeout_usecs)) { + return false; + } + while (!inner.try_dequeue(item)) { + continue; + } + return true; + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout expires. Returns false without setting `item` if the + // timeout expires, otherwise assigns to `item` and returns true. + // Never allocates. Thread-safe. + template + inline bool wait_dequeue_timed(U& item, std::chrono::duration const& timeout) + { + return wait_dequeue_timed(item, std::chrono::duration_cast(timeout).count()); + } + + // Blocks the current thread until there's something to dequeue, then + // dequeues it using an explicit consumer token. + // Never allocates. Thread-safe. + template + inline void wait_dequeue(consumer_token_t& token, U& item) + { + while (!sema->wait()) { + continue; + } + while (!inner.try_dequeue(token, item)) { + continue; + } + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout (specified in microseconds) expires. Returns false + // without setting `item` if the timeout expires, otherwise assigns + // to `item` and returns true. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue. + // Never allocates. Thread-safe. + template + inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::int64_t timeout_usecs) + { + if (!sema->wait(timeout_usecs)) { + return false; + } + while (!inner.try_dequeue(token, item)) { + continue; + } + return true; + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout expires. Returns false without setting `item` if the + // timeout expires, otherwise assigns to `item` and returns true. + // Never allocates. Thread-safe. + template + inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::chrono::duration const& timeout) + { + return wait_dequeue_timed(token, item, std::chrono::duration_cast(timeout).count()); + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued, which will + // always be at least one (this method blocks until the queue + // is non-empty) and at most max. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk(It itemFirst, size_t max) + { + size_t count = 0; + max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max); + while (count != max) { + count += inner.template try_dequeue_bulk(itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued, which can + // be 0 if the timeout expires while waiting for elements, + // and at most max. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue_bulk. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::int64_t timeout_usecs) + { + size_t count = 0; + max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs); + while (count != max) { + count += inner.template try_dequeue_bulk(itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued, which can + // be 0 if the timeout expires while waiting for elements, + // and at most max. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::chrono::duration const& timeout) + { + return wait_dequeue_bulk_timed(itemFirst, max, std::chrono::duration_cast(timeout).count()); + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued, which will + // always be at least one (this method blocks until the queue + // is non-empty) and at most max. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) + { + size_t count = 0; + max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max); + while (count != max) { + count += inner.template try_dequeue_bulk(token, itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued, which can + // be 0 if the timeout expires while waiting for elements, + // and at most max. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue_bulk. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::int64_t timeout_usecs) + { + size_t count = 0; + max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs); + while (count != max) { + count += inner.template try_dequeue_bulk(token, itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued, which can + // be 0 if the timeout expires while waiting for elements, + // and at most max. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::chrono::duration const& timeout) + { + return wait_dequeue_bulk_timed(token, itemFirst, max, std::chrono::duration_cast(timeout).count()); + } + + + // Returns an estimate of the total number of elements currently in the queue. This + // estimate is only accurate if the queue has completely stabilized before it is called + // (i.e. all enqueue and dequeue operations have completed and their memory effects are + // visible on the calling thread, and no further operations start while this method is + // being called). + // Thread-safe. + inline size_t size_approx() const + { + return (size_t)sema->availableApprox(); + } + + + // Returns true if the underlying atomic variables used by + // the queue are lock-free (they should be on most platforms). + // Thread-safe. + static bool is_lock_free() + { + return ConcurrentQueue::is_lock_free(); + } + + +private: + template + static inline U* create(A1&& a1, A2&& a2) + { + void* p = (Traits::malloc)(sizeof(U)); + return p != nullptr ? new (p) U(std::forward(a1), std::forward(a2)) : nullptr; + } + + template + static inline void destroy(U* p) + { + if (p != nullptr) { + p->~U(); + } + (Traits::free)(p); + } + +private: + ConcurrentQueue inner; + std::unique_ptr sema; +}; + + +template +inline void swap(BlockingConcurrentQueue& a, BlockingConcurrentQueue& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +} // end namespace moodycamel diff --git a/indra/llcommon/concurrentqueue.h b/indra/llcommon/concurrentqueue.h new file mode 100644 index 0000000000..609ca4ab50 --- /dev/null +++ b/indra/llcommon/concurrentqueue.h @@ -0,0 +1,3742 @@ +// Provides a C++11 implementation of a multi-producer, multi-consumer lock-free queue. +// An overview, including benchmark results, is provided here: +// http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++ +// The full design is also described in excruciating detail at: +// http://moodycamel.com/blog/2014/detailed-design-of-a-lock-free-queue + +// Simplified BSD license: +// Copyright (c) 2013-2020, Cameron Desrochers. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Also dual-licensed under the Boost Software License (see LICENSE.md) + +#pragma once + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) +// Disable -Wconversion warnings (spuriously triggered when Traits::size_t and +// Traits::index_t are set to < 32 bits, causing integer promotion, causing warnings +// upon assigning any computed values) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" + +#ifdef MCDBGQ_USE_RELACY +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" +#endif +#endif + +#if defined(_MSC_VER) && (!defined(_HAS_CXX17) || !_HAS_CXX17) +// VS2019 with /W4 warns about constant conditional expressions but unless /std=c++17 or higher +// does not support `if constexpr`, so we have no choice but to simply disable the warning +#pragma warning(push) +#pragma warning(disable: 4127) // conditional expression is constant +#endif + +#if defined(__APPLE__) +#include "TargetConditionals.h" +#endif + +#ifdef MCDBGQ_USE_RELACY +#include "relacy/relacy_std.hpp" +#include "relacy_shims.h" +// We only use malloc/free anyway, and the delete macro messes up `= delete` method declarations. +// We'll override the default trait malloc ourselves without a macro. +#undef new +#undef delete +#undef malloc +#undef free +#else +#include // Requires C++11. Sorry VS2010. +#include +#endif +#include // for max_align_t +#include +#include +#include +#include +#include +#include +#include // for CHAR_BIT +#include +#include // partly for __WINPTHREADS_VERSION if on MinGW-w64 w/ POSIX threading + +// Platform-specific definitions of a numeric thread ID type and an invalid value +namespace moodycamel { namespace details { + template struct thread_id_converter { + typedef thread_id_t thread_id_numeric_size_t; + typedef thread_id_t thread_id_hash_t; + static thread_id_hash_t prehash(thread_id_t const& x) { return x; } + }; +} } +#if defined(MCDBGQ_USE_RELACY) +namespace moodycamel { namespace details { + typedef std::uint32_t thread_id_t; + static const thread_id_t invalid_thread_id = 0xFFFFFFFFU; + static const thread_id_t invalid_thread_id2 = 0xFFFFFFFEU; + static inline thread_id_t thread_id() { return rl::thread_index(); } +} } +#elif defined(_WIN32) || defined(__WINDOWS__) || defined(__WIN32__) +// No sense pulling in windows.h in a header, we'll manually declare the function +// we use and rely on backwards-compatibility for this not to break +extern "C" __declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void); +namespace moodycamel { namespace details { + static_assert(sizeof(unsigned long) == sizeof(std::uint32_t), "Expected size of unsigned long to be 32 bits on Windows"); + typedef std::uint32_t thread_id_t; + static const thread_id_t invalid_thread_id = 0; // See http://blogs.msdn.com/b/oldnewthing/archive/2004/02/23/78395.aspx + static const thread_id_t invalid_thread_id2 = 0xFFFFFFFFU; // Not technically guaranteed to be invalid, but is never used in practice. Note that all Win32 thread IDs are presently multiples of 4. + static inline thread_id_t thread_id() { return static_cast(::GetCurrentThreadId()); } +} } +#elif defined(__arm__) || defined(_M_ARM) || defined(__aarch64__) || (defined(__APPLE__) && TARGET_OS_IPHONE) || defined(MOODYCAMEL_NO_THREAD_LOCAL) +namespace moodycamel { namespace details { + static_assert(sizeof(std::thread::id) == 4 || sizeof(std::thread::id) == 8, "std::thread::id is expected to be either 4 or 8 bytes"); + + typedef std::thread::id thread_id_t; + static const thread_id_t invalid_thread_id; // Default ctor creates invalid ID + + // Note we don't define a invalid_thread_id2 since std::thread::id doesn't have one; it's + // only used if MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is defined anyway, which it won't + // be. + static inline thread_id_t thread_id() { return std::this_thread::get_id(); } + + template struct thread_id_size { }; + template<> struct thread_id_size<4> { typedef std::uint32_t numeric_t; }; + template<> struct thread_id_size<8> { typedef std::uint64_t numeric_t; }; + + template<> struct thread_id_converter { + typedef thread_id_size::numeric_t thread_id_numeric_size_t; +#ifndef __APPLE__ + typedef std::size_t thread_id_hash_t; +#else + typedef thread_id_numeric_size_t thread_id_hash_t; +#endif + + static thread_id_hash_t prehash(thread_id_t const& x) + { +#ifndef __APPLE__ + return std::hash()(x); +#else + return *reinterpret_cast(&x); +#endif + } + }; +} } +#else +// Use a nice trick from this answer: http://stackoverflow.com/a/8438730/21475 +// In order to get a numeric thread ID in a platform-independent way, we use a thread-local +// static variable's address as a thread identifier :-) +#if defined(__GNUC__) || defined(__INTEL_COMPILER) +#define MOODYCAMEL_THREADLOCAL __thread +#elif defined(_MSC_VER) +#define MOODYCAMEL_THREADLOCAL __declspec(thread) +#else +// Assume C++11 compliant compiler +#define MOODYCAMEL_THREADLOCAL thread_local +#endif +namespace moodycamel { namespace details { + typedef std::uintptr_t thread_id_t; + static const thread_id_t invalid_thread_id = 0; // Address can't be nullptr + static const thread_id_t invalid_thread_id2 = 1; // Member accesses off a null pointer are also generally invalid. Plus it's not aligned. + inline thread_id_t thread_id() { static MOODYCAMEL_THREADLOCAL int x; return reinterpret_cast(&x); } +} } +#endif + +// Constexpr if +#ifndef MOODYCAMEL_CONSTEXPR_IF +#if (defined(_MSC_VER) && defined(_HAS_CXX17) && _HAS_CXX17) || __cplusplus > 201402L +#define MOODYCAMEL_CONSTEXPR_IF if constexpr +#define MOODYCAMEL_MAYBE_UNUSED [[maybe_unused]] +#else +#define MOODYCAMEL_CONSTEXPR_IF if +#define MOODYCAMEL_MAYBE_UNUSED +#endif +#endif + +// Exceptions +#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED +#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__)) +#define MOODYCAMEL_EXCEPTIONS_ENABLED +#endif +#endif +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED +#define MOODYCAMEL_TRY try +#define MOODYCAMEL_CATCH(...) catch(__VA_ARGS__) +#define MOODYCAMEL_RETHROW throw +#define MOODYCAMEL_THROW(expr) throw (expr) +#else +#define MOODYCAMEL_TRY MOODYCAMEL_CONSTEXPR_IF (true) +#define MOODYCAMEL_CATCH(...) else MOODYCAMEL_CONSTEXPR_IF (false) +#define MOODYCAMEL_RETHROW +#define MOODYCAMEL_THROW(expr) +#endif + +#ifndef MOODYCAMEL_NOEXCEPT +#if !defined(MOODYCAMEL_EXCEPTIONS_ENABLED) +#define MOODYCAMEL_NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) true +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) true +#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1800 +// VS2012's std::is_nothrow_[move_]constructible is broken and returns true when it shouldn't :-( +// We have to assume *all* non-trivial constructors may throw on VS2012! +#define MOODYCAMEL_NOEXCEPT _NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value : std::is_trivially_copy_constructible::value) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) +#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1900 +#define MOODYCAMEL_NOEXCEPT _NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value || std::is_nothrow_move_constructible::value : std::is_trivially_copy_constructible::value || std::is_nothrow_copy_constructible::value) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) +#else +#define MOODYCAMEL_NOEXCEPT noexcept +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) noexcept(expr) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) noexcept(expr) +#endif +#endif + +#ifndef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#ifdef MCDBGQ_USE_RELACY +#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#else +// VS2013 doesn't support `thread_local`, and MinGW-w64 w/ POSIX threading has a crippling bug: http://sourceforge.net/p/mingw-w64/bugs/445 +// g++ <=4.7 doesn't support thread_local either. +// Finally, iOS/ARM doesn't have support for it either, and g++/ARM allows it to compile but it's unconfirmed to actually work +#if (!defined(_MSC_VER) || _MSC_VER >= 1900) && (!defined(__MINGW32__) && !defined(__MINGW64__) || !defined(__WINPTHREADS_VERSION)) && (!defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) && (!defined(__APPLE__) || !TARGET_OS_IPHONE) && !defined(__arm__) && !defined(_M_ARM) && !defined(__aarch64__) +// Assume `thread_local` is fully supported in all other C++11 compilers/platforms +//#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED // always disabled for now since several users report having problems with it on +#endif +#endif +#endif + +// VS2012 doesn't support deleted functions. +// In this case, we declare the function normally but don't define it. A link error will be generated if the function is called. +#ifndef MOODYCAMEL_DELETE_FUNCTION +#if defined(_MSC_VER) && _MSC_VER < 1800 +#define MOODYCAMEL_DELETE_FUNCTION +#else +#define MOODYCAMEL_DELETE_FUNCTION = delete +#endif +#endif + +namespace moodycamel { namespace details { +#ifndef MOODYCAMEL_ALIGNAS +// VS2013 doesn't support alignas or alignof, and align() requires a constant literal +#if defined(_MSC_VER) && _MSC_VER <= 1800 +#define MOODYCAMEL_ALIGNAS(alignment) __declspec(align(alignment)) +#define MOODYCAMEL_ALIGNOF(obj) __alignof(obj) +#define MOODYCAMEL_ALIGNED_TYPE_LIKE(T, obj) typename details::Vs2013Aligned::value, T>::type + template struct Vs2013Aligned { }; // default, unsupported alignment + template struct Vs2013Aligned<1, T> { typedef __declspec(align(1)) T type; }; + template struct Vs2013Aligned<2, T> { typedef __declspec(align(2)) T type; }; + template struct Vs2013Aligned<4, T> { typedef __declspec(align(4)) T type; }; + template struct Vs2013Aligned<8, T> { typedef __declspec(align(8)) T type; }; + template struct Vs2013Aligned<16, T> { typedef __declspec(align(16)) T type; }; + template struct Vs2013Aligned<32, T> { typedef __declspec(align(32)) T type; }; + template struct Vs2013Aligned<64, T> { typedef __declspec(align(64)) T type; }; + template struct Vs2013Aligned<128, T> { typedef __declspec(align(128)) T type; }; + template struct Vs2013Aligned<256, T> { typedef __declspec(align(256)) T type; }; +#else + template struct identity { typedef T type; }; +#define MOODYCAMEL_ALIGNAS(alignment) alignas(alignment) +#define MOODYCAMEL_ALIGNOF(obj) alignof(obj) +#define MOODYCAMEL_ALIGNED_TYPE_LIKE(T, obj) alignas(alignof(obj)) typename details::identity::type +#endif +#endif +} } + + +// TSAN can false report races in lock-free code. To enable TSAN to be used from projects that use this one, +// we can apply per-function compile-time suppression. +// See https://clang.llvm.org/docs/ThreadSanitizer.html#has-feature-thread-sanitizer +#define MOODYCAMEL_NO_TSAN +#if defined(__has_feature) + #if __has_feature(thread_sanitizer) + #undef MOODYCAMEL_NO_TSAN + #define MOODYCAMEL_NO_TSAN __attribute__((no_sanitize("thread"))) + #endif // TSAN +#endif // TSAN + +// Compiler-specific likely/unlikely hints +namespace moodycamel { namespace details { +#if defined(__GNUC__) + static inline bool (likely)(bool x) { return __builtin_expect((x), true); } + static inline bool (unlikely)(bool x) { return __builtin_expect((x), false); } +#else + static inline bool (likely)(bool x) { return x; } + static inline bool (unlikely)(bool x) { return x; } +#endif +} } + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG +#include "internal/concurrentqueue_internal_debug.h" +#endif + +namespace moodycamel { +namespace details { + template + struct const_numeric_max { + static_assert(std::is_integral::value, "const_numeric_max can only be used with integers"); + static const T value = std::numeric_limits::is_signed + ? (static_cast(1) << (sizeof(T) * CHAR_BIT - 1)) - static_cast(1) + : static_cast(-1); + }; + +#if defined(__GLIBCXX__) + typedef ::max_align_t std_max_align_t; // libstdc++ forgot to add it to std:: for a while +#else + typedef std::max_align_t std_max_align_t; // Others (e.g. MSVC) insist it can *only* be accessed via std:: +#endif + + // Some platforms have incorrectly set max_align_t to a type with <8 bytes alignment even while supporting + // 8-byte aligned scalar values (*cough* 32-bit iOS). Work around this with our own union. See issue #64. + typedef union { + std_max_align_t x; + long long y; + void* z; + } max_align_t; +} + +// Default traits for the ConcurrentQueue. To change some of the +// traits without re-implementing all of them, inherit from this +// struct and shadow the declarations you wish to be different; +// since the traits are used as a template type parameter, the +// shadowed declarations will be used where defined, and the defaults +// otherwise. +struct ConcurrentQueueDefaultTraits +{ + // General-purpose size type. std::size_t is strongly recommended. + typedef std::size_t size_t; + + // The type used for the enqueue and dequeue indices. Must be at least as + // large as size_t. Should be significantly larger than the number of elements + // you expect to hold at once, especially if you have a high turnover rate; + // for example, on 32-bit x86, if you expect to have over a hundred million + // elements or pump several million elements through your queue in a very + // short space of time, using a 32-bit type *may* trigger a race condition. + // A 64-bit int type is recommended in that case, and in practice will + // prevent a race condition no matter the usage of the queue. Note that + // whether the queue is lock-free with a 64-int type depends on the whether + // std::atomic is lock-free, which is platform-specific. + typedef std::size_t index_t; + + // Internally, all elements are enqueued and dequeued from multi-element + // blocks; this is the smallest controllable unit. If you expect few elements + // but many producers, a smaller block size should be favoured. For few producers + // and/or many elements, a larger block size is preferred. A sane default + // is provided. Must be a power of 2. + static const size_t BLOCK_SIZE = 32; + + // For explicit producers (i.e. when using a producer token), the block is + // checked for being empty by iterating through a list of flags, one per element. + // For large block sizes, this is too inefficient, and switching to an atomic + // counter-based approach is faster. The switch is made for block sizes strictly + // larger than this threshold. + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = 32; + + // How many full blocks can be expected for a single explicit producer? This should + // reflect that number's maximum for optimal performance. Must be a power of 2. + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = 32; + + // How many full blocks can be expected for a single implicit producer? This should + // reflect that number's maximum for optimal performance. Must be a power of 2. + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 32; + + // The initial size of the hash table mapping thread IDs to implicit producers. + // Note that the hash is resized every time it becomes half full. + // Must be a power of two, and either 0 or at least 1. If 0, implicit production + // (using the enqueue methods without an explicit producer token) is disabled. + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = 32; + + // Controls the number of items that an explicit consumer (i.e. one with a token) + // must consume before it causes all consumers to rotate and move on to the next + // internal queue. + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 256; + + // The maximum number of elements (inclusive) that can be enqueued to a sub-queue. + // Enqueue operations that would cause this limit to be surpassed will fail. Note + // that this limit is enforced at the block level (for performance reasons), i.e. + // it's rounded up to the nearest block size. + static const size_t MAX_SUBQUEUE_SIZE = details::const_numeric_max::value; + + // The number of times to spin before sleeping when waiting on a semaphore. + // Recommended values are on the order of 1000-10000 unless the number of + // consumer threads exceeds the number of idle cores (in which case try 0-100). + // Only affects instances of the BlockingConcurrentQueue. + static const int MAX_SEMA_SPINS = 10000; + + +#ifndef MCDBGQ_USE_RELACY + // Memory allocation can be customized if needed. + // malloc should return nullptr on failure, and handle alignment like std::malloc. +#if defined(malloc) || defined(free) + // Gah, this is 2015, stop defining macros that break standard code already! + // Work around malloc/free being special macros: + static inline void* WORKAROUND_malloc(size_t size) { return malloc(size); } + static inline void WORKAROUND_free(void* ptr) { return free(ptr); } + static inline void* (malloc)(size_t size) { return WORKAROUND_malloc(size); } + static inline void (free)(void* ptr) { return WORKAROUND_free(ptr); } +#else + static inline void* malloc(size_t size) { return std::malloc(size); } + static inline void free(void* ptr) { return std::free(ptr); } +#endif +#else + // Debug versions when running under the Relacy race detector (ignore + // these in user code) + static inline void* malloc(size_t size) { return rl::rl_malloc(size, $); } + static inline void free(void* ptr) { return rl::rl_free(ptr, $); } +#endif +}; + + +// When producing or consuming many elements, the most efficient way is to: +// 1) Use one of the bulk-operation methods of the queue with a token +// 2) Failing that, use the bulk-operation methods without a token +// 3) Failing that, create a token and use that with the single-item methods +// 4) Failing that, use the single-parameter methods of the queue +// Having said that, don't create tokens willy-nilly -- ideally there should be +// a maximum of one token per thread (of each kind). +struct ProducerToken; +struct ConsumerToken; + +template class ConcurrentQueue; +template class BlockingConcurrentQueue; +class ConcurrentQueueTests; + + +namespace details +{ + struct ConcurrentQueueProducerTypelessBase + { + ConcurrentQueueProducerTypelessBase* next; + std::atomic inactive; + ProducerToken* token; + + ConcurrentQueueProducerTypelessBase() + : next(nullptr), inactive(false), token(nullptr) + { + } + }; + + template struct _hash_32_or_64 { + static inline std::uint32_t hash(std::uint32_t h) + { + // MurmurHash3 finalizer -- see https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp + // Since the thread ID is already unique, all we really want to do is propagate that + // uniqueness evenly across all the bits, so that we can use a subset of the bits while + // reducing collisions significantly + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + return h ^ (h >> 16); + } + }; + template<> struct _hash_32_or_64<1> { + static inline std::uint64_t hash(std::uint64_t h) + { + h ^= h >> 33; + h *= 0xff51afd7ed558ccd; + h ^= h >> 33; + h *= 0xc4ceb9fe1a85ec53; + return h ^ (h >> 33); + } + }; + template struct hash_32_or_64 : public _hash_32_or_64<(size > 4)> { }; + + static inline size_t hash_thread_id(thread_id_t id) + { + static_assert(sizeof(thread_id_t) <= 8, "Expected a platform where thread IDs are at most 64-bit values"); + return static_cast(hash_32_or_64::thread_id_hash_t)>::hash( + thread_id_converter::prehash(id))); + } + + template + static inline bool circular_less_than(T a, T b) + { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4554) +#endif + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "circular_less_than is intended to be used only with unsigned integer types"); + return static_cast(a - b) > static_cast(static_cast(1) << static_cast(sizeof(T) * CHAR_BIT - 1)); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + } + + template + static inline char* align_for(char* ptr) + { + const std::size_t alignment = std::alignment_of::value; + return ptr + (alignment - (reinterpret_cast(ptr) % alignment)) % alignment; + } + + template + static inline T ceil_to_pow_2(T x) + { + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "ceil_to_pow_2 is intended to be used only with unsigned integer types"); + + // Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + for (std::size_t i = 1; i < sizeof(T); i <<= 1) { + x |= x >> (i << 3); + } + ++x; + return x; + } + + template + static inline void swap_relaxed(std::atomic& left, std::atomic& right) + { + T temp = std::move(left.load(std::memory_order_relaxed)); + left.store(std::move(right.load(std::memory_order_relaxed)), std::memory_order_relaxed); + right.store(std::move(temp), std::memory_order_relaxed); + } + + template + static inline T const& nomove(T const& x) + { + return x; + } + + template + struct nomove_if + { + template + static inline T const& eval(T const& x) + { + return x; + } + }; + + template<> + struct nomove_if + { + template + static inline auto eval(U&& x) + -> decltype(std::forward(x)) + { + return std::forward(x); + } + }; + + template + static inline auto deref_noexcept(It& it) MOODYCAMEL_NOEXCEPT -> decltype(*it) + { + return *it; + } + +#if defined(__clang__) || !defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + template struct is_trivially_destructible : std::is_trivially_destructible { }; +#else + template struct is_trivially_destructible : std::has_trivial_destructor { }; +#endif + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#ifdef MCDBGQ_USE_RELACY + typedef RelacyThreadExitListener ThreadExitListener; + typedef RelacyThreadExitNotifier ThreadExitNotifier; +#else + struct ThreadExitListener + { + typedef void (*callback_t)(void*); + callback_t callback; + void* userData; + + ThreadExitListener* next; // reserved for use by the ThreadExitNotifier + }; + + + class ThreadExitNotifier + { + public: + static void subscribe(ThreadExitListener* listener) + { + auto& tlsInst = instance(); + listener->next = tlsInst.tail; + tlsInst.tail = listener; + } + + static void unsubscribe(ThreadExitListener* listener) + { + auto& tlsInst = instance(); + ThreadExitListener** prev = &tlsInst.tail; + for (auto ptr = tlsInst.tail; ptr != nullptr; ptr = ptr->next) { + if (ptr == listener) { + *prev = ptr->next; + break; + } + prev = &ptr->next; + } + } + + private: + ThreadExitNotifier() : tail(nullptr) { } + ThreadExitNotifier(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; + ThreadExitNotifier& operator=(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; + + ~ThreadExitNotifier() + { + // This thread is about to exit, let everyone know! + assert(this == &instance() && "If this assert fails, you likely have a buggy compiler! Change the preprocessor conditions such that MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is no longer defined."); + for (auto ptr = tail; ptr != nullptr; ptr = ptr->next) { + ptr->callback(ptr->userData); + } + } + + // Thread-local + static inline ThreadExitNotifier& instance() + { + static thread_local ThreadExitNotifier notifier; + return notifier; + } + + private: + ThreadExitListener* tail; + }; +#endif +#endif + + template struct static_is_lock_free_num { enum { value = 0 }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_CHAR_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_SHORT_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_INT_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_LONG_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_LLONG_LOCK_FREE }; }; + template struct static_is_lock_free : static_is_lock_free_num::type> { }; + template<> struct static_is_lock_free { enum { value = ATOMIC_BOOL_LOCK_FREE }; }; + template struct static_is_lock_free { enum { value = ATOMIC_POINTER_LOCK_FREE }; }; +} + + +struct ProducerToken +{ + template + explicit ProducerToken(ConcurrentQueue& queue); + + template + explicit ProducerToken(BlockingConcurrentQueue& queue); + + ProducerToken(ProducerToken&& other) MOODYCAMEL_NOEXCEPT + : producer(other.producer) + { + other.producer = nullptr; + if (producer != nullptr) { + producer->token = this; + } + } + + inline ProducerToken& operator=(ProducerToken&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(ProducerToken& other) MOODYCAMEL_NOEXCEPT + { + std::swap(producer, other.producer); + if (producer != nullptr) { + producer->token = this; + } + if (other.producer != nullptr) { + other.producer->token = &other; + } + } + + // A token is always valid unless: + // 1) Memory allocation failed during construction + // 2) It was moved via the move constructor + // (Note: assignment does a swap, leaving both potentially valid) + // 3) The associated queue was destroyed + // Note that if valid() returns true, that only indicates + // that the token is valid for use with a specific queue, + // but not which one; that's up to the user to track. + inline bool valid() const { return producer != nullptr; } + + ~ProducerToken() + { + if (producer != nullptr) { + producer->token = nullptr; + producer->inactive.store(true, std::memory_order_release); + } + } + + // Disable copying and assignment + ProducerToken(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; + ProducerToken& operator=(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; + +private: + template friend class ConcurrentQueue; + friend class ConcurrentQueueTests; + +protected: + details::ConcurrentQueueProducerTypelessBase* producer; +}; + + +struct ConsumerToken +{ + template + explicit ConsumerToken(ConcurrentQueue& q); + + template + explicit ConsumerToken(BlockingConcurrentQueue& q); + + ConsumerToken(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT + : initialOffset(other.initialOffset), lastKnownGlobalOffset(other.lastKnownGlobalOffset), itemsConsumedFromCurrent(other.itemsConsumedFromCurrent), currentProducer(other.currentProducer), desiredProducer(other.desiredProducer) + { + } + + inline ConsumerToken& operator=(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(ConsumerToken& other) MOODYCAMEL_NOEXCEPT + { + std::swap(initialOffset, other.initialOffset); + std::swap(lastKnownGlobalOffset, other.lastKnownGlobalOffset); + std::swap(itemsConsumedFromCurrent, other.itemsConsumedFromCurrent); + std::swap(currentProducer, other.currentProducer); + std::swap(desiredProducer, other.desiredProducer); + } + + // Disable copying and assignment + ConsumerToken(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; + ConsumerToken& operator=(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; + +private: + template friend class ConcurrentQueue; + friend class ConcurrentQueueTests; + +private: // but shared with ConcurrentQueue + std::uint32_t initialOffset; + std::uint32_t lastKnownGlobalOffset; + std::uint32_t itemsConsumedFromCurrent; + details::ConcurrentQueueProducerTypelessBase* currentProducer; + details::ConcurrentQueueProducerTypelessBase* desiredProducer; +}; + +// Need to forward-declare this swap because it's in a namespace. +// See http://stackoverflow.com/questions/4492062/why-does-a-c-friend-class-need-a-forward-declaration-only-in-other-namespaces +template +inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT; + + +template +class ConcurrentQueue +{ +public: + typedef ::moodycamel::ProducerToken producer_token_t; + typedef ::moodycamel::ConsumerToken consumer_token_t; + + typedef typename Traits::index_t index_t; + typedef typename Traits::size_t size_t; + + static const size_t BLOCK_SIZE = static_cast(Traits::BLOCK_SIZE); + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = static_cast(Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD); + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::EXPLICIT_INITIAL_INDEX_SIZE); + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::IMPLICIT_INITIAL_INDEX_SIZE); + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = static_cast(Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE); + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = static_cast(Traits::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE); +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4307) // + integral constant overflow (that's what the ternary expression is for!) +#pragma warning(disable: 4309) // static_cast: Truncation of constant value +#endif + static const size_t MAX_SUBQUEUE_SIZE = (details::const_numeric_max::value - static_cast(Traits::MAX_SUBQUEUE_SIZE) < BLOCK_SIZE) ? details::const_numeric_max::value : ((static_cast(Traits::MAX_SUBQUEUE_SIZE) + (BLOCK_SIZE - 1)) / BLOCK_SIZE * BLOCK_SIZE); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::size_t must be an unsigned integral type"); + static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::index_t must be an unsigned integral type"); + static_assert(sizeof(index_t) >= sizeof(size_t), "Traits::index_t must be at least as wide as Traits::size_t"); + static_assert((BLOCK_SIZE > 1) && !(BLOCK_SIZE & (BLOCK_SIZE - 1)), "Traits::BLOCK_SIZE must be a power of 2 (and at least 2)"); + static_assert((EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD > 1) && !(EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD & (EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD - 1)), "Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD must be a power of 2 (and greater than 1)"); + static_assert((EXPLICIT_INITIAL_INDEX_SIZE > 1) && !(EXPLICIT_INITIAL_INDEX_SIZE & (EXPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::EXPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); + static_assert((IMPLICIT_INITIAL_INDEX_SIZE > 1) && !(IMPLICIT_INITIAL_INDEX_SIZE & (IMPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::IMPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); + static_assert((INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) || !(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE & (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - 1)), "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be a power of 2"); + static_assert(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0 || INITIAL_IMPLICIT_PRODUCER_HASH_SIZE >= 1, "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be at least 1 (or 0 to disable implicit enqueueing)"); + +public: + // Creates a queue with at least `capacity` element slots; note that the + // actual number of elements that can be inserted without additional memory + // allocation depends on the number of producers and the block size (e.g. if + // the block size is equal to `capacity`, only a single block will be allocated + // up-front, which means only a single producer will be able to enqueue elements + // without an extra allocation -- blocks aren't shared between producers). + // This method is not thread safe -- it is up to the user to ensure that the + // queue is fully constructed before it starts being used by other threads (this + // includes making the memory effects of construction visible, possibly with a + // memory barrier). + explicit ConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) + { + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + populate_initial_block_list(capacity / BLOCK_SIZE + ((capacity & (BLOCK_SIZE - 1)) == 0 ? 0 : 1)); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + // Track all the producers using a fully-resolved typed list for + // each kind; this makes it possible to debug them starting from + // the root queue object (otherwise wacky casts are needed that + // don't compile in the debugger's expression evaluator). + explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + } + + // Computes the correct amount of pre-allocated blocks for you based + // on the minimum number of elements you want available at any given + // time, and the maximum concurrent number of each type of producer. + ConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) + { + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + size_t blocks = (((minCapacity + BLOCK_SIZE - 1) / BLOCK_SIZE) - 1) * (maxExplicitProducers + 1) + 2 * (maxExplicitProducers + maxImplicitProducers); + populate_initial_block_list(blocks); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + } + + // Note: The queue should not be accessed concurrently while it's + // being deleted. It's up to the user to synchronize this. + // This method is not thread safe. + ~ConcurrentQueue() + { + // Destroy producers + auto ptr = producerListTail.load(std::memory_order_relaxed); + while (ptr != nullptr) { + auto next = ptr->next_prod(); + if (ptr->token != nullptr) { + ptr->token->producer = nullptr; + } + destroy(ptr); + ptr = next; + } + + // Destroy implicit producer hash tables + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE != 0) { + auto hash = implicitProducerHash.load(std::memory_order_relaxed); + while (hash != nullptr) { + auto prev = hash->prev; + if (prev != nullptr) { // The last hash is part of this object and was not allocated dynamically + for (size_t i = 0; i != hash->capacity; ++i) { + hash->entries[i].~ImplicitProducerKVP(); + } + hash->~ImplicitProducerHash(); + (Traits::free)(hash); + } + hash = prev; + } + } + + // Destroy global free list + auto block = freeList.head_unsafe(); + while (block != nullptr) { + auto next = block->freeListNext.load(std::memory_order_relaxed); + if (block->dynamicallyAllocated) { + destroy(block); + } + block = next; + } + + // Destroy initial free list + destroy_array(initialBlockPool, initialBlockPoolSize); + } + + // Disable copying and copy assignment + ConcurrentQueue(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + ConcurrentQueue& operator=(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + + // Moving is supported, but note that it is *not* a thread-safe operation. + // Nobody can use the queue while it's being moved, and the memory effects + // of that move must be propagated to other threads before they can use it. + // Note: When a queue is moved, its tokens are still valid but can only be + // used with the destination queue (i.e. semantically they are moved along + // with the queue itself). + ConcurrentQueue(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + : producerListTail(other.producerListTail.load(std::memory_order_relaxed)), + producerCount(other.producerCount.load(std::memory_order_relaxed)), + initialBlockPoolIndex(other.initialBlockPoolIndex.load(std::memory_order_relaxed)), + initialBlockPool(other.initialBlockPool), + initialBlockPoolSize(other.initialBlockPoolSize), + freeList(std::move(other.freeList)), + nextExplicitConsumerId(other.nextExplicitConsumerId.load(std::memory_order_relaxed)), + globalExplicitConsumerOffset(other.globalExplicitConsumerOffset.load(std::memory_order_relaxed)) + { + // Move the other one into this, and leave the other one as an empty queue + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + swap_implicit_producer_hashes(other); + + other.producerListTail.store(nullptr, std::memory_order_relaxed); + other.producerCount.store(0, std::memory_order_relaxed); + other.nextExplicitConsumerId.store(0, std::memory_order_relaxed); + other.globalExplicitConsumerOffset.store(0, std::memory_order_relaxed); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + explicitProducers.store(other.explicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); + other.explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(other.implicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); + other.implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + + other.initialBlockPoolIndex.store(0, std::memory_order_relaxed); + other.initialBlockPoolSize = 0; + other.initialBlockPool = nullptr; + + reown_producers(); + } + + inline ConcurrentQueue& operator=(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + { + return swap_internal(other); + } + + // Swaps this queue's state with the other's. Not thread-safe. + // Swapping two queues does not invalidate their tokens, however + // the tokens that were created for one queue must be used with + // only the swapped queue (i.e. the tokens are tied to the + // queue's movable state, not the object itself). + inline void swap(ConcurrentQueue& other) MOODYCAMEL_NOEXCEPT + { + swap_internal(other); + } + +private: + ConcurrentQueue& swap_internal(ConcurrentQueue& other) + { + if (this == &other) { + return *this; + } + + details::swap_relaxed(producerListTail, other.producerListTail); + details::swap_relaxed(producerCount, other.producerCount); + details::swap_relaxed(initialBlockPoolIndex, other.initialBlockPoolIndex); + std::swap(initialBlockPool, other.initialBlockPool); + std::swap(initialBlockPoolSize, other.initialBlockPoolSize); + freeList.swap(other.freeList); + details::swap_relaxed(nextExplicitConsumerId, other.nextExplicitConsumerId); + details::swap_relaxed(globalExplicitConsumerOffset, other.globalExplicitConsumerOffset); + + swap_implicit_producer_hashes(other); + + reown_producers(); + other.reown_producers(); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + details::swap_relaxed(explicitProducers, other.explicitProducers); + details::swap_relaxed(implicitProducers, other.implicitProducers); +#endif + + return *this; + } + +public: + // Enqueues a single item (by copying it). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T const& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(item); + } + + // Enqueues a single item (by moving it, if possible). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T&& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(std::move(item)); + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T const& item) + { + return inner_enqueue(token, item); + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T&& item) + { + return inner_enqueue(token, std::move(item)); + } + + // Enqueues several items. + // Allocates memory if required. Only fails if memory allocation fails (or + // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved instead of copied. + // Thread-safe. + template + bool enqueue_bulk(It itemFirst, size_t count) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue_bulk(itemFirst, count); + } + + // Enqueues several items using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails + // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return inner_enqueue_bulk(token, itemFirst, count); + } + + // Enqueues a single item (by copying it). + // Does not allocate memory. Fails if not enough room to enqueue (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0). + // Thread-safe. + inline bool try_enqueue(T const& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(item); + } + + // Enqueues a single item (by moving it, if possible). + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Thread-safe. + inline bool try_enqueue(T&& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(std::move(item)); + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T const& item) + { + return inner_enqueue(token, item); + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T&& item) + { + return inner_enqueue(token, std::move(item)); + } + + // Enqueues several items. + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool try_enqueue_bulk(It itemFirst, size_t count) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue_bulk(itemFirst, count); + } + + // Enqueues several items using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return inner_enqueue_bulk(token, itemFirst, count); + } + + + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + bool try_dequeue(U& item) + { + // Instead of simply trying each producer in turn (which could cause needless contention on the first + // producer), we score them heuristically. + size_t nonEmptyCount = 0; + ProducerBase* best = nullptr; + size_t bestSize = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); nonEmptyCount < 3 && ptr != nullptr; ptr = ptr->next_prod()) { + auto size = ptr->size_approx(); + if (size > 0) { + if (size > bestSize) { + bestSize = size; + best = ptr; + } + ++nonEmptyCount; + } + } + + // If there was at least one non-empty queue but it appears empty at the time + // we try to dequeue from it, we need to make sure every queue's been tried + if (nonEmptyCount > 0) { + if ((details::likely)(best->dequeue(item))) { + return true; + } + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr != best && ptr->dequeue(item)) { + return true; + } + } + } + return false; + } + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // This differs from the try_dequeue(item) method in that this one does + // not attempt to reduce contention by interleaving the order that producer + // streams are dequeued from. So, using this method can reduce overall throughput + // under contention, but will give more predictable results in single-threaded + // consumer scenarios. This is mostly only useful for internal unit tests. + // Never allocates. Thread-safe. + template + bool try_dequeue_non_interleaved(U& item) + { + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr->dequeue(item)) { + return true; + } + } + return false; + } + + // Attempts to dequeue from the queue using an explicit consumer token. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + bool try_dequeue(consumer_token_t& token, U& item) + { + // The idea is roughly as follows: + // Every 256 items from one producer, make everyone rotate (increase the global offset) -> this means the highest efficiency consumer dictates the rotation speed of everyone else, more or less + // If you see that the global offset has changed, you must reset your consumption counter and move to your designated place + // If there's no items where you're supposed to be, keep moving until you find a producer with some items + // If the global offset has not changed but you've run out of items to consume, move over from your current position until you find an producer with something in it + + if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { + if (!update_current_producer_after_rotation(token)) { + return false; + } + } + + // If there was at least one non-empty queue but it appears empty at the time + // we try to dequeue from it, we need to make sure every queue's been tried + if (static_cast(token.currentProducer)->dequeue(item)) { + if (++token.itemsConsumedFromCurrent == EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { + globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); + } + return true; + } + + auto tail = producerListTail.load(std::memory_order_acquire); + auto ptr = static_cast(token.currentProducer)->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + while (ptr != static_cast(token.currentProducer)) { + if (ptr->dequeue(item)) { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = 1; + return true; + } + ptr = ptr->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + } + return false; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + size_t try_dequeue_bulk(It itemFirst, size_t max) + { + size_t count = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + count += ptr->dequeue_bulk(itemFirst, max - count); + if (count == max) { + break; + } + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) + { + if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { + if (!update_current_producer_after_rotation(token)) { + return 0; + } + } + + size_t count = static_cast(token.currentProducer)->dequeue_bulk(itemFirst, max); + if (count == max) { + if ((token.itemsConsumedFromCurrent += static_cast(max)) >= EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { + globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); + } + return max; + } + token.itemsConsumedFromCurrent += static_cast(count); + max -= count; + + auto tail = producerListTail.load(std::memory_order_acquire); + auto ptr = static_cast(token.currentProducer)->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + while (ptr != static_cast(token.currentProducer)) { + auto dequeued = ptr->dequeue_bulk(itemFirst, max); + count += dequeued; + if (dequeued != 0) { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = static_cast(dequeued); + } + if (dequeued == max) { + break; + } + max -= dequeued; + ptr = ptr->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + } + return count; + } + + + + // Attempts to dequeue from a specific producer's inner queue. + // If you happen to know which producer you want to dequeue from, this + // is significantly faster than using the general-case try_dequeue methods. + // Returns false if the producer's queue appeared empty at the time it + // was checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline bool try_dequeue_from_producer(producer_token_t const& producer, U& item) + { + return static_cast(producer.producer)->dequeue(item); + } + + // Attempts to dequeue several elements from a specific producer's inner queue. + // Returns the number of items actually dequeued. + // If you happen to know which producer you want to dequeue from, this + // is significantly faster than using the general-case try_dequeue methods. + // Returns 0 if the producer's queue appeared empty at the time it + // was checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline size_t try_dequeue_bulk_from_producer(producer_token_t const& producer, It itemFirst, size_t max) + { + return static_cast(producer.producer)->dequeue_bulk(itemFirst, max); + } + + + // Returns an estimate of the total number of elements currently in the queue. This + // estimate is only accurate if the queue has completely stabilized before it is called + // (i.e. all enqueue and dequeue operations have completed and their memory effects are + // visible on the calling thread, and no further operations start while this method is + // being called). + // Thread-safe. + size_t size_approx() const + { + size_t size = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + size += ptr->size_approx(); + } + return size; + } + + + // Returns true if the underlying atomic variables used by + // the queue are lock-free (they should be on most platforms). + // Thread-safe. + static bool is_lock_free() + { + return + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::thread_id_numeric_size_t>::value == 2; + } + + +private: + friend struct ProducerToken; + friend struct ConsumerToken; + struct ExplicitProducer; + friend struct ExplicitProducer; + struct ImplicitProducer; + friend struct ImplicitProducer; + friend class ConcurrentQueueTests; + + enum AllocationMode { CanAlloc, CannotAlloc }; + + + /////////////////////////////// + // Queue methods + /////////////////////////////// + + template + inline bool inner_enqueue(producer_token_t const& token, U&& element) + { + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue(std::forward(element)); + } + + template + inline bool inner_enqueue(U&& element) + { + auto producer = get_or_add_implicit_producer(); + return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue(std::forward(element)); + } + + template + inline bool inner_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue_bulk(itemFirst, count); + } + + template + inline bool inner_enqueue_bulk(It itemFirst, size_t count) + { + auto producer = get_or_add_implicit_producer(); + return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue_bulk(itemFirst, count); + } + + inline bool update_current_producer_after_rotation(consumer_token_t& token) + { + // Ah, there's been a rotation, figure out where we should be! + auto tail = producerListTail.load(std::memory_order_acquire); + if (token.desiredProducer == nullptr && tail == nullptr) { + return false; + } + auto prodCount = producerCount.load(std::memory_order_relaxed); + auto globalOffset = globalExplicitConsumerOffset.load(std::memory_order_relaxed); + if ((details::unlikely)(token.desiredProducer == nullptr)) { + // Aha, first time we're dequeueing anything. + // Figure out our local position + // Note: offset is from start, not end, but we're traversing from end -- subtract from count first + std::uint32_t offset = prodCount - 1 - (token.initialOffset % prodCount); + token.desiredProducer = tail; + for (std::uint32_t i = 0; i != offset; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + } + + std::uint32_t delta = globalOffset - token.lastKnownGlobalOffset; + if (delta >= prodCount) { + delta = delta % prodCount; + } + for (std::uint32_t i = 0; i != delta; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + + token.lastKnownGlobalOffset = globalOffset; + token.currentProducer = token.desiredProducer; + token.itemsConsumedFromCurrent = 0; + return true; + } + + + /////////////////////////// + // Free list + /////////////////////////// + + template + struct FreeListNode + { + FreeListNode() : freeListRefs(0), freeListNext(nullptr) { } + + std::atomic freeListRefs; + std::atomic freeListNext; + }; + + // A simple CAS-based lock-free free list. Not the fastest thing in the world under heavy contention, but + // simple and correct (assuming nodes are never freed until after the free list is destroyed), and fairly + // speedy under low contention. + template // N must inherit FreeListNode or have the same fields (and initialization of them) + struct FreeList + { + FreeList() : freeListHead(nullptr) { } + FreeList(FreeList&& other) : freeListHead(other.freeListHead.load(std::memory_order_relaxed)) { other.freeListHead.store(nullptr, std::memory_order_relaxed); } + void swap(FreeList& other) { details::swap_relaxed(freeListHead, other.freeListHead); } + + FreeList(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; + FreeList& operator=(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; + + inline void add(N* node) + { +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugLock lock(mutex); +#endif + // We know that the should-be-on-freelist bit is 0 at this point, so it's safe to + // set it using a fetch_add + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST, std::memory_order_acq_rel) == 0) { + // Oh look! We were the last ones referencing this node, and we know + // we want to add it to the free list, so let's do it! + add_knowing_refcount_is_zero(node); + } + } + + inline N* try_get() + { +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugLock lock(mutex); +#endif + auto head = freeListHead.load(std::memory_order_acquire); + while (head != nullptr) { + auto prevHead = head; + auto refs = head->freeListRefs.load(std::memory_order_relaxed); + if ((refs & REFS_MASK) == 0 || !head->freeListRefs.compare_exchange_strong(refs, refs + 1, std::memory_order_acquire, std::memory_order_relaxed)) { + head = freeListHead.load(std::memory_order_acquire); + continue; + } + + // Good, reference count has been incremented (it wasn't at zero), which means we can read the + // next and not worry about it changing between now and the time we do the CAS + auto next = head->freeListNext.load(std::memory_order_relaxed); + if (freeListHead.compare_exchange_strong(head, next, std::memory_order_acquire, std::memory_order_relaxed)) { + // Yay, got the node. This means it was on the list, which means shouldBeOnFreeList must be false no + // matter the refcount (because nobody else knows it's been taken off yet, it can't have been put back on). + assert((head->freeListRefs.load(std::memory_order_relaxed) & SHOULD_BE_ON_FREELIST) == 0); + + // Decrease refcount twice, once for our ref, and once for the list's ref + head->freeListRefs.fetch_sub(2, std::memory_order_release); + return head; + } + + // OK, the head must have changed on us, but we still need to decrease the refcount we increased. + // Note that we don't need to release any memory effects, but we do need to ensure that the reference + // count decrement happens-after the CAS on the head. + refs = prevHead->freeListRefs.fetch_sub(1, std::memory_order_acq_rel); + if (refs == SHOULD_BE_ON_FREELIST + 1) { + add_knowing_refcount_is_zero(prevHead); + } + } + + return nullptr; + } + + // Useful for traversing the list when there's no contention (e.g. to destroy remaining nodes) + N* head_unsafe() const { return freeListHead.load(std::memory_order_relaxed); } + + private: + inline void add_knowing_refcount_is_zero(N* node) + { + // Since the refcount is zero, and nobody can increase it once it's zero (except us, and we run + // only one copy of this method per node at a time, i.e. the single thread case), then we know + // we can safely change the next pointer of the node; however, once the refcount is back above + // zero, then other threads could increase it (happens under heavy contention, when the refcount + // goes to zero in between a load and a refcount increment of a node in try_get, then back up to + // something non-zero, then the refcount increment is done by the other thread) -- so, if the CAS + // to add the node to the actual list fails, decrease the refcount and leave the add operation to + // the next thread who puts the refcount back at zero (which could be us, hence the loop). + auto head = freeListHead.load(std::memory_order_relaxed); + while (true) { + node->freeListNext.store(head, std::memory_order_relaxed); + node->freeListRefs.store(1, std::memory_order_release); + if (!freeListHead.compare_exchange_strong(head, node, std::memory_order_release, std::memory_order_relaxed)) { + // Hmm, the add failed, but we can only try again when the refcount goes back to zero + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST - 1, std::memory_order_release) == 1) { + continue; + } + } + return; + } + } + + private: + // Implemented like a stack, but where node order doesn't matter (nodes are inserted out of order under contention) + std::atomic freeListHead; + + static const std::uint32_t REFS_MASK = 0x7FFFFFFF; + static const std::uint32_t SHOULD_BE_ON_FREELIST = 0x80000000; + +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugMutex mutex; +#endif + }; + + + /////////////////////////// + // Block + /////////////////////////// + + enum InnerQueueContext { implicit_context = 0, explicit_context = 1 }; + + struct Block + { + Block() + : next(nullptr), elementsCompletelyDequeued(0), freeListRefs(0), freeListNext(nullptr), shouldBeOnFreeList(false), dynamicallyAllocated(true) + { +#ifdef MCDBGQ_TRACKMEM + owner = nullptr; +#endif + } + + template + inline bool is_empty() const + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Check flags + for (size_t i = 0; i < BLOCK_SIZE; ++i) { + if (!emptyFlags[i].load(std::memory_order_relaxed)) { + return false; + } + } + + // Aha, empty; make sure we have all other memory effects that happened before the empty flags were set + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + else { + // Check counter + if (elementsCompletelyDequeued.load(std::memory_order_relaxed) == BLOCK_SIZE) { + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + assert(elementsCompletelyDequeued.load(std::memory_order_relaxed) <= BLOCK_SIZE); + return false; + } + } + + // Returns true if the block is now empty (does not apply in explicit context) + template + inline bool set_empty(MOODYCAMEL_MAYBE_UNUSED index_t i) + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set flag + assert(!emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].load(std::memory_order_relaxed)); + emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].store(true, std::memory_order_release); + return false; + } + else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(1, std::memory_order_release); + assert(prevVal < BLOCK_SIZE); + return prevVal == BLOCK_SIZE - 1; + } + } + + // Sets multiple contiguous item statuses to 'empty' (assumes no wrapping and count > 0). + // Returns true if the block is now empty (does not apply in explicit context). + template + inline bool set_many_empty(MOODYCAMEL_MAYBE_UNUSED index_t i, size_t count) + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set flags + std::atomic_thread_fence(std::memory_order_release); + i = BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1)) - count + 1; + for (size_t j = 0; j != count; ++j) { + assert(!emptyFlags[i + j].load(std::memory_order_relaxed)); + emptyFlags[i + j].store(true, std::memory_order_relaxed); + } + return false; + } + else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(count, std::memory_order_release); + assert(prevVal + count <= BLOCK_SIZE); + return prevVal + count == BLOCK_SIZE; + } + } + + template + inline void set_all_empty() + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set all flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(true, std::memory_order_relaxed); + } + } + else { + // Reset counter + elementsCompletelyDequeued.store(BLOCK_SIZE, std::memory_order_relaxed); + } + } + + template + inline void reset_empty() + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Reset flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(false, std::memory_order_relaxed); + } + } + else { + // Reset counter + elementsCompletelyDequeued.store(0, std::memory_order_relaxed); + } + } + + inline T* operator[](index_t idx) MOODYCAMEL_NOEXCEPT { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + inline T const* operator[](index_t idx) const MOODYCAMEL_NOEXCEPT { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + + private: + static_assert(std::alignment_of::value <= sizeof(T), "The queue does not support types with an alignment greater than their size at this time"); + MOODYCAMEL_ALIGNED_TYPE_LIKE(char[sizeof(T) * BLOCK_SIZE], T) elements; + public: + Block* next; + std::atomic elementsCompletelyDequeued; + std::atomic emptyFlags[BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD ? BLOCK_SIZE : 1]; + public: + std::atomic freeListRefs; + std::atomic freeListNext; + std::atomic shouldBeOnFreeList; + bool dynamicallyAllocated; // Perhaps a better name for this would be 'isNotPartOfInitialBlockPool' + +#ifdef MCDBGQ_TRACKMEM + void* owner; +#endif + }; + static_assert(std::alignment_of::value >= std::alignment_of::value, "Internal error: Blocks must be at least as aligned as the type they are wrapping"); + + +#ifdef MCDBGQ_TRACKMEM +public: + struct MemStats; +private: +#endif + + /////////////////////////// + // Producer base + /////////////////////////// + + struct ProducerBase : public details::ConcurrentQueueProducerTypelessBase + { + ProducerBase(ConcurrentQueue* parent_, bool isExplicit_) : + tailIndex(0), + headIndex(0), + dequeueOptimisticCount(0), + dequeueOvercommit(0), + tailBlock(nullptr), + isExplicit(isExplicit_), + parent(parent_) + { + } + + virtual ~ProducerBase() { } + + template + inline bool dequeue(U& element) + { + if (isExplicit) { + return static_cast(this)->dequeue(element); + } + else { + return static_cast(this)->dequeue(element); + } + } + + template + inline size_t dequeue_bulk(It& itemFirst, size_t max) + { + if (isExplicit) { + return static_cast(this)->dequeue_bulk(itemFirst, max); + } + else { + return static_cast(this)->dequeue_bulk(itemFirst, max); + } + } + + inline ProducerBase* next_prod() const { return static_cast(next); } + + inline size_t size_approx() const + { + auto tail = tailIndex.load(std::memory_order_relaxed); + auto head = headIndex.load(std::memory_order_relaxed); + return details::circular_less_than(head, tail) ? static_cast(tail - head) : 0; + } + + inline index_t getTail() const { return tailIndex.load(std::memory_order_relaxed); } + protected: + std::atomic tailIndex; // Where to enqueue to next + std::atomic headIndex; // Where to dequeue from next + + std::atomic dequeueOptimisticCount; + std::atomic dequeueOvercommit; + + Block* tailBlock; + + public: + bool isExplicit; + ConcurrentQueue* parent; + + protected: +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + /////////////////////////// + // Explicit queue + /////////////////////////// + + struct ExplicitProducer : public ProducerBase + { + explicit ExplicitProducer(ConcurrentQueue* parent_) : + ProducerBase(parent_, true), + blockIndex(nullptr), + pr_blockIndexSlotsUsed(0), + pr_blockIndexSize(EXPLICIT_INITIAL_INDEX_SIZE >> 1), + pr_blockIndexFront(0), + pr_blockIndexEntries(nullptr), + pr_blockIndexRaw(nullptr) + { + size_t poolBasedIndexSize = details::ceil_to_pow_2(parent_->initialBlockPoolSize) >> 1; + if (poolBasedIndexSize > pr_blockIndexSize) { + pr_blockIndexSize = poolBasedIndexSize; + } + + new_block_index(0); // This creates an index with double the number of current entries, i.e. EXPLICIT_INITIAL_INDEX_SIZE + } + + ~ExplicitProducer() + { + // Destruct any elements not yet dequeued. + // Since we're in the destructor, we can assume all elements + // are either completely dequeued or completely not (no halfways). + if (this->tailBlock != nullptr) { // Note this means there must be a block index too + // First find the block that's partially dequeued, if any + Block* halfDequeuedBlock = nullptr; + if ((this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) != 0) { + // The head's not on a block boundary, meaning a block somewhere is partially dequeued + // (or the head block is the tail block and was fully dequeued, but the head/tail are still not on a boundary) + size_t i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & (pr_blockIndexSize - 1); + while (details::circular_less_than(pr_blockIndexEntries[i].base + BLOCK_SIZE, this->headIndex.load(std::memory_order_relaxed))) { + i = (i + 1) & (pr_blockIndexSize - 1); + } + assert(details::circular_less_than(pr_blockIndexEntries[i].base, this->headIndex.load(std::memory_order_relaxed))); + halfDequeuedBlock = pr_blockIndexEntries[i].block; + } + + // Start at the head block (note the first line in the loop gives us the head from the tail on the first iteration) + auto block = this->tailBlock; + do { + block = block->next; + if (block->ConcurrentQueue::Block::template is_empty()) { + continue; + } + + size_t i = 0; // Offset into block + if (block == halfDequeuedBlock) { + i = static_cast(this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); + } + + // Walk through all the items in the block; if this is the tail block, we need to stop when we reach the tail index + auto lastValidIndex = (this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) == 0 ? BLOCK_SIZE : static_cast(this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); + while (i != BLOCK_SIZE && (block != this->tailBlock || i != lastValidIndex)) { + (*block)[i++]->~T(); + } + } while (block != this->tailBlock); + } + + // Destroy all blocks that we own + if (this->tailBlock != nullptr) { + auto block = this->tailBlock; + do { + auto nextBlock = block->next; + if (block->dynamicallyAllocated) { + destroy(block); + } + else { + this->parent->add_block_to_free_list(block); + } + block = nextBlock; + } while (block != this->tailBlock); + } + + // Destroy the block indices + auto header = static_cast(pr_blockIndexRaw); + while (header != nullptr) { + auto prev = static_cast(header->prev); + header->~BlockIndexHeader(); + (Traits::free)(header); + header = prev; + } + } + + template + inline bool enqueue(U&& element) + { + index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); + index_t newTailIndex = 1 + currentTailIndex; + if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + // We reached the end of a block, start a new one + auto startBlock = this->tailBlock; + auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; + if (this->tailBlock != nullptr && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { + // We can re-use the block ahead of us, it's empty! + this->tailBlock = this->tailBlock->next; + this->tailBlock->ConcurrentQueue::Block::template reset_empty(); + + // We'll put the block on the block index (guaranteed to be room since we're conceptually removing the + // last block from it first -- except instead of removing then adding, we can just overwrite). + // Note that there must be a valid block index here, since even if allocation failed in the ctor, + // it would have been re-attempted when adding the first block to the queue; since there is such + // a block, a block index must have been successfully allocated. + } + else { + // Whatever head value we see here is >= the last value we saw here (relatively), + // and <= its current value. Since we have the most recent tail, the head must be + // <= to it. + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) + || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { + // We can't enqueue in another block because there's not enough leeway -- the + // tail could surpass the head by the time the block fills up! (Or we'll exceed + // the size limit, if the second part of the condition was true.) + return false; + } + // We're going to need a new block; check that the block index has room + if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize) { + // Hmm, the circular block index is already full -- we'll need + // to allocate a new index. Note pr_blockIndexRaw can only be nullptr if + // the initial allocation failed in the constructor. + + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + return false; + } + else if (!new_block_index(pr_blockIndexSlotsUsed)) { + return false; + } + } + + // Insert a new block in the circular linked list + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + return false; + } +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + if (this->tailBlock == nullptr) { + newBlock->next = newBlock; + } + else { + newBlock->next = this->tailBlock->next; + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + ++pr_blockIndexSlotsUsed; + } + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + // The constructor may throw. We want the element not to appear in the queue in + // that case (without corrupting the queue): + MOODYCAMEL_TRY { + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + } + MOODYCAMEL_CATCH (...) { + // Revert change to the current block, but leave the new block available + // for next time + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? this->tailBlock : startBlock; + MOODYCAMEL_RETHROW; + } + } + else { + (void)startBlock; + (void)originalBlockIndexSlotsUsed; + } + + // Add block to block index + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + blockIndex.load(std::memory_order_relaxed)->front.store(pr_blockIndexFront, std::memory_order_release); + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + } + + // Enqueue + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + bool dequeue(U& element) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { + // Might be something to dequeue, let's give it a try + + // Note that this if is purely for performance purposes in the common case when the queue is + // empty and the values are eventually consistent -- we may enter here spuriously. + + // Note that whatever the values of overcommit and tail are, they are not going to change (unless we + // change them) and must be the same value at this point (inside the if) as when the if condition was + // evaluated. + + // We insert an acquire fence here to synchronize-with the release upon incrementing dequeueOvercommit below. + // This ensures that whatever the value we got loaded into overcommit, the load of dequeueOptisticCount in + // the fetch_add below will result in a value at least as recent as that (and therefore at least as large). + // Note that I believe a compiler (signal) fence here would be sufficient due to the nature of fetch_add (all + // read-modify-write operations are guaranteed to work on the latest value in the modification order), but + // unfortunately that can't be shown to be correct using only the C++11 standard. + // See http://stackoverflow.com/questions/18223161/what-are-the-c11-memory-ordering-guarantees-in-this-corner-case + std::atomic_thread_fence(std::memory_order_acquire); + + // Increment optimistic counter, then check if it went over the boundary + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); + + // Note that since dequeueOvercommit must be <= dequeueOptimisticCount (because dequeueOvercommit is only ever + // incremented after dequeueOptimisticCount -- this is enforced in the `else` block below), and since we now + // have a version of dequeueOptimisticCount that is at least as recent as overcommit (due to the release upon + // incrementing dequeueOvercommit and the acquire above that synchronizes with it), overcommit <= myDequeueCount. + // However, we can't assert this since both dequeueOptimisticCount and dequeueOvercommit may (independently) + // overflow; in such a case, though, the logic still holds since the difference between the two is maintained. + + // Note that we reload tail here in case it changed; it will be the same value as before or greater, since + // this load is sequenced after (happens after) the earlier load above. This is supported by read-read + // coherency (as defined in the standard), explained here: http://en.cppreference.com/w/cpp/atomic/memory_order + tail = this->tailIndex.load(std::memory_order_acquire); + if ((details::likely)(details::circular_less_than(myDequeueCount - overcommit, tail))) { + // Guaranteed to be at least one element to dequeue! + + // Get the index. Note that since there's guaranteed to be at least one element, this + // will never exceed tail. We need to do an acquire-release fence here since it's possible + // that whatever condition got us to this point was for an earlier enqueued element (that + // we already see the memory effects for), but that by the time we increment somebody else + // has incremented it, and we need to see the memory effects for *that* element, which is + // in such a case is necessarily visible on the thread that incremented it in the first + // place with the more current condition (they must have acquired a tail that is at least + // as recent). + auto index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); + + + // Determine which block the element is in + + auto localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); + + // We need to be careful here about subtracting and dividing because of index wrap-around. + // When an index wraps, we need to preserve the sign of the offset when dividing it by the + // block size (in order to get a correct signed block count offset in all cases): + auto headBase = localBlockIndex->entries[localBlockIndexHead].base; + auto blockBaseIndex = index & ~static_cast(BLOCK_SIZE - 1); + auto offset = static_cast(static_cast::type>(blockBaseIndex - headBase) / BLOCK_SIZE); + auto block = localBlockIndex->entries[(localBlockIndexHead + offset) & (localBlockIndex->size - 1)].block; + + // Dequeue + auto& el = *((*block)[index]); + if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { + // Make sure the element is still fully dequeued and destroyed even if the assignment + // throws + struct Guard { + Block* block; + index_t index; + + ~Guard() + { + (*block)[index]->~T(); + block->ConcurrentQueue::Block::template set_empty(index); + } + } guard = { block, index }; + + element = std::move(el); // NOLINT + } + else { + element = std::move(el); // NOLINT + el.~T(); // NOLINT + block->ConcurrentQueue::Block::template set_empty(index); + } + + return true; + } + else { + // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent + this->dequeueOvercommit.fetch_add(1, std::memory_order_release); // Release so that the fetch_add on dequeueOptimisticCount is guaranteed to happen before this write + } + } + + return false; + } + + template + bool MOODYCAMEL_NO_TSAN enqueue_bulk(It itemFirst, size_t count) + { + // First, we need to make sure we have enough room to enqueue all of the elements; + // this means pre-allocating blocks and putting them in the block index (but only if + // all the allocations succeeded). + index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); + auto startBlock = this->tailBlock; + auto originalBlockIndexFront = pr_blockIndexFront; + auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; + + Block* firstAllocatedBlock = nullptr; + + // Figure out how many blocks we'll need to allocate, and do so + size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); + index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + if (blockBaseDiff > 0) { + // Allocate as many blocks as possible from ahead + while (blockBaseDiff > 0 && this->tailBlock != nullptr && this->tailBlock->next != firstAllocatedBlock && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + this->tailBlock = this->tailBlock->next; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; + + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + } + + // Now allocate as many blocks as necessary from the block pool + while (blockBaseDiff > 0) { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); + if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize || full) { + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + // Failed to allocate, undo changes (but keep injected blocks) + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + else if (full || !new_block_index(originalBlockIndexSlotsUsed)) { + // Failed to allocate, undo changes (but keep injected blocks) + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + + // pr_blockIndexFront is updated inside new_block_index, so we need to + // update our fallback value too (since we keep the new index even if we + // later fail) + originalBlockIndexFront = originalBlockIndexSlotsUsed; + } + + // Insert a new block in the circular linked list + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template set_all_empty(); + if (this->tailBlock == nullptr) { + newBlock->next = newBlock; + } + else { + newBlock->next = this->tailBlock->next; + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; + + ++pr_blockIndexSlotsUsed; + + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + } + + // Excellent, all allocations succeeded. Reset each block's emptiness before we fill them up, and + // publish the new block index front + auto block = firstAllocatedBlock; + while (true) { + block->ConcurrentQueue::Block::template reset_empty(); + if (block == this->tailBlock) { + break; + } + block = block->next; + } + + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); + } + } + + // Enqueue, one block at a time + index_t newTailIndex = startTailIndex + static_cast(count); + currentTailIndex = startTailIndex; + auto endBlock = this->tailBlock; + this->tailBlock = startBlock; + assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { + this->tailBlock = firstAllocatedBlock; + } + while (true) { + index_t stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(newTailIndex, stopIndex)) { + stopIndex = newTailIndex; + } + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); + } + } + else { + MOODYCAMEL_TRY { + while (currentTailIndex != stopIndex) { + // Must use copy constructor even if move constructor is available + // because we may have to revert if there's an exception. + // Sorry about the horrible templated next line, but it was the only way + // to disable moving *at compile time*, which is important because a type + // may only define a (noexcept) move constructor, and so calls to the + // cctor will not compile, even if they are in an if branch that will never + // be executed + new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if(nullptr)) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); + ++currentTailIndex; + ++itemFirst; + } + } + MOODYCAMEL_CATCH (...) { + // Oh dear, an exception's been thrown -- destroy the elements that + // were enqueued so far and revert the entire bulk operation (we'll keep + // any allocated blocks in our linked list for later, though). + auto constructedStopIndex = currentTailIndex; + auto lastBlockEnqueued = this->tailBlock; + + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + + if (!details::is_trivially_destructible::value) { + auto block = startBlock; + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + block = firstAllocatedBlock; + } + currentTailIndex = startTailIndex; + while (true) { + stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(constructedStopIndex, stopIndex)) { + stopIndex = constructedStopIndex; + } + while (currentTailIndex != stopIndex) { + (*block)[currentTailIndex++]->~T(); + } + if (block == lastBlockEnqueued) { + break; + } + block = block->next; + } + } + MOODYCAMEL_RETHROW; + } + } + + if (this->tailBlock == endBlock) { + assert(currentTailIndex == newTailIndex); + break; + } + this->tailBlock = this->tailBlock->next; + } + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + if (firstAllocatedBlock != nullptr) + blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); + } + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + size_t dequeue_bulk(It& itemFirst, size_t max) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); + if (details::circular_less_than(0, desiredCount)) { + desiredCount = desiredCount < max ? desiredCount : max; + std::atomic_thread_fence(std::memory_order_acquire); + + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); + + tail = this->tailIndex.load(std::memory_order_acquire); + auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); + if (details::circular_less_than(0, actualCount)) { + actualCount = desiredCount < actualCount ? desiredCount : actualCount; + if (actualCount < desiredCount) { + this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); + } + + // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this + // will never exceed tail. + auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); + + // Determine which block the first element is in + auto localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); + + auto headBase = localBlockIndex->entries[localBlockIndexHead].base; + auto firstBlockBaseIndex = firstIndex & ~static_cast(BLOCK_SIZE - 1); + auto offset = static_cast(static_cast::type>(firstBlockBaseIndex - headBase) / BLOCK_SIZE); + auto indexIndex = (localBlockIndexHead + offset) & (localBlockIndex->size - 1); + + // Iterate the blocks and dequeue + auto index = firstIndex; + do { + auto firstIndexInBlock = index; + index_t endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + auto block = localBlockIndex->entries[indexIndex].block; + if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst++ = std::move(el); + el.~T(); + ++index; + } + } + else { + MOODYCAMEL_TRY { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; + } + } + MOODYCAMEL_CATCH (...) { + // It's too late to revert the dequeue, but we can make sure that all + // the dequeued objects are properly destroyed and the block index + // (and empty count) are properly updated before we propagate the exception + do { + block = localBlockIndex->entries[indexIndex].block; + while (index != endIndex) { + (*block)[index++]->~T(); + } + block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); + indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); + + firstIndexInBlock = index; + endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + } while (index != firstIndex + actualCount); + + MOODYCAMEL_RETHROW; + } + } + block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); + indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); + } while (index != firstIndex + actualCount); + + return actualCount; + } + else { + // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent + this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); + } + } + + return 0; + } + + private: + struct BlockIndexEntry + { + index_t base; + Block* block; + }; + + struct BlockIndexHeader + { + size_t size; + std::atomic front; // Current slot (not next, like pr_blockIndexFront) + BlockIndexEntry* entries; + void* prev; + }; + + + bool new_block_index(size_t numberOfFilledSlotsToExpose) + { + auto prevBlockSizeMask = pr_blockIndexSize - 1; + + // Create the new block + pr_blockIndexSize <<= 1; + auto newRawPtr = static_cast((Traits::malloc)(sizeof(BlockIndexHeader) + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * pr_blockIndexSize)); + if (newRawPtr == nullptr) { + pr_blockIndexSize >>= 1; // Reset to allow graceful retry + return false; + } + + auto newBlockIndexEntries = reinterpret_cast(details::align_for(newRawPtr + sizeof(BlockIndexHeader))); + + // Copy in all the old indices, if any + size_t j = 0; + if (pr_blockIndexSlotsUsed != 0) { + auto i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & prevBlockSizeMask; + do { + newBlockIndexEntries[j++] = pr_blockIndexEntries[i]; + i = (i + 1) & prevBlockSizeMask; + } while (i != pr_blockIndexFront); + } + + // Update everything + auto header = new (newRawPtr) BlockIndexHeader; + header->size = pr_blockIndexSize; + header->front.store(numberOfFilledSlotsToExpose - 1, std::memory_order_relaxed); + header->entries = newBlockIndexEntries; + header->prev = pr_blockIndexRaw; // we link the new block to the old one so we can free it later + + pr_blockIndexFront = j; + pr_blockIndexEntries = newBlockIndexEntries; + pr_blockIndexRaw = newRawPtr; + blockIndex.store(header, std::memory_order_release); + + return true; + } + + private: + std::atomic blockIndex; + + // To be used by producer only -- consumer must use the ones in referenced by blockIndex + size_t pr_blockIndexSlotsUsed; + size_t pr_blockIndexSize; + size_t pr_blockIndexFront; // Next slot (not current) + BlockIndexEntry* pr_blockIndexEntries; + void* pr_blockIndexRaw; + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + public: + ExplicitProducer* nextExplicitProducer; + private: +#endif + +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + ////////////////////////////////// + // Implicit queue + ////////////////////////////////// + + struct ImplicitProducer : public ProducerBase + { + ImplicitProducer(ConcurrentQueue* parent_) : + ProducerBase(parent_, false), + nextBlockIndexCapacity(IMPLICIT_INITIAL_INDEX_SIZE), + blockIndex(nullptr) + { + new_block_index(); + } + + ~ImplicitProducer() + { + // Note that since we're in the destructor we can assume that all enqueue/dequeue operations + // completed already; this means that all undequeued elements are placed contiguously across + // contiguous blocks, and that only the first and last remaining blocks can be only partially + // empty (all other remaining blocks must be completely full). + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + // Unregister ourselves for thread termination notification + if (!this->inactive.load(std::memory_order_relaxed)) { + details::ThreadExitNotifier::unsubscribe(&threadExitListener); + } +#endif + + // Destroy all remaining elements! + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto index = this->headIndex.load(std::memory_order_relaxed); + Block* block = nullptr; + assert(index == tail || details::circular_less_than(index, tail)); + bool forceFreeLastBlock = index != tail; // If we enter the loop, then the last (tail) block will not be freed + while (index != tail) { + if ((index & static_cast(BLOCK_SIZE - 1)) == 0 || block == nullptr) { + if (block != nullptr) { + // Free the old block + this->parent->add_block_to_free_list(block); + } + + block = get_block_index_entry_for_index(index)->value.load(std::memory_order_relaxed); + } + + ((*block)[index])->~T(); + ++index; + } + // Even if the queue is empty, there's still one block that's not on the free list + // (unless the head index reached the end of it, in which case the tail will be poised + // to create a new block). + if (this->tailBlock != nullptr && (forceFreeLastBlock || (tail & static_cast(BLOCK_SIZE - 1)) != 0)) { + this->parent->add_block_to_free_list(this->tailBlock); + } + + // Destroy block index + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); + if (localBlockIndex != nullptr) { + for (size_t i = 0; i != localBlockIndex->capacity; ++i) { + localBlockIndex->index[i]->~BlockIndexEntry(); + } + do { + auto prev = localBlockIndex->prev; + localBlockIndex->~BlockIndexHeader(); + (Traits::free)(localBlockIndex); + localBlockIndex = prev; + } while (localBlockIndex != nullptr); + } + } + + template + inline bool enqueue(U&& element) + { + index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); + index_t newTailIndex = 1 + currentTailIndex; + if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + // We reached the end of a block, start a new one + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { + return false; + } +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Find out where we'll be inserting this block in the block index + BlockIndexEntry* idxEntry; + if (!insert_block_index_entry(idxEntry, currentTailIndex)) { + return false; + } + + // Get ahold of a new block + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + return false; + } +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + // May throw, try to insert now before we publish the fact that we have this new block + MOODYCAMEL_TRY { + new ((*newBlock)[currentTailIndex]) T(std::forward(element)); + } + MOODYCAMEL_CATCH (...) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + this->parent->add_block_to_free_list(newBlock); + MOODYCAMEL_RETHROW; + } + } + + // Insert the new block into the index + idxEntry->value.store(newBlock, std::memory_order_relaxed); + + this->tailBlock = newBlock; + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + } + + // Enqueue + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + bool dequeue(U& element) + { + // See ExplicitProducer::dequeue for rationale and explanation + index_t tail = this->tailIndex.load(std::memory_order_relaxed); + index_t overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { + std::atomic_thread_fence(std::memory_order_acquire); + + index_t myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); + tail = this->tailIndex.load(std::memory_order_acquire); + if ((details::likely)(details::circular_less_than(myDequeueCount - overcommit, tail))) { + index_t index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); + + // Determine which block the element is in + auto entry = get_block_index_entry_for_index(index); + + // Dequeue + auto block = entry->value.load(std::memory_order_relaxed); + auto& el = *((*block)[index]); + + if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + // Note: Acquiring the mutex with every dequeue instead of only when a block + // is released is very sub-optimal, but it is, after all, purely debug code. + debug::DebugLock lock(producer->mutex); +#endif + struct Guard { + Block* block; + index_t index; + BlockIndexEntry* entry; + ConcurrentQueue* parent; + + ~Guard() + { + (*block)[index]->~T(); + if (block->ConcurrentQueue::Block::template set_empty(index)) { + entry->value.store(nullptr, std::memory_order_relaxed); + parent->add_block_to_free_list(block); + } + } + } guard = { block, index, entry, this->parent }; + + element = std::move(el); // NOLINT + } + else { + element = std::move(el); // NOLINT + el.~T(); // NOLINT + + if (block->ConcurrentQueue::Block::template set_empty(index)) { + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Add the block back into the global free pool (and remove from block index) + entry->value.store(nullptr, std::memory_order_relaxed); + } + this->parent->add_block_to_free_list(block); // releases the above store + } + } + + return true; + } + else { + this->dequeueOvercommit.fetch_add(1, std::memory_order_release); + } + } + + return false; + } + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4706) // assignment within conditional expression +#endif + template + bool enqueue_bulk(It itemFirst, size_t count) + { + // First, we need to make sure we have enough room to enqueue all of the elements; + // this means pre-allocating blocks and putting them in the block index (but only if + // all the allocations succeeded). + + // Note that the tailBlock we start off with may not be owned by us any more; + // this happens if it was filled up exactly to the top (setting tailIndex to + // the first index of the next block which is not yet allocated), then dequeued + // completely (putting it on the free list) before we enqueue again. + + index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); + auto startBlock = this->tailBlock; + Block* firstAllocatedBlock = nullptr; + auto endBlock = this->tailBlock; + + // Figure out how many blocks we'll need to allocate, and do so + size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); + index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + if (blockBaseDiff > 0) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + do { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + // Find out where we'll be inserting this block in the block index + BlockIndexEntry* idxEntry = nullptr; // initialization here unnecessary but compiler can't always tell + Block* newBlock; + bool indexInserted = false; + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); + + if (full || !(indexInserted = insert_block_index_entry(idxEntry, currentTailIndex)) || (newBlock = this->parent->ConcurrentQueue::template requisition_block()) == nullptr) { + // Index allocation or block allocation failed; revert any other allocations + // and index insertions done so far for this operation + if (indexInserted) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + } + currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { + currentTailIndex += static_cast(BLOCK_SIZE); + idxEntry = get_block_index_entry_for_index(currentTailIndex); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + rewind_block_index_tail(); + } + this->parent->add_blocks_to_free_list(firstAllocatedBlock); + this->tailBlock = startBlock; + + return false; + } + +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + newBlock->next = nullptr; + + // Insert the new block into the index + idxEntry->value.store(newBlock, std::memory_order_relaxed); + + // Store the chain of blocks so that we can undo if later allocations fail, + // and so that we can find the blocks when we do the actual enqueueing + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr) { + assert(this->tailBlock != nullptr); + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + endBlock = newBlock; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? newBlock : firstAllocatedBlock; + } while (blockBaseDiff > 0); + } + + // Enqueue, one block at a time + index_t newTailIndex = startTailIndex + static_cast(count); + currentTailIndex = startTailIndex; + this->tailBlock = startBlock; + assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { + this->tailBlock = firstAllocatedBlock; + } + while (true) { + index_t stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(newTailIndex, stopIndex)) { + stopIndex = newTailIndex; + } + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); + } + } + else { + MOODYCAMEL_TRY { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if(nullptr)) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); + ++currentTailIndex; + ++itemFirst; + } + } + MOODYCAMEL_CATCH (...) { + auto constructedStopIndex = currentTailIndex; + auto lastBlockEnqueued = this->tailBlock; + + if (!details::is_trivially_destructible::value) { + auto block = startBlock; + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + block = firstAllocatedBlock; + } + currentTailIndex = startTailIndex; + while (true) { + stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(constructedStopIndex, stopIndex)) { + stopIndex = constructedStopIndex; + } + while (currentTailIndex != stopIndex) { + (*block)[currentTailIndex++]->~T(); + } + if (block == lastBlockEnqueued) { + break; + } + block = block->next; + } + } + + currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { + currentTailIndex += static_cast(BLOCK_SIZE); + auto idxEntry = get_block_index_entry_for_index(currentTailIndex); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + rewind_block_index_tail(); + } + this->parent->add_blocks_to_free_list(firstAllocatedBlock); + this->tailBlock = startBlock; + MOODYCAMEL_RETHROW; + } + } + + if (this->tailBlock == endBlock) { + assert(currentTailIndex == newTailIndex); + break; + } + this->tailBlock = this->tailBlock->next; + } + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + template + size_t dequeue_bulk(It& itemFirst, size_t max) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); + if (details::circular_less_than(0, desiredCount)) { + desiredCount = desiredCount < max ? desiredCount : max; + std::atomic_thread_fence(std::memory_order_acquire); + + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); + + tail = this->tailIndex.load(std::memory_order_acquire); + auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); + if (details::circular_less_than(0, actualCount)) { + actualCount = desiredCount < actualCount ? desiredCount : actualCount; + if (actualCount < desiredCount) { + this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); + } + + // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this + // will never exceed tail. + auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); + + // Iterate the blocks and dequeue + auto index = firstIndex; + BlockIndexHeader* localBlockIndex; + auto indexIndex = get_block_index_index_for_index(index, localBlockIndex); + do { + auto blockStartIndex = index; + index_t endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + + auto entry = localBlockIndex->index[indexIndex]; + auto block = entry->value.load(std::memory_order_relaxed); + if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst++ = std::move(el); + el.~T(); + ++index; + } + } + else { + MOODYCAMEL_TRY { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; + } + } + MOODYCAMEL_CATCH (...) { + do { + entry = localBlockIndex->index[indexIndex]; + block = entry->value.load(std::memory_order_relaxed); + while (index != endIndex) { + (*block)[index++]->~T(); + } + + if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + entry->value.store(nullptr, std::memory_order_relaxed); + this->parent->add_block_to_free_list(block); + } + indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); + + blockStartIndex = index; + endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + } while (index != firstIndex + actualCount); + + MOODYCAMEL_RETHROW; + } + } + if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Note that the set_many_empty above did a release, meaning that anybody who acquires the block + // we're about to free can use it safely since our writes (and reads!) will have happened-before then. + entry->value.store(nullptr, std::memory_order_relaxed); + } + this->parent->add_block_to_free_list(block); // releases the above store + } + indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); + } while (index != firstIndex + actualCount); + + return actualCount; + } + else { + this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); + } + } + + return 0; + } + + private: + // The block size must be > 1, so any number with the low bit set is an invalid block base index + static const index_t INVALID_BLOCK_BASE = 1; + + struct BlockIndexEntry + { + std::atomic key; + std::atomic value; + }; + + struct BlockIndexHeader + { + size_t capacity; + std::atomic tail; + BlockIndexEntry* entries; + BlockIndexEntry** index; + BlockIndexHeader* prev; + }; + + template + inline bool insert_block_index_entry(BlockIndexEntry*& idxEntry, index_t blockStartIndex) + { + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); // We're the only writer thread, relaxed is OK + if (localBlockIndex == nullptr) { + return false; // this can happen if new_block_index failed in the constructor + } + size_t newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); + idxEntry = localBlockIndex->index[newTail]; + if (idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE || + idxEntry->value.load(std::memory_order_relaxed) == nullptr) { + + idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); + localBlockIndex->tail.store(newTail, std::memory_order_release); + return true; + } + + // No room in the old block index, try to allocate another one! + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + return false; + } + else if (!new_block_index()) { + return false; + } + localBlockIndex = blockIndex.load(std::memory_order_relaxed); + newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); + idxEntry = localBlockIndex->index[newTail]; + assert(idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE); + idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); + localBlockIndex->tail.store(newTail, std::memory_order_release); + return true; + } + + inline void rewind_block_index_tail() + { + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); + localBlockIndex->tail.store((localBlockIndex->tail.load(std::memory_order_relaxed) - 1) & (localBlockIndex->capacity - 1), std::memory_order_relaxed); + } + + inline BlockIndexEntry* get_block_index_entry_for_index(index_t index) const + { + BlockIndexHeader* localBlockIndex; + auto idx = get_block_index_index_for_index(index, localBlockIndex); + return localBlockIndex->index[idx]; + } + + inline size_t get_block_index_index_for_index(index_t index, BlockIndexHeader*& localBlockIndex) const + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + index &= ~static_cast(BLOCK_SIZE - 1); + localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto tail = localBlockIndex->tail.load(std::memory_order_acquire); + auto tailBase = localBlockIndex->index[tail]->key.load(std::memory_order_relaxed); + assert(tailBase != INVALID_BLOCK_BASE); + // Note: Must use division instead of shift because the index may wrap around, causing a negative + // offset, whose negativity we want to preserve + auto offset = static_cast(static_cast::type>(index - tailBase) / BLOCK_SIZE); + size_t idx = (tail + offset) & (localBlockIndex->capacity - 1); + assert(localBlockIndex->index[idx]->key.load(std::memory_order_relaxed) == index && localBlockIndex->index[idx]->value.load(std::memory_order_relaxed) != nullptr); + return idx; + } + + bool new_block_index() + { + auto prev = blockIndex.load(std::memory_order_relaxed); + size_t prevCapacity = prev == nullptr ? 0 : prev->capacity; + auto entryCount = prev == nullptr ? nextBlockIndexCapacity : prevCapacity; + auto raw = static_cast((Traits::malloc)( + sizeof(BlockIndexHeader) + + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * entryCount + + std::alignment_of::value - 1 + sizeof(BlockIndexEntry*) * nextBlockIndexCapacity)); + if (raw == nullptr) { + return false; + } + + auto header = new (raw) BlockIndexHeader; + auto entries = reinterpret_cast(details::align_for(raw + sizeof(BlockIndexHeader))); + auto index = reinterpret_cast(details::align_for(reinterpret_cast(entries) + sizeof(BlockIndexEntry) * entryCount)); + if (prev != nullptr) { + auto prevTail = prev->tail.load(std::memory_order_relaxed); + auto prevPos = prevTail; + size_t i = 0; + do { + prevPos = (prevPos + 1) & (prev->capacity - 1); + index[i++] = prev->index[prevPos]; + } while (prevPos != prevTail); + assert(i == prevCapacity); + } + for (size_t i = 0; i != entryCount; ++i) { + new (entries + i) BlockIndexEntry; + entries[i].key.store(INVALID_BLOCK_BASE, std::memory_order_relaxed); + index[prevCapacity + i] = entries + i; + } + header->prev = prev; + header->entries = entries; + header->index = index; + header->capacity = nextBlockIndexCapacity; + header->tail.store((prevCapacity - 1) & (nextBlockIndexCapacity - 1), std::memory_order_relaxed); + + blockIndex.store(header, std::memory_order_release); + + nextBlockIndexCapacity <<= 1; + + return true; + } + + private: + size_t nextBlockIndexCapacity; + std::atomic blockIndex; + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + public: + details::ThreadExitListener threadExitListener; + private: +#endif + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + public: + ImplicitProducer* nextImplicitProducer; + private: +#endif + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + mutable debug::DebugMutex mutex; +#endif +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + ////////////////////////////////// + // Block pool manipulation + ////////////////////////////////// + + void populate_initial_block_list(size_t blockCount) + { + initialBlockPoolSize = blockCount; + if (initialBlockPoolSize == 0) { + initialBlockPool = nullptr; + return; + } + + initialBlockPool = create_array(blockCount); + if (initialBlockPool == nullptr) { + initialBlockPoolSize = 0; + } + for (size_t i = 0; i < initialBlockPoolSize; ++i) { + initialBlockPool[i].dynamicallyAllocated = false; + } + } + + inline Block* try_get_block_from_initial_pool() + { + if (initialBlockPoolIndex.load(std::memory_order_relaxed) >= initialBlockPoolSize) { + return nullptr; + } + + auto index = initialBlockPoolIndex.fetch_add(1, std::memory_order_relaxed); + + return index < initialBlockPoolSize ? (initialBlockPool + index) : nullptr; + } + + inline void add_block_to_free_list(Block* block) + { +#ifdef MCDBGQ_TRACKMEM + block->owner = nullptr; +#endif + freeList.add(block); + } + + inline void add_blocks_to_free_list(Block* block) + { + while (block != nullptr) { + auto next = block->next; + add_block_to_free_list(block); + block = next; + } + } + + inline Block* try_get_block_from_free_list() + { + return freeList.try_get(); + } + + // Gets a free block from one of the memory pools, or allocates a new one (if applicable) + template + Block* requisition_block() + { + auto block = try_get_block_from_initial_pool(); + if (block != nullptr) { + return block; + } + + block = try_get_block_from_free_list(); + if (block != nullptr) { + return block; + } + + MOODYCAMEL_CONSTEXPR_IF (canAlloc == CanAlloc) { + return create(); + } + else { + return nullptr; + } + } + + +#ifdef MCDBGQ_TRACKMEM + public: + struct MemStats { + size_t allocatedBlocks; + size_t usedBlocks; + size_t freeBlocks; + size_t ownedBlocksExplicit; + size_t ownedBlocksImplicit; + size_t implicitProducers; + size_t explicitProducers; + size_t elementsEnqueued; + size_t blockClassBytes; + size_t queueClassBytes; + size_t implicitBlockIndexBytes; + size_t explicitBlockIndexBytes; + + friend class ConcurrentQueue; + + private: + static MemStats getFor(ConcurrentQueue* q) + { + MemStats stats = { 0 }; + + stats.elementsEnqueued = q->size_approx(); + + auto block = q->freeList.head_unsafe(); + while (block != nullptr) { + ++stats.allocatedBlocks; + ++stats.freeBlocks; + block = block->freeListNext.load(std::memory_order_relaxed); + } + + for (auto ptr = q->producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + bool implicit = dynamic_cast(ptr) != nullptr; + stats.implicitProducers += implicit ? 1 : 0; + stats.explicitProducers += implicit ? 0 : 1; + + if (implicit) { + auto prod = static_cast(ptr); + stats.queueClassBytes += sizeof(ImplicitProducer); + auto head = prod->headIndex.load(std::memory_order_relaxed); + auto tail = prod->tailIndex.load(std::memory_order_relaxed); + auto hash = prod->blockIndex.load(std::memory_order_relaxed); + if (hash != nullptr) { + for (size_t i = 0; i != hash->capacity; ++i) { + if (hash->index[i]->key.load(std::memory_order_relaxed) != ImplicitProducer::INVALID_BLOCK_BASE && hash->index[i]->value.load(std::memory_order_relaxed) != nullptr) { + ++stats.allocatedBlocks; + ++stats.ownedBlocksImplicit; + } + } + stats.implicitBlockIndexBytes += hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry); + for (; hash != nullptr; hash = hash->prev) { + stats.implicitBlockIndexBytes += sizeof(typename ImplicitProducer::BlockIndexHeader) + hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry*); + } + } + for (; details::circular_less_than(head, tail); head += BLOCK_SIZE) { + //auto block = prod->get_block_index_entry_for_index(head); + ++stats.usedBlocks; + } + } + else { + auto prod = static_cast(ptr); + stats.queueClassBytes += sizeof(ExplicitProducer); + auto tailBlock = prod->tailBlock; + bool wasNonEmpty = false; + if (tailBlock != nullptr) { + auto block = tailBlock; + do { + ++stats.allocatedBlocks; + if (!block->ConcurrentQueue::Block::template is_empty() || wasNonEmpty) { + ++stats.usedBlocks; + wasNonEmpty = wasNonEmpty || block != tailBlock; + } + ++stats.ownedBlocksExplicit; + block = block->next; + } while (block != tailBlock); + } + auto index = prod->blockIndex.load(std::memory_order_relaxed); + while (index != nullptr) { + stats.explicitBlockIndexBytes += sizeof(typename ExplicitProducer::BlockIndexHeader) + index->size * sizeof(typename ExplicitProducer::BlockIndexEntry); + index = static_cast(index->prev); + } + } + } + + auto freeOnInitialPool = q->initialBlockPoolIndex.load(std::memory_order_relaxed) >= q->initialBlockPoolSize ? 0 : q->initialBlockPoolSize - q->initialBlockPoolIndex.load(std::memory_order_relaxed); + stats.allocatedBlocks += freeOnInitialPool; + stats.freeBlocks += freeOnInitialPool; + + stats.blockClassBytes = sizeof(Block) * stats.allocatedBlocks; + stats.queueClassBytes += sizeof(ConcurrentQueue); + + return stats; + } + }; + + // For debugging only. Not thread-safe. + MemStats getMemStats() + { + return MemStats::getFor(this); + } + private: + friend struct MemStats; +#endif + + + ////////////////////////////////// + // Producer list manipulation + ////////////////////////////////// + + ProducerBase* recycle_or_create_producer(bool isExplicit) + { + bool recycled; + return recycle_or_create_producer(isExplicit, recycled); + } + + ProducerBase* recycle_or_create_producer(bool isExplicit, bool& recycled) + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + // Try to re-use one first + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr->inactive.load(std::memory_order_relaxed) && ptr->isExplicit == isExplicit) { + bool expected = true; + if (ptr->inactive.compare_exchange_strong(expected, /* desired */ false, std::memory_order_acquire, std::memory_order_relaxed)) { + // We caught one! It's been marked as activated, the caller can have it + recycled = true; + return ptr; + } + } + } + + recycled = false; + return add_producer(isExplicit ? static_cast(create(this)) : create(this)); + } + + ProducerBase* add_producer(ProducerBase* producer) + { + // Handle failed memory allocation + if (producer == nullptr) { + return nullptr; + } + + producerCount.fetch_add(1, std::memory_order_relaxed); + + // Add it to the lock-free list + auto prevTail = producerListTail.load(std::memory_order_relaxed); + do { + producer->next = prevTail; + } while (!producerListTail.compare_exchange_weak(prevTail, producer, std::memory_order_release, std::memory_order_relaxed)); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + if (producer->isExplicit) { + auto prevTailExplicit = explicitProducers.load(std::memory_order_relaxed); + do { + static_cast(producer)->nextExplicitProducer = prevTailExplicit; + } while (!explicitProducers.compare_exchange_weak(prevTailExplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); + } + else { + auto prevTailImplicit = implicitProducers.load(std::memory_order_relaxed); + do { + static_cast(producer)->nextImplicitProducer = prevTailImplicit; + } while (!implicitProducers.compare_exchange_weak(prevTailImplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); + } +#endif + + return producer; + } + + void reown_producers() + { + // After another instance is moved-into/swapped-with this one, all the + // producers we stole still think their parents are the other queue. + // So fix them up! + for (auto ptr = producerListTail.load(std::memory_order_relaxed); ptr != nullptr; ptr = ptr->next_prod()) { + ptr->parent = this; + } + } + + + ////////////////////////////////// + // Implicit producer hash + ////////////////////////////////// + + struct ImplicitProducerKVP + { + std::atomic key; + ImplicitProducer* value; // No need for atomicity since it's only read by the thread that sets it in the first place + + ImplicitProducerKVP() : value(nullptr) { } + + ImplicitProducerKVP(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT + { + key.store(other.key.load(std::memory_order_relaxed), std::memory_order_relaxed); + value = other.value; + } + + inline ImplicitProducerKVP& operator=(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + inline void swap(ImplicitProducerKVP& other) MOODYCAMEL_NOEXCEPT + { + if (this != &other) { + details::swap_relaxed(key, other.key); + std::swap(value, other.value); + } + } + }; + + template + friend void moodycamel::swap(typename ConcurrentQueue::ImplicitProducerKVP&, typename ConcurrentQueue::ImplicitProducerKVP&) MOODYCAMEL_NOEXCEPT; + + struct ImplicitProducerHash + { + size_t capacity; + ImplicitProducerKVP* entries; + ImplicitProducerHash* prev; + }; + + inline void populate_initial_implicit_producer_hash() + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) { + return; + } + else { + implicitProducerHashCount.store(0, std::memory_order_relaxed); + auto hash = &initialImplicitProducerHash; + hash->capacity = INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; + hash->entries = &initialImplicitProducerHashEntries[0]; + for (size_t i = 0; i != INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; ++i) { + initialImplicitProducerHashEntries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); + } + hash->prev = nullptr; + implicitProducerHash.store(hash, std::memory_order_relaxed); + } + } + + void swap_implicit_producer_hashes(ConcurrentQueue& other) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) { + return; + } + else { + // Swap (assumes our implicit producer hash is initialized) + initialImplicitProducerHashEntries.swap(other.initialImplicitProducerHashEntries); + initialImplicitProducerHash.entries = &initialImplicitProducerHashEntries[0]; + other.initialImplicitProducerHash.entries = &other.initialImplicitProducerHashEntries[0]; + + details::swap_relaxed(implicitProducerHashCount, other.implicitProducerHashCount); + + details::swap_relaxed(implicitProducerHash, other.implicitProducerHash); + if (implicitProducerHash.load(std::memory_order_relaxed) == &other.initialImplicitProducerHash) { + implicitProducerHash.store(&initialImplicitProducerHash, std::memory_order_relaxed); + } + else { + ImplicitProducerHash* hash; + for (hash = implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &other.initialImplicitProducerHash; hash = hash->prev) { + continue; + } + hash->prev = &initialImplicitProducerHash; + } + if (other.implicitProducerHash.load(std::memory_order_relaxed) == &initialImplicitProducerHash) { + other.implicitProducerHash.store(&other.initialImplicitProducerHash, std::memory_order_relaxed); + } + else { + ImplicitProducerHash* hash; + for (hash = other.implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &initialImplicitProducerHash; hash = hash->prev) { + continue; + } + hash->prev = &other.initialImplicitProducerHash; + } + } + } + + // Only fails (returns nullptr) if memory allocation fails + ImplicitProducer* get_or_add_implicit_producer() + { + // Note that since the data is essentially thread-local (key is thread ID), + // there's a reduced need for fences (memory ordering is already consistent + // for any individual thread), except for the current table itself. + + // Start by looking for the thread ID in the current and all previous hash tables. + // If it's not found, it must not be in there yet, since this same thread would + // have added it previously to one of the tables that we traversed. + + // Code and algorithm adapted from http://preshing.com/20130605/the-worlds-simplest-lock-free-hash-table + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + + auto id = details::thread_id(); + auto hashedId = details::hash_thread_id(id); + + auto mainHash = implicitProducerHash.load(std::memory_order_acquire); + assert(mainHash != nullptr); // silence clang-tidy and MSVC warnings (hash cannot be null) + for (auto hash = mainHash; hash != nullptr; hash = hash->prev) { + // Look for the id in this hash + auto index = hashedId; + while (true) { // Not an infinite loop because at least one slot is free in the hash table + index &= hash->capacity - 1; + + auto probedKey = hash->entries[index].key.load(std::memory_order_relaxed); + if (probedKey == id) { + // Found it! If we had to search several hashes deep, though, we should lazily add it + // to the current main hash table to avoid the extended search next time. + // Note there's guaranteed to be room in the current hash table since every subsequent + // table implicitly reserves space for all previous tables (there's only one + // implicitProducerHashCount). + auto value = hash->entries[index].value; + if (hash != mainHash) { + index = hashedId; + while (true) { + index &= mainHash->capacity - 1; + probedKey = mainHash->entries[index].key.load(std::memory_order_relaxed); + auto empty = details::invalid_thread_id; +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + auto reusable = details::invalid_thread_id2; + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed, std::memory_order_relaxed)) || + (probedKey == reusable && mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_acquire, std::memory_order_acquire))) { +#else + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed, std::memory_order_relaxed))) { +#endif + mainHash->entries[index].value = value; + break; + } + ++index; + } + } + + return value; + } + if (probedKey == details::invalid_thread_id) { + break; // Not in this hash table + } + ++index; + } + } + + // Insert! + auto newCount = 1 + implicitProducerHashCount.fetch_add(1, std::memory_order_relaxed); + while (true) { + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) + if (newCount >= (mainHash->capacity >> 1) && !implicitProducerHashResizeInProgress.test_and_set(std::memory_order_acquire)) { + // We've acquired the resize lock, try to allocate a bigger hash table. + // Note the acquire fence synchronizes with the release fence at the end of this block, and hence when + // we reload implicitProducerHash it must be the most recent version (it only gets changed within this + // locked block). + mainHash = implicitProducerHash.load(std::memory_order_acquire); + if (newCount >= (mainHash->capacity >> 1)) { + auto newCapacity = mainHash->capacity << 1; + while (newCount >= (newCapacity >> 1)) { + newCapacity <<= 1; + } + auto raw = static_cast((Traits::malloc)(sizeof(ImplicitProducerHash) + std::alignment_of::value - 1 + sizeof(ImplicitProducerKVP) * newCapacity)); + if (raw == nullptr) { + // Allocation failed + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + return nullptr; + } + + auto newHash = new (raw) ImplicitProducerHash; + newHash->capacity = static_cast(newCapacity); + newHash->entries = reinterpret_cast(details::align_for(raw + sizeof(ImplicitProducerHash))); + for (size_t i = 0; i != newCapacity; ++i) { + new (newHash->entries + i) ImplicitProducerKVP; + newHash->entries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); + } + newHash->prev = mainHash; + implicitProducerHash.store(newHash, std::memory_order_release); + implicitProducerHashResizeInProgress.clear(std::memory_order_release); + mainHash = newHash; + } + else { + implicitProducerHashResizeInProgress.clear(std::memory_order_release); + } + } + + // If it's < three-quarters full, add to the old one anyway so that we don't have to wait for the next table + // to finish being allocated by another thread (and if we just finished allocating above, the condition will + // always be true) + if (newCount < (mainHash->capacity >> 1) + (mainHash->capacity >> 2)) { + bool recycled; + auto producer = static_cast(recycle_or_create_producer(false, recycled)); + if (producer == nullptr) { + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); + return nullptr; + } + if (recycled) { + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); + } + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + producer->threadExitListener.callback = &ConcurrentQueue::implicit_producer_thread_exited_callback; + producer->threadExitListener.userData = producer; + details::ThreadExitNotifier::subscribe(&producer->threadExitListener); +#endif + + auto index = hashedId; + while (true) { + index &= mainHash->capacity - 1; + auto probedKey = mainHash->entries[index].key.load(std::memory_order_relaxed); + + auto empty = details::invalid_thread_id; +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + auto reusable = details::invalid_thread_id2; + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed, std::memory_order_relaxed)) || + (probedKey == reusable && mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_acquire, std::memory_order_acquire))) { +#else + if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed, std::memory_order_relaxed))) { +#endif + mainHash->entries[index].value = producer; + break; + } + ++index; + } + return producer; + } + + // Hmm, the old hash is quite full and somebody else is busy allocating a new one. + // We need to wait for the allocating thread to finish (if it succeeds, we add, if not, + // we try to allocate ourselves). + mainHash = implicitProducerHash.load(std::memory_order_acquire); + } + } + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + void implicit_producer_thread_exited(ImplicitProducer* producer) + { + // Remove from thread exit listeners + details::ThreadExitNotifier::unsubscribe(&producer->threadExitListener); + + // Remove from hash +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + auto hash = implicitProducerHash.load(std::memory_order_acquire); + assert(hash != nullptr); // The thread exit listener is only registered if we were added to a hash in the first place + auto id = details::thread_id(); + auto hashedId = details::hash_thread_id(id); + details::thread_id_t probedKey; + + // We need to traverse all the hashes just in case other threads aren't on the current one yet and are + // trying to add an entry thinking there's a free slot (because they reused a producer) + for (; hash != nullptr; hash = hash->prev) { + auto index = hashedId; + do { + index &= hash->capacity - 1; + probedKey = hash->entries[index].key.load(std::memory_order_relaxed); + if (probedKey == id) { + hash->entries[index].key.store(details::invalid_thread_id2, std::memory_order_release); + break; + } + ++index; + } while (probedKey != details::invalid_thread_id); // Can happen if the hash has changed but we weren't put back in it yet, or if we weren't added to this hash in the first place + } + + // Mark the queue as being recyclable + producer->inactive.store(true, std::memory_order_release); + } + + static void implicit_producer_thread_exited_callback(void* userData) + { + auto producer = static_cast(userData); + auto queue = producer->parent; + queue->implicit_producer_thread_exited(producer); + } +#endif + + ////////////////////////////////// + // Utility functions + ////////////////////////////////// + + template + static inline void* aligned_malloc(size_t size) + { + MOODYCAMEL_CONSTEXPR_IF (std::alignment_of::value <= std::alignment_of::value) + return (Traits::malloc)(size); + else { + size_t alignment = std::alignment_of::value; + void* raw = (Traits::malloc)(size + alignment - 1 + sizeof(void*)); + if (!raw) + return nullptr; + char* ptr = details::align_for(reinterpret_cast(raw) + sizeof(void*)); + *(reinterpret_cast(ptr) - 1) = raw; + return ptr; + } + } + + template + static inline void aligned_free(void* ptr) + { + MOODYCAMEL_CONSTEXPR_IF (std::alignment_of::value <= std::alignment_of::value) + return (Traits::free)(ptr); + else + (Traits::free)(ptr ? *(reinterpret_cast(ptr) - 1) : nullptr); + } + + template + static inline U* create_array(size_t count) + { + assert(count > 0); + U* p = static_cast(aligned_malloc(sizeof(U) * count)); + if (p == nullptr) + return nullptr; + + for (size_t i = 0; i != count; ++i) + new (p + i) U(); + return p; + } + + template + static inline void destroy_array(U* p, size_t count) + { + if (p != nullptr) { + assert(count > 0); + for (size_t i = count; i != 0; ) + (p + --i)->~U(); + } + aligned_free(p); + } + + template + static inline U* create() + { + void* p = aligned_malloc(sizeof(U)); + return p != nullptr ? new (p) U : nullptr; + } + + template + static inline U* create(A1&& a1) + { + void* p = aligned_malloc(sizeof(U)); + return p != nullptr ? new (p) U(std::forward(a1)) : nullptr; + } + + template + static inline void destroy(U* p) + { + if (p != nullptr) + p->~U(); + aligned_free(p); + } + +private: + std::atomic producerListTail; + std::atomic producerCount; + + std::atomic initialBlockPoolIndex; + Block* initialBlockPool; + size_t initialBlockPoolSize; + +#ifndef MCDBGQ_USEDEBUGFREELIST + FreeList freeList; +#else + debug::DebugFreeList freeList; +#endif + + std::atomic implicitProducerHash; + std::atomic implicitProducerHashCount; // Number of slots logically used + ImplicitProducerHash initialImplicitProducerHash; + std::array initialImplicitProducerHashEntries; + std::atomic_flag implicitProducerHashResizeInProgress; + + std::atomic nextExplicitConsumerId; + std::atomic globalExplicitConsumerOffset; + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugMutex implicitProdMutex; +#endif + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + std::atomic explicitProducers; + std::atomic implicitProducers; +#endif +}; + + +template +ProducerToken::ProducerToken(ConcurrentQueue& queue) + : producer(queue.recycle_or_create_producer(true)) +{ + if (producer != nullptr) { + producer->token = this; + } +} + +template +ProducerToken::ProducerToken(BlockingConcurrentQueue& queue) + : producer(reinterpret_cast*>(&queue)->recycle_or_create_producer(true)) +{ + if (producer != nullptr) { + producer->token = this; + } +} + +template +ConsumerToken::ConsumerToken(ConcurrentQueue& queue) + : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) +{ + initialOffset = queue.nextExplicitConsumerId.fetch_add(1, std::memory_order_release); + lastKnownGlobalOffset = static_cast(-1); +} + +template +ConsumerToken::ConsumerToken(BlockingConcurrentQueue& queue) + : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) +{ + initialOffset = reinterpret_cast*>(&queue)->nextExplicitConsumerId.fetch_add(1, std::memory_order_release); + lastKnownGlobalOffset = static_cast(-1); +} + +template +inline void swap(ConcurrentQueue& a, ConcurrentQueue& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +inline void swap(ProducerToken& a, ProducerToken& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +inline void swap(ConsumerToken& a, ConsumerToken& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +template +inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +} + +#if defined(_MSC_VER) && (!defined(_HAS_CXX17) || !_HAS_CXX17) +#pragma warning(pop) +#endif + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) +#pragma GCC diagnostic pop +#endif diff --git a/indra/llcommon/fstelemetry.cpp b/indra/llcommon/fstelemetry.cpp index c2bc80b66a..7be6e4b6bb 100644 --- a/indra/llcommon/fstelemetry.cpp +++ b/indra/llcommon/fstelemetry.cpp @@ -28,4 +28,5 @@ namespace FSTelemetry { bool active{false}; + } \ No newline at end of file diff --git a/indra/llcommon/fstelemetry.h b/indra/llcommon/fstelemetry.h index 9a0cdd5e18..37f9cc9a51 100644 --- a/indra/llcommon/fstelemetry.h +++ b/indra/llcommon/fstelemetry.h @@ -43,9 +43,15 @@ #define FSZoneN( name ) ZoneNamedN( ___tracy_scoped_zone, name, FSTelemetry::active) #define FSZoneC( color ) ZoneNamedC( ___tracy_scoped_zone, color, FSTelemetry::active) #define FSZoneNC( name, color ) ZoneNamedNC( ___tracy_scoped_zone, name, color, FSTelemetry::active) +#define FSZoneText( text, size ) ZoneText( text, size ) +#define FSZoneValue( num_uint64 ) ZoneValue( num_uint64 ) #define FSPlot( name, value ) TracyPlot( name, value) +#define FSPlotSq( name, lastval, newval ) TracyPlot( name, lastval);TracyPlot( name, newval); #define FSFrameMark FrameMark +#define FSFrameMarkStart( name ) FrameMarkStart( name ) +#define FSFrameMarkEnd( name ) FrameMarkEnd( name ) #define FSThreadName( name ) tracy::SetThreadName( name ) +#define FSMessageL ( message ) tracy::Profiler::Message( message, 0 ) #define FSTelemetryIsConnected TracyIsConnected #else // (no telemetry) @@ -57,15 +63,26 @@ #define FSZoneN( name ) #define FSZoneC( color ) #define FSZoneNC( name, color ) +#define FSZoneText( text, size ) +#define FSZoneValue( num_uint64 ) #define FSPlot( name, value ) +#define FSPlotSq( name, lastval, newval ) #define FSFrameMark +#define FSFrameMarkStart( name ) +#define FSFrameMarkEnd( name ) #define FSThreadName( name ) +#define FSMessageL( message ) #define FSTelemetryIsConnected #endif // TRACY_ENABLE +#include +#include +#include + namespace FSTelemetry { extern bool active; -} + +}// namespace FSTelemetry #endif \ No newline at end of file diff --git a/indra/llcommon/lightweightsemaphore.h b/indra/llcommon/lightweightsemaphore.h new file mode 100644 index 0000000000..b0f24e1cd8 --- /dev/null +++ b/indra/llcommon/lightweightsemaphore.h @@ -0,0 +1,411 @@ +// Provides an efficient implementation of a semaphore (LightweightSemaphore). +// This is an extension of Jeff Preshing's sempahore implementation (licensed +// under the terms of its separate zlib license) that has been adapted and +// extended by Cameron Desrochers. + +#pragma once + +#include // For std::size_t +#include +#include // For std::make_signed + +#if defined(_WIN32) +// Avoid including windows.h in a header; we only need a handful of +// items, so we'll redeclare them here (this is relatively safe since +// the API generally has to remain stable between Windows versions). +// I know this is an ugly hack but it still beats polluting the global +// namespace with thousands of generic names or adding a .cpp for nothing. +extern "C" { + struct _SECURITY_ATTRIBUTES; + __declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName); + __declspec(dllimport) int __stdcall CloseHandle(void* hObject); + __declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds); + __declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount); +} +#elif defined(__MACH__) +#include +#elif defined(__unix__) +#include +#endif + +namespace moodycamel +{ +namespace details +{ + +// Code in the mpmc_sema namespace below is an adaptation of Jeff Preshing's +// portable + lightweight semaphore implementations, originally from +// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h +// LICENSE: +// Copyright (c) 2015 Jeff Preshing +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgement in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +#if defined(_WIN32) +class Semaphore +{ +private: + void* m_hSema; + + Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + +public: + Semaphore(int initialCount = 0) + { + assert(initialCount >= 0); + const long maxLong = 0x7fffffff; + m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr); + assert(m_hSema); + } + + ~Semaphore() + { + CloseHandle(m_hSema); + } + + bool wait() + { + const unsigned long infinite = 0xffffffff; + return WaitForSingleObject(m_hSema, infinite) == 0; + } + + bool try_wait() + { + return WaitForSingleObject(m_hSema, 0) == 0; + } + + bool timed_wait(std::uint64_t usecs) + { + return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0; + } + + void signal(int count = 1) + { + while (!ReleaseSemaphore(m_hSema, count, nullptr)); + } +}; +#elif defined(__MACH__) +//--------------------------------------------------------- +// Semaphore (Apple iOS and OSX) +// Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html +//--------------------------------------------------------- +class Semaphore +{ +private: + semaphore_t m_sema; + + Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + +public: + Semaphore(int initialCount = 0) + { + assert(initialCount >= 0); + kern_return_t rc = semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount); + assert(rc == KERN_SUCCESS); + (void)rc; + } + + ~Semaphore() + { + semaphore_destroy(mach_task_self(), m_sema); + } + + bool wait() + { + return semaphore_wait(m_sema) == KERN_SUCCESS; + } + + bool try_wait() + { + return timed_wait(0); + } + + bool timed_wait(std::uint64_t timeout_usecs) + { + mach_timespec_t ts; + ts.tv_sec = static_cast(timeout_usecs / 1000000); + ts.tv_nsec = static_cast((timeout_usecs % 1000000) * 1000); + + // added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html + kern_return_t rc = semaphore_timedwait(m_sema, ts); + return rc == KERN_SUCCESS; + } + + void signal() + { + while (semaphore_signal(m_sema) != KERN_SUCCESS); + } + + void signal(int count) + { + while (count-- > 0) + { + while (semaphore_signal(m_sema) != KERN_SUCCESS); + } + } +}; +#elif defined(__unix__) +//--------------------------------------------------------- +// Semaphore (POSIX, Linux) +//--------------------------------------------------------- +class Semaphore +{ +private: + sem_t m_sema; + + Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + +public: + Semaphore(int initialCount = 0) + { + assert(initialCount >= 0); + int rc = sem_init(&m_sema, 0, static_cast(initialCount)); + assert(rc == 0); + (void)rc; + } + + ~Semaphore() + { + sem_destroy(&m_sema); + } + + bool wait() + { + // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error + int rc; + do { + rc = sem_wait(&m_sema); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + bool try_wait() + { + int rc; + do { + rc = sem_trywait(&m_sema); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + bool timed_wait(std::uint64_t usecs) + { + struct timespec ts; + const int usecs_in_1_sec = 1000000; + const int nsecs_in_1_sec = 1000000000; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += (time_t)(usecs / usecs_in_1_sec); + ts.tv_nsec += (long)(usecs % usecs_in_1_sec) * 1000; + // sem_timedwait bombs if you have more than 1e9 in tv_nsec + // so we have to clean things up before passing it in + if (ts.tv_nsec >= nsecs_in_1_sec) { + ts.tv_nsec -= nsecs_in_1_sec; + ++ts.tv_sec; + } + + int rc; + do { + rc = sem_timedwait(&m_sema, &ts); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + void signal() + { + while (sem_post(&m_sema) == -1); + } + + void signal(int count) + { + while (count-- > 0) + { + while (sem_post(&m_sema) == -1); + } + } +}; +#else +#error Unsupported platform! (No semaphore wrapper available) +#endif + +} // end namespace details + + +//--------------------------------------------------------- +// LightweightSemaphore +//--------------------------------------------------------- +class LightweightSemaphore +{ +public: + typedef std::make_signed::type ssize_t; + +private: + std::atomic m_count; + details::Semaphore m_sema; + int m_maxSpins; + + bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) + { + ssize_t oldCount; + int spin = m_maxSpins; + while (--spin >= 0) + { + oldCount = m_count.load(std::memory_order_relaxed); + if ((oldCount > 0) && m_count.compare_exchange_strong(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed)) + return true; + std::atomic_signal_fence(std::memory_order_acquire); // Prevent the compiler from collapsing the loop. + } + oldCount = m_count.fetch_sub(1, std::memory_order_acquire); + if (oldCount > 0) + return true; + if (timeout_usecs < 0) + { + if (m_sema.wait()) + return true; + } + if (timeout_usecs > 0 && m_sema.timed_wait((std::uint64_t)timeout_usecs)) + return true; + // At this point, we've timed out waiting for the semaphore, but the + // count is still decremented indicating we may still be waiting on + // it. So we have to re-adjust the count, but only if the semaphore + // wasn't signaled enough times for us too since then. If it was, we + // need to release the semaphore too. + while (true) + { + oldCount = m_count.load(std::memory_order_acquire); + if (oldCount >= 0 && m_sema.try_wait()) + return true; + if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed)) + return false; + } + } + + ssize_t waitManyWithPartialSpinning(ssize_t max, std::int64_t timeout_usecs = -1) + { + assert(max > 0); + ssize_t oldCount; + int spin = m_maxSpins; + while (--spin >= 0) + { + oldCount = m_count.load(std::memory_order_relaxed); + if (oldCount > 0) + { + ssize_t newCount = oldCount > max ? oldCount - max : 0; + if (m_count.compare_exchange_strong(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed)) + return oldCount - newCount; + } + std::atomic_signal_fence(std::memory_order_acquire); + } + oldCount = m_count.fetch_sub(1, std::memory_order_acquire); + if (oldCount <= 0) + { + if ((timeout_usecs == 0) || (timeout_usecs < 0 && !m_sema.wait()) || (timeout_usecs > 0 && !m_sema.timed_wait((std::uint64_t)timeout_usecs))) + { + while (true) + { + oldCount = m_count.load(std::memory_order_acquire); + if (oldCount >= 0 && m_sema.try_wait()) + break; + if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed)) + return 0; + } + } + } + if (max > 1) + return 1 + tryWaitMany(max - 1); + return 1; + } + +public: + LightweightSemaphore(ssize_t initialCount = 0, int maxSpins = 10000) : m_count(initialCount), m_maxSpins(maxSpins) + { + assert(initialCount >= 0); + assert(maxSpins >= 0); + } + + bool tryWait() + { + ssize_t oldCount = m_count.load(std::memory_order_relaxed); + while (oldCount > 0) + { + if (m_count.compare_exchange_weak(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed)) + return true; + } + return false; + } + + bool wait() + { + return tryWait() || waitWithPartialSpinning(); + } + + bool wait(std::int64_t timeout_usecs) + { + return tryWait() || waitWithPartialSpinning(timeout_usecs); + } + + // Acquires between 0 and (greedily) max, inclusive + ssize_t tryWaitMany(ssize_t max) + { + assert(max >= 0); + ssize_t oldCount = m_count.load(std::memory_order_relaxed); + while (oldCount > 0) + { + ssize_t newCount = oldCount > max ? oldCount - max : 0; + if (m_count.compare_exchange_weak(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed)) + return oldCount - newCount; + } + return 0; + } + + // Acquires at least one, and (greedily) at most max + ssize_t waitMany(ssize_t max, std::int64_t timeout_usecs) + { + assert(max >= 0); + ssize_t result = tryWaitMany(max); + if (result == 0 && max > 0) + result = waitManyWithPartialSpinning(max, timeout_usecs); + return result; + } + + ssize_t waitMany(ssize_t max) + { + ssize_t result = waitMany(max, -1); + assert(result > 0); + return result; + } + + void signal(ssize_t count = 1) + { + assert(count >= 0); + ssize_t oldCount = m_count.fetch_add(count, std::memory_order_release); + ssize_t toRelease = -oldCount < count ? -oldCount : count; + if (toRelease > 0) + { + m_sema.signal((int)toRelease); + } + } + + std::size_t availableApprox() const + { + ssize_t count = m_count.load(std::memory_order_relaxed); + return count > 0 ? static_cast(count) : 0; + } +}; + +} // end namespace moodycamel diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h index 26197cb692..95c9543751 100644 --- a/indra/llcommon/llpreprocessor.h +++ b/indra/llcommon/llpreprocessor.h @@ -40,6 +40,12 @@ #define LL_BIG_ENDIAN 1 #endif +// FIRE-31221 workaround for newer glibc versions, patch from Lance Corrimal +// Figure out GLIBC version - 2.34 needs an additional include as a build fix +#if (__GLIBC__*1000 + __GLIBC_MINOR__) >= 2034 +#include +#endif +// // Per-compiler switches diff --git a/indra/llcommon/lltracerecording.h b/indra/llcommon/lltracerecording.h index d0b4a842a6..2af5273d70 100644 --- a/indra/llcommon/lltracerecording.h +++ b/indra/llcommon/lltracerecording.h @@ -580,6 +580,31 @@ namespace LLTrace return typename RelatedTypes::fractional_t(getPeriodMeanPerSec(static_cast&>(stat), num_periods)); } + template + typename RelatedTypes::fractional_t getPeriodMedianPerSec(const StatType& stat, S32 num_periods = S32_MAX) + { + num_periods = llmin(num_periods, getNumRecordedPeriods()); + + std::vector ::fractional_t> buf; + for (S32 i = 1; i <= num_periods; i++) + { + Recording& recording = getPrevRecording(i); + if (recording.getDuration() > (F32Seconds)0.f) + { + buf.push_back(recording.getPerSec(stat)); + } + } + std::sort(buf.begin(), buf.end()); + + return typename RelatedTypes::fractional_t((buf.size() % 2 == 0) ? (buf[buf.size() / 2 - 1] + buf[buf.size() / 2]) / 2 : buf[buf.size() / 2]); + } + + template + typename RelatedTypes::fractional_t getPeriodMedianPerSec(const CountStatHandle& stat, S32 num_periods = S32_MAX) + { + return typename RelatedTypes::fractional_t(getPeriodMedianPerSec(static_cast&>(stat), num_periods)); + } + // // PERIODIC STANDARD DEVIATION // diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp index 7ff040a602..191f8512e0 100644 --- a/indra/llcommon/lluuid.cpp +++ b/indra/llcommon/lluuid.cpp @@ -44,7 +44,7 @@ #include "lltimer.h" #include "llthread.h" #include "llmutex.h" - +#include "fstelemetry.h" const LLUUID LLUUID::null; const LLTransactionID LLTransactionID::tnull; @@ -155,6 +155,7 @@ U32 janky_fast_random_seeded_bytes(U32 seed, U32 val) // Common to all UUID implementations void LLUUID::toString(std::string& out) const { + FSZone; out = llformat( "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", (U8)(mData[0]), @@ -217,6 +218,7 @@ BOOL LLUUID::set(const char* in_string, BOOL emit) BOOL LLUUID::set(const std::string& in_string, BOOL emit) { + FSZone; BOOL broken_format = FALSE; // empty strings should make NULL uuid @@ -1027,6 +1029,7 @@ LLUUID::LLUUID() // Copy constructor LLUUID::LLUUID(const LLUUID& rhs) { + FSZone; // Fix for misaligned unsigned ints in LLUUID; by Sovereign Engineer / Shyotl Kuhr //U32 *tmp = (U32 *)mData; //U32 *rhstmp = (U32 *)rhs.mData; @@ -1045,6 +1048,7 @@ LLUUID::LLUUID() // Assignment LLUUID& LLUUID::operator=(const LLUUID& rhs) { + FSZone; // Fix for misaligned unsigned ints in LLUUID; by Sovereign Engineer / Shyotl Kuhr //// No need to check the case where this==&rhs. The branch is slower than the write. //U32 *tmp = (U32 *)mData; diff --git a/indra/llcommon/lluuid.h b/indra/llcommon/lluuid.h index b6399ad094..32f8b6c7c2 100644 --- a/indra/llcommon/lluuid.h +++ b/indra/llcommon/lluuid.h @@ -29,10 +29,11 @@ #include #include #include +#include // for std::memmove #include "stdtypes.h" #include "llpreprocessor.h" #include - +#include "fstelemetry.h" class LLMutex; const S32 UUID_BYTES = 16; @@ -56,7 +57,9 @@ public: explicit LLUUID(const char *in_string); // Convert from string. explicit LLUUID(const std::string& in_string); // Convert from string. LLUUID(const LLUUID &in); + LLUUID(LLUUID&& rhs) noexcept { FSZone; std::memmove(mData, rhs.mData, sizeof(mData));}; LLUUID &operator=(const LLUUID &rhs); + LLUUID &operator=(LLUUID &&rhs) noexcept {FSZone; std::memmove(mData, rhs.mData, sizeof(mData));return *this;}; ~LLUUID(); @@ -112,6 +115,61 @@ public: void toString(std::string& out) const; void toCompressedString(char *out) const; // Does not allocate memory, needs 17 characters (including \0) void toCompressedString(std::string& out) const; + + // last 4 chars for quick ref - Very lightweight, no nul-term added - provide your own, ensure min 4 bytes. +# define hexnybl(N) (N)>9?((N)-10)+'a':(N)+'0' + inline char * toShortString(char *out) const + { + FSZone; + out[0] = hexnybl(mData[14]>>4); + out[1] = hexnybl(mData[14]&15); + out[2] = hexnybl(mData[15]>>4); + out[3] = hexnybl(mData[15]&15); + return out; + } + // full uuid - Much lighterweight than default, no allocation, or nul-term added - provide your own, ensure min 36 bytes. + inline char * toStringFast(char *out) const + { + FSZone; + out[0] = hexnybl(mData[0]>>4); + out[1] = hexnybl(mData[0]&15); + out[2] = hexnybl(mData[1]>>4); + out[3] = hexnybl(mData[1]&15); + out[4] = hexnybl(mData[2]>>4); + out[5] = hexnybl(mData[2]&15); + out[6] = hexnybl(mData[3]>>4); + out[7] = hexnybl(mData[3]&15); + out[8] = '-'; + out[9] = hexnybl(mData[4]>>4); + out[10] = hexnybl(mData[4]&15); + out[11] = hexnybl(mData[5]>>4); + out[12] = hexnybl(mData[5]&15); + out[13] = '-'; + out[14] = hexnybl(mData[6]>>4); + out[15] = hexnybl(mData[6]&15); + out[16] = hexnybl(mData[7]>>4); + out[17] = hexnybl(mData[7]&15); + out[18] = '-'; + out[19] = hexnybl(mData[8]>>4); + out[20] = hexnybl(mData[8]&15); + out[21] = hexnybl(mData[9]>>4); + out[22] = hexnybl(mData[9]&15); + out[23] = '-'; + out[24] = hexnybl(mData[10]>>4); + out[25] = hexnybl(mData[10]&15); + out[26] = hexnybl(mData[11]>>4); + out[27] = hexnybl(mData[11]&15); + out[28] = hexnybl(mData[12]>>4); + out[29] = hexnybl(mData[12]&15); + out[30] = hexnybl(mData[13]>>4); + out[31] = hexnybl(mData[13]&15); + out[32] = hexnybl(mData[14]>>4); + out[33] = hexnybl(mData[14]&15); + out[34] = hexnybl(mData[15]>>4); + out[35] = hexnybl(mData[15]&15); + return out; + } + std::string asString() const; std::string getString() const; diff --git a/indra/llcorehttp/_thread.h b/indra/llcorehttp/_thread.h index 22b7750bad..b784adfa85 100644 --- a/indra/llcorehttp/_thread.h +++ b/indra/llcorehttp/_thread.h @@ -35,6 +35,7 @@ #include "apr.h" // thread-related functions #include "_refcounted.h" +#include "fstelemetry.h" namespace LLCoreInt { @@ -54,6 +55,10 @@ private: void run() { // THREAD CONTEXT + // - Add threadnames + LL_INFOS("THREAD") << "Started unnamed HTTP thread " << LL_ENDL; + FSThreadName( "HTTP" ); + // // Take out additional reference for the at_exit handler addRef(); diff --git a/indra/llcrashlogger/llcrashlogger.cpp b/indra/llcrashlogger/llcrashlogger.cpp index 9d3ad8b3b9..b7c6cae08a 100644 --- a/indra/llcrashlogger/llcrashlogger.cpp +++ b/indra/llcrashlogger/llcrashlogger.cpp @@ -785,7 +785,7 @@ void LLCrashLogger::init_curl() } CRYPTO_set_locking_callback(ssl_locking_callback); - CRYPTO_THREADID_set_callback(ssl_thread_id_callback); + (void)CRYPTO_THREADID_set_callback(ssl_thread_id_callback); } } diff --git a/indra/llui/llscrolllistcell.cpp b/indra/llui/llscrolllistcell.cpp index 13839da400..61470d1440 100644 --- a/indra/llui/llscrolllistcell.cpp +++ b/indra/llui/llscrolllistcell.cpp @@ -54,6 +54,10 @@ LLScrollListCell* LLScrollListCell::create(const LLScrollListCell::Params& cell_ { cell = new LLScrollListIconText(cell_p); } + else if (cell_p.type() == "bar") + { + cell = new LLScrollListBar(cell_p); + } else // default is "text" { cell = new LLScrollListText(cell_p); @@ -165,6 +169,74 @@ void LLScrollListIcon::draw(const LLColor4& color, const LLColor4& highlight_col } } +// +// LLScrollListBar +// +LLScrollListBar::LLScrollListBar(const LLScrollListCell::Params& p) + : LLScrollListCell(p), + mRatio(0), + mColor(p.color), + mBottom(1), + mLeftPad(1), + mRightPad(1) +{} + +LLScrollListBar::~LLScrollListBar() +{ +} + +/*virtual*/ +S32 LLScrollListBar::getHeight() const +{ + return LLScrollListCell::getHeight(); +} + +/*virtual*/ +const LLSD LLScrollListBar::getValue() const +{ + return LLStringUtil::null; +} + +void LLScrollListBar::setValue(const LLSD& value) +{ + if (value.has("ratio")) + { + mRatio = value["ratio"].asReal(); + } + if (value.has("bottom")) + { + mBottom = value["bottom"].asInteger(); + } + if (value.has("left_pad")) + { + mLeftPad = value["left_pad"].asInteger(); + } + if (value.has("right_pad")) + { + mRightPad = value["right_pad"].asInteger(); + } +} + +void LLScrollListBar::setColor(const LLColor4& color) +{ + mColor = color; +} + +S32 LLScrollListBar::getWidth() const +{ + return LLScrollListCell::getWidth(); +} + + +void LLScrollListBar::draw(const LLColor4& color, const LLColor4& highlight_color) const +{ + S32 bar_width = getWidth() - mLeftPad - mRightPad; + S32 left = bar_width - bar_width * mRatio; + left = llclamp(left, mLeftPad, getWidth() - mRightPad - 1); + + gl_rect_2d(left, mBottom, getWidth() - mRightPad, mBottom - 1, mColor); +} + // // LLScrollListText // diff --git a/indra/llui/llscrolllistcell.h b/indra/llui/llscrolllistcell.h index 19576fb247..9a659dfd0d 100644 --- a/indra/llui/llscrolllistcell.h +++ b/indra/llui/llscrolllistcell.h @@ -33,6 +33,7 @@ #include "lluistring.h" #include "v4color.h" #include "llui.h" +#include "llgltexture.h" class LLCheckBoxCtrl; class LLSD; @@ -153,6 +154,7 @@ public: void setText(const LLStringExplicit& text); void setFontStyle(const U8 font_style); + void setAlignment(LLFontGL::HAlign align) { mFontAlignment = align; } protected: LLUIString mText; @@ -192,6 +194,26 @@ private: LLFontGL::HAlign mAlignment; }; + +class LLScrollListBar : public LLScrollListCell +{ +public: + LLScrollListBar(const LLScrollListCell::Params& p); + /*virtual*/ ~LLScrollListBar(); + /*virtual*/ void draw(const LLColor4& color, const LLColor4& highlight_color) const; + /*virtual*/ S32 getWidth() const; + /*virtual*/ S32 getHeight() const; + /*virtual*/ const LLSD getValue() const; + /*virtual*/ void setColor(const LLColor4&); + /*virtual*/ void setValue(const LLSD& value); + +private: + LLColor4 mColor; + F32 mRatio; + S32 mBottom; + S32 mRightPad; + S32 mLeftPad; +}; /* * An interactive cell containing a check box. */ diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 1581f45731..4e49c41c89 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -397,9 +397,12 @@ set(viewer_SOURCE_FILES llfloaterpathfindinglinksets.cpp llfloaterpathfindingobjects.cpp llfloaterpay.cpp + # llfloaterperformance.cpp replaced with fs version due to large changes and likelihood that LL version will not release. + fsfloaterperformance.cpp llfloaterperms.cpp llfloaterpostprocess.cpp llfloaterpreference.cpp + # llfloaterpreferencesgraphicsadvanced.cpp llfloaterpreferenceviewadvanced.cpp llfloaterpreviewtrash.cpp llfloaterproperties.cpp @@ -1176,9 +1179,12 @@ set(viewer_HEADER_FILES llfloaterpathfindinglinksets.h llfloaterpathfindingobjects.h llfloaterpay.h + # llfloaterperformance.h replaced with fs version due to large changes and likelihood that LL version will not release. + fsfloaterperformance.h llfloaterperms.h llfloaterpostprocess.h llfloaterpreference.h + # llfloaterpreferencesgraphicsadvanced.h llfloaterpreferenceviewadvanced.h llfloaterpreviewtrash.h llfloaterproperties.h @@ -1678,6 +1684,10 @@ configure_file( list(APPEND viewer_HEADER_FILES ${CMAKE_CURRENT_BINARY_DIR}/fsversionvalues.h) # + # Performance stast support + list(APPEND viewer_SOURCE_FILES fsperfstats.cpp) + list(APPEND viewer_HEADER_FILES fsperfstats.h) + # source_group("CMake Rules" FILES ViewerInstall.cmake) #build_data.json creation moved to viewer_manifest.py MAINT-6413 @@ -2562,7 +2572,7 @@ if (NOT ENABLE_MEDIA_PLUGINS) else (NOT ENABLE_MEDIA_PLUGINS) set(COPY_INPUT_DEPENDENCIES ${VIEWER_BINARY_NAME} - linux-crash-logger + #linux-crash-logger SLPlugin media_plugin_cef #media_plugin_gstreamer010 diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml index a13da0b23a..057aa6cea6 100644 --- a/indra/newview/app_settings/commands.xml +++ b/indra/newview/app_settings/commands.xml @@ -638,4 +638,14 @@ is_running_function="Floater.IsOpen" is_running_parameters="my_environments" /> + diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 110e905785..53333b3f73 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -8184,7 +8184,7 @@ Comment Duration in seconds of the login SRV request timeout Persist - 0 + 1 Type F32 Value @@ -25729,6 +25729,39 @@ Change of this parameter will affect the layout of buttons in notification toast Value 0 + FSTargetFPS + + Comment + Desired minimum FPS + Persist + 1 + Type + U32 + Value + 25 + + FSAutoTuneFPS + + Comment + Allow the viewer to adjust your settings to achieve target FPS + Persist + 0 + Type + Boolean + Value + 0 + + FSPerfFloaterSmoothingPeriods + + Comment + Number of periods to smooth the stats over + Persist + 1 + Type + U32 + Value + 50 + FSAutoUnmuteSounds Comment @@ -25751,5 +25784,93 @@ Change of this parameter will affect the layout of buttons in notification toast Value 0 + RenderJellyDollsAsImpostors + + Comment + Use an impostor instead of a JellyDoll for better visuals (true) + Persist + 1 + Type + Boolean + Value + 1 + + FSPerfStatsCaptureEnabled + + Comment + Enable/disable render time data to support autotune. + Persist + 1 + Type + Boolean + Value + 1 + + FSAutoTuneImpostorByDistEnabled + + Comment + Enable/disable using MaxNonImpostor to limit avatar rendering by distance. + Persist + 1 + Type + Boolean + Value + 1 + + FSRenderAvatarMaxART + + Comment + Render Time Limit in microseconds (0.0 = no limit) + Persist + 0 + Type + F32 + Value + 4.699 + + FSAutoTuneRenderFarClipMin + + Comment + The lowest draw distance that auto tune is allowed to use + Persist + 0 + Type + F32 + Value + 32.0 + + FSAutoTuneRenderFarClipTarget + + Comment + The draw distance that auto tune will try to achieve + Persist + 0 + Type + F32 + Value + 256.0 + + FSAutoTuneImpostorFarAwayDistance + + Comment + Avatars beyond this range will automatically be optimized + Persist + 0 + Type + F32 + Value + 64.0 + + FSTuningFPSStrategy + + Comment + Strategy to use when tuning FPS. 0=Tune avatar rendering only, 1=Tune both avatar and global scene settings. + Persist + 1 + Type + U32 + Value + 0 + diff --git a/indra/newview/fsfloaterperformance.cpp b/indra/newview/fsfloaterperformance.cpp new file mode 100644 index 0000000000..860a1e2fd9 --- /dev/null +++ b/indra/newview/fsfloaterperformance.cpp @@ -0,0 +1,1039 @@ +/** + * @file llfloaterperformance.cpp + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2021, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "fsfloaterperformance.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llappearancemgr.h" +#include "llavataractions.h" +#include "llavatarrendernotifier.h" +#include "llcheckboxctrl.h" +#include "llfeaturemanager.h" +#include "llfloaterpreference.h" // LLAvatarComplexityControls +#include "llfloaterreg.h" +#include "llmoveview.h" // for LLPanelStandStopFlying +#include "llnamelistctrl.h" +#include "llradiogroup.h" +#include "llselectmgr.h" +#include "llsliderctrl.h" +#include "lltextbox.h" +#include "llcombobox.h" +#include "lltrans.h" +#include "llviewerobjectlist.h" +#include "llviewerjoystick.h" +#include "llviewermediafocus.h" +#include "llvoavatar.h" +#include "llvoavatarself.h" +#include "pipeline.h" +#include "llviewercontrol.h" +#include "fsavatarrenderpersistence.h" +#include "fsperfstats.h" // performance stats support +#include "fslslbridge.h" + +const F32 REFRESH_INTERVAL = 1.0f; +const S32 BAR_LEFT_PAD = 2; +const S32 BAR_RIGHT_PAD = 5; +const S32 BAR_BOTTOM_PAD = 9; + +constexpr auto AvType {FSPerfStats::ObjType_t::OT_AVATAR}; +constexpr auto AttType {FSPerfStats::ObjType_t::OT_ATTACHMENT}; +constexpr auto HudType {FSPerfStats::ObjType_t::OT_HUD}; +constexpr auto SceneType {FSPerfStats::ObjType_t::OT_GENERAL}; +class FSExceptionsContextMenu : public LLListContextMenu +{ +public: + FSExceptionsContextMenu(FSFloaterPerformance* floater_settings) + : mFloaterPerformance(floater_settings) + {} +protected: + LLContextMenu* createMenu() + { + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + registrar.add("Settings.SetRendering", boost::bind(&FSFloaterPerformance::onCustomAction, mFloaterPerformance, _2, mUUIDs.front())); + enable_registrar.add("Settings.IsSelected", boost::bind(&FSFloaterPerformance::isActionChecked, mFloaterPerformance, _2, mUUIDs.front())); + registrar.add("Avatar.Extended", boost::bind(&FSFloaterPerformance::onExtendedAction, mFloaterPerformance, _2, mUUIDs.front())); + LLContextMenu* menu = createFromFile("menu_perf_avatar_rendering_settings.xml"); + + return menu; + } + + FSFloaterPerformance* mFloaterPerformance; +}; + + + +FSFloaterPerformance::FSFloaterPerformance(const LLSD& key) +: LLFloater(key), + mUpdateTimer(new LLTimer()), + mNearbyMaxComplexity(0) +{ + mContextMenu = new FSExceptionsContextMenu(this); +} + +FSFloaterPerformance::~FSFloaterPerformance() +{ + mComplexityChangedSignal.disconnect(); + delete mContextMenu; + delete mUpdateTimer; +} + +BOOL FSFloaterPerformance::postBuild() +{ + mMainPanel = getChild("panel_performance_main"); + mNearbyPanel = getChild("panel_performance_nearby"); + mComplexityPanel = getChild("panel_performance_complexity"); + mSettingsPanel = getChild("panel_performance_preferences"); + mHUDsPanel = getChild("panel_performance_huds"); + mAutoTunePanel = getChild("panel_performance_autotune"); + + getChild("nearby_subpanel")->setMouseDownCallback(boost::bind(&FSFloaterPerformance::showSelectedPanel, this, mNearbyPanel)); + getChild("complexity_subpanel")->setMouseDownCallback(boost::bind(&FSFloaterPerformance::showSelectedPanel, this, mComplexityPanel)); + getChild("settings_subpanel")->setMouseDownCallback(boost::bind(&FSFloaterPerformance::showSelectedPanel, this, mSettingsPanel)); + getChild("huds_subpanel")->setMouseDownCallback(boost::bind(&FSFloaterPerformance::showSelectedPanel, this, mHUDsPanel)); + + auto tgt_panel = findChild("target_subpanel"); + if (tgt_panel) + { + tgt_panel->getChild("target_btn")->setCommitCallback(boost::bind(&FSFloaterPerformance::showSelectedPanel, this, mAutoTunePanel)); + } + + initBackBtn(mNearbyPanel); + initBackBtn(mComplexityPanel); + initBackBtn(mSettingsPanel); + initBackBtn(mHUDsPanel); + initBackBtn(mAutoTunePanel); + + mHUDList = mHUDsPanel->getChild("hud_list"); + mHUDList->setNameListType(LLNameListCtrl::SPECIAL); + mHUDList->setHoverIconName("StopReload_Off"); + mHUDList->setIconClickedCallback(boost::bind(&FSFloaterPerformance::detachItem, this, _1)); + + mObjectList = mComplexityPanel->getChild("obj_list"); + mObjectList->setNameListType(LLNameListCtrl::SPECIAL); + mObjectList->setHoverIconName("StopReload_Off"); + mObjectList->setIconClickedCallback(boost::bind(&FSFloaterPerformance::detachItem, this, _1)); + + mSettingsPanel->getChild("graphics_quality")->setCommitCallback(boost::bind(&FSFloaterPerformance::onChangeQuality, this, _2)); + + mNearbyPanel->getChild("exceptions_btn")->setCommitCallback(boost::bind(&FSFloaterPerformance::onClickExceptions, this)); + mNearbyPanel->getChild("hide_avatars")->setCommitCallback(boost::bind(&FSFloaterPerformance::onClickHideAvatars, this)); + mNearbyPanel->getChild("hide_avatars")->set(!LLPipeline::hasRenderTypeControl(LLPipeline::RENDER_TYPE_AVATAR)); + mNearbyList = mNearbyPanel->getChild("nearby_list"); + mNearbyList->setRightMouseDownCallback(boost::bind(&FSFloaterPerformance::onAvatarListRightClick, this, _1, _2, _3)); + + updateComplexityText(); + mComplexityChangedSignal = gSavedSettings.getControl("RenderAvatarMaxComplexity")->getCommitSignal()->connect(boost::bind(&FSFloaterPerformance::updateComplexityText, this)); + mNearbyPanel->getChild("IndirectMaxComplexity")->setCommitCallback(boost::bind(&FSFloaterPerformance::updateMaxComplexity, this)); + + mMaxARTChangedSignal = gSavedSettings.getControl("FSRenderAvatarMaxART")->getCommitSignal()->connect(boost::bind(&FSFloaterPerformance::updateMaxRenderTime, this)); + mNearbyPanel->getChild("FSRenderAvatarMaxART")->setCommitCallback(boost::bind(&FSFloaterPerformance::updateMaxRenderTime, this)); + + LLAvatarComplexityControls::setIndirectMaxArc(); + + return TRUE; +} + +void FSFloaterPerformance::showSelectedPanel(LLPanel* selected_panel) +{ + hidePanels(); + mMainPanel->setVisible(FALSE); + selected_panel->setVisible(TRUE); + + if (mHUDsPanel == selected_panel) + { + populateHUDList(); + } + else if (mNearbyPanel == selected_panel) + { + populateNearbyList(); + } + else if (mComplexityPanel == selected_panel) + { + populateObjectList(); + } +} + +void FSFloaterPerformance::draw() +{ + const S32 NUM_PERIODS = 50; + constexpr auto NANOS = 1000000000; + + static LLCachedControl fpsCap(gSavedSettings, "FramePerSecondLimit"); // user limited FPS + static LLCachedControl targetFPS(gSavedSettings, "FSTargetFPS"); // desired FPS + static LLCachedControl tuningStrategy(gSavedSettings, "FSTuningFPSStrategy"); + + if (mUpdateTimer->hasExpired()) + { + LLStringUtil::format_map_t args; + + auto fps = LLTrace::get_frame_recording().getPeriodMedianPerSec(LLStatViewer::FPS, NUM_PERIODS); + getChild("fps_value")->setValue((S32)llround(fps)); + + auto target_frame_time_ns = NANOS/(targetFPS==0?1:targetFPS); + + FSPerfStats::bufferToggleLock.lock(); // prevent toggle for a moment + + auto tot_frame_time_ns = FSPerfStats::raw_to_ns(FSPerfStats::StatsRecorder::getSceneStat(FSPerfStats::StatType_t::RENDER_FRAME)); + // cumulative avatar time (includes idle processing, attachments and base av) + auto tot_avatar_time_raw = FSPerfStats::StatsRecorder::getSum(AvType, FSPerfStats::StatType_t::RENDER_COMBINED); + // cumulative avatar render specific time (a bit arbitrary as the processing is too.) + auto tot_avatar_render_time_raw = tot_avatar_time_raw - FSPerfStats::StatsRecorder::getSum(AvType, FSPerfStats::StatType_t::RENDER_IDLE); + // the time spent this frame on the "display()" call. Treated as "tot time rendering" + auto tot_render_time_raw = FSPerfStats::StatsRecorder::getSceneStat(FSPerfStats::StatType_t::RENDER_DISPLAY); + // sleep time is basically forced sleep when window out of focus + auto tot_sleep_time_raw = FSPerfStats::StatsRecorder::getSceneStat(FSPerfStats::StatType_t::RENDER_SLEEP); + // time spent on UI + auto tot_ui_time_raw = FSPerfStats::StatsRecorder::getSceneStat(FSPerfStats::StatType_t::RENDER_UI); + // cumulative time spent rendering HUDS + auto tot_huds_time_raw = FSPerfStats::StatsRecorder::getSceneStat(FSPerfStats::StatType_t::RENDER_HUDS); + // "idle" time. This is the time spent in the idle poll section of the main loop, we DO remove the avatar idle time as the avatar number we display is the total avatar time inclusive of idle processing. + auto tot_idle_time_raw = FSPerfStats::StatsRecorder::getSceneStat(FSPerfStats::StatType_t::RENDER_IDLE) - FSPerfStats::StatsRecorder::getSum(AvType, FSPerfStats::StatType_t::RENDER_IDLE); + // similar to sleep time, induced by FPS limit + auto tot_limit_time_raw = FSPerfStats::StatsRecorder::getSceneStat(FSPerfStats::StatType_t::RENDER_FPSLIMIT); + // swap time is time spent in swap buffer + auto tot_swap_time_raw = FSPerfStats::StatsRecorder::getSceneStat(FSPerfStats::StatType_t::RENDER_SWAP); + + FSPerfStats::bufferToggleLock.unlock(); + + auto unreliable = false; // if there is something to skew the stats such as sleep of fps cap + auto tot_avatar_time_ns = FSPerfStats::raw_to_ns( tot_avatar_time_raw ); + auto tot_huds_time_ns = FSPerfStats::raw_to_ns( tot_huds_time_raw ); + // UI time includes HUD time so dedut that before we calc percentages + auto tot_ui_time_ns = FSPerfStats::raw_to_ns( tot_ui_time_raw - tot_huds_time_raw); + + // auto tot_sleep_time_ns = FSPerfStats::raw_to_ns( tot_sleep_time_raw ); + // auto tot_limit_time_ns = FSPerfStats::raw_to_ns( tot_limit_time_raw ); + + // auto tot_render_time_ns = FSPerfStats::raw_to_ns( tot_render_time_raw ); + auto tot_idle_time_ns = FSPerfStats::raw_to_ns( tot_idle_time_raw ); + auto tot_swap_time_ns = FSPerfStats::raw_to_ns( tot_swap_time_raw ); + auto tot_scene_time_ns = FSPerfStats::raw_to_ns( tot_render_time_raw - tot_avatar_render_time_raw - tot_swap_time_raw - tot_ui_time_raw); + + + // // remove time spent sleeping for fps limit or out of focus. + // tot_frame_time_ns -= tot_limit_time_ns; + // tot_frame_time_ns -= tot_sleep_time_ns; + + if(tot_frame_time_ns == 0) + { + LL_WARNS("performance") << "things went wrong, quit while we can." << LL_ENDL; + return; + } + auto pct_avatar_time = (tot_avatar_time_ns * 100)/tot_frame_time_ns; + auto pct_huds_time = (tot_huds_time_ns * 100)/tot_frame_time_ns; + auto pct_ui_time = (tot_ui_time_ns * 100)/tot_frame_time_ns; + auto pct_idle_time = (tot_idle_time_ns * 100)/tot_frame_time_ns; + auto pct_swap_time = (tot_swap_time_ns * 100)/tot_frame_time_ns; + auto pct_scene_render_time = (tot_scene_time_ns * 100)/tot_frame_time_ns; + pct_avatar_time = llclamp(pct_avatar_time,0.,100.); + pct_huds_time = llclamp(pct_huds_time,0.,100.); + pct_ui_time = llclamp(pct_ui_time,0.,100.); + pct_idle_time = llclamp(pct_idle_time,0.,100.); + pct_swap_time = llclamp(pct_swap_time,0.,100.); + pct_scene_render_time = llclamp(pct_scene_render_time,0.,100.); + + args["AV_FRAME_PCT"] = llformat("%02u", (U32)llround(pct_avatar_time)); + args["HUDS_FRAME_PCT"] = llformat("%02u", (U32)llround(pct_huds_time)); + args["UI_FRAME_PCT"] = llformat("%02u", (U32)llround(pct_ui_time)); + args["IDLE_FRAME_PCT"] = llformat("%02u", (U32)llround(pct_idle_time)); + args["SWAP_FRAME_PCT"] = llformat("%02u", (U32)llround(pct_swap_time)); + args["SCENERY_FRAME_PCT"] = llformat("%02u", (U32)llround(pct_scene_render_time)); + args["TOT_FRAME_TIME"] = llformat("%02u", (U32)llround(tot_frame_time_ns/1000000)); + args["FPSCAP"] = llformat("%02u", (U32)fpsCap); + args["FPSTARGET"] = llformat("%02u", (U32)targetFPS); + + getChild("av_frame_stats")->setText(getString("av_frame_pct", args)); + getChild("huds_frame_stats")->setText(getString("huds_frame_pct", args)); + getChild("frame_breakdown")->setText(getString("frame_stats", args)); + + auto textbox = getChild("fps_warning"); + if (tot_sleep_time_raw > 0) // We are sleeping because view is not focussed + { + textbox->setVisible(true); + textbox->setText(getString("focus_fps")); + textbox->setColor(LLUIColorTable::instance().getColor("DrYellow")); + unreliable = true; + } + else if (tot_limit_time_raw > 0) + { + textbox->setVisible(true); + textbox->setText(getString("limit_fps", args)); + textbox->setColor(LLUIColorTable::instance().getColor("DrYellow")); + unreliable = true; + } + else if (FSPerfStats::autoTune) + { + textbox->setVisible(true); + textbox->setText(getString("tuning_fps", args)); + textbox->setColor(LLUIColorTable::instance().getColor("green")); + } + else + { + textbox->setVisible(false); + } + + if (FSPerfStats::autoTune && !unreliable ) + { + // the tuning itself is managed from another thread but we can report progress here + + // Is our target frame time lower than current? If so we need to take action to reduce draw overheads. + if (target_frame_time_ns <= tot_frame_time_ns) + { + U32 non_avatar_time_ns = tot_frame_time_ns - tot_avatar_time_ns; + // If the target frame time < non avatar frame time then we can pototentially reach it. + if (non_avatar_time_ns < target_frame_time_ns) + { + textbox->setColor(LLUIColorTable::instance().getColor("orange")); + } + else + { + // TODO(Beq): Set advisory text for further actions + textbox->setColor(LLUIColorTable::instance().getColor("red")); + } + } + else if (target_frame_time_ns > (tot_frame_time_ns + FSPerfStats::renderAvatarMaxART_ns)) + { + // if we have more time to spare. Display this (the service will update things) + textbox->setColor(LLUIColorTable::instance().getColor("green")); + } + } + + if (mHUDsPanel->getVisible()) + { + populateHUDList(); + } + else if (mNearbyPanel->getVisible()) + { + populateNearbyList(); + mNearbyPanel->getChild("hide_avatars")->set(!LLPipeline::hasRenderTypeControl(LLPipeline::RENDER_TYPE_AVATAR)); + } + else if (mComplexityPanel->getVisible()) + { + populateObjectList(); + } + + mUpdateTimer->setTimerExpirySec(REFRESH_INTERVAL); + } + LLFloater::draw(); +} + +void FSFloaterPerformance::showMainPanel() +{ + hidePanels(); + mMainPanel->setVisible(TRUE); +} + +void FSFloaterPerformance::hidePanels() +{ + mNearbyPanel->setVisible(FALSE); + mComplexityPanel->setVisible(FALSE); + mHUDsPanel->setVisible(FALSE); + mSettingsPanel->setVisible(FALSE); + mAutoTunePanel->setVisible(FALSE); +} + +void FSFloaterPerformance::initBackBtn(LLPanel* panel) +{ + panel->getChild("back_btn")->setCommitCallback(boost::bind(&FSFloaterPerformance::showMainPanel, this)); + panel->getChild("back_lbl")->setShowCursorHand(false); + panel->getChild("back_lbl")->setSoundFlags(LLView::MOUSE_UP); + panel->getChild("back_lbl")->setClickedCallback(boost::bind(&FSFloaterPerformance::showMainPanel, this)); +} + +void FSFloaterPerformance::populateHUDList() +{ + S32 prev_pos = mHUDList->getScrollPos(); + LLUUID prev_selected_id = mHUDList->getSelectedSpecialId(); + mHUDList->clearRows(); + mHUDList->updateColumns(true); + + hud_complexity_list_t complexity_list = LLHUDRenderNotifier::getInstance()->getHUDComplexityList(); + + hud_complexity_list_t::iterator iter = complexity_list.begin(); + hud_complexity_list_t::iterator end = complexity_list.end(); + + U32 max_complexity = 0; + for (; iter != end; ++iter) + { + max_complexity = llmax(max_complexity, (*iter).objectsCost); + } + + auto huds_max_render_time_raw = FSPerfStats::StatsRecorder::getMax(HudType, FSPerfStats::StatType_t::RENDER_GEOMETRY); + for (iter = complexity_list.begin(); iter != end; ++iter) + { + LLHUDComplexity hud_object_complexity = *iter; + + if (hud_object_complexity.objectName == FSLSLBridge::instance().currentFullName()) + { + continue; + } + + auto hud_render_time_raw = FSPerfStats::StatsRecorder::get(HudType, hud_object_complexity.objectId, FSPerfStats::StatType_t::RENDER_GEOMETRY); + LLSD item; + S32 obj_cost_short = llmax((S32)hud_object_complexity.objectsCost / 1000, 1); + + item["special_id"] = hud_object_complexity.objectId; + item["target"] = LLNameListCtrl::SPECIAL; + LLSD& row = item["columns"]; + row[0]["column"] = "art_visual"; + row[0]["type"] = "bar"; + LLSD& value = row[0]["value"]; + value["ratio"] = (F32)hud_render_time_raw / huds_max_render_time_raw; + value["bottom"] = BAR_BOTTOM_PAD; + value["left_pad"] = BAR_LEFT_PAD; + value["right_pad"] = BAR_RIGHT_PAD; + + row[1]["column"] = "art_value"; + row[1]["type"] = "text"; + row[1]["value"] = llformat( "%.2f",FSPerfStats::raw_to_us(hud_render_time_raw) ); + row[1]["font"]["name"] = "SANSSERIF"; + + row[2]["column"] = "complex_value"; + row[2]["type"] = "text"; + row[2]["value"] = std::to_string(obj_cost_short); + row[2]["font"]["name"] = "SANSSERIF"; + + row[3]["column"] = "name"; + row[3]["type"] = "text"; + row[3]["value"] = hud_object_complexity.objectName; + row[3]["font"]["name"] = "SANSSERIF"; + + LLScrollListItem* obj = mHUDList->addElement(item); + if (obj) + { + // ART value + LLScrollListText* value_text = dynamic_cast(obj->getColumn(1)); + if (value_text) + { + value_text->setAlignment(LLFontGL::RIGHT); + } + // ARC value + value_text = dynamic_cast(obj->getColumn(2)); + if (value_text) + { + value_text->setAlignment(LLFontGL::RIGHT); + } + // name + value_text = dynamic_cast(obj->getColumn(3)); + if (value_text) + { + value_text->setAlignment(LLFontGL::LEFT); + } + } + } + + mHUDList->sortByColumnIndex(1, FALSE); + mHUDList->setScrollPos(prev_pos); + mHUDList->selectItemBySpecialId(prev_selected_id); +} + +void FSFloaterPerformance::populateObjectList() +{ + S32 prev_pos = mObjectList->getScrollPos(); + auto prev_selected_id = mObjectList->getSelectedSpecialId(); + + std::string current_sort_col = mObjectList->getSortColumnName(); + BOOL current_sort_asc = mObjectList->getSortAscending(); + + mObjectList->clearRows(); + mObjectList->updateColumns(true); + + object_complexity_list_t attachment_list = LLAvatarRenderNotifier::getInstance()->getObjectComplexityList(); + + object_complexity_list_t::iterator iter = attachment_list.begin(); + object_complexity_list_t::iterator end = attachment_list.end(); + + U32 max_complexity = 0; + for (; iter != end; ++iter) + { + max_complexity = llmax(max_complexity, (*iter).objectCost); + } + + // for consistency we lock the buffer while we build the list. In theory this is uncontended as th ebuffer should only toggle on end of frame + { + std::lock_guard guard{FSPerfStats::bufferToggleLock}; + auto att_max_render_time_raw = FSPerfStats::StatsRecorder::getMax(AttType, FSPerfStats::StatType_t::RENDER_COMBINED); + auto att_sum_render_time_raw = FSPerfStats::StatsRecorder::getSum(AttType, FSPerfStats::StatType_t::RENDER_COMBINED); + LL_DEBUGS("PerfFloater") << "Attachments for frame : " << gFrameCount << " Max:" << att_max_render_time_raw << LL_ENDL; + for (iter = attachment_list.begin(); iter != end; ++iter) + { + LLObjectComplexity attachment_complexity = *iter; + S32 obj_cost_short = llmax((S32)attachment_complexity.objectCost / 1000, 1); + + auto& attID{attachment_complexity.objectId}; + auto& attName{attachment_complexity.objectName}; + auto attach_render_time_raw = FSPerfStats::StatsRecorder::get(AttType, attID, FSPerfStats::StatType_t::RENDER_COMBINED); + LL_DEBUGS("PerfFloater") << "Att: " << attName << " (" << attID.asString() << ") Cost: " << FSPerfStats::raw_to_us(attach_render_time_raw) << LL_ENDL; + LLSD item; + item["special_id"] = attID; + item["target"] = LLNameListCtrl::SPECIAL; + LLSD& row = item["columns"]; + row[0]["column"] = "art_visual"; + row[0]["type"] = "bar"; + LLSD& value = row[0]["value"]; + value["ratio"] = ((F32)attach_render_time_raw) / att_max_render_time_raw; + value["bottom"] = BAR_BOTTOM_PAD; + value["left_pad"] = BAR_LEFT_PAD; + value["right_pad"] = BAR_RIGHT_PAD; + + row[1]["column"] = "art_value"; + row[1]["type"] = "text"; + // row[1]["value"] = std::to_string(obj_cost_short); + row[1]["value"] = llformat( "%.2f", FSPerfStats::raw_to_us(attach_render_time_raw) ); + row[1]["font"]["name"] = "SANSSERIF"; + + row[2]["column"] = "complex_value"; + row[2]["type"] = "text"; + row[2]["value"] = std::to_string(obj_cost_short); + row[2]["font"]["name"] = "SANSSERIF"; + + row[3]["column"] = "name"; + row[3]["type"] = "text"; + row[3]["value"] = attName; + row[3]["font"]["name"] = "SANSSERIF"; + + LLScrollListItem* obj = mObjectList->addElement(item); + if (obj) + { + // ART value + LLScrollListText* value_text = dynamic_cast(obj->getColumn(1)); + if (value_text) + { + value_text->setAlignment(LLFontGL::RIGHT); + } + // ARC value + value_text = dynamic_cast(obj->getColumn(2)); + if (value_text) + { + value_text->setAlignment(LLFontGL::RIGHT); + } + } + } + + auto textbox = getChild("tot_att_count"); + LLStringUtil::format_map_t args; + args["TOT_ATT"] = llformat("%d", (int64_t)attachment_list.size()); + args["TOT_ATT_TIME"] = llformat("%.2f", FSPerfStats::raw_to_us(att_sum_render_time_raw)); + textbox->setText(getString("tot_att_template", args)); + } + + LL_DEBUGS("PerfFloater") << "Attachments for frame : " << gFrameCount << " COMPLETED" << LL_ENDL; + mNearbyList->sortByColumn(current_sort_col, current_sort_asc); + mObjectList->setScrollPos(prev_pos); + mObjectList->selectItemBySpecialId(prev_selected_id); +} + +void FSFloaterPerformance::populateNearbyList() +{ + S32 prev_pos = mNearbyList->getScrollPos(); + LLUUID prev_selected_id = mNearbyList->getStringUUIDSelectedItem(); + std::string current_sort_col = mNearbyList->getSortColumnName(); + BOOL current_sort_asc = mNearbyList->getSortAscending(); + + if (current_sort_col == "art_visual") + { + current_sort_col = "art_value"; + current_sort_asc = false; + } + + mNearbyList->clearRows(); + mNearbyList->updateColumns(true); + + std::vector valid_nearby_avs; + getNearbyAvatars(valid_nearby_avs); + + std::vector::iterator char_iter = valid_nearby_avs.begin(); + + FSPerfStats::bufferToggleLock.lock(); + auto av_render_max_raw = FSPerfStats::StatsRecorder::getMax(AvType, FSPerfStats::StatType_t::RENDER_COMBINED); + auto av_render_tot_raw = FSPerfStats::StatsRecorder::getSum(AvType, FSPerfStats::StatType_t::RENDER_COMBINED); + FSPerfStats::bufferToggleLock.unlock(); + + // FSPlot("max ART", (int64_t)av_render_max_raw); + // FSPlot("Num av", (int64_t)valid_nearby_avs.size()); + + while (char_iter != valid_nearby_avs.end()) + { + LLVOAvatar* avatar = dynamic_cast(*char_iter); + if (avatar) + { + auto overall_appearance = avatar->getOverallAppearance(); + if (overall_appearance == LLVOAvatar::AOA_INVISIBLE) + { + continue; + } + + S32 complexity_short = llmax((S32)avatar->getVisualComplexity() / 1000, 1); + + FSPerfStats::bufferToggleLock.lock(); + auto render_av_raw = FSPerfStats::StatsRecorder::get(AvType, avatar->getID(),FSPerfStats::StatType_t::RENDER_COMBINED); + FSPerfStats::bufferToggleLock.unlock(); + + auto is_slow = avatar->isTooSlowWithShadows(); + + LLSD item; + item["id"] = avatar->getID(); + LLSD& row = item["columns"]; + row[0]["column"] = "art_visual"; + row[0]["type"] = "bar"; + LLSD& value = row[0]["value"]; + // The ratio used in the bar is the current cost, as soon as we take action this changes so we keep the + // pre-tune value for the numerical column and sorting. + value["ratio"] = (double)render_av_raw / av_render_max_raw; + value["bottom"] = BAR_BOTTOM_PAD; + value["left_pad"] = BAR_LEFT_PAD; + value["right_pad"] = BAR_RIGHT_PAD; + + row[1]["column"] = "art_value"; + row[1]["type"] = "text"; + if (is_slow) + { + row[1]["value"] = llformat( "%.2f", FSPerfStats::raw_to_us( avatar->getLastART() ) ); + } + else + { + row[1]["value"] = llformat( "%.2f", FSPerfStats::raw_to_us( render_av_raw ) ); + } + row[1]["font"]["name"] = "SANSSERIF"; + row[1]["width"] = "50"; + + row[2]["column"] = "complex_value"; + row[2]["type"] = "text"; + row[2]["value"] = std::to_string(complexity_short); + row[2]["font"]["name"] = "SANSSERIF"; + row[2]["width"] = "50"; + + row[3]["column"] = "state"; + row[3]["type"] = "text"; + if (is_slow) + { + if (avatar->isTooSlowWithoutShadows()) + { + row[3]["value"] = std::string{"I"}; + } + else + { + row[3]["value"] = std::string{"S"}; + } + } + else + { + row[3]["value"] = std::string{" "}; + } + + row[3]["font"]["name"] = "SANSSERIF"; + + row[4]["column"] = "name"; + + LLScrollListItem* av_item = mNearbyList->addElement(item); + if (av_item) + { + LLScrollListText* art_text = dynamic_cast(av_item->getColumn(1)); + if (art_text) + { + art_text->setAlignment(LLFontGL::RIGHT); + } + LLScrollListText* value_text = dynamic_cast(av_item->getColumn(2)); + if (value_text) + { + value_text->setAlignment(LLFontGL::RIGHT); + } + LLScrollListText* state_text = dynamic_cast(av_item->getColumn(3)); + if (state_text) + { + state_text->setAlignment(LLFontGL::HCENTER); + } + LLScrollListText* name_text = dynamic_cast(av_item->getColumn(4)); + if (name_text) + { + if (avatar->isSelf()) + { + name_text->setColor(LLUIColorTable::instance().getColor("DrYellow")); + } + else + { + std::string color = "white"; + if (is_slow || LLVOAvatar::AOA_JELLYDOLL == overall_appearance) + { + color = "LabelDisabledColor"; + LLScrollListBar* bar = dynamic_cast(av_item->getColumn(0)); + if (bar) + { + bar->setColor(LLUIColorTable::instance().getColor(color)); + } + } + else if (LLVOAvatar::AOA_NORMAL == overall_appearance) + { + color = LLAvatarActions::isFriend(avatar->getID()) ? "ConversationFriendColor" : "white"; + } + name_text->setColor(LLUIColorTable::instance().getColor(color)); + } + } + } + } + char_iter++; + } + mNearbyList->sortByColumn(current_sort_col, current_sort_asc); + mNearbyList->setScrollPos(prev_pos); + mNearbyList->selectByID(prev_selected_id); + + auto textbox = getChild("tot_av_count"); + LLStringUtil::format_map_t args; + args["TOT_AV"] = llformat("%d", (int64_t)valid_nearby_avs.size()); + args["TOT_AV_TIME"] = llformat("%.2f", FSPerfStats::raw_to_us(av_render_tot_raw)); + textbox->setText(getString("tot_av_template", args)); +} + +void FSFloaterPerformance::getNearbyAvatars(std::vector &valid_nearby_avs) +{ + static LLCachedControl render_far_clip(gSavedSettings, "RenderFarClip", 64); + mNearbyMaxComplexity = 0; + F32 radius = render_far_clip * render_far_clip; + std::vector::iterator char_iter = LLCharacter::sInstances.begin(); + while (char_iter != LLCharacter::sInstances.end()) + { + LLVOAvatar* avatar = dynamic_cast(*char_iter); + if (avatar && !avatar->isDead() && !avatar->isControlAvatar()) + { + if ((dist_vec_squared(avatar->getPositionGlobal(), gAgent.getPositionGlobal()) > radius) && + (dist_vec_squared(avatar->getPositionGlobal(), gAgentCamera.getCameraPositionGlobal()) > radius)) + { + char_iter++; + continue; + } + avatar->calculateUpdateRenderComplexity(); + mNearbyMaxComplexity = llmax(mNearbyMaxComplexity, (S32)avatar->getVisualComplexity()); + valid_nearby_avs.push_back(*char_iter); + } + char_iter++; + } +} + +void FSFloaterPerformance::detachItem(const LLUUID& item_id) +{ + LLAppearanceMgr::instance().removeItemFromAvatar(item_id); +} + +void FSFloaterPerformance::onChangeQuality(const LLSD& data) +{ + LLFloaterPreference* instance = LLFloaterReg::getTypedInstance("preferences"); + if (instance) + { + instance->onChangeQuality(data); + } +} + +void FSFloaterPerformance::onClickHideAvatars() +{ + LLPipeline::toggleRenderTypeControl(LLPipeline::RENDER_TYPE_AVATAR); +} + +void FSFloaterPerformance::onClickFocusAvatar() +{ + FSPerfStats::StatsRecorder::setFocusAv(mNearbyCombo->getSelectedValue().asUUID()); +} + +void FSFloaterPerformance::onClickExceptions() +{ + // [FS Persisted Avatar Render Settings] + //LLFloaterReg::showInstance("avatar_render_settings"); + LLFloaterReg::showInstance("fs_avatar_render_settings"); + // +} + +void FSFloaterPerformance::updateMaxComplexity() +{ + LLAvatarComplexityControls::updateMax( + mNearbyPanel->getChild("IndirectMaxComplexity"), + mNearbyPanel->getChild("IndirectMaxComplexityText"), + true); +} + +void FSFloaterPerformance::updateMaxRenderTime() +{ + LLAvatarComplexityControls::updateMaxRenderTime( + mNearbyPanel->getChild("FSRenderAvatarMaxART"), + mNearbyPanel->getChild("FSRenderAvatarMaxARTText"), + true); +} + +void FSFloaterPerformance::updateMaxRenderTimeText() +{ + LLAvatarComplexityControls::setRenderTimeText( + gSavedSettings.getF32("FSRenderAvatarMaxART"), + mNearbyPanel->getChild("FSRenderAvatarMaxARTText", true), + true); +} + +void FSFloaterPerformance::updateComplexityText() +{ + LLAvatarComplexityControls::setText(gSavedSettings.getU32("RenderAvatarMaxComplexity"), + mNearbyPanel->getChild("IndirectMaxComplexityText", true), + true); +} + +static LLVOAvatar* find_avatar(const LLUUID& id) +{ + LLViewerObject *obj = gObjectList.findObject(id); + while (obj && obj->isAttachment()) + { + obj = (LLViewerObject *)obj->getParent(); + } + + if (obj && obj->isAvatar()) + { + return (LLVOAvatar*)obj; + } + else + { + return NULL; + } +} + +void FSFloaterPerformance::onCustomAction(const LLSD& userdata, const LLUUID& av_id) +{ + const std::string command_name = userdata.asString(); + + LLVOAvatar::VisualMuteSettings new_setting = LLVOAvatar::AV_RENDER_NORMALLY; + if ("default" == command_name) + { + new_setting = LLVOAvatar::AV_RENDER_NORMALLY; + } + else if ("never" == command_name) + { + new_setting = LLVOAvatar::AV_DO_NOT_RENDER; + } + else if ("always" == command_name) + { + new_setting = LLVOAvatar::AV_ALWAYS_RENDER; + } + + LLVOAvatar *avatarp = find_avatar(av_id); + if (avatarp) + { + avatarp->setVisualMuteSettings(new_setting); + } + else + { + // [FS Persisted Avatar Render Settings] + //LLRenderMuteList::getInstance()->saveVisualMuteSetting(av_id, new_setting); + FSAvatarRenderPersistence::instance().setAvatarRenderSettings(av_id, LLVOAvatar::VisualMuteSettings(new_setting)); + // + } +} + +bool FSFloaterPerformance::isActionChecked(const LLSD& userdata, const LLUUID& av_id) +{ + const std::string command_name = userdata.asString(); + + // [FS Persisted Avatar Render Settings] + //S32 visual_setting = LLRenderMuteList::getInstance()->getSavedVisualMuteSetting(av_id); + S32 visual_setting = (S32)FSAvatarRenderPersistence::instance().getAvatarRenderSettings(av_id); + // + if ("default" == command_name) + { + return (visual_setting == LLVOAvatar::AV_RENDER_NORMALLY); + } + else if ("non_default" == command_name) + { + return (visual_setting != LLVOAvatar::AV_RENDER_NORMALLY); + } + else if ("never" == command_name) + { + return (visual_setting == LLVOAvatar::AV_DO_NOT_RENDER); + } + else if ("always" == command_name) + { + return (visual_setting == LLVOAvatar::AV_ALWAYS_RENDER); + } + return false; +} + +void FSFloaterPerformance::onAvatarListRightClick(LLUICtrl* ctrl, S32 x, S32 y) +{ + LLNameListCtrl* list = dynamic_cast(ctrl); + if (!list) return; + list->selectItemAt(x, y, MASK_NONE); + uuid_vec_t selected_uuids; + + if ((list->getCurrentID().notNull()) && (list->getCurrentID() != gAgentID)) + { + selected_uuids.push_back(list->getCurrentID()); + mContextMenu->show(ctrl, selected_uuids, x, y); + } +} + +void FSFloaterPerformance::onExtendedAction(const LLSD& userdata, const LLUUID& av_id) +{ + const std::string command_name = userdata.asString(); + + LLViewerObject* objectp = gObjectList.findObject(av_id); + if (!objectp) + { + return; + } + auto avp = objectp->asAvatar(); + if ("inspect" == command_name) + { + for (LLVOAvatar::attachment_map_t::iterator iter = avp->mAttachmentPoints.begin(); + iter != avp->mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + + if (!attachment) + { + continue; + } + + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject* attached_object = attachment_iter->get(); + + if ( attached_object && !attached_object->isDead() ) + { + LLSelectMgr::getInstance()->selectObjectAndFamily(attached_object); + } + } + } + LLFloaterReg::showInstance("inspect"); + } + else if ("zoom" == command_name) + { + // Disable flycam if active. Without this, the requested look-at doesn't happen because the flycam code overrides all other camera motion. + bool fly_cam_status(LLViewerJoystick::getInstance()->getOverrideCamera()); + if (fly_cam_status) + { + LLViewerJoystick::getInstance()->setOverrideCamera(false); + LLPanelStandStopFlying::clearStandStopFlyingMode(LLPanelStandStopFlying::SSFM_FLYCAM); + // *NOTE: Above may not be the proper way to disable flycam. What I really want to do is just be able to move the camera and then leave the flycam in the the same state it was in, just moved to the new location. ~Cron + } + + LLViewerJoystick::getInstance()->setCameraNeedsUpdate(true); // Fixes an edge case where if the user has JUST disabled flycam themselves, the camera gets stuck waiting for input. + + gAgentCamera.setFocusOnAvatar(FALSE, ANIMATE); + + gAgentCamera.setLookAt(LOOKAT_TARGET_SELECT, objectp); + + // Place the camera looking at the object, along the line from the camera to the object, + // and sufficiently far enough away for the object to fill 3/4 of the screen, + // but not so close that the bbox's nearest possible vertex goes inside the near clip. + // Logic C&P'd from LLViewerMediaFocus::setCameraZoom() and then edited as needed + + LLBBox bbox = objectp->getBoundingBoxAgent(); + LLVector3d center(gAgent.getPosGlobalFromAgent(bbox.getCenterAgent())); + F32 height; + F32 width; + F32 depth; + F32 angle_of_view; + F32 distance; + + LLVector3d target_pos(center); + LLVector3d camera_dir(gAgentCamera.getCameraPositionGlobal() - target_pos); + camera_dir.normalize(); + + // We need the aspect ratio, and the 3 components of the bbox as height, width, and depth. + F32 aspect_ratio(LLViewerMediaFocus::getBBoxAspectRatio(bbox, LLVector3(camera_dir), &height, &width, &depth)); + F32 camera_aspect(LLViewerCamera::getInstance()->getAspect()); + + // We will normally use the side of the volume aligned with the short side of the screen (i.e. the height for + // a screen in a landscape aspect ratio), however there is an edge case where the aspect ratio of the object is + // more extreme than the screen. In this case we invert the logic, using the longer component of both the object + // and the screen. + bool invert((camera_aspect > 1.0f && aspect_ratio > camera_aspect) || (camera_aspect < 1.0f && aspect_ratio < camera_aspect)); + + // To calculate the optimum viewing distance we will need the angle of the shorter side of the view rectangle. + // In portrait mode this is the width, and in landscape it is the height. + // We then calculate the distance based on the corresponding side of the object bbox (width for portrait, height for landscape) + // We will add half the depth of the bounding box, as the distance projection uses the center point of the bbox. + if (camera_aspect < 1.0f || invert) + { + angle_of_view = llmax(0.1f, LLViewerCamera::getInstance()->getView() * LLViewerCamera::getInstance()->getAspect()); + distance = width * 0.5 * 1.1 / tanf(angle_of_view * 0.5f); + } + else + { + angle_of_view = llmax(0.1f, LLViewerCamera::getInstance()->getView()); + distance = height * 0.5 * 1.1 / tanf(angle_of_view * 0.5f); + } + + distance += depth * 0.5; + + // Verify that the bounding box isn't inside the near clip. Using OBB-plane intersection to check if the + // near-clip plane intersects with the bounding box, and if it does, adjust the distance such that the + // object doesn't clip. + LLVector3d bbox_extents(bbox.getExtentLocal()); + LLVector3d axis_x = LLVector3d(1, 0, 0) * bbox.getRotation(); + LLVector3d axis_y = LLVector3d(0, 1, 0) * bbox.getRotation(); + LLVector3d axis_z = LLVector3d(0, 0, 1) * bbox.getRotation(); + //Normal of nearclip plane is camera_dir. + F32 min_near_clip_dist = bbox_extents.mdV[VX] * (camera_dir * axis_x) + bbox_extents.mdV[VY] * (camera_dir * axis_y) + bbox_extents.mdV[VZ] * (camera_dir * axis_z); // http://www.gamasutra.com/view/feature/131790/simple_intersection_tests_for_games.php?page=7 + F32 camera_to_near_clip_dist(LLViewerCamera::getInstance()->getNear()); + F32 min_camera_dist(min_near_clip_dist + camera_to_near_clip_dist); + if (distance < min_camera_dist) + { + // Camera is too close to object, some parts MIGHT clip. Move camera away to the position where clipping barely doesn't happen. + distance = min_camera_dist; + } + + LLVector3d camera_pos(target_pos + camera_dir * distance); + + if (camera_dir == LLVector3d::z_axis || camera_dir == LLVector3d::z_axis_neg) + { + // If the direction points directly up, the camera will "flip" around. + // We try to avoid this by adjusting the target camera position a + // smidge towards current camera position + // *NOTE: this solution is not perfect. All it attempts to solve is the + // "looking down" problem where the camera flips around when it animates + // to that position. You still are not guaranteed to be looking at the + // object in the correct orientation. What this solution does is it will + // put the camera into position keeping as best it can the current + // orientation with respect to the direction wanted. In other words, if + // before zoom the object appears "upside down" from the camera, after + /// zooming it will still be upside down, but at least it will not flip. + LLVector3d cur_camera_pos = LLVector3d(gAgentCamera.getCameraPositionGlobal()); + LLVector3d delta = (cur_camera_pos - camera_pos); + F64 len = delta.length(); + delta.normalize(); + // Move 1% of the distance towards original camera location + camera_pos += 0.01 * len * delta; + } + + gAgentCamera.setCameraPosAndFocusGlobal(camera_pos, target_pos, objectp->getID()); + + // *TODO: Re-enable joystick flycam if we disabled it earlier... Have to find some form of callback as re-enabling at this point causes the camera motion to not happen. ~Cron + //if (fly_cam_status) + //{ + // LLViewerJoystick::getInstance()->toggleFlycam(); + //} + } +} + +// EOF diff --git a/indra/newview/fsfloaterperformance.h b/indra/newview/fsfloaterperformance.h new file mode 100644 index 0000000000..034c772ae2 --- /dev/null +++ b/indra/newview/fsfloaterperformance.h @@ -0,0 +1,98 @@ +/** + * @file fsfloaterperformance.h + * + * This is forked directly from an early access release of llfloaterperformance.h + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2021, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef FS_FLOATERPERFORMANCE_H +#define FS_FLOATERPERFORMANCE_H + +#include "llfloater.h" +#include "lllistcontextmenu.h" + +class LLCharacter; +class LLNameListCtrl; +class LLComboBox; + +class FSFloaterPerformance : public LLFloater +{ +public: + FSFloaterPerformance(const LLSD& key); + virtual ~FSFloaterPerformance(); + + /*virtual*/ BOOL postBuild(); + /*virtual*/ void draw(); + + void showSelectedPanel(LLPanel* selected_panel); + void showMainPanel(); + void hidePanels(); + + void detachItem(const LLUUID& item_id); + + void onAvatarListRightClick(LLUICtrl* ctrl, S32 x, S32 y); + + void onCustomAction (const LLSD& userdata, const LLUUID& av_id); + bool isActionChecked(const LLSD& userdata, const LLUUID& av_id); + void onExtendedAction(const LLSD& userdata, const LLUUID& av_id); + +private: + void initBackBtn(LLPanel* panel); + void populateHUDList(); + void populateObjectList(); + void populateNearbyList(); + + void onChangeQuality(const LLSD& data); + void onClickHideAvatars(); + void onClickExceptions(); + void onClickFocusAvatar(); + + void updateMaxComplexity(); + void updateComplexityText(); + void updateMaxRenderTime(); + void updateMaxRenderTimeText(); + + void getNearbyAvatars(std::vector &valid_nearby_avs); + + LLPanel* mMainPanel; + LLPanel* mNearbyPanel; + LLPanel* mComplexityPanel; + LLPanel* mHUDsPanel; + LLPanel* mSettingsPanel; + LLPanel* mAutoTunePanel; + LLNameListCtrl* mHUDList; + LLNameListCtrl* mObjectList; + LLNameListCtrl* mNearbyList; + LLComboBox* mNearbyCombo; + + LLListContextMenu* mContextMenu; + + LLTimer* mUpdateTimer; + + S32 mNearbyMaxComplexity; + + boost::signals2::connection mComplexityChangedSignal; + boost::signals2::connection mMaxARTChangedSignal; +}; + +#endif // FS_FLOATERPERFORMANCE_H diff --git a/indra/newview/fsperfstats.cpp b/indra/newview/fsperfstats.cpp new file mode 100644 index 0000000000..3f94f9cf1d --- /dev/null +++ b/indra/newview/fsperfstats.cpp @@ -0,0 +1,419 @@ +/** + * @file fsperfstats.cpp + * @brief Stats collection to support perf floater and auto tune + * + * $LicenseInfo:firstyear=2021&license=fsviewerlgpl$ + * Phoenix Firestorm Viewer Source Code + * Copyright (C) 2021, The Phoenix Firestorm Project, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA + * http://www.firestormviewer.org + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "fsperfstats.h" +#include "llcontrol.h" +#include "pipeline.h" +#include "llagentcamera.h" +#include "llvoavatar.h" +#include "llworld.h" + +extern LLControlGroup gSavedSettings; + +namespace FSPerfStats +{ + #ifdef USAGE_TRACKING + std::atomic inUse{0}; + std::atomic inUseAvatar{0}; + std::atomic inUseScene{0}; + std::atomic inUseAttachment{0}; + std::atomic inUseAttachmentRigged{0}; + std::atomic inUseAttachmentUnRigged{0}; +#endif + + std::atomic tunedAvatars{0}; + U32 targetFPS; // desired FPS + U64 renderAvatarMaxART_ns{(U64)(ART_UNLIMITED_NANOS)}; // highest render time we'll allow without culling features + U32 fpsTuningStrategy{0}; // linked to FSTuningFPSStrategy + U32 lastGlobalPrefChange{0}; + std::mutex bufferToggleLock{}; + bool autoTune{false}; + + std::atomic StatsRecorder::writeBuffer{0}; + bool StatsRecorder::collectionEnabled{true}; + LLUUID StatsRecorder::focusAv{LLUUID::null}; + std::array StatsRecorder::statsDoubleBuffer{ {} }; + std::array StatsRecorder::max{ {} }; + std::array StatsRecorder::sum{ {} }; + + + StatsRecorder::StatsRecorder():q(1024*16),t(&StatsRecorder::run) + { + // create a queue + // create a thread to consume from the queue + + FSPerfStats::targetFPS = gSavedSettings.getU32("FSTargetFPS"); + FSPerfStats::autoTune = gSavedSettings.getBOOL("FSAutoTuneFPS"); + + updateRenderCostLimitFromSettings(); + + t.detach(); + } + + // static + void StatsRecorder::toggleBuffer() + { + FSZone; + using ST = StatType_t; + + bool unreliable{false}; + LLCachedControl smoothingPeriods(gSavedSettings, "FSPerfFloaterSmoothingPeriods"); + + auto& sceneStats = statsDoubleBuffer[writeBuffer][static_cast(ObjType_t::OT_GENERAL)][LLUUID::null]; + auto& lastStats = statsDoubleBuffer[writeBuffer ^ 1][static_cast(ObjType_t::OT_GENERAL)][LLUUID::null]; + + static constexpr std::initializer_list sceneStatsToAvg = { + StatType_t::RENDER_FRAME, + StatType_t::RENDER_DISPLAY, + StatType_t::RENDER_HUDS, + StatType_t::RENDER_UI, + StatType_t::RENDER_SWAP, + // RENDER_LFS, + // RENDER_MESHREPO, + StatType_t::RENDER_IDLE }; + + static constexpr std::initializer_list avatarStatsToAvg = { + StatType_t::RENDER_GEOMETRY, + StatType_t::RENDER_SHADOWS, + StatType_t::RENDER_COMBINED, + StatType_t::RENDER_IDLE }; + + + if( sceneStats[static_cast(StatType_t::RENDER_FPSLIMIT)] != 0 || sceneStats[static_cast(StatType_t::RENDER_SLEEP)] != 0 ) + { + unreliable = true; + lastStats[static_cast(StatType_t::RENDER_FPSLIMIT)] = sceneStats[static_cast(StatType_t::RENDER_FPSLIMIT)]; + lastStats[static_cast(StatType_t::RENDER_SLEEP)] = sceneStats[static_cast(StatType_t::RENDER_SLEEP)]; + } + + if(!unreliable) + { + // only use these stats when things are reliable. + + for(auto & statEntry : sceneStatsToAvg) + { + auto avg = lastStats[static_cast(statEntry)]; + auto val = sceneStats[static_cast(statEntry)]; + sceneStats[static_cast(statEntry)] = avg + (val/smoothingPeriods) - (avg/smoothingPeriods); + // LL_INFOS("scenestats") << "Scenestat: " << static_cast(statEntry) << " before=" << avg << " new=" << val << " newavg=" << statsDoubleBuffer[writeBuffer][static_cast(ObjType_t::OT_GENERAL)][LLUUID::null][static_cast(statEntry)] << LL_ENDL; + } + + auto& statsMap = statsDoubleBuffer[writeBuffer][static_cast(ObjType_t::OT_ATTACHMENT)]; + for(auto& stat_entry : statsMap) + { + auto val = stat_entry.second[static_cast(ST::RENDER_COMBINED)]; + if(val>smoothingPeriods){ + auto avg = statsDoubleBuffer[writeBuffer ^ 1][static_cast(ObjType_t::OT_ATTACHMENT)][stat_entry.first][static_cast(ST::RENDER_COMBINED)]; + stat_entry.second[static_cast(ST::RENDER_COMBINED)] = avg + (val/smoothingPeriods) - (avg/smoothingPeriods); + } + } + + + auto& statsMapAv = statsDoubleBuffer[writeBuffer][static_cast(ObjType_t::OT_AVATAR)]; + for(auto& stat_entry : statsMapAv) + { + for(auto& stat : avatarStatsToAvg) + { + auto val = stat_entry.second[static_cast(stat)]; + if(val>smoothingPeriods) + { + auto avg = statsDoubleBuffer[writeBuffer ^ 1][static_cast(ObjType_t::OT_AVATAR)][stat_entry.first][static_cast(stat)]; + stat_entry.second[static_cast(stat)] = avg + (val/smoothingPeriods) - (avg/smoothingPeriods); + } + } + } + + // swap the buffers + if(enabled()) + { + std::lock_guard lock{bufferToggleLock}; + writeBuffer ^= 1; + }; // note we are relying on atomic updates here. The risk is low and would cause minor errors in the stats display. + } + + // clean the write maps in all cases. + auto& statsTypeMatrix = statsDoubleBuffer[writeBuffer]; + for(auto& statsMap : statsTypeMatrix) + { + FSZoneN("Clear stats maps"); + for(auto& stat_entry : statsMap) + { + std::fill_n(stat_entry.second.begin() ,static_cast(ST::STATS_COUNT),0); + } + statsMap.clear(); + } + for(int i=0; i< static_cast(ObjType_t::OT_COUNT); i++) + { + FSZoneN("clear max/sum"); + max[writeBuffer][i].fill(0); + sum[writeBuffer][i].fill(0); + } + + // and now adjust the visuals. + if(autoTune) + { + updateAvatarParams(); + } + } + + // clear buffers when we change region or need a hard reset. + // static + void StatsRecorder::clearStatsBuffers() + { + FSZone; + using ST = StatType_t; + + auto& statsTypeMatrix = statsDoubleBuffer[writeBuffer]; + for(auto& statsMap : statsTypeMatrix) + { + FSZoneN("Clear stats maps"); + for(auto& stat_entry : statsMap) + { + std::fill_n(stat_entry.second.begin() ,static_cast(ST::STATS_COUNT),0); + } + statsMap.clear(); + } + for(int i=0; i< static_cast(ObjType_t::OT_COUNT); i++) + { + FSZoneN("clear max/sum"); + max[writeBuffer][i].fill(0); + sum[writeBuffer][i].fill(0); + } + // swap the clean buffers in + if(enabled()) + { + std::lock_guard lock{bufferToggleLock}; + writeBuffer ^= 1; + }; + // repeat before we start processing new stuff + for(auto& statsMap : statsTypeMatrix) + { + FSZoneN("Clear stats maps"); + for(auto& stat_entry : statsMap) + { + std::fill_n(stat_entry.second.begin() ,static_cast(ST::STATS_COUNT),0); + } + statsMap.clear(); + } + for(int i=0; i< static_cast(ObjType_t::OT_COUNT); i++) + { + FSZoneN("clear max/sum"); + max[writeBuffer][i].fill(0); + sum[writeBuffer][i].fill(0); + } + } + + // static + void StatsRecorder::updateSettingsFromRenderCostLimit() + { + static LLCachedControl maxRenderCost_us(gSavedSettings, "FSRenderAvatarMaxART"); + if( (F32)maxRenderCost_us != log10( ( (F32)FSPerfStats::renderAvatarMaxART_ns )/1000 ) ) + { + if( FSPerfStats::renderAvatarMaxART_ns != 0 ) + { + gSavedSettings.setF32( "FSRenderAvatarMaxART", log10( ( (F32)FSPerfStats::renderAvatarMaxART_ns )/1000 ) ); + } + else + { + gSavedSettings.setF32( "FSRenderAvatarMaxART",log10( FSPerfStats::ART_UNLIMITED_NANOS/1000 ) ); + } + } + } + + // static + void StatsRecorder::updateRenderCostLimitFromSettings() + { + const auto newval = gSavedSettings.getF32("FSRenderAvatarMaxART"); + if(newval < log10(FSPerfStats::ART_UNLIMITED_NANOS/1000)) + { + FSPerfStats::renderAvatarMaxART_ns = pow(10,newval)*1000; + } + else + { + FSPerfStats::renderAvatarMaxART_ns = 0; + }; + } + + //static + int StatsRecorder::countNearbyAvatars(S32 distance) + { + const auto our_pos = gAgentCamera.getCameraPositionGlobal(); + + std::vector positions; + uuid_vec_t avatar_ids; + LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, our_pos, distance); + return positions.size(); + } + + // static + void StatsRecorder::updateAvatarParams() + { + static LLCachedControl drawDistance(gSavedSettings, "RenderFarClip"); + static LLCachedControl userMinDrawDistance(gSavedSettings, "FSAutoTuneRenderFarClipMin"); + static LLCachedControl userTargetDrawDistance(gSavedSettings, "FSAutoTuneRenderFarClipTarget"); + static LLCachedControl impostorDistance(gSavedSettings, "FSAutoTuneImpostorFarAwayDistance"); + static LLCachedControl impostorDistanceTuning(gSavedSettings, "FSAutoTuneImpostorByDistEnabled"); + static LLCachedControl maxNonImpostors (gSavedSettings, "IndirectMaxNonImpostors"); + + if(impostorDistanceTuning) + { + // if we have less than the user's "max Non-Impostors" avatars within the desired range then adjust the limit. + // also adjusts back up again for nearby crowds. + auto count = countNearbyAvatars(std::min(drawDistance, impostorDistance)); + if( count != maxNonImpostors ) + { + gSavedSettings.setU32("IndirectMaxNonImpostors", (count < LLVOAvatar::NON_IMPOSTORS_MAX_SLIDER)?count : LLVOAvatar::NON_IMPOSTORS_MAX_SLIDER); + LL_DEBUGS("AutoTune") << "There are " << count << "avatars within " << std::min(drawDistance, impostorDistance) << "m of the camera" << LL_ENDL; + } + } + + auto av_render_max_raw = FSPerfStats::StatsRecorder::getMax(ObjType_t::OT_AVATAR, FSPerfStats::StatType_t::RENDER_COMBINED); + // Is our target frame time lower than current? If so we need to take action to reduce draw overheads. + // cumulative avatar time (includes idle processing, attachments and base av) + auto tot_avatar_time_raw = FSPerfStats::StatsRecorder::getSum(ObjType_t::OT_AVATAR, FSPerfStats::StatType_t::RENDER_COMBINED); + // sleep time is basically forced sleep when window out of focus + auto tot_sleep_time_raw = FSPerfStats::StatsRecorder::getSceneStat(FSPerfStats::StatType_t::RENDER_SLEEP); + // similar to sleep time, induced by FPS limit + auto tot_limit_time_raw = FSPerfStats::StatsRecorder::getSceneStat(FSPerfStats::StatType_t::RENDER_FPSLIMIT); + + + // the time spent this frame on the "doFrame" call. Treated as "tot time for frame" + auto tot_frame_time_raw = FSPerfStats::StatsRecorder::getSceneStat(FSPerfStats::StatType_t::RENDER_FRAME); + + if( tot_sleep_time_raw != 0 ) + { + // Note: we do not average sleep + // if at some point we need to, the averaging will need to take this into account or + // we forever think we're in the background due to residuals. + LL_DEBUGS() << "No tuning when not in focus" << LL_ENDL; + return; + } + + // The frametime budget we have based on the target FPS selected + auto target_frame_time_raw = (U64)llround((F64)LLTrace::BlockTimer::countsPerSecond()/(targetFPS==0?1:targetFPS)); + // LL_INFOS() << "Effective FPS(raw):" << tot_frame_time_raw << " Target:" << target_frame_time_raw << LL_ENDL; + + if( tot_limit_time_raw != 0) + { + // This could be problematic. + tot_frame_time_raw -= tot_limit_time_raw; + } + // 1) Is the target frame tim lower than current? + if( target_frame_time_raw <= tot_frame_time_raw ) + { + // if so we've got work to do + + // how much of the frame was spent on non avatar related work? + U32 non_avatar_time_raw = tot_frame_time_raw - tot_avatar_time_raw; + + // If the target frame time < non avatar frame time thne adjusting avatars is only goin gto get us so far. + U64 target_avatar_time_raw; + if(target_frame_time_raw < non_avatar_time_raw) + { + // we cannnot do this by avatar adjustment alone. + if((gFrameCount - FSPerfStats::lastGlobalPrefChange) > 10) // give changes a short time to take effect. + { + if(FSPerfStats::fpsTuningStrategy == 1) + { + // 1 - hack the water to opaque. all non opaque have a significant hit, this is a big boost for (arguably) a minor visual hit. + // the other reflection options make comparatively little change and iof this overshoots we'll be stepping back up later + if(LLPipeline::RenderReflectionDetail != -2) + { + gSavedSettings.setS32("RenderReflectionDetail", -2); + FSPerfStats::lastGlobalPrefChange = gFrameCount; + return; + } + else // deliberately "else" here so we only do these in steps + { + // step down the DD by 10m per update + auto new_dd = (drawDistance-10>userMinDrawDistance)?(drawDistance - 10) : userMinDrawDistance; + if(new_dd != drawDistance) + { + gSavedSettings.setF32("RenderFarClip", new_dd); + FSPerfStats::lastGlobalPrefChange = gFrameCount; + return; + } + } + } + } + target_avatar_time_raw = 0; + } + else + { + // desired avatar budget. + target_avatar_time_raw = target_frame_time_raw - non_avatar_time_raw; + } + + if( target_avatar_time_raw < tot_avatar_time_raw ) + { + // we need to spend less time drawing avatars to meet our budget + // Note: working in usecs now cos reasons. + auto new_render_limit_ns {renderAvatarMaxART_ns}; + // max render this frame may be higher than the last (cos new entrants and jitter) so make sure we are heading in the right direction + if(FSPerfStats::raw_to_ns(av_render_max_raw) < renderAvatarMaxART_ns) + { + new_render_limit_ns = FSPerfStats::raw_to_ns(av_render_max_raw); + } + else + { + new_render_limit_ns = renderAvatarMaxART_ns; + } + new_render_limit_ns -= FSPerfStats::ART_MIN_ADJUST_DOWN_NANOS; + + // bounce at the bottom to prevent "no limit" + new_render_limit_ns = std::max((U64)new_render_limit_ns, (U64)FSPerfStats::ART_MINIMUM_NANOS); + + // assign the new value + renderAvatarMaxART_ns = new_render_limit_ns; + // LL_DEBUGS() << "AUTO_TUNE: avatar_budget adjusted to:" << new_render_limit_ns << LL_ENDL; + } + // LL_DEBUGS() << "AUTO_TUNE: Target frame time:"<< FSPerfStats::raw_to_us(target_frame_time_raw) << "usecs (non_avatar is " << FSPerfStats::raw_to_us(non_avatar_time_raw) << "usecs) Max cost limited=" << renderAvatarMaxART_ns << LL_ENDL; + } + else if( FSPerfStats::raw_to_ns(target_frame_time_raw) > (FSPerfStats::raw_to_ns(tot_frame_time_raw) + renderAvatarMaxART_ns) ) + { + if( FSPerfStats::tunedAvatars >= 0 ) + { + // if we have more time to spare let's shift up little in the hope we'll restore an avatar. + renderAvatarMaxART_ns += FSPerfStats::ART_MIN_ADJUST_UP_NANOS; + } + if( drawDistance < userTargetDrawDistance ) + { + gSavedSettings.setF32("RenderFarClip", drawDistance + 10.); + } + if( (target_frame_time_raw * 1.5) > tot_frame_time_raw && + FSPerfStats::tunedAvatars == 0 && + drawDistance >= userTargetDrawDistance) + { + // if everything else is "max" and we have 50% headroom let's knock the water quality up a notch at a time. + auto water = gSavedSettings.getS32("RenderReflectionDetail"); + gSavedSettings.setS32("RenderReflectionDetail", water+1); + } + } + updateSettingsFromRenderCostLimit(); + } +} \ No newline at end of file diff --git a/indra/newview/fsperfstats.h b/indra/newview/fsperfstats.h new file mode 100644 index 0000000000..df6afda31f --- /dev/null +++ b/indra/newview/fsperfstats.h @@ -0,0 +1,498 @@ +#pragma once +#ifndef FS_PERFSTATS_H_INCLUDED +#define FS_PERFSTATS_H_INCLUDED +/** + * @file fsperfstats.h + * @brief Statistics collection to support autotune and perf flaoter. + * + * $LicenseInfo:firstyear=2021&license=fsviewerlgpl$ + * Phoenix Firestorm Viewer Source Code + * Copyright (C) 2021, The Phoenix Firestorm Project, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA + * http://www.firestormviewer.org + * $/LicenseInfo$ + */ + +#include +#include +#include +#include +#include +#include "lluuid.h" +#include "llfasttimer.h" +#include "blockingconcurrentqueue.h" +#include "llapp.h" +#include "fstelemetry.h" +#include "pipeline.h" + +// Additional logging options. These can skew inworld numbers so onyl use for debugging and tracking issues +#ifdef FS_HAS_TELEMETRY_SUPPORT +// USAGE_TRACKING - displays overlapping stats that may imply double counting. +// ATTACHMENT_TRACKING - displays detailed tracking info for Avatar and Attachment. very heavy overhead. +// #define USAGE_TRACKING +// #define ATTACHMENT_TRACKING +#else +#undef USAGE_TRACKING +#undef ATTACHMENT_TRACKING +#endif + +extern U32 gFrameCount; +extern LLUUID gAgentID; +namespace FSPerfStats +{ +#ifdef USAGE_TRACKING + extern std::atomic inUse; + extern std::atomic inUseAvatar; + extern std::atomic inUseScene; + extern std::atomic inUseAttachment; + extern std::atomic inUseAttachmentRigged; + extern std::atomic inUseAttachmentUnRigged; +#endif +// Note if changing these, they should correspond with the log range of the correpsonding sliders + constexpr U64 ART_UNLIMITED_NANOS{50000000}; + constexpr U64 ART_MINIMUM_NANOS{100000}; + constexpr U64 ART_MIN_ADJUST_UP_NANOS{10000}; + constexpr U64 ART_MIN_ADJUST_DOWN_NANOS{10000}; + + constexpr F32 PREFERRED_DD{180}; + + extern std::atomic tunedAvatars; + extern U32 targetFPS; // desired FPS + extern U64 renderAvatarMaxART_ns; + extern U32 fpsTuningStrategy; + extern U32 lastGlobalPrefChange; + extern std::mutex bufferToggleLock; + extern bool autoTune; + + enum class ObjType_t{ + OT_GENERAL=0, // Also Unknown. Used for n/a type stats such as scenery + OT_AVATAR, + OT_ATTACHMENT, + OT_HUD, + OT_COUNT + }; + enum class StatType_t{ + RENDER_GEOMETRY=0, + RENDER_SHADOWS, + RENDER_HUDS, + RENDER_UI, + RENDER_COMBINED, + RENDER_SWAP, + RENDER_FRAME, + RENDER_DISPLAY, + RENDER_SLEEP, + RENDER_LFS, + RENDER_MESHREPO, + RENDER_FPSLIMIT, + RENDER_FPS, + RENDER_IDLE, + RENDER_DONE, // toggle buffer & clearbuffer (see processUpdate for hackery) + STATS_COUNT + }; + + struct StatsRecord + { + StatType_t statType; + ObjType_t objType; + LLUUID avID; + LLUUID objID; + uint64_t time; + bool isRigged; + bool isHUD; + }; + + class StatsRecorder{ + using Queue = moodycamel::BlockingConcurrentQueue; + public: + + static inline StatsRecorder& getInstance() + { + static StatsRecorder instance; + // volatile int dummy{}; + return instance; + } + static inline void setFocusAv(const LLUUID& avID){focusAv = avID;}; + static inline const LLUUID& getFocusAv(){return (focusAv);}; + static inline void send(StatsRecord&& u){StatsRecorder::getInstance().q.enqueue(u);}; + static void endFrame(){StatsRecorder::getInstance().q.enqueue(StatsRecord{StatType_t::RENDER_DONE, ObjType_t::OT_GENERAL, LLUUID::null, LLUUID::null, 0});}; + static void clearStats(){StatsRecorder::getInstance().q.enqueue(StatsRecord{StatType_t::RENDER_DONE, ObjType_t::OT_GENERAL, LLUUID::null, LLUUID::null, 1});}; + + static inline void setEnabled(bool on_or_off){collectionEnabled=on_or_off;}; + static inline void enable() { collectionEnabled=true; }; + static inline void disable() { collectionEnabled=false; }; + static inline bool enabled() { return(collectionEnabled); }; + + static inline int getReadBufferIndex() { return (writeBuffer ^ 1); }; + // static inline const StatsTypeMatrix& getCurrentStatsMatrix(){ return statsDoubleBuffer[getReadBufferIndex()];} + static inline uint64_t get(ObjType_t otype, LLUUID id, StatType_t type) + { + return statsDoubleBuffer[getReadBufferIndex()][static_cast(otype)][id][static_cast(type)]; + } + static inline uint64_t getSceneStat(StatType_t type) + { + return statsDoubleBuffer[getReadBufferIndex()][static_cast(ObjType_t::OT_GENERAL)][LLUUID::null][static_cast(type)]; + } + + static inline uint64_t getSum(ObjType_t otype, StatType_t type) + { + return sum[getReadBufferIndex()][static_cast(otype)][static_cast(type)]; + } + static inline uint64_t getMax(ObjType_t otype, StatType_t type) + { + return max[getReadBufferIndex()][static_cast(otype)][static_cast(type)]; + } + static void updateSettingsFromRenderCostLimit(); + static void updateRenderCostLimitFromSettings(); + static void updateAvatarParams(); + private: + StatsRecorder(); + + static int countNearbyAvatars(S32 distance); +// StatsArray is a uint64_t for each possible statistic type. + using StatsArray = std::array(FSPerfStats::StatType_t::STATS_COUNT)>; + using StatsMap = std::unordered_map; + using StatsTypeMatrix = std::array(FSPerfStats::ObjType_t::OT_COUNT)>; + using StatsSummaryArray = std::array(FSPerfStats::ObjType_t::OT_COUNT)>; + + static std::atomic writeBuffer; + static LLUUID focusAv; + static std::array statsDoubleBuffer; + static std::array max; + static std::array sum; + static bool collectionEnabled; + + + void processUpdate(const StatsRecord& upd) + { + FSZone; + // LL_INFOS("perfstats") << "processing update:" << LL_ENDL; + using ST = StatType_t; + // Note: nullptr is used as the key for global stats + #ifdef FS_HAS_TELEMETRY + static char avstr[36]; + static char obstr[36]; + #endif + + if(upd.statType == StatType_t::RENDER_DONE && upd.objType == ObjType_t::OT_GENERAL && upd.time == 0) + { + // LL_INFOS("perfstats") << "End of Frame Toggle Buffer:" << gFrameCount << LL_ENDL; + toggleBuffer(); + return; + } + if(upd.statType == StatType_t::RENDER_DONE && upd.objType == ObjType_t::OT_GENERAL && upd.time == 1) + { + // LL_INFOS("perfstats") << "New region - clear buffers:" << gFrameCount << LL_ENDL; + clearStatsBuffers(); + return; + } + + auto ot{upd.objType}; + auto& key{upd.objID}; + auto& avKey{upd.avID}; + auto type {upd.statType}; + auto val {upd.time}; + + FSZoneText(key.toStringFast(obstr),36); + FSZoneText(avKey.toStringFast(avstr),36); + FSZoneValue(val); + + if(ot == ObjType_t::OT_GENERAL) + { + // LL_INFOS("perfstats") << "General update:" << LL_ENDL; + doUpd(key, ot, type,val); + return; + } + + if(ot == ObjType_t::OT_AVATAR) + { + // LL_INFOS("perfstats") << "Avatar update:" << LL_ENDL; + doUpd(avKey, ot, type, val); + return; + } + + if(ot == ObjType_t::OT_ATTACHMENT) + { + if( !upd.isRigged && !upd.isHUD ) + { + // For all attachments that are not rigged we add them to the avatar (for all avatars) cost. + doUpd(avKey, ObjType_t::OT_AVATAR, type, val); + } + if( avKey == focusAv ) + { + // For attachments that are for the focusAv (self for now) we record them for the attachment/complexity view + if(upd.isHUD) + { + ot = ObjType_t::OT_HUD; + } + // LL_INFOS("perfstats") << "frame: " << gFrameCount << " Attachment update("<< (type==StatType_t::RENDER_GEOMETRY?"GEOMETRY":"SHADOW") << ": " << key.asString() << " = " << val << LL_ENDL; + doUpd(key, ot, type, val); + } + else + { + // LL_INFOS("perfstats") << "frame: " << gFrameCount << " non-self Att update("<< (type==StatType_t::RENDER_GEOMETRY?"GEOMETRY":"SHADOW") << ": " << key.asString() << " = " << val << " for av " << avKey.asString() << LL_ENDL; + } + } + + } + + static inline void doUpd(const LLUUID& key, ObjType_t ot, StatType_t type, uint64_t val) + { + FSZone; + using ST = StatType_t; + StatsMap& stm {statsDoubleBuffer[writeBuffer][static_cast(ot)]}; + auto& thisAsset = stm[key]; + + thisAsset[static_cast(type)] += val; + thisAsset[static_cast(ST::RENDER_COMBINED)] += val; + + sum[writeBuffer][static_cast(ot)][static_cast(type)] += val; + sum[writeBuffer][static_cast(ot)][static_cast(ST::RENDER_COMBINED)] += val; + + if(max[writeBuffer][static_cast(ot)][static_cast(type)] < thisAsset[static_cast(type)]) + { + max[writeBuffer][static_cast(ot)][static_cast(type)] = thisAsset[static_cast(type)]; + } + if(max[writeBuffer][static_cast(ot)][static_cast(ST::RENDER_COMBINED)] < thisAsset[static_cast(ST::RENDER_COMBINED)]) + { + max[writeBuffer][static_cast(ot)][static_cast(ST::RENDER_COMBINED)] = thisAsset[static_cast(ST::RENDER_COMBINED)]; + } + } + + static void toggleBuffer(); + static void clearStatsBuffers(); + + // thread entry + static void run() + { + StatsRecord upd[10]; + auto& instance {StatsRecorder::getInstance()}; + FSThreadName( "PerfStats" ); + + while( enabled() && !LLApp::isExiting() ) + { + FSZoneN("perf batch"); + auto count = instance.q.wait_dequeue_bulk_timed(upd, 10, std::chrono::milliseconds(10)); + if(count) + { + FSZoneValue(count); + // LL_INFOS("perfstats") << "processing " << count << " updates." << LL_ENDL; + for(auto i =0; i < count; i++) + { + instance.processUpdate(upd[i]); + } + } + } + } + + Queue q; + std::thread t; + + ~StatsRecorder() = default; + StatsRecorder(const StatsRecorder&) = delete; + StatsRecorder& operator=(const StatsRecorder&) = delete; + + }; + + template + class RecordTime + { + + private: + RecordTime(const RecordTime&) = delete; + RecordTime() = delete; + U64 start; + public: + StatsRecord stat; + + RecordTime( const LLUUID& av, const LLUUID& id, StatType_t type, bool isRiggedAtt=false, bool isHUDAtt=false): + start{LLTrace::BlockTimer::getCPUClockCount64()}, + stat{type, ObjTypeDiscriminator, std::move(av), std::move(id), 0, isRiggedAtt, isHUDAtt} + { + FSZoneC(tracy::Color::Orange); + #ifdef USAGE_TRACKING + if(stat.objType == FSPerfStats::ObjType_t::OT_ATTACHMENT) + { + if(!stat.isRigged && FSPerfStats::inUseAvatar){FSZoneText("OVERLAP AVATAR",14);} + + FSPlotSq("InUse", (int64_t)FSPerfStats::inUse, (int64_t)FSPerfStats::inUse+1); + FSPerfStats::inUse++; + FSPlotSq("InUseAttachment", (int64_t)FSPerfStats::inUseAttachment, (int64_t)FSPerfStats::inUseAttachment+1); + FSPerfStats::inUseAttachment++; + if (stat.isRigged) + { + FSPlotSq("InUseAttachmentRigged", (int64_t)FSPerfStats::inUseAttachmentRigged,(int64_t)FSPerfStats::inUseAttachmentRigged+1); + FSPerfStats::inUseAttachmentRigged++; + } + else + { + FSPlotSq("InUseAttachmentUnRigged", (int64_t)FSPerfStats::inUseAttachmentUnRigged,(int64_t)FSPerfStats::inUseAttachmentUnRigged+1); + FSPerfStats::inUseAttachmentUnRigged++; + } + } + #endif + + }; + + template < ObjType_t OD = ObjTypeDiscriminator, + std::enable_if_t * = nullptr> + RecordTime( StatType_t type ):RecordTime(LLUUID::null, LLUUID::null, type ) + { + FSZone; + #ifdef USAGE_TRACKING + FSPlotSq("InUseScene", (int64_t)FSPerfStats::inUseScene, (int64_t)FSPerfStats::inUseScene+1); + FSPerfStats::inUseScene++; + FSPlotSq("InUse", (int64_t)FSPerfStats::inUse, (int64_t)FSPerfStats::inUse+1); + FSPerfStats::inUse++; + #endif + }; + + template < ObjType_t OD = ObjTypeDiscriminator, + std::enable_if_t * = nullptr> + RecordTime( const LLUUID & av, StatType_t type ):RecordTime(std::move(av), LLUUID::null, type) + { + FSZoneC(tracy::Color::Purple); + + #ifdef USAGE_TRACKING + if(FSPerfStats::inUseAvatar){FSZoneText("OVERLAP AVATAR",14);} + + FSPlotSq("InUseAv", (int64_t)FSPerfStats::inUseAvatar, (int64_t)FSPerfStats::inUseAvatar+1); + FSPerfStats::inUseAvatar++; + FSPlotSq("InUse", (int64_t)FSPerfStats::inUse, (int64_t)FSPerfStats::inUse+1); + FSPerfStats::inUse++; + + #endif + + }; + + ~RecordTime() + { + if(!FSPerfStats::StatsRecorder::enabled()) + { + return; + } + + + FSZoneC(tracy::Color::Red); + + #ifdef USAGE_TRACKING + FSPlotSq("InUse", (int64_t)FSPerfStats::inUse,(int64_t)FSPerfStats::inUse-1); + --FSPerfStats::inUse; + if (stat.objType == FSPerfStats::ObjType_t::OT_ATTACHMENT) + { + FSPlotSq("InUseAttachment", (int64_t)FSPerfStats::inUseAttachment,(int64_t)FSPerfStats::inUseAttachment-1); + --FSPerfStats::inUseAttachment; + if (stat.isRigged) + { + FSPlotSq("InUseAttachmentRigged", (int64_t)FSPerfStats::inUseAttachmentRigged,(int64_t)FSPerfStats::inUseAttachmentRigged-1); + --FSPerfStats::inUseAttachmentRigged; + } + else + { + FSPlotSq("InUseAttachmentUnRigged", (int64_t)FSPerfStats::inUseAttachmentUnRigged,(int64_t)FSPerfStats::inUseAttachmentUnRigged-1); + --FSPerfStats::inUseAttachmentUnRigged; + } + } + if (stat.objType == FSPerfStats::ObjType_t::OT_GENERAL) + { + FSPlotSq("InUseScene", (int64_t)FSPerfStats::inUseScene,(int64_t)FSPerfStats::inUseScene-1); + --FSPerfStats::inUseScene; + } + if( stat.objType == FSPerfStats::ObjType_t::OT_AVATAR ) + { + FSPlotSq("InUseAv", (int64_t)FSPerfStats::inUseAvatar, (int64_t)FSPerfStats::inUseAvatar-1); + --FSPerfStats::inUseAvatar; + } + #endif + stat.time = LLTrace::BlockTimer::getCPUClockCount64() - start; + + #ifdef ATTACHMENT_TRACKING + static char obstr[36]; + static char avstr[36]; + FSZoneValue(static_cast(stat.objType)); + FSZoneText(stat.avID.toStringFast(avstr), 36); + FSZoneText(stat.objID.toStringFast(obstr), 36); + FSZoneValue(stat.time); + #endif + + StatsRecorder::send(std::move(stat)); + }; + + + }; + + + inline double raw_to_ns(U64 raw) { return (static_cast(raw) * 1000000000.0) / (F64)LLTrace::BlockTimer::countsPerSecond(); }; + inline double raw_to_us(U64 raw) { return (static_cast(raw) * 1000000.0) / (F64)LLTrace::BlockTimer::countsPerSecond(); }; + inline double raw_to_ms(U64 raw) { return (static_cast(raw) * 1000.0) / (F64)LLTrace::BlockTimer::countsPerSecond(); }; + + using RecordSceneTime = RecordTime; + using RecordAvatarTime = RecordTime; + using RecordAttachmentTime = RecordTime; + using RecordHudAttachmentTime = RecordTime; + +};// namespace FSPerfStats + +// helper functions +using RATptr = std::unique_ptr; + +template +static inline void trackAttachments(const T * vobj, bool isRigged, RATptr* ratPtrp) +{ + if( !vobj ){ ratPtrp->reset(); return;}; + + const T* rootAtt{vobj}; + if( rootAtt->isAttachment() ) + { + FSZone; + + while( !rootAtt->isRootEdit() ) + { + rootAtt = (T*)(rootAtt->getParent()); + } + + auto avPtr = (T*)(rootAtt->getParent()); + if(!avPtr){ratPtrp->reset(); return;} + + auto& av = avPtr->getID(); + auto& obj = rootAtt->getAttachmentItemID(); + if(!*ratPtrp || (*ratPtrp)->stat.objID != obj || (*ratPtrp)->stat.avID != av ) + { + #if TRACY_ENABLE && defined(ATTACHMENT_TRACKING) + FSZoneNC( "trackAttachments:new", tracy::Color::Red ); + auto& str = rootAtt->getAttachmentItemName(); + FSZoneText(str.c_str(), str.size()); + FSZoneText(isRigged?"Rigged ":"Unrigged",8); + static char avStr[36]; + av.toStringFast(avStr); + static char obStr[4]; + obj.toShortString(obStr); + FSZoneText( avStr, 36); + FSZoneText( obStr, 4); + #endif + if(*ratPtrp){ratPtrp->reset();}; // deliberately reset to ensure destruction before construction of replacement. + *ratPtrp = std::make_unique( av, + obj, + ( (LLPipeline::sShadowRender)?FSPerfStats::StatType_t::RENDER_SHADOWS : FSPerfStats::StatType_t::RENDER_GEOMETRY ), + isRigged, + rootAtt->isHUDAttachment()); + } + } + return; +}; + +#endif \ No newline at end of file diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 16fcb39121..7f7403ad84 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -286,6 +286,7 @@ #include "fsassetblacklist.h" #include "fstelemetry.h" // Tracy profiler support +#include "fsperfstats.h" // performance stats support #if LL_LINUX && LL_GTK #include "glib.h" @@ -1579,6 +1580,7 @@ void LLAppViewer::initMaxHeapSize() } static LLTrace::BlockTimerStatHandle FTM_MESSAGES("System Messages"); +static LLTrace::BlockTimerStatHandle FTM_MESSAGES2("System Messages2"); static LLTrace::BlockTimerStatHandle FTM_SLEEP("Sleep"); static LLTrace::BlockTimerStatHandle FTM_YIELD("Yield"); @@ -1640,6 +1642,10 @@ bool LLAppViewer::frame() bool LLAppViewer::doFrame() { +// Perfstats collection Frame boundary +{ + FSPerfStats::RecordSceneTime T (FSPerfStats::StatType_t::RENDER_FRAME); + LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop")); LLSD newFrame; // telemetry enabling. @@ -1674,6 +1680,7 @@ bool LLAppViewer::doFrame() nd::etw::logFrame(); // Write the start of each frame. Even if our Provider (Firestorm) would be enabled, this has only light impact. Does nothing on OSX and Linux. LL_RECORD_BLOCK_TIME(FTM_FRAME); + {FSPerfStats::RecordSceneTime T (FSPerfStats::StatType_t::RENDER_IDLE); // perf stats LLTrace::BlockTimer::processTimes(); LLTrace::get_frame_recording().nextPeriod(); LLTrace::BlockTimer::logStats(); @@ -1682,8 +1689,9 @@ bool LLAppViewer::doFrame() //clear call stack records LL_CLEAR_CALLSTACKS(); - + } // perf stats { + {FSPerfStats::RecordSceneTime T (FSPerfStats::StatType_t::RENDER_IDLE); // ensure we have the entire top scope of frame covered // MaxFPS Viewer-Chui merge error // Check if we need to restore rendering masks. if (restore_rendering_masks) @@ -1721,7 +1729,7 @@ bool LLAppViewer::doFrame() if (gViewerWindow) { - LL_RECORD_BLOCK_TIME(FTM_MESSAGES); + LL_RECORD_BLOCK_TIME(FTM_MESSAGES2); if (!restoreErrorTrap()) { LL_WARNS() << " Someone took over my signal/exception handler (post messagehandling)!" << LL_ENDL; @@ -1744,8 +1752,11 @@ bool LLAppViewer::doFrame() // canonical per-frame event mainloop.post(newFrame); // give listeners a chance to run + { + FSZoneN("Main:Coro"); llcoro::suspend(); - + } + }// ensure we have the entire top scope of frame covered if (!LLApp::isExiting()) { pingMainloopTimeout("Main:JoystickKeyboard"); @@ -1761,6 +1772,8 @@ bool LLAppViewer::doFrame() && (gHeadlessClient || !gViewerWindow->getShowProgress()) && !gFocusMgr.focusLocked()) { + FSPerfStats::RecordSceneTime T (FSPerfStats::StatType_t::RENDER_IDLE); + FSZoneN("Main:JoystickKeyboard"); joystick->scanJoystick(); gKeyboard->scanKeyboard(); gViewerInput.scanMouse(); @@ -1776,6 +1789,8 @@ bool LLAppViewer::doFrame() // Update state based on messages, user input, object idle. { + FSPerfStats::RecordSceneTime T (FSPerfStats::StatType_t::RENDER_IDLE); + pauseMainloopTimeout(); // *TODO: Remove. Messages shouldn't be stalling for 20+ seconds! LL_RECORD_BLOCK_TIME(FTM_IDLE); @@ -1786,6 +1801,7 @@ bool LLAppViewer::doFrame() if (gDoDisconnect && (LLStartUp::getStartupState() == STATE_STARTED)) { + FSZoneN("Shutdown:SaveSnapshot"); pauseMainloopTimeout(); saveFinalSnapshot(); disconnectViewer(); @@ -1796,6 +1812,7 @@ bool LLAppViewer::doFrame() // *TODO: Should we run display() even during gHeadlessClient? DK 2011-02-18 if (!LLApp::isExiting() && !gHeadlessClient && gViewerWindow) { + FSZoneN("Main:Display"); pingMainloopTimeout("Main:Display"); gGLActive = TRUE; @@ -1819,8 +1836,12 @@ bool LLAppViewer::doFrame() // pingMainloopTimeout("Main:Snapshot"); + { + FSPerfStats::RecordSceneTime T (FSPerfStats::StatType_t::RENDER_IDLE); + FSZoneN("Main:Snapshot"); LLFloaterSnapshot::update(); // take snapshots LLFloaterOutfitSnapshot::update(); + } gGLActive = FALSE; } } @@ -1854,6 +1875,7 @@ bool LLAppViewer::doFrame() // of equal priority on Windows if (milliseconds_to_sleep > 0) { + FSPerfStats::RecordSceneTime T ( FSPerfStats::StatType_t::RENDER_SLEEP ); ms_sleep(milliseconds_to_sleep); // also pause worker threads during this wait period LLAppViewer::getTextureCache()->pause(); @@ -1931,6 +1953,7 @@ bool LLAppViewer::doFrame() if (fsLimitFramerate && LLStartUp::getStartupState() == STATE_STARTED && !gTeleportDisplay && !logoutRequestSent() && max_fps > F_APPROXIMATELY_ZERO) { // Sleep a while to limit frame rate. + FSPerfStats::RecordSceneTime T ( FSPerfStats::StatType_t::RENDER_FPSLIMIT ); F32 min_frame_time = 1.f / (F32)max_fps; S32 milliseconds_to_sleep = llclamp((S32)((min_frame_time - frameTimer.getElapsedTimeF64()) * 1000.f), 0, 1000); if (milliseconds_to_sleep > 0) @@ -1970,7 +1993,8 @@ bool LLAppViewer::doFrame() } FSFrameMark; // Tracy support delineate Frame LLPROFILE_UPDATE(); - + } + FSPerfStats::StatsRecorder::endFrame(); return ! LLApp::isRunning(); } @@ -5819,6 +5843,7 @@ void LLAppViewer::idle() if (!(logoutRequestSent() && hasSavedFinalSnapshot())) { + FSPerfStats::tunedAvatars=0; // reset the number of avatars that have been tweaked. gObjectList.update(gAgent); } } diff --git a/indra/newview/llavatarrendernotifier.cpp b/indra/newview/llavatarrendernotifier.cpp index b608646abd..697f8f8459 100644 --- a/indra/newview/llavatarrendernotifier.cpp +++ b/indra/newview/llavatarrendernotifier.cpp @@ -244,6 +244,12 @@ void LLAvatarRenderNotifier::updateNotificationAgent(U32 agentComplexity) // save the value for use in following messages mLatestAgentComplexity = agentComplexity; + static LLCachedControl show_my_complexity_changes(gSavedSettings, "ShowMyComplexityChanges", 20); + if (!show_my_complexity_changes) + { + return; + } + if (!isAgentAvatarValid() || !gAgentWearables.areWearablesLoaded()) { // data not ready, nothing to show. @@ -291,7 +297,8 @@ static const char* e_hud_messages[] = }; LLHUDRenderNotifier::LLHUDRenderNotifier() : -mReportedHUDWarning(WARN_NONE) +mReportedHUDWarning(WARN_NONE), +mHUDsCount(0) { } @@ -307,6 +314,15 @@ void LLHUDRenderNotifier::updateNotificationHUD(hud_complexity_list_t complexity return; } + mHUDComplexityList = complexity; + mHUDsCount = mHUDComplexityList.size(); + + static LLCachedControl show_my_complexity_changes(gSavedSettings, "ShowMyComplexityChanges", 20); + if (!show_my_complexity_changes) + { + return; + } + // TODO: // Find a way to show message with list of issues, but without making it too large // and intrusive. diff --git a/indra/newview/llavatarrendernotifier.h b/indra/newview/llavatarrendernotifier.h index ec17b3d9e6..658d696458 100644 --- a/indra/newview/llavatarrendernotifier.h +++ b/indra/newview/llavatarrendernotifier.h @@ -30,6 +30,9 @@ #define LL_llavatarrendernotifier_H #include "llnotificationptr.h" +#include "llviewerobject.h" +#include "llhudobject.h" + class LLViewerRegion; @@ -63,6 +66,25 @@ struct LLHUDComplexity typedef std::list hud_complexity_list_t; +struct LLObjectComplexity +{ + LLObjectComplexity() + { + reset(); + } + void reset() + { + objectId = LLUUID::null; + objectName = ""; + objectCost = 0; + } + LLUUID objectId; + std::string objectName; + U32 objectCost; +}; + +typedef std::list object_complexity_list_t; + // Class to notify user about drastic changes in agent's render weights or if other agents // reported that user's agent is too 'heavy' for their settings class LLAvatarRenderNotifier : public LLSingleton @@ -77,6 +99,9 @@ public: void updateNotificationState(); void updateNotificationAgent(U32 agentComplexity); + void setObjectComplexityList(object_complexity_list_t object_list) { mObjectComplexityList = object_list; } + object_complexity_list_t getObjectComplexityList() { return mObjectComplexityList; } + private: LLNotificationPtr mNotificationPtr; @@ -109,6 +134,8 @@ private: // Used to detect changes in voavatar's rezzed status. // If value decreases - there were changes in outfit. S32 mLastOutfitRezStatus; + + object_complexity_list_t mObjectComplexityList; }; // Class to notify user about heavy set of HUD @@ -121,6 +148,9 @@ public: void updateNotificationHUD(hud_complexity_list_t complexity); bool isNotificationVisible(); + hud_complexity_list_t getHUDComplexityList() { return mHUDComplexityList; } + S32 getHUDsCount() { return mHUDsCount; } + private: enum EWarnLevel { @@ -141,6 +171,8 @@ private: EWarnLevel mReportedHUDWarning; LLHUDComplexity mLatestHUDComplexity; LLFrameTimer mHUDPopUpDelayTimer; + hud_complexity_list_t mHUDComplexityList; + S32 mHUDsCount; }; #endif /* ! defined(LL_llavatarrendernotifier_H) */ diff --git a/indra/newview/llcontrolavatar.cpp b/indra/newview/llcontrolavatar.cpp index 8cd0332be9..6cdd30e698 100644 --- a/indra/newview/llcontrolavatar.cpp +++ b/indra/newview/llcontrolavatar.cpp @@ -80,6 +80,7 @@ void LLControlAvatar::initInstance() const LLVOAvatar *LLControlAvatar::getAttachedAvatar() const { + FSZone; if (mRootVolp && mRootVolp->isAttachment()) { return mRootVolp->getAvatarAncestor(); @@ -89,6 +90,7 @@ const LLVOAvatar *LLControlAvatar::getAttachedAvatar() const LLVOAvatar *LLControlAvatar::getAttachedAvatar() { + FSZone; if (mRootVolp && mRootVolp->isAttachment()) { return mRootVolp->getAvatarAncestor(); diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp index d583a692f9..2ebd699b15 100644 --- a/indra/newview/lldrawpool.cpp +++ b/indra/newview/lldrawpool.cpp @@ -50,6 +50,7 @@ #include "lldrawpoolwlsky.h" #include "llglslshader.h" #include "llglcommonfunc.h" +#include "fsperfstats.h" // performance stats support S32 LLDrawPool::sNumDrawPools = 0; @@ -388,10 +389,21 @@ void LLRenderPass::renderGroup(LLSpatialGroup* group, U32 type, U32 mask, BOOL t { LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[type]; + std::unique_ptr ratPtr{}; // Perf stats for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) { LLDrawInfo *pparams = *k; if (pparams) { + // Capture render times + if(pparams->mFace) + { + LLViewerObject* vobj = pparams->mFace->getViewerObject(); + if(vobj->isAttachment()) + { + trackAttachments( vobj, pparams->mFace->isState(LLFace::RIGGED),&ratPtr); + } + } + // pushBatch(*pparams, mask, texture); } } @@ -404,11 +416,22 @@ void LLRenderPass::renderTexture(U32 type, U32 mask, BOOL batch_textures) void LLRenderPass::pushBatches(U32 type, U32 mask, BOOL texture, BOOL batch_textures) { + std::unique_ptr ratPtr{}; for (LLCullResult::drawinfo_iterator i = gPipeline.beginRenderMap(type); i != gPipeline.endRenderMap(type); ++i) { LLDrawInfo* pparams = *i; if (pparams) { + // Capture render times + if(pparams->mFace) + { + LLViewerObject* vobj = pparams->mFace->getViewerObject(); + if(vobj->isAttachment()) + { + trackAttachments( vobj, pparams->mFace->isState(LLFace::RIGGED),&ratPtr); + } + } + // pushBatch(*pparams, mask, texture, batch_textures); } } @@ -416,11 +439,22 @@ void LLRenderPass::pushBatches(U32 type, U32 mask, BOOL texture, BOOL batch_text void LLRenderPass::pushMaskBatches(U32 type, U32 mask, BOOL texture, BOOL batch_textures) { + std::unique_ptr ratPtr{}; for (LLCullResult::drawinfo_iterator i = gPipeline.beginRenderMap(type); i != gPipeline.endRenderMap(type); ++i) { LLDrawInfo* pparams = *i; if (pparams) { + // Capture render times + if((*pparams).mFace) + { + LLViewerObject* vobj = (*pparams).mFace->getViewerObject(); + if(vobj->isAttachment()) + { + trackAttachments( vobj, (*pparams).mFace->isState(LLFace::RIGGED),&ratPtr); + } + } + // if (LLGLSLShader::sCurBoundShaderPtr) { LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff); diff --git a/indra/newview/lldrawpoolalpha.cpp b/indra/newview/lldrawpoolalpha.cpp index 87bf2cb098..c6ec168cc2 100644 --- a/indra/newview/lldrawpoolalpha.cpp +++ b/indra/newview/lldrawpoolalpha.cpp @@ -48,6 +48,7 @@ #include "lldrawpoolwater.h" #include "llspatialpartition.h" #include "llglcommonfunc.h" +#include "fsperfstats.h" // performance stats support BOOL LLDrawPoolAlpha::sShowDebugAlpha = FALSE; @@ -341,6 +342,7 @@ void LLDrawPoolAlpha::render(S32 pass) void LLDrawPoolAlpha::renderAlphaHighlight(U32 mask) { + FSZone; for (LLCullResult::sg_iterator i = gPipeline.beginAlphaGroups(); i != gPipeline.endAlphaGroups(); ++i) { LLSpatialGroup* group = *i; @@ -349,9 +351,20 @@ void LLDrawPoolAlpha::renderAlphaHighlight(U32 mask) { LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[LLRenderPass::PASS_ALPHA]; + std::unique_ptr ratPtr{}; // Render time Stats collection for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) { LLDrawInfo& params = **k; + // Capture render times + if(params.mFace) + { + LLViewerObject* vobj = (LLViewerObject *)params.mFace->getViewerObject(); + if(vobj->isAttachment()) + { + trackAttachments( vobj, params.mFace->isState(LLFace::RIGGED), &ratPtr ); + } + } + // if (params.mParticle) { @@ -477,6 +490,7 @@ void LLDrawPoolAlpha::RestoreTexSetup(bool tex_setup) void LLDrawPoolAlpha::renderSimples(U32 mask, std::vector& simples) { + FSZone; gPipeline.enableLightsDynamic(); simple_shader->bind(); simple_shader->bindTexture(LLShaderMgr::BUMP_MAP, LLViewerFetchedTexture::sFlatNormalImagep); @@ -485,8 +499,17 @@ void LLDrawPoolAlpha::renderSimples(U32 mask, std::vector& simples) simple_shader->uniform1f(LLShaderMgr::ENVIRONMENT_INTENSITY, 0.0f); simple_shader->uniform1f(LLShaderMgr::EMISSIVE_BRIGHTNESS, 0.0f); bool use_shaders = gPipeline.canUseVertexShaders(); + std::unique_ptr ratPtr{};// Render time Stats collection for (LLDrawInfo* draw : simples) { + // Capture render times + FSZoneN("Simples"); + auto vobj = draw->mFace?draw->mFace->getViewerObject():nullptr; + if(vobj && vobj->isAttachment()) + { + trackAttachments( vobj, draw->mFace->isState(LLFace::RIGGED), &ratPtr ); + } + // bool tex_setup = TexSetup(draw, use_shaders, false, simple_shader); LLGLEnableFunc stencil_test(GL_STENCIL_TEST, draw->mSelected, &LLGLCommonFunc::selected_stencil_test); gGL.blendFunc((LLRender::eBlendFactor) draw->mBlendFuncSrc, (LLRender::eBlendFactor) draw->mBlendFuncDst, mAlphaSFactor, mAlphaDFactor); @@ -503,8 +526,17 @@ void LLDrawPoolAlpha::renderFullbrights(U32 mask, std::vector& full fullbright_shader->bind(); fullbright_shader->uniform1f(LLShaderMgr::EMISSIVE_BRIGHTNESS, 1.0f); bool use_shaders = gPipeline.canUseVertexShaders(); + std::unique_ptr ratPtr{}; // Render time Stats collection for (LLDrawInfo* draw : fullbrights) { + // Capture render times + FSZoneN("Fullbrights"); + auto vobj = draw->mFace?draw->mFace->getViewerObject():nullptr; + if(vobj && vobj->isAttachment()) + { + trackAttachments( vobj, draw->mFace->isState(LLFace::RIGGED), &ratPtr ); + } + // bool tex_setup = TexSetup(draw, use_shaders, false, fullbright_shader); LLGLEnableFunc stencil_test(GL_STENCIL_TEST, draw->mSelected, &LLGLCommonFunc::selected_stencil_test); @@ -518,13 +550,24 @@ void LLDrawPoolAlpha::renderFullbrights(U32 mask, std::vector& full void LLDrawPoolAlpha::renderMaterials(U32 mask, std::vector& materials) { + FSZone;// Tracy markup LLGLSLShader::bindNoShader(); current_shader = NULL; gPipeline.enableLightsDynamic(); bool use_shaders = gPipeline.canUseVertexShaders(); + std::unique_ptr ratPtr{}; // Render time Stats collection for (LLDrawInfo* draw : materials) { + // Capture render times + FSZoneN("Materials"); + auto vobj = draw->mFace?draw->mFace->getViewerObject():nullptr; + if(vobj && vobj->isAttachment()) + { + trackAttachments( vobj, draw->mFace->isState(LLFace::RIGGED), &ratPtr ); + } + // + U32 mask = draw->mShaderMask; llassert(mask < LLMaterial::SHADER_COUNT); @@ -605,8 +648,18 @@ void LLDrawPoolAlpha::renderEmissives(U32 mask, std::vector& emissi // don't touch color, add to alpha (glow) gGL.blendFunc(LLRender::BF_ZERO, LLRender::BF_ONE, LLRender::BF_ONE, LLRender::BF_ONE); bool use_shaders = gPipeline.canUseVertexShaders(); + std::unique_ptr ratPtr{}; // Render time Stats collection for (LLDrawInfo* draw : emissives) { + // Capture render times + FSZoneN("Emissives"); + auto vobj = draw->mFace?draw->mFace->getViewerObject():nullptr; + if(vobj && vobj->isAttachment()) + { + trackAttachments( vobj, draw->mFace->isState(LLFace::RIGGED), &ratPtr ); + } + // + bool tex_setup = TexSetup(draw, use_shaders, false, emissive_shader); drawEmissive(mask, draw); RestoreTexSetup(tex_setup); @@ -664,6 +717,7 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, S32 pass) LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[LLRenderPass::PASS_ALPHA]; + std::unique_ptr ratPtr{}; // Render time Stats collection for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) { LLDrawInfo& params = **k; @@ -679,6 +733,18 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, S32 pass) continue; } + // Capture render times + if(params.mFace) + { + LLViewerObject* vobj = (LLViewerObject *)params.mFace->getViewerObject(); + + if(vobj->isAttachment()) + { + trackAttachments( vobj, params.mFace->isState(LLFace::RIGGED), &ratPtr ); + } + } + // + // Fix for bug - NORSPEC-271 // If the face is more than 90% transparent, then don't update the Depth buffer for Dof // We don't want the nearly invisible objects to cause of DoF effects @@ -858,7 +924,9 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, S32 pass) gGL.matrixMode(LLRender::MM_MODELVIEW); } } - + // performance stats + ratPtr.reset(); // force the final batch to terminate to avoid double counting on the subsidiary batches for FB and Emmissives + // if (batch_fullbrights) { light_enabled = false; diff --git a/indra/newview/lldrawpoolavatar.cpp b/indra/newview/lldrawpoolavatar.cpp index 80d45d6fb3..06da6e4504 100644 --- a/indra/newview/lldrawpoolavatar.cpp +++ b/indra/newview/lldrawpoolavatar.cpp @@ -59,6 +59,8 @@ // void drawBoxOutline(const LLVector3& pos,const LLVector3& size); // llspatialpartition.cpp // #include "llnetmap.h" +#include "fsperfstats.h" // performance stats support + static U32 sDataMask = LLDrawPoolAvatar::VERTEX_DATA_MASK; static U32 sBufferUsage = GL_STREAM_DRAW_ARB; @@ -579,13 +581,16 @@ void LLDrawPoolAvatar::renderShadow(S32 pass) { return; } + FSPerfStats::RecordAvatarTime T(avatarp->getID(), FSPerfStats::StatType_t::RENDER_SHADOWS); + LLVOAvatar::AvatarOverallAppearance oa = avatarp->getOverallAppearance(); BOOL impostor = !LLPipeline::sImpostorRender && avatarp->isImpostor(); // plain old impostors are passing through the shadow pipeline // if (oa == LLVOAvatar::AOA_INVISIBLE || // (impostor && oa == LLVOAvatar::AOA_JELLYDOLL)) // Note: Impostors should not cast shadows, also all JDs are impostor nowadays so we do not need the extra check at all. - if (impostor || (oa == LLVOAvatar::AOA_INVISIBLE) ) + // also no shadows if the shadows are causing this avatar to breach the limit. + if ( avatarp->isTooSlowWithShadows() || impostor || (oa == LLVOAvatar::AOA_INVISIBLE) ) // { // No shadows for jellydolled or invisible avs. @@ -1462,6 +1467,7 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) if (pass == -1) { + FSZoneN("pass -1"); for (S32 i = 1; i < getNumPasses(); i++) { //skip foot shadows prerender(); @@ -1478,7 +1484,7 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) return; } - LLVOAvatar *avatarp = NULL; + LLVOAvatar *avatarp { nullptr }; if (single_avatar) { @@ -1486,6 +1492,7 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) } else { + FSZoneN("Find avatarp"); // Tracy markup const LLFace *facep = mDrawFace[0]; if (!facep->getDrawable()) { @@ -1498,11 +1505,16 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) { return; } + FSPerfStats::RecordAvatarTime T(avatarp->getID(), FSPerfStats::StatType_t::RENDER_GEOMETRY); // Add avatar hitbox debug + { + FSZoneN("cached control renderhitboxes"); static LLCachedControl render_hitbox(gSavedSettings, "DebugRenderHitboxes", false); + if (render_hitbox && pass == 2) { + FSZoneN("render_hitbox"); LLGLSLShader* current_shader_program = NULL; // load the debug output shader @@ -1581,10 +1593,14 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) } } } - // - + }// +// rendertime Tracy annotations +{ + FSZoneN("check fully_loaded"); +// if (!single_avatar && !avatarp->isFullyLoaded() ) { + FSZoneN("avatar not loaded"); if (pass==0 && (!gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_PARTICLES) || LLViewerPartSim::getMaxPartCount() <= 0)) { // debug code to draw a sphere in place of avatar @@ -1607,9 +1623,14 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) // don't render please return; } +}// rendertime Tracy annotations BOOL impostor = !LLPipeline::sImpostorRender && avatarp->isImpostor() && !single_avatar; +// rendertime Tracy annotations +{ + FSZoneN("check appearance"); +// if (( /*avatarp->isInMuteList() // Partially undo MAINT-5700: Draw imposter for muted avatars ||*/ impostor || (LLVOAvatar::AOA_NORMAL != avatarp->getOverallAppearance() && !avatarp->needsImpostorUpdate()) ) && pass != 0) @@ -1617,6 +1638,7 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) { //don't draw anything but the impostor for impostored avatars return; } +}// rendertime Tracy annotations if (pass == 0 && !impostor && LLPipeline::sUnderWaterRender) { //don't draw foot shadows under water @@ -1632,6 +1654,7 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) if (pass == 0) { + FSZoneN("pass 0"); if (!LLPipeline::sReflectionRender) { LLVOAvatar::sNumVisibleAvatars++; @@ -1640,6 +1663,7 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) // if (impostor || (LLVOAvatar::AV_DO_NOT_RENDER == avatarp->getVisualMuteSettings() && !avatarp->needsImpostorUpdate())) if (impostor || (LLVOAvatar::AOA_NORMAL != avatarp->getOverallAppearance() && !avatarp->needsImpostorUpdate())) { + FSZoneN("render impostor"); if (LLPipeline::sRenderDeferred && !LLPipeline::sReflectionRender && avatarp->mImpostor.isComplete()) { // FIRE-9179: Crash fix @@ -1665,6 +1689,7 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) if (pass == 1) { + FSZoneN("render rigid meshes (eyeballs)"); // render rigid meshes (eyeballs) first avatarp->renderRigid(); return; @@ -1672,12 +1697,15 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) if (pass == 3) { + FSZoneN("pass 3"); if (is_deferred_render) { + FSZoneN("deferred rigged simple"); renderDeferredRiggedSimple(avatarp); } else { + FSZoneN("non-deferred rigged"); renderRiggedSimple(avatarp); if (LLPipeline::sRenderDeferred) @@ -1701,12 +1729,15 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) if (pass == 4) { + FSZoneN("pass 4"); if (is_deferred_render) { + FSZoneN("deferred rigged bump"); renderDeferredRiggedBump(avatarp); } else { + FSZoneN("non-deferred fullbright"); renderRiggedFullbright(avatarp); } @@ -1715,6 +1746,7 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) if (is_deferred_render && pass >= 5 && pass <= 21) { + FSZoneN("deferred passes 5-21"); S32 p = pass-5; if (p != 1 && @@ -1722,6 +1754,7 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) p != 9 && p != 13) { + FSZoneN("deferred rigged material"); renderDeferredRiggedMaterial(avatarp, p); } return; @@ -1732,6 +1765,7 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) if (pass == 5) { + FSZoneN("rigged shiny"); renderRiggedShinySimple(avatarp); return; @@ -1739,6 +1773,7 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) if (pass == 6) { + FSZoneN("rigged FB shiny"); renderRiggedFullbrightShiny(avatarp); return; } @@ -1747,10 +1782,12 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) { if (pass == 7) { + FSZoneN("pass 7 rigged Alpha"); renderRiggedAlpha(avatarp); if (LLPipeline::sRenderDeferred && !is_post_deferred_render) { //render transparent materials under water + FSZoneN("rigged Alpha Blend"); LLGLEnable blend(GL_BLEND); gGL.setColorMask(true, true); @@ -1771,6 +1808,7 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) if (pass == 8) { + FSZoneN("pass 8 rigged FB Alpha"); renderRiggedFullbrightAlpha(avatarp); return; } @@ -1787,6 +1825,7 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) } { + FSZoneN("post deferred rigged Alpha"); LLGLEnable blend(GL_BLEND); renderDeferredRiggedMaterial(avatarp, p); } @@ -1794,6 +1833,7 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) } else if (pass == 9) { + FSZoneN("pass 9 - rigged glow"); renderRiggedGlow(avatarp); return; } @@ -1801,6 +1841,7 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) if (pass == 13) { + FSZoneN("pass 13 - rigged glow"); renderRiggedGlow(avatarp); return; @@ -1808,6 +1849,7 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) if ((sShaderLevel >= SHADER_LEVEL_CLOTH)) { + FSZoneN("shader level > CLOTH"); LLMatrix4 rot_mat; LLViewerCamera::getInstance()->getMatrixToLocal(rot_mat); LLMatrix4 cfr(OGL_TO_CFR_ROTATION); @@ -1833,6 +1875,7 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) if( !single_avatar || (avatarp == single_avatar) ) { + FSZoneN("renderSkinned"); avatarp->renderSkinned(); } } @@ -2228,6 +2271,7 @@ void LLDrawPoolAvatar::renderRigged(LLVOAvatar* avatar, U32 type, bool glow) stop_glerror(); + std::unique_ptr ratPtr{};// Perf stats capture for (U32 i = 0; i < mRiggedFace[type].size(); ++i) { LLFace* face = mRiggedFace[type][i]; @@ -2250,7 +2294,14 @@ void LLDrawPoolAvatar::renderRigged(LLVOAvatar* avatar, U32 type, bool glow) { continue; } - + + // Capture render times + if(vobj->isAttachment()) + { + trackAttachments( vobj, true, &ratPtr); + } + // + LLVolume* volume = vobj->getVolume(); S32 te = face->getTEOffset(); @@ -2545,12 +2596,17 @@ static LLTrace::BlockTimerStatHandle FTM_RIGGED_VBO("Rigged VBO"); void LLDrawPoolAvatar::updateRiggedVertexBuffers(LLVOAvatar* avatar) { LL_RECORD_BLOCK_TIME(FTM_RIGGED_VBO); - + // render stats collection + if(!avatar)return; // in theory this never happens...right + FSPerfStats::RecordAvatarTime T( avatar->getID(), ( (LLPipeline::sShadowRender)?FSPerfStats::StatType_t::RENDER_SHADOWS : FSPerfStats::StatType_t::RENDER_GEOMETRY ) ); + // //update rigged vertex buffers for (U32 type = 0; type < NUM_RIGGED_PASSES; ++type) { + std::unique_ptr ratPtr{}; for (U32 i = 0; i < mRiggedFace[type].size(); ++i) { + FSZoneN("updateRiggedVBO"); LLFace* face = mRiggedFace[type][i]; LLDrawable* drawable = face->getDrawable(); if (!drawable) @@ -2564,7 +2620,12 @@ void LLDrawPoolAvatar::updateRiggedVertexBuffers(LLVOAvatar* avatar) { continue; } - + // Capture render times + if(vobj->isAttachment()) + { + trackAttachments( vobj, true, &ratPtr ); + } + // LLVolume* volume = vobj->getVolume(); S32 te = face->getTEOffset(); diff --git a/indra/newview/lldrawpoolbump.cpp b/indra/newview/lldrawpoolbump.cpp index c7cb6567b5..d49c0d95d3 100644 --- a/indra/newview/lldrawpoolbump.cpp +++ b/indra/newview/lldrawpoolbump.cpp @@ -47,6 +47,7 @@ #include "pipeline.h" #include "llspatialpartition.h" #include "llviewershadermgr.h" +#include "fsperfstats.h" // performance stats support //#include "llimagebmp.h" //#include "../tools/imdebug/imdebug.h" @@ -640,13 +641,22 @@ void LLDrawPoolBump::endFullbrightShiny() } void LLDrawPoolBump::renderGroup(LLSpatialGroup* group, U32 type, U32 mask, BOOL texture = TRUE) -{ +{ + FSZone; LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[type]; + std::unique_ptr ratPtr{}; // render time capture for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) { LLDrawInfo& params = **k; + // Capture render times + LLViewerObject* vobj = (LLViewerObject *)params.mFace->getViewerObject(); + if( vobj && vobj->isAttachment() ) + { + trackAttachments( vobj, params.mFace->isState(LLFace::RIGGED), &ratPtr ); + } + // applyModelMatrix(params); if (params.mGroup) @@ -888,10 +898,21 @@ void LLDrawPoolBump::renderDeferred(S32 pass) U32 mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_COLOR; + std::unique_ptr ratPtr{}; // render time capture for (LLCullResult::drawinfo_iterator i = begin; i != end; ++i) { LLDrawInfo& params = **i; - + // Capture render times + if(params.mFace) + { + LLViewerObject* vobj = (LLViewerObject *)params.mFace->getViewerObject(); + + if(vobj && vobj->isAttachment()) + { + trackAttachments( vobj, params.mFace->isState(LLFace::RIGGED), &ratPtr ); + } + } + // gDeferredBumpProgram.setMinimumAlpha(params.mAlphaMaskCutoff); LLDrawPoolBump::bindBumpMap(params, bump_channel); pushBatch(params, mask, TRUE); @@ -1499,9 +1520,21 @@ void LLDrawPoolBump::renderBump(U32 type, U32 mask) LLCullResult::drawinfo_iterator begin = gPipeline.beginRenderMap(type); LLCullResult::drawinfo_iterator end = gPipeline.endRenderMap(type); + std::unique_ptr ratPtr{}; // render time capture for (LLCullResult::drawinfo_iterator i = begin; i != end; ++i) { LLDrawInfo& params = **i; + // Capture render times + if(params.mFace) + { + LLViewerObject* vobj = (LLViewerObject *)params.mFace->getViewerObject(); + + if( vobj && vobj->isAttachment() ) + { + trackAttachments( vobj, params.mFace->isState(LLFace::RIGGED), &ratPtr ); + } + } + // if (LLDrawPoolBump::bindBumpMap(params)) { @@ -1512,6 +1545,7 @@ void LLDrawPoolBump::renderBump(U32 type, U32 mask) void LLDrawPoolBump::pushBatch(LLDrawInfo& params, U32 mask, BOOL texture, BOOL batch_textures) { + FSZone; applyModelMatrix(params); bool tex_setup = false; diff --git a/indra/newview/lldrawpoolmaterials.cpp b/indra/newview/lldrawpoolmaterials.cpp index 05b0c1f1a9..34dbc350b9 100644 --- a/indra/newview/lldrawpoolmaterials.cpp +++ b/indra/newview/lldrawpoolmaterials.cpp @@ -31,6 +31,7 @@ #include "llviewershadermgr.h" #include "pipeline.h" #include "llglcommonfunc.h" +#include "fsperfstats.h" // performance stats support S32 diffuse_channel = -1; @@ -135,10 +136,23 @@ void LLDrawPoolMaterials::renderDeferred(S32 pass) LLCullResult::drawinfo_iterator begin = gPipeline.beginRenderMap(type); LLCullResult::drawinfo_iterator end = gPipeline.endRenderMap(type); + std::unique_ptr ratPtr{}; // render time capture for (LLCullResult::drawinfo_iterator i = begin; i != end; ++i) { LLDrawInfo& params = **i; - + + // Capture render times + if(params.mFace) + { + LLViewerObject* vobj = (LLViewerObject *)params.mFace->getViewerObject(); + + if( vobj && vobj->isAttachment() ) + { + trackAttachments( vobj, params.mFace->isState(LLFace::RIGGED), &ratPtr ); + } + } + // + mShader->uniform4f(LLShaderMgr::SPECULAR_COLOR, params.mSpecColor.mV[0], params.mSpecColor.mV[1], params.mSpecColor.mV[2], params.mSpecColor.mV[3]); mShader->uniform1f(LLShaderMgr::ENVIRONMENT_INTENSITY, params.mEnvIntensity); diff --git a/indra/newview/lldrawpoolsimple.cpp b/indra/newview/lldrawpoolsimple.cpp index f211cf6e27..05ac2fad6a 100644 --- a/indra/newview/lldrawpoolsimple.cpp +++ b/indra/newview/lldrawpoolsimple.cpp @@ -36,6 +36,7 @@ #include "llspatialpartition.h" #include "llviewershadermgr.h" #include "llrender.h" +#include "fsperfstats.h" static LLGLSLShader* simple_shader = NULL; static LLGLSLShader* fullbright_shader = NULL; @@ -224,7 +225,7 @@ void LLDrawPoolSimple::render(S32 pass) LLGLDisable blend(GL_BLEND); { //render simple - LL_RECORD_BLOCK_TIME(FTM_RENDER_SIMPLE); + LL_RECORD_BLOCK_TIME(FTM_RENDER_SIMPLE); gPipeline.enableLightsDynamic(); if (mShaderLevel > 0) diff --git a/indra/newview/lldynamictexture.cpp b/indra/newview/lldynamictexture.cpp index 86c598f70a..8761524e89 100644 --- a/indra/newview/lldynamictexture.cpp +++ b/indra/newview/lldynamictexture.cpp @@ -228,12 +228,15 @@ BOOL LLViewerDynamicTexture::updateAllInstances() BOOL ret = FALSE ; for( S32 order = 0; order < ORDER_COUNT; order++ ) { + FSZone; for (instance_list_t::iterator iter = LLViewerDynamicTexture::sInstances[order].begin(); iter != LLViewerDynamicTexture::sInstances[order].end(); ++iter) { + FSZone; LLViewerDynamicTexture *dynamicTexture = *iter; if (dynamicTexture->needsRender()) - { + { + FSZoneN("needsRender"); glClear(GL_DEPTH_BUFFER_BIT); gDepthDirty = TRUE; @@ -241,13 +244,19 @@ BOOL LLViewerDynamicTexture::updateAllInstances() dynamicTexture->setBoundTarget(use_fbo ? &gPipeline.mBake : nullptr); dynamicTexture->preRender(); // Must be called outside of startRender() result = FALSE; + { + FSZoneN("DynTexture->render"); if (dynamicTexture->render()) { ret = TRUE ; result = TRUE; sNumRenders++; } + } + { + FSZoneN("flush"); gGL.flush(); + } LLVertexBuffer::unbind(); dynamicTexture->setBoundTarget(nullptr); dynamicTexture->postRender(result); diff --git a/indra/newview/llface.cpp b/indra/newview/llface.cpp index 316951c316..c28fbfc369 100644 --- a/indra/newview/llface.cpp +++ b/indra/newview/llface.cpp @@ -59,6 +59,7 @@ // [RLVa:KB] - Checked: RLVa-2.0.0 #include "rlvhandler.h" // [/RLVa:KB] +#include "fsperfstats.h" // performance stats support #if LL_LINUX // Work-around spurious used before init warning on Vector4a @@ -642,6 +643,13 @@ void renderFace(LLDrawable* drawable, LLFace *face) LLVOVolume* vobj = drawable->getVOVolume(); if (vobj) { + // Placeholder - This function emits drawcalls but is only used in one place and not useful for stats. + // TODO(Beq) if we need this consider moving it to llSelectMgr loop instead to reduce overhead. + // std::unique_ptr ratPtr{}; + // if(vobj->isAttachment()) + // { + // trackAttachments(vobj, LLPipeline::sShadowRender, &ratPtr); + // } LLVolume* volume = NULL; if (drawable->isState(LLDrawable::RIGGED)) @@ -1240,6 +1248,11 @@ bool LLFace::canRenderAsMask() { return false; } + + // shortcircuit fully alpha faces + if(getViewerObject()->isHUDAttachment()){return false;}; + if(te->getAlpha() == 0.0f && (te->getGlow() == 0.f)){FSZoneN("beqshortcircuit invisible");return true;} + // LLMaterial* mat = te->getMaterialParams(); if (mat && mat->getDiffuseAlphaMode() == LLMaterial::DIFFUSE_ALPHA_MODE_BLEND) diff --git a/indra/newview/llfloaterautoreplacesettings.cpp b/indra/newview/llfloaterautoreplacesettings.cpp index ec05ba924c..d455b685fd 100644 --- a/indra/newview/llfloaterautoreplacesettings.cpp +++ b/indra/newview/llfloaterautoreplacesettings.cpp @@ -64,6 +64,7 @@ #include "llnotificationhandler.h" #include "llnotificationmanager.h" #include "llnotificationsutil.h" +#include "llviewercontrol.h" LLFloaterAutoReplaceSettings::LLFloaterAutoReplaceSettings(const LLSD& key) diff --git a/indra/newview/llfloateravatarrendersettings.cpp b/indra/newview/llfloateravatarrendersettings.cpp index 15870faa62..1864612487 100644 --- a/indra/newview/llfloateravatarrendersettings.cpp +++ b/indra/newview/llfloateravatarrendersettings.cpp @@ -91,7 +91,6 @@ BOOL LLFloaterAvatarRenderSettings::postBuild() LLFloater::postBuild(); mAvatarSettingsList = getChild("render_settings_list"); mAvatarSettingsList->setRightMouseDownCallback(boost::bind(&LLFloaterAvatarRenderSettings::onAvatarListRightClick, this, _1, _2, _3)); - getChild("people_filter_input")->setCommitCallback(boost::bind(&LLFloaterAvatarRenderSettings::onFilterEdit, this, _2)); return TRUE; } @@ -135,37 +134,13 @@ void LLFloaterAvatarRenderSettings::updateList() { item_params.value = iter->first; LLAvatarNameCache::get(iter->first, &av_name); - if(!isHiddenRow(av_name.getCompleteName())) - { - item_params.columns.add().value(av_name.getCompleteName()).column("name"); - std::string setting = getString(iter->second == 1 ? "av_never_render" : "av_always_render"); - item_params.columns.add().value(setting).column("setting"); - std::string timestamp = createTimestamp(LLRenderMuteList::getInstance()->getVisualMuteDate(iter->first)); - item_params.columns.add().value(timestamp).column("timestamp"); - mAvatarSettingsList->addNameItemRow(item_params); - } + item_params.columns.add().value(av_name.getCompleteName()).column("name"); + std::string setting = getString(iter->second == 1 ? "av_never_render" : "av_always_render"); + item_params.columns.add().value(setting).column("setting"); + mAvatarSettingsList->addNameItemRow(item_params); } } -void LLFloaterAvatarRenderSettings::onFilterEdit(const std::string& search_string) -{ - std::string filter_upper = search_string; - LLStringUtil::toUpper(filter_upper); - if (mNameFilter != filter_upper) - { - mNameFilter = filter_upper; - mNeedsUpdate = true; - } -} - -bool LLFloaterAvatarRenderSettings::isHiddenRow(const std::string& av_name) -{ - if (mNameFilter.empty()) return false; - std::string upper_name = av_name; - LLStringUtil::toUpper(upper_name); - return std::string::npos == upper_name.find(mNameFilter); -} - static LLVOAvatar* find_avatar(const LLUUID& id) { LLViewerObject *obj = gObjectList.findObject(id); @@ -216,6 +191,10 @@ bool LLFloaterAvatarRenderSettings::isActionChecked(const LLSD& userdata, const { return (visual_setting == S32(LLVOAvatar::AV_RENDER_NORMALLY)); } + else if ("non_default" == command_name) + { + return (visual_setting != S32(LLVOAvatar::AV_RENDER_NORMALLY)); + } else if ("never" == command_name) { return (visual_setting == S32(LLVOAvatar::AV_DO_NOT_RENDER)); diff --git a/indra/newview/llfloateravatarrendersettings.h b/indra/newview/llfloateravatarrendersettings.h index af36b67bd0..d03f035b4d 100644 --- a/indra/newview/llfloateravatarrendersettings.h +++ b/indra/newview/llfloateravatarrendersettings.h @@ -50,7 +50,6 @@ public: void onAvatarListRightClick(LLUICtrl* ctrl, S32 x, S32 y); void updateList(); - void onFilterEdit(const std::string& search_string); void onCustomAction (const LLSD& userdata, const LLUUID& av_id); bool isActionChecked(const LLSD& userdata, const LLUUID& av_id); void onClickAdd(const LLSD& userdata); @@ -61,15 +60,12 @@ public: static void setNeedsUpdate(); private: - bool isHiddenRow(const std::string& av_name); void callbackAvatarPicked(const uuid_vec_t& ids, S32 visual_setting); void removePicker(); bool mNeedsUpdate; LLListContextMenu* mContextMenu; LLNameListCtrl* mAvatarSettingsList; - - std::string mNameFilter; }; diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index 143203d24d..a6c8bf2405 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -53,6 +53,7 @@ #include "llfloaterreg.h" #include "llfloaterabout.h" #include "llfavoritesbar.h" +#include "llfloaterpreferencesgraphicsadvanced.h" #include "llfloatersidepanelcontainer.h" // [FS communication UI] //#include "llfloaterimsession.h" @@ -84,7 +85,6 @@ #include "llviewereventrecorder.h" #include "llviewermessage.h" #include "llviewerwindow.h" -#include "llviewershadermgr.h" #include "llviewerthrottle.h" #include "llvoavatarself.h" #include "llvotree.h" @@ -108,11 +108,9 @@ #include "lltextbox.h" #include "llui.h" #include "llviewerobjectlist.h" -#include "llvoavatar.h" #include "llvovolume.h" #include "llwindow.h" #include "llworld.h" -#include "pipeline.h" #include "lluictrlfactory.h" #include "llviewermedia.h" #include "llpluginclassmedia.h" @@ -128,8 +126,6 @@ #include "llpresetsmanager.h" #include "llviewercontrol.h" #include "llpresetsmanager.h" -#include "llfeaturemanager.h" -#include "llviewertexturelist.h" #include "llsearchableui.h" @@ -143,18 +139,19 @@ #include "llaudioengine.h" // Output device selection #include "llavatarname.h" // Deeper name cache stuffs #include "llclipboard.h" // Support preferences search SLURLs -#include "lleventtimer.h" -#include "llviewermenufile.h" // FIRE-23606 Reveal path to external script editor in prefernces #include "lldiriterator.h" // for populating the fonts combo +#include "lleventtimer.h" #include "llline.h" #include "lllocationhistory.h" #include "llpanelblockedlist.h" #include "llpanelmaininventory.h" #include "llscrolllistctrl.h" -#include "llspellcheck.h" #include "llsdserialize.h" // KB: SkinsSelector +#include "llspellcheck.h" #include "lltoolbarview.h" +#include "llviewermenufile.h" // FIRE-23606 Reveal path to external script editor in prefernces #include "llviewernetwork.h" // +#include "llviewershadermgr.h" #include "NACLantispam.h" #include "../llcrashlogger/llcrashlogger.h" #if LL_WINDOWS @@ -168,9 +165,11 @@ #endif // +#include "fsperfstats.h"// perfstats + // FIRE-19539 - Include the alert messages in Prefs>Notifications>Alerts in preference Search. #include "llfiltereditor.h" - +#include "llviewershadermgr.h" // FIRE-6340, FIRE-6567 - Setting Bandwidth issues //const F32 BANDWIDTH_UPDATER_TIMEOUT = 0.5f; char const* const VISIBILITY_DEFAULT = "default"; @@ -508,6 +507,8 @@ LLFloaterPreference::LLFloaterPreference(const LLSD& key) LLAvatarPropertiesProcessor::getInstance()->addObserver( gAgent.getID(), this ); + mComplexityChangedSignal = gSavedSettings.getControl("RenderAvatarMaxComplexity")->getCommitSignal()->connect(boost::bind(&LLFloaterPreference::updateComplexityText, this)); + mCommitCallbackRegistrar.add("Pref.ClearLog", boost::bind(&LLConversationLog::onClearLog, &LLConversationLog::instance())); mCommitCallbackRegistrar.add("Pref.DeleteTranscripts", boost::bind(&LLFloaterPreference::onDeleteTranscripts, this)); mCommitCallbackRegistrar.add("UpdateFilter", boost::bind(&LLFloaterPreference::onUpdateFilterTerm, this, false)); // Hook up for filtering @@ -893,6 +894,7 @@ void LLFloaterPreference::onDoNotDisturbResponseChanged() LLFloaterPreference::~LLFloaterPreference() { LLConversationLog::instance().removeObserver(this); + mComplexityChangedSignal.disconnect(); } // FIRE-19539 - Include the alert messages in Prefs>Notifications>Alerts in preference Search. @@ -1277,33 +1279,6 @@ void LLFloaterPreference::onRenderOptionEnable() refreshEnabledGraphics(); } -void LLFloaterPreferenceGraphicsAdvanced::onRenderOptionEnable() -{ - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->refresh(); - } - - refreshEnabledGraphics(); -} - -void LLFloaterPreferenceGraphicsAdvanced::onAdvancedAtmosphericsEnable() -{ - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->refresh(); - } - - refreshEnabledGraphics(); -} - -void LLFloaterPreferenceGraphicsAdvanced::refreshEnabledGraphics() -{ - refreshEnabledState(); -} - void LLFloaterPreference::onAvatarImpostorsEnable() { refreshEnabledGraphics(); @@ -2326,129 +2301,6 @@ void LLFloaterPreference::onCopySearch() } // -void LLFloaterPreferenceGraphicsAdvanced::refreshEnabledState() -{ - LLComboBox* ctrl_reflections = getChild("Reflections"); - LLTextBox* reflections_text = getChild("ReflectionsText"); - - // Reflections - BOOL reflections = gGLManager.mHasCubeMap && LLCubeMap::sUseCubeMaps; - ctrl_reflections->setEnabled(reflections); - reflections_text->setEnabled(reflections); - - // Transparent Water - LLCheckBoxCtrl* transparent_water_ctrl = getChild("TransparentWater"); - - // Bump & Shiny - LLCheckBoxCtrl* bumpshiny_ctrl = getChild("BumpShiny"); - bool bumpshiny = gGLManager.mHasCubeMap && LLCubeMap::sUseCubeMaps && LLFeatureManager::getInstance()->isFeatureAvailable("RenderObjectBump"); - bumpshiny_ctrl->setEnabled(bumpshiny ? TRUE : FALSE); - - // Avatar Mode - // Enable Avatar Shaders - LLCheckBoxCtrl* ctrl_avatar_vp = getChild("AvatarVertexProgram"); - // Avatar Render Mode - LLCheckBoxCtrl* ctrl_avatar_cloth = getChild("AvatarCloth"); - - bool avatar_vp_enabled = LLFeatureManager::getInstance()->isFeatureAvailable("RenderAvatarVP"); - if (LLViewerShaderMgr::sInitialized) - { - S32 max_avatar_shader = LLViewerShaderMgr::instance()->mMaxAvatarShaderLevel; - avatar_vp_enabled = (max_avatar_shader > 0) ? TRUE : FALSE; - } - - ctrl_avatar_vp->setEnabled(avatar_vp_enabled); - - if (gSavedSettings.getBOOL("RenderAvatarVP") == FALSE) - { - ctrl_avatar_cloth->setEnabled(FALSE); - } - else - { - ctrl_avatar_cloth->setEnabled(TRUE); - } - - /* remove orphaned code left over from EEP - // Vertex Shaders, Global Shader Enable - // SL-12594 Basic shaders are always enabled. DJH TODO clean up now-orphaned state handling code - LLSliderCtrl* terrain_detail = getChild("TerrainDetail"); // can be linked with control var - LLTextBox* terrain_text = getChild("TerrainDetailText"); - terrain_detail->setEnabled(FALSE); - terrain_text->setEnabled(FALSE); - */ - - // WindLight - LLCheckBoxCtrl* ctrl_wind_light = getChild("WindLightUseAtmosShaders"); - LLSliderCtrl* sky = getChild("SkyMeshDetail"); - LLTextBox* sky_text = getChild("SkyMeshDetailText"); -// [RLVa:KB] - Checked: 2010-03-18 (RLVa-1.2.0a) | Modified: RLVa-0.2.0a - // "Atmospheric Shaders" can't be disabled - but can be enabled - under @setenv=n - ctrl_wind_light->setEnabled( (RlvActions::canChangeEnvironment()) || (!gSavedSettings.getBOOL("WindLightUseAtmosShaders"))); -// [/RLVa:KB] -// ctrl_wind_light->setEnabled(TRUE); - sky->setEnabled(TRUE); - sky_text->setEnabled(TRUE); - - //Deferred/SSAO/Shadows - LLCheckBoxCtrl* ctrl_deferred = getChild("UseLightShaders"); - - BOOL enabled = LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred") && - ((bumpshiny_ctrl && bumpshiny_ctrl->get()) ? TRUE : FALSE) && - ((transparent_water_ctrl && transparent_water_ctrl->get()) ? TRUE : FALSE) && - gGLManager.mHasFramebufferObject && - gSavedSettings.getBOOL("RenderAvatarVP") && - (ctrl_wind_light->get()) ? TRUE : FALSE; - - ctrl_deferred->setEnabled(enabled); - - LLCheckBoxCtrl* ctrl_ssao = getChild("UseSSAO"); - LLCheckBoxCtrl* ctrl_dof = getChild("UseDoF"); - LLComboBox* ctrl_shadow = getChild("ShadowDetail"); - LLTextBox* shadow_text = getChild("RenderShadowDetailText"); - - // note, okay here to get from ctrl_deferred as it's twin, ctrl_deferred2 will alway match it - enabled = enabled && LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferredSSAO") && (ctrl_deferred->get() ? TRUE : FALSE); - - ctrl_deferred->set(gSavedSettings.getBOOL("RenderDeferred")); - - ctrl_ssao->setEnabled(enabled); - ctrl_dof->setEnabled(enabled); - - enabled = enabled && LLFeatureManager::getInstance()->isFeatureAvailable("RenderShadowDetail"); - - ctrl_shadow->setEnabled(enabled); - shadow_text->setEnabled(enabled); - - // Hardware settings - F32 mem_multiplier = gSavedSettings.getF32("RenderTextureMemoryMultiple"); - S32Megabytes min_tex_mem = LLViewerTextureList::getMinVideoRamSetting(); - S32Megabytes max_tex_mem = LLViewerTextureList::getMaxVideoRamSetting(false, mem_multiplier); - getChild("GraphicsCardTextureMemory")->setMinValue(min_tex_mem.value()); - getChild("GraphicsCardTextureMemory")->setMaxValue(max_tex_mem.value()); - - if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderVBOEnable") || - !gGLManager.mHasVertexBufferObject) - { - getChildView("vbo")->setEnabled(FALSE); - } - - if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderCompressTextures") || - !gGLManager.mHasVertexBufferObject) - { - getChildView("texture compression")->setEnabled(FALSE); - } - - // if no windlight shaders, turn off nighttime brightness, gamma, and fog distance - LLUICtrl* gamma_ctrl = getChild("gamma"); - gamma_ctrl->setEnabled(!gPipeline.canUseWindLightShaders()); - getChildView("(brightness, lower is brighter)")->setEnabled(!gPipeline.canUseWindLightShaders()); - getChildView("fog")->setEnabled(!gPipeline.canUseWindLightShaders()); - getChildView("antialiasing restart")->setVisible(!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred")); - - // now turn off any features that are unavailable - disableUnavailableSettings(); -} - // static void LLAvatarComplexityControls::setIndirectControls() @@ -2595,118 +2447,6 @@ void LLFloaterPreference::disableUnavailableSettings() } } -void LLFloaterPreferenceGraphicsAdvanced::disableUnavailableSettings() -{ - LLComboBox* ctrl_reflections = getChild("Reflections"); - LLTextBox* reflections_text = getChild("ReflectionsText"); - LLCheckBoxCtrl* ctrl_avatar_vp = getChild("AvatarVertexProgram"); - LLCheckBoxCtrl* ctrl_avatar_cloth = getChild("AvatarCloth"); - LLCheckBoxCtrl* ctrl_wind_light = getChild("WindLightUseAtmosShaders"); - LLCheckBoxCtrl* ctrl_deferred = getChild("UseLightShaders"); - LLComboBox* ctrl_shadows = getChild("ShadowDetail"); - LLTextBox* shadows_text = getChild("RenderShadowDetailText"); - LLCheckBoxCtrl* ctrl_ssao = getChild("UseSSAO"); - LLCheckBoxCtrl* ctrl_dof = getChild("UseDoF"); - LLSliderCtrl* sky = getChild("SkyMeshDetail"); - LLTextBox* sky_text = getChild("SkyMeshDetailText"); - - // disabled windlight - if (!LLFeatureManager::getInstance()->isFeatureAvailable("WindLightUseAtmosShaders")) - { - ctrl_wind_light->setEnabled(FALSE); - ctrl_wind_light->setValue(FALSE); - - sky->setEnabled(FALSE); - sky_text->setEnabled(FALSE); - - //deferred needs windlight, disable deferred - ctrl_shadows->setEnabled(FALSE); - ctrl_shadows->setValue(0); - shadows_text->setEnabled(FALSE); - - ctrl_ssao->setEnabled(FALSE); - ctrl_ssao->setValue(FALSE); - - ctrl_dof->setEnabled(FALSE); - ctrl_dof->setValue(FALSE); - - ctrl_deferred->setEnabled(FALSE); - ctrl_deferred->setValue(FALSE); - } - - // disabled deferred - if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred") || - !gGLManager.mHasFramebufferObject) - { - ctrl_shadows->setEnabled(FALSE); - ctrl_shadows->setValue(0); - shadows_text->setEnabled(FALSE); - - ctrl_ssao->setEnabled(FALSE); - ctrl_ssao->setValue(FALSE); - - ctrl_dof->setEnabled(FALSE); - ctrl_dof->setValue(FALSE); - - ctrl_deferred->setEnabled(FALSE); - ctrl_deferred->setValue(FALSE); - } - - // disabled deferred SSAO - if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferredSSAO")) - { - ctrl_ssao->setEnabled(FALSE); - ctrl_ssao->setValue(FALSE); - } - - // disabled deferred shadows - if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderShadowDetail")) - { - ctrl_shadows->setEnabled(FALSE); - ctrl_shadows->setValue(0); - shadows_text->setEnabled(FALSE); - } - - // disabled reflections - if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderReflectionDetail")) - { - ctrl_reflections->setEnabled(FALSE); - ctrl_reflections->setValue(FALSE); - reflections_text->setEnabled(FALSE); - } - - // disabled av - if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderAvatarVP")) - { - ctrl_avatar_vp->setEnabled(FALSE); - ctrl_avatar_vp->setValue(FALSE); - - ctrl_avatar_cloth->setEnabled(FALSE); - ctrl_avatar_cloth->setValue(FALSE); - - //deferred needs AvatarVP, disable deferred - ctrl_shadows->setEnabled(FALSE); - ctrl_shadows->setValue(0); - shadows_text->setEnabled(FALSE); - - ctrl_ssao->setEnabled(FALSE); - ctrl_ssao->setValue(FALSE); - - ctrl_dof->setEnabled(FALSE); - ctrl_dof->setValue(FALSE); - - ctrl_deferred->setEnabled(FALSE); - ctrl_deferred->setValue(FALSE); - } - - // disabled cloth - if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderAvatarCloth")) - { - ctrl_avatar_cloth->setEnabled(FALSE); - ctrl_avatar_cloth->setValue(FALSE); - } -} - void LLFloaterPreference::refresh() { LLPanel::refresh(); @@ -2731,32 +2471,6 @@ void LLFloaterPreference::refresh() updateClickActionViews(); } -void LLFloaterPreferenceGraphicsAdvanced::refresh() -{ - getChild("fsaa")->setValue((LLSD::Integer) gSavedSettings.getU32("RenderFSAASamples")); - - // sliders and their text boxes - // mPostProcess = gSavedSettings.getS32("RenderGlowResolutionPow"); - // slider text boxes - updateSliderText(getChild("ObjectMeshDetail", true), getChild("ObjectMeshDetailText", true)); - updateSliderText(getChild("FlexibleMeshDetail", true), getChild("FlexibleMeshDetailText", true)); - updateSliderText(getChild("TreeMeshDetail", true), getChild("TreeMeshDetailText", true)); - updateSliderText(getChild("AvatarMeshDetail", true), getChild("AvatarMeshDetailText", true)); - updateSliderText(getChild("AvatarPhysicsDetail", true), getChild("AvatarPhysicsDetailText", true)); - updateSliderText(getChild("TerrainMeshDetail", true), getChild("TerrainMeshDetailText", true)); - updateSliderText(getChild("RenderPostProcess", true), getChild("PostProcessText", true)); - updateSliderText(getChild("SkyMeshDetail", true), getChild("SkyMeshDetailText", true)); - updateSliderText(getChild("TerrainDetail", true), getChild("TerrainDetailText", true)); - LLAvatarComplexityControls::setIndirectControls(); - setMaxNonImpostorsText( - gSavedSettings.getU32("RenderAvatarMaxNonImpostors"), - getChild("IndirectMaxNonImpostorsText", true)); - LLAvatarComplexityControls::setText( - gSavedSettings.getU32("RenderAvatarMaxComplexity"), - getChild("IndirectMaxComplexityText", true)); - refreshEnabledState(); -} - void LLFloaterPreference::onCommitWindowedMode() { refresh(); @@ -3120,64 +2834,7 @@ void LLFloaterPreference::updateMaxComplexityLabel(const LLSD& newvalue) } // -void LLFloaterPreferenceGraphicsAdvanced::updateSliderText(LLSliderCtrl* ctrl, LLTextBox* text_box) -{ - if (text_box == NULL || ctrl== NULL) - return; - - // get range and points when text should change - F32 value = (F32)ctrl->getValue().asReal(); - F32 min = ctrl->getMinValue(); - F32 max = ctrl->getMaxValue(); - F32 range = max - min; - llassert(range > 0); - F32 midPoint = min + range / 3.0f; - F32 highPoint = min + (2.0f * range / 3.0f); - - // choose the right text - if (value < midPoint) - { - text_box->setText(LLTrans::getString("GraphicsQualityLow")); - } - else if (value < highPoint) - { - text_box->setText(LLTrans::getString("GraphicsQualityMid")); - } - else - { - text_box->setText(LLTrans::getString("GraphicsQualityHigh")); - } -} - -void LLFloaterPreferenceGraphicsAdvanced::updateMaxNonImpostors() -{ - // Called when the IndirectMaxNonImpostors control changes - // Responsible for fixing the slider label (IndirectMaxNonImpostorsText) and setting RenderAvatarMaxNonImpostors - LLSliderCtrl* ctrl = getChild("IndirectMaxNonImpostors",true); - U32 value = ctrl->getValue().asInteger(); - - if (0 == value || LLVOAvatar::NON_IMPOSTORS_MAX_SLIDER <= value) - { - value=0; - } - gSavedSettings.setU32("RenderAvatarMaxNonImpostors", value); - LLVOAvatar::updateImpostorRendering(value); // make it effective immediately - setMaxNonImpostorsText(value, getChild("IndirectMaxNonImpostorsText")); -} - -void LLFloaterPreferenceGraphicsAdvanced::setMaxNonImpostorsText(U32 value, LLTextBox* text_box) -{ - if (0 == value) - { - text_box->setText(LLTrans::getString("no_limit")); - } - else - { - text_box->setText(llformat("%d", value)); - } -} - -void LLAvatarComplexityControls::updateMax(LLSliderCtrl* slider, LLTextBox* value_label) +void LLAvatarComplexityControls::updateMax(LLSliderCtrl* slider, LLTextBox* value_label, bool short_val) { // Called when the IndirectMaxComplexity control changes // Responsible for fixing the slider label (IndirectMaxComplexityText) and setting RenderAvatarMaxComplexity @@ -3199,10 +2856,10 @@ void LLAvatarComplexityControls::updateMax(LLSliderCtrl* slider, LLTextBox* valu } gSavedSettings.setU32("RenderAvatarMaxComplexity", (U32)max_arc); - setText(max_arc, value_label); + setText(max_arc, value_label, short_val); } -void LLAvatarComplexityControls::setText(U32 value, LLTextBox* text_box) +void LLAvatarComplexityControls::setText(U32 value, LLTextBox* text_box, bool short_val) { if (0 == value) { @@ -3211,28 +2868,45 @@ void LLAvatarComplexityControls::setText(U32 value, LLTextBox* text_box) else { // Proper number formatting with delimiter - //text_box->setText(llformat("%d", value)); + //std::string text_value = short_val ? llformat("%d", value / 1000) : llformat("%d", value); + //text_box->setText(text_value); std::string output_string; LLLocale locale(""); - LLResMgr::getInstance()->getIntegerString(output_string, value); + LLResMgr::getInstance()->getIntegerString(output_string, (short_val ? value / 1000 : value)); text_box->setText(output_string); } } +// redner time controls +void LLAvatarComplexityControls::updateMaxRenderTime(LLSliderCtrl* slider, LLTextBox* value_label, bool short_val) +{ + setRenderTimeText((F32)(FSPerfStats::renderAvatarMaxART_ns/1000), value_label, short_val); +} + +void LLAvatarComplexityControls::setRenderTimeText(F32 value, LLTextBox* text_box, bool short_val) +{ + if (0 == value) + { + text_box->setText(LLTrans::getString("no_limit")); + } + else + { + text_box->setText(llformat("%.0f", value)); + } +} +// void LLFloaterPreference::updateMaxComplexity() { // Called when the IndirectMaxComplexity control changes LLAvatarComplexityControls::updateMax( getChild("IndirectMaxComplexity"), getChild("IndirectMaxComplexityText")); +} - LLFloaterPreferenceGraphicsAdvanced* floater_graphics_advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced"); - if (floater_graphics_advanced) - { - LLAvatarComplexityControls::updateMax( - floater_graphics_advanced->getChild("IndirectMaxComplexity"), - floater_graphics_advanced->getChild("IndirectMaxComplexityText")); - } +void LLFloaterPreference::updateComplexityText() +{ + LLAvatarComplexityControls::setText(gSavedSettings.getU32("RenderAvatarMaxComplexity"), + getChild("IndirectMaxComplexityText", true)); } bool LLFloaterPreference::loadFromFilename(const std::string& filename, std::map &label_map) @@ -3274,22 +2948,6 @@ bool LLFloaterPreference::loadFromFilename(const std::string& filename, std::map return true; } -void LLFloaterPreferenceGraphicsAdvanced::updateMaxComplexity() -{ - // Called when the IndirectMaxComplexity control changes - LLAvatarComplexityControls::updateMax( - getChild("IndirectMaxComplexity"), - getChild("IndirectMaxComplexityText")); - - LLFloaterPreference* floater_preferences = LLFloaterReg::findTypedInstance("preferences"); - if (floater_preferences) - { - LLAvatarComplexityControls::updateMax( - floater_preferences->getChild("IndirectMaxComplexity"), - floater_preferences->getChild("IndirectMaxComplexityText")); - } -} - void LLFloaterPreference::onChangeMaturity() { U8 sim_access = gSavedSettings.getU32("PreferredMaturity"); @@ -5077,18 +4735,6 @@ void LLPanelPreferenceControls::onCancelKeyBind() pControlsTable->deselectAllItems(); } -LLFloaterPreferenceGraphicsAdvanced::LLFloaterPreferenceGraphicsAdvanced(const LLSD& key) - : LLFloater(key) -{ - mCommitCallbackRegistrar.add("Pref.RenderOptionUpdate", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onRenderOptionEnable, this)); - mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxNonImpostors", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateMaxNonImpostors,this)); - mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxComplexity", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateMaxComplexity,this)); -} - -LLFloaterPreferenceGraphicsAdvanced::~LLFloaterPreferenceGraphicsAdvanced() -{ -} - LLFloaterPreferenceProxy::LLFloaterPreferenceProxy(const LLSD& key) : LLFloater(key), mSocksSettingsDirty(false) @@ -5098,41 +4744,6 @@ LLFloaterPreferenceProxy::LLFloaterPreferenceProxy(const LLSD& key) mCommitCallbackRegistrar.add("Proxy.Change", boost::bind(&LLFloaterPreferenceProxy::onChangeSocksSettings, this)); } -BOOL LLFloaterPreferenceGraphicsAdvanced::postBuild() -{ - // Don't do this on Mac as their braindead GL versioning - // sets this when 8x and 16x are indeed available - // -#if !LL_DARWIN - if (gGLManager.mIsIntel || gGLManager.mGLVersion < 3.f) - { //remove FSAA settings above "4x" - LLComboBox* combo = getChild("fsaa"); - combo->remove("8x"); - combo->remove("16x"); - } - - LLCheckBoxCtrl *use_HiDPI = getChild("use HiDPI"); - use_HiDPI->setVisible(FALSE); -#endif - - return TRUE; -} - -void LLFloaterPreferenceGraphicsAdvanced::onOpen(const LLSD& key) -{ - refresh(); -} - -void LLFloaterPreferenceGraphicsAdvanced::onClickCloseBtn(bool app_quitting) -{ - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->cancel(); - } - updateMaxComplexity(); -} - LLFloaterPreferenceProxy::~LLFloaterPreferenceProxy() { } diff --git a/indra/newview/llfloaterpreference.h b/indra/newview/llfloaterpreference.h index b348629957..d18cccedbe 100644 --- a/indra/newview/llfloaterpreference.h +++ b/indra/newview/llfloaterpreference.h @@ -121,12 +121,10 @@ public: void updateClickActionViews(); void updateSearchableItems(); -// Make onBtnOk() public for settings backup panel -//protected: - void onBtnOK(const LLSD& userdata); -protected: -// - void onBtnCancel(const LLSD& userdata); + void onBtnOK(const LLSD& userdata); + void onBtnCancel(const LLSD& userdata); + +protected: //void onClickClearCache(); // Clear viewer texture cache, file cache on next startup // AO: was protected, moved to public void onClickBrowserClearCache(); // Clear web history and caches as well as viewer caches above @@ -302,6 +300,7 @@ private: void onDeleteTranscriptsResponse(const LLSD& notification, const LLSD& response); void updateDeleteTranscriptsButton(); void updateMaxComplexity(); + void updateComplexityText(); static bool loadFromFilename(const std::string& filename, std::map &label_map); static std::string sSkin; @@ -323,6 +322,8 @@ private: std::unique_ptr< ll::prefs::SearchData > mSearchData; bool mSearchDataDirty; + boost::signals2::connection mComplexityChangedSignal; + void onUpdateFilterTerm( bool force = false ); void collectSearchableItems(); @@ -475,37 +476,15 @@ private: S32 mEditingMode; }; -class LLFloaterPreferenceGraphicsAdvanced : public LLFloater -{ - public: - LLFloaterPreferenceGraphicsAdvanced(const LLSD& key); - ~LLFloaterPreferenceGraphicsAdvanced(); - /*virtual*/ BOOL postBuild(); - void onOpen(const LLSD& key); - void onClickCloseBtn(bool app_quitting); - void disableUnavailableSettings(); - void refreshEnabledGraphics(); - void refreshEnabledState(); - void updateSliderText(LLSliderCtrl* ctrl, LLTextBox* text_box); - void updateMaxNonImpostors(); - void setMaxNonImpostorsText(U32 value, LLTextBox* text_box); - void updateMaxComplexity(); - void setMaxComplexityText(U32 value, LLTextBox* text_box); - static void setIndirectControls(); - static void setIndirectMaxNonImpostors(); - static void setIndirectMaxArc(); - void refresh(); - // callback for when client modifies a render option - void onRenderOptionEnable(); - void onAdvancedAtmosphericsEnable(); - LOG_CLASS(LLFloaterPreferenceGraphicsAdvanced); -}; - class LLAvatarComplexityControls { public: - static void updateMax(LLSliderCtrl* slider, LLTextBox* value_label); - static void setText(U32 value, LLTextBox* text_box); + static void updateMax(LLSliderCtrl* slider, LLTextBox* value_label, bool short_val = false); + static void setText(U32 value, LLTextBox* text_box, bool short_val = false); + // for render time support + static void updateMaxRenderTime(LLSliderCtrl* slider, LLTextBox* value_label, bool short_val = false); + static void setRenderTimeText(F32 value, LLTextBox* text_box, bool short_val = false); + // static void setIndirectControls(); static void setIndirectMaxNonImpostors(); static void setIndirectMaxArc(); diff --git a/indra/newview/llfloaterpreferencesgraphicsadvanced.cpp b/indra/newview/llfloaterpreferencesgraphicsadvanced.cpp new file mode 100644 index 0000000000..653d26ff30 --- /dev/null +++ b/indra/newview/llfloaterpreferencesgraphicsadvanced.cpp @@ -0,0 +1,474 @@ +/** + * @file llfloaterpreferencesgraphicsadvanced.cpp + * @brief floater for adjusting camera position + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2021, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloaterpreferencesgraphicsadvanced.h" + +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llfeaturemanager.h" +#include "llfloaterpreference.h" +#include "llfloaterreg.h" +#include "llsliderctrl.h" +#include "lltextbox.h" +#include "lltrans.h" +#include "llviewershadermgr.h" +#include "llviewertexturelist.h" +#include "llvoavatar.h" +#include "pipeline.h" +#include "llviewercontrol.h" +#include "rlvactions.h" + +LLFloaterPreferenceGraphicsAdvanced::LLFloaterPreferenceGraphicsAdvanced(const LLSD& key) + : LLFloater(key) +{ + mCommitCallbackRegistrar.add("Pref.RenderOptionUpdate", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onRenderOptionEnable, this)); + mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxNonImpostors", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateMaxNonImpostors,this)); + mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxComplexity", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateMaxComplexity,this)); + + mCommitCallbackRegistrar.add("Pref.Cancel", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onBtnCancel, this, _2)); + mCommitCallbackRegistrar.add("Pref.OK", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onBtnOK, this, _2)); +} + +LLFloaterPreferenceGraphicsAdvanced::~LLFloaterPreferenceGraphicsAdvanced() +{ + mComplexityChangedSignal.disconnect(); +} + +BOOL LLFloaterPreferenceGraphicsAdvanced::postBuild() +{ + // Don't do this on Mac as their braindead GL versioning + // sets this when 8x and 16x are indeed available + // +#if !LL_DARWIN + if (gGLManager.mIsIntel || gGLManager.mGLVersion < 3.f) + { //remove FSAA settings above "4x" + LLComboBox* combo = getChild("fsaa"); + combo->remove("8x"); + combo->remove("16x"); + } + + LLCheckBoxCtrl *use_HiDPI = getChild("use HiDPI"); + use_HiDPI->setVisible(FALSE); +#endif + + mComplexityChangedSignal = gSavedSettings.getControl("RenderAvatarMaxComplexity")->getCommitSignal()->connect(boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateComplexityText, this)); + + return TRUE; +} + +void LLFloaterPreferenceGraphicsAdvanced::onOpen(const LLSD& key) +{ + refresh(); +} + +void LLFloaterPreferenceGraphicsAdvanced::onClickCloseBtn(bool app_quitting) +{ + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->cancel(); + } + updateMaxComplexity(); +} + +void LLFloaterPreferenceGraphicsAdvanced::onRenderOptionEnable() +{ + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->refresh(); + } + + refreshEnabledGraphics(); +} + +void LLFloaterPreferenceGraphicsAdvanced::onAdvancedAtmosphericsEnable() +{ + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->refresh(); + } + + refreshEnabledGraphics(); +} + +void LLFloaterPreferenceGraphicsAdvanced::refresh() +{ + getChild("fsaa")->setValue((LLSD::Integer) gSavedSettings.getU32("RenderFSAASamples")); + + // sliders and their text boxes + // mPostProcess = gSavedSettings.getS32("RenderGlowResolutionPow"); + // slider text boxes + updateSliderText(getChild("ObjectMeshDetail", true), getChild("ObjectMeshDetailText", true)); + updateSliderText(getChild("FlexibleMeshDetail", true), getChild("FlexibleMeshDetailText", true)); + updateSliderText(getChild("TreeMeshDetail", true), getChild("TreeMeshDetailText", true)); + updateSliderText(getChild("AvatarMeshDetail", true), getChild("AvatarMeshDetailText", true)); + updateSliderText(getChild("AvatarPhysicsDetail", true), getChild("AvatarPhysicsDetailText", true)); + updateSliderText(getChild("TerrainMeshDetail", true), getChild("TerrainMeshDetailText", true)); + updateSliderText(getChild("RenderPostProcess", true), getChild("PostProcessText", true)); + updateSliderText(getChild("SkyMeshDetail", true), getChild("SkyMeshDetailText", true)); + updateSliderText(getChild("TerrainDetail", true), getChild("TerrainDetailText", true)); + LLAvatarComplexityControls::setIndirectControls(); + setMaxNonImpostorsText( + gSavedSettings.getU32("RenderAvatarMaxNonImpostors"), + getChild("IndirectMaxNonImpostorsText", true)); + LLAvatarComplexityControls::setText( + gSavedSettings.getU32("RenderAvatarMaxComplexity"), + getChild("IndirectMaxComplexityText", true)); + refreshEnabledState(); +} + +void LLFloaterPreferenceGraphicsAdvanced::refreshEnabledGraphics() +{ + refreshEnabledState(); +} + +void LLFloaterPreferenceGraphicsAdvanced::updateMaxComplexity() +{ + // Called when the IndirectMaxComplexity control changes + LLAvatarComplexityControls::updateMax( + getChild("IndirectMaxComplexity"), + getChild("IndirectMaxComplexityText")); +} + +void LLFloaterPreferenceGraphicsAdvanced::updateComplexityText() +{ + LLAvatarComplexityControls::setText(gSavedSettings.getU32("RenderAvatarMaxComplexity"), + getChild("IndirectMaxComplexityText", true)); +} + +void LLFloaterPreferenceGraphicsAdvanced::updateSliderText(LLSliderCtrl* ctrl, LLTextBox* text_box) +{ + if (text_box == NULL || ctrl== NULL) + return; + + // get range and points when text should change + F32 value = (F32)ctrl->getValue().asReal(); + F32 min = ctrl->getMinValue(); + F32 max = ctrl->getMaxValue(); + F32 range = max - min; + llassert(range > 0); + F32 midPoint = min + range / 3.0f; + F32 highPoint = min + (2.0f * range / 3.0f); + + // choose the right text + if (value < midPoint) + { + text_box->setText(LLTrans::getString("GraphicsQualityLow")); + } + else if (value < highPoint) + { + text_box->setText(LLTrans::getString("GraphicsQualityMid")); + } + else + { + text_box->setText(LLTrans::getString("GraphicsQualityHigh")); + } +} + +void LLFloaterPreferenceGraphicsAdvanced::updateMaxNonImpostors() +{ + // Called when the IndirectMaxNonImpostors control changes + // Responsible for fixing the slider label (IndirectMaxNonImpostorsText) and setting RenderAvatarMaxNonImpostors + LLSliderCtrl* ctrl = getChild("IndirectMaxNonImpostors",true); + U32 value = ctrl->getValue().asInteger(); + + if (0 == value || LLVOAvatar::NON_IMPOSTORS_MAX_SLIDER <= value) + { + value=0; + } + gSavedSettings.setU32("RenderAvatarMaxNonImpostors", value); + LLVOAvatar::updateImpostorRendering(value); // make it effective immediately + setMaxNonImpostorsText(value, getChild("IndirectMaxNonImpostorsText")); +} + +void LLFloaterPreferenceGraphicsAdvanced::setMaxNonImpostorsText(U32 value, LLTextBox* text_box) +{ + if (0 == value) + { + text_box->setText(LLTrans::getString("no_limit")); + } + else + { + text_box->setText(llformat("%d", value)); + } +} + +void LLFloaterPreferenceGraphicsAdvanced::disableUnavailableSettings() +{ + LLComboBox* ctrl_reflections = getChild("Reflections"); + LLTextBox* reflections_text = getChild("ReflectionsText"); + LLCheckBoxCtrl* ctrl_avatar_vp = getChild("AvatarVertexProgram"); + LLCheckBoxCtrl* ctrl_avatar_cloth = getChild("AvatarCloth"); + LLCheckBoxCtrl* ctrl_wind_light = getChild("WindLightUseAtmosShaders"); + LLCheckBoxCtrl* ctrl_deferred = getChild("UseLightShaders"); + LLComboBox* ctrl_shadows = getChild("ShadowDetail"); + LLTextBox* shadows_text = getChild("RenderShadowDetailText"); + LLCheckBoxCtrl* ctrl_ssao = getChild("UseSSAO"); + LLCheckBoxCtrl* ctrl_dof = getChild("UseDoF"); + LLSliderCtrl* sky = getChild("SkyMeshDetail"); + LLTextBox* sky_text = getChild("SkyMeshDetailText"); + + // disabled windlight + if (!LLFeatureManager::getInstance()->isFeatureAvailable("WindLightUseAtmosShaders")) + { + ctrl_wind_light->setEnabled(FALSE); + ctrl_wind_light->setValue(FALSE); + + sky->setEnabled(FALSE); + sky_text->setEnabled(FALSE); + + //deferred needs windlight, disable deferred + ctrl_shadows->setEnabled(FALSE); + ctrl_shadows->setValue(0); + shadows_text->setEnabled(FALSE); + + ctrl_ssao->setEnabled(FALSE); + ctrl_ssao->setValue(FALSE); + + ctrl_dof->setEnabled(FALSE); + ctrl_dof->setValue(FALSE); + + ctrl_deferred->setEnabled(FALSE); + ctrl_deferred->setValue(FALSE); + } + + // disabled deferred + if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred") || + !gGLManager.mHasFramebufferObject) + { + ctrl_shadows->setEnabled(FALSE); + ctrl_shadows->setValue(0); + shadows_text->setEnabled(FALSE); + + ctrl_ssao->setEnabled(FALSE); + ctrl_ssao->setValue(FALSE); + + ctrl_dof->setEnabled(FALSE); + ctrl_dof->setValue(FALSE); + + ctrl_deferred->setEnabled(FALSE); + ctrl_deferred->setValue(FALSE); + } + + // disabled deferred SSAO + if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferredSSAO")) + { + ctrl_ssao->setEnabled(FALSE); + ctrl_ssao->setValue(FALSE); + } + + // disabled deferred shadows + if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderShadowDetail")) + { + ctrl_shadows->setEnabled(FALSE); + ctrl_shadows->setValue(0); + shadows_text->setEnabled(FALSE); + } + + // disabled reflections + if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderReflectionDetail")) + { + ctrl_reflections->setEnabled(FALSE); + ctrl_reflections->setValue(FALSE); + reflections_text->setEnabled(FALSE); + } + + // disabled av + if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderAvatarVP")) + { + ctrl_avatar_vp->setEnabled(FALSE); + ctrl_avatar_vp->setValue(FALSE); + + ctrl_avatar_cloth->setEnabled(FALSE); + ctrl_avatar_cloth->setValue(FALSE); + + //deferred needs AvatarVP, disable deferred + ctrl_shadows->setEnabled(FALSE); + ctrl_shadows->setValue(0); + shadows_text->setEnabled(FALSE); + + ctrl_ssao->setEnabled(FALSE); + ctrl_ssao->setValue(FALSE); + + ctrl_dof->setEnabled(FALSE); + ctrl_dof->setValue(FALSE); + + ctrl_deferred->setEnabled(FALSE); + ctrl_deferred->setValue(FALSE); + } + + // disabled cloth + if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderAvatarCloth")) + { + ctrl_avatar_cloth->setEnabled(FALSE); + ctrl_avatar_cloth->setValue(FALSE); + } +} + +void LLFloaterPreferenceGraphicsAdvanced::refreshEnabledState() +{ + LLComboBox* ctrl_reflections = getChild("Reflections"); + LLTextBox* reflections_text = getChild("ReflectionsText"); + + // Reflections + BOOL reflections = gGLManager.mHasCubeMap && LLCubeMap::sUseCubeMaps; + ctrl_reflections->setEnabled(reflections); + reflections_text->setEnabled(reflections); + + // Transparent Water + LLCheckBoxCtrl* transparent_water_ctrl = getChild("TransparentWater"); + + // Bump & Shiny + LLCheckBoxCtrl* bumpshiny_ctrl = getChild("BumpShiny"); + bool bumpshiny = gGLManager.mHasCubeMap && LLCubeMap::sUseCubeMaps && LLFeatureManager::getInstance()->isFeatureAvailable("RenderObjectBump"); + bumpshiny_ctrl->setEnabled(bumpshiny ? TRUE : FALSE); + + // Avatar Mode + // Enable Avatar Shaders + LLCheckBoxCtrl* ctrl_avatar_vp = getChild("AvatarVertexProgram"); + // Avatar Render Mode + LLCheckBoxCtrl* ctrl_avatar_cloth = getChild("AvatarCloth"); + + bool avatar_vp_enabled = LLFeatureManager::getInstance()->isFeatureAvailable("RenderAvatarVP"); + if (LLViewerShaderMgr::sInitialized) + { + S32 max_avatar_shader = LLViewerShaderMgr::instance()->mMaxAvatarShaderLevel; + avatar_vp_enabled = (max_avatar_shader > 0) ? TRUE : FALSE; + } + + ctrl_avatar_vp->setEnabled(avatar_vp_enabled); + + if (gSavedSettings.getBOOL("RenderAvatarVP") == FALSE) + { + ctrl_avatar_cloth->setEnabled(FALSE); + } + else + { + ctrl_avatar_cloth->setEnabled(TRUE); + } + + /* remove orphaned code left over from EEP + // Vertex Shaders, Global Shader Enable + // SL-12594 Basic shaders are always enabled. DJH TODO clean up now-orphaned state handling code + LLSliderCtrl* terrain_detail = getChild("TerrainDetail"); // can be linked with control var + LLTextBox* terrain_text = getChild("TerrainDetailText"); + + terrain_detail->setEnabled(FALSE); + terrain_text->setEnabled(FALSE); + */ + + // WindLight + LLCheckBoxCtrl* ctrl_wind_light = getChild("WindLightUseAtmosShaders"); + LLSliderCtrl* sky = getChild("SkyMeshDetail"); + LLTextBox* sky_text = getChild("SkyMeshDetailText"); +// [RLVa:KB] - Checked: 2010-03-18 (RLVa-1.2.0a) | Modified: RLVa-0.2.0a + // "Atmospheric Shaders" can't be disabled - but can be enabled - under @setenv=n + ctrl_wind_light->setEnabled((RlvActions::canChangeEnvironment()) || (!gSavedSettings.getBOOL("WindLightUseAtmosShaders"))); +// [/RLVa:KB] +// ctrl_wind_light->setEnabled(TRUE); + sky->setEnabled(TRUE); + sky_text->setEnabled(TRUE); + + //Deferred/SSAO/Shadows + LLCheckBoxCtrl* ctrl_deferred = getChild("UseLightShaders"); + + BOOL enabled = LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred") && + ((bumpshiny_ctrl && bumpshiny_ctrl->get()) ? TRUE : FALSE) && + ((transparent_water_ctrl && transparent_water_ctrl->get()) ? TRUE : FALSE) && + gGLManager.mHasFramebufferObject && + gSavedSettings.getBOOL("RenderAvatarVP") && + (ctrl_wind_light->get()) ? TRUE : FALSE; + + ctrl_deferred->setEnabled(enabled); + + LLCheckBoxCtrl* ctrl_ssao = getChild("UseSSAO"); + LLCheckBoxCtrl* ctrl_dof = getChild("UseDoF"); + LLComboBox* ctrl_shadow = getChild("ShadowDetail"); + LLTextBox* shadow_text = getChild("RenderShadowDetailText"); + + // note, okay here to get from ctrl_deferred as it's twin, ctrl_deferred2 will alway match it + enabled = enabled && LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferredSSAO") && (ctrl_deferred->get() ? TRUE : FALSE); + + ctrl_deferred->set(gSavedSettings.getBOOL("RenderDeferred")); + + ctrl_ssao->setEnabled(enabled); + ctrl_dof->setEnabled(enabled); + + enabled = enabled && LLFeatureManager::getInstance()->isFeatureAvailable("RenderShadowDetail"); + + ctrl_shadow->setEnabled(enabled); + shadow_text->setEnabled(enabled); + + // Hardware settings + F32 mem_multiplier = gSavedSettings.getF32("RenderTextureMemoryMultiple"); + S32Megabytes min_tex_mem = LLViewerTextureList::getMinVideoRamSetting(); + S32Megabytes max_tex_mem = LLViewerTextureList::getMaxVideoRamSetting(false, mem_multiplier); + getChild("GraphicsCardTextureMemory")->setMinValue(min_tex_mem.value()); + getChild("GraphicsCardTextureMemory")->setMaxValue(max_tex_mem.value()); + + if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderVBOEnable") || + !gGLManager.mHasVertexBufferObject) + { + getChildView("vbo")->setEnabled(FALSE); + } + + if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderCompressTextures") || + !gGLManager.mHasVertexBufferObject) + { + getChildView("texture compression")->setEnabled(FALSE); + } + + // if no windlight shaders, turn off nighttime brightness, gamma, and fog distance + LLUICtrl* gamma_ctrl = getChild("gamma"); + gamma_ctrl->setEnabled(!gPipeline.canUseWindLightShaders()); + getChildView("(brightness, lower is brighter)")->setEnabled(!gPipeline.canUseWindLightShaders()); + getChildView("fog")->setEnabled(!gPipeline.canUseWindLightShaders()); + getChildView("antialiasing restart")->setVisible(!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred")); + + // now turn off any features that are unavailable + disableUnavailableSettings(); +} + +void LLFloaterPreferenceGraphicsAdvanced::onBtnOK(const LLSD& userdata) +{ + LLFloaterPreference* instance = LLFloaterReg::getTypedInstance("preferences"); + if (instance) + { + instance->onBtnOK(userdata); + } +} + +void LLFloaterPreferenceGraphicsAdvanced::onBtnCancel(const LLSD& userdata) +{ + LLFloaterPreference* instance = LLFloaterReg::getTypedInstance("preferences"); + if (instance) + { + instance->onBtnCancel(userdata); + } +} diff --git a/indra/newview/llfloaterpreferencesgraphicsadvanced.h b/indra/newview/llfloaterpreferencesgraphicsadvanced.h new file mode 100644 index 0000000000..c5d21ba35b --- /dev/null +++ b/indra/newview/llfloaterpreferencesgraphicsadvanced.h @@ -0,0 +1,65 @@ +/** + * @file llfloaterpreferencesgraphicsadvanced.h + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2021, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLFLOATERPREFERENCEGRAPHICSADVANCED_H +#define LLFLOATERPREFERENCEGRAPHICSADVANCED_H + +#include "llcontrol.h" +#include "llfloater.h" + +class LLSliderCtrl; +class LLTextBox; + +class LLFloaterPreferenceGraphicsAdvanced : public LLFloater +{ +public: + LLFloaterPreferenceGraphicsAdvanced(const LLSD& key); + ~LLFloaterPreferenceGraphicsAdvanced(); + /*virtual*/ BOOL postBuild(); + void onOpen(const LLSD& key); + void onClickCloseBtn(bool app_quitting); + void disableUnavailableSettings(); + void refreshEnabledGraphics(); + void refreshEnabledState(); + void updateSliderText(LLSliderCtrl* ctrl, LLTextBox* text_box); + void updateMaxNonImpostors(); + void setMaxNonImpostorsText(U32 value, LLTextBox* text_box); + void updateMaxComplexity(); + void updateComplexityText(); + void refresh(); + // callback for when client modifies a render option + void onRenderOptionEnable(); + void onAdvancedAtmosphericsEnable(); + LOG_CLASS(LLFloaterPreferenceGraphicsAdvanced); + +protected: + void onBtnOK(const LLSD& userdata); + void onBtnCancel(const LLSD& userdata); + + boost::signals2::connection mComplexityChangedSignal; +}; + +#endif //LLFLOATERPREFERENCEGRAPHICSADVANCED_H + diff --git a/indra/newview/llnamelistctrl.cpp b/indra/newview/llnamelistctrl.cpp index f82595097c..de04ec5e63 100644 --- a/indra/newview/llnamelistctrl.cpp +++ b/indra/newview/llnamelistctrl.cpp @@ -71,9 +71,11 @@ LLNameListCtrl::LLNameListCtrl(const LLNameListCtrl::Params& p) mNameColumnIndex(p.name_column.column_index), mNameColumn(p.name_column.column_name), mAllowCallingCardDrop(p.allow_calling_card_drop), - mShortNames(p.short_names) + mShortNames(p.short_names), // Fix Baker's NameListCtrl un-fix - //mPendingLookupsRemaining(0) + //mPendingLookupsRemaining(0), + mHoverIconName("Info_Small"), + mNameListType(INDIVIDUAL) {} // public @@ -140,7 +142,12 @@ BOOL LLNameListCtrl::handleDragAndDrop( void LLNameListCtrl::showInspector(const LLUUID& avatar_id, bool is_group, bool is_experience) { - if(is_experience) + if (isSpecialType()) + { + mIconClickedSignal(avatar_id); + return; + } + if(is_experience) { LLFloaterReg::showInstance("experience_profile", avatar_id, true); return; @@ -224,14 +231,16 @@ BOOL LLNameListCtrl::handleToolTip(S32 x, S32 y, MASK mask) S32 column_index = getColumnIndexFromOffset(x); LLNameListItem* hit_item = dynamic_cast(hitItem(x, y)); LLFloater* floater = gFloaterView->getParentFloater(this); - if (floater + + + if (floater && floater->isFrontmost() && hit_item - && column_index == mNameColumnIndex) + && ((column_index == mNameColumnIndex) || isSpecialType())) { - // ...this is the column with the avatar name - LLUUID avatar_id = hit_item->getUUID(); - if (avatar_id.notNull()) + // ...this is the column with the avatar name + LLUUID item_id = isSpecialType() ? hit_item->getSpecialID() : hit_item->getUUID(); + if (item_id.notNull()) { // ...valid avatar id @@ -239,13 +248,13 @@ BOOL LLNameListCtrl::handleToolTip(S32 x, S32 y, MASK mask) if (hit_cell) { S32 row_index = getItemIndex(hit_item); - LLRect cell_rect = getCellRect(row_index, column_index); + LLRect cell_rect = getCellRect(row_index, isSpecialType() ? getNumColumns() - 1 : column_index); // Convert rect local to screen coordinates LLRect sticky_rect; localRectToScreen(cell_rect, &sticky_rect); // Spawn at right side of cell - LLPointer icon = LLUI::getUIImage("Info_Small"); + LLPointer icon = LLUI::getUIImage(mHoverIconName); S32 screenX = sticky_rect.mRight - info_icon_size; S32 screenY = sticky_rect.mTop - (sticky_rect.getHeight() - icon->getHeight()) / 2; LLCoordGL pos(screenX, screenY); @@ -259,7 +268,7 @@ BOOL LLNameListCtrl::handleToolTip(S32 x, S32 y, MASK mask) LLToolTip::Params params; params.background_visible(false); - params.click_callback(boost::bind(&LLNameListCtrl::showInspector, this, avatar_id, is_group, is_experience)); + params.click_callback(boost::bind(&LLNameListCtrl::showInspector, this, item_id, is_group, is_experience)); params.delay_time(0.0f); // spawn instantly on hover params.image(icon); params.message(""); @@ -336,6 +345,7 @@ LLScrollListItem* LLNameListCtrl::addNameItemRow( // use supplied name by default std::string fullname = name_item.name; + switch(name_item.target) { case GROUP: @@ -354,8 +364,10 @@ LLScrollListItem* LLNameListCtrl::addNameItemRow( } break; case SPECIAL: - // just use supplied name - break; + { + item->setSpecialID(name_item.special_id()); + return item; + } case INDIVIDUAL: { LLAvatarName av_name; @@ -366,7 +378,7 @@ LLScrollListItem* LLNameListCtrl::addNameItemRow( else if (LLAvatarNameCache::get(id, &av_name)) { if (mShortNames) - fullname = av_name.getDisplayName(); + fullname = av_name.getDisplayName(true); else fullname = av_name.getCompleteName(); } @@ -437,7 +449,8 @@ void LLNameListCtrl::removeNameItem(const LLUUID& agent_id) for (item_list::iterator it = getItemList().begin(); it != getItemList().end(); it++) { LLScrollListItem* item = *it; - if (item->getUUID() == agent_id) + LLUUID cur_id = isSpecialType() ? dynamic_cast(item)->getSpecialID() : item->getUUID(); + if (cur_id == agent_id) { idx = getItemIndex(item); break; @@ -469,6 +482,34 @@ LLScrollListItem* LLNameListCtrl::getNameItemByAgentId(const LLUUID& agent_id) return NULL; } +void LLNameListCtrl::selectItemBySpecialId(const LLUUID& special_id) +{ + if (special_id.isNull()) + { + return; + } + + for (item_list::iterator it = getItemList().begin(); it != getItemList().end(); it++) + { + LLNameListItem* item = dynamic_cast(*it); + if (item && item->getSpecialID() == special_id) + { + item->setSelected(TRUE); + break; + } + } +} + +LLUUID LLNameListCtrl::getSelectedSpecialId() +{ + LLNameListItem* item = dynamic_cast(getFirstSelected()); + if(item) + { + return item->getSpecialID(); + } + return LLUUID(); +} + void LLNameListCtrl::onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name, std::string suffix, diff --git a/indra/newview/llnamelistctrl.h b/indra/newview/llnamelistctrl.h index e7c11a343b..ca81c8bfd9 100644 --- a/indra/newview/llnamelistctrl.h +++ b/indra/newview/llnamelistctrl.h @@ -52,6 +52,8 @@ public: void setIsGroup(bool is_group) { mIsGroup = is_group; } bool isExperience() const { return mIsExperience; } void setIsExperience(bool is_experience) { mIsExperience = is_experience; } + void setSpecialID(const LLUUID& special_id) { mSpecialID = special_id; } + const LLUUID& getSpecialID() const { return mSpecialID; } protected: friend class LLNameListCtrl; @@ -74,6 +76,8 @@ protected: private: bool mIsGroup; bool mIsExperience; + + LLUUID mSpecialID; }; @@ -104,10 +108,12 @@ public: { Optional name; Optional target; + Optional special_id; NameItem() : name("name"), - target("target", INDIVIDUAL) + target("target", INDIVIDUAL), + special_id("special_id", LLUUID()) {} }; @@ -168,6 +174,9 @@ public: LLScrollListItem* getNameItemByAgentId(const LLUUID& agent_id); + void selectItemBySpecialId(const LLUUID& special_id); + LLUUID getSelectedSpecialId(); + // LLView interface /*virtual*/ BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void *cargo_data, @@ -182,6 +191,12 @@ public: /*virtual*/ void updateColumns(bool force_update); /*virtual*/ void mouseOverHighlightNthItem( S32 index ); + + bool isSpecialType() { return (mNameListType == SPECIAL); } + + void setNameListType(e_name_type type) { mNameListType = type; } + void setHoverIconName(std::string icon_name) { mHoverIconName = icon_name; } + private: void showInspector(const LLUUID& avatar_id, bool is_group, bool is_experience = false); void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name, std::string suffix, std::string prefix, LLHandle item); @@ -196,17 +211,28 @@ private: avatar_name_cache_connection_map_t mAvatarNameCacheConnections; avatar_name_cache_connection_map_t mGroupNameCacheConnections; -// Fix Baker's NameListCtrl un-fix -// S32 mPendingLookupsRemaining; -// namelist_complete_signal_t mNameListCompleteSignal; -// -//public: -// boost::signals2::connection setOnNameListCompleteCallback(boost::function onNameListCompleteCallback) -// { -// return mNameListCompleteSignal.connect(onNameListCompleteCallback); -// } -// + // Fix Baker's NameListCtrl un-fix + //S32 mPendingLookupsRemaining; + //namelist_complete_signal_t mNameListCompleteSignal; + // + std::string mHoverIconName; + e_name_type mNameListType; + + boost::signals2::signal mIconClickedSignal; + +public: + // Fix Baker's NameListCtrl un-fix + //boost::signals2::connection setOnNameListCompleteCallback(boost::function onNameListCompleteCallback) + //{ + // return mNameListCompleteSignal.connect(onNameListCompleteCallback); + //} + // + + boost::signals2::connection setIconClickedCallback(boost::function cb) + { + return mIconClickedSignal.connect(cb); + } }; diff --git a/indra/newview/llpresetsmanager.cpp b/indra/newview/llpresetsmanager.cpp index d99466ec28..b25b79c5a2 100644 --- a/indra/newview/llpresetsmanager.cpp +++ b/indra/newview/llpresetsmanager.cpp @@ -42,6 +42,7 @@ #include "llagentcamera.h" #include "llfile.h" #include "quickprefs.h" +#include "fsperfstats.h" // avoid triggering reloads while autotuning LLPresetsManager::LLPresetsManager() // Graphic preset controls independent from XUI @@ -708,7 +709,9 @@ void LLPresetsManager::handleGraphicPresetControlChanged(LLControlVariablePtr co { LL_DEBUGS() << "Handling graphic preset control change: control = " << control->getName() << " - new = " << new_value << " - old = " << old_value << LL_ENDL; - if (!mIsLoadingPreset && (!mIsDrawDistanceSteppingActive || control->getName() != "RenderFarClip")) + if (!mIsLoadingPreset && + (!mIsDrawDistanceSteppingActive || control->getName() != "RenderFarClip") && + (!FSPerfStats::autoTune) ) { LL_DEBUGS() << "Trigger graphic preset control changed signal" << LL_ENDL; diff --git a/indra/newview/llsechandler_basic.cpp b/indra/newview/llsechandler_basic.cpp index 8acb18821c..72c7c9cde2 100644 --- a/indra/newview/llsechandler_basic.cpp +++ b/indra/newview/llsechandler_basic.cpp @@ -355,7 +355,7 @@ LLSD cert_name_from_X509_NAME(X509_NAME* name) char buffer[32]; X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, entry_index); - std::string name_value = std::string((const char*)ASN1_STRING_data(X509_NAME_ENTRY_get_data(entry)), + std::string name_value = std::string((const char*)ASN1_STRING_get0_data(X509_NAME_ENTRY_get_data(entry)), ASN1_STRING_length(X509_NAME_ENTRY_get_data(entry))); ASN1_OBJECT* name_obj = X509_NAME_ENTRY_get_object(entry); diff --git a/indra/newview/llspatialpartition.cpp b/indra/newview/llspatialpartition.cpp index 2f0a78b1b2..83ac99e085 100644 --- a/indra/newview/llspatialpartition.cpp +++ b/indra/newview/llspatialpartition.cpp @@ -1130,6 +1130,7 @@ public: virtual S32 frustumCheck(const LLViewerOctreeGroup* group) { + FSZone; S32 res = AABBInFrustumNoFarClipGroupBounds(group); if (res != 0) { @@ -1140,6 +1141,7 @@ public: virtual S32 frustumCheckObjects(const LLViewerOctreeGroup* group) { + FSZone; S32 res = AABBInFrustumNoFarClipObjectBounds(group); if (res != 0) { @@ -1150,6 +1152,7 @@ public: virtual void processGroup(LLViewerOctreeGroup* base_group) { + FSZone; LLSpatialGroup* group = (LLSpatialGroup*)base_group; if (group->needsUpdate() || group->getVisible(LLViewerCamera::sCurCameraID) < LLDrawable::getCurrentFrame() - 1) diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index a0af9608ca..65fff0fb12 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -211,7 +211,7 @@ #include "llenvironment.h" #include "llstacktrace.h" - +#include "fsperfstats.h" #if LL_WINDOWS #include "lldxhardware.h" #endif @@ -2158,6 +2158,10 @@ bool idle_startup() update_static_eyes(); // + // + gAgent.addRegionChangedCallback(boost::bind(&FSPerfStats::StatsRecorder::clearStats)); + // + // *Note: this is where gWorldMap used to be initialized. // register null callbacks for audio until the audio system is initialized @@ -4265,6 +4269,10 @@ bool process_login_success_response(U32 &first_sim_size_x, U32 &first_sim_size_y // unpack login data needed by the application text = response["agent_id"].asString(); if(!text.empty()) gAgentID.set(text); + // Performance floater initialisation + FSPerfStats::StatsRecorder::setEnabled(gSavedSettings.getBOOL("FSPerfStatsCaptureEnabled")); + FSPerfStats::StatsRecorder::setFocusAv(gAgentID); + // // gDebugInfo["AgentID"] = text; // [SL:KB] - Patch: Viewer-CrashReporting | Checked: 2010-11-16 (Catznip-2.6.0a) | Added: Catznip-2.4.0b if (gCrashSettings.getBOOL("CrashSubmitName")) diff --git a/indra/newview/llstatusbar.cpp b/indra/newview/llstatusbar.cpp index c1fdd85996..6641c03645 100644 --- a/indra/newview/llstatusbar.cpp +++ b/indra/newview/llstatusbar.cpp @@ -587,10 +587,10 @@ void LLStatusBar::refresh() // FIRE-14482: Show FPS in status bar static LLCachedControl fsStatusBarShowFPS(gSavedSettings, "FSStatusBarShowFPS"); - if (fsStatusBarShowFPS && mFPSUpdateTimer.getElapsedTimeF32() > 0.5f) + if (fsStatusBarShowFPS && mFPSUpdateTimer.getElapsedTimeF32() > 1.f) { mFPSUpdateTimer.reset(); - mFPSText->setText(llformat("%.1f", LLTrace::get_frame_recording().getPeriodMeanPerSec(LLStatViewer::FPS))); + mFPSText->setText(llformat("%.1f", LLTrace::get_frame_recording().getPeriodMedianPerSec(LLStatViewer::FPS))); } // diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index c5fe50f6c3..06dbe31ac5 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -107,7 +107,7 @@ #include "llviewerregion.h" #include "NACLantispam.h" #include "nd/ndlogthrottle.h" - +#include "fsperfstats.h" // Run Prio 0 default bento pose in the background to fix splayed hands, open mouths, etc. #include "llanimationstates.h" @@ -1073,6 +1073,40 @@ void handleDiskCacheSizeChanged(const LLSD& newValue) } // +// perrf floater stuffs +void handleTargetFPSChanged(const LLSD& newValue) +{ + const auto targetFPS = gSavedSettings.getU32("FSTargetFPS"); + FSPerfStats::targetFPS = targetFPS; +} + +// perrf floater stuffs +void handleAutoTuneFPSChanged(const LLSD& newValue) +{ + const auto newval = gSavedSettings.getBOOL("FSAutoTuneFPS"); + FSPerfStats::autoTune = newval; + if(newval && FSPerfStats::renderAvatarMaxART_ns == 0) // If we've enabled autotune we override "unlimited" to max + { + gSavedSettings.setF32("FSRenderAvatarMaxART",log10(FSPerfStats::ART_UNLIMITED_NANOS-1000));//triggers callback to update static var + } +} + +void handleRenderAvatarMaxARTChanged(const LLSD& newValue) +{ + FSPerfStats::StatsRecorder::updateRenderCostLimitFromSettings(); +} +void handleFPSTuningStrategyChanged(const LLSD& newValue) +{ + const auto newval = gSavedSettings.getU32("FSTuningFPSStrategy"); + FSPerfStats::fpsTuningStrategy = newval; +} +void handlePerformanceStatsEnabledChanged(const LLSD& newValue) +{ + const auto newval = gSavedSettings.getBOOL("FSPerfStatsCaptureEnabled"); + FSPerfStats::StatsRecorder::setEnabled(newval); +} +// + //////////////////////////////////////////////////////////////////////////// void settings_setup_listeners() @@ -1327,6 +1361,14 @@ void settings_setup_listeners() // Better asset cache size control gSavedSettings.getControl("FSDiskCacheSize")->getSignal()->connect(boost::bind(&handleDiskCacheSizeChanged, _2)); + + // perf floater controls + gSavedSettings.getControl("FSTargetFPS")->getSignal()->connect(boost::bind(&handleTargetFPSChanged, _2)); + gSavedSettings.getControl("FSAutoTuneFPS")->getSignal()->connect(boost::bind(&handleAutoTuneFPSChanged, _2)); + gSavedSettings.getControl("FSRenderAvatarMaxART")->getSignal()->connect(boost::bind(&handleRenderAvatarMaxARTChanged, _2)); + gSavedSettings.getControl("FSTuningFPSStrategy")->getSignal()->connect(boost::bind(&handleFPSTuningStrategyChanged, _2)); + gSavedSettings.getControl("FSPerfStatsCaptureEnabled")->getSignal()->connect(boost::bind(&handlePerformanceStatsEnabledChanged, _2)); + // } #if TEST_CACHED_CONTROL diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index e23d990184..fe78e0df51 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -85,6 +85,7 @@ // [/RLVa:KB] #include "llpresetsmanager.h" #include "fsdata.h" +#include "fsperfstats.h" // performance stats support extern LLPointer gStartTexture; extern bool gShiftFrame; @@ -280,6 +281,7 @@ static LLTrace::BlockTimerStatHandle FTM_EEP_UPDATE("Env Update"); // Paint the display! void display(BOOL rebuild, F32 zoom_factor, int subfield, BOOL for_snapshot) { + FSPerfStats::RecordSceneTime T (FSPerfStats::StatType_t::RENDER_DISPLAY); // render time capture - This is the main stat for overall rendering. LL_RECORD_BLOCK_TIME(FTM_RENDER); LLViewerCamera& camera = LLViewerCamera::instance(); // Factor out calls to getInstance @@ -1192,6 +1194,7 @@ void display(BOOL rebuild, F32 zoom_factor, int subfield, BOOL for_snapshot) void render_hud_attachments() { + FSPerfStats::RecordSceneTime T ( FSPerfStats::StatType_t::RENDER_HUDS); // render time capture - Primary contributor to HUDs (though these end up in render batches) gGL.matrixMode(LLRender::MM_PROJECTION); gGL.pushMatrix(); gGL.matrixMode(LLRender::MM_MODELVIEW); @@ -1399,6 +1402,7 @@ bool setup_hud_matrices(const LLRect& screen_region) void render_ui(F32 zoom_factor, int subfield) { + FSPerfStats::RecordSceneTime T ( FSPerfStats::StatType_t::RENDER_UI ); // render time capture - Primary UI stat can have HUD time overlap (TODO) LL_RECORD_BLOCK_TIME(FTM_RENDER_UI); LLGLState::checkStates(); @@ -1484,6 +1488,7 @@ static LLTrace::BlockTimerStatHandle FTM_SWAP("Swap"); void swap() { + FSPerfStats::RecordSceneTime T ( FSPerfStats::StatType_t::RENDER_SWAP ); // render time capture - Swap buffer time - can signify excessive data transfer to/from GPU LL_RECORD_BLOCK_TIME(FTM_SWAP); if (gDisplaySwapBuffers) diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 393b62d61a..029670ce6e 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -105,9 +105,12 @@ #include "llfloaterpathfindingconsole.h" #include "llfloaterpathfindinglinksets.h" #include "llfloaterpay.h" +// #include "llfloaterperformance.h" rename to fs as ll version no released +#include "fsfloaterperformance.h" #include "llfloaterperms.h" #include "llfloaterpostprocess.h" #include "llfloaterpreference.h" +#include "llfloaterpreferencesgraphicsadvanced.h" #include "llfloaterpreferenceviewadvanced.h" #include "llfloaterpreviewtrash.h" #include "llfloaterproperties.h" @@ -379,10 +382,11 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("pathfinding_linksets", "floater_pathfinding_linksets.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("pathfinding_console", "floater_pathfinding_console.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("people", "floater_people.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + //LLFloaterReg::add("performance", "floater_performance.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("perms_default", "floater_perms_default.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("places", "floater_places.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("preferences", "floater_preferences.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - LLFloaterReg::add("prefs_graphics_advanced", "floater_preferences_graphics_advanced.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + // LLFloaterReg::add("prefs_graphics_advanced", "floater_preferences_graphics_advanced.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("prefs_view_advanced", "floater_preferences_view_advanced.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("prefs_proxy", "floater_preferences_proxy.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("prefs_spellchecker_import", "floater_spellcheck_import.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); @@ -500,6 +504,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("media_lists", "floater_media_lists.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("money_tracker", "floater_fs_money_tracker.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("particle_editor","floater_particle_editor.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("performance", "floater_performance.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add(PHOTOTOOLS_FLOATER, "floater_phototools.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("phototools_camera", "floater_phototools_camera.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("publish_classified_fs", "floater_publish_classified.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); diff --git a/indra/newview/llviewerjointmesh.cpp b/indra/newview/llviewerjointmesh.cpp index d2cfe2cf98..e873057191 100644 --- a/indra/newview/llviewerjointmesh.cpp +++ b/indra/newview/llviewerjointmesh.cpp @@ -55,6 +55,7 @@ #include "m3math.h" #include "m4math.h" #include "llmatrix4a.h" +#include "fsperfstats.h" // performance stats support #if !LL_DARWIN && !LL_LINUX extern PFNGLWEIGHTPOINTERARBPROC glWeightPointerARB; @@ -222,6 +223,7 @@ int compare_int(const void *a, const void *b) //-------------------------------------------------------------------- U32 LLViewerJointMesh::drawShape( F32 pixelArea, BOOL first_pass, BOOL is_dummy) { + FSZone; if (!mValid || !mMesh || !mFace || !mVisible || !mFace->getVertexBuffer() || mMesh->getNumFaces() == 0 || @@ -230,6 +232,15 @@ U32 LLViewerJointMesh::drawShape( F32 pixelArea, BOOL first_pass, BOOL is_dummy) return 0; } + // render time capture + // TODO(Beq) This path does not appear to have attachments. Prove this then remove. + std::unique_ptr ratPtr{}; + auto vobj = mFace->getViewerObject(); + if( vobj && vobj->isAttachment() ) + { + trackAttachments( vobj, mFace->isState(LLFace::RIGGED), &ratPtr ); + } +// U32 triangle_count = 0; S32 diffuse_channel = LLDrawPoolAvatar::sDiffuseChannel; diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 726abf199e..9262e15403 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -3878,6 +3878,9 @@ bool check_avatar_render_mode(U32 mode) return FSAvatarRenderPersistence::instance().getAvatarRenderSettings(avatar->getID()) == LLVOAvatar::AV_ALWAYS_RENDER; // [/RLVa:KB] // return (avatar->getVisualMuteSettings() == LLVOAvatar::AV_ALWAYS_RENDER); + case 4: + return FSAvatarRenderPersistence::instance().getAvatarRenderSettings(avatar->getID()) != LLVOAvatar::AV_RENDER_NORMALLY; + // return FSAvatarRenderPersistence::instance().getAvatarRenderSettings(avatar->getID()) == LLVOAvatar::AV_RENDER_NORMALLY; default: return false; } @@ -3905,6 +3908,8 @@ class LLAvatarCheckImpostorMode : public view_listener_t // return (avatar->getVisualMuteSettings() == LLVOAvatar::AV_DO_NOT_RENDER); // case 2: // return (avatar->getVisualMuteSettings() == LLVOAvatar::AV_ALWAYS_RENDER); + // case 4: + // return (avatar->getVisualMuteSettings() != LLVOAvatar::AV_RENDER_NORMALLY); // default: // return false; //} diff --git a/indra/newview/llvieweroctree.cpp b/indra/newview/llvieweroctree.cpp index cdfd7c2f04..69cc810c00 100644 --- a/indra/newview/llvieweroctree.cpp +++ b/indra/newview/llvieweroctree.cpp @@ -1361,6 +1361,7 @@ bool LLViewerOctreeCull::earlyFail(LLViewerOctreeGroup* group) //virtual void LLViewerOctreeCull::traverse(const OctreeNode* n) { + FSZone; LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) n->getListener(0); if (earlyFail(group)) @@ -1371,14 +1372,17 @@ void LLViewerOctreeCull::traverse(const OctreeNode* n) if (mRes == 2 || (mRes && group->hasState(LLViewerOctreeGroup::SKIP_FRUSTUM_CHECK))) { //fully in, just add everything + FSZoneN("AllInside"); OctreeTraveler::traverse(n); } else { + FSZoneN("Check inside?") mRes = frustumCheck(group); if (mRes) { //at least partially in, run on down + FSZoneN("PartiallyIn"); OctreeTraveler::traverse(n); } diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index b8792875c3..1afd3ddd0b 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -3342,7 +3342,8 @@ BOOL LLViewerWindow::handleKey(KEY key, MASK mask) // ToUnicodeEx changes buffer state on OS below Win10, which is undesirable, // but since we already did a TranslateMessage() in gatherInput(), this // should have no negative effect - int res = ToUnicodeEx(key, 0, keyboard_state, chars, char_count, 1 << 2 /*do not modify buffer flag*/, layout); + // ToUnicodeEx works with virtual key codes + int res = ToUnicodeEx(raw_key, 0, keyboard_state, chars, char_count, 1 << 2 /*do not modify buffer flag*/, layout); if (res == 1 && chars[0] >= 0x20) { // Let it fall through to character handler and get a WM_CHAR. @@ -3819,13 +3820,13 @@ void append_xui_tooltip(LLView* viewp, LLToolTip::Params& params) } } -static LLTrace::BlockTimerStatHandle ftm("Update UI"); +static LLTrace::BlockTimerStatHandle FTM_UPDATE_UI("Update UI"); // rename to sensible symbol // Update UI based on stored mouse position from mouse-move // event processing. void LLViewerWindow::updateUI() { - LL_RECORD_BLOCK_TIME(ftm); + LL_RECORD_BLOCK_TIME(FTM_UPDATE_UI); // rename to sensible symbol static std::string last_handle_msg; diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 52a051379e..fe2ce3c8ad 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -134,6 +134,7 @@ #include "fslslbridge.h" // Movelock position refresh #include "fsdiscordconnect.h" // tapping a place that happens on landing in world to start up discord +#include "fsperfstats.h" // performance stats support extern F32 SPEED_ADJUST_MAX; extern F32 SPEED_ADJUST_MAX_SEC; @@ -601,7 +602,6 @@ bool LLVOAvatar::sLimitNonImpostors = false; // True unless RenderAvatarMaxNonIm F32 LLVOAvatar::sRenderDistance = 256.f; S32 LLVOAvatar::sNumVisibleAvatars = 0; S32 LLVOAvatar::sNumLODChangesThisFrame = 0; - // const LLUUID LLVOAvatar::sStepSoundOnLand("e8af4a28-aa83-4310-a7c4-c047e15ea0df"); - Commented out for FIRE-3169: Option to change the default footsteps sound const LLUUID LLVOAvatar::sStepSounds[LL_MCODE_END] = { @@ -2696,7 +2696,10 @@ void LLVOAvatar::idleUpdate(LLAgent &agent, const F64 &time) LL_INFOS() << "Warning! Idle on dead avatar" << LL_ENDL; return; } - + // record time and refresh "tooSlow" status + FSPerfStats::RecordAvatarTime T(getID(), FSPerfStats::StatType_t::RENDER_IDLE); // per avatar "idle" time. + updateTooSlow(); + // ; // Use LLCachedControl static LLCachedControl disable_all_render_types(gSavedSettings, "DisableAllRenderTypes"); if (!(gPipeline.hasRenderType(mIsControlAvatar ? LLPipeline::RENDER_TYPE_CONTROL_AV : LLPipeline::RENDER_TYPE_AVATAR)) @@ -2704,7 +2707,11 @@ void LLVOAvatar::idleUpdate(LLAgent &agent, const F64 &time) && !(disable_all_render_types) && !isSelf()) // { - return; + if (!mIsControlAvatar) + { + idleUpdateNameTag( mLastRootPos ); + } + return; } // Update should be happening max once per frame. @@ -2736,7 +2743,7 @@ void LLVOAvatar::idleUpdate(LLAgent &agent, const F64 &time) // } - LLScopedContextString str("avatar_idle_update " + getFullname()); + // LLScopedContextString str("avatar_idle_update " + getFullname()); // remove unused scoped string checkTextureLoading() ; @@ -3399,14 +3406,12 @@ void LLVOAvatar::idleUpdateNameTag(const LLVector3& root_pos_last) fRlvShowAvName = (fRlvShowAvTag) && (RlvActions::canShowName(RlvActions::SNC_DEFAULT, getID())); } // [/RLVa:KB] - BOOL visible_avatar = isVisible() || mNeedsAnimUpdate; BOOL visible_chat = useChatBubbles && (mChats.size() || mTyping); BOOL visible_typing = useTypingBubbles && mTyping; BOOL render_name = visible_chat || visible_typing || - (visible_avatar && // [RLVa:KB] - Checked: RLVa-2.0.1 - (fRlvShowAvTag) && + ((fRlvShowAvTag) && // [/RLVa:KB] ((sRenderName == RENDER_NAME_ALWAYS) || (sRenderName == RENDER_NAME_FADE && time_visible < NAME_SHOW_TIME))); @@ -4209,6 +4214,7 @@ void LLVOAvatar::slamPosition() bool LLVOAvatar::isVisuallyMuted() { + FSZone; // Tracy accounting for imposter testing. bool muted = false; // FIRE-11783: Always visually mute avatars that are muted @@ -4261,6 +4267,7 @@ bool LLVOAvatar::isVisuallyMuted() bool LLVOAvatar::isInMuteList() const { + FSZone; // Tracy accounting for imposter testing. bool muted = false; F64 now = LLFrameTimer::getTotalSeconds(); if (now < mCachedMuteListUpdateTime) @@ -4694,11 +4701,12 @@ void LLVOAvatar::updateFootstepSounds() void LLVOAvatar::computeUpdatePeriod() { bool visually_muted = isVisuallyMuted(); + bool slow = isTooSlowWithoutShadows();// the geometry alone is forcing this to be slow so we must imposter if (mDrawable.notNull() && isVisible() && (!isSelf() || visually_muted) && !isUIAvatar() - && (sLimitNonImpostors || visually_muted) + && (sLimitNonImpostors || visually_muted || slow) // imposter slow avatars irrespective of nonimposter setting. && !mNeedsAnimUpdate && !sFreezeCounter) { @@ -4710,8 +4718,11 @@ void LLVOAvatar::computeUpdatePeriod() const S32 UPDATE_RATE_SLOW = 64; const S32 UPDATE_RATE_MED = 48; const S32 UPDATE_RATE_FAST = 32; - - if (visually_muted) + if(slow) + { + mUpdatePeriod = UPDATE_RATE_FAST; + } + else if (visually_muted) { // visually muted avatars update at lowest rate mUpdatePeriod = UPDATE_RATE_SLOW; } @@ -5569,6 +5580,7 @@ bool LLVOAvatar::shouldAlphaMask() //----------------------------------------------------------------------------- U32 LLVOAvatar::renderSkinned() { + FSZone; // Tracy accounting for imposter testing. U32 num_indices = 0; if (!mIsBuilt) @@ -5805,6 +5817,7 @@ U32 LLVOAvatar::renderSkinned() U32 LLVOAvatar::renderTransparent(BOOL first_pass) { + FSZone; // Tracy accounting for render tracking U32 num_indices = 0; if( isWearingWearableType( LLWearableType::WT_SKIRT ) && (isUIAvatar() || isTextureVisible(TEX_SKIRT_BAKED)) ) { @@ -5857,6 +5870,7 @@ U32 LLVOAvatar::renderTransparent(BOOL first_pass) //----------------------------------------------------------------------------- U32 LLVOAvatar::renderRigid() { + FSZone; // Tracy accounting for render tracking U32 num_indices = 0; if (!mIsBuilt) @@ -5906,6 +5920,7 @@ U32 LLVOAvatar::renderRigid() U32 LLVOAvatar::renderImpostor(LLColor4U color, S32 diffuse_channel) { + FSZone; // Tracy accounting for render tracking if (!mImpostor.isComplete()) { return 0; @@ -6921,6 +6936,7 @@ const LLUUID& LLVOAvatar::getID() const LLJoint *LLVOAvatar::getJoint( const JointKey &name ) // { + FSZone; // Query by JointKey rather than just a string, the key can be a U32 index for faster lookup //joint_map_t::iterator iter = mJointMap.find( name ); @@ -9116,6 +9132,94 @@ BOOL LLVOAvatar::isFullyLoaded() const // return (mRenderUnloadedAvatar || mFullyLoaded); } +// use Avatar Render Time as complexity metric +// markARTStale - Mark stale and set the frameupdate to now so that we can wait at least one frame to get a revised number. +void LLVOAvatar::markARTStale() +{ + mARTStale=true; + mLastARTUpdateFrame = LLFrameTimer::getFrameCount(); +} + +// Udpate Avatar state based on render time +void LLVOAvatar::updateTooSlow() +{ + FSZone; + static LLCachedControl alwaysRenderFriends(gSavedSettings, "AlwaysRenderFriends"); + const auto id = getID(); + + // mTooSlow - Is the avatar flagged as being slow (includes shadow time) + // mTooSlowWithoutShadows - Is the avatar flagged as being slow even with shadows removed. + // mARTStale - the rendertime we have is stale because of an update. We need to force a re-render to re-assess slowness + + if( mARTStale ) + { + if ( LLFrameTimer::getFrameCount() - mLastARTUpdateFrame < 5 ) + { + // LL_INFOS() << this->getFullname() << " marked stale " << LL_ENDL; + // we've not had a chance to update yet (allow a few to be certain a full frame has passed) + return; + } + + mARTStale = false; + mTooSlow = false; + mTooSlowWithoutShadows = false; + // LL_INFOS() << this->getFullname() << " refreshed ART combined = " << mRenderTime << " @ " << mLastARTUpdateFrame << LL_ENDL; + } + + // Either we're not stale or we've updated. + + U64 render_time_raw; + U64 render_geom_time_raw; + + if( !mTooSlow ) + { + // we are fully rendered, so we use the live values + std::lock_guard lock{FSPerfStats::bufferToggleLock}; + render_time_raw = FSPerfStats::StatsRecorder::get(FSPerfStats::ObjType_t::OT_AVATAR, id, FSPerfStats::StatType_t::RENDER_COMBINED); + render_geom_time_raw = FSPerfStats::StatsRecorder::get(FSPerfStats::ObjType_t::OT_AVATAR, id, FSPerfStats::StatType_t::RENDER_GEOMETRY); + } + else + { + // use the cached values. + render_time_raw = mRenderTime; + render_geom_time_raw = mGeomTime; + } + if( (FSPerfStats::renderAvatarMaxART_ns > 0) && + (FSPerfStats::raw_to_ns(render_time_raw) >= FSPerfStats::renderAvatarMaxART_ns) ) + { + if( !mTooSlow ) // if we were previously not slow (with or without shadows.) + { + // if we weren't capped, we are now + mLastARTUpdateFrame = LLFrameTimer::getFrameCount(); + mRenderTime = render_time_raw; + mGeomTime = render_geom_time_raw; + mARTStale = false; + mTooSlow = true; + } + if(!mTooSlowWithoutShadows) // if we were not previously above the full impostor cap + { + bool render_friend_or_exception = ( alwaysRenderFriends && LLAvatarTracker::instance().isBuddy( id ) ) || + ( getVisualMuteSettings() == LLVOAvatar::AV_ALWAYS_RENDER ); + if(!render_friend_or_exception) + { + // Note: slow rendering Friends still get their shadows zapped. + mTooSlowWithoutShadows = (FSPerfStats::raw_to_ns(render_geom_time_raw) >= FSPerfStats::renderAvatarMaxART_ns); + } + } + } + else + { + // LL_INFOS() << this->getFullname() << " ("<< (combined?"combined":"geometry") << ") good render time = " << FSPerfStats::raw_to_ns(render_time_raw) << " vs ("<< LLVOAvatar::sRenderTimeCap_ns << " set @ " << mLastARTUpdateFrame << LL_ENDL; + mTooSlow = false; + mTooSlowWithoutShadows = false; + } + if(mTooSlow) + { + FSPerfStats::tunedAvatars++; // increment the number of avatars that have been tweaked. + } +} +// + bool LLVOAvatar::isTooComplex() const { bool too_complex; @@ -10130,7 +10234,6 @@ void LLVOAvatar::processAvatarAppearance( LLMessageSystem* mesgsys ) //bool enable_verbose_dumps = gSavedSettings.getBOOL("DebugAvatarAppearanceMessage"); static LLCachedControl enable_verbose_dumps(gSavedSettings, "DebugAvatarAppearanceMessage"); // - std::string dump_prefix = getFullname() + "_" + (isSelf()?"s":"o") + "_"; if (gSavedSettings.getBOOL("BlockAvatarAppearanceMessages")) { LL_WARNS() << "Blocking AvatarAppearance message" << LL_ENDL; @@ -10143,6 +10246,7 @@ void LLVOAvatar::processAvatarAppearance( LLMessageSystem* mesgsys ) parseAppearanceMessage(mesgsys, *contents); if (enable_verbose_dumps) { + std::string dump_prefix = getFullname() + "_" + (isSelf()?"s":"o") + "_"; dumpAppearanceMsgParams(dump_prefix + "appearance_msg", *contents); } @@ -10424,7 +10528,6 @@ void LLVOAvatar::applyParsedAppearanceMessage(LLAppearanceMessageContents& conte updateMeshTextures(); updateMeshVisibility(); - } LLViewerTexture* LLVOAvatar::getBakedTexture(const U8 te) @@ -11408,7 +11511,14 @@ void LLVOAvatar::updateImpostors() // virtual BOOL LLVOAvatar::isImpostor() { - return isVisuallyMuted() || (sLimitNonImpostors && (mUpdatePeriod > 1)); +// render time handling using tooSlow() +// return isVisuallyMuted() || (sLimitNonImpostors && (mUpdatePeriod > 1)); + return ( + isVisuallyMuted() || + isTooSlowWithoutShadows() || + (sLimitNonImpostors && (mUpdatePeriod > 1) ) + ); +// } BOOL LLVOAvatar::shouldImpostor(const F32 rank_factor) @@ -11421,6 +11531,15 @@ BOOL LLVOAvatar::shouldImpostor(const F32 rank_factor) { return true; } +// render time handling using tooSlow() + // return sLimitNonImpostors && (mVisibilityRank > sMaxNonImpostors * rank_factor); + // static LLCachedControl render_jellys_As_imposters(gSavedSettings, "RenderJellyDollsAsImpostors"); + + if (isTooSlowWithoutShadows()) + { + return true; + } +// return sLimitNonImpostors && (mVisibilityRank > sMaxNonImpostors * rank_factor); } @@ -11596,6 +11715,7 @@ void LLVOAvatar::updateVisualComplexity() LL_DEBUGS("AvatarRender") << "avatar " << getID() << " appearance changed" << LL_ENDL; // Set the cache time to in the past so it's updated ASAP mVisualComplexityStale = true; + markARTStale(); } // Account for the complexity of a single top-level object associated @@ -11607,6 +11727,7 @@ void LLVOAvatar::accountRenderComplexityForObject( LLVOVolume::texture_cost_t& textures, U32& cost, hud_complexity_list_t& hud_complexity_list, + object_complexity_list_t& object_complexity_list, // Show per-item complexity in COF std::map& item_complexity, std::map& temp_item_complexity) @@ -11629,12 +11750,12 @@ void LLVOAvatar::accountRenderComplexityForObject( F32 attachment_volume_cost = 0; F32 attachment_texture_cost = 0; F32 attachment_children_cost = 0; - const F32 animated_object_attachment_surcharge = 1000; + const F32 animated_object_attachment_surcharge = 1000; - if (attached_object->isAnimatedObject()) - { - attachment_volume_cost += animated_object_attachment_surcharge; - } + if (attached_object->isAnimatedObject()) + { + attachment_volume_cost += animated_object_attachment_surcharge; + } attachment_volume_cost += volume->getRenderCost(textures); const_child_list_t children = volume->getChildren(); @@ -11669,6 +11790,15 @@ void LLVOAvatar::accountRenderComplexityForObject( // Limit attachment complexity to avoid signed integer flipping of the wearer's ACI cost += (U32)llclamp(attachment_total_cost, MIN_ATTACHMENT_COMPLEXITY, max_attachment_complexity); + if (isSelf()) + { + LLObjectComplexity object_complexity; + object_complexity.objectName = attached_object->getAttachmentItemName(); + object_complexity.objectId = attached_object->getAttachmentItemID(); + object_complexity.objectCost = attachment_total_cost; + object_complexity_list.push_back(object_complexity); + } + // Show per-item complexity in COF if (isSelf()) { @@ -11680,8 +11810,8 @@ void LLVOAvatar::accountRenderComplexityForObject( { temp_item_complexity.insert(std::make_pair(attached_object->getID(), (U32)attachment_total_cost)); } - } // + } } } } @@ -11776,6 +11906,8 @@ void LLVOAvatar::calculateUpdateRenderComplexity() U32 cost = VISUAL_COMPLEXITY_UNKNOWN; LLVOVolume::texture_cost_t textures; hud_complexity_list_t hud_complexity_list; + object_complexity_list_t object_complexity_list; + // BOM constrain number of bake requests when BOM not supported // for (U8 baked_index = 0; baked_index < BAKED_NUM_INDICES; baked_index++) for (U8 baked_index = 0; baked_index < getNumBakes(); baked_index++) @@ -11823,8 +11955,8 @@ void LLVOAvatar::calculateUpdateRenderComplexity() { accountRenderComplexityForObject(volp, max_attachment_complexity, // Show per-item complexity in COF - //textures, cost, hud_complexity_list); - textures, cost, hud_complexity_list, item_complexity, temp_item_complexity); + //textures, cost, hud_complexity_list, object_complexity_list); + textures, cost, hud_complexity_list, object_complexity_list, item_complexity, temp_item_complexity); // } } @@ -11850,8 +11982,8 @@ void LLVOAvatar::calculateUpdateRenderComplexity() const LLViewerObject* attached_object = attachment_iter->get(); accountRenderComplexityForObject(attached_object, max_attachment_complexity, // Show per-item complexity in COF - //textures, cost, hud_complexity_list); - textures, cost, hud_complexity_list, item_complexity, temp_item_complexity); + //textures, cost, hud_complexity_list, object_complexity_list); + textures, cost, hud_complexity_list, object_complexity_list, item_complexity, temp_item_complexity); // } } @@ -11913,13 +12045,13 @@ void LLVOAvatar::calculateUpdateRenderComplexity() mVisualComplexity = cost; mVisualComplexityStale = false; - static LLCachedControl show_my_complexity_changes(gSavedSettings, "ShowMyComplexityChanges", 20); - - if (isSelf() && show_my_complexity_changes) + if (isSelf()) { // Avatar complexity LLAvatarRenderNotifier::getInstance()->updateNotificationAgent(mVisualComplexity); + LLAvatarRenderNotifier::getInstance()->setObjectComplexityList(object_complexity_list); + // HUD complexity LLHUDRenderNotifier::getInstance()->updateNotificationHUD(hud_complexity_list); } @@ -11938,6 +12070,7 @@ void LLVOAvatar::setVisualMuteSettings(VisualMuteSettings set) mVisuallyMuteSetting = set; mNeedsImpostorUpdate = TRUE; mLastImpostorUpdateReason = 7; + markARTStale();// Force a refresh of the ART to take into account new setting. // [FS Persisted Avatar Render Settings] //LLRenderMuteList::getInstance()->saveVisualMuteSetting(getID(), S32(set)); @@ -12090,7 +12223,7 @@ LLVOAvatar::AvatarOverallAppearance LLVOAvatar::getOverallAppearance() const } else // !isSelf() { - if (isInMuteList()) + if (isInMuteList()) { result = AOA_INVISIBLE; } diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index 078473be92..4b00beae29 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -311,6 +311,7 @@ public: LLVOVolume::texture_cost_t& textures, U32& cost, hud_complexity_list_t& hud_complexity_list, + object_complexity_list_t& object_complexity_list, // Show per-item complexity in COF std::map& item_complexity, std::map& temp_item_complexity); @@ -354,6 +355,7 @@ public: static F32 sPhysicsLODFactor; // user-settable physics LOD factor static BOOL sJointDebug; // output total number of joints being touched for each avatar static BOOL sDebugAvatarRotation; + static U64 sRenderTimeLimit_ns; // nanosecond time limit for avatar rendering 0 is unlimited. static LLPartSysData sCloud; //-------------------------------------------------------------------- @@ -367,6 +369,17 @@ public: //-------------------------------------------------------------------- public: BOOL isFullyLoaded() const; + // check and return current state relative to limits + // default will test only the geometry (combined=false). + // this allows us to disable shadows separately on complex avatars. + inline bool isTooSlowWithShadows() const {return mTooSlow;}; + inline bool isTooSlowWithoutShadows() const {return mTooSlowWithoutShadows;}; + inline bool isTooSlow(bool combined = false) const + { + return(combined?mTooSlow:mTooSlowWithoutShadows); + } + void updateTooSlow(); + // virtual bool isTooComplex() const; // FIRE-29012: Standalone animesh avatars get affected by complexity limit; changed to virtual bool visualParamWeightsAreDefault(); virtual bool getIsCloud() const; @@ -388,6 +401,7 @@ public: void logMetricsTimerRecord(const std::string& phase_name, F32 elapsed, bool completed); void calcMutedAVColor(); + void markARTStale(); protected: LLViewerStats::PhaseMap& getPhases() { return mPhases; } @@ -405,6 +419,15 @@ private: LLColor4 mMutedAVColor; LLFrameTimer mFullyLoadedTimer; LLFrameTimer mRuthTimer; + U32 mLastARTUpdateFrame{0}; + U64 mRenderTime{0}; + U64 mGeomTime{0}; + bool mARTStale{true}; + bool mARTCapped{false}; + // variables to hold "slowness" status + bool mTooSlow{false}; + bool mTooSlowWithoutShadows{false}; + // private: LLViewerStats::PhaseMap mPhases; @@ -1188,6 +1211,8 @@ public: // COF version of last appearance message received for this av. S32 mLastUpdateReceivedCOFVersion; + U64 getLastART() const { return mRenderTime; } + /** Diagnostics ** ** *******************************************************************************/ diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index fd9457e64f..0bec369c93 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -91,6 +91,7 @@ #include "rlvlocks.h" // [/RLVa:KB] #include "llviewernetwork.h" +#include "fsperfstats.h" // performance stats support const F32 FORCE_SIMPLE_RENDER_AREA = 512.f; const F32 FORCE_CULL_AREA = 8.f; @@ -5569,7 +5570,7 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, } } - if (type == LLRenderPass::PASS_ALPHA) + // if (type == LLRenderPass::PASS_ALPHA) // allow tracking through pipeline { //for alpha sorting facep->setDrawInfo(draw_info); } @@ -5785,6 +5786,7 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) LL_RECORD_BLOCK_TIME(FTM_REBUILD_VOLUME_FACE_LIST); //get all the faces into a list + std::unique_ptr ratPtr{}; // render time capture for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter) { @@ -5807,6 +5809,7 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) continue; } + // Stop doing stupid stuff we don;t need to. // Moving this inside a debug enabled check // std::string vobj_name = llformat("Vol%p", vobj); @@ -5817,6 +5820,12 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) { continue; } + // Capture render times + if(vobj->isAttachment()) + { + trackAttachments( vobj, drawablep->isState(LLDrawable::RIGGED),&ratPtr); + } + // LLVolume* volume = vobj->getVolume(); if (volume) @@ -6384,7 +6393,7 @@ static LLTrace::BlockTimerStatHandle FTM_REBUILD_MESH_FLUSH("Flush Mesh"); void LLVolumeGeometryManager::rebuildMesh(LLSpatialGroup* group) { llassert(group); - LL_RECORD_BLOCK_TIME(FTM_REBUILD_VOLUME_VB);// move out one scope (but are these even useful as dupes?) + // LL_RECORD_BLOCK_TIME(FTM_REBUILD_VOLUME_VB);// High volume remove (roughly 1000:1 ratio to inside the if statement) if (group && group->hasState(LLSpatialGroup::MESH_DIRTY) && !group->hasState(LLSpatialGroup::GEOM_DIRTY)) { // LL_RECORD_BLOCK_TIME(FTM_REBUILD_VOLUME_VB);// move out one scope (but are these even useful as dupes?) @@ -6399,13 +6408,21 @@ void LLVolumeGeometryManager::rebuildMesh(LLSpatialGroup* group) U32 buffer_count = 0; + std::unique_ptr ratPtr{}; // capture render times for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter) { LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable(); if (drawablep && !drawablep->isDead() && drawablep->isState(LLDrawable::REBUILD_ALL) && !drawablep->isState(LLDrawable::RIGGED) ) { + FSZoneN("Rebuild all non-Rigged") LLVOVolume* vobj = drawablep->getVOVolume(); + // capture render times + if( vobj && vobj->isAttachment() ) + { + trackAttachments( vobj, drawablep->isState(LLDrawable::RIGGED), &ratPtr ); + } + // // avoid unfortunate sleep during trylock by static check //if(debugLoggingEnabled("AnimatedObjectsLinkset")) static auto debug_logging_on = debugLoggingEnabled("AnimatedObjectsLinkset"); @@ -6818,10 +6835,18 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace U32 indices_index = 0; U16 index_offset = 0; + std::unique_ptr ratPtr; // capture render times while (face_iter < i) { //update face indices for new buffer facep = *face_iter; + LLViewerObject* vobj = facep->getViewerObject(); + // capture render times + if(vobj && vobj->isAttachment()) + { + trackAttachments(vobj, LLPipeline::sShadowRender, &ratPtr); + } + // if (buffer.isNull()) { // Bulk allocation failed @@ -7037,8 +7062,12 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace else if (is_alpha) { // can we safely treat this as an alpha mask? - if (facep->getFaceColor().mV[3] <= 0.f) + // Nothing actually sets facecolor use the TE alpha instead. + // if (facep->getFaceColor().mV[3] <= 0.f) + if (te->getAlpha() <=0.f || facep->getFaceColor().mV[3] <= 0.f) + // { //100% transparent, don't render unless we're highlighting transparent + FSZoneN("facep->alpha -> invisible"); registerFace(group, facep, LLRenderPass::PASS_ALPHA_INVISIBLE); } else if (facep->canRenderAsMask()) diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index be66720237..34d02d643f 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -370,6 +370,7 @@ bool LLPipeline::sReflectionRender = false; bool LLPipeline::sDistortionRender = false; bool LLPipeline::sImpostorRender = false; bool LLPipeline::sImpostorRenderAlphaDepthPass = false; +bool LLPipeline::sShowJellyDollAsImpostor = true; bool LLPipeline::sUnderWaterRender = false; bool LLPipeline::sTextureBindTest = false; bool LLPipeline::sRenderFrameTest = false; @@ -1195,6 +1196,7 @@ void LLPipeline::refreshCachedSettings() LLPipeline::sAutoMaskAlphaDeferred = gSavedSettings.getBOOL("RenderAutoMaskAlphaDeferred"); LLPipeline::sAutoMaskAlphaNonDeferred = gSavedSettings.getBOOL("RenderAutoMaskAlphaNonDeferred"); LLPipeline::sUseFarClip = gSavedSettings.getBOOL("RenderUseFarClip"); + LLPipeline::sShowJellyDollAsImpostor = gSavedSettings.getBOOL("RenderJellyDollsAsImpostors"); LLVOAvatar::sMaxNonImpostors = gSavedSettings.getU32("RenderAvatarMaxNonImpostors"); LLVOAvatar::updateImpostorRendering(LLVOAvatar::sMaxNonImpostors); LLPipeline::sDelayVBUpdate = gSavedSettings.getBOOL("RenderDelayVBUpdate"); @@ -2829,6 +2831,7 @@ void LLPipeline::doOcclusion(LLCamera& camera, LLRenderTarget& source, LLRenderT void LLPipeline::doOcclusion(LLCamera& camera) { + FSZoneN("doOcclusion"); if (LLPipeline::sUseOcclusion > 1 && !LLSpatialPartition::sTeleportRequested && (sCull->hasOcclusionGroups() || LLVOCachePartition::sNeedsOcclusionCheck)) { @@ -3544,6 +3547,8 @@ void LLPipeline::stateSort(LLCamera& camera, LLCullResult &result) //LLVertexBuffer::unbind(); grabReferences(result); + { + FSZoneN("checkOcclusionAndRebuildMesh"); for (LLCullResult::sg_iterator iter = sCull->beginDrawableGroups(); iter != sCull->endDrawableGroups(); ++iter) { LLSpatialGroup* group = *iter; @@ -3567,9 +3572,11 @@ void LLPipeline::stateSort(LLCamera& camera, LLCullResult &result) } } } + } if (LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD) { + FSZoneN("WorldCamera"); LLSpatialGroup* last_group = NULL; BOOL fov_changed = LLViewerCamera::getInstance()->isDefaultFOVChanged(); for (LLCullResult::bridge_iterator i = sCull->beginVisibleBridge(); i != sCull->endVisibleBridge(); ++i) @@ -3603,7 +3610,8 @@ void LLPipeline::stateSort(LLCamera& camera, LLCullResult &result) last_group->mLastUpdateDistance = last_group->mDistance; } } - + { + FSZoneN("StateSort: visible groups"); for (LLCullResult::sg_iterator iter = sCull->beginVisibleGroups(); iter != sCull->endVisibleGroups(); ++iter) { LLSpatialGroup* group = *iter; @@ -3622,7 +3630,7 @@ void LLPipeline::stateSort(LLCamera& camera, LLCullResult &result) group->rebuildMesh(); } } - } + }} { LL_RECORD_BLOCK_TIME(FTM_STATESORT_DRAWABLE); @@ -3978,6 +3986,8 @@ void LLPipeline::postSort(LLCamera& camera) LL_PUSH_CALLSTACKS(); //rebuild drawable geometry + { + FSZoneN("PostSort: rebuildGeom") for (LLCullResult::sg_iterator i = sCull->beginDrawableGroups(); i != sCull->endDrawableGroups(); ++i) { LLSpatialGroup* group = *i; @@ -3996,6 +4006,8 @@ void LLPipeline::postSort(LLCamera& camera) //build render map + { + FSZoneN("build render map"); for (LLCullResult::sg_iterator i = sCull->beginVisibleGroups(); i != sCull->endVisibleGroups(); ++i) { LLSpatialGroup* group = *i; @@ -4041,6 +4053,7 @@ void LLPipeline::postSort(LLCamera& camera) if (hasRenderType(LLPipeline::RENDER_TYPE_PASS_ALPHA)) { + FSZoneN("Collect Alpha groups"); LLSpatialGroup::draw_map_t::iterator alpha = group->mDrawMap.find(LLRenderPass::PASS_ALPHA); if (alpha != group->mDrawMap.end()) @@ -4066,6 +4079,7 @@ void LLPipeline::postSort(LLCamera& camera) } } } + } //flush particle VB if (LLVOPartGroup::sVB) @@ -4091,12 +4105,14 @@ void LLPipeline::postSort(LLCamera& camera) glBeginQueryARB(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, mMeshDirtyQueryObject); }*/ - + { + FSZoneN("rebuild delayed upd groups") } //pack vertex buffers for groups that chose to delay their updates for (LLSpatialGroup::sg_vector_t::iterator iter = mMeshDirtyGroup.begin(); iter != mMeshDirtyGroup.end(); ++iter) { (*iter)->rebuildMesh(); } + } /*if (use_transform_feedback) { @@ -4105,12 +4121,17 @@ void LLPipeline::postSort(LLCamera& camera) mMeshDirtyGroup.clear(); + { + FSZoneN("sort alpha groups") if (!sShadowRender) { std::sort(sCull->beginAlphaGroups(), sCull->endAlphaGroups(), LLSpatialGroup::CompareDepthGreater()); } + } LL_PUSH_CALLSTACKS(); + { + FSZoneN("beacon rendering flags"); // only render if the flag is set. The flag is only set if we are in edit mode or the toggle is set in the menus // Ansariel: Make beacons also show when beacons floater is closed. if (/*LLFloaterReg::instanceVisible("beacons") &&*/ !sShadowRender) @@ -4164,6 +4185,7 @@ void LLPipeline::postSort(LLCamera& camera) forAllVisibleDrawables(renderSoundHighlights); } } + } LL_PUSH_CALLSTACKS(); // If managing your telehub, draw beacons at telehub and currently selected spawnpoint. if (LLFloaterTelehub::renderBeacons()) @@ -4173,6 +4195,7 @@ void LLPipeline::postSort(LLCamera& camera) if (!sShadowRender) { + FSZoneN("Render face highlights"); mSelectedFaces.clear(); LLPipeline::setRenderHighlightTextureChannel(gFloaterTools->getPanelFace()->getTextureChannelToEdit()); @@ -11241,9 +11264,11 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar) << " is " << ( too_complex ? "" : "not ") << "too complex" << LL_ENDL; + bool too_slow = avatar->isTooSlowWithoutShadows(); // only if we really have to do we imposter. + pushRenderTypeMask(); - if (visually_muted || too_complex) + if ( !too_slow && ( visually_muted || too_complex ) ) { andRenderTypeMask(LLPipeline::RENDER_TYPE_AVATAR, LLPipeline::RENDER_TYPE_CONTROL_AV, diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h index d9c8aaef29..d8ce953187 100644 --- a/indra/newview/pipeline.h +++ b/indra/newview/pipeline.h @@ -600,6 +600,7 @@ public: static bool sDistortionRender; static bool sImpostorRender; static bool sImpostorRenderAlphaDepthPass; + static bool sShowJellyDollAsImpostor; static bool sUnderWaterRender; static bool sRenderGlow; static bool sTextureBindTest; diff --git a/indra/newview/skins/ansastorm/colors.xml b/indra/newview/skins/ansastorm/colors.xml index 1e740bf20c..a69ba44a78 100644 --- a/indra/newview/skins/ansastorm/colors.xml +++ b/indra/newview/skins/ansastorm/colors.xml @@ -1318,6 +1318,12 @@ + + + + + + diff --git a/indra/newview/skins/default/textures/toolbar_icons/performance.png b/indra/newview/skins/default/textures/toolbar_icons/performance.png new file mode 100644 index 0000000000..91baf849c8 Binary files /dev/null and b/indra/newview/skins/default/textures/toolbar_icons/performance.png differ diff --git a/indra/newview/skins/default/xui/de/floater_avatar_render_settings.xml b/indra/newview/skins/default/xui/de/floater_avatar_render_settings.xml index 6632f34a6d..b429053d7c 100644 --- a/indra/newview/skins/default/xui/de/floater_avatar_render_settings.xml +++ b/indra/newview/skins/default/xui/de/floater_avatar_render_settings.xml @@ -1,12 +1,12 @@ - + - - - - + + + + diff --git a/indra/newview/skins/default/xui/de/floater_fs_avatar_render_settings.xml b/indra/newview/skins/default/xui/de/floater_fs_avatar_render_settings.xml index 85284bc6b4..f6295d81d5 100644 --- a/indra/newview/skins/default/xui/de/floater_fs_avatar_render_settings.xml +++ b/indra/newview/skins/default/xui/de/floater_fs_avatar_render_settings.xml @@ -1,16 +1,16 @@ - + Niemals - Komplett + Immer - + + + + + Graphics settings + + + Choose settings for distance, water, lighting and more. + + + + + + Avatars nearby + + + Manage which nearby avatars are fully displayed. + + +Time spent +drawing +avatars + + + 00% + + + + + + Your avatar complexity + + + Be a good citizen. Manage the impact of your avatar + + + + + + Your active HUDs + + + Removing unnecessary HUDs may improve speed. + + +Time spent +drawing +HUDs + + + 00% + + + + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml b/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml index ad8f588908..a807789ff6 100644 --- a/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml +++ b/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml @@ -6,7 +6,7 @@ help_topic="Preferences_Graphics_Advanced" single_instance="true" save_rect="true" - title="ADVANCED GRAPHICS PREFERENCES" + title="Advanced Graphics Preferences" width="800"> diff --git a/indra/newview/skins/default/xui/en/menu_attachment_other.xml b/indra/newview/skins/default/xui/en/menu_attachment_other.xml index bcb916ff46..7bf0beb2c0 100644 --- a/indra/newview/skins/default/xui/en/menu_attachment_other.xml +++ b/indra/newview/skins/default/xui/en/menu_attachment_other.xml @@ -169,18 +169,18 @@ + name="AlwaysRenderFully" + label="Always Display Full Detail"> + parameter="2" /> + parameter="2" /> + label="Never Display Full Detail"> @@ -189,14 +189,14 @@ parameter="1" /> - + + parameter="4" /> + parameter="0" /> + name="AlwaysRenderFully" + label="Always Display Full Detail"> + parameter="2" /> + parameter="2" /> + label="Never Display Full Detail"> @@ -217,14 +217,14 @@ parameter="1" /> - + + parameter="4" /> + parameter="0" /> - - - - - + - - + + + + + + + diff --git a/indra/newview/skins/default/xui/en/menu_avatar_rendering_settings_add.xml b/indra/newview/skins/default/xui/en/menu_avatar_rendering_settings_add.xml index a9d4c6a47b..d4f77108ab 100644 --- a/indra/newview/skins/default/xui/en/menu_avatar_rendering_settings_add.xml +++ b/indra/newview/skins/default/xui/en/menu_avatar_rendering_settings_add.xml @@ -4,13 +4,13 @@ left="0" bottom="0" visible="false" mouse_opaque="false"> diff --git a/indra/newview/skins/default/xui/en/menu_fs_avatar_render_setting.xml b/indra/newview/skins/default/xui/en/menu_fs_avatar_render_setting.xml index e4d89a1042..d5cf922508 100644 --- a/indra/newview/skins/default/xui/en/menu_fs_avatar_render_setting.xml +++ b/indra/newview/skins/default/xui/en/menu_fs_avatar_render_setting.xml @@ -3,15 +3,15 @@ layout="topleft" name="Avatar Render Settings Menu"> + name="av_render_always"> + parameter="2" /> + name="av_render_normal"> + parameter="0" /> diff --git a/indra/newview/skins/default/xui/en/menu_perf_avatar_rendering_settings.xml b/indra/newview/skins/default/xui/en/menu_perf_avatar_rendering_settings.xml new file mode 100644 index 0000000000..5bde641b5c --- /dev/null +++ b/indra/newview/skins/default/xui/en/menu_perf_avatar_rendering_settings.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/menu_pie_attachment_other.xml b/indra/newview/skins/default/xui/en/menu_pie_attachment_other.xml index 8bf5ad094d..f5acadbaba 100644 --- a/indra/newview/skins/default/xui/en/menu_pie_attachment_other.xml +++ b/indra/newview/skins/default/xui/en/menu_pie_attachment_other.xml @@ -221,13 +221,16 @@ + label="Display >"> + + label="Display >"> + - + + + + + + + + Back + + +Auto Tune Preferences + + + +Distant Avatars + + + + + +Avatars that are further away still have a high impact. +Set the distance from camera beyond which an avatar will be optimized. + + + +Visibility distance tuning limits + + + + + + +When adjusting scene parameters, autotune will optimize draw distance to between the minimum and the preferred. + + diff --git a/indra/newview/skins/default/xui/en/panel_performance_complexity.xml b/indra/newview/skins/default/xui/en/panel_performance_complexity.xml new file mode 100644 index 0000000000..f8a0189469 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_performance_complexity.xml @@ -0,0 +1,116 @@ + + + + + Back + + + Avatar attachment complexity + + + Total: 50 (120000.10μs) + + + Attachments make your avatar more complex and slower to render. + + +This screen allows you to view the attachments of your own avatar. + + +You may remove your own attachments quickly and easily by hitting the 'X'. + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/panel_performance_huds.xml b/indra/newview/skins/default/xui/en/panel_performance_huds.xml new file mode 100644 index 0000000000..0b79433f75 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_performance_huds.xml @@ -0,0 +1,105 @@ + + + + + Back + + + Your active HUDs + + + Detaching HUDs you aren't using saves memory and can make the viewer run faster. + + + HUDs are often heavily scripted and also contribute to server-side lag. + + + Note: Using a HUD's minimize button does not detach it. Use the X to remove it. + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/panel_performance_nearby.xml b/indra/newview/skins/default/xui/en/panel_performance_nearby.xml new file mode 100644 index 0000000000..fdc4339635 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_performance_nearby.xml @@ -0,0 +1,273 @@ + + + + + Back + + + Avatars nearby + + Total: 50 (120000.10μs) + + + Hide the most complex avatars to boost speed. + + + + + 0 + + + + + no limit + + + + + + + + + + You can also right-click on an avatar in-world to control display. + + + + + + + + + Name tags: + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/panel_performance_preferences.xml b/indra/newview/skins/default/xui/en/panel_performance_preferences.xml new file mode 100644 index 0000000000..369615e235 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_performance_preferences.xml @@ -0,0 +1,518 @@ + + + + + Back + + + Graphics settings + + + + Quality vs speed + + +Faster + + + + + + + + + + Better Quality + + + + + + Low + + + Mid + + + High + + + Ultra + + + Choosing a preset will reset all manual changes you have made. + + + + Visibility distance + + + Faster + + + + m + + + Farther + + + Keep this low for better performance, increase it to see further afield. + + + + Environment + + +Reducing / eliminating shadows can be a boost to FPS but impacts +the ambience and appearance of the scene. + + + Shadow sources: + + + + + + + + + Water + + + Reducing water effects quality can greatly improve frame rate. + + + Water Reflections: + + + + + + + + + + + + + Photography + + +Photographers need high quality, but this is often +at the cost of frame rate. [APP_NAME] phototools can +help you find the right balance. + + + diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 652472923c..04c157de7c 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -2665,6 +2665,7 @@ Try enclosing path to the editor with double quotes. Mini-map Walk / run / fly People + Graphics speed Picks Places Preferences @@ -2730,6 +2731,7 @@ Try enclosing path to the editor with double quotes. Show nearby people (CTRL+SHIFT+M) Moving your avatar Friends, groups, and nearby people (CTRL+SHIFT+A) + Tune graphics settings Places to show as favorites in your profile Places you've saved (ALT+H) Preferences (CTRL+P) @@ -2928,7 +2930,7 @@ Try enclosing path to the editor with double quotes. -Empty list- Default (None) - No Limit + No limit The physics shape contains triangles which are too small. Try simplifying the physics model. diff --git a/indra/newview/skins/default/xui/pl/floater_avatar_render_settings.xml b/indra/newview/skins/default/xui/pl/floater_avatar_render_settings.xml index 02fee6b3a1..210e5adfc4 100644 --- a/indra/newview/skins/default/xui/pl/floater_avatar_render_settings.xml +++ b/indra/newview/skins/default/xui/pl/floater_avatar_render_settings.xml @@ -1,12 +1,12 @@ - + - - - - + + + + diff --git a/indra/newview/skins/default/xui/pl/floater_fs_avatar_render_settings.xml b/indra/newview/skins/default/xui/pl/floater_fs_avatar_render_settings.xml index a0004117ba..53e1b09282 100644 --- a/indra/newview/skins/default/xui/pl/floater_fs_avatar_render_settings.xml +++ b/indra/newview/skins/default/xui/pl/floater_fs_avatar_render_settings.xml @@ -1,5 +1,5 @@ - + Nigdy @@ -10,7 +10,7 @@ - +