From 0c999849cc7e2805bf761f234b697a4726b06599 Mon Sep 17 00:00:00 2001 From: Beq Date: Wed, 3 Nov 2021 16:26:01 +0000 Subject: [PATCH] relocated and extended perfstats Major changes in processing. More configuration options to control tweaks tweak application migrated from performance floater to service etc. --- indra/newview/fsperfstats.cpp | 344 ++++++++++++++++++++++++++ indra/newview/fsperfstats.h | 439 ++++++++++++++++++++++++++++++++++ 2 files changed, 783 insertions(+) create mode 100644 indra/newview/fsperfstats.cpp create mode 100644 indra/newview/fsperfstats.h diff --git a/indra/newview/fsperfstats.cpp b/indra/newview/fsperfstats.cpp new file mode 100644 index 0000000000..a62559c335 --- /dev/null +++ b/indra/newview/fsperfstats.cpp @@ -0,0 +1,344 @@ +/** + * @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" + +extern LLControlGroup gSavedSettings; + +namespace FSPerfStats +{ + #ifdef USAGE_TRACKING + std::atomic inUse{0}; + std::atomic inUseAvatar{0}; +#endif + std::atomic tunedAvatars{0}; + U32 targetFPS; // desired FPS + U32 renderAvatarMaxART{50000}; // highest render time we'll allow without culling features + U32 fpsTuningStrategy{0}; // linked to FSTuningFPSStrategy + U32 lastGlobalPrefChange{0}; + std::mutex bufferToggleLock{}; + bool autoTune{false}; + + U32 smoothingPeriods{1}; // number of frames to smooth over. + + + 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"); + FSPerfStats::renderAvatarMaxART = gSavedSettings.getBOOL("FSRenderAvatarMaxART"); + FSPerfStats::smoothingPeriods = gSavedSettings.getU32("FSPerfFloaterSmoothingPeriods"); + + t.detach(); + } + + // static + void StatsRecorder::toggleBuffer() + { + FSZone; + using ST = StatType_t; + + bool unreliable{false}; + + 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; + } + + 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::updateAvatarParams() + { + LLCachedControl drawDistance(gSavedSettings, "RenderFarClip"); + 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 || tot_limit_time_raw !=0 ) + { + // Note: we do not average sleep and fpslimit, therefore we cannot reliably use them here. + // 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; + } + + // LL_INFOS() << "Effective FPS:" << (1000000/FSPerfStats::raw_to_us(tot_frame_time_raw)) << " Target:" << targetFPS << LL_ENDL; + + // The frametime budget we have based on the target FPS selected + auto target_frame_time_raw = (U64)llround(get_timer_info().mClockFrequency/(targetFPS==0?1:targetFPS)); + // LL_INFOS() << "Effective FPS(raw):" << tot_frame_time_raw << " Target:" << target_frame_time_raw << LL_ENDL; + + // 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 significan t hit, this is a big boost. + if(LLPipeline::RenderReflectionDetail != -2) + { + gSavedSettings.setS32("RenderReflectionDetail", -2); + FSPerfStats::lastGlobalPrefChange = gFrameCount; + } + else // deliberately "else" here so we only do these in steps + { + // step down the DD by 10m per update + auto new_dd = (drawDistance>42)?(drawDistance - 10) : 32; + gSavedSettings.setF32("RenderFarClip", new_dd); + FSPerfStats::lastGlobalPrefChange = gFrameCount; + } + } + } + // slam the avatar time to 0 "imposter all the things" + 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. + U32 new_render_limit_us {0}; + // 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_us(av_render_max_raw) < renderAvatarMaxART) + { + new_render_limit_us = FSPerfStats::raw_to_us(av_render_max_raw); + } + else + { + new_render_limit_us = renderAvatarMaxART; + } + new_render_limit_us -= 100; + // bounce at the bottom to prevent "no limit" + if(new_render_limit_us <= 0 || new_render_limit_us >1000000) + { + new_render_limit_us = 100; + } + // assign the new value + renderAvatarMaxART = new_render_limit_us; + // LL_DEBUGS() << "AUTO_TUNE: avatar_budget adjusted to:" << new_render_limit_us << 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 << LL_ENDL; + } + else if( FSPerfStats::raw_to_us(target_frame_time_raw) > (FSPerfStats::raw_to_us(tot_frame_time_raw) + renderAvatarMaxART) ) + { + 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 += 10; + } + if(drawDistance < 180.) // TODO(Beq) make this less arbitrary + { + gSavedSettings.setF32("RenderFarClip", drawDistance + 10.); + } + if( (FSPerfStats::raw_to_us(target_frame_time_raw) * 1.5) > FSPerfStats::raw_to_us(tot_frame_time_raw) && + FSPerfStats::tunedAvatars == 0 && + drawDistance >= 128. ) + { + // 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); + } + } + } +} \ No newline at end of file diff --git a/indra/newview/fsperfstats.h b/indra/newview/fsperfstats.h new file mode 100644 index 0000000000..677d4e1419 --- /dev/null +++ b/indra/newview/fsperfstats.h @@ -0,0 +1,439 @@ +#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 "lltimer.h" +#include "blockingconcurrentqueue.h" +#include "llapp.h" +#include "fstelemetry.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; +#endif + extern std::atomic tunedAvatars; + extern U32 targetFPS; // desired FPS + extern U32 renderAvatarMaxART; + extern U32 fpsTuningStrategy; + extern U32 lastGlobalPrefChange; + extern std::mutex bufferToggleLock; + extern bool autoTune; + + extern U32 smoothingPeriods; // number of frames to smooth over. + + 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; + uint32_t count; + bool isRigged; + }; + + 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 StatsRecorder::updateAvatarParams(); + private: + StatsRecorder(); + +// 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 + static char avstr[36]; + static char obstr[36]; + 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 ) + { + // 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 + // 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( !LLApp::isExiting() ) + { + FSZone("perf batch"); + auto count = instance.q.wait_dequeue_bulk_timed(upd, 10, std::chrono::milliseconds(5)); + if(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):start{LLTimer::getCurrentClockCount()}, + stat{type, ObjTypeDiscriminator, std::move(av), std::move(id), 0, isRiggedAtt}{ + FSZoneC(tracy::Color::Orange); + #ifdef USAGE_TRACKING + if(stat.objType == FSPerfStats::ObjType_t::OT_ATTACHMENT) + { + if(FSPerfStats::inUse){FSZoneText("OVERLAP ATT",11);} + if(!stat.isRigged && FSPerfStats::inUseAvatar){FSZoneText("OVERLAP AVATAR",14);} + + FSPlot("InUse", (int64_t)FSPerfStats::inUse); + FSPlot("InUseAv", (int64_t)FSPerfStats::inUseAvatar); + FSPerfStats::inUse++; + if( !stat.isRigged ) {FSPerfStats::inUseAvatar++;}; + FSPlot("InUse", (int64_t)FSPerfStats::inUse); + FSPlot("InUseAv", (int64_t)FSPerfStats::inUseAvatar); + } + #endif + + }; + + template < typename = std::enable_if_t > + RecordTime( StatType_t type ):RecordTime(LLUUID::null, LLUUID::null, type ) + { + FSZone; + }; + + template < typename = std::enable_if_t > + 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);} + + FSPlot("InUseAv", (int64_t)FSPerfStats::inUseAvatar); + FSPerfStats::inUseAvatar++; + FSPlot("InUseAv", (int64_t)FSPerfStats::inUseAvatar); + #endif + + }; + + ~RecordTime() + { + if(!FSPerfStats::StatsRecorder::enabled()) + { + return; + } + + + FSZoneC(tracy::Color::Red); + + #ifdef USAGE_TRACKING + if (stat.objType == FSPerfStats::ObjType_t::OT_ATTACHMENT) + { + FSPlot("InUse", (int64_t)FSPerfStats::inUse); + --FSPerfStats::inUse; + FSPlot("InUse", (int64_t)FSPerfStats::inUse); + } + if( ( stat.objType == FSPerfStats::ObjType_t::OT_ATTACHMENT && !stat.isRigged ) || stat.objType == FSPerfStats::ObjType_t::OT_AVATAR ) + { + FSPlot("InUseAv", (int64_t)FSPerfStats::inUseAvatar); + --FSPerfStats::inUseAvatar; + FSPlot("InUseAv", (int64_t)FSPerfStats::inUseAvatar); + } + #endif + + stat.time = LLTimer::getCurrentClockCount() - 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) * get_timer_info().mClockFrequencyInv; }; + inline double raw_to_us(U64 raw) { return (static_cast(raw) * 1000000.0) * get_timer_info().mClockFrequencyInv; }; + inline double raw_to_ms(U64 raw) { return (static_cast(raw) * 1000.0) * get_timer_info().mClockFrequencyInv; }; + + using RecordSceneTime = RecordTime; + using RecordAvatarTime = RecordTime; + using RecordAttachmentTime = 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()); + 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 ); + } + (*ratPtrp)->stat.count++; + } + return; +}; + +#endif \ No newline at end of file