From 26efc7e376ef52284a6281f36cf45eb03bc13507 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 Sep 2024 15:25:07 -0400 Subject: Pass std::string_view by value, not by const reference. Consensus seems to be that (a) string_view is, in effect, already a reference, (b) it's small enough to make pass-by-value reasonable and (c) the optimizer can reason about values way better than it can about references. --- indra/llcommon/hexdump.h | 4 ++-- indra/llcommon/llleaplistener.cpp | 2 +- indra/llcommon/llleaplistener.h | 2 +- indra/llcommon/lua_function.cpp | 4 ++-- indra/llcommon/lua_function.h | 8 ++++---- indra/llcommon/tests/llrand_test.cpp | 2 +- indra/newview/llluamanager.cpp | 2 +- indra/newview/tests/llluamanager_test.cpp | 2 +- indra/test/namedtempfile.h | 24 ++++++++++++------------ 9 files changed, 25 insertions(+), 25 deletions(-) diff --git a/indra/llcommon/hexdump.h b/indra/llcommon/hexdump.h index ab5ba2b16d..4b734426a3 100755 --- a/indra/llcommon/hexdump.h +++ b/indra/llcommon/hexdump.h @@ -25,7 +25,7 @@ namespace LL class hexdump { public: - hexdump(const std::string_view& data): + hexdump(std::string_view data): hexdump(data.data(), data.length()) {} @@ -66,7 +66,7 @@ private: class hexmix { public: - hexmix(const std::string_view& data): + hexmix(std::string_view data): mData(data) {} diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp index b81ee66ba9..9742c9e9de 100644 --- a/indra/llcommon/llleaplistener.cpp +++ b/indra/llcommon/llleaplistener.cpp @@ -54,7 +54,7 @@ return features; } -LLLeapListener::LLLeapListener(const std::string_view& caller, const Callback& callback): +LLLeapListener::LLLeapListener(std::string_view caller, const Callback& callback): // Each LEAP plugin has an instance of this listener. Make the command // pump name difficult for other such plugins to guess. LLEventAPI(LLUUID::generateNewID().asString(), diff --git a/indra/llcommon/llleaplistener.h b/indra/llcommon/llleaplistener.h index d36d2ff8db..d38a6f4ace 100644 --- a/indra/llcommon/llleaplistener.h +++ b/indra/llcommon/llleaplistener.h @@ -28,7 +28,7 @@ public: * event is received. */ using Callback = std::function; - LLLeapListener(const std::string_view& caller, const Callback& callback); + LLLeapListener(std::string_view caller, const Callback& callback); ~LLLeapListener(); LLEventPump& getReplyPump() { return mReplyPump; } diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index eefb1e62cf..380e650360 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -999,8 +999,8 @@ LuaPopper::~LuaPopper() /***************************************************************************** * LuaFunction class *****************************************************************************/ -LuaFunction::LuaFunction(const std::string_view& name, lua_CFunction function, - const std::string_view& helptext) +LuaFunction::LuaFunction(std::string_view name, lua_CFunction function, + std::string_view helptext) { const auto& [registry, lookup] = getState(); registry.emplace(name, Registry::mapped_type{ function, helptext }); diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 967d8eaba1..12e74b5d04 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -351,7 +351,7 @@ auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value) // return to C++, from table at index, the value of field k (without metamethods) template -auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) +auto lua_rawgetfield(lua_State* L, int index, std::string_view k) { index = lua_absindex(L, index); lua_checkdelta(L); @@ -364,7 +364,7 @@ auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k) // set in table at index, as field k, the specified C++ value (without metamethods) template -void lua_rawsetfield(lua_State* L, int index, const std::string_view& k, const T& value) +void lua_rawsetfield(lua_State* L, int index, std::string_view k, const T& value) { index = lua_absindex(L, index); lua_checkdelta(L); @@ -389,8 +389,8 @@ void lua_rawsetfield(lua_State* L, int index, const std::string_view& k, const T class LuaFunction { public: - LuaFunction(const std::string_view& name, lua_CFunction function, - const std::string_view& helptext); + LuaFunction(std::string_view name, lua_CFunction function, + std::string_view helptext); static void init(lua_State* L); diff --git a/indra/llcommon/tests/llrand_test.cpp b/indra/llcommon/tests/llrand_test.cpp index a0dd4ef576..85dd53ce96 100644 --- a/indra/llcommon/tests/llrand_test.cpp +++ b/indra/llcommon/tests/llrand_test.cpp @@ -38,7 +38,7 @@ // testing extent < 0, negate the return value and the extent before passing // into ensure_in_range(). template -void ensure_in_range(const std::string_view& name, +void ensure_in_range(std::string_view name, NUMBER value, NUMBER low, NUMBER high) { auto failmsg{ stringize(name, " >= ", low, " (", value, ')') }; diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index 6a725e785f..0bd21516bc 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -65,7 +65,7 @@ lua_function(sleep, "sleep(seconds): pause the running coroutine") // This function consumes ALL Lua stack arguments and returns concatenated // message string -std::string lua_print_msg(lua_State* L, const std::string_view& level) +std::string lua_print_msg(lua_State* L, std::string_view level) { // On top of existing Lua arguments, we're going to push tostring() and // duplicate each existing stack entry so we can stringize each one. diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index 8d1333815b..f0a1b32eed 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -123,7 +123,7 @@ namespace tut } } - void from_lua(const std::string& desc, const std::string_view& construct, const LLSD& expect) + void from_lua(const std::string& desc, std::string_view construct, const LLSD& expect) { LLSD fromlua; LLStreamListener pump("testpump", diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index 8027f95728..96b19523ab 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -32,18 +32,18 @@ class NamedTempFile: public boost::noncopyable { LOG_CLASS(NamedTempFile); public: - NamedTempFile(const std::string_view& pfx, - const std::string_view& content, - const std::string_view& sfx=std::string_view("")) + NamedTempFile(std::string_view pfx, + std::string_view content, + std::string_view sfx=std::string_view("")) { createFile(pfx, [&content](std::ostream& out){ out << content; }, sfx); } // Disambiguate when passing string literal -- unclear why a string // literal should be ambiguous wrt std::string_view and Streamer - NamedTempFile(const std::string_view& pfx, + NamedTempFile(std::string_view pfx, const char* content, - const std::string_view& sfx=std::string_view("")) + std::string_view sfx=std::string_view("")) { createFile(pfx, [&content](std::ostream& out){ out << content; }, sfx); } @@ -53,9 +53,9 @@ public: // (boost::phoenix::placeholders::arg1 << "the value is " << 17 << '\n') typedef std::function Streamer; - NamedTempFile(const std::string_view& pfx, + NamedTempFile(std::string_view pfx, const Streamer& func, - const std::string_view& sfx=std::string_view("")) + std::string_view sfx=std::string_view("")) { createFile(pfx, func, sfx); } @@ -94,8 +94,8 @@ public: return out; } - static boost::filesystem::path temp_path(const std::string_view& pfx="", - const std::string_view& sfx="") + static boost::filesystem::path temp_path(std::string_view pfx="", + std::string_view sfx="") { // This variable is set by GitHub actions and is the recommended place // to put temp files belonging to an actions job. @@ -114,9 +114,9 @@ public: } protected: - void createFile(const std::string_view& pfx, + void createFile(std::string_view pfx, const Streamer& func, - const std::string_view& sfx) + std::string_view sfx) { // Create file in a temporary place. mPath = temp_path(pfx, sfx); @@ -137,7 +137,7 @@ class NamedExtTempFile: public NamedTempFile { LOG_CLASS(NamedExtTempFile); public: - NamedExtTempFile(const std::string& ext, const std::string_view& content): + NamedExtTempFile(const std::string& ext, std::string_view content): NamedTempFile(remove_dot(ext), content, ensure_dot(ext)) {} -- cgit v1.2.3 From 9f38f25b93be2566399fac2d528da9810edd2fa6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 Sep 2024 15:28:16 -0400 Subject: llinstancetracker.h was missing an #include from last merge. --- indra/llcommon/llinstancetracker.h | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index 03418e9bad..b5c681e60a 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -41,6 +41,7 @@ #include #include +#include "llprofiler.h" #include "lockstatic.h" #include "stringize.h" -- cgit v1.2.3 From dca6f0deae49d133f180ef937939b8648649fbc6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 Sep 2024 17:16:14 -0400 Subject: Fix risky signature of `wchar_to_utf8chars()`. Add `ll_convert()` alias. `wchar_to_utf8chars()` used to require a `char*` output buffer with no length, assuming that its caller knew enough to provide a buffer of sufficient length. In fact a `char[8]` buffer suffices, but nothing in the header indicated that. Eliminate the output parameter and return `std::string`. Fix the few existing callers. Also set an `ll_convert_alias` so that `ll_convert_to(llwchar)` directly calls `wchar_to_utf8chars()`. Replace instances of the workaround `wstring_to_utf8str(LLWString(1, llwchar))`. --- indra/llcommon/llstring.cpp | 20 ++++++++------------ indra/llcommon/llstring.h | 6 +++++- indra/llui/llviewereventrecorder.cpp | 11 +++++------ indra/newview/llpanelemojicomplete.cpp | 3 +-- indra/newview/llviewermedia.cpp | 3 ++- 5 files changed, 21 insertions(+), 22 deletions(-) diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 505789f9ea..4d7cf90310 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -141,10 +141,10 @@ std::string rawstr_to_utf8(const std::string& raw) return wstring_to_utf8str(wstr); } -std::ptrdiff_t wchar_to_utf8chars(llwchar in_char, char* outchars) +std::string wchar_to_utf8chars(llwchar in_char) { - U32 cur_char = (U32)in_char; - char* base = outchars; + U32 cur_char(in_char); + char buff[8], *outchars = buff; if (cur_char < 0x80) { *outchars++ = (U8)cur_char; @@ -189,7 +189,7 @@ std::ptrdiff_t wchar_to_utf8chars(llwchar in_char, char* outchars) LL_WARNS() << "Invalid Unicode character " << cur_char << "!" << LL_ENDL; *outchars++ = LL_UNKNOWN_CHAR; } - return outchars - base; + return { buff, std::string::size_type(outchars - buff) }; } auto utf16chars_to_wchar(const U16* inchars, llwchar* outchar) @@ -367,13 +367,12 @@ std::string wchar_utf8_preview(const llwchar wc) std::ostringstream oss; oss << std::hex << std::uppercase << (U32)wc; - U8 out_bytes[8]; - U32 size = (U32)wchar_to_utf8chars(wc, (char*)out_bytes); + auto out_bytes = wchar_to_utf8chars(wc); - if (size > 1) + if (out_bytes.length() > 1) { oss << " ["; - for (U32 i = 0; i < size; ++i) + for (U32 i = 0; i < out_bytes.length(); ++i) { if (i) { @@ -492,10 +491,7 @@ std::string wstring_to_utf8str(const llwchar* utf32str, size_t len) S32 i = 0; while (i < len) { - char tchars[8]; /* Flawfinder: ignore */ - auto n = wchar_to_utf8chars(utf32str[i], tchars); - tchars[n] = 0; - out += tchars; + out += wchar_to_utf8chars(utf32str[i]); i++; } return out; diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index e4be1efaed..b552aede82 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -707,7 +707,11 @@ ll_convert_forms(ll_convert_alias, LLWString, std::string, utf8str_to_ // Same function, better name. JC inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); } -LL_COMMON_API std::ptrdiff_t wchar_to_utf8chars(llwchar inchar, char* outchars); +// return a UTF-8 string representation of a single llwchar, which we +// occasionally require: +// cheaper than ll_convert_to(LLWString(1, inchar)) +LL_COMMON_API std::string wchar_to_utf8chars(llwchar inchar); +ll_convert_alias(std::string, llwchar, wchar_to_utf8chars(in)); ll_convert_forms(ll_convert_alias, std::string, LLWString, wstring_to_utf8str); ll_convert_forms(ll_convert_u16_alias, std::string, llutf16string, utf16str_to_utf8str); diff --git a/indra/llui/llviewereventrecorder.cpp b/indra/llui/llviewereventrecorder.cpp index 6d907d7e45..0a4fe5234b 100644 --- a/indra/llui/llviewereventrecorder.cpp +++ b/indra/llui/llviewereventrecorder.cpp @@ -24,9 +24,10 @@ */ -#include "llviewereventrecorder.h" -#include "llui.h" #include "llleap.h" +#include "llstring.h" +#include "llui.h" +#include "llviewereventrecorder.h" LLViewerEventRecorder::LLViewerEventRecorder() { @@ -247,11 +248,9 @@ void LLViewerEventRecorder::logKeyUnicodeEvent(llwchar uni_char) { // keycode...or // char - LL_DEBUGS() << "Wrapped in conversion to wstring " << wstring_to_utf8str(LLWString( 1, uni_char)) << "\n" << LL_ENDL; + LL_DEBUGS() << "Wrapped in conversion to wstring " << ll_convert_to(uni_char) << "\n" << LL_ENDL; - event.insert("char", - LLSD( wstring_to_utf8str(LLWString( 1,uni_char)) ) - ); + event.insert("char", LLSD(ll_convert_to(uni_char))); // path (optional) - for now we are not recording path for key events during record - should not be needed for full record and playback of recorded steps // as a vita script - it does become useful if you edit the resulting vita script and wish to remove some steps leading to a key event - that sort of edit might diff --git a/indra/newview/llpanelemojicomplete.cpp b/indra/newview/llpanelemojicomplete.cpp index cb89a5910e..7f72677e34 100644 --- a/indra/newview/llpanelemojicomplete.cpp +++ b/indra/newview/llpanelemojicomplete.cpp @@ -280,8 +280,7 @@ void LLPanelEmojiComplete::onCommit() { if (mCurSelected < mTotalEmojis) { - LLSD value(wstring_to_utf8str(LLWString(1, mEmojis[mCurSelected].Character))); - setValue(value); + setValue(ll_convert_to(mEmojis[mCurSelected].Character)); LLUICtrl::onCommit(); } } diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index 9739cac311..1c8f1a32b9 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -2766,7 +2766,8 @@ bool LLViewerMediaImpl::handleUnicodeCharHere(llwchar uni_char) { LLSD native_key_data = gViewerWindow->getWindow()->getNativeKeyData(); - mMediaSource->textInput(wstring_to_utf8str(LLWString(1, uni_char)), gKeyboard->currentMask(false), native_key_data); + mMediaSource->textInput(ll_convert_to(uni_char), + gKeyboard->currentMask(false), native_key_data); } } -- cgit v1.2.3 From 96e061547d15cb44dbe091e7aec507b2d9c87da2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 Sep 2024 17:22:35 -0400 Subject: In llstring.cpp, build result strings using basic_ostringstream. Many of the string conversion functions in llstring.cpp would build their result strings using successive concatenation operations, piece by piece. This can be expensive in allocations. Instead, use a std::basic_ostringstream of char type appropriate to the return string type to aggregate piecewise string building. --- indra/llcommon/llstring.cpp | 100 +++++++++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 42 deletions(-) diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 4d7cf90310..614075498c 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -214,7 +214,8 @@ auto utf16chars_to_wchar(const U16* inchars, llwchar* outchar) llutf16string wstring_to_utf16str(const llwchar* utf32str, size_t len) { - llutf16string out; + // ostringstream for llutf16string + std::basic_ostringstream out; S32 i = 0; while (i < len) @@ -222,16 +223,16 @@ llutf16string wstring_to_utf16str(const llwchar* utf32str, size_t len) U32 cur_char = utf32str[i]; if (cur_char > 0xFFFF) { - out += (0xD7C0 + (cur_char >> 10)); - out += (0xDC00 | (cur_char & 0x3FF)); + out.put(U16(0xD7C0 + (cur_char >> 10))); + out.put(U16(0xDC00 | (cur_char & 0x3FF))); } else { - out += cur_char; + out.put(U16(cur_char)); } i++; } - return out; + return out.str(); } llutf16string utf8str_to_utf16str( const char* utf8str, size_t len ) @@ -242,8 +243,10 @@ llutf16string utf8str_to_utf16str( const char* utf8str, size_t len ) LLWString utf16str_to_wstring(const U16* utf16str, size_t len) { - LLWString wout; - if (len == 0) return wout; + if (len == 0) return {}; + + // ostringstream for LLWString + std::basic_ostringstream wout; S32 i = 0; const U16* chars16 = utf16str; @@ -251,9 +254,9 @@ LLWString utf16str_to_wstring(const U16* utf16str, size_t len) { llwchar cur_char; i += (S32)utf16chars_to_wchar(chars16+i, &cur_char); - wout += cur_char; + wout << cur_char; } - return wout; + return wout.str(); } // Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. @@ -398,7 +401,8 @@ S32 wstring_utf8_length(const LLWString& wstr) LLWString utf8str_to_wstring(const char* utf8str, size_t len) { - LLWString wout; + // ostringstream for LLWString + std::basic_ostringstream wout; S32 i = 0; while (i < len) @@ -441,7 +445,7 @@ LLWString utf8str_to_wstring(const char* utf8str, size_t len) } else { - wout += LL_UNKNOWN_CHAR; + wout << LL_UNKNOWN_CHAR; ++i; continue; } @@ -478,23 +482,21 @@ LLWString utf8str_to_wstring(const char* utf8str, size_t len) } } - wout += unichar; + wout << unichar; ++i; } - return wout; + return wout.str(); } std::string wstring_to_utf8str(const llwchar* utf32str, size_t len) { - std::string out; + std::ostringstream out; - S32 i = 0; - while (i < len) + for (size_t i = 0; i < len; ++i) { - out += wchar_to_utf8chars(utf32str[i]); - i++; + out << wchar_to_utf8chars(utf32str[i]); } - return out; + return out.str(); } std::string utf16str_to_utf8str(const U16* utf16str, size_t len) @@ -682,7 +684,21 @@ llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t lengt std::string utf8str_showBytesUTF8(const std::string& utf8str) { - std::string result; + std::ostringstream result; + char lastchar = '\0'; + auto append = [&result, &lastchar](char c) + { + lastchar = c; + result << c; + }; + auto appends = [&result, &lastchar](const std::string& s) + { + if (! s.empty()) + { + lastchar = s.back(); + result << s; + } + }; bool in_sequence = false; size_t sequence_size = 0; @@ -691,9 +707,9 @@ std::string utf8str_showBytesUTF8(const std::string& utf8str) auto open_sequence = [&]() { - if (!result.empty() && result.back() != '\n') - result += '\n'; // Use LF as a separator before new UTF-8 sequence - result += '['; + if (lastchar != '\0' && lastchar != '\n') + append('\n'); // Use LF as a separator before new UTF-8 sequence + append('['); in_sequence = true; }; @@ -702,9 +718,9 @@ std::string utf8str_showBytesUTF8(const std::string& utf8str) llwchar unicode = utf8str_to_wchar(utf8str, byte_index - sequence_size, sequence_size); if (unicode != LL_UNKNOWN_CHAR) { - result += llformat("+%04X", unicode); + appends(llformat("+%04X", unicode)); } - result += ']'; + append(']'); in_sequence = false; sequence_size = 0; }; @@ -725,9 +741,9 @@ std::string utf8str_showBytesUTF8(const std::string& utf8str) } else // Continue the same UTF-8 sequence { - result += '.'; + append('.'); } - result += llformat("%02X", byte); // The byte is represented in hexadecimal form + appends(llformat("%02X", byte)); // The byte is represented in hexadecimal form ++sequence_size; } else // ASCII symbol is represented as a character @@ -737,10 +753,10 @@ std::string utf8str_showBytesUTF8(const std::string& utf8str) close_sequence(); if (byte != '\n') { - result += '\n'; // Use LF as a separator between UTF-8 and ASCII + append('\n'); // Use LF as a separator between UTF-8 and ASCII } } - result += byte; + append(byte); } ++byte_index; } @@ -750,7 +766,7 @@ std::string utf8str_showBytesUTF8(const std::string& utf8str) close_sequence(); } - return result; + return result.str(); } // Search for any emoji symbol, return true if found @@ -1583,7 +1599,7 @@ S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions) LL_PROFILE_ZONE_SCOPED_CATEGORY_STRING; S32 res = 0; - std::string output; + std::ostringstream output; std::vector tokens; std::string::size_type start = 0; @@ -1591,7 +1607,7 @@ S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions) std::string::size_type key_start = 0; while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos) { - output += std::string(s, prev_start, key_start-prev_start); + output << std::string(s, prev_start, key_start-prev_start); prev_start = start; bool found_replacement = false; @@ -1632,20 +1648,20 @@ S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions) if (found_replacement) { - output += replacement; + output << replacement; res++; } else { // we had no replacement, use the string as is // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-" - output += std::string(s, key_start, start-key_start); + output << std::string(s, key_start, start-key_start); } tokens.clear(); } // send the remainder of the string (with no further matches for bracketed names) - output += std::string(s, start); - s = output; + output << std::string(s, start); + s = output.str(); return res; } @@ -1661,7 +1677,7 @@ S32 LLStringUtil::format(std::string& s, const LLSD& substitutions) return res; } - std::string output; + std::ostringstream output; std::vector tokens; std::string::size_type start = 0; @@ -1669,7 +1685,7 @@ S32 LLStringUtil::format(std::string& s, const LLSD& substitutions) std::string::size_type key_start = 0; while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos) { - output += std::string(s, prev_start, key_start-prev_start); + output << std::string(s, prev_start, key_start-prev_start); prev_start = start; bool found_replacement = false; @@ -1702,20 +1718,20 @@ S32 LLStringUtil::format(std::string& s, const LLSD& substitutions) if (found_replacement) { - output += replacement; + output << replacement; res++; } else { // we had no replacement, use the string as is // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-" - output += std::string(s, key_start, start-key_start); + output << std::string(s, key_start, start-key_start); } tokens.clear(); } // send the remainder of the string (with no further matches for bracketed names) - output += std::string(s, start); - s = output; + output << std::string(s, start); + s = output.str(); return res; } -- cgit v1.2.3 From d5712689d36a1ee1af32242706901fde7229b08d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 11 Sep 2024 12:29:18 -0400 Subject: Make Develop->Render Tests->Frame Profile dump JSON to a file too. Make `LLGLSLShader::finishProfile()` accept a string pathname instead of a bool and, in addition to logging statistics to the viewer log, output statistics to that file as JSON. The calls that used to pass `emit_report=false` now pass `report_name=std::string()`. Make llviewerdisplay.cpp's `display()` function synthesize a profile filename in the viewer's logs directory, and pass that filename to `LLGLSLShader::finishProfile()`. --- indra/llrender/llglslshader.cpp | 120 +++++++++++++++++++++++-------------- indra/llrender/llglslshader.h | 5 +- indra/newview/llfeaturemanager.cpp | 2 +- indra/newview/llglsandbox.cpp | 2 +- indra/newview/llviewerdisplay.cpp | 102 +++++++++++++++++++++++-------- 5 files changed, 158 insertions(+), 73 deletions(-) diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index a157bfee21..bb734971d5 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -41,6 +41,8 @@ #include "OpenGL/OpenGL.h" #endif +#include + // Print-print list of shader included source files that are linked together via glAttachShader() // i.e. On macOS / OSX the AMD GLSL linker will display an error if a varying is left in an undefined state. #define DEBUG_SHADER_INCLUDES 0 @@ -101,9 +103,9 @@ void LLGLSLShader::initProfile() sTotalSamplesDrawn = 0; sTotalBinds = 0; - for (std::set::iterator iter = sInstances.begin(); iter != sInstances.end(); ++iter) + for (auto ptr : sInstances) { - (*iter)->clearStats(); + ptr->clearStats(); } } @@ -117,48 +119,71 @@ struct LLGLSLShaderCompareTimeElapsed }; //static -void LLGLSLShader::finishProfile(bool emit_report) +void LLGLSLShader::finishProfile(const std::string& report_name) { sProfileEnabled = false; - if (emit_report) + if (! report_name.empty()) { - std::vector sorted; - - for (std::set::iterator iter = sInstances.begin(); iter != sInstances.end(); ++iter) - { - sorted.push_back(*iter); - } - + std::vector sorted(sInstances.begin(), sInstances.end()); std::sort(sorted.begin(), sorted.end(), LLGLSLShaderCompareTimeElapsed()); + boost::json::object stats; + auto shadersit = stats.emplace("shaders", boost::json::array_kind).first; + auto& shaders = shadersit->value().as_array(); bool unbound = false; - for (std::vector::iterator iter = sorted.begin(); iter != sorted.end(); ++iter) + for (auto ptr : sorted) { - (*iter)->dumpStats(); - if ((*iter)->mBinds == 0) + if (ptr->mBinds == 0) { unbound = true; } + else + { + auto& shaderit = shaders.emplace_back(boost::json::object_kind); + ptr->dumpStats(shaderit.as_object()); + } } + constexpr float mega = 1'000'000.f; + float totalTimeMs = sTotalTimeElapsed / mega; LL_INFOS() << "-----------------------------------" << LL_ENDL; - LL_INFOS() << "Total rendering time: " << llformat("%.4f ms", sTotalTimeElapsed / 1000000.f) << LL_ENDL; - LL_INFOS() << "Total samples drawn: " << llformat("%.4f million", sTotalSamplesDrawn / 1000000.f) << LL_ENDL; - LL_INFOS() << "Total triangles drawn: " << llformat("%.3f million", sTotalTrianglesDrawn / 1000000.f) << LL_ENDL; + LL_INFOS() << "Total rendering time: " << llformat("%.4f ms", totalTimeMs) << LL_ENDL; + LL_INFOS() << "Total samples drawn: " << llformat("%.4f million", sTotalSamplesDrawn / mega) << LL_ENDL; + LL_INFOS() << "Total triangles drawn: " << llformat("%.3f million", sTotalTrianglesDrawn / mega) << LL_ENDL; LL_INFOS() << "-----------------------------------" << LL_ENDL; - + auto totalsit = stats.emplace("totals", boost::json::object_kind).first; + auto& totals = totalsit->value().as_object(); + totals.emplace("time", totalTimeMs / 1000.0); + totals.emplace("binds", sTotalBinds); + totals.emplace("samples", sTotalSamplesDrawn); + totals.emplace("triangles", sTotalTrianglesDrawn); + + auto unusedit = stats.emplace("unused", boost::json::array_kind).first; + auto& unused = unusedit->value().as_array(); if (unbound) { LL_INFOS() << "The following shaders were unused: " << LL_ENDL; - for (std::vector::iterator iter = sorted.begin(); iter != sorted.end(); ++iter) + for (auto ptr : sorted) { - if ((*iter)->mBinds == 0) + if (ptr->mBinds == 0) { - LL_INFOS() << (*iter)->mName << LL_ENDL; + LL_INFOS() << ptr->mName << LL_ENDL; + unused.emplace_back(ptr->mName); } } } + + std::ofstream outf(report_name); + if (! outf) + { + LL_WARNS() << "Couldn't write to " << std::quoted(report_name) << LL_ENDL; + } + else + { + outf << stats; + LL_INFOS() << "(also dumped to " << std::quoted(report_name) << ")" << LL_ENDL; + } } } @@ -170,36 +195,43 @@ void LLGLSLShader::clearStats() mBinds = 0; } -void LLGLSLShader::dumpStats() +void LLGLSLShader::dumpStats(boost::json::object& stats) { - if (mBinds > 0) + stats.emplace("name", mName); + auto filesit = stats.emplace("files", boost::json::array_kind).first; + auto& files = filesit->value().as_array(); + LL_INFOS() << "=============================================" << LL_ENDL; + LL_INFOS() << mName << LL_ENDL; + for (U32 i = 0; i < mShaderFiles.size(); ++i) { - LL_INFOS() << "=============================================" << LL_ENDL; - LL_INFOS() << mName << LL_ENDL; - for (U32 i = 0; i < mShaderFiles.size(); ++i) - { - LL_INFOS() << mShaderFiles[i].first << LL_ENDL; - } - LL_INFOS() << "=============================================" << LL_ENDL; + LL_INFOS() << mShaderFiles[i].first << LL_ENDL; + files.emplace_back(mShaderFiles[i].first); + } + LL_INFOS() << "=============================================" << LL_ENDL; - F32 ms = mTimeElapsed / 1000000.f; - F32 seconds = ms / 1000.f; + constexpr float mega = 1'000'000.f; + constexpr double giga = 1'000'000'000.0; + F32 ms = mTimeElapsed / mega; + F32 seconds = ms / 1000.f; - F32 pct_tris = (F32)mTrianglesDrawn / (F32)sTotalTrianglesDrawn * 100.f; - F32 tris_sec = (F32)(mTrianglesDrawn / 1000000.0); - tris_sec /= seconds; + F32 pct_tris = (F32)mTrianglesDrawn / (F32)sTotalTrianglesDrawn * 100.f; + F32 tris_sec = (F32)(mTrianglesDrawn / mega); + tris_sec /= seconds; - F32 pct_samples = (F32)((F64)mSamplesDrawn / (F64)sTotalSamplesDrawn) * 100.f; - F32 samples_sec = (F32)(mSamplesDrawn / 1000000000.0); - samples_sec /= seconds; + F32 pct_samples = (F32)((F64)mSamplesDrawn / (F64)sTotalSamplesDrawn) * 100.f; + F32 samples_sec = (F32)(mSamplesDrawn / giga); + samples_sec /= seconds; - F32 pct_binds = (F32)mBinds / (F32)sTotalBinds * 100.f; + F32 pct_binds = (F32)mBinds / (F32)sTotalBinds * 100.f; - LL_INFOS() << "Triangles Drawn: " << mTrianglesDrawn << " " << llformat("(%.2f pct of total, %.3f million/sec)", pct_tris, tris_sec) << LL_ENDL; - LL_INFOS() << "Binds: " << mBinds << " " << llformat("(%.2f pct of total)", pct_binds) << LL_ENDL; - LL_INFOS() << "SamplesDrawn: " << mSamplesDrawn << " " << llformat("(%.2f pct of total, %.3f billion/sec)", pct_samples, samples_sec) << LL_ENDL; - LL_INFOS() << "Time Elapsed: " << mTimeElapsed << " " << llformat("(%.2f pct of total, %.5f ms)\n", (F32)((F64)mTimeElapsed / (F64)sTotalTimeElapsed) * 100.f, ms) << LL_ENDL; - } + LL_INFOS() << "Triangles Drawn: " << mTrianglesDrawn << " " << llformat("(%.2f pct of total, %.3f million/sec)", pct_tris, tris_sec) << LL_ENDL; + LL_INFOS() << "Binds: " << mBinds << " " << llformat("(%.2f pct of total)", pct_binds) << LL_ENDL; + LL_INFOS() << "SamplesDrawn: " << mSamplesDrawn << " " << llformat("(%.2f pct of total, %.3f billion/sec)", pct_samples, samples_sec) << LL_ENDL; + LL_INFOS() << "Time Elapsed: " << mTimeElapsed << " " << llformat("(%.2f pct of total, %.5f ms)\n", (F32)((F64)mTimeElapsed / (F64)sTotalTimeElapsed) * 100.f, ms) << LL_ENDL; + stats.emplace("time", seconds); + stats.emplace("binds", mBinds); + stats.emplace("samples", mSamplesDrawn); + stats.emplace("triangles", mTrianglesDrawn); } //static diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h index 27c8f0b7d0..efcbaf42e8 100644 --- a/indra/llrender/llglslshader.h +++ b/indra/llrender/llglslshader.h @@ -30,6 +30,7 @@ #include "llgl.h" #include "llrender.h" #include "llstaticstringtable.h" +#include #include class LLShaderFeatures @@ -169,14 +170,14 @@ public: static U32 sMaxGLTFNodes; static void initProfile(); - static void finishProfile(bool emit_report = true); + static void finishProfile(const std::string& report_name={}); static void startProfile(); static void stopProfile(); void unload(); void clearStats(); - void dumpStats(); + void dumpStats(boost::json::object& stats); // place query objects for profiling if profiling is enabled // if for_runtime is true, will place timer query only whether or not profiling is enabled diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp index aa04221f4b..dded4d1e92 100644 --- a/indra/newview/llfeaturemanager.cpp +++ b/indra/newview/llfeaturemanager.cpp @@ -393,7 +393,7 @@ F32 logExceptionBenchmark() __except (msc_exception_filter(GetExceptionCode(), GetExceptionInformation())) { // HACK - ensure that profiling is disabled - LLGLSLShader::finishProfile(false); + LLGLSLShader::finishProfile(); // convert to C++ styled exception char integer_string[32]; diff --git a/indra/newview/llglsandbox.cpp b/indra/newview/llglsandbox.cpp index 930a8c28d9..c932312135 100644 --- a/indra/newview/llglsandbox.cpp +++ b/indra/newview/llglsandbox.cpp @@ -923,7 +923,7 @@ struct ShaderProfileHelper } ~ShaderProfileHelper() { - LLGLSLShader::finishProfile(false); + LLGLSLShader::finishProfile(); } }; diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index 9bd0973cc0..c62702e820 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -28,58 +28,69 @@ #include "llviewerdisplay.h" -#include "llgl.h" -#include "llrender.h" -#include "llglheaders.h" -#include "llgltfmateriallist.h" +#include "fsyspath.h" +#include "hexdump.h" #include "llagent.h" #include "llagentcamera.h" -#include "llviewercontrol.h" +#include "llappviewer.h" #include "llcoord.h" #include "llcriticaldamp.h" +#include "llcubemap.h" #include "lldir.h" -#include "lldynamictexture.h" #include "lldrawpoolalpha.h" +#include "lldrawpoolbump.h" +#include "lldrawpoolwater.h" +#include "lldynamictexture.h" +#include "llenvironment.h" +#include "llfasttimer.h" #include "llfeaturemanager.h" -//#include "llfirstuse.h" +#include "llfloatertools.h" +#include "llfocusmgr.h" +#include "llgl.h" +#include "llglheaders.h" +#include "llgltfmateriallist.h" #include "llhudmanager.h" #include "llimagepng.h" +#include "llmachineid.h" #include "llmemory.h" +#include "llparcel.h" +#include "llperfstats.h" +#include "llpostprocess.h" +#include "llrender.h" +#include "llscenemonitor.h" #include "llselectmgr.h" #include "llsky.h" +#include "llspatialpartition.h" #include "llstartup.h" +#include "llstartup.h" +#include "lltooldraganddrop.h" #include "lltoolfocus.h" #include "lltoolmgr.h" -#include "lltooldraganddrop.h" #include "lltoolpie.h" #include "lltracker.h" #include "lltrans.h" #include "llui.h" +#include "lluuid.h" +#include "llversioninfo.h" #include "llviewercamera.h" +#include "llviewercontrol.h" +#include "llviewernetwork.h" #include "llviewerobjectlist.h" #include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewershadermgr.h" +#include "llviewertexturelist.h" #include "llviewerwindow.h" #include "llvoavatarself.h" #include "llvograss.h" #include "llworld.h" #include "pipeline.h" -#include "llspatialpartition.h" -#include "llappviewer.h" -#include "llstartup.h" -#include "llviewershadermgr.h" -#include "llfasttimer.h" -#include "llfloatertools.h" -#include "llviewertexturelist.h" -#include "llfocusmgr.h" -#include "llcubemap.h" -#include "llviewerregion.h" -#include "lldrawpoolwater.h" -#include "lldrawpoolbump.h" -#include "llpostprocess.h" -#include "llscenemonitor.h" -#include "llenvironment.h" -#include "llperfstats.h" +#include + +#include +#include +#include extern LLPointer gStartTexture; extern bool gShiftFrame; @@ -123,6 +134,8 @@ void render_ui_3d(); void render_ui_2d(); void render_disconnected_background(); +std::string getProfileStatsFilename(); + void display_startup() { if ( !gViewerWindow @@ -1023,10 +1036,49 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) if (gShaderProfileFrame) { gShaderProfileFrame = false; - LLGLSLShader::finishProfile(); + LLGLSLShader::finishProfile(getProfileStatsFilename()); } } +std::string getProfileStatsFilename() +{ + std::ostringstream basebuff; + // viewer build + basebuff << "profile.v" << LLVersionInfo::instance().getBuild(); + // machine ID: zero-initialize unique_id in case LLMachineID fails + unsigned char unique_id[MAC_ADDRESS_BYTES]{}; + LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); + basebuff << ".m" << LL::hexdump(unique_id, sizeof(unique_id)); + // region ID + LLViewerRegion *region = gAgent.getRegion(); + basebuff << ".r" << (region? region->getRegionID() : LLUUID()); + // local parcel ID + LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); + basebuff << ".p" << (parcel? parcel->getLocalID() : 0); + // date/time -- omit seconds for now + auto now = LLDate::now(); + basebuff << ".t" << LLDate::now().toHTTPDateString("%Y-%m-%dT%H-%M-"); + // put this candidate file in our logs directory + auto base = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, basebuff.str()); + S32 sec; + now.split(nullptr, nullptr, nullptr, nullptr, nullptr, &sec); + // Loop over finished filename, incrementing sec until we find one that + // doesn't yet exist. Should rarely loop (only if successive calls within + // same second), may produce (e.g.) sec==61, but avoids collisions and + // preserves chronological filename sort order. + std::string name; + std::error_code ec; + do + { + // base + missing 2-digit seconds, append ".json" + // post-increment sec in case we have to try again + name = stringize(base, std::setw(2), std::setfill('0'), sec++, ".json"); + } while (std::filesystem::exists(fsyspath(name), ec)); + // Ignoring ec means we might potentially return a name that does already + // exist -- but if we can't check its existence, what more can we do? + return name; +} + // WIP simplified copy of display() that does minimal work void display_cube_face() { -- cgit v1.2.3 From c6e6f44f50b4de391000c5b9f781a2f0a5024e76 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 09:12:33 -0400 Subject: Give `LLGLSLShader::finishProfile()` a static default string param. `finishProfile()` is called at least once within a `__try` block. If we default its `report_name` parameter to a temporary `std::string`, that temporary must be destroyed when the stack is unwound, which `__try` forbids. --- indra/llrender/llglslshader.cpp | 1 + indra/llrender/llglslshader.h | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index bb734971d5..56f1533708 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -65,6 +65,7 @@ U64 LLGLSLShader::sTotalTimeElapsed = 0; U32 LLGLSLShader::sTotalTrianglesDrawn = 0; U64 LLGLSLShader::sTotalSamplesDrawn = 0; U32 LLGLSLShader::sTotalBinds = 0; +std::string LLGLSLShader::sDefaultReportName; //UI shader -- declared here so llui_libtest will link properly LLGLSLShader gUIProgram; diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h index efcbaf42e8..a9b9bfafa8 100644 --- a/indra/llrender/llglslshader.h +++ b/indra/llrender/llglslshader.h @@ -170,7 +170,7 @@ public: static U32 sMaxGLTFNodes; static void initProfile(); - static void finishProfile(const std::string& report_name={}); + static void finishProfile(const std::string& report_name=sDefaultReportName); static void startProfile(); static void stopProfile(); @@ -364,6 +364,11 @@ public: private: void unloadInternal(); + // This must be static because finishProfile() is called at least once + // within a __try block. If we default its report_name parameter to a + // temporary std::string, that temporary must be destroyed when the stack + // is unwound, which __try forbids. + static std::string sDefaultReportName; }; //UI shader (declared here so llui_libtest will link properly) -- cgit v1.2.3 From 0c1b3c8a38542b5aab10527cf411b57642b1f70f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 09:24:20 -0400 Subject: Specialize `std::numpunct` to fix broken MS `basic_ostream`. MSVC's `std::basic_ostream` template is not implemented in a general way: it can only be instantiated for certain specific `CHAR` types. Declaring a `std::basic_ostringstream` fails on MSVC with C2941. The ugly workaround from Stack Overflow is to clone-and-edit Microsoft's `std::numpunct` template, locally specializing it for the desired `CHAR` type. --- indra/llcommon/llstring.cpp | 158 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 614075498c..23b12bb98d 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -31,10 +31,168 @@ #include "llfasttimer.h" #include "llsd.h" #include +#include #if LL_WINDOWS #include "llwin32headerslean.h" #include // for WideCharToMultiByte + +// From https://stackoverflow.com/a/48716488: +// how to work around MSVC's broken implementation of std::basic_ostream +// (get C2941 otherwise) +// The problem is that the MSVC implementation doesn't generalize for +// arbitrary CHAR. +namespace std +{ + +// The std::numpunct<_Elem> template on which this specialization is based was +// copied 2024-09-11 from xlocnum header with versions: +// FRAMEWORK40VERSION="v4.0" +// FRAMEWORKVERSION="v4.0.30319" +// FRAMEWORKVERSION64="v4.0.30319" +// UCRTVERSION="10.0.22621.0" +// VCTOOLSVERSION="14.40.33807" +// VISUALSTUDIOVERSION="17.0" +// WINDOWSSDKLIBVERSION="10.0.22621.0" +// WINDOWSSDKVERSION="10.0.22621.0" +// Not sure which of the above versions tracks changes to xlocnum. +template <> +class numpunct : public locale::facet // facet for defining numeric punctuation text +{ +private: + friend _Tidy_guard; + +public: + using string_type = basic_string, allocator>; + using char_type = llwchar; + + static locale::id id; // unique facet id + + llwchar decimal_point() const { + return do_decimal_point(); + } + + llwchar thousands_sep() const { + return do_thousands_sep(); + } + + string grouping() const { + return do_grouping(); + } + + string_type falsename() const { + return do_falsename(); + } + + string_type truename() const { + return do_truename(); + } + + explicit numpunct(size_t _Refs = 0) : locale::facet(_Refs) { // construct from current locale + _BEGIN_LOCINFO(_Lobj) + _Init(_Lobj); + if (_Kseparator == 0) { + _Kseparator = // NB: differs from "C" locale + _Maklocchr(',', static_cast(nullptr), _Lobj._Getcvt()); + } + _END_LOCINFO() + } + + numpunct(const _Locinfo& _Lobj, size_t _Refs = 0, bool _Isdef = false) : locale::facet(_Refs) { + _Init(_Lobj, _Isdef); + } + + static size_t _Getcat(const locale::facet** _Ppf = nullptr, const locale* _Ploc = nullptr) { + // return locale category mask and construct standard facet + if (_Ppf && !*_Ppf) { + *_Ppf = new numpunct(_Locinfo(_Ploc->_C_str()), 0, true); + } + return _X_NUMERIC; + } + +protected: + __CLR_OR_THIS_CALL ~numpunct() noexcept override { + _Tidy(); + } + + numpunct(const char* _Locname, size_t _Refs = 0, bool _Isdef = false) : locale::facet(_Refs) { + _BEGIN_LOCINFO(_Lobj(_Locname)) + _Init(_Lobj, _Isdef); + _END_LOCINFO() + } + + template + void _Getvals(_Elem2, const lconv* _Ptr, _Locinfo::_Cvtvec _Cvt) { // get values + _Dp = _Maklocchr(_Ptr->decimal_point[0], static_cast<_Elem2*>(nullptr), _Cvt); + _Kseparator = _Maklocchr(_Ptr->thousands_sep[0], static_cast<_Elem2*>(nullptr), _Cvt); + } + + void _Getvals(wchar_t, const lconv* _Ptr, _Locinfo::_Cvtvec) { // get values + _Dp = static_cast(_Ptr->_W_decimal_point[0]); + _Kseparator = static_cast(_Ptr->_W_thousands_sep[0]); + } + + void _Init(const _Locinfo& _Lobj, bool _Isdef = false) { // initialize from _Lobj + const lconv* _Ptr = _Lobj._Getlconv(); + _Locinfo::_Cvtvec _Cvt = _Lobj._Getcvt(); // conversion information + + _Grouping = nullptr; + _Falsename = nullptr; + _Truename = nullptr; + + _Tidy_guard _Guard{this}; + _Grouping = _Maklocstr(_Isdef ? "" : _Ptr->grouping, static_cast(nullptr), _Lobj._Getcvt()); + _Falsename = _Maklocstr(_Lobj._Getfalse(), static_cast(nullptr), _Cvt); + _Truename = _Maklocstr(_Lobj._Gettrue(), static_cast(nullptr), _Cvt); + _Guard._Target = nullptr; + + if (_Isdef) { // apply defaults for required facets + // _Grouping = _Maklocstr("", static_cast(nullptr), _Cvt); + _Dp = _Maklocchr('.', static_cast(nullptr), _Cvt); + _Kseparator = _Maklocchr(',', static_cast(nullptr), _Cvt); + } else { + _Getvals(llwchar{}, _Ptr, _Cvt); + } + } + + virtual llwchar __CLR_OR_THIS_CALL do_decimal_point() const { + return _Dp; + } + + virtual llwchar __CLR_OR_THIS_CALL do_thousands_sep() const { + return _Kseparator; + } + + virtual string __CLR_OR_THIS_CALL do_grouping() const { + return string{_Grouping}; + } + + virtual string_type __CLR_OR_THIS_CALL do_falsename() const { + return string_type{_Falsename}; + } + + virtual string_type __CLR_OR_THIS_CALL do_truename() const { + return string_type{_Truename}; + } + +private: + void _Tidy() noexcept { // free all storage + _CSTD free(const_cast(_Grouping)); + _CSTD free(const_cast(_Falsename)); + _CSTD free(const_cast(_Truename)); + } + + const char* _Grouping; // grouping string, "" for "C" locale + llwchar _Dp; // decimal point, '.' for "C" locale + llwchar _Kseparator; // thousands separator, '\0' for "C" locale + const llwchar* _Falsename; // name for false, "false" for "C" locale + const llwchar* _Truename; // name for true, "true" for "C" locale +}; + +locale::id numpunct::id; + +} // namespace std + #endif std::string ll_safe_string(const char* in) -- cgit v1.2.3 From b898276a4f480eb5cf3fbc27bea01e2f4a261374 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 09:39:06 -0400 Subject: Work around broken MS `std::basic_ostream`. MSVC's `std::basic_ostream` template is not implemented in a general way: it can only be instantiated for certain specific `CHAR` types. Declaring a `std::basic_ostringstream` fails on MSVC with C2941. Fortunately both llstring.cpp functions that build a `LLWString` incrementally have the same characteristics: (a) they each build it one character at a time, and (b) the length of the result `LLWString` won't exceed the known length of the input string. So it works to declare a `std::vector`, `reserve()` the input length and `push_back()` individual characters. Then we can use `LLWString`'s range constructor to immediately allocate the right size. --- indra/llcommon/llstring.cpp | 187 +++++--------------------------------------- 1 file changed, 21 insertions(+), 166 deletions(-) diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 23b12bb98d..005864a843 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -36,163 +36,6 @@ #if LL_WINDOWS #include "llwin32headerslean.h" #include // for WideCharToMultiByte - -// From https://stackoverflow.com/a/48716488: -// how to work around MSVC's broken implementation of std::basic_ostream -// (get C2941 otherwise) -// The problem is that the MSVC implementation doesn't generalize for -// arbitrary CHAR. -namespace std -{ - -// The std::numpunct<_Elem> template on which this specialization is based was -// copied 2024-09-11 from xlocnum header with versions: -// FRAMEWORK40VERSION="v4.0" -// FRAMEWORKVERSION="v4.0.30319" -// FRAMEWORKVERSION64="v4.0.30319" -// UCRTVERSION="10.0.22621.0" -// VCTOOLSVERSION="14.40.33807" -// VISUALSTUDIOVERSION="17.0" -// WINDOWSSDKLIBVERSION="10.0.22621.0" -// WINDOWSSDKVERSION="10.0.22621.0" -// Not sure which of the above versions tracks changes to xlocnum. -template <> -class numpunct : public locale::facet // facet for defining numeric punctuation text -{ -private: - friend _Tidy_guard; - -public: - using string_type = basic_string, allocator>; - using char_type = llwchar; - - static locale::id id; // unique facet id - - llwchar decimal_point() const { - return do_decimal_point(); - } - - llwchar thousands_sep() const { - return do_thousands_sep(); - } - - string grouping() const { - return do_grouping(); - } - - string_type falsename() const { - return do_falsename(); - } - - string_type truename() const { - return do_truename(); - } - - explicit numpunct(size_t _Refs = 0) : locale::facet(_Refs) { // construct from current locale - _BEGIN_LOCINFO(_Lobj) - _Init(_Lobj); - if (_Kseparator == 0) { - _Kseparator = // NB: differs from "C" locale - _Maklocchr(',', static_cast(nullptr), _Lobj._Getcvt()); - } - _END_LOCINFO() - } - - numpunct(const _Locinfo& _Lobj, size_t _Refs = 0, bool _Isdef = false) : locale::facet(_Refs) { - _Init(_Lobj, _Isdef); - } - - static size_t _Getcat(const locale::facet** _Ppf = nullptr, const locale* _Ploc = nullptr) { - // return locale category mask and construct standard facet - if (_Ppf && !*_Ppf) { - *_Ppf = new numpunct(_Locinfo(_Ploc->_C_str()), 0, true); - } - return _X_NUMERIC; - } - -protected: - __CLR_OR_THIS_CALL ~numpunct() noexcept override { - _Tidy(); - } - - numpunct(const char* _Locname, size_t _Refs = 0, bool _Isdef = false) : locale::facet(_Refs) { - _BEGIN_LOCINFO(_Lobj(_Locname)) - _Init(_Lobj, _Isdef); - _END_LOCINFO() - } - - template - void _Getvals(_Elem2, const lconv* _Ptr, _Locinfo::_Cvtvec _Cvt) { // get values - _Dp = _Maklocchr(_Ptr->decimal_point[0], static_cast<_Elem2*>(nullptr), _Cvt); - _Kseparator = _Maklocchr(_Ptr->thousands_sep[0], static_cast<_Elem2*>(nullptr), _Cvt); - } - - void _Getvals(wchar_t, const lconv* _Ptr, _Locinfo::_Cvtvec) { // get values - _Dp = static_cast(_Ptr->_W_decimal_point[0]); - _Kseparator = static_cast(_Ptr->_W_thousands_sep[0]); - } - - void _Init(const _Locinfo& _Lobj, bool _Isdef = false) { // initialize from _Lobj - const lconv* _Ptr = _Lobj._Getlconv(); - _Locinfo::_Cvtvec _Cvt = _Lobj._Getcvt(); // conversion information - - _Grouping = nullptr; - _Falsename = nullptr; - _Truename = nullptr; - - _Tidy_guard _Guard{this}; - _Grouping = _Maklocstr(_Isdef ? "" : _Ptr->grouping, static_cast(nullptr), _Lobj._Getcvt()); - _Falsename = _Maklocstr(_Lobj._Getfalse(), static_cast(nullptr), _Cvt); - _Truename = _Maklocstr(_Lobj._Gettrue(), static_cast(nullptr), _Cvt); - _Guard._Target = nullptr; - - if (_Isdef) { // apply defaults for required facets - // _Grouping = _Maklocstr("", static_cast(nullptr), _Cvt); - _Dp = _Maklocchr('.', static_cast(nullptr), _Cvt); - _Kseparator = _Maklocchr(',', static_cast(nullptr), _Cvt); - } else { - _Getvals(llwchar{}, _Ptr, _Cvt); - } - } - - virtual llwchar __CLR_OR_THIS_CALL do_decimal_point() const { - return _Dp; - } - - virtual llwchar __CLR_OR_THIS_CALL do_thousands_sep() const { - return _Kseparator; - } - - virtual string __CLR_OR_THIS_CALL do_grouping() const { - return string{_Grouping}; - } - - virtual string_type __CLR_OR_THIS_CALL do_falsename() const { - return string_type{_Falsename}; - } - - virtual string_type __CLR_OR_THIS_CALL do_truename() const { - return string_type{_Truename}; - } - -private: - void _Tidy() noexcept { // free all storage - _CSTD free(const_cast(_Grouping)); - _CSTD free(const_cast(_Falsename)); - _CSTD free(const_cast(_Truename)); - } - - const char* _Grouping; // grouping string, "" for "C" locale - llwchar _Dp; // decimal point, '.' for "C" locale - llwchar _Kseparator; // thousands separator, '\0' for "C" locale - const llwchar* _Falsename; // name for false, "false" for "C" locale - const llwchar* _Truename; // name for true, "true" for "C" locale -}; - -locale::id numpunct::id; - -} // namespace std - #endif std::string ll_safe_string(const char* in) @@ -403,8 +246,14 @@ LLWString utf16str_to_wstring(const U16* utf16str, size_t len) { if (len == 0) return {}; - // ostringstream for LLWString - std::basic_ostringstream wout; + // MS doesn't support std::basic_ostringstream; have to work + // around it. + std::vector wout; + // We want to minimize allocations. We don't know how many llwchars we'll + // generate from this utf16str, but we do know the length should be at + // most len. So if we reserve 'len' llwchars, we shouldn't need to expand + // wout incrementally. + wout.reserve(len); S32 i = 0; const U16* chars16 = utf16str; @@ -412,9 +261,9 @@ LLWString utf16str_to_wstring(const U16* utf16str, size_t len) { llwchar cur_char; i += (S32)utf16chars_to_wchar(chars16+i, &cur_char); - wout << cur_char; + wout.push_back(cur_char); } - return wout.str(); + return { wout.begin(), wout.end() }; } // Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. @@ -559,8 +408,14 @@ S32 wstring_utf8_length(const LLWString& wstr) LLWString utf8str_to_wstring(const char* utf8str, size_t len) { - // ostringstream for LLWString - std::basic_ostringstream wout; + // MS doesn't support std::basic_ostringstream; have to work + // around it. + std::vector wout; + // We want to minimize allocations. We don't know how many llwchars we'll + // generate from this utf8str, but we do know the length should be at most + // len. So if we reserve 'len' llwchars, we shouldn't need to expand wout + // incrementally. + wout.reserve(len); S32 i = 0; while (i < len) @@ -603,7 +458,7 @@ LLWString utf8str_to_wstring(const char* utf8str, size_t len) } else { - wout << LL_UNKNOWN_CHAR; + wout.push_back(LL_UNKNOWN_CHAR); ++i; continue; } @@ -640,10 +495,10 @@ LLWString utf8str_to_wstring(const char* utf8str, size_t len) } } - wout << unichar; + wout.push_back(unichar); ++i; } - return wout.str(); + return { wout.begin(), wout.end() }; } std::string wstring_to_utf8str(const llwchar* utf32str, size_t len) -- cgit v1.2.3 From f789bf05053c99d550e6d95e9a512c984ea43ed5 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 10:39:05 -0400 Subject: Populate the viewer package's lua/auto subdir as well as require. --- indra/newview/viewer_manifest.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index aa2b0b0e25..c403b57813 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -169,8 +169,9 @@ class ViewerManifest(LLManifest): with self.prefix(src_dst="scripts/lua"): self.path("*.lua") self.path("*.xml") - with self.prefix(src_dst='require'): - self.path("*.lua") + for subdir in 'require', 'auto': + with self.prefix(src_dst=subdir): + self.path("*.lua") #build_data.json. Standard with exception handling is fine. If we can't open a new file for writing, we have worse problems #platform is computed above with other arg parsing -- cgit v1.2.3 From 034d13bcd77c3cbba00da1ef6c3c59d22f4a689e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 10:39:26 -0400 Subject: Disable happy-path destructor semantics when unwinding C++ stack. If the C++ runtime is already handling an exception, don't try to launch more Lua operations. --- indra/llcommon/lua_function.cpp | 8 +++++++- indra/llcommon/lua_function.h | 5 ++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 380e650360..2557fd0cc9 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -709,6 +709,11 @@ int lua_metaipair(lua_State* L) LuaState::~LuaState() { + // If we're unwinding the stack due to an exception, don't bother trying + // to call any callbacks -- either Lua or C++. + if (std::uncaught_exceptions() != 0) + return; + /*---------------------------- feature flag ----------------------------*/ if (mFeature) /*---------------------------- feature flag ----------------------------*/ @@ -990,7 +995,8 @@ lua_function(atexit, "atexit(function): " *****************************************************************************/ LuaPopper::~LuaPopper() { - if (mCount) + // If we're unwinding the C++ stack due to an exception, don't pop! + if (std::uncaught_exceptions() == 0 && mCount) { lua_pop(mState, mCount); } diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index 12e74b5d04..10c201c234 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -172,7 +172,10 @@ public: LuaRemover& operator=(const LuaRemover&) = delete; ~LuaRemover() { - lua_remove(mState, mIndex); + // If we're unwinding the C++ stack due to an exception, don't mess + // with the Lua stack! + if (std::uncaught_exceptions() == 0) + lua_remove(mState, mIndex); } private: -- cgit v1.2.3 From 4ae12a08265998055627a0530f6e7ef8da5ca2ed Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 11:05:24 -0400 Subject: Add LLAgent.teleport() Lua function that wraps existing "LLTeleportHandler" LEAP listener. --- indra/newview/scripts/lua/require/LLAgent.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/indra/newview/scripts/lua/require/LLAgent.lua b/indra/newview/scripts/lua/require/LLAgent.lua index 07ef1e0b0b..9eebede59f 100644 --- a/indra/newview/scripts/lua/require/LLAgent.lua +++ b/indra/newview/scripts/lua/require/LLAgent.lua @@ -71,4 +71,12 @@ function LLAgent.getAnimationInfo(item_id) return leap.request('LLAgent', {op = 'getAnimationInfo', item_id=item_id}).anim_info end +-- Teleport to specified "regionname" at specified region-relative "x", "y", "z". +-- If "regionname" omitted, teleport to GLOBAL coordinates "x", "y", "z". +function LLAgent.teleport(...) + local args = mapargs('regionname,x,y,z', ...) + args.op = 'teleport' + return leap.request('LLTeleportHandler', args) +end + return LLAgent -- cgit v1.2.3 From 499c62637f487b673bac86be566588a8ccde388d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 11:14:40 -0400 Subject: Recursively package all of indra/newview/scripts/lua. Instead of trying to continue mirroring the lua subdirectory structure in viewer_manifest.py, and enumerating the relevant file extensions, just pack up the whole subtree. --- indra/newview/viewer_manifest.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index c403b57813..9f77dba3bc 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -166,12 +166,7 @@ class ViewerManifest(LLManifest): self.path("*/*/*/*.js") self.path("*/*/*.html") - with self.prefix(src_dst="scripts/lua"): - self.path("*.lua") - self.path("*.xml") - for subdir in 'require', 'auto': - with self.prefix(src_dst=subdir): - self.path("*.lua") + self.path('scripts/lua') #build_data.json. Standard with exception handling is fine. If we can't open a new file for writing, we have worse problems #platform is computed above with other arg parsing -- cgit v1.2.3 From a68bb4eb27573a9313169799a188dda3ae765666 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 12:27:08 -0400 Subject: Support "LLTeleportHandler" "teleport" regionname="home". --- indra/newview/llurldispatcher.cpp | 9 ++++++++- indra/newview/scripts/lua/require/LLAgent.lua | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/indra/newview/llurldispatcher.cpp b/indra/newview/llurldispatcher.cpp index 39a9f0f8bc..166542324d 100644 --- a/indra/newview/llurldispatcher.cpp +++ b/indra/newview/llurldispatcher.cpp @@ -289,6 +289,8 @@ public: LLEventAPI::add("teleport", "Teleport to specified [\"regionname\"] at\n" "specified region-relative [\"x\"], [\"y\"], [\"z\"].\n" + "If [\"regionname\"] is \"home\", ignore [\"x\"], [\"y\"], [\"z\"]\n" + "and teleport home.\n" "If [\"regionname\"] omitted, teleport to GLOBAL\n" "coordinates [\"x\"], [\"y\"], [\"z\"].", &LLTeleportHandler::from_event); @@ -328,7 +330,12 @@ public: void from_event(const LLSD& params) const { Response response(LLSD(), params); - if (params.has("regionname")) + if (params["regionname"].asString() == "home") + { + gAgent.teleportHome(); + response["message"] = "Teleporting home"; + } + else if (params.has("regionname")) { // region specified, coordinates (if any) are region-local LLVector3 local_pos( diff --git a/indra/newview/scripts/lua/require/LLAgent.lua b/indra/newview/scripts/lua/require/LLAgent.lua index 9eebede59f..5cee998fcd 100644 --- a/indra/newview/scripts/lua/require/LLAgent.lua +++ b/indra/newview/scripts/lua/require/LLAgent.lua @@ -72,11 +72,12 @@ function LLAgent.getAnimationInfo(item_id) end -- Teleport to specified "regionname" at specified region-relative "x", "y", "z". +-- If "regionname" is "home", ignore "x", "y", "z" and teleport home. -- If "regionname" omitted, teleport to GLOBAL coordinates "x", "y", "z". function LLAgent.teleport(...) local args = mapargs('regionname,x,y,z', ...) args.op = 'teleport' - return leap.request('LLTeleportHandler', args) + return leap.request('LLTeleportHandler', args).message end return LLAgent -- cgit v1.2.3 From ab3083819793a30911354670a7929b0d3f7c104c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 12:28:05 -0400 Subject: Add a JSON frame profile stats file pretty-printer script. --- scripts/perf/profile_pretty.py | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 scripts/perf/profile_pretty.py diff --git a/scripts/perf/profile_pretty.py b/scripts/perf/profile_pretty.py new file mode 100644 index 0000000000..ca52fe366a --- /dev/null +++ b/scripts/perf/profile_pretty.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""\ +@file profile_pretty.py +@author Nat Goodspeed +@date 2024-09-12 +@brief Pretty-print a JSON file from Develop -> Render Tests -> Frame Profile + +$LicenseInfo:firstyear=2024&license=viewerlgpl$ +Copyright (c) 2024, Linden Research, Inc. +$/LicenseInfo$ +""" + +import logsdir +import json +from pathlib import Path +import sys + +class Error(Exception): + pass + +def pretty(path=None): + if not path: + logs = logsdir.logsdir() + profiles = Path(logs).glob('profile.*.json') + sort = [(p.stat().st_mtime, p) for p in profiles] + sort.sort(reverse=True) + try: + path = sort[0][1] + except IndexError: + raise Error(f'No profile.*.json files in {logs}') + # print path to sys.stderr in case user is redirecting stdout + print(path, file=sys.stderr) + + with open(path) as inf: + data = json.load(inf) + json.dump(data, sys.stdout, indent=4) + +def main(*raw_args): + from argparse import ArgumentParser + parser = ArgumentParser(description=""" +%(prog)s pretty-prints a JSON file from Develop -> Render Tests -> Frame Profile. +The file produced by the viewer is a single dense line of JSON. +""") + parser.add_argument('path', nargs='?', + help="""profile filename to pretty-print (default is most recent)""") + + args = parser.parse_args(raw_args) + pretty(args.path) + +if __name__ == "__main__": + try: + sys.exit(main(*sys.argv[1:])) + except (Error, OSError, json.JSONDecodeError) as err: + sys.exit(str(err)) -- cgit v1.2.3 From d60b1f92213ace6a8ab6a4a60cb01a43f45d3955 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 13:43:36 -0400 Subject: Add script to convert frame profile JSON file to CSV. Also slightly refactor profile_pretty.py. --- scripts/perf/profile_csv.py | 72 ++++++++++++++++++++++++++++++++++++++++++ scripts/perf/profile_pretty.py | 26 +++++++-------- 2 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 scripts/perf/profile_csv.py diff --git a/scripts/perf/profile_csv.py b/scripts/perf/profile_csv.py new file mode 100644 index 0000000000..273e3b7434 --- /dev/null +++ b/scripts/perf/profile_csv.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""\ +@file profile_csv.py +@author Nat Goodspeed +@date 2024-09-12 +@brief Convert a JSON file from Develop -> Render Tests -> Frame Profile to CSV + +$LicenseInfo:firstyear=2024&license=viewerlgpl$ +Copyright (c) 2024, Linden Research, Inc. +$/LicenseInfo$ +""" + +import logsdir +import json +from pathlib import Path +import sys + +class Error(Exception): + pass + +def convert(path, totals=True, unused=True, file=sys.stdout): + with open(path) as inf: + data = json.load(inf) + print('"name", "file1", "file2", "time", "binds", "samples", "triangles"', file=file) + + if totals: + t = data['totals'] + print(f'"totals", "", "", {t["time"]}, {t["binds"]}, {t["samples"]}, {t["triangles"]}', + file=file) + + for sh in data['shaders']: + print(f'"{sh["name"]}", "{sh["files"][0]}", "{sh["files"][1]}", ' + f'{sh["time"]}, {sh["binds"]}, {sh["samples"]}, {sh["triangles"]}', file=file) + + if unused: + for u in data['unused']: + print(f'"{u}", "", "", 0, 0, 0, 0', file=file) + +def main(*raw_args): + from argparse import ArgumentParser + parser = ArgumentParser(description=""" +%(prog)s converts a JSON file from Develop -> Render Tests -> Frame Profile to +a more-or-less equivalent CSV file. It expands the totals stats and unused +shaders list to full shaders lines. +""") + parser.add_argument('-t', '--totals', action='store_false', default=True, + help="""omit totals from CSV file""") + parser.add_argument('-u', '--unused', action='store_false', default=True, + help="""omit unused shaders from CSV file""") + parser.add_argument('path', nargs='?', + help="""profile filename to convert (default is most recent)""") + + args = parser.parse_args(raw_args) + if not args.path: + logs = logsdir.logsdir() + profiles = Path(logs).glob('profile.*.json') + sort = [(p.stat().st_mtime, p) for p in profiles] + sort.sort(reverse=True) + try: + args.path = sort[0][1] + except IndexError: + raise Error(f'No profile.*.json files in {logs}') + # print path to sys.stderr in case user is redirecting stdout + print(args.path, file=sys.stderr) + + convert(args.path, totals=args.totals, unused=args.unused) + +if __name__ == "__main__": + try: + sys.exit(main(*sys.argv[1:])) + except (Error, OSError, json.JSONDecodeError) as err: + sys.exit(str(err)) diff --git a/scripts/perf/profile_pretty.py b/scripts/perf/profile_pretty.py index ca52fe366a..15b6efd94d 100644 --- a/scripts/perf/profile_pretty.py +++ b/scripts/perf/profile_pretty.py @@ -18,19 +18,7 @@ import sys class Error(Exception): pass -def pretty(path=None): - if not path: - logs = logsdir.logsdir() - profiles = Path(logs).glob('profile.*.json') - sort = [(p.stat().st_mtime, p) for p in profiles] - sort.sort(reverse=True) - try: - path = sort[0][1] - except IndexError: - raise Error(f'No profile.*.json files in {logs}') - # print path to sys.stderr in case user is redirecting stdout - print(path, file=sys.stderr) - +def pretty(path): with open(path) as inf: data = json.load(inf) json.dump(data, sys.stdout, indent=4) @@ -45,6 +33,18 @@ The file produced by the viewer is a single dense line of JSON. help="""profile filename to pretty-print (default is most recent)""") args = parser.parse_args(raw_args) + if not args.path: + logs = logsdir.logsdir() + profiles = Path(logs).glob('profile.*.json') + sort = [(p.stat().st_mtime, p) for p in profiles] + sort.sort(reverse=True) + try: + args.path = sort[0][1] + except IndexError: + raise Error(f'No profile.*.json files in {logs}') + # print path to sys.stderr in case user is redirecting stdout + print(args.path, file=sys.stderr) + pretty(args.path) if __name__ == "__main__": -- cgit v1.2.3 From 6c6a42fbbc231bad6a7921c37cdcb82d17dc225f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 13:46:30 -0400 Subject: Let test_animation.lua cope with the case of 0 animations. --- indra/newview/scripts/lua/test_animation.lua | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/indra/newview/scripts/lua/test_animation.lua b/indra/newview/scripts/lua/test_animation.lua index c16fef4918..37e7254a6c 100644 --- a/indra/newview/scripts/lua/test_animation.lua +++ b/indra/newview/scripts/lua/test_animation.lua @@ -11,18 +11,22 @@ for key in pairs(anims) do table.insert(anim_ids, key) end --- Start playing a random animation -math.randomseed(os.time()) -local random_id = anim_ids[math.random(#anim_ids)] -local anim_info = LLAgent.getAnimationInfo(random_id) +if #anim_ids == 0 then + print("No animations found") +else + -- Start playing a random animation + math.randomseed(os.time()) + local random_id = anim_ids[math.random(#anim_ids)] + local anim_info = LLAgent.getAnimationInfo(random_id) -print("Starting animation locally: " .. anims[random_id].name) -print("Loop: " .. anim_info.is_loop .. " Joints: " .. anim_info.num_joints .. " Duration " .. tonumber(string.format("%.2f", anim_info.duration))) -LLAgent.playAnimation{item_id=random_id} + print("Starting animation locally: " .. anims[random_id].name) + print("Loop: " .. anim_info.is_loop .. " Joints: " .. anim_info.num_joints .. " Duration " .. tonumber(string.format("%.2f", anim_info.duration))) + LLAgent.playAnimation{item_id=random_id} --- Stop animation after 3 sec if it's looped or longer than 3 sec -if anim_info.is_loop == 1 or anim_info.duration > 3 then - LL.sleep(3) - print("Stop animation.") - LLAgent.stopAnimation(random_id) + -- Stop animation after 3 sec if it's looped or longer than 3 sec + if anim_info.is_loop == 1 or anim_info.duration > 3 then + LL.sleep(3) + print("Stop animation.") + LLAgent.stopAnimation(random_id) + end end -- cgit v1.2.3 From 2b851fbd0b4f72f46ec68965b3e06451e20a5b39 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 15:08:54 -0400 Subject: Mediate "LLAppViewer" "userQuit" et al. via "mainloop" WorkQueue. Empirically, this works better than engaging the respective LLAppViewer methods directly. --- indra/newview/llappviewerlistener.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/indra/newview/llappviewerlistener.cpp b/indra/newview/llappviewerlistener.cpp index d02b1b4a79..4690c91b61 100644 --- a/indra/newview/llappviewerlistener.cpp +++ b/indra/newview/llappviewerlistener.cpp @@ -35,6 +35,7 @@ // external library headers // other Linden headers #include "llappviewer.h" +#include "workqueue.h" LLAppViewerListener::LLAppViewerListener(const LLAppViewerGetter& getter): LLEventAPI("LLAppViewer", @@ -56,17 +57,23 @@ LLAppViewerListener::LLAppViewerListener(const LLAppViewerGetter& getter): void LLAppViewerListener::userQuit(const LLSD& event) { LL_INFOS() << "Listener requested user quit" << LL_ENDL; - mAppViewerGetter()->userQuit(); + // Trying to engage this from (e.g.) a Lua-hosting C++ coroutine runs + // afoul of an assert in the logging machinery that LLMutex must be locked + // only from the main coroutine. + LL::WorkQueue::getInstance("mainloop")->post( + [appviewer=mAppViewerGetter()]{ appviewer->userQuit(); }); } void LLAppViewerListener::requestQuit(const LLSD& event) { LL_INFOS() << "Listener requested quit" << LL_ENDL; - mAppViewerGetter()->requestQuit(); + LL::WorkQueue::getInstance("mainloop")->post( + [appviewer=mAppViewerGetter()]{ appviewer->requestQuit(); }); } void LLAppViewerListener::forceQuit(const LLSD& event) { LL_INFOS() << "Listener requested force quit" << LL_ENDL; - mAppViewerGetter()->forceQuit(); + LL::WorkQueue::getInstance("mainloop")->post( + [appviewer=mAppViewerGetter()]{ appviewer->forceQuit(); }); } -- cgit v1.2.3 From 1b7fdac4689e29ec3f64c37f10b843a114d7805d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 15:09:24 -0400 Subject: Add frame_profile.lua to TP to known spot and take frame profile. frame_profile.lua teleports home when done. Further add frame_profile bash script to run the specified viewer, automatically log into said known spot, take frame profile and quit. The frame_profile bash script runs frame_profile_quit.lua. frame_profile_quit.lua is derived from frame_profile.lua, but different: it doesn't teleport either way because it assumes autologin to the target location, and because it logs out instead of returning home. --- indra/newview/scripts/lua/frame_profile.lua | 24 +++++++++++++++++++++++ indra/newview/scripts/lua/frame_profile_quit.lua | 25 ++++++++++++++++++++++++ scripts/perf/frame_profile | 4 ++++ 3 files changed, 53 insertions(+) create mode 100644 indra/newview/scripts/lua/frame_profile.lua create mode 100644 indra/newview/scripts/lua/frame_profile_quit.lua create mode 100755 scripts/perf/frame_profile diff --git a/indra/newview/scripts/lua/frame_profile.lua b/indra/newview/scripts/lua/frame_profile.lua new file mode 100644 index 0000000000..3c6353ff68 --- /dev/null +++ b/indra/newview/scripts/lua/frame_profile.lua @@ -0,0 +1,24 @@ +-- Trigger Develop -> Render Tests -> Frame Profile + +LLAgent = require 'LLAgent' +startup = require 'startup' +Timer = (require 'timers').Timer +UI = require 'UI' + +startup.wait('STATE_STARTED') + +-- teleport to http://maps.secondlife.com/secondlife/Bug%20Island/220/224/27 +print(LLAgent.teleport{regionname='Bug Island', x=220, y=224, z=27}) +Timer(10, 'wait') +LLAgent.setCamera{camera_pos={220, 224, 26}, camera_locked=true, + focus_pos ={228, 232, 26}, focus_locked=true} +Timer(1, 'wait') +-- This freezes the viewer for perceptible realtime +UI.popup:tip('starting Render Tests -> Frame Profile') +UI.call("Advanced.ClickRenderProfile") +Timer(1, 'wait') +LLAgent.removeCamParams() +LLAgent.setFollowCamActive(false) + +-- Home, James! +print(LLAgent.teleport('home')) diff --git a/indra/newview/scripts/lua/frame_profile_quit.lua b/indra/newview/scripts/lua/frame_profile_quit.lua new file mode 100644 index 0000000000..e3177a3f67 --- /dev/null +++ b/indra/newview/scripts/lua/frame_profile_quit.lua @@ -0,0 +1,25 @@ +-- Trigger Develop -> Render Tests -> Frame Profile and quit + +LLAgent = require 'LLAgent' +logout = require 'logout' +startup = require 'startup' +Timer = (require 'timers').Timer +UI = require 'UI' + +startup.wait('STATE_STARTED') + +-- Assume we logged into http://maps.secondlife.com/secondlife/Bug%20Island/220/224/27 +-- (see frame_profile bash script) +Timer(10, 'wait') +LLAgent.setCamera{camera_pos={220, 224, 26}, camera_locked=true, + focus_pos ={228, 232, 26}, focus_locked=true} +Timer(1, 'wait') +-- This freezes the viewer for perceptible realtime +UI.popup:tip('starting Render Tests -> Frame Profile') +UI.call("Advanced.ClickRenderProfile") +Timer(1, 'wait') +LLAgent.removeCamParams() +LLAgent.setFollowCamActive(false) + +-- done +logout() diff --git a/scripts/perf/frame_profile b/scripts/perf/frame_profile new file mode 100755 index 0000000000..75bf0a3105 --- /dev/null +++ b/scripts/perf/frame_profile @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +"$1" --autologin --luafile frame_profile_quit.lua \ + http://maps.secondlife.com/secondlife/Bug%20Island/220/224/27 -- cgit v1.2.3 From d6f3f20af6cccf53746cbf1fdf39bc4e235c4f0d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 16:56:05 -0400 Subject: Convenience tweak for passing a Mac "Second Life Mumble.app" bundle --- scripts/perf/frame_profile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/perf/frame_profile b/scripts/perf/frame_profile index 75bf0a3105..0a4e0a74ff 100755 --- a/scripts/perf/frame_profile +++ b/scripts/perf/frame_profile @@ -1,4 +1,10 @@ #!/usr/bin/env bash -"$1" --autologin --luafile frame_profile_quit.lua \ +exe="$1" +if [[ "$OSTYPE" == darwin* && -d "$exe" && "$exe" == *.app ]] +then + exe="$(ls "$exe/Contents/MacOS/Second Life "*)" +fi + +"$exe" --autologin --luafile frame_profile_quit.lua \ http://maps.secondlife.com/secondlife/Bug%20Island/220/224/27 -- cgit v1.2.3